rtp-connect 1.6 → 1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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