rtp-connect 1.6 → 1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZDIyZThkNzk2OGI3OTdlMjY5MDk4MDQ2YmU5ODBhMTk3NTM2NzUwNg==
5
+ data.tar.gz: !binary |-
6
+ MzdjNjVjMTAwYjc2Yzg5YzI5ZTI1YjBiY2ZlNDA0MmIyYjE4MGNiNQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NzZlMGZmMDY3ODVkNjgyOGE2ZTMzMzZkNWMzMWUxMzU4MTk0NGIzY2UzZTYx
10
+ Y2RhODM1NDdlNzE0ZDZhN2JkOWRlYmZhZWUzNzI2MmQ5YzIwOTE2ZjYwZmYw
11
+ ZDZhZmRjZDc3NzAxNTU4ZDRiODdmYmEzMmRiMGYwMDI5MTRmNTY=
12
+ data.tar.gz: !binary |-
13
+ ZTYwNjBkOTI2NjNkOGFiNjdmMDJiMjU5NzNjZmNhOTQyYjVlNTFhNWI5NTIy
14
+ MTFmNWNhMTlmMzFmZjkxYjZlZTI4ZTVkNjA2OTkxYzY3ZTM0OGY2ZjRjZWY4
15
+ ODRhNDRiYTExODllYzQ1MWIyOTU5MWE0ZDYxYzNjMWJkOWQ2ZWM=
@@ -1,6 +1,21 @@
1
- = 1.6
1
+ # 1.7
2
2
 
3
- === 12th December, 2013
3
+ ## 17th September 2014
4
+
5
+ * Added a repair option for trying to fix records containing invalid CSV format (i.e. unescaped " characters).
6
+ * Added an option for skipping unknown record types when reading an RTPConnect file (instead of raising an error).
7
+ * Fixed an issue with writing records containing attributes of mixed encoding.
8
+ * Added attributes for table top displacements in the Site Setup record (Mosaiq 2.6).
9
+ * Upgraded test suite for Rspec 3.
10
+ * Added support for the Extended Plan Definition record (Mosaiq 2.5).
11
+ * Added option for ignoring invalid checksums.
12
+ * Switched from RDoc to Markdown format.
13
+ * Plan#to_dcm improvements.
14
+
15
+
16
+ # 1.6
17
+
18
+ ## 12th December, 2013
4
19
 
5
20
  * Plan#to_dcm improvements:
6
21
  * Added support for VMAT by improving the handling of control point conversion.
@@ -17,9 +32,9 @@
17
32
  * Make sure that the last cumulative meterset weight exactly equals the final cumulative meterset weight.
18
33
 
19
34
 
20
- = 1.5
35
+ # 1.5
21
36
 
22
- === 24th October, 2013
37
+ ## 24th October, 2013
23
38
 
24
39
  * Added support for the Simulation Field record.
25
40
  * Bumped required Ruby version to 1.9.3.
@@ -42,25 +57,25 @@
42
57
  * Device Serial Number
43
58
 
44
59
 
45
- = 1.4
60
+ # 1.4
46
61
 
47
- === 10th April, 2013
62
+ ## 10th April, 2013
48
63
 
49
64
  * Support an extended ascii character set (ISO8859-1 encoding) for record values and file read/write.
50
65
 
51
66
 
52
- = 1.3
67
+ # 1.3
53
68
 
54
- === 12th October, 2012
69
+ ## 12th October, 2012
55
70
 
56
71
  * Added support for the updated ExtendedField record values introduced in Mosaiq 2.4.
57
72
  * Simply log a warning instead of raising an exception when reading a record with more values than excpected.
58
73
  * Allow reading (incomplete) records that contain the required values but not all the optional ones (instead of raising an exception).
59
74
 
60
75
 
61
- = 1.2
76
+ # 1.2
62
77
 
63
- === 13th July, 2012
78
+ ## 13th July, 2012
64
79
 
65
80
  * Converted documentation format from RDoc to YARD.
66
81
  * Added support for the Dose Tracking record.
@@ -68,9 +83,9 @@
68
83
  * Added the to_dcm conversion method for converting from RTP to DICOM.
69
84
 
70
85
 
71
- = 1.1
86
+ # 1.1
72
87
 
73
- === 18th April, 2012
88
+ ## 18th April, 2012
74
89
 
75
90
  * Added to_* methods for all records to complete the implementation of dynamic typing
76
91
  * Added comparison related methods to the record classes: #==, #eql? and #hash
@@ -80,9 +95,9 @@
80
95
  * Fixed an issue where reading a string with a single digit checksum would fail.
81
96
 
82
97
 
83
- = 1.0
98
+ # 1.0
84
99
 
85
- === 29th December, 2011
100
+ ## 29th December, 2011
86
101
 
87
102
  First public release.
88
103
  The library, although missing support for a number of record types, should be usable
@@ -1,35 +1,41 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rtp-connect (1.6)
4
+ rtp-connect (1.7)
5
5
 
6
6
  GEM
7
7
  remote: http://www.rubygems.org/
8
8
  specs:
9
- dicom (0.9.5)
10
- diff-lcs (1.2.1)
11
- metaclass (0.0.1)
12
- mocha (0.13.2)
9
+ dicom (0.9.6)
10
+ diff-lcs (1.2.5)
11
+ metaclass (0.0.4)
12
+ mocha (1.1.0)
13
13
  metaclass (~> 0.0.1)
14
- rake (0.9.6)
15
- rspec (2.13.0)
16
- rspec-core (~> 2.13.0)
17
- rspec-expectations (~> 2.13.0)
18
- rspec-mocks (~> 2.13.0)
19
- rspec-core (2.13.0)
20
- rspec-expectations (2.13.0)
21
- diff-lcs (>= 1.1.3, < 2.0)
22
- rspec-mocks (2.13.0)
23
- yard (0.8.5)
14
+ rake (10.3.2)
15
+ redcarpet (3.1.2)
16
+ rspec (3.0.0)
17
+ rspec-core (~> 3.0.0)
18
+ rspec-expectations (~> 3.0.0)
19
+ rspec-mocks (~> 3.0.0)
20
+ rspec-core (3.0.4)
21
+ rspec-support (~> 3.0.0)
22
+ rspec-expectations (3.0.4)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.0.0)
25
+ rspec-mocks (3.0.4)
26
+ rspec-support (~> 3.0.0)
27
+ rspec-support (3.0.4)
28
+ yard (0.8.7.3)
24
29
 
25
30
  PLATFORMS
26
31
  x86-mingw32
27
32
 
28
33
  DEPENDENCIES
29
- bundler (~> 1.3)
30
- dicom (~> 0.9.5)
31
- mocha (~> 0.13)
32
- rake (~> 0.9.6)
33
- rspec (~> 2.13)
34
+ bundler (~> 1.6)
35
+ dicom (~> 0.9, >= 0.9.6)
36
+ mocha (~> 1.1)
37
+ rake (~> 10.3)
38
+ redcarpet (~> 3.1)
39
+ rspec (~> 3.0)
34
40
  rtp-connect!
35
- yard (~> 0.8.5)
41
+ yard (~> 0.8, >= 0.8.7)
@@ -1,4 +1,4 @@
1
- = RTPConnect
1
+ # RTPConnect
2
2
 
3
3
  The RTPConnect library allows you to read, edit and write RTPConnect files in Ruby.
4
4
  RTPConnect is a file format used in radiotherapy (e.g. Mosaiq) for export & import
@@ -6,67 +6,78 @@ of treatment planning data. The library is written entirely in Ruby and has no
6
6
  external dependencies.
7
7
 
8
8
 
9
- == INSTALLATION
9
+ ## INSTALLATION
10
10
 
11
11
  gem install rtp-connect
12
12
 
13
13
 
14
- == REQUIREMENTS
14
+ ## REQUIREMENTS
15
15
 
16
16
  * Ruby 1.9.3 (or higher)
17
17
 
18
18
 
19
- == BASIC USAGE
19
+ ## BASIC USAGE
20
20
 
21
- === Load & Include
21
+ ### Load & Include
22
22
 
23
- require 'rtp-connect'
24
- include RTP
23
+ require 'rtp-connect'
24
+ include RTP
25
25
 
26
- === Read, modify and write
26
+ ### Read, modify and write
27
27
 
28
- # Read file:
29
- rtp = Plan.read('some_file.rtp')
30
- # Extract the Patient's Name:
31
- name = rtp.patient_last_name
32
- # Modify the Patient's Name:
33
- rtp.patient_last_name = 'Anonymous'
34
- # Write to file:
35
- rtp.write('new_file.rtp')
28
+ # Read file:
29
+ rtp = Plan.read('some_file.rtp')
30
+ # Extract the Patient's Name:
31
+ name = rtp.patient_last_name
32
+ # Modify the Patient's Name:
33
+ rtp.patient_last_name = 'Anonymous'
34
+ # Write to file:
35
+ rtp.write('new_file.rtp')
36
36
 
37
- === Create a new Plan Definition Record from scratch
37
+ ### Create a new Plan Definition Record from scratch
38
38
 
39
- # Create the instance:
40
- rtp = Plan.new
41
- # Set the Patient's ID attribute:
42
- rtp.patient_id = '12345'
43
- # Export the instance to an RTP string (with CRC):
44
- output = rtp.to_s
39
+ # Create the instance:
40
+ rtp = Plan.new
41
+ # Set the Patient's ID attribute:
42
+ rtp.patient_id = '12345'
43
+ # Export the instance to an RTP string (with CRC):
44
+ output = rtp.to_s
45
45
 
46
- === Convert an RTP file to DICOM:
46
+ ### Fix invalid RTP files:
47
47
 
48
- p = Plan.read('some_file.rtp')
49
- dcm = p.to_dcm
50
- dcm.write('rtplan.dcm')
48
+ # Read an RTP file containing invalid checksum(s):
49
+ rtp = Plan.read('invalid_crc.rtp', ignore_crc: true)
50
+ # Read an RTP file containing unknown record type(s):
51
+ rtp = Plan.read('custom.rtp', skip_unknown: true)
52
+ # Read an RTP file containing invalid CSV format:
53
+ rtp = Plan.read('invalid_csv.rtp', repair: true)
54
+ # Write a corrected RTP file:
55
+ rtp.write('valid.rtp')
51
56
 
52
- === Log settings
57
+ ### Convert an RTP file to DICOM:
53
58
 
54
- # Change the log level so that only error messages are displayed:
55
- RTP.logger.level = Logger::ERROR
56
- # Setting up a simple file log:
57
- l = Logger.new('my_logfile.log')
58
- RTP.logger = l
59
- # Create a logger which ages logfile daily/monthly:
60
- RTP.logger = Logger.new('foo.log', 'daily')
61
- RTP.logger = Logger.new('foo.log', 'monthly')
59
+ p = Plan.read('some_file.rtp')
60
+ dcm = p.to_dcm
61
+ dcm.write('rtplan.dcm')
62
62
 
63
- === Scripts
63
+ ### Log settings
64
+
65
+ # Change the log level so that only error messages are displayed:
66
+ RTP.logger.level = Logger::ERROR
67
+ # Setting up a simple file log:
68
+ l = Logger.new('my_logfile.log')
69
+ RTP.logger = l
70
+ # Create a logger which ages logfile daily/monthly:
71
+ RTP.logger = Logger.new('foo.log', 'daily')
72
+ RTP.logger = Logger.new('foo.log', 'monthly')
73
+
74
+ ### Scripts
64
75
 
65
76
  For more comprehensive and useful examples, check out the scripts folder
66
77
  which contains various Ruby scripts that intends to show off real world
67
78
  usage scenarios of the RTPConnect library.
68
79
 
69
- === IRB Tip
80
+ ### IRB Tip
70
81
 
71
82
  When working with the RTPConnect library in irb, you may be annoyed with all
72
83
  the information that is printed to screen, regardless of your log level.
@@ -75,21 +86,23 @@ automatically printed to the screen. A useful hack to avoid this effect is
75
86
  to append ";0" after a command.
76
87
 
77
88
  Example:
78
- rtp = Plan.read('some_file.rtp') ;0
89
+
90
+ rtp = Plan.read('some_file.rtp') ;0
79
91
 
80
92
 
81
- == RESOURCES
93
+ ## RESOURCES
82
94
 
83
- * {Rubygems download}[https://rubygems.org/gems/rtp-connect]
84
- * {Documentation}[http://rubydoc.info/gems/rtp-connect/frames]
85
- * {Source code repository}[https://github.com/dicom/rtp-connect]
95
+ * [Rubygems download](https://rubygems.org/gems/rtp-connect)
96
+ * [Documentation](http://rubydoc.info/gems/rtp-connect/frames)
97
+ * [Source code repository](https://github.com/dicom/rtp-connect)
86
98
 
87
99
 
88
- == RESTRICTIONS
100
+ ## RESTRICTIONS
89
101
 
90
- === Supported records
102
+ ### Supported records
91
103
 
92
104
  * Plan definition [PLAN_DEF]
105
+ * Extended plan definition [EXTENDED_PLAN_DEF]
93
106
  * Prescription site [RX_DEF]
94
107
  * Site setup [SITE_SETUP_DEF]
95
108
  * Simulation field [SIM_DEF]
@@ -98,9 +111,8 @@ Example:
98
111
  * Control point record [CONTROL_PT_DEF]
99
112
  * Dose tracking record [DOSE_DEF]
100
113
 
101
- === Unsupported records
114
+ ### Unsupported records
102
115
 
103
- * Extended plan definition [EXTENDED_PLAN_DEF]
104
116
  * Document based treatment field [PDF_FIELD_DEF]
105
117
  * Multileaf collimator [MLC_DEF]
106
118
  * MLC shape [MLC_SHAPE_DEF]
@@ -109,9 +121,9 @@ Example:
109
121
  If you encounter an RTP file with an unsupported record type, please contact me.
110
122
 
111
123
 
112
- == COPYRIGHT
124
+ ## COPYRIGHT
113
125
 
114
- Copyright 2011-2013 Christoffer Lervåg
126
+ Copyright 2011-2014 Christoffer Lervåg
115
127
 
116
128
  This program is free software: you can redistribute it and/or modify
117
129
  it under the terms of the GNU General Public License as published by
@@ -127,7 +139,7 @@ You should have received a copy of the GNU General Public License
127
139
  along with this program. If not, see http://www.gnu.org/licenses/ .
128
140
 
129
141
 
130
- == ABOUT THE AUTHOR
142
+ ## ABOUT THE AUTHOR
131
143
 
132
144
  * Name: Christoffer Lervåg
133
145
  * Location: Norway
@@ -6,6 +6,7 @@ require_relative 'rtp-connect/logging'
6
6
  require_relative 'rtp-connect/record'
7
7
  # Core library:
8
8
  require_relative 'rtp-connect/plan'
9
+ require_relative 'rtp-connect/extended_plan'
9
10
  require_relative 'rtp-connect/plan_to_dcm'
10
11
  require_relative 'rtp-connect/prescription'
11
12
  require_relative 'rtp-connect/site_setup'
@@ -42,6 +42,7 @@ module RTP
42
42
  # Pairs of RTPConnect keywords and parse method names.
43
43
  PARSE_METHOD = {
44
44
  "PLAN_DEF" => :plan_definition,
45
+ "EXTENDED_PLAN_DEF" => :extended_plan,
45
46
  "RX_DEF" => :prescription_site,
46
47
  "SITE_SETUP_DEF" => :site_setup,
47
48
  "SIM_DEF" => :simulation_field,
@@ -56,50 +56,8 @@ module RTP
56
56
  # @raise [ArgumentError] if given a string containing an invalid number of elements
57
57
  #
58
58
  def self.load(string, parent)
59
- # Get the quote-less values:
60
- values = string.to_s.values
61
- low_limit = 233
62
- high_limit = 233
63
- raise ArgumentError, "Invalid argument 'string': Expected at least #{low_limit} elements, got #{values.length}." if values.length < low_limit
64
- RTP.logger.warn "The number of elements (#{values.length}) for this ControlPoint 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
65
59
  cp = self.new(parent)
66
- # Assign the values to attributes:
67
- cp.keyword = values[0]
68
- cp.field_id = values[1]
69
- cp.mlc_type = values[2]
70
- cp.mlc_leaves = values[3]
71
- cp.total_control_points = values[4]
72
- cp.control_pt_number = values[5]
73
- cp.mu_convention = values[6]
74
- cp.monitor_units = values[7]
75
- cp.wedge_position = values[8]
76
- cp.energy = values[9]
77
- cp.doserate = values[10]
78
- cp.ssd = values[11]
79
- cp.scale_convention = values[12]
80
- cp.gantry_angle = values[13]
81
- cp.gantry_dir = values[14]
82
- cp.collimator_angle = values[15]
83
- cp.collimator_dir = values[16]
84
- cp.field_x_mode = values[17]
85
- cp.field_x = values[18]
86
- cp.collimator_x1 = values[19]
87
- cp.collimator_x2 = values[20]
88
- cp.field_y_mode = values[21]
89
- cp.field_y = values[22]
90
- cp.collimator_y1 = values[23]
91
- cp.collimator_y2 = values[24]
92
- cp.couch_vertical = values[25]
93
- cp.couch_lateral = values[26]
94
- cp.couch_longitudinal = values[27]
95
- cp.couch_angle = values[28]
96
- cp.couch_dir = values[29]
97
- cp.couch_pedestal = values[30]
98
- cp.couch_ped_dir = values[31]
99
- cp.mlc_lp_a = [*values[32..131]]
100
- cp.mlc_lp_b = [*values[132..231]]
101
- cp.crc = values[-1]
102
- return cp
60
+ cp.load(string)
103
61
  end
104
62
 
105
63
  # Creates a new ControlPoint.
@@ -107,14 +65,51 @@ module RTP
107
65
  # @param [Record] parent a record which is used to determine the proper parent of this instance
108
66
  #
109
67
  def initialize(parent)
68
+ super('CONTROL_PT_DEF', 233, 233)
110
69
  # Child:
111
70
  @mlc_shape = nil
112
71
  # Parent relation (may get more than one type of record here):
113
72
  @parent = get_parent(parent.to_record, Field)
114
73
  @parent.add_control_point(self)
115
- @keyword = 'CONTROL_PT_DEF'
116
74
  @mlc_lp_a = Array.new(100)
117
75
  @mlc_lp_b = Array.new(100)
76
+ @attributes = [
77
+ # Required:
78
+ :keyword,
79
+ :field_id,
80
+ :mlc_type,
81
+ :mlc_leaves,
82
+ :total_control_points,
83
+ :control_pt_number,
84
+ :mu_convention,
85
+ :monitor_units,
86
+ :wedge_position,
87
+ :energy,
88
+ :doserate,
89
+ :ssd,
90
+ :scale_convention,
91
+ :gantry_angle,
92
+ :gantry_dir,
93
+ :collimator_angle,
94
+ :collimator_dir,
95
+ :field_x_mode,
96
+ :field_x,
97
+ :collimator_x1,
98
+ :collimator_x2,
99
+ :field_y_mode,
100
+ :field_y,
101
+ :collimator_y1,
102
+ :collimator_y2,
103
+ :couch_vertical,
104
+ :couch_lateral,
105
+ :couch_longitudinal,
106
+ :couch_angle,
107
+ :couch_dir,
108
+ :couch_pedestal,
109
+ :couch_ped_dir,
110
+ :mlc_lp_a,
111
+ :mlc_lp_b
112
+ ]
118
113
  end
119
114
 
120
115
  # Checks for equality.
@@ -145,51 +140,67 @@ module RTP
145
140
 
146
141
  # Converts the collimator_x1 attribute to proper DICOM format.
147
142
  #
143
+ # @param [Symbol] scale if set, relevant device parameters are converted from a native readout format to IEC1217 (supported values are :elekta & :varian)
148
144
  # @return [Float] the DICOM-formatted collimator_x1 attribute
149
145
  #
150
- def dcm_collimator_x1
151
- attribute = (scale_convertion? ? :collimator_y1 : :collimator_x1)
152
- target = (@field_x_mode && !@field_x_mode.empty? ? self : @parent)
153
- target.send(attribute).to_f * 10 * scale_factor
146
+ def dcm_collimator_x1(scale=nil)
147
+ coeff = 1
148
+ axis = :x
149
+ if scale == :elekta
150
+ axis = :y
151
+ coeff = -1
152
+ elsif scale == :varian
153
+ coeff = -1
154
+ end
155
+ dcm_collimator1(axis, coeff)
154
156
  end
155
157
 
156
158
  # Converts the collimator_x2 attribute to proper DICOM format.
157
159
  #
160
+ # @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
158
161
  # @return [Float] the DICOM-formatted collimator_x2 attribute
159
162
  #
160
- def dcm_collimator_x2
161
- attribute = (scale_convertion? ? :collimator_y2 : :collimator_x2)
162
- target = (@field_x_mode && !@field_x_mode.empty? ? self : @parent)
163
- target.send(attribute).to_f * 10
163
+ def dcm_collimator_x2(scale=nil)
164
+ axis = (scale == :elekta ? :y : :x)
165
+ dcm_collimator2(axis, coeff=1)
164
166
  end
165
167
 
166
168
  # Converts the collimator_y1 attribute to proper DICOM format.
167
169
  #
170
+ # @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
168
171
  # @return [Float] the DICOM-formatted collimator_y1 attribute
169
172
  #
170
- def dcm_collimator_y1
171
- attribute = (scale_convertion? ? :collimator_x1 : :collimator_y1)
172
- target = (@field_y_mode && !@field_y_mode.empty? ? self : @parent)
173
- target.send(attribute).to_f * 10 * scale_factor
173
+ def dcm_collimator_y1(scale=nil)
174
+ coeff = 1
175
+ axis = :y
176
+ if scale == :elekta
177
+ axis = :x
178
+ coeff = -1
179
+ elsif scale == :varian
180
+ coeff = -1
181
+ end
182
+ dcm_collimator1(axis, coeff)
174
183
  end
175
184
 
176
185
  # Converts the collimator_y2 attribute to proper DICOM format.
177
186
  #
187
+ # @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
178
188
  # @return [Float] the DICOM-formatted collimator_y2 attribute
179
189
  #
180
- def dcm_collimator_y2
181
- attribute = (scale_convertion? ? :collimator_x2 : :collimator_y2)
182
- target = (@field_y_mode && !@field_y_mode.empty? ? self : @parent)
183
- target.send(attribute).to_f * 10
190
+ def dcm_collimator_y2(scale=nil)
191
+ axis = (scale == :elekta ? :x : :y)
192
+ dcm_collimator2(axis, coeff=1)
184
193
  end
185
194
 
186
195
  # Converts the mlc_lp_a & mlc_lp_b attributes to a proper DICOM formatted string.
187
196
  #
197
+ # @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
188
198
  # @return [String] the DICOM-formatted leaf pair positions
189
199
  #
190
- def dcm_mlc_positions
200
+ def dcm_mlc_positions(scale=nil)
201
+ coeff = (scale == :elekta ? -1 : 1)
191
202
  # As with the collimators, the first side (1/a) may need scale invertion:
192
- pos_a = @mlc_lp_a.collect{|p| (p.to_f * 10 * scale_factor).round(1) unless p.empty?}.compact
203
+ pos_a = @mlc_lp_a.collect{|p| (p.to_f * 10 * coeff).round(1) unless p.empty?}.compact
193
204
  pos_b = @mlc_lp_b.collect{|p| (p.to_f * 10).round(1) unless p.empty?}.compact
194
205
  (pos_a + pos_b).join("\\")
195
206
  end
@@ -219,7 +230,7 @@ module RTP
219
230
  # @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
220
231
  #
221
232
  def values
222
- return [
233
+ [
223
234
  @keyword,
224
235
  @field_id,
225
236
  @mlc_type,
@@ -289,9 +300,7 @@ module RTP
289
300
  # @param [Array<nil, #to_s>] array the new attribute values
290
301
  #
291
302
  def mlc_lp_a=(array)
292
- array = array.to_a
293
- raise ArgumentError, "Invalid argument 'array'. Expected length 100, got #{array.length}." unless array.length == 100
294
- @mlc_lp_a = array.collect! {|e| e && e.to_s.strip}
303
+ @mlc_lp_a = array.to_a.validate_and_process(100)
295
304
  end
296
305
 
297
306
  # Sets the mlc_lp_b attribute.
@@ -301,21 +310,7 @@ module RTP
301
310
  # @param [Array<nil, #to_s>] array the new attribute values
302
311
  #
303
312
  def mlc_lp_b=(array)
304
- array = array.to_a
305
- raise ArgumentError, "Invalid argument 'array'. Expected length 100, got #{array.length}." unless array.length == 100
306
- @mlc_lp_b = array.collect! {|e| e && e.to_s.strip}
307
- end
308
-
309
- # Sets the keyword attribute.
310
- #
311
- # @note Since only a specific string is accepted, this is more of an argument check than a traditional setter method
312
- # @param [#to_s] value the new attribute value
313
- # @raise [ArgumentError] if given an unexpected keyword
314
- #
315
- def keyword=(value)
316
- value = value.to_s.upcase
317
- raise ArgumentError, "Invalid keyword. Expected 'CONTROL_PT_DEF', got #{value}." unless value == "CONTROL_PT_DEF"
318
- @keyword = value
313
+ @mlc_lp_b = array.to_a.validate_and_process(100)
319
314
  end
320
315
 
321
316
  # Sets the field_id attribute.
@@ -577,31 +572,78 @@ module RTP
577
572
  #
578
573
  alias_method :state, :values
579
574
 
580
- # Checks whether the contents of the this record indicates that scale
581
- # convertion is to be applied. This convertion entails converting a value
582
- # from IEC1217 format to the target machine's native readout format.
583
- # Note that the scope of this scale conversion is not precisely known (the
584
- # current implementation is based on a few observations made from a single
585
- # RTP file).
575
+ # Converts the collimator attribute to proper DICOM format.
586
576
  #
587
- # @return [Boolean] true if the scale convention attribute indicates scale convertion
577
+ # @param [Symbol] axis a representation for the axis of interest (x or y)
578
+ # @param [Integer] coeff a coeffecient (of -1 or 1) which the attribute is multiplied with
579
+ # @return [Float] the DICOM-formatted collimator attribute
588
580
  #
589
- def scale_convertion?
590
- # A scale convention of 1 means that geometric parameters are represented
591
- # in the target machine's native readout format, as opposed to the IEC 1217
592
- # convention. The consequences of this is not totally clear, but at least for
593
- # an Elekta device, there are a number of convertions which seems to be indicated.
594
- @scale_convention.to_i == 1 ? true : false
581
+ def dcm_collimator1(axis, coeff)
582
+ mode = self.send("field_#{axis}_mode")
583
+ if mode && !mode.empty?
584
+ target = self
585
+ else
586
+ target = @parent
587
+ end
588
+ target.send("collimator_#{axis}1").to_f * 10 * coeff
595
589
  end
596
590
 
597
- # Gives a factor used for scale convertion, which depends on the
598
- # 'scale_convention' attribute.
591
+ # Converts the collimator attribute to proper DICOM format.
599
592
  #
600
- # @param [Numerical] value the value to process
601
- # @return [Numerical] the scale converted value
593
+ # @param [Symbol] axis a representation for the axis of interest (x or y)
594
+ # @param [Integer] coeff a coeffecient (of -1 or 1) which the attribute is multiplied with
595
+ # @return [Float] the DICOM-formatted collimator attribute
602
596
  #
603
- def scale_factor
604
- scale_convertion? ? -1 : 1
597
+ def dcm_collimator2(axis, coeff)
598
+ mode = self.send("field_#{axis}_mode")
599
+ if mode && !mode.empty?
600
+ target = self
601
+ else
602
+ target = @parent
603
+ end
604
+ target.send("collimator_#{axis}2").to_f * 10 * coeff
605
+ end
606
+
607
+ # Sets the attributes of the record instance.
608
+ #
609
+ # @param [Array<String>] values the record attributes (as parsed from a record string)
610
+ #
611
+ def set_attributes(values)
612
+ self.keyword = values[0]
613
+ @field_id = values[1]
614
+ @mlc_type = values[2]
615
+ @mlc_leaves = values[3]
616
+ @total_control_points = values[4]
617
+ @control_pt_number = values[5]
618
+ @mu_convention = values[6]
619
+ @monitor_units = values[7]
620
+ @wedge_position = values[8]
621
+ @energy = values[9]
622
+ @doserate = values[10]
623
+ @ssd = values[11]
624
+ @scale_convention = values[12]
625
+ @gantry_angle = values[13]
626
+ @gantry_dir = values[14]
627
+ @collimator_angle = values[15]
628
+ @collimator_dir = values[16]
629
+ @field_x_mode = values[17]
630
+ @field_x = values[18]
631
+ @collimator_x1 = values[19]
632
+ @collimator_x2 = values[20]
633
+ @field_y_mode = values[21]
634
+ @field_y = values[22]
635
+ @collimator_y1 = values[23]
636
+ @collimator_y2 = values[24]
637
+ @couch_vertical = values[25]
638
+ @couch_lateral = values[26]
639
+ @couch_longitudinal = values[27]
640
+ @couch_angle = values[28]
641
+ @couch_dir = values[29]
642
+ @couch_pedestal = values[30]
643
+ @couch_ped_dir = values[31]
644
+ @mlc_lp_a = [*values[32..131]]
645
+ @mlc_lp_b = [*values[132..231]]
646
+ @crc = values[-1]
605
647
  end
606
648
 
607
649
  end