rtp-connect 1.6 → 1.11
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.
- checksums.yaml +7 -0
- data/{CHANGELOG.rdoc → CHANGELOG.md} +137 -90
- data/COPYING +674 -674
- data/Gemfile +2 -2
- data/Gemfile.lock +31 -21
- data/README.md +161 -0
- data/lib/rtp-connect.rb +1 -0
- data/lib/rtp-connect/constants.rb +58 -57
- data/lib/rtp-connect/control_point.rb +158 -118
- data/lib/rtp-connect/dose_tracking.rb +37 -54
- data/lib/rtp-connect/extended_field.rb +36 -69
- data/lib/rtp-connect/extended_plan.rb +127 -0
- data/lib/rtp-connect/field.rb +158 -143
- data/lib/rtp-connect/methods.rb +85 -62
- data/lib/rtp-connect/plan.rb +645 -636
- data/lib/rtp-connect/plan_to_dcm.rb +668 -694
- data/lib/rtp-connect/prescription.rb +57 -74
- data/lib/rtp-connect/record.rb +225 -57
- data/lib/rtp-connect/ruby_extensions.rb +34 -3
- data/lib/rtp-connect/simulation_field.rb +606 -701
- data/lib/rtp-connect/site_setup.rb +112 -80
- data/lib/rtp-connect/version.rb +5 -5
- data/rakefile.rb +0 -1
- data/rtp-connect.gemspec +27 -27
- metadata +67 -58
- data/README.rdoc +0 -136
@@ -9,7 +9,7 @@ module RTP
|
|
9
9
|
class Prescription < Record
|
10
10
|
|
11
11
|
# The Record which this instance belongs to.
|
12
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
#
|
125
|
+
# Removes the reference of the given instance from this instance.
|
127
126
|
#
|
128
|
-
# @
|
127
|
+
# @param [Field, SimulationField, SiteSetup] record a child record to be removed from this instance
|
129
128
|
#
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
#
|
137
|
-
#
|
138
|
-
|
139
|
-
|
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
|
-
#
|
148
|
+
# Removes all simulation_field references from this instance.
|
159
149
|
#
|
160
|
-
|
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
|
163
|
-
|
156
|
+
def delete_site_setup
|
157
|
+
delete_child(:site_setup)
|
164
158
|
end
|
165
159
|
|
166
|
-
#
|
167
|
-
# to a properly formatted RTPConnect ascii string.
|
160
|
+
# Computes a hash code for this object.
|
168
161
|
#
|
169
|
-
# @
|
162
|
+
# @note Two objects with the same attributes will have the same hash code.
|
170
163
|
#
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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
|
-
|
182
|
-
|
183
|
-
# Sets the keyword attribute.
|
170
|
+
# Returns self.
|
184
171
|
#
|
185
|
-
# @
|
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
|
190
|
-
|
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.
|
data/lib/rtp-connect/record.rb
CHANGED
@@ -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
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
#
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
#
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
|
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
|