Class: RTKIT::PixelData
- Inherits:
-
Object
- Object
- RTKIT::PixelData
- Defined in:
- lib/rtkit/pixel_data.rb
Overview
A collection of methods for dealing with pixel data in both 2D images and 3D volumes.
Inheritance
These methods are available to instances of the following classes:
-
Image
-
Dose
Instance Method Summary collapse
-
#coordinates_from_indices(column_indices, row_indices) ⇒ Object
Converts from two NArrays of image X & Y indices to physical coordinates X, Y & Z (in mm).
-
#coordinates_to_indices(x, y, z) ⇒ Object
Converts from three (float) NArrays of X, Y & Z physical coordinates (in mm) to image slice indices X & Y.
-
#draw_lines(column_indices, row_indices, image, value) ⇒ Object
Fills the provided image array with lines of a specified value, based on two vectors of column and row indices.
-
#flood_fill(col, row, image, fill_value) ⇒ Object
Iterative, queue based flood fill algorithm.
-
#indices_general_to_specific(indices, n_cols) ⇒ Object
Converts general image indices to specific column and row indices based on the provided image indices and the number of columns in the image.
-
#indices_specific_to_general(column_indices, row_indices, n_cols) ⇒ Object
Converts specific x and y indices to general image indices based on the provided specific indices and x size of the NArray image.
-
#print_img(narr = @narray) ⇒ Object
A convenience method for printing image information.
Instance Method Details
#coordinates_from_indices(column_indices, row_indices) ⇒ Object
Converts from two NArrays of image X & Y indices to physical coordinates X, Y & Z (in mm). The X, Y & Z coordinates are returned in three NArrays of equal size as the input index NArrays. The image coordinates are calculated using the direction cosines of the Image Orientation (Patient) element (0020,0037).
Notes
-
For details about Image orientation, refer to the DICOM standard: PS 3.3 C.7.6.2.1.1
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/rtkit/pixel_data.rb', line 21 def coordinates_from_indices(column_indices, row_indices) raise ArgumentError, "Invalid argument 'column_indices'. Expected NArray, got #{column_indices.class}." unless column_indices.is_a?(NArray) raise ArgumentError, "Invalid argument 'row_indices'. Expected NArray, got #{row_indices.class}." unless row_indices.is_a?(NArray) raise ArgumentError, "Invalid arguments. Expected NArrays of equal length, got #{column_indices.length} and #{row_indices.length}." unless column_indices.length == row_indices.length raise "Invalid attribute 'cosines'. Expected a 6 element Array, got #{cosines.class} #{cosines.length if cosines.is_a?(Array)}." unless cosines.is_a?(Array) && cosines.length == 6 raise "Invalid attribute 'pos_x'. Expected Float, got #{pos_x.class}." unless pos_x.is_a?(Float) raise "Invalid attribute 'pos_y'. Expected Float, got #{pos_y.class}." unless pos_y.is_a?(Float) raise "Invalid attribute 'pos_slice'. Expected Float, got #{pos_slice.class}." unless pos_slice.is_a?(Float) raise "Invalid attribute 'col_spacing'. Expected Float, got #{col_spacing.class}." unless col_spacing.is_a?(Float) raise "Invalid attribute 'row_spacing'. Expected Float, got #{row_spacing.class}." unless row_spacing.is_a?(Float) # Convert indices integers to floats: column_indices = column_indices.to_f row_indices = row_indices.to_f # Calculate the coordinates by multiplying indices with the direction cosines and applying the image offset: x = pos_x + (column_indices * col_spacing * cosines[0]) + (row_indices * row_spacing * cosines[3]) y = pos_y + (column_indices * col_spacing * cosines[1]) + (row_indices * row_spacing * cosines[4]) z = pos_slice + (column_indices * col_spacing * cosines[2]) + (row_indices * row_spacing * cosines[5]) return x, y, z end |
#coordinates_to_indices(x, y, z) ⇒ Object
Converts from three (float) NArrays of X, Y & Z physical coordinates (in mm) to image slice indices X & Y. The X & Y indices are returned in two NArrays of equal size as the input coordinate NArrays. The image indices are calculated using the direction cosines of the Image Orientation (Patient) element (0020,0037).
Notes
-
For details about Image orientation, refer to the DICOM standard: PS 3.3 C.7.6.2.1.1
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/rtkit/pixel_data.rb', line 49 def coordinates_to_indices(x, y, z) raise ArgumentError, "Invalid argument 'x'. Expected NArray, got #{x.class}." unless x.is_a?(NArray) raise ArgumentError, "Invalid argument 'y'. Expected NArray, got #{y.class}." unless y.is_a?(NArray) raise ArgumentError, "Invalid argument 'z'. Expected NArray, got #{z.class}." unless z.is_a?(NArray) raise ArgumentError, "Invalid arguments. Expected NArrays of equal length, got #{x.length}, #{y.length} and #{z.length}." unless [x.length, y.length, z.length].uniq.length == 1 raise "Invalid attribute 'cosines'. Expected a 6 element Array, got #{cosines.class} #{cosines.length if cosines.is_a?(Array)}." unless cosines.is_a?(Array) && cosines.length == 6 raise "Invalid attribute 'pos_x'. Expected Float, got #{pos_x.class}." unless pos_x.is_a?(Float) raise "Invalid attribute 'pos_y'. Expected Float, got #{pos_y.class}." unless pos_y.is_a?(Float) raise "Invalid attribute 'pos_slice'. Expected Float, got #{pos_slice.class}." unless pos_slice.is_a?(Float) raise "Invalid attribute 'col_spacing'. Expected Float, got #{col_spacing.class}." unless col_spacing.is_a?(Float) raise "Invalid attribute 'row_spacing'. Expected Float, got #{row_spacing.class}." unless row_spacing.is_a?(Float) # Calculate the indices by multiplying coordinates with the direction cosines and applying the image offset: column_indices = ((x-pos_x)/col_spacing*cosines[0] + (y-pos_y)/col_spacing*cosines[1] + (z-pos_slice)/col_spacing*cosines[2]).round row_indices = ((x-pos_x)/row_spacing*cosines[3] + (y-pos_y)/row_spacing*cosines[4] + (z-pos_slice)/row_spacing*cosines[5]).round return column_indices, row_indices end |
#draw_lines(column_indices, row_indices, image, value) ⇒ Object
Fills the provided image array with lines of a specified value, based on two vectors of column and row indices. The image is expected to be a (two-dimensional) NArray. Returns the processed image array.
70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/rtkit/pixel_data.rb', line 70 def draw_lines(column_indices, row_indices, image, value) raise ArgumentError, "Invalid argument 'column_indices'. Expected Array, got #{column_indices.class}." unless column_indices.is_a?(Array) raise ArgumentError, "Invalid argument 'row_indices'. Expected Array, got #{row_indices.class}." unless row_indices.is_a?(Array) raise ArgumentError, "Invalid arguments. Expected Arrays of equal length, got #{column_indices.length}, #{row_indices.length}." unless column_indices.length == row_indices.length raise ArgumentError, "Invalid argument 'image'. Expected NArray, got #{image.class}." unless image.is_a?(NArray) raise ArgumentError, "Invalid number of dimensions for argument 'image'. Expected 2, got #{image.shape.length}." unless image.shape.length == 2 raise ArgumentError, "Invalid argument 'value'. Expected Integer, got #{value.class}." unless value.is_a?(Integer) column_indices.each_index do |i| image = draw_line(column_indices[i-1], column_indices[i], row_indices[i-1], row_indices[i], image, value) end return image end |
#flood_fill(col, row, image, fill_value) ⇒ Object
Iterative, queue based flood fill algorithm. Replaces all pixels of a specific value that are contained by pixels of different value. The replacement value along with the starting coordinates are passed as parameters to this method. It seems a recursive method is not suited for Ruby due to its limited stack space (a problem in general for scripting languages).
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/rtkit/pixel_data.rb', line 88 def flood_fill(col, row, image, fill_value) existing_value = image[col, row] queue = Array.new queue.push([col, row]) until queue.empty? col, row = queue.shift if image[col, row] == existing_value west_col, west_row = ff_find_border(col, row, existing_value, :west, image) east_col, east_row = ff_find_border(col, row, existing_value, :east, image) # Fill the line between the two border pixels (i.e. not touching the border pixels): image[west_col..east_col, row] = fill_value q = west_col while q <= east_col [:north, :south].each do |direction| same_col, next_row = ff_neighbour(q, row, direction) begin queue.push([q, next_row]) if image[q, next_row] == existing_value rescue # Out of bounds. Do nothing. end end q, same_row = ff_neighbour(q, row, :east) end end end return image end |
#indices_general_to_specific(indices, n_cols) ⇒ Object
Converts general image indices to specific column and row indices based on the provided image indices and the number of columns in the image.
119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/rtkit/pixel_data.rb', line 119 def indices_general_to_specific(indices, n_cols) if indices.is_a?(Array) row_indices = indices.collect{|i| i/n_cols} column_indices = [indices, row_indices].transpose.collect{|i| i[0] - i[1] * n_cols} else # Assume Fixnum or NArray: row_indices = indices/n_cols # Values are automatically rounded down. column_indices = indices-row_indices*n_cols end return column_indices, row_indices end |
#indices_specific_to_general(column_indices, row_indices, n_cols) ⇒ Object
Converts specific x and y indices to general image indices based on the provided specific indices and x size of the NArray image.
133 134 135 136 137 138 139 140 141 142 |
# File 'lib/rtkit/pixel_data.rb', line 133 def indices_specific_to_general(column_indices, row_indices, n_cols) if column_indices.is_a?(Array) indices = Array.new column_indices.each_index {|i| indices << column_indices[i] + row_indices[i] * n_cols} return indices else # Assume Fixnum or NArray: return column_indices + row_indices * n_cols end end |
#print_img(narr = @narray) ⇒ Object
A convenience method for printing image information. NB! This has been used only for debugging, and will soon be removed.
147 148 149 150 151 152 |
# File 'lib/rtkit/pixel_data.rb', line 147 def print_img(narr=@narray) puts "Image dimensions: #{@columns}*#{@rows}" narr.shape[0].times do |i| puts narr[true, i].to_a.to_s end end |