rtp-connect 1.6 → 1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -29,23 +29,8 @@ module RTP
29
29
  # @raise [ArgumentError] if given a string containing an invalid number of elements
30
30
  #
31
31
  def self.load(string, parent)
32
- # Get the quote-less values:
33
- values = string.to_s.values
34
- low_limit = 24
35
- high_limit = 26
36
- raise ArgumentError, "Invalid argument 'string': Expected at least #{low_limit} elements, got #{values.length}." if values.length < low_limit
37
- RTP.logger.warn "The number of elements (#{values.length}) for this DoseTracking record exceeds the known number of data items for this record (#{high_limit}). This may indicate an invalid record or that the RTP format has recently been expanded with new items." if values.length > high_limit
38
32
  d = self.new(parent)
39
- # Assign the values to attributes:
40
- d.keyword = values[0]
41
- d.region_name = values[1]
42
- d.region_prior_dose = values[2]
43
- d.field_ids = values.values_at(3, 5, 7, 9, 11, 13, 15, 17, 19, 21)
44
- d.region_coeffs = values.values_at(4, 6, 8, 10, 12, 14, 16, 18, 20, 22)
45
- d.actual_dose = values[23]
46
- d.actual_fractions = values[24]
47
- d.crc = values[-1]
48
- return d
33
+ d.load(string)
49
34
  end
50
35
 
51
36
  # Creates a new DoseTracking.
@@ -53,14 +38,25 @@ module RTP
53
38
  # @param [Record] parent a record which is used to determine the proper parent of this instance
54
39
  #
55
40
  def initialize(parent)
41
+ super('DOSE_DEF', 24, 26)
56
42
  # Child records:
57
43
  @dose_actions = Array.new
58
44
  # Parent relation (may get more than one type of record here):
59
45
  @parent = get_parent(parent.to_record, Plan)
60
46
  @parent.add_dose_tracking(self)
61
- @keyword = 'DOSE_DEF'
62
47
  @field_ids = Array.new(10)
63
48
  @region_coeffs = Array.new(10)
49
+ @attributes = [
50
+ # Required:
51
+ :keyword,
52
+ :region_name,
53
+ :region_prior_dose,
54
+ :field_ids,
55
+ :region_coeffs,
56
+ # Optional:
57
+ :actual_dose,
58
+ :actual_fractions
59
+ ]
64
60
  end
65
61
 
66
62
  # Checks for equality.
@@ -105,7 +101,7 @@ module RTP
105
101
  # @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
106
102
  #
107
103
  def values
108
- return [
104
+ [
109
105
  @keyword,
110
106
  @region_name,
111
107
  @region_prior_dose,
@@ -148,9 +144,7 @@ module RTP
148
144
  # @param [Array<nil, #to_s>] array the new attribute values
149
145
  #
150
146
  def field_ids=(array)
151
- array = array.to_a
152
- raise ArgumentError, "Invalid argument 'array'. Expected length 10, got #{array.length}." unless array.length == 10
153
- @field_ids = array.collect! {|e| e && e.to_s}
147
+ @field_ids = array.to_a.validate_and_process(10)
154
148
  end
155
149
 
156
150
  # Sets the region_coeffs attribute.
@@ -160,21 +154,7 @@ module RTP
160
154
  # @param [Array<nil, #to_s>] array the new attribute values
161
155
  #
162
156
  def region_coeffs=(array)
163
- array = array.to_a
164
- raise ArgumentError, "Invalid argument 'array'. Expected length 10, got #{array.length}." unless array.length == 10
165
- @region_coeffs = array.collect! {|e| e && e.to_s}
166
- end
167
-
168
- # Sets the keyword attribute.
169
- #
170
- # @note Since only a specific string is accepted, this is more of an argument check than a traditional setter method
171
- # @param [#to_s] value the new attribute value
172
- # @raise [ArgumentError] if given an unexpected keyword
173
- #
174
- def keyword=(value)
175
- value = value.to_s.upcase
176
- raise ArgumentError, "Invalid keyword. Expected 'DOSE_DEF', got #{value}." unless value == "DOSE_DEF"
177
- @keyword = value
157
+ @region_coeffs = array.to_a.validate_and_process(10)
178
158
  end
179
159
 
180
160
  # Sets the region_name attribute.
@@ -220,6 +200,21 @@ module RTP
220
200
  #
221
201
  alias_method :state, :values
222
202
 
203
+ # Sets the attributes of the record instance.
204
+ #
205
+ # @param [Array<String>] values the record attributes (as parsed from a record string)
206
+ #
207
+ def set_attributes(values)
208
+ self.keyword = values[0]
209
+ @region_name = values[1]
210
+ @region_prior_dose = values[2]
211
+ @field_ids = values.values_at(3, 5, 7, 9, 11, 13, 15, 17, 19, 21)
212
+ @region_coeffs = values.values_at(4, 6, 8, 10, 12, 14, 16, 18, 20, 22)
213
+ @actual_dose = values[23]
214
+ @actual_fractions = values[24]
215
+ @crc = values[-1]
216
+ end
217
+
223
218
  end
224
219
 
225
220
  end
@@ -27,26 +27,8 @@ module RTP
27
27
  # @raise [ArgumentError] if given a string containing an invalid number of elements
28
28
  #
29
29
  def self.load(string, parent)
30
- # Get the quote-less values:
31
- values = string.to_s.values
32
- low_limit = 4
33
- high_limit = 10
34
- raise ArgumentError, "Invalid argument 'string': Expected at least #{low_limit} elements, got #{values.length}." if values.length < low_limit
35
- RTP.logger.warn "The number of elements (#{values.length}) for this ExtendedField record exceeds the known number of data items for this record (#{high_limit}). This may indicate an invalid record or that the RTP format has recently been expanded with new items." if values.length > high_limit
36
30
  ef = self.new(parent)
37
- # Mandatory attributes:
38
- ef.keyword = values[0]
39
- ef.field_id = values[1]
40
- ef.original_plan_uid = values[2]
41
- # Optional attributes:
42
- ef.original_beam_number = values[3]
43
- ef.original_beam_name = values[4]
44
- ef.is_fff = values[5] if values[5]
45
- ef.accessory_code = values[6]
46
- ef.accessory_type = values[7]
47
- ef.high_dose_authorization = values[8]
48
- ef.crc = values[-1]
49
- return ef
31
+ ef.load(string)
50
32
  end
51
33
 
52
34
  # Creates a new (treatment) ExtendedField.
@@ -54,10 +36,23 @@ module RTP
54
36
  # @param [Record] parent a record which is used to determine the proper parent of this instance
55
37
  #
56
38
  def initialize(parent)
39
+ super('EXTENDED_FIELD_DEF', 4, 10)
57
40
  # Parent relation (may get more than one type of record here):
58
41
  @parent = get_parent(parent.to_record, Field)
59
42
  @parent.add_extended_field(self)
60
- @keyword = 'EXTENDED_FIELD_DEF'
43
+ @attributes = [
44
+ # Required:
45
+ :keyword,
46
+ :field_id,
47
+ :original_plan_uid,
48
+ # Optional:
49
+ :original_beam_number,
50
+ :original_beam_name,
51
+ :is_fff,
52
+ :accessory_code,
53
+ :accessory_type,
54
+ :high_dose_authorization
55
+ ]
61
56
  end
62
57
 
63
58
  # Checks for equality.
@@ -94,25 +89,6 @@ module RTP
94
89
  state.hash
95
90
  end
96
91
 
97
- # Collects the values (attributes) of this instance.
98
- #
99
- # @note The CRC is not considered part of the actual values and is excluded.
100
- # @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
101
- #
102
- def values
103
- return [
104
- @keyword,
105
- @field_id,
106
- @original_plan_uid,
107
- @original_beam_number,
108
- @original_beam_name,
109
- @is_fff,
110
- @accessory_code,
111
- @accessory_type,
112
- @high_dose_authorization
113
- ]
114
- end
115
-
116
92
  # Returns self.
117
93
  #
118
94
  # @return [ExtendedField] self
@@ -138,18 +114,6 @@ module RTP
138
114
 
139
115
  alias :to_str :to_s
140
116
 
141
- # Sets the keyword attribute.
142
- #
143
- # @note Since only a specific string is accepted, this is more of an argument check than a traditional setter method
144
- # @param [#to_s] value the new attribute value
145
- # @raise [ArgumentError] if given an unexpected keyword
146
- #
147
- def keyword=(value)
148
- value = value.to_s.upcase
149
- raise ArgumentError, "Invalid keyword. Expected 'EXTENDED_FIELD_DEF', got #{value}." unless value == "EXTENDED_FIELD_DEF"
150
- @keyword = value
151
- end
152
-
153
117
  # Sets the field_id attribute.
154
118
  #
155
119
  # @param [nil, #to_s] value the new attribute value
@@ -0,0 +1,133 @@
1
+ module RTP
2
+
3
+ # The Extended plan class.
4
+ #
5
+ # @note Relations:
6
+ # * Parent: Plan
7
+ # * Children: none
8
+ #
9
+ class ExtendedPlan < Record
10
+
11
+ # The Record which this instance belongs to.
12
+ attr_reader :parent
13
+ attr_reader :encoding
14
+ attr_reader :fullname
15
+
16
+ # Creates a new ExtendedPlan by parsing a RTPConnect string line.
17
+ #
18
+ # @param [#to_s] string the extended plan definition record string line
19
+ # @param [Record] parent a record which is used to determine the proper parent of this instance
20
+ # @return [ExtendedPlan] the created ExtendedPlan instance
21
+ # @raise [ArgumentError] if given a string containing an invalid number of elements
22
+ #
23
+ def self.load(string, parent)
24
+ ep = self.new(parent)
25
+ ep.load(string)
26
+ end
27
+
28
+ # Creates a new ExtendedPlan.
29
+ #
30
+ # @param [Record] parent a record which is used to determine the proper parent of this instance
31
+ #
32
+ def initialize(parent)
33
+ super('EXTENDED_PLAN_DEF', 4, 4)
34
+ # Parent relation (may get more than one type of record here):
35
+ @parent = get_parent(parent.to_record, Plan)
36
+ @parent.add_extended_plan(self)
37
+ @attributes = [
38
+ # Required:
39
+ :keyword,
40
+ :encoding,
41
+ :fullname
42
+ ]
43
+ end
44
+
45
+ # Checks for equality.
46
+ #
47
+ # Other and self are considered equivalent if they are
48
+ # of compatible types and their attributes are equivalent.
49
+ #
50
+ # @param other an object to be compared with self.
51
+ # @return [Boolean] true if self and other are considered equivalent
52
+ #
53
+ def ==(other)
54
+ if other.respond_to?(:to_extended_plan)
55
+ other.send(:state) == state
56
+ end
57
+ end
58
+
59
+ alias_method :eql?, :==
60
+
61
+ # Gives an empty array, as these instances are child-less by definition.
62
+ #
63
+ # @return [Array] an emtpy array
64
+ #
65
+ def children
66
+ return Array.new
67
+ end
68
+
69
+ # Computes a hash code for this object.
70
+ #
71
+ # @note Two objects with the same attributes will have the same hash code.
72
+ #
73
+ # @return [Fixnum] the object's hash code
74
+ #
75
+ def hash
76
+ state.hash
77
+ end
78
+
79
+ # Returns self.
80
+ #
81
+ # @return [ExtendedPlan] self
82
+ #
83
+ def to_extended_plan
84
+ self
85
+ end
86
+
87
+ # Encodes the ExtendedPlan object + any hiearchy of child objects,
88
+ # to a properly formatted RTPConnect ascii string.
89
+ #
90
+ # @return [String] an RTP string with a single or multiple lines/records
91
+ #
92
+ def to_s
93
+ str = encode
94
+ if children
95
+ children.each do |child|
96
+ str += child.to_s
97
+ end
98
+ end
99
+ return str
100
+ end
101
+
102
+ alias :to_str :to_s
103
+
104
+ # Sets the encoding attribute.
105
+ #
106
+ # @param [nil, #to_s] value the new attribute value
107
+ #
108
+ def encoding=(value)
109
+ @encoding = value && value.to_s
110
+ end
111
+
112
+ # Sets the fullname attribute.
113
+ #
114
+ # @param [nil, #to_s] value the new attribute value
115
+ #
116
+ def fullname=(value)
117
+ @fullname = value && value.to_s
118
+ end
119
+
120
+
121
+ private
122
+
123
+
124
+ # Collects the attributes of this instance.
125
+ #
126
+ # @note The CRC is not considered part of the attributes of interest and is excluded
127
+ # @return [Array<String>] an array of attributes
128
+ #
129
+ alias_method :state, :values
130
+
131
+ end
132
+
133
+ end
@@ -70,64 +70,8 @@ module RTP
70
70
  # @raise [ArgumentError] if given a string containing an invalid number of elements
71
71
  #
72
72
  def self.load(string, parent)
73
- # Get the quote-less values:
74
- values = string.to_s.values
75
- low_limit = 27
76
- high_limit = 49
77
- raise ArgumentError, "Invalid argument 'string': Expected at least #{low_limit} elements, got #{values.length}." if values.length < low_limit
78
- RTP.logger.warn "The number of elements (#{values.length}) for this Field record exceeds the known number of data items for this record (#{high_limit}). This may indicate an invalid record or that the RTP format has recently been expanded with new items." if values.length > high_limit
79
73
  f = self.new(parent)
80
- # Assign the values to attributes:
81
- f.keyword = values[0]
82
- f.rx_site_name = values[1]
83
- f.field_name = values[2]
84
- f.field_id = values[3]
85
- f.field_note = values[4]
86
- f.field_dose = values[5]
87
- f.field_monitor_units = values[6]
88
- f.wedge_monitor_units = values[7]
89
- f.treatment_machine = values[8]
90
- f.treatment_type = values[9]
91
- f.modality = values[10]
92
- f.energy = values[11]
93
- f.time = values[12]
94
- f.doserate = values[13]
95
- f.sad = values[14]
96
- f.ssd = values[15]
97
- f.gantry_angle = values[16]
98
- f.collimator_angle = values[17]
99
- f.field_x_mode = values[18]
100
- f.field_x = values[19]
101
- f.collimator_x1 = values[20]
102
- f.collimator_x2 = values[21]
103
- f.field_y_mode = values[22]
104
- f.field_y = values[23]
105
- f.collimator_y1 = values[24]
106
- f.collimator_y2 = values[25]
107
- f.couch_vertical = values[26]
108
- f.couch_lateral = values[27]
109
- f.couch_longitudinal = values[28]
110
- f.couch_angle = values[29]
111
- f.couch_pedestal = values[30]
112
- f.tolerance_table = values[31]
113
- f.arc_direction = values[32]
114
- f.arc_start_angle = values[33]
115
- f.arc_stop_angle = values[34]
116
- f.arc_mu_degree = values[35]
117
- f.wedge = values[36]
118
- f.dynamic_wedge = values[37]
119
- f.block = values[38]
120
- f.compensator = values[39]
121
- f.e_applicator = values[40]
122
- f.e_field_def_aperture = values[41]
123
- f.bolus = values[42]
124
- f.portfilm_mu_open = values[43]
125
- f.portfilm_coeff_open = values[44]
126
- f.portfilm_delta_open = values[45]
127
- f.portfilm_mu_treat = values[46]
128
- f.portfilm_coeff_treat = values[47]
129
- f.crc = values[-1]
130
- return f
74
+ f.load(string)
131
75
  end
132
76
 
133
77
  # Creates a new (treatment) Field.
@@ -135,13 +79,65 @@ module RTP
135
79
  # @param [Record] parent a record which is used to determine the proper parent of this instance
136
80
  #
137
81
  def initialize(parent)
82
+ super('FIELD_DEF', 27, 49)
138
83
  # Child records:
139
84
  @control_points = Array.new
140
85
  @extended_field = nil
141
86
  # Parent relation (may get more than one type of record here):
142
87
  @parent = get_parent(parent.to_record, Prescription)
143
88
  @parent.add_field(self)
144
- @keyword = 'FIELD_DEF'
89
+ @attributes = [
90
+ # Required:
91
+ :keyword,
92
+ :rx_site_name,
93
+ :field_name,
94
+ :field_id,
95
+ :field_note,
96
+ :field_dose,
97
+ :field_monitor_units,
98
+ :wedge_monitor_units,
99
+ :treatment_machine,
100
+ :treatment_type,
101
+ :modality,
102
+ :energy,
103
+ :time,
104
+ :doserate,
105
+ :sad,
106
+ :ssd,
107
+ :gantry_angle,
108
+ :collimator_angle,
109
+ :field_x_mode,
110
+ :field_x,
111
+ :collimator_x1,
112
+ :collimator_x2,
113
+ :field_y_mode,
114
+ :field_y,
115
+ :collimator_y1,
116
+ :collimator_y2,
117
+ # Optional:
118
+ :couch_vertical,
119
+ :couch_lateral,
120
+ :couch_longitudinal,
121
+ :couch_angle,
122
+ :couch_pedestal,
123
+ :tolerance_table,
124
+ :arc_direction,
125
+ :arc_start_angle,
126
+ :arc_stop_angle,
127
+ :arc_mu_degree,
128
+ :wedge,
129
+ :dynamic_wedge,
130
+ :block,
131
+ :compensator,
132
+ :e_applicator,
133
+ :e_field_def_aperture,
134
+ :bolus,
135
+ :portfilm_mu_open,
136
+ :portfilm_coeff_open,
137
+ :portfilm_delta_open,
138
+ :portfilm_mu_treat,
139
+ :portfilm_coeff_treat
140
+ ]
145
141
  end
146
142
 
147
143
  # Checks for equality.
@@ -184,6 +180,38 @@ module RTP
184
180
  return [@extended_field, @control_points].flatten.compact
185
181
  end
186
182
 
183
+ # Converts the collimator_x1 attribute to proper DICOM format.
184
+ #
185
+ # @return [Float] the DICOM-formatted collimator_x1 attribute
186
+ #
187
+ def dcm_collimator_x1
188
+ dcm_collimator1(:x)
189
+ end
190
+
191
+ # Converts the collimator_x2 attribute to proper DICOM format.
192
+ #
193
+ # @return [Float] the DICOM-formatted collimator_x2 attribute
194
+ #
195
+ def dcm_collimator_x2
196
+ value = @collimator_x2.to_f * 10
197
+ end
198
+
199
+ # Converts the collimator_y1 attribute to proper DICOM format.
200
+ #
201
+ # @return [Float] the DICOM-formatted collimator_y1 attribute
202
+ #
203
+ def dcm_collimator_y1
204
+ dcm_collimator1(:y)
205
+ end
206
+
207
+ # Converts the collimator_y2 attribute to proper DICOM format.
208
+ #
209
+ # @return [Float] the DICOM-formatted collimator_y2 attribute
210
+ #
211
+ def dcm_collimator_y2
212
+ value = @collimator_y2.to_f * 10
213
+ end
214
+
187
215
  # Computes a hash code for this object.
188
216
  #
189
217
  # @note Two objects with the same attributes will have the same hash code.
@@ -194,64 +222,6 @@ module RTP
194
222
  state.hash
195
223
  end
196
224
 
197
- # Collects the values (attributes) of this instance.
198
- #
199
- # @note The CRC is not considered part of the actual values and is excluded.
200
- # @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
201
- #
202
- def values
203
- return [
204
- @keyword,
205
- @rx_site_name,
206
- @field_name,
207
- @field_id,
208
- @field_note,
209
- @field_dose,
210
- @field_monitor_units,
211
- @wedge_monitor_units,
212
- @treatment_machine,
213
- @treatment_type,
214
- @modality,
215
- @energy,
216
- @time,
217
- @doserate,
218
- @sad,
219
- @ssd,
220
- @gantry_angle,
221
- @collimator_angle,
222
- @field_x_mode,
223
- @field_x,
224
- @collimator_x1,
225
- @collimator_x2,
226
- @field_y_mode,
227
- @field_y,
228
- @collimator_y1,
229
- @collimator_y2,
230
- @couch_vertical,
231
- @couch_lateral,
232
- @couch_longitudinal,
233
- @couch_angle,
234
- @couch_pedestal,
235
- @tolerance_table,
236
- @arc_direction,
237
- @arc_start_angle,
238
- @arc_stop_angle,
239
- @arc_mu_degree,
240
- @wedge,
241
- @dynamic_wedge,
242
- @block,
243
- @compensator,
244
- @e_applicator,
245
- @e_field_def_aperture,
246
- @bolus,
247
- @portfilm_mu_open,
248
- @portfilm_coeff_open,
249
- @portfilm_delta_open,
250
- @portfilm_mu_treat,
251
- @portfilm_coeff_treat
252
- ]
253
- end
254
-
255
225
  # Returns self.
256
226
  #
257
227
  # @return [Field] self
@@ -277,18 +247,6 @@ module RTP
277
247
 
278
248
  alias :to_str :to_s
279
249
 
280
- # Sets the keyword attribute.
281
- #
282
- # @note Since only a specific string is accepted, this is more of an argument check than a traditional setter method
283
- # @param [#to_s] value the new attribute value
284
- # @raise [ArgumentError] if given an unexpected keyword
285
- #
286
- def keyword=(value)
287
- value = value.to_s.upcase
288
- raise ArgumentError, "Invalid keyword. Expected 'FIELD_DEF', got #{value}." unless value == "FIELD_DEF"
289
- @keyword = value
290
- end
291
-
292
250
  # Sets the rx_site_name attribute.
293
251
  #
294
252
  # @param [nil, #to_s] value the new attribute value
@@ -676,6 +634,21 @@ module RTP
676
634
  #
677
635
  alias_method :state, :values
678
636
 
637
+ # Converts the collimator attribute to proper DICOM format.
638
+ #
639
+ # @param [Symbol] axis a representation for the axis of interest (x or y)
640
+ # @return [Float] the DICOM-formatted collimator attribute
641
+ #
642
+ def dcm_collimator1(axis)
643
+ value = self.send("collimator_#{axis}1").to_f * 10
644
+ mode = self.send("field_#{axis}_mode")
645
+ if mode && mode.upcase == 'SYM' && value > 0
646
+ -value
647
+ else
648
+ value
649
+ end
650
+ end
651
+
679
652
  end
680
653
 
681
654
  end