Class: RTKIT::DoseVolume

Inherits:
Series
  • Object
show all
Includes:
ImageParent
Defined in:
lib/rtkit/dose_volume.rb

Overview

Contains the DICOM data and methods related to a pixel dose volume.

Inheritance

  • DoseVolume 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 included from ImageParent

#slice_spacing, #update_image_position

Methods inherited from Series

#image_modality?, #uid

Constructor Details

#initialize(sop_uid, frame, series, options = {}) ⇒ DoseVolume

Creates a new Volume instance. The SOP Instance UID tag value is used to uniquely identify a volume.

Parameters

  • sop_uid – The SOP Instance UID string.

  • frame – The Frame instance that this DoseVolume belongs to.

  • series – The Series instance that this Image belongs to.

  • options – A hash of parameters.

Options

  • :sum – Boolean. If true, the DoseVolume will not be added as a (beam) volume to the parent RTDose.

Raises:

  • (ArgumentError)


60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/rtkit/dose_volume.rb', line 60

def initialize(sop_uid, frame, series, options={})
  raise ArgumentError, "Invalid argument 'sop_uid'. Expected String, got #{sop_uid.class}." unless sop_uid.is_a?(String)
  raise ArgumentError, "Invalid argument 'frame'. Expected Frame, got #{frame.class}." unless frame.is_a?(Frame)
  raise ArgumentError, "Invalid argument 'series'. Expected Series, got #{series.class}." unless series.is_a?(Series)
  raise ArgumentError, "Invalid argument 'series'. Expected Series to have an image related modality, got #{series.modality}." unless IMAGE_MODALITIES.include?(series.modality)
  # Pass attributes to Series initialization:
  super(series.uid, 'RTDOSE', series.study)
  # Key attributes:
  @sop_uid = sop_uid
  @frame = frame
  @dose_series = series
  # Default attributes:
  @images = Array.new
  @associated_images = Hash.new
  @image_positions = Hash.new
  # Register ourselves with the DoseSeries:
  @dose_series.add_volume(self) unless options[:sum]
  # Register ourselves with the study & frame:
  #@study.add_series(self)
  @frame.add_series(self)
end

Instance Attribute Details

#dcmObject (readonly)

The DObject instance of this dose Volume.



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

def dcm
  @dcm
end

#dose_seriesObject (readonly)

The DoseSeries that this dose Volume belongs to.



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

def dose_series
  @dose_series
end

#frameObject

The Frame (of Reference) which this DoseVolume belongs to.



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

def frame
  @frame
end

#imagesObject (readonly)

An array of dose pixel Image instances (frames) associated with this dose Volume.



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

def images
  @images
end

#scalingObject

The Dose Grid Scaling factor (float).



22
23
24
# File 'lib/rtkit/dose_volume.rb', line 22

def scaling
  @scaling
end

#sop_uidObject (readonly)

The SOP Instance UID.



24
25
26
# File 'lib/rtkit/dose_volume.rb', line 24

def sop_uid
  @sop_uid
end

Class Method Details

.load(dcm, series) ⇒ Object

Creates a new Volume instance by loading image information from the specified DICOM object. The volume object’s SOP Instance UID string value is used to uniquely identify a volume.

Parameters

  • dcm – An instance of a DICOM object (DObject).

  • series – The Series instance that this Volume belongs to.

Raises:

  • (ArgumentError)


34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/rtkit/dose_volume.rb', line 34

def self.load(dcm, series)
  raise ArgumentError, "Invalid argument 'dcm'. Expected DObject, got #{dcm.class}." unless dcm.is_a?(DICOM::DObject)
  raise ArgumentError, "Invalid argument 'series'. Expected Series, got #{series.class}." unless series.is_a?(Series)
  raise ArgumentError, "Invalid argument 'dcm'. Expected an image related modality, got #{dcm.value(MODALITY)}." unless IMAGE_MODALITIES.include?(dcm.value(MODALITY))
  sop_uid = dcm.value(SOP_UID)
  # Check if a Frame with the given UID already exists, and if not, create one:
  frame = series.study.patient.dataset.frame(dcm.value(FRAME_OF_REF)) || frame = series.study.patient.create_frame(dcm.value(FRAME_OF_REF), dcm.value(POS_REF_INDICATOR))
  # Create the RTDose instance:
  volume = self.new(sop_uid, frame, series)
  volume.add(dcm)
  return volume
end

Instance Method Details

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

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



84
85
86
87
88
# File 'lib/rtkit/dose_volume.rb', line 84

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

#add(dcm) ⇒ Object

Registers a DICOM Object to the dose Volume, and processes it to create (and reference) a (dose) Image instance (frame) linked to this dose Volume.

Raises:

  • (ArgumentError)


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/rtkit/dose_volume.rb', line 95

def add(dcm)
  raise ArgumentError, "Invalid argument 'dcm'. Expected DObject, got #{dcm.class}." unless dcm.is_a?(DICOM::DObject)
  @dcm = dcm
  self.scaling = dcm.value(DOSE_GRID_SCALING)
  pixels = dcm.narray
  rows = dcm.value(ROWS)
  cols = dcm.value(COLUMNS)
  image_position = dcm.value(IMAGE_POSITION).split("\\")
  pos_x = image_position[0].to_f
  pos_y = image_position[1].to_f
  frame_origin = image_position[2].to_f
  cosines = dcm.value(IMAGE_ORIENTATION).split("\\").collect {|val| val.to_f} if dcm.value(IMAGE_ORIENTATION)
  spacing = dcm.value(SPACING).split("\\")
  col_spacing = spacing[1].to_f
  row_spacing = spacing[0].to_f
  nr_frames = dcm.value(NR_FRAMES).to_i
  frame_offsets = dcm.value(GRID_FRAME_OFFSETS).split("\\").collect {|value| value.to_f}
  sop_uids = RTKIT.sop_uids(nr_frames)
  # Iterate each frame and create dose images:
  nr_frames.times do |i|
    # Create an Image instance (using an arbitrary UID, as individual dose frames don't really have UIDs in DICOM):
    img = Image.new(sop_uids[i], self)
    # Fill in image information:
    img.columns = cols
    img.rows = rows
    img.pos_x = pos_x
    img.pos_y = pos_y
    img.pos_slice = frame_origin + frame_offsets[i]
    img.col_spacing = col_spacing
    img.row_spacing = row_spacing
    img.cosines = cosines
    # Fill in the pixel frame data:
    img.narray = pixels[i, true, true]
  end
end

#add_image(image) ⇒ Object

Adds an Image to this Volume.

Raises:

  • (ArgumentError)


133
134
135
136
137
138
# File 'lib/rtkit/dose_volume.rb', line 133

def add_image(image)
  raise ArgumentError, "Invalid argument 'image'. Expected Image, got #{image.class}." unless image.is_a?(Image)
  @images << image unless @associated_images[image.uid]
  @associated_images[image.uid] = image
  @image_positions[image.pos_slice] = image
end

#bin_volume(options = {}) ⇒ Object

Creates a binary volume object consisting of a series of binary (dose thresholded) images, extracted from this dose volume. Returns a BinVolume instance with binary image references equal to the number of dose images defined for this DoseVolume.

Notes

  • Even though listed as optional parameters, at least one of the :min and :max options must be specified in order to construct a valid binary volume.

Parameters

  • options – A hash of parameters.

Options

  • :min – Float. The lower dose threshold for dose elements to be included in the resulting dose bin volume.

  • :max – Float. The upper dose threshold for dose elements to be included in the resulting dose bin volume.

  • :volume – By default the BinVolume is created against the images of this DoseVolume. Optionally, an ImageSeries used by the ROI’s of this Study can be specified.

Raises:

  • (ArgumentError)


160
161
162
163
164
# File 'lib/rtkit/dose_volume.rb', line 160

def bin_volume(options={})
  raise ArgumentError, "Need at least one dose limit parameter. Neither :min nor :max was specified." unless options[:min] or options[:max]
  volume = options[:volume] || self
  return BinVolume.from_dose(self, options[:min], options[:max], volume)
end

#distribution(roi = nil) ⇒ Object

Returns the dose distribution for a specified ROI (or entire volume) and a specified beam (or all beams).

Parameters

  • roi – A specific ROI for which to evalute the dose in (if omitted, the entire volume is evaluted).

Raises:

  • (ArgumentError)


173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/rtkit/dose_volume.rb', line 173

def distribution(roi=nil)
  raise ArgumentError, "Invalid argument 'roi'. Expected ROI, got #{roi.class}." if roi && !roi.is_a?(ROI)
  raise ArgumentError, "Invalid argument 'roi'. The specified ROI does not have the same StructureSet parent as this DoseVolume." if roi && roi.struct != @dose_series.plan.struct
  if roi
    # Extract a binary volume from the ROI, based on this DoseVolume:
    bin_vol = roi.bin_volume(self)
  else
    # Create a binary volume which marks the entire dose volume:
    bin_vol = BinVolume.from_volume(self)
  end
  # Create a DoseDistribution from the BinVolume:
  dose_distribution = DoseDistribution.create(bin_vol)
end

#dose_arrObject

Returns the 3D dose pixel NArray retrieved from the #narray method, multiplied with the scaling coefficient, which in effect yields a 3D dose array.



191
192
193
194
# File 'lib/rtkit/dose_volume.rb', line 191

def dose_arr
  # Convert integer array to float array and multiply:
  return narray.to_type(4) * @scaling
end

#hashObject

Generates a Fixnum hash value for this instance.



198
199
200
# File 'lib/rtkit/dose_volume.rb', line 198

def hash
  state.hash
end

#image(*args) ⇒ Object

Returns the Image 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 Image instance associated with the Volume is returned.

Parameters

  • uid_or_pos – String/Float. The value of the SOP Instance UID element or the image position.

Raises:

  • (ArgumentError)


210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/rtkit/dose_volume.rb', line 210

def image(*args)
  raise ArgumentError, "Expected one or none arguments, got #{args.length}." unless [0, 1].include?(args.length)
  if args.length == 1
    if args.first.is_a?(Float)
      # Presumably an image position:
      return @image_positions[args.first]
    else
      # Presumably a uid string:
      return @associated_images[args.first && args.first.to_s]
    end
  else
    # No argument used, therefore we return the first Image instance:
    return @images.first
  end
end

#narrayObject

Builds a 3D dose pixel NArray from the dose images belonging to this DoseVolume. The array has shape [frames, columns, rows] and contains pixel values. To convert to dose values, the array must be multiplied with the scaling attribute.



231
232
233
234
235
236
237
238
239
# File 'lib/rtkit/dose_volume.rb', line 231

def narray
  if @images.length > 0
    narr = NArray.int(@images.length, @images.first.columns, @images.first.rows)
    @images.each_index do |i|
      narr[i, true, true] = @images[i].narray
    end
    return narr
  end
end

#to_dose_volumeObject

Returns self.



253
254
255
# File 'lib/rtkit/dose_volume.rb', line 253

def to_dose_volume
  self
end