rtkit 0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +10 -0
- data/COPYING +674 -0
- data/README.rdoc +107 -0
- data/lib/rtkit.rb +68 -0
- data/lib/rtkit/beam.rb +346 -0
- data/lib/rtkit/bin_image.rb +578 -0
- data/lib/rtkit/bin_matcher.rb +241 -0
- data/lib/rtkit/bin_volume.rb +263 -0
- data/lib/rtkit/collimator.rb +157 -0
- data/lib/rtkit/collimator_setup.rb +143 -0
- data/lib/rtkit/constants.rb +215 -0
- data/lib/rtkit/contour.rb +213 -0
- data/lib/rtkit/control_point.rb +371 -0
- data/lib/rtkit/coordinate.rb +83 -0
- data/lib/rtkit/data_set.rb +264 -0
- data/lib/rtkit/dose.rb +70 -0
- data/lib/rtkit/dose_distribution.rb +206 -0
- data/lib/rtkit/dose_volume.rb +280 -0
- data/lib/rtkit/frame.rb +164 -0
- data/lib/rtkit/image.rb +372 -0
- data/lib/rtkit/image_series.rb +290 -0
- data/lib/rtkit/logging.rb +158 -0
- data/lib/rtkit/methods.rb +105 -0
- data/lib/rtkit/mixins/image_parent.rb +40 -0
- data/lib/rtkit/patient.rb +229 -0
- data/lib/rtkit/pixel_data.rb +237 -0
- data/lib/rtkit/plan.rb +259 -0
- data/lib/rtkit/plane.rb +165 -0
- data/lib/rtkit/roi.rb +388 -0
- data/lib/rtkit/rt_dose.rb +237 -0
- data/lib/rtkit/rt_image.rb +179 -0
- data/lib/rtkit/ruby_extensions.rb +165 -0
- data/lib/rtkit/selection.rb +189 -0
- data/lib/rtkit/series.rb +77 -0
- data/lib/rtkit/setup.rb +198 -0
- data/lib/rtkit/slice.rb +184 -0
- data/lib/rtkit/staple.rb +305 -0
- data/lib/rtkit/structure_set.rb +442 -0
- data/lib/rtkit/study.rb +214 -0
- data/lib/rtkit/variables.rb +23 -0
- data/lib/rtkit/version.rb +6 -0
- metadata +159 -0
@@ -0,0 +1,213 @@
|
|
1
|
+
module RTKIT
|
2
|
+
|
3
|
+
# Contains DICOM data and methods related to a Contour.
|
4
|
+
# A set of Contours in a set of Slices defines a ROI.
|
5
|
+
#
|
6
|
+
# === Relations
|
7
|
+
#
|
8
|
+
# * The Contour belongs to a Slice.
|
9
|
+
# * A Contour has many Coordinates.
|
10
|
+
#
|
11
|
+
# === Resources
|
12
|
+
#
|
13
|
+
# * ROI Contour Module: PS 3.3, C.8.8.6
|
14
|
+
# * Patient Based Coordinate System: PS 3.3, C.7.6.2.1.1
|
15
|
+
#
|
16
|
+
class Contour
|
17
|
+
|
18
|
+
# An array of Coordinates (x,y,z - triplets).
|
19
|
+
attr_reader :coordinates
|
20
|
+
# Contour Number.
|
21
|
+
attr_reader :number
|
22
|
+
# The Slice that the Contour belongs to.
|
23
|
+
attr_reader :slice
|
24
|
+
# Contour Geometric Type.
|
25
|
+
attr_reader :type
|
26
|
+
|
27
|
+
# Creates a new Contour instance from x, y and z coordinate arrays.
|
28
|
+
# This method also creates and connects any child Coordinates as indicated by the coordinate arrays.
|
29
|
+
# Returns the Contour instance.
|
30
|
+
#
|
31
|
+
# === Parameters
|
32
|
+
#
|
33
|
+
# * <tt>x</tt> -- An array of x coordinates (Floats).
|
34
|
+
# * <tt>y</tt> -- An array of y coordinates (Floats).
|
35
|
+
# * <tt>z</tt> -- An array of z coordinates (Floats).
|
36
|
+
# * <tt>slice</tt> -- The Slice instance that this Contour belongs to.
|
37
|
+
#
|
38
|
+
def self.create_from_coordinates(x, y, z, slice)
|
39
|
+
raise ArgumentError, "Invalid argument 'x'. Expected Array, got #{x.class}." unless x.is_a?(Array)
|
40
|
+
raise ArgumentError, "Invalid argument 'y'. Expected Array, got #{y.class}." unless y.is_a?(Array)
|
41
|
+
raise ArgumentError, "Invalid argument 'z'. Expected Array, got #{z.class}." unless z.is_a?(Array)
|
42
|
+
raise ArgumentError, "Invalid argument 'slice'. Expected Slice, got #{slice.class}." unless slice.is_a?(Slice)
|
43
|
+
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
|
44
|
+
number = slice.roi.num_contours + 1
|
45
|
+
# Create the Contour:
|
46
|
+
c = self.new(slice, :number => number)
|
47
|
+
# Create the Coordinates belonging to this Contour:
|
48
|
+
x.each_index do |i|
|
49
|
+
Coordinate.new(x[i], y[i], z[i], c)
|
50
|
+
end
|
51
|
+
return c
|
52
|
+
end
|
53
|
+
|
54
|
+
# Creates a new Contour instance from a contour item.
|
55
|
+
# This method also creates and connects any Coordinates as indicated by the item.
|
56
|
+
# Returns the Contour instance.
|
57
|
+
#
|
58
|
+
# === Parameters
|
59
|
+
#
|
60
|
+
# * <tt>contour_item</tt> -- An array of contour items from the Contour Sequence in ROI Contour Sequence, belonging to the same slice.
|
61
|
+
# * <tt>slice</tt> -- The Slice instance that this Contour belongs to.
|
62
|
+
#
|
63
|
+
def self.create_from_item(contour_item, slice)
|
64
|
+
raise ArgumentError, "Invalid argument 'contour_item'. Expected Item, got #{contour_item.class}." unless contour_item.is_a?(DICOM::Item)
|
65
|
+
raise ArgumentError, "Invalid argument 'slice'. Expected Slice, got #{slice.class}." unless slice.is_a?(Slice)
|
66
|
+
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)
|
67
|
+
number = (contour_item.value(CONTOUR_NUMBER) ? contour_item.value(CONTOUR_NUMBER).to_i : nil)
|
68
|
+
type = contour_item.value(CONTOUR_GEO_TYPE)
|
69
|
+
#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.
|
70
|
+
# Create the Contour:
|
71
|
+
c = self.new(slice, :type => type, :number => number)
|
72
|
+
# Create the Coordinates belonging to this Contour:
|
73
|
+
c.create_coordinates(contour_item.value(CONTOUR_DATA))
|
74
|
+
return c
|
75
|
+
end
|
76
|
+
|
77
|
+
# Creates a new Contour instance.
|
78
|
+
#
|
79
|
+
# === Parameters
|
80
|
+
#
|
81
|
+
# * <tt>slice</tt> -- The Slice instance that this Contour belongs to.
|
82
|
+
# * <tt>options</tt> -- A hash of parameters.
|
83
|
+
#
|
84
|
+
# === Options
|
85
|
+
#
|
86
|
+
# * <tt>:number</tt> -- Integer. The Contour Number.
|
87
|
+
# * <tt>:type</tt> -- String. The Contour Geometric Type. Defaults to 'CLOSED_PLANAR'.
|
88
|
+
#
|
89
|
+
def initialize(slice, options={})
|
90
|
+
raise ArgumentError, "Invalid argument 'slice'. Expected Slice, got #{slice.class}." unless slice.is_a?(Slice)
|
91
|
+
raise ArgumentError, "Invalid option :number. Expected Integer, got #{options[:number].class}." if options[:number] && !options[:number].is_a?(Integer)
|
92
|
+
raise ArgumentError, "Invalid option :type. Expected String, got #{options[:type].class}." if options[:type] && !options[:type].is_a?(String)
|
93
|
+
# Key attributes:
|
94
|
+
@coordinates = Array.new
|
95
|
+
@slice = slice
|
96
|
+
@type = options[:type] || 'CLOSED_PLANAR'
|
97
|
+
@number = options[:number] # don't need a default value for this attribute
|
98
|
+
# Register ourselves with the Slice:
|
99
|
+
@slice.add_contour(self)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns true if the argument is an instance with attributes equal to self.
|
103
|
+
#
|
104
|
+
def ==(other)
|
105
|
+
if other.respond_to?(:to_contour)
|
106
|
+
other.send(:state) == state
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
alias_method :eql?, :==
|
111
|
+
|
112
|
+
# Adds a Coordinate instance to this Contour.
|
113
|
+
#
|
114
|
+
def add_coordinate(coordinate)
|
115
|
+
raise ArgumentError, "Invalid argument 'coordinate'. Expected Coordinate, got #{coordinate.class}." unless coordinate.is_a?(Coordinate)
|
116
|
+
@coordinates << coordinate unless @coordinates.include?(coordinate)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns all Coordinates of this Contour, packed to a string in the format
|
120
|
+
# used in the Contour Data DICOM Element (3006,0050).
|
121
|
+
# Returns an empty string if the Contour contains no coordinates.
|
122
|
+
#
|
123
|
+
def contour_data
|
124
|
+
x, y, z = coords
|
125
|
+
return [x, y, z].transpose.flatten.join("\\")
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns all Coordinates of this Contour, in arrays of x, y and z coordinates.
|
129
|
+
#
|
130
|
+
def coords
|
131
|
+
x, y, z = Array.new, Array.new, Array.new
|
132
|
+
@coordinates.each do |coord|
|
133
|
+
x << coord.x
|
134
|
+
y << coord.y
|
135
|
+
z << coord.z
|
136
|
+
end
|
137
|
+
return x, y, z
|
138
|
+
end
|
139
|
+
|
140
|
+
# Creates and connects Coordinate instances with this Contour instance
|
141
|
+
# by processing the value of the Contour Data element.
|
142
|
+
#
|
143
|
+
# === Parameters
|
144
|
+
#
|
145
|
+
# * <tt>contour_data</tt> -- The value of the Contour Data Element (A String of backslash-separated of xyz coordinate triplets).
|
146
|
+
#
|
147
|
+
def create_coordinates(contour_data)
|
148
|
+
raise ArgumentError, "Invalid argument 'contour_data'. Expected String (or nil), got #{contour_data.class}." unless [String, NilClass].include?(contour_data.class)
|
149
|
+
if contour_data && contour_data != ""
|
150
|
+
# Split the number strings, sperated by a '\', into an array:
|
151
|
+
string_values = contour_data.split("\\")
|
152
|
+
size = string_values.length/3
|
153
|
+
# Extract every third value of the string array as x, y, and z, respectively, and collect them as floats instead of strings:
|
154
|
+
x = string_values.values_at(*(Array.new(size){|i| i*3 })).collect{|val| val.to_f}
|
155
|
+
y = string_values.values_at(*(Array.new(size){|i| i*3+1})).collect{|val| val.to_f}
|
156
|
+
z = string_values.values_at(*(Array.new(size){|i| i*3+2})).collect{|val| val.to_f}
|
157
|
+
x.each_index do |i|
|
158
|
+
Coordinate.new(x[i], y[i], z[i], self)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Generates a Fixnum hash value for this instance.
|
164
|
+
#
|
165
|
+
def hash
|
166
|
+
state.hash
|
167
|
+
end
|
168
|
+
|
169
|
+
=begin
|
170
|
+
# Returns the number of Coordinates (Corner Points) belonging to this Contour.
|
171
|
+
#
|
172
|
+
def length
|
173
|
+
@coordinates.length
|
174
|
+
end
|
175
|
+
|
176
|
+
alias_method :size, :length
|
177
|
+
=end
|
178
|
+
|
179
|
+
# Returns self.
|
180
|
+
#
|
181
|
+
def to_contour
|
182
|
+
self
|
183
|
+
end
|
184
|
+
|
185
|
+
# Creates and returns a Contour Sequence Item from the attributes of the Contour.
|
186
|
+
#
|
187
|
+
def to_item
|
188
|
+
# FIXME: We need to decide on how to principally handle the situation when an image series has not been
|
189
|
+
# loaded, and how to set up the ROI slices. A possible solution is to create Image instances if they hasn't been loaded.
|
190
|
+
item = DICOM::Item.new
|
191
|
+
item.add(DICOM::Sequence.new(CONTOUR_IMAGE_SQ))
|
192
|
+
item[CONTOUR_IMAGE_SQ].add_item
|
193
|
+
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.
|
194
|
+
item[CONTOUR_IMAGE_SQ][0].add(DICOM::Element.new(REF_SOP_UID, @slice.uid))
|
195
|
+
item.add(DICOM::Element.new(CONTOUR_GEO_TYPE, @type))
|
196
|
+
item.add(DICOM::Element.new(NR_CONTOUR_POINTS, @coordinates.length.to_s))
|
197
|
+
item.add(DICOM::Element.new(CONTOUR_NUMBER, @number.to_s))
|
198
|
+
item.add(DICOM::Element.new(CONTOUR_DATA, contour_data))
|
199
|
+
return item
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
|
206
|
+
# Returns the attributes of this instance in an array (for comparison purposes).
|
207
|
+
#
|
208
|
+
def state
|
209
|
+
[@coordinates, @number, @type]
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,371 @@
|
|
1
|
+
module RTKIT
|
2
|
+
|
3
|
+
# Contains DICOM data and methods related to a ControlPoint.
|
4
|
+
#
|
5
|
+
# === Notes
|
6
|
+
#
|
7
|
+
# The first control point in a given beam defines the intial setup, and contains
|
8
|
+
# all applicable parameters. The rest of the control points contains the parameters
|
9
|
+
# which change at any control point.
|
10
|
+
#
|
11
|
+
# === Relations
|
12
|
+
#
|
13
|
+
# * A Beam has many ControlPoints.
|
14
|
+
# * A ControlPoint has many Collimators.
|
15
|
+
#
|
16
|
+
class ControlPoint
|
17
|
+
|
18
|
+
|
19
|
+
# The Beam that the ControlPoint is defined in.
|
20
|
+
attr_reader :beam
|
21
|
+
# Collimator (beam limiting device) angle (float).
|
22
|
+
attr_reader :collimator_angle
|
23
|
+
# Collimator (beam limiting device) rotation direction (string).
|
24
|
+
attr_reader :collimator_direction
|
25
|
+
# An array containing the ControlPoint's collimators.
|
26
|
+
attr_reader :collimators
|
27
|
+
# Cumulative meterset weight (float).
|
28
|
+
attr_reader :cum_meterset
|
29
|
+
# Nominal beam energy (float).
|
30
|
+
attr_reader :energy
|
31
|
+
# Gantry angle (float).
|
32
|
+
attr_reader :gantry_angle
|
33
|
+
# Gantry rotation direction (string).
|
34
|
+
attr_reader :gantry_direction
|
35
|
+
# Control point index (integer).
|
36
|
+
attr_reader :index
|
37
|
+
# Isosenter position (a coordinate triplet of positions x, y, z).
|
38
|
+
attr_reader :iso
|
39
|
+
# Pedestal angle (float).
|
40
|
+
attr_reader :pedestal_angle
|
41
|
+
# Pedestal rotation direction (string).
|
42
|
+
attr_reader :pedestal_direction
|
43
|
+
# Source to surface distance (float).
|
44
|
+
attr_reader :ssd
|
45
|
+
# Table top angle (float).
|
46
|
+
attr_reader :table_top_angle
|
47
|
+
# Table top rotation direction (string).
|
48
|
+
attr_reader :table_top_direction
|
49
|
+
# Table top lateral position (float).
|
50
|
+
attr_reader :table_top_lateral
|
51
|
+
# Table top longitudinal position (float).
|
52
|
+
attr_reader :table_top_longitudinal
|
53
|
+
# Table top vertical position (float).
|
54
|
+
attr_reader :table_top_vertical
|
55
|
+
|
56
|
+
# Creates a new control point instance from a Control Point Sequence Item (from an RTPlan file).
|
57
|
+
# Returns the ControlPoint instance.
|
58
|
+
#
|
59
|
+
# === Parameters
|
60
|
+
#
|
61
|
+
# * <tt>cp_item</tt> -- An item from the Control Point Sequence in the DObject of a RTPlan file.
|
62
|
+
# * <tt>beam</tt> -- The Beam instance that this ControlPoint belongs to.
|
63
|
+
#
|
64
|
+
def self.create_from_item(cp_item, beam)
|
65
|
+
raise ArgumentError, "Invalid argument 'cp_item'. Expected DICOM::Item, got #{cp_item.class}." unless cp_item.is_a?(DICOM::Item)
|
66
|
+
raise ArgumentError, "Invalid argument 'beam'. Expected Beam, got #{beam.class}." unless beam.is_a?(Beam)
|
67
|
+
# Values from the Structure Set ROI Sequence Item:
|
68
|
+
index = cp_item.value(CONTROL_POINT_INDEX)
|
69
|
+
cum_meterset = cp_item.value(CUM_METERSET_WEIGHT)
|
70
|
+
# Create the Beam instance:
|
71
|
+
cp = self.new(index, cum_meterset, beam)
|
72
|
+
# Set optional values:
|
73
|
+
cp.collimator_angle = cp_item.value(COLL_ANGLE)
|
74
|
+
cp.collimator_direction = cp_item.value(COLL_DIRECTION)
|
75
|
+
cp.energy = cp_item.value(BEAM_ENERGY)
|
76
|
+
cp.gantry_angle = cp_item.value(GANTRY_ANGLE)
|
77
|
+
cp.gantry_direction = cp_item.value(GANTRY_DIRECTION)
|
78
|
+
cp.iso = cp_item.value(ISO_POS)
|
79
|
+
cp.pedestal_angle = cp_item.value(PEDESTAL_ANGLE)
|
80
|
+
cp.pedestal_direction = cp_item.value(PEDESTAL_DIRECTION)
|
81
|
+
cp.ssd = cp_item.value(SSD).to_f if cp_item.exists?(SSD)
|
82
|
+
cp.table_top_angle = cp_item.value(TABLE_TOP_ANGLE)
|
83
|
+
cp.table_top_direction = cp_item.value(TABLE_TOP_DIRECTION)
|
84
|
+
cp.table_top_lateral = cp_item.value(TABLE_TOP_LATERAL)
|
85
|
+
cp.table_top_vertical = cp_item.value(TABLE_TOP_VERTICAL)
|
86
|
+
cp.table_top_longitudinal = cp_item.value(TABLE_TOP_LONGITUDINAL)
|
87
|
+
# Iterate the beam limiting device position items and create Collimator instances:
|
88
|
+
if cp_item.exists?(COLL_POS_SQ)
|
89
|
+
cp_item[COLL_POS_SQ].each do |coll_item|
|
90
|
+
CollimatorSetup.create_from_item(coll_item, cp)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
return cp
|
94
|
+
end
|
95
|
+
|
96
|
+
# Creates a new ControlPoint instance.
|
97
|
+
#
|
98
|
+
# === Parameters
|
99
|
+
#
|
100
|
+
# * <tt>index</tt> -- Integer. The control point index.
|
101
|
+
# * <tt>meterset</tt> -- The control point's cumulative meterset weight.
|
102
|
+
# * <tt>beam</tt> -- The Beam instance that this ControlPoint belongs to.
|
103
|
+
# * <tt>options</tt> -- A hash of parameters.
|
104
|
+
#
|
105
|
+
# === Options
|
106
|
+
#
|
107
|
+
# * <tt>:type</tt> -- String. Beam type. Defaults to 'STATIC'.
|
108
|
+
# * <tt>:delivery_type</tt> -- String. Treatment delivery type. Defaults to 'TREATMENT'.
|
109
|
+
# * <tt>:description</tt> -- String. Beam description. Defaults to the 'name' attribute.
|
110
|
+
# * <tt>:rad_type</tt> -- String. Radiation type. Defaults to 'PHOTON'.
|
111
|
+
# * <tt>:sad</tt> -- Float. Source-axis distance. Defaults to 1000.0.
|
112
|
+
# * <tt>:unit</tt> -- String. The primary dosimeter unit. Defaults to 'MU'.
|
113
|
+
#
|
114
|
+
def initialize(index, cum_meterset, beam)
|
115
|
+
raise ArgumentError, "Invalid argument 'beam'. Expected Beam, got #{beam.class}." unless beam.is_a?(Beam)
|
116
|
+
# Set values:
|
117
|
+
@collimators = Array.new
|
118
|
+
@associated_collimators = Hash.new
|
119
|
+
self.index = index
|
120
|
+
self.cum_meterset = cum_meterset
|
121
|
+
# Set references:
|
122
|
+
@beam = beam
|
123
|
+
# Register ourselves with the Beam:
|
124
|
+
@beam.add_control_point(self)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns true if the argument is an instance with attributes equal to self.
|
128
|
+
#
|
129
|
+
def ==(other)
|
130
|
+
if other.respond_to?(:to_control_point)
|
131
|
+
other.send(:state) == state
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
alias_method :eql?, :==
|
136
|
+
|
137
|
+
# Adds a CollimatorSetup instance to this ControlPoint.
|
138
|
+
#
|
139
|
+
def add_collimator(coll)
|
140
|
+
raise ArgumentError, "Invalid argument 'coll'. Expected CollimatorSetup, got #{coll.class}." unless coll.is_a?(CollimatorSetup)
|
141
|
+
@collimators << coll unless @associated_collimators[coll.type]
|
142
|
+
@associated_collimators[coll.type] = coll
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns the CollimatorSetup instance mathcing the specified device type string (if an argument is used).
|
146
|
+
# If a specified type doesn't match, nil is returned.
|
147
|
+
# If no argument is passed, the first CollimatorSetup instance associated with the ControlPoint is returned.
|
148
|
+
#
|
149
|
+
# === Parameters
|
150
|
+
#
|
151
|
+
# * <tt>type</tt> -- String. The RT Beam Limiting Device Type value.
|
152
|
+
#
|
153
|
+
def collimator(*args)
|
154
|
+
raise ArgumentError, "Expected one or none arguments, got #{args.length}." unless [0, 1].include?(args.length)
|
155
|
+
if args.length == 1
|
156
|
+
return @associated_collimators[args.first && args.first.to_s]
|
157
|
+
else
|
158
|
+
# No argument used, therefore we return the first CollimatorSetup instance:
|
159
|
+
return @collimators.first
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Sets a new beam limiting device angle.
|
164
|
+
#
|
165
|
+
# === Parameters
|
166
|
+
#
|
167
|
+
# * <tt>value</tt> -- Float. The beam limiting device angle (300A,0120).
|
168
|
+
#
|
169
|
+
def collimator_angle=(value)
|
170
|
+
@collimator_angle = value && value.to_f
|
171
|
+
end
|
172
|
+
|
173
|
+
# Sets a new beam limiting device rotation direction.
|
174
|
+
#
|
175
|
+
# === Parameters
|
176
|
+
#
|
177
|
+
# * <tt>value</tt> -- String. The beam limiting device rotation direction (300A,0121).
|
178
|
+
#
|
179
|
+
def collimator_direction=(value)
|
180
|
+
@collimator_direction = value && value.to_s
|
181
|
+
end
|
182
|
+
|
183
|
+
# Sets a new cumulative meterset weight.
|
184
|
+
#
|
185
|
+
# === Parameters
|
186
|
+
#
|
187
|
+
# * <tt>value</tt> -- Float. The cumulative meterset weight (300A,0134).
|
188
|
+
#
|
189
|
+
def cum_meterset=(value)
|
190
|
+
raise ArgumentError, "Argument 'value' must be defined (got #{value.class})." unless value
|
191
|
+
@cum_meterset = value && value.to_f
|
192
|
+
end
|
193
|
+
|
194
|
+
# Sets a new nominal beam energy.
|
195
|
+
#
|
196
|
+
# === Parameters
|
197
|
+
#
|
198
|
+
# * <tt>value</tt> -- Float. The nominal beam energy (300A,0114).
|
199
|
+
#
|
200
|
+
def energy=(value)
|
201
|
+
@energy = value && value.to_f
|
202
|
+
end
|
203
|
+
|
204
|
+
# Sets a new gantry angle.
|
205
|
+
#
|
206
|
+
# === Parameters
|
207
|
+
#
|
208
|
+
# * <tt>value</tt> -- Float. The gantry angle (300A,011E).
|
209
|
+
#
|
210
|
+
def gantry_angle=(value)
|
211
|
+
@gantry_angle = value && value.to_f
|
212
|
+
end
|
213
|
+
|
214
|
+
# Sets a new gantry rotation direction.
|
215
|
+
#
|
216
|
+
# === Parameters
|
217
|
+
#
|
218
|
+
# * <tt>value</tt> -- String. The gantry rotation direction (300A,011F).
|
219
|
+
#
|
220
|
+
def gantry_direction=(value)
|
221
|
+
@gantry_direction = value && value.to_s
|
222
|
+
end
|
223
|
+
|
224
|
+
# Generates a Fixnum hash value for this instance.
|
225
|
+
#
|
226
|
+
def hash
|
227
|
+
state.hash
|
228
|
+
end
|
229
|
+
|
230
|
+
# Sets a new control point index.
|
231
|
+
#
|
232
|
+
# === Parameters
|
233
|
+
#
|
234
|
+
# * <tt>value</tt> -- Integer. The control point index (300A,0112).
|
235
|
+
#
|
236
|
+
def index=(value)
|
237
|
+
raise ArgumentError, "Argument 'value' must be defined (got #{value.class})." unless value
|
238
|
+
@index = value.to_i
|
239
|
+
end
|
240
|
+
|
241
|
+
# Sets a new isosenter position (a coordinate triplet of positions x, y, z).
|
242
|
+
#
|
243
|
+
# === Parameters
|
244
|
+
#
|
245
|
+
# * <tt>value</tt> -- Coordinate/String. The isocenter position (300A,0112).
|
246
|
+
#
|
247
|
+
def iso=(value)
|
248
|
+
@iso = value && value.to_coordinate
|
249
|
+
end
|
250
|
+
|
251
|
+
# Sets a new patient support angle.
|
252
|
+
#
|
253
|
+
# === Parameters
|
254
|
+
#
|
255
|
+
# * <tt>value</tt> -- Float. The patient support angle (300A,0122).
|
256
|
+
#
|
257
|
+
def pedestal_angle=(value)
|
258
|
+
@pedestal_angle = value && value.to_f
|
259
|
+
end
|
260
|
+
|
261
|
+
# Sets a new patient support rotation direction.
|
262
|
+
#
|
263
|
+
# === Parameters
|
264
|
+
#
|
265
|
+
# * <tt>value</tt> -- String. The patient support rotation direction (300A,0123).
|
266
|
+
#
|
267
|
+
def pedestal_direction=(value)
|
268
|
+
@pedestal_direction = value && value.to_s
|
269
|
+
end
|
270
|
+
|
271
|
+
# Sets a new source to surface distance.
|
272
|
+
#
|
273
|
+
# === Parameters
|
274
|
+
#
|
275
|
+
# * <tt>value</tt> -- Float. The source to surface distance (300A,012C).
|
276
|
+
#
|
277
|
+
def ssd=(value)
|
278
|
+
@ssd = value && value.to_f
|
279
|
+
end
|
280
|
+
|
281
|
+
# Sets a new table top eccentric angle.
|
282
|
+
#
|
283
|
+
# === Parameters
|
284
|
+
#
|
285
|
+
# * <tt>value</tt> -- Float. The table top eccentric angle (300A,0125).
|
286
|
+
#
|
287
|
+
def table_top_angle=(value)
|
288
|
+
@table_top_angle = value && value.to_f
|
289
|
+
end
|
290
|
+
|
291
|
+
# Sets a new table top eccentric rotation direction.
|
292
|
+
#
|
293
|
+
# === Parameters
|
294
|
+
#
|
295
|
+
# * <tt>value</tt> -- String. The table top eccentric rotation direction (300A,0126).
|
296
|
+
#
|
297
|
+
def table_top_direction=(value)
|
298
|
+
@table_top_direction = value && value.to_s
|
299
|
+
end
|
300
|
+
|
301
|
+
# Sets a new table top lateral position.
|
302
|
+
#
|
303
|
+
# === Parameters
|
304
|
+
#
|
305
|
+
# * <tt>value</tt> -- Float. The table top lateral position (300A,0125).
|
306
|
+
#
|
307
|
+
def table_top_lateral=(value)
|
308
|
+
@table_top_lateral = value && value.to_f
|
309
|
+
end
|
310
|
+
|
311
|
+
# Sets a new table top longitudinal position.
|
312
|
+
#
|
313
|
+
# === Parameters
|
314
|
+
#
|
315
|
+
# * <tt>value</tt> -- Float. The table top longitudinal position (300A,0125).
|
316
|
+
#
|
317
|
+
def table_top_longitudinal=(value)
|
318
|
+
@table_top_longitudinal = value && value.to_f
|
319
|
+
end
|
320
|
+
|
321
|
+
# Sets a new table top vertical position.
|
322
|
+
#
|
323
|
+
# === Parameters
|
324
|
+
#
|
325
|
+
# * <tt>value</tt> -- Float. The table top vertical position (300A,0125).
|
326
|
+
#
|
327
|
+
def table_top_vertical=(value)
|
328
|
+
@table_top_vertical = value && value.to_f
|
329
|
+
end
|
330
|
+
|
331
|
+
# Returns self.
|
332
|
+
#
|
333
|
+
def to_control_point
|
334
|
+
self
|
335
|
+
end
|
336
|
+
|
337
|
+
# Creates and returns a Control Point Sequence Item from the attributes of the ControlPoint.
|
338
|
+
#
|
339
|
+
def to_item
|
340
|
+
item = DICOM::Item.new
|
341
|
+
item.add(DICOM::Element.new(ROI_COLOR, @color))
|
342
|
+
s = DICOM::Sequence.new(CONTOUR_SQ)
|
343
|
+
item.add(s)
|
344
|
+
item.add(DICOM::Element.new(REF_ROI_NUMBER, @number.to_s))
|
345
|
+
# Add Contour items to the Contour Sequence (one or several items per Slice):
|
346
|
+
@slices.each do |slice|
|
347
|
+
slice.contours.each do |contour|
|
348
|
+
s.add_item(contour.to_item)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
return item
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
private
|
356
|
+
|
357
|
+
|
358
|
+
# Returns the attributes of this instance in an array (for comparison purposes).
|
359
|
+
#
|
360
|
+
def state
|
361
|
+
[@collimators, @collimator_angle, @collimator_direction, @cum_meterset, @energy,
|
362
|
+
@gantry_angle, @gantry_direction, @index, @iso, @pedestal_angle, @pedestal_direction,
|
363
|
+
@ssd, @table_top_angle, @table_top_direction, @table_top_lateral,
|
364
|
+
@table_top_longitudinal, @table_top_vertical
|
365
|
+
]
|
366
|
+
end
|
367
|
+
|
368
|
+
|
369
|
+
end
|
370
|
+
|
371
|
+
end
|