rtp-connect 1.6 → 1.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,8 +8,11 @@ module RTP
8
8
  #
9
9
  class DoseTracking < Record
10
10
 
11
+ # The number of attributes not having their own variable for this record (20 - 2).
12
+ NR_SURPLUS_ATTRIBUTES = 18
13
+
11
14
  # The Record which this instance belongs to.
12
- attr_reader :parent
15
+ attr_accessor :parent
13
16
  # The DoseAction records (if any) that belongs to this DoseTracking.
14
17
  attr_reader :dose_actions
15
18
  attr_reader :region_name
@@ -29,23 +32,8 @@ module RTP
29
32
  # @raise [ArgumentError] if given a string containing an invalid number of elements
30
33
  #
31
34
  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
35
  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
36
+ d.load(string)
49
37
  end
50
38
 
51
39
  # Creates a new DoseTracking.
@@ -53,14 +41,25 @@ module RTP
53
41
  # @param [Record] parent a record which is used to determine the proper parent of this instance
54
42
  #
55
43
  def initialize(parent)
44
+ super('DOSE_DEF', 24, 26)
56
45
  # Child records:
57
46
  @dose_actions = Array.new
58
47
  # Parent relation (may get more than one type of record here):
59
48
  @parent = get_parent(parent.to_record, Plan)
60
49
  @parent.add_dose_tracking(self)
61
- @keyword = 'DOSE_DEF'
62
50
  @field_ids = Array.new(10)
63
51
  @region_coeffs = Array.new(10)
52
+ @attributes = [
53
+ # Required:
54
+ :keyword,
55
+ :region_name,
56
+ :region_prior_dose,
57
+ :field_ids,
58
+ :region_coeffs,
59
+ # Optional:
60
+ :actual_dose,
61
+ :actual_fractions
62
+ ]
64
63
  end
65
64
 
66
65
  # Checks for equality.
@@ -105,7 +104,7 @@ module RTP
105
104
  # @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
106
105
  #
107
106
  def values
108
- return [
107
+ [
109
108
  @keyword,
110
109
  @region_name,
111
110
  @region_prior_dose,
@@ -124,23 +123,6 @@ module RTP
124
123
  self
125
124
  end
126
125
 
127
- # Encodes the DoseTracking object + any hiearchy of child objects,
128
- # to a properly formatted RTPConnect ascii string.
129
- #
130
- # @return [String] an RTP string with a single or multiple lines/records
131
- #
132
- def to_s
133
- str = encode
134
- if children
135
- children.each do |child|
136
- str += child.to_s
137
- end
138
- end
139
- return str
140
- end
141
-
142
- alias :to_str :to_s
143
-
144
126
  # Sets the field_ids attribute.
145
127
  #
146
128
  # @note As opposed to the ordinary (string) attributes, this attribute
@@ -148,9 +130,7 @@ module RTP
148
130
  # @param [Array<nil, #to_s>] array the new attribute values
149
131
  #
150
132
  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}
133
+ @field_ids = array.to_a.validate_and_process(10)
154
134
  end
155
135
 
156
136
  # Sets the region_coeffs attribute.
@@ -160,21 +140,7 @@ module RTP
160
140
  # @param [Array<nil, #to_s>] array the new attribute values
161
141
  #
162
142
  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
143
+ @region_coeffs = array.to_a.validate_and_process(10)
178
144
  end
179
145
 
180
146
  # Sets the region_name attribute.
@@ -220,6 +186,23 @@ module RTP
220
186
  #
221
187
  alias_method :state, :values
222
188
 
189
+ # Gives an array of indices indicating where the attributes of this record gets its
190
+ # values from in the comma separated string which the instance is created from.
191
+ #
192
+ # @param [Integer] length the number of elements to create in the indices array
193
+ #
194
+ def import_indices(length)
195
+ # Note that this method is defined in the parent Record class, where it is
196
+ # used for most record types. However, because this record has two attributes
197
+ # which contain an array of values, we use a custom import_indices method.
198
+ ind = Array.new(length - NR_SURPLUS_ATTRIBUTES) { |i| i }
199
+ # Override indices for field_ids and region_coeffs:
200
+ ind[3] = [3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
201
+ ind[4] = [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]
202
+ ind[5, 6] = [23, 24]
203
+ ind
204
+ end
205
+
223
206
  end
224
207
 
225
208
  end
@@ -9,7 +9,7 @@ module RTP
9
9
  class ExtendedField < Record
10
10
 
11
11
  # The Record which this instance belongs to.
12
- attr_reader :parent
12
+ attr_accessor :parent
13
13
  attr_reader :field_id
14
14
  attr_reader :original_plan_uid
15
15
  attr_reader :original_beam_number
@@ -18,6 +18,8 @@ module RTP
18
18
  attr_reader :accessory_code
19
19
  attr_reader :accessory_type
20
20
  attr_reader :high_dose_authorization
21
+ attr_reader :referenced_rt_plan_uid
22
+ attr_reader :referenced_rt_plan_relationship
21
23
 
22
24
  # Creates a new (treatment) ExtendedField by parsing a RTPConnect string line.
23
25
  #
@@ -27,26 +29,8 @@ module RTP
27
29
  # @raise [ArgumentError] if given a string containing an invalid number of elements
28
30
  #
29
31
  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
32
  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
33
+ ef.load(string)
50
34
  end
51
35
 
52
36
  # Creates a new (treatment) ExtendedField.
@@ -54,10 +38,25 @@ module RTP
54
38
  # @param [Record] parent a record which is used to determine the proper parent of this instance
55
39
  #
56
40
  def initialize(parent)
41
+ super('EXTENDED_FIELD_DEF', 4, 12)
57
42
  # Parent relation (may get more than one type of record here):
58
43
  @parent = get_parent(parent.to_record, Field)
59
44
  @parent.add_extended_field(self)
60
- @keyword = 'EXTENDED_FIELD_DEF'
45
+ @attributes = [
46
+ # Required:
47
+ :keyword,
48
+ :field_id,
49
+ :original_plan_uid,
50
+ # Optional:
51
+ :original_beam_number,
52
+ :original_beam_name,
53
+ :is_fff,
54
+ :accessory_code,
55
+ :accessory_type,
56
+ :high_dose_authorization,
57
+ :referenced_rt_plan_uid,
58
+ :referenced_rt_plan_relationship
59
+ ]
61
60
  end
62
61
 
63
62
  # Checks for equality.
@@ -94,25 +93,6 @@ module RTP
94
93
  state.hash
95
94
  end
96
95
 
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
96
  # Returns self.
117
97
  #
118
98
  # @return [ExtendedField] self
@@ -121,35 +101,6 @@ module RTP
121
101
  self
122
102
  end
123
103
 
124
- # Encodes the ExtendedField object + any hiearchy of child objects,
125
- # to a properly formatted RTPConnect ascii string.
126
- #
127
- # @return [String] an RTP string with a single or multiple lines/records
128
- #
129
- def to_s
130
- str = encode
131
- if children
132
- children.each do |child|
133
- str += child.to_s
134
- end
135
- end
136
- return str
137
- end
138
-
139
- alias :to_str :to_s
140
-
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
104
  # Sets the field_id attribute.
154
105
  #
155
106
  # @param [nil, #to_s] value the new attribute value
@@ -214,6 +165,22 @@ module RTP
214
165
  @high_dose_authorization = value && value.to_s
215
166
  end
216
167
 
168
+ # Sets the referenced_rt_plan_uid attribute.
169
+ #
170
+ # @param [nil, #to_s] value the new attribute value
171
+ #
172
+ def referenced_rt_plan_uid=(value)
173
+ @referenced_rt_plan_uid = value && value.to_s
174
+ end
175
+
176
+ # Sets the referenced_rt_plan_relationship attribute.
177
+ #
178
+ # @param [nil, #to_s] value the new attribute value
179
+ #
180
+ def referenced_rt_plan_relationship=(value)
181
+ @referenced_rt_plan_relationship = value && value.to_s
182
+ end
183
+
217
184
 
218
185
  private
219
186
 
@@ -0,0 +1,127 @@
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_accessor :parent
13
+ attr_reader :encoding
14
+ attr_reader :fullname
15
+ attr_reader :patient_comments
16
+
17
+ # Creates a new ExtendedPlan by parsing a RTPConnect string line.
18
+ #
19
+ # @param [#to_s] string the extended plan definition record string line
20
+ # @param [Record] parent a record which is used to determine the proper parent of this instance
21
+ # @return [ExtendedPlan] the created ExtendedPlan instance
22
+ # @raise [ArgumentError] if given a string containing an invalid number of elements
23
+ #
24
+ def self.load(string, parent)
25
+ ep = self.new(parent)
26
+ ep.load(string)
27
+ end
28
+
29
+ # Creates a new ExtendedPlan.
30
+ #
31
+ # @param [Record] parent a record which is used to determine the proper parent of this instance
32
+ #
33
+ def initialize(parent)
34
+ super('EXTENDED_PLAN_DEF', 4, 5)
35
+ # Parent relation (may get more than one type of record here):
36
+ @parent = get_parent(parent.to_record, Plan)
37
+ @parent.add_extended_plan(self)
38
+ @attributes = [
39
+ # Required:
40
+ :keyword,
41
+ :encoding,
42
+ :fullname,
43
+ # Optional:
44
+ :patient_comments
45
+ ]
46
+ end
47
+
48
+ # Checks for equality.
49
+ #
50
+ # Other and self are considered equivalent if they are
51
+ # of compatible types and their attributes are equivalent.
52
+ #
53
+ # @param other an object to be compared with self.
54
+ # @return [Boolean] true if self and other are considered equivalent
55
+ #
56
+ def ==(other)
57
+ if other.respond_to?(:to_extended_plan)
58
+ other.send(:state) == state
59
+ end
60
+ end
61
+
62
+ alias_method :eql?, :==
63
+
64
+ # Gives an empty array, as these instances are child-less by definition.
65
+ #
66
+ # @return [Array] an emtpy array
67
+ #
68
+ def children
69
+ return Array.new
70
+ end
71
+
72
+ # Computes a hash code for this object.
73
+ #
74
+ # @note Two objects with the same attributes will have the same hash code.
75
+ #
76
+ # @return [Fixnum] the object's hash code
77
+ #
78
+ def hash
79
+ state.hash
80
+ end
81
+
82
+ # Returns self.
83
+ #
84
+ # @return [ExtendedPlan] self
85
+ #
86
+ def to_extended_plan
87
+ self
88
+ end
89
+
90
+ # Sets the encoding attribute.
91
+ #
92
+ # @param [nil, #to_s] value the new attribute value
93
+ #
94
+ def encoding=(value)
95
+ @encoding = value && value.to_s
96
+ end
97
+
98
+ # Sets the fullname attribute.
99
+ #
100
+ # @param [nil, #to_s] value the new attribute value
101
+ #
102
+ def fullname=(value)
103
+ @fullname = value && value.to_s
104
+ end
105
+
106
+ # Sets the patient_comments attribute.
107
+ #
108
+ # @param [nil, #to_s] value the new attribute value
109
+ #
110
+ def patient_comments=(value)
111
+ @patient_comments = value && value.to_s
112
+ end
113
+
114
+
115
+ private
116
+
117
+
118
+ # Collects the attributes of this instance.
119
+ #
120
+ # @note The CRC is not considered part of the attributes of interest and is excluded
121
+ # @return [Array<String>] an array of attributes
122
+ #
123
+ alias_method :state, :values
124
+
125
+ end
126
+
127
+ end
@@ -9,7 +9,7 @@ module RTP
9
9
  class Field < Record
10
10
 
11
11
  # The Record which this instance belongs to.
12
- attr_reader :parent
12
+ attr_accessor :parent
13
13
  # The ExtendedField record (if any) that belongs to this Field.
14
14
  attr_reader :extended_field
15
15
  # An array of ControlPoint records (if any) that belongs to this Field.
@@ -61,6 +61,9 @@ module RTP
61
61
  attr_reader :portfilm_delta_open
62
62
  attr_reader :portfilm_mu_treat
63
63
  attr_reader :portfilm_coeff_treat
64
+ attr_reader :iso_pos_x
65
+ attr_reader :iso_pos_y
66
+ attr_reader :iso_pos_z
64
67
 
65
68
  # Creates a new (treatment) Field by parsing a RTPConnect string line.
66
69
  #
@@ -70,64 +73,8 @@ module RTP
70
73
  # @raise [ArgumentError] if given a string containing an invalid number of elements
71
74
  #
72
75
  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
76
  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
77
+ f.load(string)
131
78
  end
132
79
 
133
80
  # Creates a new (treatment) Field.
@@ -135,13 +82,68 @@ module RTP
135
82
  # @param [Record] parent a record which is used to determine the proper parent of this instance
136
83
  #
137
84
  def initialize(parent)
85
+ super('FIELD_DEF', 27, 52)
138
86
  # Child records:
139
87
  @control_points = Array.new
140
88
  @extended_field = nil
141
89
  # Parent relation (may get more than one type of record here):
142
90
  @parent = get_parent(parent.to_record, Prescription)
143
91
  @parent.add_field(self)
144
- @keyword = 'FIELD_DEF'
92
+ @attributes = [
93
+ # Required:
94
+ :keyword,
95
+ :rx_site_name,
96
+ :field_name,
97
+ :field_id,
98
+ :field_note,
99
+ :field_dose,
100
+ :field_monitor_units,
101
+ :wedge_monitor_units,
102
+ :treatment_machine,
103
+ :treatment_type,
104
+ :modality,
105
+ :energy,
106
+ :time,
107
+ :doserate,
108
+ :sad,
109
+ :ssd,
110
+ :gantry_angle,
111
+ :collimator_angle,
112
+ :field_x_mode,
113
+ :field_x,
114
+ :collimator_x1,
115
+ :collimator_x2,
116
+ :field_y_mode,
117
+ :field_y,
118
+ :collimator_y1,
119
+ :collimator_y2,
120
+ # Optional:
121
+ :couch_vertical,
122
+ :couch_lateral,
123
+ :couch_longitudinal,
124
+ :couch_angle,
125
+ :couch_pedestal,
126
+ :tolerance_table,
127
+ :arc_direction,
128
+ :arc_start_angle,
129
+ :arc_stop_angle,
130
+ :arc_mu_degree,
131
+ :wedge,
132
+ :dynamic_wedge,
133
+ :block,
134
+ :compensator,
135
+ :e_applicator,
136
+ :e_field_def_aperture,
137
+ :bolus,
138
+ :portfilm_mu_open,
139
+ :portfilm_coeff_open,
140
+ :portfilm_delta_open,
141
+ :portfilm_mu_treat,
142
+ :portfilm_coeff_treat,
143
+ :iso_pos_x,
144
+ :iso_pos_y,
145
+ :iso_pos_z
146
+ ]
145
147
  end
146
148
 
147
149
  # Checks for equality.
@@ -166,6 +168,7 @@ module RTP
166
168
  #
167
169
  def add_control_point(child)
168
170
  @control_points << child.to_control_point
171
+ child.parent = self
169
172
  end
170
173
 
171
174
  # Adds an extended treatment field record to this instance.
@@ -174,6 +177,7 @@ module RTP
174
177
  #
175
178
  def add_extended_field(child)
176
179
  @extended_field = child.to_extended_field
180
+ child.parent = self
177
181
  end
178
182
 
179
183
  # Collects the child records of this instance in a properly sorted array.
@@ -184,109 +188,81 @@ module RTP
184
188
  return [@extended_field, @control_points].flatten.compact
185
189
  end
186
190
 
187
- # Computes a hash code for this object.
191
+ # Converts the collimator_x1 attribute to proper DICOM format.
188
192
  #
189
- # @note Two objects with the same attributes will have the same hash code.
193
+ # @return [Float] the DICOM-formatted collimator_x1 attribute
190
194
  #
191
- # @return [Fixnum] the object's hash code
195
+ def dcm_collimator_x1
196
+ dcm_collimator1(:x)
197
+ end
198
+
199
+ # Converts the collimator_x2 attribute to proper DICOM format.
192
200
  #
193
- def hash
194
- state.hash
201
+ # @return [Float] the DICOM-formatted collimator_x2 attribute
202
+ #
203
+ def dcm_collimator_x2
204
+ value = @collimator_x2.to_f * 10
195
205
  end
196
206
 
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
- ]
207
+ # Converts the collimator_y1 attribute to proper DICOM format.
208
+ #
209
+ # @return [Float] the DICOM-formatted collimator_y1 attribute
210
+ #
211
+ def dcm_collimator_y1
212
+ dcm_collimator1(:y)
253
213
  end
254
214
 
255
- # Returns self.
215
+ # Converts the collimator_y2 attribute to proper DICOM format.
256
216
  #
257
- # @return [Field] self
217
+ # @return [Float] the DICOM-formatted collimator_y2 attribute
258
218
  #
259
- def to_field
260
- self
219
+ def dcm_collimator_y2
220
+ value = @collimator_y2.to_f * 10
261
221
  end
262
222
 
263
- # Encodes the Field object + any hiearchy of child objects,
264
- # to a properly formatted RTPConnect ascii string.
223
+ # Removes the reference of the given instance from this instance.
265
224
  #
266
- # @return [String] an RTP string with a single or multiple lines/records
225
+ # @param [ControlPoint, ExtendedField] record a child record to be removed from this instance
267
226
  #
268
- def to_s
269
- str = encode
270
- if children
271
- children.each do |child|
272
- str += child.to_s
273
- end
227
+ def delete(record)
228
+ case record
229
+ when ControlPoint
230
+ delete_child(:control_points, record)
231
+ when ExtendedField
232
+ delete_extended_field
233
+ else
234
+ logger.warn("Unknown class (record) given to Field#delete: #{record.class}")
274
235
  end
275
- return str
276
236
  end
277
237
 
278
- alias :to_str :to_s
238
+ # Removes all control point references from this instance.
239
+ #
240
+ def delete_control_points
241
+ delete_children(:control_points)
242
+ end
243
+
244
+ # Removes the extended field reference from this instance.
245
+ #
246
+ def delete_extended_field
247
+ delete_child(:extended_field)
248
+ end
279
249
 
280
- # Sets the keyword attribute.
250
+ # Computes a hash code for this object.
251
+ #
252
+ # @note Two objects with the same attributes will have the same hash code.
281
253
  #
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
254
+ # @return [Fixnum] the object's hash code
285
255
  #
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
256
+ def hash
257
+ state.hash
258
+ end
259
+
260
+ # Returns self.
261
+ #
262
+ # @return [Field] self
263
+ #
264
+ def to_field
265
+ self
290
266
  end
291
267
 
292
268
  # Sets the rx_site_name attribute.
@@ -665,6 +641,30 @@ module RTP
665
641
  @portfilm_coeff_treat = value && value.to_s
666
642
  end
667
643
 
644
+ # Sets the iso_pos_x attribute.
645
+ #
646
+ # @param [nil, #to_s] value the new attribute value
647
+ #
648
+ def iso_pos_x=(value)
649
+ @iso_pos_x = value && value.to_s.strip
650
+ end
651
+
652
+ # Sets the iso_pos_y attribute.
653
+ #
654
+ # @param [nil, #to_s] value the new attribute value
655
+ #
656
+ def iso_pos_y=(value)
657
+ @iso_pos_y = value && value.to_s.strip
658
+ end
659
+
660
+ # Sets the iso_pos_z attribute.
661
+ #
662
+ # @param [nil, #to_s] value the new attribute value
663
+ #
664
+ def iso_pos_z=(value)
665
+ @iso_pos_z = value && value.to_s.strip
666
+ end
667
+
668
668
 
669
669
  private
670
670
 
@@ -676,6 +676,21 @@ module RTP
676
676
  #
677
677
  alias_method :state, :values
678
678
 
679
+ # Converts the collimator attribute to proper DICOM format.
680
+ #
681
+ # @param [Symbol] axis a representation for the axis of interest (x or y)
682
+ # @return [Float] the DICOM-formatted collimator attribute
683
+ #
684
+ def dcm_collimator1(axis)
685
+ value = self.send("collimator_#{axis}1").to_f * 10
686
+ mode = self.send("field_#{axis}_mode")
687
+ if mode && mode.upcase == 'SYM' && value > 0
688
+ -value
689
+ else
690
+ value
691
+ end
692
+ end
693
+
679
694
  end
680
695
 
681
696
  end