rtp-connect 1.6 → 1.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,7 +9,7 @@ module RTP
9
9
  class Prescription < Record
10
10
 
11
11
  # The Record which this instance belongs to.
12
- attr_reader :parent
12
+ attr_accessor :parent
13
13
  # The SiteSetup record (if any) that belongs to this Prescription.
14
14
  attr_reader :site_setup
15
15
  # An array of SimulationField records (if any) that belongs to this Prescription.
@@ -36,28 +36,8 @@ module RTP
36
36
  # @raise [ArgumentError] if given a string containing an invalid number of elements
37
37
  #
38
38
  def self.load(string, parent)
39
- # Get the quote-less values:
40
- values = string.to_s.values
41
- low_limit = 4
42
- high_limit = 13
43
- raise ArgumentError, "Invalid argument 'string': Expected at least #{low_limit} elements, got #{values.length}." if values.length < low_limit
44
- RTP.logger.warn "The number of elements (#{values.length}) for this Prescription 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
45
39
  p = self.new(parent)
46
- # Assign the values to attributes:
47
- p.keyword = values[0]
48
- p.course_id = values[1]
49
- p.rx_site_name = values[2]
50
- p.technique = values[3]
51
- p.modality = values[4]
52
- p.dose_spec = values[5]
53
- p.rx_depth = values[6]
54
- p.dose_ttl = values[7]
55
- p.dose_tx = values[8]
56
- p.pattern = values[9]
57
- p.rx_note = values[10]
58
- p.number_of_fields = values[11]
59
- p.crc = values[-1]
60
- return p
40
+ p.load(string)
61
41
  end
62
42
 
63
43
  # Creates a new Prescription site.
@@ -65,6 +45,7 @@ module RTP
65
45
  # @param [Record] parent a record which is used to determine the proper parent of this instance
66
46
  #
67
47
  def initialize(parent)
48
+ super('RX_DEF', 4, 13)
68
49
  # Child objects:
69
50
  @site_setup = nil
70
51
  @fields = Array.new
@@ -72,7 +53,22 @@ module RTP
72
53
  # Parent relation (may get more than one type of record here):
73
54
  @parent = get_parent(parent.to_record, Plan)
74
55
  @parent.add_prescription(self)
75
- @keyword = 'RX_DEF'
56
+ @attributes = [
57
+ # Required:
58
+ :keyword,
59
+ :course_id,
60
+ :rx_site_name,
61
+ # Optional:
62
+ :technique,
63
+ :modality,
64
+ :dose_spec,
65
+ :rx_depth,
66
+ :dose_ttl,
67
+ :dose_tx,
68
+ :pattern,
69
+ :rx_note,
70
+ :number_of_fields
71
+ ]
76
72
  end
77
73
 
78
74
  # Checks for equality.
@@ -97,6 +93,7 @@ module RTP
97
93
  #
98
94
  def add_field(child)
99
95
  @fields << child.to_field
96
+ child.parent = self
100
97
  end
101
98
 
102
99
  # Adds a simulation field record to this instance.
@@ -105,6 +102,7 @@ module RTP
105
102
  #
106
103
  def add_simulation_field(child)
107
104
  @simulation_fields << child.to_simulation_field
105
+ child.parent = self
108
106
  end
109
107
 
110
108
  # Adds a site setup record to this instance.
@@ -113,6 +111,7 @@ module RTP
113
111
  #
114
112
  def add_site_setup(child)
115
113
  @site_setup = child.to_site_setup
114
+ child.parent = self
116
115
  end
117
116
 
118
117
  # Collects the child records of this instance in a properly sorted array.
@@ -123,73 +122,57 @@ module RTP
123
122
  return [@site_setup, @simulation_fields, @fields].flatten.compact
124
123
  end
125
124
 
126
- # Computes a hash code for this object.
125
+ # Removes the reference of the given instance from this instance.
127
126
  #
128
- # @note Two objects with the same attributes will have the same hash code.
127
+ # @param [Field, SimulationField, SiteSetup] record a child record to be removed from this instance
129
128
  #
130
- # @return [Fixnum] the object's hash code
131
- #
132
- def hash
133
- state.hash
129
+ def delete(record)
130
+ case record
131
+ when Field
132
+ delete_child(:fields, record)
133
+ when SimulationField
134
+ delete_child(:simulation_fields, record)
135
+ when SiteSetup
136
+ delete_site_setup
137
+ else
138
+ logger.warn("Unknown class (record) given to Prescription#delete: #{record.class}")
139
+ end
134
140
  end
135
141
 
136
- # Collects the values (attributes) of this instance.
137
- #
138
- # @note The CRC is not considered part of the actual values and is excluded.
139
- # @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
140
- #
141
- def values
142
- return [
143
- @keyword,
144
- @course_id,
145
- @rx_site_name,
146
- @technique,
147
- @modality,
148
- @dose_spec,
149
- @rx_depth,
150
- @dose_ttl,
151
- @dose_tx,
152
- @pattern,
153
- @rx_note,
154
- @number_of_fields
155
- ]
142
+ # Removes all field references from this instance.
143
+ #
144
+ def delete_fields
145
+ delete_children(:fields)
156
146
  end
157
147
 
158
- # Returns self.
148
+ # Removes all simulation_field references from this instance.
159
149
  #
160
- # @return [Prescription] self
150
+ def delete_simulation_fields
151
+ delete_children(:simulation_fields)
152
+ end
153
+
154
+ # Removes the site setup reference from this instance.
161
155
  #
162
- def to_prescription
163
- self
156
+ def delete_site_setup
157
+ delete_child(:site_setup)
164
158
  end
165
159
 
166
- # Encodes the Prescription object + any hiearchy of child objects,
167
- # to a properly formatted RTPConnect ascii string.
160
+ # Computes a hash code for this object.
168
161
  #
169
- # @return [String] an RTP string with a single or multiple lines/records
162
+ # @note Two objects with the same attributes will have the same hash code.
170
163
  #
171
- def to_s
172
- str = encode
173
- if children
174
- children.each do |child|
175
- str += child.to_s
176
- end
177
- end
178
- return str
164
+ # @return [Fixnum] the object's hash code
165
+ #
166
+ def hash
167
+ state.hash
179
168
  end
180
169
 
181
- alias :to_str :to_s
182
-
183
- # Sets the keyword attribute.
170
+ # Returns self.
184
171
  #
185
- # @note Since only a specific string is accepted, this is more of an argument check than a traditional setter method
186
- # @param [#to_s] value the new attribute value
187
- # @raise [ArgumentError] if given an unexpected keyword
172
+ # @return [Prescription] self
188
173
  #
189
- def keyword=(value)
190
- value = value.to_s.upcase
191
- raise ArgumentError, "Invalid keyword. Expected 'RX_DEF', got #{value}." unless value == "RX_DEF"
192
- @keyword = value
174
+ def to_prescription
175
+ self
193
176
  end
194
177
 
195
178
  # Sets the course_id attribute.
@@ -1,58 +1,226 @@
1
- module RTP
2
-
3
- # The Record class contains attributes and methods that are common
4
- # for the various record types defined in the RTPConnect standard.
5
- #
6
- class Record
7
-
8
- # The keyword defines the record type of a particular RTP string line.
9
- attr_reader :keyword
10
- # The CRC is used to validate the integrity of the content of the RTP string line.
11
- attr_reader :crc
12
-
13
- # Sets the crc (checksum) attribute.
14
- #
15
- # @note This value is not used when creating an RTP string from a record (a new crc is calculated)
16
- # @param [#to_s] value the new attribute value
17
- #
18
- def crc=(value)
19
- @crc = value.to_s
20
- end
21
-
22
- # Encodes a string from the contents of this instance.
23
- #
24
- # This produces the full record string line, including a computed CRC checksum.
25
- #
26
- # @return [String] a proper RTPConnect type CSV string
27
- #
28
- def encode
29
- content = CSV.generate_line(values, force_quotes: true, row_sep: '') + ","
30
- checksum = content.checksum
31
- # Complete string is content + checksum (in double quotes) + carriage return + line feed
32
- return (content + checksum.to_s.wrap + "\r\n").encode('ISO8859-1')
33
- end
34
-
35
- # Follows the tree of parents until the appropriate parent of the requesting record is found.
36
- #
37
- # @param [Record] last_parent the previous parent (the record from the previous line in the RTP file)
38
- # @param [Record] klass the expected parent record class of this record (e.g. Plan, Field)
39
- #
40
- def get_parent(last_parent, klass)
41
- if last_parent.is_a?(klass)
42
- return last_parent
43
- else
44
- return last_parent.get_parent(last_parent.parent, klass)
45
- end
46
- end
47
-
48
- # Returns self.
49
- #
50
- # @return [Record] self
51
- #
52
- def to_record
53
- self
54
- end
55
-
56
- end
57
-
1
+ module RTP
2
+
3
+ # The Record class contains attributes and methods that are common
4
+ # for the various record types defined in the RTPConnect standard.
5
+ #
6
+ class Record
7
+
8
+ # For most record types, there are no surplus (grouped) attributes.
9
+ NR_SURPLUS_ATTRIBUTES = 0
10
+
11
+ # An array of the record's attributes.
12
+ attr_reader :attributes
13
+ # The keyword defines the record type of a particular RTP string line.
14
+ attr_reader :keyword
15
+ # The CRC is used to validate the integrity of the content of the RTP string line.
16
+ attr_reader :crc
17
+
18
+ # Creates a new Record.
19
+ #
20
+ # @param [String] keyword the keyword which identifies this record
21
+ # @param [Integer] min_elements the minimum number of data elements required for this record
22
+ # @param [Integer] max_elements the maximum supported number of data elements for this record
23
+ #
24
+ def initialize(keyword, min_elements, max_elements)
25
+ @keyword = keyword
26
+ @min_elements = min_elements
27
+ @max_elements = max_elements
28
+ end
29
+
30
+ # Sets the crc (checksum) attribute.
31
+ #
32
+ # @note This value is not used when creating an RTP string from a record (a new crc is calculated)
33
+ # @param [#to_s] value the new attribute value
34
+ #
35
+ def crc=(value)
36
+ @crc = value.to_s
37
+ end
38
+
39
+ # Encodes a string from the contents of this instance.
40
+ #
41
+ # This produces the full record string line, including a computed CRC checksum.
42
+ #
43
+ # @param [Hash] options an optional hash parameter
44
+ # @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output
45
+ # @return [String] a proper RTPConnect type CSV string
46
+ #
47
+ def encode(options={})
48
+ encoded_values = values.collect {|v| v && v.encode('ISO8859-1')}
49
+ encoded_values = discard_unsupported_attributes(encoded_values, options) if options[:version]
50
+ content = CSV.generate_line(encoded_values, force_quotes: true, row_sep: '') + ","
51
+ checksum = content.checksum
52
+ # Complete string is content + checksum (in double quotes) + carriage return + line feed
53
+ return (content + checksum.to_s.wrap + "\r\n").encode('ISO8859-1')
54
+ end
55
+
56
+ # Follows the tree of parents until the appropriate parent of the requesting record is found.
57
+ #
58
+ # @param [Record] last_parent the previous parent (the record from the previous line in the RTP file)
59
+ # @param [Record] klass the expected parent record class of this record (e.g. Plan, Field)
60
+ #
61
+ def get_parent(last_parent, klass)
62
+ if last_parent.is_a?(klass)
63
+ return last_parent
64
+ else
65
+ return last_parent.get_parent(last_parent.parent, klass)
66
+ end
67
+ end
68
+
69
+ # Verifies a proposed keyword attribute.
70
+ #
71
+ # @note Since only a specific string is accepted, this is more of an argument check than a traditional setter method.
72
+ # @param [#to_s] value the proposed keyword attribute
73
+ # @raise [ArgumentError] if given an unexpected keyword
74
+ #
75
+ def keyword=(value)
76
+ value = value.to_s.upcase
77
+ raise ArgumentError, "Invalid keyword. Expected '#{@keyword}', got #{value}." unless value == @keyword
78
+ end
79
+
80
+ # Sets up a record by parsing a RTPConnect string line.
81
+ #
82
+ # @param [#to_s] string the extended treatment field definition record string line
83
+ # @return [Record] the updated Record instance
84
+ # @raise [ArgumentError] if given a string containing an invalid number of elements
85
+ #
86
+ def load(string, options={})
87
+ # Extract processed values:
88
+ values = string.to_s.values(options[:repair])
89
+ raise ArgumentError, "Invalid argument 'string': Expected at least #{@min_elements} elements for #{@keyword}, got #{values.length}." if values.length < @min_elements
90
+ RTP.logger.warn "The number of given elements (#{values.length}) exceeds the known number of data elements for this record (#{@max_elements}). This may indicate an invalid string record or that the RTP format has recently been expanded with new elements." if values.length > @max_elements
91
+ self.send(:set_attributes, values)
92
+ self
93
+ end
94
+
95
+ # Returns self.
96
+ #
97
+ # @return [Record] self
98
+ #
99
+ def to_record
100
+ self
101
+ end
102
+
103
+ # Encodes the record + any hiearchy of child objects,
104
+ # to a properly formatted RTPConnect ascii string.
105
+ #
106
+ # @param [Hash] options an optional hash parameter
107
+ # @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output
108
+ # @return [String] an RTP string with a single or multiple lines/records
109
+ #
110
+ def to_s(options={})
111
+ str = encode(options)
112
+ children.each do |child|
113
+ # Note that the extended plan record was introduced in Mosaiq 2.5.
114
+ str += child.to_s(options) unless child.class == ExtendedPlan && options[:version].to_f < 2.5
115
+ end
116
+ str
117
+ end
118
+
119
+ alias :to_str :to_s
120
+
121
+ # Collects the values (attributes) of this instance.
122
+ #
123
+ # @note The CRC is not considered part of the actual values and is excluded.
124
+ # @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
125
+ #
126
+ def values
127
+ @attributes.collect {|attribute| self.send(attribute)}
128
+ end
129
+
130
+
131
+ private
132
+
133
+
134
+ # Removes the reference of the given instance from the attribute of this record.
135
+ #
136
+ # @param [Symbol] attribute the name of the child attribute from which to remove a child
137
+ # @param [Record] instance a child record to be removed from this instance
138
+ #
139
+ def delete_child(attribute, instance=nil)
140
+ if self.send(attribute).is_a?(Array)
141
+ deleted = self.send(attribute).delete(instance)
142
+ deleted.parent = nil if deleted
143
+ else
144
+ self.send(attribute).parent = nil if self.send(attribute)
145
+ self.instance_variable_set("@#{attribute}", nil)
146
+ end
147
+ end
148
+
149
+ # Removes all child references of the given type from this instance.
150
+ #
151
+ # @param [Symbol] attribute the name of the child attribute to be cleared
152
+ #
153
+ def delete_children(attribute)
154
+ self.send(attribute).each { |c| c.parent = nil }
155
+ self.send(attribute).clear
156
+ end
157
+
158
+ # Sets the attributes of the record instance.
159
+ #
160
+ # @param [Array<String>] values the record attributes (as parsed from a record string)
161
+ #
162
+ def set_attributes(values)
163
+ import_indices([values.length - 1, @max_elements - 1].min).each_with_index do |indices, i|
164
+ param = nil
165
+ if indices
166
+ param = values.values_at(*indices)
167
+ param = param[0] if param.length == 1
168
+ end
169
+ self.send("#{@attributes[i]}=", param)
170
+ end
171
+ @crc = values[-1]
172
+ end
173
+
174
+ # Gives an array of indices indicating where the attributes of this record gets its
175
+ # values from in the comma separated string which the instance is created from.
176
+ #
177
+ # @param [Integer] length the number of elements to create in the indices array
178
+ #
179
+ def import_indices(length)
180
+ Array.new(length - NR_SURPLUS_ATTRIBUTES) { |i| [i] }
181
+ end
182
+
183
+ # Removes any attributes that are newer than the given compatibility target version.
184
+ # E.g. if a compatibility version of Mosaiq 2.4 is specified, attributes that were
185
+ # introduced in Mosaiq 2.5 or later is removed before the RTP string is created.
186
+ #
187
+ # @param [Array<String>] values the complete set of values of this record
188
+ # @param [Hash] options an optional hash parameter
189
+ # @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output
190
+ # @return [Array<String>] an array of attributes where some of the recent attributes may have been removed
191
+ #
192
+ def discard_unsupported_attributes(values, options={})
193
+ ver = options[:version].to_f
194
+ case self
195
+ when SiteSetup
196
+ if ver >= 2.81
197
+ values
198
+ elsif ver >= 2.65
199
+ values[0..-3]
200
+ elsif ver >= 2.6
201
+ values[0..-7]
202
+ else
203
+ values[0..-10]
204
+ end
205
+ when Field
206
+ ver >= 2.64 ? values : values[0..-4]
207
+ when ExtendedField
208
+ if ver >= 2.65
209
+ values
210
+ elsif ver >= 2.4
211
+ values[0..-3]
212
+ else
213
+ values[0..-7]
214
+ end
215
+ when ControlPoint
216
+ ver >= 2.64 ? values : values[0..31] + values[35..-1]
217
+ when ExtendedPlan
218
+ ver >= 2.81 ? values : values[0..-2]
219
+ else
220
+ values
221
+ end
222
+ end
223
+
224
+ end
225
+
58
226
  end