Class: RTKIT::Contour

Inherits:
Object
  • Object
show all
Defined in:
lib/rtkit/contour.rb

Overview

Contains DICOM data and methods related to a Contour. A set of Contours in a set of Slices defines a ROI.

Relations

  • The Contour belongs to a Slice.

  • A Contour has many Coordinates.

Resources

  • ROI Contour Module: PS 3.3, C.8.8.6

  • Patient Based Coordinate System: PS 3.3, C.7.6.2.1.1

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(slice, options = {}) ⇒ Contour

Creates a new Contour instance.

Parameters

  • slice – The Slice instance that this Contour belongs to.

  • options – A hash of parameters.

Options

  • :number – Integer. The Contour Number.

  • :type – String. The Contour Geometric Type. Defaults to ‘CLOSED_PLANAR’.

Raises:

  • (ArgumentError)


89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/rtkit/contour.rb', line 89

def initialize(slice, options={})
  raise ArgumentError, "Invalid argument 'slice'. Expected Slice, got #{slice.class}." unless slice.is_a?(Slice)
  raise ArgumentError, "Invalid option :number. Expected Integer, got #{options[:number].class}." if options[:number] && !options[:number].is_a?(Integer)
  raise ArgumentError, "Invalid option :type. Expected String, got #{options[:type].class}." if options[:type] && !options[:type].is_a?(String)
  # Key attributes:
  @coordinates = Array.new
  @slice = slice
  @type = options[:type] || 'CLOSED_PLANAR'
  @number = options[:number] # don't need a default value for this attribute
  # Register ourselves with the Slice:
  @slice.add_contour(self)
end

Instance Attribute Details

#coordinatesObject (readonly)

An array of Coordinates (x,y,z - triplets).



19
20
21
# File 'lib/rtkit/contour.rb', line 19

def coordinates
  @coordinates
end

#numberObject (readonly)

Contour Number.



21
22
23
# File 'lib/rtkit/contour.rb', line 21

def number
  @number
end

#sliceObject (readonly)

The Slice that the Contour belongs to.



23
24
25
# File 'lib/rtkit/contour.rb', line 23

def slice
  @slice
end

#typeObject (readonly)

Contour Geometric Type.



25
26
27
# File 'lib/rtkit/contour.rb', line 25

def type
  @type
end

Class Method Details

.create_from_coordinates(x, y, z, slice) ⇒ Object

Creates a new Contour instance from x, y and z coordinate arrays. This method also creates and connects any child Coordinates as indicated by the coordinate arrays. Returns the Contour instance.

Parameters

  • x – An array of x coordinates (Floats).

  • y – An array of y coordinates (Floats).

  • z – An array of z coordinates (Floats).

  • slice – The Slice instance that this Contour belongs to.

Raises:

  • (ArgumentError)


38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/rtkit/contour.rb', line 38

def self.create_from_coordinates(x, y, z, slice)
  raise ArgumentError, "Invalid argument 'x'. Expected Array, got #{x.class}." unless x.is_a?(Array)
  raise ArgumentError, "Invalid argument 'y'. Expected Array, got #{y.class}." unless y.is_a?(Array)
  raise ArgumentError, "Invalid argument 'z'. Expected Array, got #{z.class}." unless z.is_a?(Array)
  raise ArgumentError, "Invalid argument 'slice'. Expected Slice, got #{slice.class}." unless slice.is_a?(Slice)
  raise ArgumentError, "The coordinate arrays are of unequal length [#{x.length}, #{y.length}, #{z.length}]." unless [x.length, y.length, z.length].uniq.length == 1
  number = slice.roi.num_contours + 1
  # Create the Contour:
  c = self.new(slice, :number => number)
  # Create the Coordinates belonging to this Contour:
  x.each_index do |i|
    Coordinate.new(x[i], y[i], z[i], c)
  end
  return c
end

.create_from_item(contour_item, slice) ⇒ Object

Creates a new Contour instance from a contour item. This method also creates and connects any Coordinates as indicated by the item. Returns the Contour instance.

Parameters

  • contour_item – An array of contour items from the Contour Sequence in ROI Contour Sequence, belonging to the same slice.

  • slice – The Slice instance that this Contour belongs to.

Raises:

  • (ArgumentError)


63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/rtkit/contour.rb', line 63

def self.create_from_item(contour_item, slice)
  raise ArgumentError, "Invalid argument 'contour_item'. Expected Item, got #{contour_item.class}." unless contour_item.is_a?(DICOM::Item)
  raise ArgumentError, "Invalid argument 'slice'. Expected Slice, got #{slice.class}." unless slice.is_a?(Slice)
  raise ArgumentError, "Invalid argument 'contour_item'. The specified Item does not contain a Contour Data Value (Element '3006,0050')." unless contour_item.value(CONTOUR_DATA)
  number = (contour_item.value(CONTOUR_NUMBER) ? contour_item.value(CONTOUR_NUMBER).to_i : nil)
  type = contour_item.value(CONTOUR_GEO_TYPE)
  #size = contour_item.value(NR_CONTOUR_POINTS) # May be used for QA of the content of the item, but not needed in the Contour object.
  # Create the Contour:
  c = self.new(slice, :type => type, :number => number)
  # Create the Coordinates belonging to this Contour:
  c.create_coordinates(contour_item.value(CONTOUR_DATA))
  return c
end

Instance Method Details

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

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



104
105
106
107
108
# File 'lib/rtkit/contour.rb', line 104

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

#add_coordinate(coordinate) ⇒ Object

Adds a Coordinate instance to this Contour.

Raises:

  • (ArgumentError)


114
115
116
117
# File 'lib/rtkit/contour.rb', line 114

def add_coordinate(coordinate)
  raise ArgumentError, "Invalid argument 'coordinate'. Expected Coordinate, got #{coordinate.class}." unless coordinate.is_a?(Coordinate)
  @coordinates << coordinate unless @coordinates.include?(coordinate)
end

#contour_dataObject

Returns all Coordinates of this Contour, packed to a string in the format used in the Contour Data DICOM Element (3006,0050). Returns an empty string if the Contour contains no coordinates.



123
124
125
126
# File 'lib/rtkit/contour.rb', line 123

def contour_data
  x, y, z = coords
  return [x, y, z].transpose.flatten.join("\\")
end

#coordsObject

Returns all Coordinates of this Contour, in arrays of x, y and z coordinates.



130
131
132
133
134
135
136
137
138
# File 'lib/rtkit/contour.rb', line 130

def coords
  x, y, z = Array.new, Array.new, Array.new
  @coordinates.each do |coord|
    x << coord.x
    y << coord.y
    z << coord.z
  end
  return x, y, z
end

#create_coordinates(contour_data) ⇒ Object

Creates and connects Coordinate instances with this Contour instance by processing the value of the Contour Data element.

Parameters

  • contour_data – The value of the Contour Data Element (A String of backslash-separated of xyz coordinate triplets).

Raises:

  • (ArgumentError)


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/rtkit/contour.rb', line 147

def create_coordinates(contour_data)
  raise ArgumentError, "Invalid argument 'contour_data'. Expected String (or nil), got #{contour_data.class}." unless [String, NilClass].include?(contour_data.class)
  if contour_data && contour_data != ""
    # Split the number strings, sperated by a '\', into an array:
    string_values = contour_data.split("\\")
    size = string_values.length/3
    # Extract every third value of the string array as x, y, and z, respectively, and collect them as floats instead of strings:
    x = string_values.values_at(*(Array.new(size){|i| i*3    })).collect{|val| val.to_f}
    y = string_values.values_at(*(Array.new(size){|i| i*3+1})).collect{|val| val.to_f}
    z = string_values.values_at(*(Array.new(size){|i| i*3+2})).collect{|val| val.to_f}
    x.each_index do |i|
      Coordinate.new(x[i], y[i], z[i], self)
    end
  end
end

#hashObject

Generates a Fixnum hash value for this instance.



165
166
167
# File 'lib/rtkit/contour.rb', line 165

def hash
  state.hash
end

#to_contourObject

Returns self.



181
182
183
# File 'lib/rtkit/contour.rb', line 181

def to_contour
  self
end

#to_itemObject

Creates and returns a Contour Sequence Item from the attributes of the Contour.



187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/rtkit/contour.rb', line 187

def to_item
  # FIXME: We need to decide on how to principally handle the situation when an image series has not been
  # loaded, and how to set up the ROI slices. A possible solution is to create Image instances if they hasn't been loaded.
  item = DICOM::Item.new
  item.add(DICOM::Sequence.new(CONTOUR_IMAGE_SQ))
  item[CONTOUR_IMAGE_SQ].add_item
  item[CONTOUR_IMAGE_SQ][0].add(DICOM::Element.new(REF_SOP_CLASS_UID, @slice.image ? @slice.image.series.class_uid : '1.2.840.10008.5.1.4.1.1.2')) # Deafult to CT if image ref. doesn't exist.
  item[CONTOUR_IMAGE_SQ][0].add(DICOM::Element.new(REF_SOP_UID, @slice.uid))
  item.add(DICOM::Element.new(CONTOUR_GEO_TYPE, @type))
  item.add(DICOM::Element.new(NR_CONTOUR_POINTS, @coordinates.length.to_s))
  item.add(DICOM::Element.new(CONTOUR_NUMBER, @number.to_s))
  item.add(DICOM::Element.new(CONTOUR_DATA, contour_data))
  return item
end