Class: RTKIT::BinImage

Inherits:
PixelData show all
Defined in:
lib/rtkit/bin_image.rb

Overview

Contains the DICOM data and methods related to a binary image.

Inheritance

  • As the BinImage class inherits from the PixelData class, all PixelData methods are available to instances of BinImage.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from PixelData

#coordinates_from_indices, #coordinates_to_indices, #draw_lines, #flood_fill, #indices_general_to_specific, #indices_specific_to_general, #print_img

Constructor Details

#initialize(narray, image) ⇒ BinImage

Creates a new BinImage instance.

Parameters

  • narray – A binary, two-dimensional NArray.

  • image – The Image instance that this BinImage is associated with.

Raises:

  • (ArgumentError)


53
54
55
56
57
58
59
60
61
# File 'lib/rtkit/bin_image.rb', line 53

def initialize(narray, image)
  raise ArgumentError, "Invalid argument 'narray'. Expected NArray, got #{narray.class}." unless narray.is_a?(NArray)
  raise ArgumentError, "Invalid argument 'image'. Expected Image, got #{image.class}." unless image.is_a?(Image)
  raise ArgumentError, "Invalid argument 'narray'. Expected two-dimensional NArray, got #{narray.shape.length} dimensions." unless narray.shape.length == 2
  raise ArgumentError, "Invalid argument 'narray'. Expected NArray of element size 1 byte, got #{narray.element_size} bytes (per element)." unless narray.element_size == 1
  raise ArgumentError, "Invalid argument 'narray'. Expected binary NArray with max value 1, got #{narray.max} as max." if narray.max > 1
  self.narray = narray
  @image = image
end

Instance Attribute Details

#imageObject (readonly)

The BinImage’s Image reference.



12
13
14
# File 'lib/rtkit/bin_image.rb', line 12

def image
  @image
end

#narrayObject

The binary numerical image array.



14
15
16
# File 'lib/rtkit/bin_image.rb', line 14

def narray
  @narray
end

#narray_indicesObject (readonly)

A narray containing pixel indices.



16
17
18
# File 'lib/rtkit/bin_image.rb', line 16

def narray_indices
  @narray_indices
end

Class Method Details

.from_contours(contours, image, bin_volume) ⇒ Object

Creates a new BinImage instance from an array of contours. The BinVolume is typically defined from a ROI delineation against an image series, but it may also be applied to an rtdose ‘image’ series. Returns the BinVolume instance.

Parameters

  • contours – An array of contours from which to fill in a binary image.

  • image – The image that this BinImage instance will be based on.

  • bin_volume – The BinVolume instance that this bin_image belongs to.

Raises:

  • (ArgumentError)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/rtkit/bin_image.rb', line 29

def self.from_contours(contours, image, bin_volume)
  raise ArgumentError, "Invalid argument 'contours'. Expected Array, got #{contours.class}." unless contours.is_a?(Array)
  raise ArgumentError, "Invalid argument 'image'. Expected Image, got #{image.class}." unless image.is_a?(Image)
  raise ArgumentError, "Invalid argument 'bin_volume'. Expected BinVolume, got #{bin_volume.class}." unless bin_volume.is_a?(BinVolume)
  # Create the narray to be used:
  narr = NArray.byte(image.columns, image.rows)
  # Create the BinImage instance:
  bi = self.new(narr, image)
  # Delineate and fill for each contour:
  contours.each do |contour|
    x, y, z = contour.coords
    bi.add(image.binary_image(x, y, z))
  end
  bin_volume.add(bi)
  return bi
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?

Returns true if the argument is an instance with attributes equal to self.



65
66
67
68
69
# File 'lib/rtkit/bin_image.rb', line 65

def ==(other)
  if other.respond_to?(:to_bin_image)
    other.send(:state) == state
  end
end

#add(pixels) ⇒ Object

Adds a binary image array to the image array of this instance. Any segmented pixels in the new array (value = 1), is added (value set eql to 1) to the instance array.

Raises:

  • (ArgumentError)


76
77
78
79
80
81
82
# File 'lib/rtkit/bin_image.rb', line 76

def add(pixels)
  raise ArgumentError, "Invalid argument 'pixels'. Expected NArray, got #{pixels.class}." unless pixels.is_a?(NArray)
  raise ArgumentError, "Invalid argument 'pixels'. Expected NArray of element size 1 byte, got #{pixels.element_size} bytes (per element)." unless pixels.element_size == 1
  raise ArgumentError, "Invalid argument 'pixels'. Expected binary NArray with max value 1, got #{pixels.max} as max." if pixels.max > 1
  raise ArgumentError, "Invalid argument 'pixels'. Expected NArray to have same dimension as the instance array. Got #{pixels.shape}, expected #{@narray.shape}." unless pixels.shape == @narray.shape
  @narray[(pixels > 0).where] = 1
end

#area(type = true) ⇒ Object

Calculates the area defined by true/false (1/0) pixels. By default, the area of the true pixels are returned. Returns a float value, in units of millimeters squared.

Parameters

  • type – Boolean. Pixel type of interest.



92
93
94
95
96
97
98
99
100
# File 'lib/rtkit/bin_image.rb', line 92

def area(type=true)
  if type
    number = (@narray.eq 1).where.length
  else
    number = (@narray.eq 0).where.length
  end
  # Total area is number of pixels times the area per pixel:
  return number * @image.pixel_area
end

#col_spacingObject

Returns the col_spacing attribute from the Image reference. This attribute defines the physical distance (in millimeters) between columns in the pixel data (i.e. horisontal spacing).



105
106
107
# File 'lib/rtkit/bin_image.rb', line 105

def col_spacing
  return @image.col_spacing
end

#columnsObject

Returns the number of columns in the binary array.



111
112
113
# File 'lib/rtkit/bin_image.rb', line 111

def columns
  return @narray.shape[0]
end

#contour_imageObject

Applies the contour indices of this instance to an empty image (2D NArray) to create a ‘contour image’. Each separate contour is indicated by individual integers (e.g. 1,2,3 etc).



119
120
121
122
123
124
125
# File 'lib/rtkit/bin_image.rb', line 119

def contour_image
  img = NArray.byte(columns, rows)
  contour_indices.each_with_index do |contour, i|
    img[contour.indices] = i + 1
  end
  return img
end

#contour_indicesObject

Extracts the contour indices of the (filled) structures contained in the BinImage, by performing a contour tracing algorithm on the binary image. Returns an array filled with contour Selection instances, with length equal to the number of separated structures in the image.

Notes

Restrictions

  • Does not detect inner contour of hollow structures (holes).



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/rtkit/bin_image.rb', line 141

def contour_indices
  # Create the array to be returned:
  contours = Array.new
  # Initialize the contour extraction process if indicated:
  if @narray.segmented?
    # Initialize some variables used by the contour algorithm:
    initialize_contour_reorder_structures unless @reorder
    # The contour algorithm needs the image to be padded with a border of zero-pixels:
    original_image = @narray
    padded_image = NArray.byte(columns + 2, rows + 2)
    padded_image[1..-2, 1..-2] = @narray
    # Temporarily replace our instance image with the padded image:
    self.narray = padded_image
    # Get the contours:
    padded_contours = extract_contours
    # Convert from padded indices to proper indices:
    padded_contours.each do |padded_contour|
      padded_contour.shift_and_crop(-1, -1)
      contours << padded_contour
    end
    # Restore the instance image:
    self.narray = original_image
  end
  return contours
end

#cosinesObject

Returns the cosines attribute from the Image reference.



169
170
171
# File 'lib/rtkit/bin_image.rb', line 169

def cosines
  return @image.cosines
end

#hashObject

Generates a Fixnum hash value for this instance.



175
176
177
# File 'lib/rtkit/bin_image.rb', line 175

def hash
  state.hash
end

#pos_sliceObject

Returns the pos_slice attribute from the Image reference. This attribute defines the physical position (in millimeters) of the image slice. Returns nil if there is no Image reference.



195
196
197
# File 'lib/rtkit/bin_image.rb', line 195

def pos_slice
  return @image ? @image.pos_slice : nil
end

#pos_xObject

Returns the pos_x attribute from the Image reference. This attribute defines the physical position (in millimeters) of the first (left) column in the pixel data.



202
203
204
# File 'lib/rtkit/bin_image.rb', line 202

def pos_x
  return @image.pos_x
end

#pos_yObject

Returns the pos_y attribute from the Image reference. This attribute defines the physical position (in millimeters) of the first (top) row in the pixel data.



209
210
211
# File 'lib/rtkit/bin_image.rb', line 209

def pos_y
  return @image.pos_y
end

#row_spacingObject

Returns the row_spacing attribute from the Image reference. This attribute defines the physical distance (in millimeters) between rows in the pixel data (i.e. vertical spacing).



216
217
218
# File 'lib/rtkit/bin_image.rb', line 216

def row_spacing
  return @image.row_spacing
end

#rowsObject

Returns the number of rows in the binary array.



222
223
224
# File 'lib/rtkit/bin_image.rb', line 222

def rows
  return @narray.shape[1]
end

#selectionObject

Creates a Selection containing all ‘segmented’ indices of this instance, i.e. indices of all pixels with a value of 1. Returns the Selection instance.



230
231
232
233
234
# File 'lib/rtkit/bin_image.rb', line 230

def selection
  s = Selection.new(self)
  s.add_indices((@narray.eq 1).where)
  return s
end

#to_bin_imageObject

Returns self.



238
239
240
# File 'lib/rtkit/bin_image.rb', line 238

def to_bin_image
  self
end

#to_bin_volume(series, source = nil) ⇒ Object

Converts the BinImage instance to a single image BinVolume instance.

Parameters

  • series – The image series (e.g. ImageSeries or DoseVolume) which forms the reference data of the BinVolume.

  • source – The object which is the source of the binary (segmented) data (i.e. ROI or Dose/Hounsfield threshold).



249
250
251
# File 'lib/rtkit/bin_image.rb', line 249

def to_bin_volume(series, source=nil)
  bin_volume = BinVolume.new(series, :images => [self], :source => source)
end

#to_contours(slice) ⇒ Object

Creates an array of Contour instances from the segmentation of this BinImage. Returns the array of Contours. Returns an empty array if no Contours are created (empty BinImage).

Parameters

  • slice – A Slice instance which the Contours will be connected to.

Raises:

  • (ArgumentError)


261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/rtkit/bin_image.rb', line 261

def to_contours(slice)
  raise ArgumentError, "Invalid argument 'slice. Expected Slice, got #{slice.class}." unless slice.is_a?(Slice)
  contours = Array.new
  # Iterate the extracted collection of contour indices and convert to Contour instances:
  contour_indices.each do |contour|
    # Convert column and row indices to X, Y and Z coordinates:
    x, y, z = coordinates_from_indices(NArray.to_na(contour.columns), NArray.to_na(contour.rows))
    # Convert NArray to Array and round the coordinate floats:
    x = x.to_a.collect {|f| f.round(1)}
    y = y.to_a.collect {|f| f.round(1)}
    z = z.to_a.collect {|f| f.round(3)}
    contours << Contour.create_from_coordinates(x, y, z, slice)
  end
  return contours
end

#to_dcmObject

Dumps the BinImage instance to a DObject. This is achieved by copying the Elements of the DICOM object of the Image instance referenced by this BinImage, and replacing its pixel data with the NArray of this instance. Returns the DObject instance.



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/rtkit/bin_image.rb', line 282

def to_dcm
  # Use the original DICOM object as a starting point (keeping all non-sequence elements):
  # Note: Something like dcm.dup doesn't work here because that only performs a shallow copy on the DObject instance.
  dcm = DICOM::DObject.new
  @image.dcm.each_element do |element|
    # A bit of a hack to regenerate the DICOM elements:
    begin
      if element.value
        # ATM this fails for tags with integer values converted to a backslash-separated string:
        DICOM::Element.new(element.tag, element.value, :parent => dcm)
      else
        # Transfer the binary content as long as it is not the pixel data string:
        DICOM::Element.new(element.tag, element.bin, :encoded => true, :parent => dcm)
      end
    rescue
      DICOM::Element.new(element.tag, element.value.split("\\").collect {|val| val.to_i}, :parent => dcm) if element.value
    end
  end
  dcm.delete_group('0002')
  # Format the DICOM image ensure good contrast amongst the binary pixel values:
  # Window Center:
  DICOM::Element.new('0028,1050', '128', :parent => dcm)
  # Window Width:
  DICOM::Element.new('0028,1051', '256', :parent => dcm)
  # Rescale Intercept:
  DICOM::Element.new('0028,1052', '0', :parent => dcm)
  # Rescale Slope:
  DICOM::Element.new('0028,1053', '1', :parent => dcm)
  # Pixel data:
  dcm.pixels = @narray*255
  return dcm
end

#to_slice(roi) ⇒ Object

Creates a Slice instance from the segmentation of this BinImage. This method also creates and connects any child structures as indicated in the item (e.g. Contours). Returns the Slice instance.

Parameters

  • roi – A ROI instance which the Slice will be connected to.

Raises:

  • (ArgumentError)


323
324
325
326
327
328
329
330
# File 'lib/rtkit/bin_image.rb', line 323

def to_slice(roi)
  raise ArgumentError, "Invalid argument 'roi'. Expected ROI, got #{roi.class}." unless roi.is_a?(ROI)
  # Create the Slice:
  s = Slice.new(@image.uid, roi)
  # Create Contours:
  to_contours(s)
  return s
end

#write(path) ⇒ Object

Writes the BinImage to a DICOM file given by the specified file string.



334
335
336
337
# File 'lib/rtkit/bin_image.rb', line 334

def write(path)
  dcm = to_dcm
  dcm.write(path)
end