Class: RTKIT::StructureSet

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

Overview

The StructureSet class contains methods that are specific for this modality (RTSTRUCT).

Inheritance

  • StructureSet inherits all methods and attributes from the Series class.

Instance Attribute Summary collapse

Attributes inherited from Series

#class_uid, #date, #description, #modality, #series_uid, #study, #time

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Series

#image_modality?, #uid

Constructor Details

#initialize(sop_uid, image_series, options = {}) ⇒ StructureSet

Creates a new StructureSet instance.

Parameters

  • sop_uid – The SOP Instance UID string.

  • image_series – An Image Series that this StructureSet belongs to.

  • options – A hash of parameters.

Options

  • :date – String. The Series Date (DICOM tag ‘0008,0021’).

  • :time – String. The Series Time (DICOM tag ‘0008,0031’).

  • :description – String. The Series Description (DICOM tag ‘0008,103E’).

  • :series_uid – String. The Series Instance UID (DICOM tag ‘0020,000E’).

Raises:

  • (ArgumentError)


94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/rtkit/structure_set.rb', line 94

def initialize(sop_uid, image_series, options={})
  raise ArgumentError, "Invalid argument 'sop_uid'. Expected String, got #{sop_uid.class}." unless sop_uid.is_a?(String)
  raise ArgumentError, "Invalid argument 'image_series'. Expected ImageSeries, got #{image_series.class}." unless image_series.is_a?(ImageSeries)
  # Pass attributes to Series initialization:

  options[:class_uid] = '1.2.840.10008.5.1.4.1.1.481.3' # RT Structure Set Storage

  # Get a randomized Series UID unless it has been defined in the options hash:

  series_uid = options[:series_uid] || RTKIT.series_uid
  super(series_uid, 'RTSTRUCT', image_series.study, options)
  @sop_uid = sop_uid
  # Default attributes:

  @image_series = Array.new
  @rois = Array.new
  @plans = Array.new
  @associated_plans = Hash.new
  # Register ourselves with the ImageSeries:

  image_series.add_struct(self)
  @image_series << image_series
end

Instance Attribute Details

#dcmObject (readonly)

The original DObject instance of the StructureSet.



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

def dcm
  @dcm
end

#image_seriesObject (readonly)

An array of ImageSeries that this Structure Set Series references has ROIs defined for.



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

def image_series
  @image_series
end

#plansObject (readonly)

An array of RTPlans associated with this Structure Set Series.



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

def plans
  @plans
end

#roisObject (readonly)

An array of ROIs belonging to this structure set.



18
19
20
# File 'lib/rtkit/structure_set.rb', line 18

def rois
  @rois
end

#sop_uidObject (readonly)

The SOP Instance UID.



20
21
22
# File 'lib/rtkit/structure_set.rb', line 20

def sop_uid
  @sop_uid
end

Class Method Details

.image_series(dcm, study) ⇒ Object

Identifies the ImageSeries that the StructureSet object belongs to. If the referenced instances (ImageSeries & Frame) does not exist, they are created by this method.

Raises:

  • (ArgumentError)


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/rtkit/structure_set.rb', line 53

def self.image_series(dcm, study)
  raise ArgumentError, "Invalid argument 'dcm'. Expected DObject, got #{dcm.class}." unless dcm.is_a?(DICOM::DObject)
  raise ArgumentError, "Invalid argument 'study'. Expected Study, got #{study.class}." unless study.is_a?(Study)
  raise ArgumentError, "Invalid argument 'dcm'. Expected DObject with modality 'RTSTUCT', got #{dcm.value(MODALITY)}." unless dcm.value(MODALITY) == 'RTSTRUCT'
  # Extract the Referenced Frame UID:

  begin
    ref_frame_of_ref = dcm[REF_FRAME_OF_REF_SQ][0].value(FRAME_OF_REF)
  rescue
    ref_frame_of_ref = nil
  end
  # Extract referenced Image Series UID:

  begin
    ref_series_uid = dcm[REF_FRAME_OF_REF_SQ][0][RT_REF_STUDY_SQ][0][RT_REF_SERIES_SQ][0].value(SERIES_UID)
  rescue
    ref_series_uid = nil
  end
  # Create the Frame if it doesn't exist:

  f = study.patient.dataset.frame(ref_frame_of_ref)
  f = Frame.new(ref_frame_of_ref, study.patient) unless f
  # Create the ImageSeries if it doesnt exist:

  is = f.series(ref_series_uid)
  is = ImageSeries.new(ref_series_uid, 'CT', f, study) unless is
  study.add_series(is)
  return is
end

.load(dcm, study) ⇒ Object

Creates a new StructureSet instance by loading the relevant information from the specified DICOM object. The SOP Instance UID string value is used to uniquely identify a StructureSet instance.

Parameters

  • dcm – An instance of a DICOM object (DICOM::DObject) with modality ‘RTSTRUCT’.

  • study – The Study instance that this StructureSet belongs to.

Raises:

  • (ArgumentError)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/rtkit/structure_set.rb', line 30

def self.load(dcm, study)
  raise ArgumentError, "Invalid argument 'dcm'. Expected DObject, got #{dcm.class}." unless dcm.is_a?(DICOM::DObject)
  raise ArgumentError, "Invalid argument 'study'. Expected Study, got #{study.class}." unless study.is_a?(Study)
  raise ArgumentError, "Invalid argument 'dcm'. Expected DObject with modality 'RTSTUCT', got #{dcm.value(MODALITY)}." unless dcm.value(MODALITY) == 'RTSTRUCT'
  # Required attributes:

  sop_uid = dcm.value(SOP_UID)
  # Optional attributes:

  class_uid = dcm.value(SOP_CLASS)
  date = dcm.value(SERIES_DATE)
  time = dcm.value(SERIES_TIME)
  description = dcm.value(SERIES_DESCR)
  series_uid = dcm.value(SERIES_UID)
  # Get the corresponding image series:

  image_series = self.image_series(dcm, study)
  # Create the StructureSet instance:

  ss = self.new(sop_uid, image_series, :class_uid => class_uid, :date => date, :time => time, :description => description, :series_uid => series_uid)
  ss.add(dcm)
  return ss
end

Instance Method Details

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

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



115
116
117
118
119
# File 'lib/rtkit/structure_set.rb', line 115

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

#add(dcm) ⇒ Object

Registers a DICOM Object to the StructureSet, and processes it to create (and reference) the ROIs contained in the object.

Raises:

  • (ArgumentError)


126
127
128
129
130
# File 'lib/rtkit/structure_set.rb', line 126

def add(dcm)
  raise ArgumentError, "Invalid argument 'dcm'. Expected DObject, got #{dcm.class}." unless dcm.is_a?(DICOM::DObject)
  @dcm = dcm
  load_rois
end

#add_plan(plan) ⇒ Object

Adds a Plan Series to this StructureSet. Note: Intended for internal use in the library only.

Raises:

  • (ArgumentError)


135
136
137
138
139
# File 'lib/rtkit/structure_set.rb', line 135

def add_plan(plan)
  raise ArgumentError, "Invalid argument 'plan'. Expected Plan, got #{plan.class}." unless plan.is_a?(Plan)
  @plans << plan unless @associated_plans[plan.uid]
  @associated_plans[plan.uid] = plan
end

#add_roi(roi) ⇒ Object

Adds a ROI instance to this StructureSet.

Raises:

  • (ArgumentError)


143
144
145
146
# File 'lib/rtkit/structure_set.rb', line 143

def add_roi(roi)
  raise ArgumentError, "Invalid argument 'roi'. Expected ROI, got #{roi.class}." unless roi.is_a?(ROI)
  @rois << roi unless @rois.include?(roi)
end

#create_roi(frame, options = {}) ⇒ Object

Creates a ROI belonging to this StructureSet. Returns the created ROI.

Notes

  • The ROI is created without Slices, and these must be added after the ROI creation.

Parameters

  • frame – The Frame instance which the ROI will belong to.

  • options – A hash of parameters.

Options

  • :algorithm – String. The ROI Generation Algorithm. Defaults to ‘Automatic’.

  • :name – String. The ROI Name. Defaults to ‘RTKIT-VOLUME’.

  • :number – Integer. The ROI Number. Defaults to the first available ROI Number in the StructureSet.

  • :interpreter – String. The ROI Interpreter. Defaults to ‘RTKIT’.

  • :type – String. The ROI Interpreted Type. Defaults to ‘CONTROL’.

Raises:

  • (ArgumentError)


168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/rtkit/structure_set.rb', line 168

def create_roi(frame, options={})
  raise ArgumentError, "Expected Frame, got #{frame.class}." unless frame.is_a?(Frame)
  # Set values:

  algorithm = options[:algorithm] || 'Automatic'
  name = options[:name] || 'RTKIT-VOLUME'
  interpreter = options[:interpreter] || 'RTKIT'
  type = options[:type] || 'CONTROL'
  if options[:number]
    raise ArgumentError, "Expected Integer, got #{options[:number].class} for the option :number." unless options[:number].is_a?(Integer)
    raise ArgumentError, "The specified ROI Number (#{options[:roi_number]}) is already used by one of the existing ROIs (#{roi_numbers})." if roi_numbers.include?(options[:number])
    number = options[:number]
  else
    number = (roi_numbers.max ? roi_numbers.max + 1 : 1)
  end
  # Create ROI:

  roi = ROI.new(name, number, frame, self, :algorithm => algorithm, :name => name, :number => number, :interpreter => interpreter, :type => type)
  return roi
end

#hashObject

Generates a Fixnum hash value for this instance.



189
190
191
# File 'lib/rtkit/structure_set.rb', line 189

def hash
  state.hash
end

#plan(*args) ⇒ Object

Returns the Plan instance mathcing the specified SOP Instance UID (if an argument is used). If a specified UID doesn’t match, nil is returned. If no argument is passed, the first Plan instance associated with the StructureSet is returned.

Parameters

  • uid – String. The value of the SOP Instance UID element.

Raises:

  • (ArgumentError)


201
202
203
204
205
206
207
208
209
210
# File 'lib/rtkit/structure_set.rb', line 201

def plan(*args)
  raise ArgumentError, "Expected one or none arguments, got #{args.length}." unless [0, 1].include?(args.length)
  if args.length == 1
    raise ArgumentError, "Expected String (or nil), got #{args.first.class}." unless [String, NilClass].include?(args.first.class)
    return @associated_plans[args.first]
  else
    # No argument used, therefore we return the first Plan instance:

    return @plans.first
  end
end

#remove_roi(instance_or_number) ⇒ Object

Removes the ROI (specified by a ROI Number) from the Structure Set.

Parameters

  • instance_or_number – The ROI Instance (or ROI Number of the instance) to be removed.

Raises:

  • (ArgumentError)


218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/rtkit/structure_set.rb', line 218

def remove_roi(instance_or_number)
  raise ArgumentError, "Invalid argument 'instance_or_number'. Expected a ROI Instance or an Integer (ROI Number). Got #{instance_or_number.class}." unless [ROI, Integer].include?(instance_or_number.class)
  roi_instance = instance_or_number
  if instance_or_number.is_a?(Integer)
    roi_instance = roi(instance_or_number)
  end
  index = @rois.index(roi_instance)
  if index
    @rois.delete_at(index)
    roi_instance.remove_references
  end
end

#remove_roisObject

Removes all ROIs from the Structure Set.



233
234
235
236
237
238
# File 'lib/rtkit/structure_set.rb', line 233

def remove_rois
  @rois.each do |roi|
    roi.remove_references
  end
  @rois = Array.new
end

#roi(name_or_number) ⇒ Object

Returns a ROI that matches the specified number or name. Returns nil if no match is found.

Raises:

  • (ArgumentError)


243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/rtkit/structure_set.rb', line 243

def roi(name_or_number)
  raise ArgumentError, "Invalid argument 'name_or_number'. Expected String or Integer, got #{name_or_number.class}." unless [String, Integer, Fixnum].include?(name_or_number.class)
  if name_or_number.is_a?(String)
    @rois.each do |r|
      return r if r.name == name_or_number
    end
  else
    @rois.each do |r|
      return r if r.number == name_or_number
    end
  end
  return nil
end

#roi_namesObject

Returns the ROI Names assigned to the various structures present in the structure set. The names are returned in an array.



260
261
262
263
264
265
266
# File 'lib/rtkit/structure_set.rb', line 260

def roi_names
  names = Array.new
  @rois.each do |roi|
    names << roi.name
  end
  return names
end

#roi_numbersObject

Returns the ROI Numbers assigned to the various structures present in the structure set. The numbers are returned in an array.



271
272
273
274
275
276
277
# File 'lib/rtkit/structure_set.rb', line 271

def roi_numbers
  numbers = Array.new
  @rois.each do |roi|
    numbers << roi.number
  end
  return numbers
end

#rois_in_frame(uid) ⇒ Object

Returns all ROIs defined in this structure set that belongs to the specified Frame of Reference UID. Returns an empty array if no matching ROIs are found.

Raises:

  • (ArgumentError)


282
283
284
285
286
287
288
289
# File 'lib/rtkit/structure_set.rb', line 282

def rois_in_frame(uid)
  raise ArgumentError, "Expected String, got #{uid.class}." unless uid.is_a?(String)
  frame_rois = Array.new
  @rois.each do |roi|
    frame_rois << roi if roi.frame.uid == uid
  end
  return frame_rois
end

#set_colorsObject

Sets new color values for all ROIs belonging to the StructureSet. Color values will be selected in a way which attempts to make the ROI colors maximally different. The method uses a predefined list containing 54 colors, which means for the rare case of more than 24 ROIs, some will not be assigned a color. Obviously, the more ROIs to assign colors to, the more similar the color values will be.



297
298
299
300
301
302
303
304
305
306
# File 'lib/rtkit/structure_set.rb', line 297

def set_colors
  if @rois.length > 0
    # Determine colors:

    initialize_colors
    # Set colors:

    @rois.each_index do |i|
      @rois[i].color = @colors[i] if i < 24
    end
  end
end

#set_numbersObject

Sets new ROI Numbers to all ROIs belonging to the StructureSet. Numbers increase sequentially, starting at 1 for the first ROI.



311
312
313
314
315
# File 'lib/rtkit/structure_set.rb', line 311

def set_numbers
  @rois.each_with_index do |roi, i|
    roi.number = i + 1
  end
end

#to_dcmObject

Dumps the StructureSet instance to a DObject. This overwrites the dcm instance attribute. Returns the DObject instance.



321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/rtkit/structure_set.rb', line 321

def to_dcm
  # Use the original DICOM object as a starting point (keeping all non-sequence elements):

  #@dcm[REF_FRAME_OF_REF_SQ].delete_children

  @dcm[STRUCTURE_SET_ROI_SQ].delete_children
  @dcm[ROI_CONTOUR_SQ].delete_children
  @dcm[RT_ROI_OBS_SQ].delete_children
  # Create DICOM

  @rois.each do |roi|
    @dcm[STRUCTURE_SET_ROI_SQ].add_item(roi.ss_item)
    @dcm[ROI_CONTOUR_SQ].add_item(roi.contour_item)
    @dcm[RT_ROI_OBS_SQ].add_item(roi.obs_item)
  end
  return @dcm
end

#to_structure_setObject

Returns self.



338
339
340
# File 'lib/rtkit/structure_set.rb', line 338

def to_structure_set
  self
end

#write(path) ⇒ Object

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



344
345
346
347
# File 'lib/rtkit/structure_set.rb', line 344

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