rtp-connect 1.5 → 1.6

Sign up to get free protection for your applications and to get access to all the features.
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