rtp-connect 1.5 → 1.6

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.
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,22 @@
1
+ = 1.6
2
+
3
+ === 12th December, 2013
4
+
5
+ * Plan#to_dcm improvements:
6
+ * Added support for VMAT by improving the handling of control point conversion.
7
+ * Made dose reference sequences optional.
8
+ * Order beam limiting device items alphabetically.
9
+ * Added rudimentary support for scale conversion (scale convention = 1 in control point records).
10
+ * More robust extraction of jaw position.
11
+ * More robust handling of cases with missing structure set in the RTP file.
12
+ * Add support for tolerance table sequence.
13
+ * Fixed a bug with missing leaf boundary value for 80 and 160 leaf MLCs.
14
+ * Switched to using fractional cumulative meterset weight, which seems to be more commonly used in commercial systems.
15
+ * Don't create an SSD DICOM element if the SSD attribute in the RTP file is undefined.
16
+ * Only write control point attributes which have changed since the previous/initial control point of each beam.
17
+ * Make sure that the last cumulative meterset weight exactly equals the final cumulative meterset weight.
18
+
19
+
1
20
  = 1.5
2
21
 
3
22
  === 24th October, 2013
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rtp-connect (1.5)
4
+ rtp-connect (1.6)
5
5
 
6
6
  GEM
7
7
  remote: http://www.rubygems.org/
@@ -143,6 +143,57 @@ module RTP
143
143
  return Array.new
144
144
  end
145
145
 
146
+ # Converts the collimator_x1 attribute to proper DICOM format.
147
+ #
148
+ # @return [Float] the DICOM-formatted collimator_x1 attribute
149
+ #
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
154
+ end
155
+
156
+ # Converts the collimator_x2 attribute to proper DICOM format.
157
+ #
158
+ # @return [Float] the DICOM-formatted collimator_x2 attribute
159
+ #
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
164
+ end
165
+
166
+ # Converts the collimator_y1 attribute to proper DICOM format.
167
+ #
168
+ # @return [Float] the DICOM-formatted collimator_y1 attribute
169
+ #
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
174
+ end
175
+
176
+ # Converts the collimator_y2 attribute to proper DICOM format.
177
+ #
178
+ # @return [Float] the DICOM-formatted collimator_y2 attribute
179
+ #
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
184
+ end
185
+
186
+ # Converts the mlc_lp_a & mlc_lp_b attributes to a proper DICOM formatted string.
187
+ #
188
+ # @return [String] the DICOM-formatted leaf pair positions
189
+ #
190
+ def dcm_mlc_positions
191
+ # 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
193
+ pos_b = @mlc_lp_b.collect{|p| (p.to_f * 10).round(1) unless p.empty?}.compact
194
+ (pos_a + pos_b).join("\\")
195
+ end
196
+
146
197
  # Computes a hash code for this object.
147
198
  #
148
199
  # @note Two objects with the same attributes will have the same hash code.
@@ -526,6 +577,33 @@ module RTP
526
577
  #
527
578
  alias_method :state, :values
528
579
 
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).
586
+ #
587
+ # @return [Boolean] true if the scale convention attribute indicates scale convertion
588
+ #
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
595
+ end
596
+
597
+ # Gives a factor used for scale convertion, which depends on the
598
+ # 'scale_convention' attribute.
599
+ #
600
+ # @param [Numerical] value the value to process
601
+ # @return [Numerical] the scale converted value
602
+ #
603
+ def scale_factor
604
+ scale_convertion? ? -1 : 1
605
+ end
606
+
529
607
  end
530
608
 
531
609
  end
@@ -20,7 +20,7 @@ module RTP
20
20
  -15, -5, 5, 15, 25, 35, 45, 55, 65, 75, 85, 95, 105, 115, 125, 135, 200
21
21
  ]
22
22
  when 40
23
- Array.new(nr_leaves) {|i| (i * 400 / nr_leaves.to_f - 200).to_i}
23
+ Array.new(nr_leaves+1) {|i| (i * 400 / nr_leaves.to_f - 200).to_i}
24
24
  when 41
25
25
  [-200, -195, -185, -175, -165, -155, -145, -135, -125, -115,
26
26
  -105, -95, -85, -75, -65, -55, -45, -35, -25, -15, -5, 5, 15, 25, 35, 45,
@@ -33,7 +33,7 @@ module RTP
33
33
  70, 75, 80, 85, 90, 95, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200
34
34
  ]
35
35
  when 80
36
- Array.new(nr_leaves) {|i| (i * 400 / nr_leaves.to_f - 200).to_i}
36
+ Array.new(nr_leaves+1) {|i| (i * 400 / nr_leaves.to_f - 200).to_i}
37
37
  else
38
38
  raise ArgumentError, "Unsupported number of leaves: #{nr_leaves}"
39
39
  end
@@ -5,11 +5,12 @@ module RTP
5
5
  # Converts the Plan (and child) records to a
6
6
  # DICOM::DObject of modality RTPLAN.
7
7
  #
8
- # @note Only static photon plans have been tested.
9
- # Electron beams or dynamic photon beams may give an invalid DICOM file.
8
+ # @note Only photon plans have been tested.
9
+ # Electron beams beams may give an invalid DICOM file.
10
10
  # Also note that, due to limitations in the RTP file format, some original
11
11
  # values can not be recreated, like e.g. Study UID or Series UID.
12
12
  # @param [Hash] options the options to use for creating the DICOM object
13
+ # @option options [Boolean] :dose_ref if set, Dose Reference & Referenced Dose Reference sequences will be included in the generated DICOM file
13
14
  # @option options [String] :manufacturer the value used for the manufacturer tag (0008,0070) in the beam sequence
14
15
  # @option options [String] :model the value used for the manufacturer's model name tag (0008,1090) in the beam sequence
15
16
  # @option options [String] :serial_number the value used for the device serial number tag (0018,1000) in the beam sequence
@@ -103,27 +104,34 @@ module RTP
103
104
  # RT Plan Time:
104
105
  plan_time = @plan_time.empty? ? Time.now.strftime("%H%M%S") : @plan_time
105
106
  DICOM::Element.new('300A,0007', plan_time, :parent => dcm)
106
- # RT Plan Geometry:
107
- DICOM::Element.new('300A,000C', 'PATIENT', :parent => dcm)
108
107
  # Approval Status:
109
108
  DICOM::Element.new('300E,0002', 'UNAPPROVED', :parent => dcm)
110
109
  #
111
110
  # SEQUENCES:
112
111
  #
113
- #
114
- # Referenced Structure Set Sequence:
115
- #
116
- ss_seq = DICOM::Sequence.new('300C,0060', :parent => dcm)
117
- ss_item = DICOM::Item.new(:parent => ss_seq)
118
- # Referenced SOP Class UID:
119
- DICOM::Element.new('0008,1150', '1.2.840.10008.5.1.4.1.1.481.3', :parent => ss_item)
120
- # Referenced SOP Instance UID (if an original UID is not present, we make up a UID):
121
- begin
122
- ref_ss_uid = p.site_setup.structure_set_uid.empty? ? DICOM.generate_uid : p.site_setup.structure_set_uid
123
- rescue
124
- ref_ss_uid = DICOM.generate_uid
112
+ # Tolerance Table Sequence:
113
+ if p && p.fields.first && !p.fields.first.tolerance_table.empty?
114
+ tt_seq = DICOM::Sequence.new('300A,0040', :parent => dcm)
115
+ tt_item = DICOM::Item.new(:parent => tt_seq)
116
+ # Tolerance Table Number:
117
+ DICOM::Element.new('300A,0042', p.fields.first.tolerance_table, :parent => tt_item)
118
+ end
119
+ # Structure set information:
120
+ if p && p.site_setup && !p.site_setup.structure_set_uid.empty?
121
+ #
122
+ # Referenced Structure Set Sequence:
123
+ #
124
+ ss_seq = DICOM::Sequence.new('300C,0060', :parent => dcm)
125
+ ss_item = DICOM::Item.new(:parent => ss_seq)
126
+ # Referenced SOP Class UID:
127
+ DICOM::Element.new('0008,1150', '1.2.840.10008.5.1.4.1.1.481.3', :parent => ss_item)
128
+ DICOM::Element.new('0008,1155', p.site_setup.structure_set_uid, :parent => ss_item)
129
+ # RT Plan Geometry:
130
+ DICOM::Element.new('300A,000C', 'PATIENT', :parent => dcm)
131
+ else
132
+ # RT Plan Geometry:
133
+ DICOM::Element.new('300A,000C', 'TREATMENT_DEVICE', :parent => dcm)
125
134
  end
126
- DICOM::Element.new('0008,1155', ref_ss_uid, :parent => ss_item)
127
135
  #
128
136
  # Patient Setup Sequence:
129
137
  #
@@ -143,16 +151,7 @@ module RTP
143
151
  #
144
152
  # Dose Reference Sequence:
145
153
  #
146
- dr_seq = DICOM::Sequence.new('300A,0010', :parent => dcm)
147
- dr_item = DICOM::Item.new(:parent => dr_seq)
148
- # Dose Reference Number:
149
- DICOM::Element.new('300A,0012', '1', :parent => dr_item)
150
- # Dose Reference Structure Type:
151
- DICOM::Element.new('300A,0014', 'SITE', :parent => dr_item)
152
- # Dose Reference Description:
153
- DICOM::Element.new('300A,0016', plan_name, :parent => dr_item)
154
- # Dose Reference Type:
155
- DICOM::Element.new('300A,0020', 'TARGET', :parent => dr_item)
154
+ create_dose_reference(dcm, plan_name) if options[:dose_ref]
156
155
  #
157
156
  # Fraction Group Sequence:
158
157
  #
@@ -183,6 +182,8 @@ module RTP
183
182
  unless field.modality == 'Unspecified'
184
183
  # If this is an electron beam, a warning should be printed, as these are less reliably converted:
185
184
  logger.warn("This is not a photon beam (#{field.modality}). Beware that DICOM conversion of Electron beams are experimental, and other modalities are unsupported.") if field.modality != 'Xrays'
185
+ # Reset control point 'current value' attributes:
186
+ reset_cp_current_attributes
186
187
  # Beam number and name:
187
188
  beam_number = field.extended_field ? field.extended_field.original_beam_number : (i + 1).to_s
188
189
  beam_name = field.extended_field ? field.extended_field.original_beam_name : field.field_name
@@ -220,6 +221,7 @@ module RTP
220
221
  beam_type = case field.treatment_type
221
222
  when 'Static' then 'STATIC'
222
223
  when 'StepNShoot' then 'STATIC'
224
+ when 'VMAT' then 'DYNAMIC'
223
225
  else logger.error("The beam type (treatment type) #{field.treatment_type} is not yet supported.")
224
226
  end
225
227
  DICOM::Element.new('300A,00C4', beam_type, :parent => b_item)
@@ -241,36 +243,13 @@ module RTP
241
243
  # Number of Blocks:
242
244
  DICOM::Element.new('300A,00F0', (field.block.empty? ? '0' : '1'), :parent => b_item)
243
245
  # Final Cumulative Meterset Weight:
244
- DICOM::Element.new('300A,010E', field.field_monitor_units, :parent => b_item)
246
+ DICOM::Element.new('300A,010E', 1, :parent => b_item)
245
247
  # Referenced Patient Setup Number:
246
248
  DICOM::Element.new('300C,006A', '1', :parent => b_item)
247
249
  #
248
250
  # Beam Limiting Device Sequence:
249
251
  #
250
- bl_seq = DICOM::Sequence.new('300A,00B6', :parent => b_item)
251
- # Always create one ASYMY item:
252
- bl_item_y = DICOM::Item.new(:parent => bl_seq)
253
- # RT Beam Limiting Device Type:
254
- DICOM::Element.new('300A,00B8', "ASYMY", :parent => bl_item_y)
255
- # Number of Leaf/Jaw Pairs:
256
- DICOM::Element.new('300A,00BC', "1", :parent => bl_item_y)
257
- # The ASYMX item ('backup jaws') only exsists on some models:
258
- if ['SYM', 'ASY'].include?(field.field_x_mode.upcase)
259
- bl_item_x = DICOM::Item.new(:parent => bl_seq)
260
- DICOM::Element.new('300A,00B8', "ASYMX", :parent => bl_item_x)
261
- DICOM::Element.new('300A,00BC', "1", :parent => bl_item_x)
262
- end
263
- # MLCX item is only created if leaves are defined:
264
- # (NB: The RTP file doesn't specify leaf position boundaries, so we
265
- # have to set these based on a set of known MLC types, their number
266
- # of leaves, and their leaf boundary positions.)
267
- if field.control_points.length > 0
268
- bl_item_mlcx = DICOM::Item.new(:parent => bl_seq)
269
- DICOM::Element.new('300A,00B8', "MLCX", :parent => bl_item_mlcx)
270
- num_leaves = field.control_points.first.mlc_leaves.to_i
271
- DICOM::Element.new('300A,00BC', num_leaves.to_s, :parent => bl_item_mlcx)
272
- DICOM::Element.new('300A,00BE', "#{RTP.leaf_boundaries(num_leaves).join("\\")}", :parent => bl_item_mlcx)
273
- end
252
+ create_beam_limiting_devices(b_item, field)
274
253
  #
275
254
  # Block Sequence (if any):
276
255
  # FIXME: It seems that the Block Sequence (300A,00F4) may be
@@ -340,189 +319,25 @@ module RTP
340
319
  DICOM::Element.new('300A,012C', '', :parent => cp_item)
341
320
  end
342
321
  # Source to Surface Distance:
343
- DICOM::Element.new('300A,0130', "#{field.ssd.to_f * 10}", :parent => cp_item)
322
+ add_ssd(field.ssd, cp_item)
344
323
  # Cumulative Meterset Weight:
345
- DICOM::Element.new('300A,0134', "0.0", :parent => cp_item)
324
+ DICOM::Element.new('300A,0134', '0', :parent => cp_item)
346
325
  # Beam Limiting Device Position Sequence:
347
- dp_seq = DICOM::Sequence.new('300A,011A', :parent => cp_item)
348
- # Always create one ASYMY item:
349
- dp_item_y = DICOM::Item.new(:parent => dp_seq)
350
- # RT Beam Limiting Device Type:
351
- DICOM::Element.new('300A,00B8', "ASYMY", :parent => dp_item_y)
352
- # Leaf/Jaw Positions:
353
- DICOM::Element.new('300A,011C', "#{field.collimator_y1.to_f * 10}\\#{field.collimator_y2.to_f * 10}", :parent => dp_item_y)
354
- # The ASYMX item ('backup jaws') only exsists on some models:
355
- if ['SYM', 'ASY'].include?(field.field_x_mode.upcase)
356
- dp_item_x = DICOM::Item.new(:parent => dp_seq)
357
- DICOM::Element.new('300A,00B8', "ASYMX", :parent => dp_item_x)
358
- DICOM::Element.new('300A,011C', "#{field.collimator_x1.to_f * 10}\\#{field.collimator_x2.to_f * 10}", :parent => dp_item_x)
359
- end
360
- # MLCX:
361
- if field.control_points.length > 0
362
- dp_item_mlcx = DICOM::Item.new(:parent => dp_seq)
363
- # RT Beam Limiting Device Type:
364
- DICOM::Element.new('300A,00B8', "MLCX", :parent => dp_item_mlcx)
365
- # Leaf/Jaw Positions:
366
- pos_a = field.control_points.first.mlc_lp_a.collect{|p| (p.to_f * 10).round(2) unless p.empty?}.compact
367
- pos_b = field.control_points.first.mlc_lp_b.collect{|p| (p.to_f * 10).round(2) unless p.empty?}.compact
368
- leaf_pos = "#{pos_a.join("\\")}\\#{pos_b.join("\\")}"
369
- DICOM::Element.new('300A,011C', leaf_pos, :parent => dp_item_mlcx)
370
- end
326
+ create_beam_limiting_device_positions(cp_item, field.control_points.first)
371
327
  # Referenced Dose Reference Sequence:
372
- rd_seq = DICOM::Sequence.new('300C,0050', :parent => cp_item)
373
- rd_item = DICOM::Item.new(:parent => rd_seq)
374
- # Cumulative Dose Reference Coeffecient:
375
- DICOM::Element.new('300A,010C', '', :parent => rd_item)
376
- # Referenced Dose Reference Number:
377
- DICOM::Element.new('300C,0051', '1', :parent => rd_item)
328
+ create_referenced_dose_reference(cp_item) if options[:dose_ref]
378
329
  # Second CP:
379
330
  cp_item = DICOM::Item.new(:parent => cp_seq)
380
331
  # Control Point Index:
381
332
  DICOM::Element.new('300A,0112', "1", :parent => cp_item)
382
333
  # Cumulative Meterset Weight:
383
- DICOM::Element.new('300A,0134', field.field_monitor_units, :parent => cp_item)
334
+ DICOM::Element.new('300A,0134', '1', :parent => cp_item)
384
335
  else
385
- # When we have multiple (2n) control points, iterate and pick settings from the CPs:
386
- field.control_points.each_slice(2) do |cp1, cp2|
387
- cp_item1 = DICOM::Item.new(:parent => cp_seq)
388
- cp_item2 = DICOM::Item.new(:parent => cp_seq)
389
- # First control point:
390
- # Control Point Index:
391
- DICOM::Element.new('300A,0112', "#{cp1.index}", :parent => cp_item1)
392
- # Nominal Beam Energy:
393
- DICOM::Element.new('300A,0114', "#{cp1.energy.to_f}", :parent => cp_item1)
394
- # Dose Rate Set:
395
- DICOM::Element.new('300A,0115', cp1.doserate, :parent => cp_item1)
396
- # Gantry Angle:
397
- DICOM::Element.new('300A,011E', cp1.gantry_angle, :parent => cp_item1)
398
- # Gantry Rotation Direction:
399
- DICOM::Element.new('300A,011F', (cp1.gantry_dir.empty? ? 'NONE' : cp1.gantry_dir), :parent => cp_item1)
400
- # Beam Limiting Device Angle:
401
- DICOM::Element.new('300A,0120', cp1.collimator_angle, :parent => cp_item1)
402
- # Beam Limiting Device Rotation Direction:
403
- DICOM::Element.new('300A,0121', (cp1.collimator_dir.empty? ? 'NONE' : cp1.collimator_dir), :parent => cp_item1)
404
- # Patient Support Angle:
405
- DICOM::Element.new('300A,0122', cp1.couch_pedestal, :parent => cp_item1)
406
- # Patient Support Rotation Direction:
407
- DICOM::Element.new('300A,0123', (cp1.couch_ped_dir.empty? ? 'NONE' : cp1.couch_ped_dir), :parent => cp_item1)
408
- # Table Top Eccentric Angle:
409
- DICOM::Element.new('300A,0125', cp1.couch_angle, :parent => cp_item1)
410
- # Table Top Eccentric Rotation Direction:
411
- DICOM::Element.new('300A,0126', (cp1.couch_dir.empty? ? 'NONE' : cp1.couch_dir), :parent => cp_item1)
412
- # Table Top Vertical Position:
413
- couch_vert = cp1.couch_vertical.empty? ? '' : (cp1.couch_vertical.to_f * 10).to_s
414
- DICOM::Element.new('300A,0128', couch_vert, :parent => cp_item1)
415
- # Table Top Longitudinal Position:
416
- couch_long = cp1.couch_longitudinal.empty? ? '' : (cp1.couch_longitudinal.to_f * 10).to_s
417
- DICOM::Element.new('300A,0129', couch_long, :parent => cp_item1)
418
- # Table Top Lateral Position:
419
- couch_lat = cp1.couch_lateral.empty? ? '' : (cp1.couch_lateral.to_f * 10).to_s
420
- DICOM::Element.new('300A,012A', couch_lat, :parent => cp_item1)
421
- # Isocenter Position (x\y\z):
422
- if p.site_setup
423
- DICOM::Element.new('300A,012C', "#{(p.site_setup.iso_pos_x.to_f * 10).round(2)}\\#{(p.site_setup.iso_pos_y.to_f * 10).round(2)}\\#{(p.site_setup.iso_pos_z.to_f * 10).round(2)}", :parent => cp_item1)
424
- else
425
- logger.warn("No Site Setup record exists for this plan. Unable to provide an isosenter position.")
426
- DICOM::Element.new('300A,012C', '', :parent => cp_item1)
427
- end
428
- # Source to Surface Distance:
429
- DICOM::Element.new('300A,0130', "#{cp1.ssd.to_f * 10}", :parent => cp_item1)
430
- # Cumulative Meterset Weight:
431
- mu_weight = (cp1.monitor_units.to_f * field.field_monitor_units.to_f).round(4)
432
- DICOM::Element.new('300A,0134', "#{mu_weight}", :parent => cp_item1)
433
- # Beam Limiting Device Position Sequence:
434
- dp_seq = DICOM::Sequence.new('300A,011A', :parent => cp_item1)
435
- # Always create one ASYMY item:
436
- dp_item_y = DICOM::Item.new(:parent => dp_seq)
437
- # RT Beam Limiting Device Type:
438
- DICOM::Element.new('300A,00B8', "ASYMY", :parent => dp_item_y)
439
- # Leaf/Jaw Positions:
440
- DICOM::Element.new('300A,011C', "#{field.collimator_y1.to_f * 10}\\#{field.collimator_y2.to_f * 10}", :parent => dp_item_y)
441
- # The ASYMX item ('backup jaws') only exsists on some models:
442
- if ['SYM', 'ASY'].include?(field.field_x_mode.upcase)
443
- dp_item_x = DICOM::Item.new(:parent => dp_seq)
444
- DICOM::Element.new('300A,00B8', "ASYMX", :parent => dp_item_x)
445
- DICOM::Element.new('300A,011C', "#{field.collimator_x1.to_f * 10}\\#{field.collimator_x2.to_f * 10}", :parent => dp_item_x)
446
- end
447
- # MLCX:
448
- dp_item_mlcx = DICOM::Item.new(:parent => dp_seq)
449
- # RT Beam Limiting Device Type:
450
- DICOM::Element.new('300A,00B8', "MLCX", :parent => dp_item_mlcx)
451
- # Leaf/Jaw Positions:
452
- pos_a = cp1.mlc_lp_a.collect{|p| (p.to_f * 10).round(2) unless p.empty?}.compact
453
- pos_b = cp1.mlc_lp_b.collect{|p| (p.to_f * 10).round(2) unless p.empty?}.compact
454
- leaf_pos = "#{pos_a.join("\\")}\\#{pos_b.join("\\")}"
455
- DICOM::Element.new('300A,011C', leaf_pos, :parent => dp_item_mlcx)
456
- # Referenced Dose Reference Sequence:
457
- rd_seq = DICOM::Sequence.new('300C,0050', :parent => cp_item1)
458
- rd_item = DICOM::Item.new(:parent => rd_seq)
459
- # Cumulative Dose Reference Coeffecient:
460
- DICOM::Element.new('300A,010C', '', :parent => rd_item)
461
- # Referenced Dose Reference Number:
462
- DICOM::Element.new('300C,0051', '1', :parent => rd_item)
463
- # Second control point:
464
- # Always include index and cumulative weight:
465
- DICOM::Element.new('300A,0112', "#{cp2.index}", :parent => cp_item2)
466
- mu_weight = (cp2.monitor_units.to_f * field.field_monitor_units.to_f).round(4)
467
- DICOM::Element.new('300A,0134', "#{mu_weight}", :parent => cp_item2)
468
- # The other parameters are only included if they have changed from the previous control point:
469
- # Nominal Beam Energy:
470
- DICOM::Element.new('300A,0114', "#{cp2.energy.to_f}", :parent => cp_item2) if cp2.energy != cp1.energy
471
- # Dose Rate Set:
472
- DICOM::Element.new('300A,0115', cp1.doserate, :parent => cp_item2) if cp2.doserate != cp1.doserate
473
- # Gantry Angle:
474
- DICOM::Element.new('300A,011E', cp2.gantry_angle, :parent => cp_item2) if cp2.gantry_angle != cp1.gantry_angle
475
- # Gantry Rotation Direction:
476
- DICOM::Element.new('300A,011F', (cp2.gantry_dir.empty? ? 'NONE' : cp2.gantry_dir), :parent => cp_item2) if cp2.gantry_dir != cp1.gantry_dir
477
- # Beam Limiting Device Angle:
478
- DICOM::Element.new('300A,0120', cp2.collimator_angle, :parent => cp_item2) if cp2.collimator_angle != cp1.collimator_angle
479
- # Beam Limiting Device Rotation Direction:
480
- DICOM::Element.new('300A,0121', (cp2.collimator_dir.empty? ? 'NONE' : cp2.collimator_dir), :parent => cp_item2) if cp2.collimator_dir != cp1.collimator_dir
481
- # Patient Support Angle:
482
- DICOM::Element.new('300A,0122', cp2.couch_pedestal, :parent => cp_item2) if cp2.couch_pedestal != cp1.couch_pedestal
483
- # Patient Support Rotation Direction:
484
- DICOM::Element.new('300A,0123', (cp2.couch_ped_dir.empty? ? 'NONE' : cp2.couch_ped_dir), :parent => cp_item2) if cp2.couch_ped_dir != cp1.couch_ped_dir
485
- # Table Top Eccentric Angle:
486
- DICOM::Element.new('300A,0125', cp2.couch_angle, :parent => cp_item2) if cp2.couch_angle != cp1.couch_angle
487
- # Table Top Eccentric Rotation Direction:
488
- DICOM::Element.new('300A,0126', (cp2.couch_dir.empty? ? 'NONE' : cp2.couch_dir), :parent => cp_item2) if cp2.couch_dir != cp1.couch_dir
489
- # Table Top Vertical Position:
490
- couch_vert = cp2.couch_vertical.empty? ? '' : (cp2.couch_vertical.to_f * 10).to_s
491
- DICOM::Element.new('300A,0128', couch_vert, :parent => cp_item2) if cp2.couch_vertical != cp1.couch_vertical
492
- # Table Top Longitudinal Position:
493
- couch_long = cp2.couch_longitudinal.empty? ? '' : (cp2.couch_longitudinal.to_f * 10).to_s
494
- DICOM::Element.new('300A,0129', couch_long, :parent => cp_item2) if cp2.couch_longitudinal != cp1.couch_longitudinal
495
- # Table Top Lateral Position:
496
- couch_lat = cp2.couch_lateral.empty? ? '' : (cp2.couch_lateral.to_f * 10).to_s
497
- DICOM::Element.new('300A,012A', couch_lat, :parent => cp_item2) if cp2.couch_lateral != cp1.couch_lateral
498
- # Source to Surface Distance:
499
- DICOM::Element.new('300A,0130', "#{cp2.ssd.to_f * 10}", :parent => cp_item2) if cp2.ssd != cp1.ssd
500
- # Beam Limiting Device Position Sequence:
501
- dp_seq = DICOM::Sequence.new('300A,011A', :parent => cp_item2)
502
- # ASYMX:
503
- if cp2.collimator_x1 != cp1.collimator_x1
504
- dp_item_x = DICOM::Item.new(:parent => dp_seq)
505
- DICOM::Element.new('300A,00B8', "ASYMX", :parent => dp_item_x)
506
- DICOM::Element.new('300A,011C', "#{field.collimator_x1.to_f * 10}\\#{field.collimator_x2.to_f * 10}", :parent => dp_item_x)
507
- end
508
- # ASYMY:
509
- if cp2.collimator_y1 != cp1.collimator_y1
510
- dp_item_y = DICOM::Item.new(:parent => dp_seq)
511
- DICOM::Element.new('300A,00B8', "ASYMY", :parent => dp_item_y)
512
- DICOM::Element.new('300A,011C', "#{field.collimator_y1.to_f * 10}\\#{field.collimator_y2.to_f * 10}", :parent => dp_item_y)
513
- end
514
- # MLCX:
515
- if cp2.mlc_lp_a != cp1.mlc_lp_a or cp2.mlc_lp_b != cp1.mlc_lp_b
516
- dp_item_mlcx = DICOM::Item.new(:parent => dp_seq)
517
- # RT Beam Limiting Device Type:
518
- DICOM::Element.new('300A,00B8', "MLCX", :parent => dp_item_mlcx)
519
- # Leaf/Jaw Positions:
520
- pos_a = cp2.mlc_lp_a.collect{|p| (p.to_f * 10).round(2) unless p.empty?}.compact
521
- pos_b = cp2.mlc_lp_b.collect{|p| (p.to_f * 10).round(2) unless p.empty?}.compact
522
- leaf_pos = "#{pos_a.join("\\")}\\#{pos_b.join("\\")}"
523
- DICOM::Element.new('300A,011C', leaf_pos, :parent => dp_item_mlcx)
524
- end
525
- end
336
+ # When we have multiple (2 or more) control points, iterate each control point:
337
+ field.control_points.each { |cp| create_control_point(cp, cp_seq, options) }
338
+ # Make sure that hte cumulative meterset weight of the last control
339
+ # point is '1' (exactly equal to final cumulative meterset weight):
340
+ cp_seq.items.last['300A,0134'].value = '1'
526
341
  end
527
342
  # Number of Control Points:
528
343
  DICOM::Element.new('300A,0110', b_item['300A,0111'].items.length, :parent => b_item)
@@ -536,6 +351,345 @@ module RTP
536
351
  return dcm
537
352
  end
538
353
 
354
+
355
+ private
356
+
357
+
358
+ # Adds Collimator Angle elements to a Control Point Item.
359
+ # Note that the element is only added if there is no 'current' attribute
360
+ # defined, or the given value is different form the current attribute.
361
+ #
362
+ # @param [String, NilClass] value1 the collimator angle attribute
363
+ # @param [String, NilClass] value2 the collimator rotation direction attribute
364
+ # @param [DICOM::Item] item the DICOM control point item in which to create an element
365
+ #
366
+ def add_collimator(value1, value2, item)
367
+ if !@current_collimator || value1 != @current_collimator
368
+ @current_collimator = value1
369
+ DICOM::Element.new('300A,0120', value1, :parent => item)
370
+ DICOM::Element.new('300A,0121', (value2.empty? ? 'NONE' : value2), :parent => item)
371
+ end
372
+ end
373
+
374
+ # Adds Table Top Eccentric Angle elements to a Control Point Item.
375
+ # Note that the element is only added if there is no 'current' attribute
376
+ # defined, or the given value is different form the current attribute.
377
+ #
378
+ # @param [String, NilClass] value1 the table top eccentric angle attribute
379
+ # @param [String, NilClass] value2 the table top eccentric rotation direction attribute
380
+ # @param [DICOM::Item] item the DICOM control point item in which to create an element
381
+ #
382
+ def add_couch_angle(value1, value2, item)
383
+ if !@current_couch_angle || value1 != @current_couch_angle
384
+ @current_couch_angle = value1
385
+ DICOM::Element.new('300A,0125', value1, :parent => item)
386
+ DICOM::Element.new('300A,0126', (value2.empty? ? 'NONE' : value2), :parent => item)
387
+ end
388
+ end
389
+
390
+ # Adds a Table Top Lateral Position element to a Control Point Item.
391
+ # Note that the element is only added if there is no 'current' attribute
392
+ # defined, or the given value is different form the current attribute.
393
+ #
394
+ # @param [String, NilClass] value the couch lateral attribute
395
+ # @param [DICOM::Item] item the DICOM control point item in which to create an element
396
+ #
397
+ def add_couch_lateral(value, item)
398
+ if !@current_couch_lateral || value != @current_couch_lateral
399
+ @current_couch_lateral = value
400
+ DICOM::Element.new('300A,012A', (value.empty? ? '' : value.to_f * 10), :parent => item)
401
+ end
402
+ end
403
+
404
+ # Adds a Table Top Longitudinal Position element to a Control Point Item.
405
+ # Note that the element is only added if there is no 'current' attribute
406
+ # defined, or the given value is different form the current attribute.
407
+ #
408
+ # @param [String, NilClass] value the couch longitudinal attribute
409
+ # @param [DICOM::Item] item the DICOM control point item in which to create an element
410
+ #
411
+ def add_couch_longitudinal(value, item)
412
+ if !@current_couch_longitudinal || value != @current_couch_longitudinal
413
+ @current_couch_longitudinal = value
414
+ DICOM::Element.new('300A,0129', (value.empty? ? '' : value.to_f * 10), :parent => item)
415
+ end
416
+ end
417
+
418
+ # Adds Patient Support Angle elements to a Control Point Item.
419
+ # Note that the element is only added if there is no 'current' attribute
420
+ # defined, or the given value is different form the current attribute.
421
+ #
422
+ # @param [String, NilClass] value1 the patient support angle attribute
423
+ # @param [String, NilClass] value2 the patient support rotation direction attribute
424
+ # @param [DICOM::Item] item the DICOM control point item in which to create an element
425
+ #
426
+ def add_couch_pedestal(value1, value2, item)
427
+ if !@current_couch_pedestal || value1 != @current_couch_pedestal
428
+ @current_couch_pedestal = value1
429
+ DICOM::Element.new('300A,0122', value1, :parent => item)
430
+ DICOM::Element.new('300A,0123', (value2.empty? ? 'NONE' : value2), :parent => item)
431
+ end
432
+ end
433
+
434
+ # Adds a Table Top Vertical Position element to a Control Point Item.
435
+ # Note that the element is only added if there is no 'current' attribute
436
+ # defined, or the given value is different form the current attribute.
437
+ #
438
+ # @param [String, NilClass] value the couch vertical attribute
439
+ # @param [DICOM::Item] item the DICOM control point item in which to create an element
440
+ #
441
+ def add_couch_vertical(value, item)
442
+ if !@current_couch_vertical || value != @current_couch_vertical
443
+ @current_couch_vertical = value
444
+ DICOM::Element.new('300A,0128', (value.empty? ? '' : value.to_f * 10), :parent => item)
445
+ end
446
+ end
447
+
448
+ # Adds a Dose Rate Set element to a Control Point Item.
449
+ # Note that the element is only added if there is no 'current' attribute
450
+ # defined, or the given value is different form the current attribute.
451
+ #
452
+ # @param [String, NilClass] value the doserate attribute
453
+ # @param [DICOM::Item] item the DICOM control point item in which to create an element
454
+ #
455
+ def add_doserate(value, item)
456
+ if !@current_doserate || value != @current_doserate
457
+ @current_doserate = value
458
+ DICOM::Element.new('300A,0115', value, :parent => item)
459
+ end
460
+ end
461
+
462
+ # Adds a Nominal Beam Energy element to a Control Point Item.
463
+ # Note that the element is only added if there is no 'current' attribute
464
+ # defined, or the given value is different form the current attribute.
465
+ #
466
+ # @param [String, NilClass] value the energy attribute
467
+ # @param [DICOM::Item] item the DICOM control point item in which to create an element
468
+ #
469
+ def add_energy(value, item)
470
+ if !@current_energy || value != @current_energy
471
+ @current_energy = value
472
+ DICOM::Element.new('300A,0114', "#{value.to_f}", :parent => item)
473
+ end
474
+ end
475
+
476
+ # Adds Gantry Angle elements to a Control Point Item.
477
+ # Note that the element is only added if there is no 'current' attribute
478
+ # defined, or the given value is different form the current attribute.
479
+ #
480
+ # @param [String, NilClass] value1 the gantry angle attribute
481
+ # @param [String, NilClass] value2 the gantry rotation direction attribute
482
+ # @param [DICOM::Item] item the DICOM control point item in which to create an element
483
+ #
484
+ def add_gantry(value1, value2, item)
485
+ if !@current_gantry || value1 != @current_gantry
486
+ @current_gantry = value1
487
+ DICOM::Element.new('300A,011E', value1, :parent => item)
488
+ DICOM::Element.new('300A,011F', (value2.empty? ? 'NONE' : value2), :parent => item)
489
+ end
490
+ end
491
+
492
+ # Adds an Isosenter element to a Control Point Item.
493
+ # Note that the element is only added if there is a Site Setup record present,
494
+ # and it contains a real (non-empty) value. Also, the element is only added if there
495
+ # is no 'current' attribute defined, or the given value is different form the current attribute.
496
+ #
497
+ # @param [SiteSetup, NilClass] site_setup the associated site setup record
498
+ # @param [DICOM::Item] item the DICOM control point item in which to create an element
499
+ #
500
+ def add_isosenter(site_setup, item)
501
+ if site_setup
502
+ # Create an element if the value is new or unique:
503
+ if !@current_isosenter
504
+ iso = "#{(site_setup.iso_pos_x.to_f * 10).round(2)}\\#{(site_setup.iso_pos_y.to_f * 10).round(2)}\\#{(site_setup.iso_pos_z.to_f * 10).round(2)}"
505
+ if iso != @current_isosenter
506
+ @current_isosenter = iso
507
+ DICOM::Element.new('300A,012C', iso, :parent => item)
508
+ end
509
+ end
510
+ else
511
+ # Log a warning if this is the first control point:
512
+ unless @current_isosenter
513
+ logger.warn("No Site Setup record exists for this plan. Unable to provide an isosenter position.")
514
+ end
515
+ end
516
+ end
517
+
518
+ # Adds a Source to Surface Distance element to a Control Point Item.
519
+ # Note that the element is only added if the SSD attribute contains
520
+ # real (non-empty) value.
521
+ #
522
+ # @param [String, NilClass] value the SSD attribute
523
+ # @param [DICOM::Item] item the DICOM control point item in which to create an element
524
+ #
525
+ def add_ssd(value, item)
526
+ DICOM::Element.new('300A,0130', "#{value.to_f * 10}", :parent => item) if value && !value.empty?
527
+ end
528
+
529
+ # Creates a control point item in the given control point sequence, based
530
+ # on an RTP control point record.
531
+ #
532
+ # @param [ControlPoint] cp the RTP ControlPoint record to convert
533
+ # @param [DICOM::Sequence] sequence the DICOM parent sequence of the item to be created
534
+ # @param [Hash] options the options to use for creating the control point
535
+ # @option options [Boolean] :dose_ref if set, a Referenced Dose Reference sequence will be included in the generated control point item
536
+ # @return [DICOM::Item] the constructed control point DICOM item
537
+ #
538
+ def create_control_point(cp, sequence, options={})
539
+ cp_item = DICOM::Item.new(:parent => sequence)
540
+ # Some CP attributes will always be written (CP index, BLD positions & Cumulative meterset weight).
541
+ # The other attributes are only written if they are different from the previous control point.
542
+ # Control Point Index:
543
+ DICOM::Element.new('300A,0112', "#{cp.index}", :parent => cp_item)
544
+ # Beam Limiting Device Position Sequence:
545
+ create_beam_limiting_device_positions(cp_item, cp)
546
+ # Source to Surface Distance:
547
+ add_ssd(cp.ssd, cp_item)
548
+ # Cumulative Meterset Weight:
549
+ DICOM::Element.new('300A,0134', cp.monitor_units.to_f, :parent => cp_item)
550
+ # Referenced Dose Reference Sequence:
551
+ create_referenced_dose_reference(cp_item) if options[:dose_ref]
552
+ # Attributes that are only added if they carry an updated value:
553
+ # Nominal Beam Energy:
554
+ add_energy(cp.energy, cp_item)
555
+ # Dose Rate Set:
556
+ add_doserate(cp.doserate, cp_item)
557
+ # Gantry Angle & Rotation Direction:
558
+ add_gantry(cp.gantry_angle, cp.gantry_dir, cp_item)
559
+ # Beam Limiting Device Angle & Rotation Direction:
560
+ add_collimator(cp.collimator_angle, cp.collimator_dir, cp_item)
561
+ # Patient Support Angle & Rotation Direction:
562
+ add_couch_pedestal(cp.couch_pedestal, cp.couch_ped_dir, cp_item)
563
+ # Table Top Eccentric Angle & Rotation Direction:
564
+ add_couch_angle(cp.couch_angle, cp.couch_dir, cp_item)
565
+ # Table Top Vertical Position:
566
+ add_couch_vertical(cp.couch_vertical, cp_item)
567
+ # Table Top Longitudinal Position:
568
+ add_couch_longitudinal(cp.couch_vertical, cp_item)
569
+ # Table Top Lateral Position:
570
+ add_couch_lateral(cp.couch_vertical, cp_item)
571
+ # Isocenter Position (x\y\z):
572
+ add_isosenter(cp.parent.parent.site_setup, cp_item)
573
+ cp_item
574
+ end
575
+
576
+ # Creates a beam limiting device sequence in the given DICOM object.
577
+ #
578
+ # @param [DICOM::Item] beam_item the DICOM beam item in which to insert the sequence
579
+ # @param [Field] field the RTP field to fetch device parameters from
580
+ # @return [DICOM::Sequence] the constructed beam limiting device sequence
581
+ #
582
+ def create_beam_limiting_devices(beam_item, field)
583
+ bl_seq = DICOM::Sequence.new('300A,00B6', :parent => beam_item)
584
+ # The ASYMX item ('backup jaws') doesn't exist on all models:
585
+ if ['SYM', 'ASY'].include?(field.field_x_mode.upcase)
586
+ bl_item_x = DICOM::Item.new(:parent => bl_seq)
587
+ DICOM::Element.new('300A,00B8', "ASYMX", :parent => bl_item_x)
588
+ DICOM::Element.new('300A,00BC', "1", :parent => bl_item_x)
589
+ end
590
+ # The ASYMY item is always created:
591
+ bl_item_y = DICOM::Item.new(:parent => bl_seq)
592
+ # RT Beam Limiting Device Type:
593
+ DICOM::Element.new('300A,00B8', "ASYMY", :parent => bl_item_y)
594
+ # Number of Leaf/Jaw Pairs:
595
+ DICOM::Element.new('300A,00BC', "1", :parent => bl_item_y)
596
+ # MLCX item is only created if leaves are defined:
597
+ # (NB: The RTP file doesn't specify leaf position boundaries, so we
598
+ # have to set these based on a set of known MLC types, their number
599
+ # of leaves, and their leaf boundary positions.)
600
+ if field.control_points.length > 0
601
+ bl_item_mlcx = DICOM::Item.new(:parent => bl_seq)
602
+ DICOM::Element.new('300A,00B8', "MLCX", :parent => bl_item_mlcx)
603
+ num_leaves = field.control_points.first.mlc_leaves.to_i
604
+ DICOM::Element.new('300A,00BC', num_leaves.to_s, :parent => bl_item_mlcx)
605
+ DICOM::Element.new('300A,00BE', "#{RTP.leaf_boundaries(num_leaves).join("\\")}", :parent => bl_item_mlcx)
606
+ end
607
+ bl_seq
608
+ end
609
+
610
+ # Creates a beam limiting device positions sequence in the given DICOM object.
611
+ #
612
+ # @param [DICOM::Item] cp_item the DICOM control point item in which to insert the sequence
613
+ # @param [ControlPoint] cp the RTP control point to fetch device parameters from
614
+ # @return [DICOM::Sequence] the constructed beam limiting device positions sequence
615
+ #
616
+ def create_beam_limiting_device_positions(cp_item, cp)
617
+ dp_seq = DICOM::Sequence.new('300A,011A', :parent => cp_item)
618
+ # The ASYMX item ('backup jaws') doesn't exist on all models:
619
+ if ['SYM', 'ASY'].include?(cp.parent.field_x_mode.upcase)
620
+ dp_item_x = DICOM::Item.new(:parent => dp_seq)
621
+ DICOM::Element.new('300A,00B8', "ASYMX", :parent => dp_item_x)
622
+ DICOM::Element.new('300A,011C', "#{cp.dcm_collimator_x1}\\#{cp.dcm_collimator_x2}", :parent => dp_item_x)
623
+ end
624
+ # Always create one ASYMY item:
625
+ dp_item_y = DICOM::Item.new(:parent => dp_seq)
626
+ # RT Beam Limiting Device Type:
627
+ DICOM::Element.new('300A,00B8', "ASYMY", :parent => dp_item_y)
628
+ # Leaf/Jaw Positions:
629
+ DICOM::Element.new('300A,011C', "#{cp.dcm_collimator_y1}\\#{cp.dcm_collimator_y2}", :parent => dp_item_y)
630
+ # MLCX:
631
+ dp_item_mlcx = DICOM::Item.new(:parent => dp_seq)
632
+ # RT Beam Limiting Device Type:
633
+ DICOM::Element.new('300A,00B8', "MLCX", :parent => dp_item_mlcx)
634
+ # Leaf/Jaw Positions:
635
+ DICOM::Element.new('300A,011C', cp.dcm_mlc_positions, :parent => dp_item_mlcx)
636
+ dp_seq
637
+ end
638
+
639
+ # Creates a dose reference sequence in the given DICOM object.
640
+ #
641
+ # @param [DICOM::DObject] dcm the DICOM object in which to insert the sequence
642
+ # @param [String] description the value to use for Dose Reference Description
643
+ # @return [DICOM::Sequence] the constructed dose reference sequence
644
+ #
645
+ def create_dose_reference(dcm, description)
646
+ dr_seq = DICOM::Sequence.new('300A,0010', :parent => dcm)
647
+ dr_item = DICOM::Item.new(:parent => dr_seq)
648
+ # Dose Reference Number:
649
+ DICOM::Element.new('300A,0012', '1', :parent => dr_item)
650
+ # Dose Reference Structure Type:
651
+ DICOM::Element.new('300A,0014', 'SITE', :parent => dr_item)
652
+ # Dose Reference Description:
653
+ DICOM::Element.new('300A,0016', description, :parent => dr_item)
654
+ # Dose Reference Type:
655
+ DICOM::Element.new('300A,0020', 'TARGET', :parent => dr_item)
656
+ dr_seq
657
+ end
658
+
659
+ # Creates a referenced dose reference sequence in the given DICOM object.
660
+ #
661
+ # @param [DICOM::Item] cp_item the DICOM item in which to insert the sequence
662
+ # @return [DICOM::Sequence] the constructed referenced dose reference sequence
663
+ #
664
+ def create_referenced_dose_reference(cp_item)
665
+ # Referenced Dose Reference Sequence:
666
+ rd_seq = DICOM::Sequence.new('300C,0050', :parent => cp_item)
667
+ rd_item = DICOM::Item.new(:parent => rd_seq)
668
+ # Cumulative Dose Reference Coeffecient:
669
+ DICOM::Element.new('300A,010C', '', :parent => rd_item)
670
+ # Referenced Dose Reference Number:
671
+ DICOM::Element.new('300C,0051', '1', :parent => rd_item)
672
+ rd_seq
673
+ end
674
+
675
+ # Resets the types of control point attributes that are only written to the
676
+ # first control point item, and for following control point items only when
677
+ # they are different from the 'current' value. When a new field is reached,
678
+ # it is essential to reset these attributes, or else we could risk to start
679
+ # the field with a control point with missing attributes, if one of its first
680
+ # attributes is equal to the last attribute of the previous field.
681
+ #
682
+ def reset_cp_current_attributes
683
+ @current_gantry = nil
684
+ @current_collimator = nil
685
+ @current_couch_pedestal = nil
686
+ @current_couch_angle = nil
687
+ @current_couch_vertical = nil
688
+ @current_couch_longitudinal = nil
689
+ @current_couch_lateral = nil
690
+ @current_isosenter = nil
691
+ end
692
+
539
693
  end
540
694
 
541
695
  end
@@ -1,6 +1,6 @@
1
1
  module RTP
2
2
 
3
3
  # The RTPConnect library version string.
4
- VERSION = '1.5'
4
+ VERSION = '1.6'
5
5
 
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rtp-connect
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.5'
4
+ version: '1.6'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-24 00:00:00.000000000 Z
12
+ date: 2013-12-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -159,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
159
  version: '0'
160
160
  segments:
161
161
  - 0
162
- hash: 1061055599
162
+ hash: -111446803
163
163
  requirements: []
164
164
  rubyforge_project: rtp-connect
165
165
  rubygems_version: 1.8.24