class RTP::Plan

The Plan class is the highest level Record in the RTPConnect records hierarchy, and the one the user will interact with to read, modify and write files.

@note Relations:

* Parent: nil
* Children: Prescription, DoseTracking

Attributes

author_first_name[R]
author_last_name[R]
author_middle_initial[R]
course_id[R]
current_collimator[RW]
current_couch_angle[RW]
current_couch_lateral[RW]
current_couch_longitudinal[RW]
current_couch_pedestal[RW]
current_couch_vertical[RW]
current_gantry[RW]
diagnosis[R]
dose_trackings[R]

An array of DoseTracking records (if any) that belongs to this Plan.

extended_plan[R]

The ExtendedPlan record (if any) that belongs to this Plan.

md_approve_first_name[R]
md_approve_last_name[R]
md_approve_middle_initial[R]
md_first_name[R]
md_last_name[R]
md_middle_initial[R]
parent[R]

The Record which this instance belongs to (nil by definition).

patient_first_name[R]
patient_id[R]
patient_last_name[R]
patient_middle_initial[R]
phy_approve_first_name[R]
phy_approve_last_name[R]
phy_approve_middle_initial[R]
plan_date[R]
plan_id[R]
plan_time[R]
prescriptions[R]

An array of Prescription records (if any) that belongs to this Plan.

rtp_if_protocol[R]
rtp_if_version[R]
rtp_mfg[R]
rtp_model[R]
rtp_version[R]

Public Class Methods

load(string, options={}) click to toggle source

Creates a new Plan by loading a plan definition string (i.e. a single line).

@note This method does not perform crc verification on the given string.

If such verification is desired, use methods ::parse or ::read instead.

@param [#to_s] string the plan definition record string line @param [Hash] options the options to use for loading the plan definition string @option options [Boolean] :repair if true, a record containing invalid CSV will be attempted fixed and loaded @return [Plan] the created Plan instance @raise [ArgumentError] if given a string containing an invalid number of elements

# File lib/rtp-connect/plan.rb, line 73
def self.load(string, options={})
  rtp = self.new
  rtp.load(string, options)
end
new() click to toggle source

Creates a new Plan.

Calls superclass method
# File lib/rtp-connect/plan.rb, line 156
def initialize
  super('PLAN_DEF', 10, 28)
  @current_parent = self
  # Child records:
  @extended_plan = nil
  @prescriptions = Array.new
  @dose_trackings = Array.new
  # No parent (by definition) for the Plan record:
  @parent = nil
  @attributes = [
    # Required:
    :keyword,
    :patient_id,
    :patient_last_name,
    :patient_first_name,
    :patient_middle_initial,
    :plan_id,
    :plan_date,
    :plan_time,
    :course_id,
    # Optional:
    :diagnosis,
    :md_last_name,
    :md_first_name,
    :md_middle_initial,
    :md_approve_last_name,
    :md_approve_first_name,
    :md_approve_middle_initial,
    :phy_approve_last_name,
    :phy_approve_first_name,
    :phy_approve_middle_initial,
    :author_last_name,
    :author_first_name,
    :author_middle_initial,
    :rtp_mfg,
    :rtp_model,
    :rtp_version,
    :rtp_if_protocol,
    :rtp_if_version
  ]
end
parse(string, options={}) click to toggle source

Creates a Plan instance by parsing an RTPConnect string.

@param [#to_s] string an RTPConnect ascii string (with single or multiple lines/records) @param [Hash] options the options to use for parsing the RTP string @option options [Boolean] :ignore_crc if true, the RTP records will be successfully loaded even if their checksums are invalid @option options [Boolean] :repair if true, any RTP records containing invalid CSV will be attempted fixed and loaded @option options [Boolean] :skip_unknown if true, unknown records will be skipped, and record instances will be built from the remaining recognized string records @return [Plan] the created Plan instance @raise [ArgumentError] if given an invalid string record

# File lib/rtp-connect/plan.rb, line 88
def self.parse(string, options={})
  lines = string.to_s.split("\r\n")
  # Create the Plan object:
  line = lines.first
  RTP.verify(line, options)
  rtp = self.load(line, options)
  lines[1..-1].each do |line|
    # Validate, determine type, and process the line accordingly to
    # build the hierarchy of records:
    RTP.verify(line, options)
    values = line.values(options[:repair])
    keyword = values.first
    method = RTP::PARSE_METHOD[keyword]
    if method
      rtp.send(method, line)
    else
      if options[:skip_unknown]
        logger.warn("Skipped unknown record definition: #{keyword}")
      else
        raise ArgumentError, "Unknown keyword #{keyword} extracted from string."
      end
    end
  end
  return rtp
end
read(file, options={}) click to toggle source

Creates an Plan instance by reading and parsing an RTPConnect file.

@param [String] file a string which specifies the path of the RTPConnect file to be loaded @param [Hash] options the options to use for reading the RTP file @option options [Boolean] :ignore_crc if true, the RTP records will be successfully loaded even if their checksums are invalid @option options [Boolean] :repair if true, any RTP records containing invalid CSV will be attempted fixed and loaded @option options [Boolean] :skip_unknown if true, unknown records will be skipped, and record instances will be built from the remaining recognized string records @return [Plan] the created Plan instance @raise [ArgumentError] if given an invalid file or the file given contains an invalid record

# File lib/rtp-connect/plan.rb, line 124
def self.read(file, options={})
  raise ArgumentError, "Invalid argument 'file'. Expected String, got #{file.class}." unless file.is_a?(String)
  # Read the file content:
  str = nil
  unless File.exist?(file)
    logger.error("Invalid (non-existing) file: #{file}")
  else
    unless File.readable?(file)
      logger.error("File exists but I don't have permission to read it: #{file}")
    else
      if File.directory?(file)
        logger.error("Expected a file, got a directory: #{file}")
      else
        if File.size(file) < 10
          logger.error("This file is too small to contain valid RTP information: #{file}.")
        else
          str = File.open(file, 'rb:ISO8859-1') { |f| f.read }
        end
      end
    end
  end
  # Parse the file contents and create the RTP instance:
  if str
    rtp = self.parse(str, options)
  else
    raise "An RTP::Plan object could not be created from the specified file. Check the log for more details."
  end
  return rtp
end

Public Instance Methods

==(other) click to toggle source

Checks for equality.

Other and self are considered equivalent if they are of compatible types and their attributes are equivalent.

@param other an object to be compared with self. @return [Boolean] true if self and other are considered equivalent

# File lib/rtp-connect/plan.rb, line 206
def ==(other)
  if other.respond_to?(:to_plan)
    other.send(:state) == state
  end
end
Also aliased as: eql?
add_dose_tracking(child) click to toggle source

Adds a dose tracking record to this instance.

@param [DoseTracking] child a DoseTracking instance which is to be associated with self

# File lib/rtp-connect/plan.rb, line 218
def add_dose_tracking(child)
  @dose_trackings << child.to_dose_tracking
  child.parent = self
end
add_extended_plan(child) click to toggle source

Adds an extended plan record to this instance.

@param [ExtendedPlan] child an ExtendedPlan instance which is to be associated with self

# File lib/rtp-connect/plan.rb, line 227
def add_extended_plan(child)
  @extended_plan = child.to_extended_plan
  child.parent = self
end
add_prescription(child) click to toggle source

Adds a prescription site record to this instance.

@param [Prescription] child a Prescription instance which is to be associated with self

# File lib/rtp-connect/plan.rb, line 236
def add_prescription(child)
  @prescriptions << child.to_prescription
  child.parent = self
end
author_first_name=(value) click to toggle source

Sets the author_first_name attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 477
def author_first_name=(value)
  @author_first_name = value && value.to_s
end
author_last_name=(value) click to toggle source

Sets the author_last_name attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 469
def author_last_name=(value)
  @author_last_name = value && value.to_s
end
author_middle_initial=(value) click to toggle source

Sets the author_middle_initial attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 485
def author_middle_initial=(value)
  @author_middle_initial = value && value.to_s
end
children() click to toggle source

Collects the child records of this instance in a properly sorted array.

@return [Array<Prescription, DoseTracking>] a sorted array of self's child records

# File lib/rtp-connect/plan.rb, line 245
def children
  return [@extended_plan, @prescriptions, @dose_trackings].flatten.compact
end
course_id=(value) click to toggle source

Sets the course_id attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 381
def course_id=(value)
  @course_id = value && value.to_s
end
delete(record) click to toggle source

Removes the reference of the given instance from this instance.

@param [ExtendedPlan, Prescription, DoseTracking] record a child record to be removed from this instance

# File lib/rtp-connect/plan.rb, line 253
def delete(record)
  case record
  when Prescription
    delete_child(:prescriptions, record)
  when DoseTracking
    delete_child(:dose_trackings, record)
  when ExtendedPlan
    delete_extended_plan
  else
    logger.warn("Unknown class (record) given to Plan#delete: #{record.class}")
  end
end
delete_dose_trackings() click to toggle source

Removes all dose_tracking references from this instance.

# File lib/rtp-connect/plan.rb, line 268
def delete_dose_trackings
  delete_children(:dose_trackings)
end
delete_extended_plan() click to toggle source

Removes the extended plan reference from this instance.

# File lib/rtp-connect/plan.rb, line 274
def delete_extended_plan
  delete_child(:extended_plan)
end
delete_prescriptions() click to toggle source

Removes all prescription references from this instance.

# File lib/rtp-connect/plan.rb, line 280
def delete_prescriptions
  delete_children(:prescriptions)
end
diagnosis=(value) click to toggle source

Sets the diagnosis attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 389
def diagnosis=(value)
  @diagnosis = value && value.to_s
end
eql?(other)
Alias for: ==
hash() click to toggle source

Computes a hash code for this object.

@note Two objects with the same attributes will have the same hash code.

@return [Fixnum] the object's hash code

# File lib/rtp-connect/plan.rb, line 290
def hash
  state.hash
end
md_approve_first_name=(value) click to toggle source

Sets the md_approve_first_name attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 429
def md_approve_first_name=(value)
  @md_approve_first_name = value && value.to_s
end
md_approve_last_name=(value) click to toggle source

Sets the md_approve_last_name attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 421
def md_approve_last_name=(value)
  @md_approve_last_name = value && value.to_s
end
md_approve_middle_initial=(value) click to toggle source

Sets the md_approve_middle_initial attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 437
def md_approve_middle_initial=(value)
  @md_approve_middle_initial = value && value.to_s
end
md_first_name=(value) click to toggle source

Sets the md_first_name attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 405
def md_first_name=(value)
  @md_first_name = value && value.to_s
end
md_last_name=(value) click to toggle source

Sets the md_last_name attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 397
def md_last_name=(value)
  @md_last_name = value && value.to_s
end
md_middle_initial=(value) click to toggle source

Sets the md_middle_initial attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 413
def md_middle_initial=(value)
  @md_middle_initial = value && value.to_s
end
patient_first_name=(value) click to toggle source

Sets the patient_first_name attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 341
def patient_first_name=(value)
  @patient_first_name = value && value.to_s
end
patient_id=(value) click to toggle source

Sets the patient_id attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 327
def patient_id=(value)
  @patient_id = value && value.to_s
end
patient_last_name=(value) click to toggle source

Sets the patient_last_name attribute.

# File lib/rtp-connect/plan.rb, line 333
def patient_last_name=(value)
  @patient_last_name = value && value.to_s
end
patient_middle_initial=(value) click to toggle source

Sets the patient_middle_initial attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 349
def patient_middle_initial=(value)
  @patient_middle_initial = value && value.to_s
end
phy_approve_first_name=(value) click to toggle source

Sets the phy_approve_first_name attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 453
def phy_approve_first_name=(value)
  @phy_approve_first_name = value && value.to_s
end
phy_approve_last_name=(value) click to toggle source

Sets the phy_approve_last_name attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 445
def phy_approve_last_name=(value)
  @phy_approve_last_name = value && value.to_s
end
phy_approve_middle_initial=(value) click to toggle source

Sets the phy_approve_middle_initial attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 461
def phy_approve_middle_initial=(value)
  @phy_approve_middle_initial = value && value.to_s
end
plan_date=(value) click to toggle source

Sets the plan_date attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 365
def plan_date=(value)
  @plan_date = value && value.to_s
end
plan_id=(value) click to toggle source

Sets the plan_id attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 357
def plan_id=(value)
  @plan_id = value && value.to_s
end
plan_time=(value) click to toggle source

Sets the plan_time attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 373
def plan_time=(value)
  @plan_time = value && value.to_s
end
rtp_if_protocol=(value) click to toggle source

Sets the rtp_if_protocol attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 517
def rtp_if_protocol=(value)
  @rtp_if_protocol = value && value.to_s
end
rtp_if_version=(value) click to toggle source

Sets the rtp_if_version attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 525
def rtp_if_version=(value)
  @rtp_if_version = value && value.to_s
end
rtp_mfg=(value) click to toggle source

Sets the rtp_mfg attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 493
def rtp_mfg=(value)
  @rtp_mfg = value && value.to_s
end
rtp_model=(value) click to toggle source

Sets the rtp_model attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 501
def rtp_model=(value)
  @rtp_model = value && value.to_s
end
rtp_version=(value) click to toggle source

Sets the rtp_version attribute.

@param [nil, to_s] value the new attribute value

# File lib/rtp-connect/plan.rb, line 509
def rtp_version=(value)
  @rtp_version = value && value.to_s
end
to_dcm(options={}) click to toggle source

Converts the Plan (and child) records to a DICOM::DObject of modality RTPLAN.

@note Only photon plans have been tested.

Electron beams beams may give an invalid DICOM file.
Also note that, due to limitations in the RTP file format, some original
values can not be recreated, like e.g. Study UID or Series UID.

@param [Hash] options the options to use for creating the DICOM object @option options [Boolean] :dose_ref if set, Dose Reference & Referenced Dose Reference sequences will be included in the generated DICOM file @option options [String] :manufacturer the value used for the manufacturer tag (0008,0070) in the beam sequence @option options [String] :model the value used for the manufacturer's model name tag (0008,1090) in the beam sequence @option options [Symbol] :scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian) @option options [String] :serial_number the value used for the device serial number tag (0018,1000) in the beam sequence @return [DICOM::DObject] the converted DICOM object

# File lib/rtp-connect/plan_to_dcm.rb, line 28
def to_dcm(options={})
  #
  # FIXME: This method is rather big, with a few sections of somewhat similar, repeating code.
  # Refactoring and simplifying it at some stage might be a good idea.
  #
  require 'dicom'
  original_level = DICOM.logger.level
  DICOM.logger.level = Logger::FATAL
  p = @prescriptions.first
  # If no prescription is present, we are not going to be able to make a valid DICOM object:
  logger.error("No Prescription Record present. Unable to build a valid RTPLAN DICOM object.") unless p
  dcm = DICOM::DObject.new
  #
  # TOP LEVEL TAGS:
  #
  # Specific Character Set:
  DICOM::Element.new('0008,0005', 'ISO_IR 100', :parent => dcm)
  # Instance Creation Date
  DICOM::Element.new('0008,0012', Time.now.strftime("%Y%m%d"), :parent => dcm)
  # Instance Creation Time:
  DICOM::Element.new('0008,0013', Time.now.strftime("%H%M%S"), :parent => dcm)
  # SOP Class UID:
  DICOM::Element.new('0008,0016', '1.2.840.10008.5.1.4.1.1.481.5', :parent => dcm)
  # SOP Instance UID (if an original UID is not present, we make up a UID):
  begin
    sop_uid = p.fields.first.extended_field.original_plan_uid.empty? ? DICOM.generate_uid : p.fields.first.extended_field.original_plan_uid
  rescue
    sop_uid = DICOM.generate_uid
  end
  DICOM::Element.new('0008,0018', sop_uid, :parent => dcm)
  # Study Date
  DICOM::Element.new('0008,0020', Time.now.strftime("%Y%m%d"), :parent => dcm)
  # Study Time:
  DICOM::Element.new('0008,0030', Time.now.strftime("%H%M%S"), :parent => dcm)
  # Accession Number:
  DICOM::Element.new('0008,0050', '', :parent => dcm)
  # Modality:
  DICOM::Element.new('0008,0060', 'RTPLAN', :parent => dcm)
  # Manufacturer:
  DICOM::Element.new('0008,0070', 'rtp-connect', :parent => dcm)
  # Referring Physician's Name:
  DICOM::Element.new('0008,0090', "#{@md_last_name}^#{@md_first_name}^#{@md_middle_name}^^", :parent => dcm)
  # Operator's Name:
  DICOM::Element.new('0008,1070', "#{@author_last_name}^#{@author_first_name}^#{@author_middle_name}^^", :parent => dcm)
  # Patient's Name:
  DICOM::Element.new('0010,0010', "#{@patient_last_name}^#{@patient_first_name}^#{@patient_middle_name}^^", :parent => dcm)
  # Patient ID:
  DICOM::Element.new('0010,0020', @patient_id, :parent => dcm)
  # Patient's Birth Date:
  DICOM::Element.new('0010,0030', '', :parent => dcm)
  # Patient's Sex:
  DICOM::Element.new('0010,0040', '', :parent => dcm)
  # Manufacturer's Model Name:
  DICOM::Element.new('0008,1090', 'RTP-to-DICOM', :parent => dcm)
  # Software Version(s):
  DICOM::Element.new('0018,1020', "RubyRTP#{VERSION}", :parent => dcm)
  # Study Instance UID:
  DICOM::Element.new('0020,000D', DICOM.generate_uid, :parent => dcm)
  # Series Instance UID:
  DICOM::Element.new('0020,000E', DICOM.generate_uid, :parent => dcm)
  # Study ID:
  DICOM::Element.new('0020,0010', '1', :parent => dcm)
  # Series Number:
  DICOM::Element.new('0020,0011', '1', :parent => dcm)
  # Frame of Reference UID (if an original UID is not present, we make up a UID):
  begin
    for_uid = p.site_setup.frame_of_ref_uid.empty? ? DICOM.generate_uid : p.site_setup.frame_of_ref_uid
  rescue
    for_uid = DICOM.generate_uid
  end
  DICOM::Element.new('0020,0052', for_uid, :parent => dcm)
  # Position Reference Indicator:
  DICOM::Element.new('0020,1040', '', :parent => dcm)
  # RT Plan Label (max 16 characters):
  plan_label = p ? p.rx_site_name[0..15] : @course_id
  DICOM::Element.new('300A,0002', plan_label, :parent => dcm)
  # RT Plan Name:
  plan_name = p ? p.rx_site_name : @course_id
  DICOM::Element.new('300A,0003', plan_name, :parent => dcm)
  # RT Plan Description:
  plan_desc = p ? p.technique : @diagnosis
  DICOM::Element.new('300A,0004', plan_desc, :parent => dcm)
  # RT Plan Date:
  plan_date = @plan_date.empty? ? Time.now.strftime("%Y%m%d") : @plan_date
  DICOM::Element.new('300A,0006', plan_date, :parent => dcm)
  # RT Plan Time:
  plan_time = @plan_time.empty? ? Time.now.strftime("%H%M%S") : @plan_time
  DICOM::Element.new('300A,0007', plan_time, :parent => dcm)
  # Approval Status:
  DICOM::Element.new('300E,0002', 'UNAPPROVED', :parent => dcm)
  #
  # SEQUENCES:
  #
  # Tolerance Table Sequence:
  if p && p.fields.first && !p.fields.first.tolerance_table.empty?
    tt_seq = DICOM::Sequence.new('300A,0040', :parent => dcm)
    tt_item = DICOM::Item.new(:parent => tt_seq)
    # Tolerance Table Number:
    DICOM::Element.new('300A,0042', p.fields.first.tolerance_table, :parent => tt_item)
  end
  # Structure set information:
  if p && p.site_setup && !p.site_setup.structure_set_uid.empty?
    #
    # Referenced Structure Set Sequence:
    #
    ss_seq = DICOM::Sequence.new('300C,0060', :parent => dcm)
    ss_item = DICOM::Item.new(:parent => ss_seq)
    # Referenced SOP Class UID:
    DICOM::Element.new('0008,1150', '1.2.840.10008.5.1.4.1.1.481.3', :parent => ss_item)
    DICOM::Element.new('0008,1155', p.site_setup.structure_set_uid, :parent => ss_item)
    # RT Plan Geometry:
    DICOM::Element.new('300A,000C', 'PATIENT', :parent => dcm)
  else
    # RT Plan Geometry:
    DICOM::Element.new('300A,000C', 'TREATMENT_DEVICE', :parent => dcm)
  end
  #
  # Patient Setup Sequence:
  #
  ps_seq = DICOM::Sequence.new('300A,0180', :parent => dcm)
  ps_item = DICOM::Item.new(:parent => ps_seq)
  # Patient Position:
  begin
    pat_pos = p.site_setup.patient_orientation.empty? ? 'HFS' : p.site_setup.patient_orientation
  rescue
    pat_pos = 'HFS'
  end
  DICOM::Element.new('0018,5100', pat_pos, :parent => ps_item)
  # Patient Setup Number:
  DICOM::Element.new('300A,0182', '1', :parent => ps_item)
  # Setup Technique (assume Isocentric):
  DICOM::Element.new('300A,01B0', 'ISOCENTRIC', :parent => ps_item)
  #
  # Dose Reference Sequence:
  #
  create_dose_reference(dcm, plan_name) if options[:dose_ref]
  #
  # Fraction Group Sequence:
  #
  fg_seq = DICOM::Sequence.new('300A,0070', :parent => dcm)
  fg_item = DICOM::Item.new(:parent => fg_seq)
  # Fraction Group Number:
  DICOM::Element.new('300A,0071', '1', :parent => fg_item)
  # Number of Fractions Planned (try to derive from total dose/fraction dose, or use 1 as default):
  begin
    num_frac = p.dose_ttl.empty? || p.dose_tx.empty? ? '1' : (p.dose_ttl.to_i / p.dose_tx.to_f).round.to_s
  rescue
    num_frac = '0'
  end
  DICOM::Element.new('300A,0078', num_frac, :parent => fg_item)
  # Number of Brachy Application Setups:
  DICOM::Element.new('300A,00A0', '0', :parent => fg_item)
  # Referenced Beam Sequence (items created for each beam below):
  rb_seq = DICOM::Sequence.new('300C,0004', :parent => fg_item)
  #
  # Beam Sequence:
  #
  b_seq = DICOM::Sequence.new('300A,00B0', :parent => dcm)
  if p
    # If no fields are present, we are not going to be able to make a valid DICOM object:
    logger.error("No Field Record present. Unable to build a valid RTPLAN DICOM object.") unless p.fields.length > 0
    p.fields.each_with_index do |field, i|
      # Fields with modality 'Unspecified' (e.g. CT or 2dkV) must be skipped:
      unless field.modality == 'Unspecified'
        # If this is an electron beam, a warning should be printed, as these are less reliably converted:
        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'
        # Reset control point 'current value' attributes:
        reset_cp_current_attributes
        # Beam number and name:
        beam_number = field.extended_field ? field.extended_field.original_beam_number : (i + 1).to_s
        beam_name = field.extended_field ? field.extended_field.original_beam_name : field.field_name
        # Ref Beam Item:
        rb_item = DICOM::Item.new(:parent => rb_seq)
        # Beam Dose (convert from cGy to Gy):
        field_dose = field.field_dose.empty? ? '' : (field.field_dose.to_f * 0.01).round(4).to_s
        DICOM::Element.new('300A,0084', field_dose, :parent => rb_item)
        # Beam Meterset:
        DICOM::Element.new('300A,0086', field.field_monitor_units, :parent => rb_item)
        # Referenced Beam Number:
        DICOM::Element.new('300C,0006', beam_number, :parent => rb_item)
        # Beam Item:
        b_item = DICOM::Item.new(:parent => b_seq)
        # Optional method values:
        # Manufacturer:
        DICOM::Element.new('0008,0070', options[:manufacturer], :parent => b_item) if options[:manufacturer]
        # Manufacturer's Model Name:
        DICOM::Element.new('0008,1090', options[:model], :parent => b_item) if options[:model]
        # Device Serial Number:
        DICOM::Element.new('0018,1000', options[:serial_number], :parent => b_item) if options[:serial_number]
        # Treatment Machine Name (max 16 characters):
        DICOM::Element.new('300A,00B2', field.treatment_machine[0..15], :parent => b_item)
        # Primary Dosimeter Unit:
        DICOM::Element.new('300A,00B3', 'MU', :parent => b_item)
        # Source-Axis Distance (convert to mm):
        DICOM::Element.new('300A,00B4', "#{field.sad.to_f * 10}", :parent => b_item)
        # Beam Number:
        DICOM::Element.new('300A,00C0', beam_number, :parent => b_item)
        # Beam Name:
        DICOM::Element.new('300A,00C2', beam_name, :parent => b_item)
        # Beam Description:
        DICOM::Element.new('300A,00C3', field.field_note, :parent => b_item)
        # Beam Type:
        beam_type = case field.treatment_type
          when 'Static' then 'STATIC'
          when 'StepNShoot' then 'STATIC'
          when 'VMAT' then 'DYNAMIC'
          else logger.error("The beam type (treatment type) #{field.treatment_type} is not yet supported.")
        end
        DICOM::Element.new('300A,00C4', beam_type, :parent => b_item)
        # Radiation Type:
        rad_type = case field.modality
          when 'Elect' then 'ELECTRON'
          when 'Xrays' then 'PHOTON'
          else logger.error("The radiation type (modality) #{field.modality} is not yet supported.")
        end
        DICOM::Element.new('300A,00C6', rad_type, :parent => b_item)
        # Treatment Delivery Type:
        DICOM::Element.new('300A,00CE', 'TREATMENT', :parent => b_item)
        # Number of Wedges:
        DICOM::Element.new('300A,00D0', (field.wedge.empty? ? '0' : '1'), :parent => b_item)
        # Number of Compensators:
        DICOM::Element.new('300A,00E0', (field.compensator.empty? ? '0' : '1'), :parent => b_item)
        # Number of Boli:
        DICOM::Element.new('300A,00ED', (field.bolus.empty? ? '0' : '1'), :parent => b_item)
        # Number of Blocks:
        DICOM::Element.new('300A,00F0', (field.block.empty? ? '0' : '1'), :parent => b_item)
        # Final Cumulative Meterset Weight:
        DICOM::Element.new('300A,010E', 1, :parent => b_item)
        # Referenced Patient Setup Number:
        DICOM::Element.new('300C,006A', '1', :parent => b_item)
        #
        # Beam Limiting Device Sequence:
        #
        create_beam_limiting_devices(b_item, field)
        #
        # Block Sequence (if any):
        # FIXME: It seems that the Block Sequence (300A,00F4) may be
        # difficult (impossible?) to reconstruct based on the RTP file's
        # information, and thus it is skipped altogether.
        #
        #
        # Applicator Sequence (if any):
        #
        unless field.e_applicator.empty?
          app_seq = DICOM::Sequence.new('300A,0107', :parent => b_item)
          app_item = DICOM::Item.new(:parent => app_seq)
          # Applicator ID:
          DICOM::Element.new('300A,0108', field.e_field_def_aperture, :parent => app_item)
          # Applicator Type:
          DICOM::Element.new('300A,0109', "ELECTRON_#{field.e_applicator.upcase}", :parent => app_item)
          # Applicator Description:
          DICOM::Element.new('300A,010A', "Appl. #{field.e_field_def_aperture}", :parent => app_item)
        end
        #
        # Control Point Sequence:
        #
        # A field may have 0 (no MLC), 1 (conventional beam with MLC) or 2n (IMRT) control points.
        # The DICOM file shall always contain 2n control points (minimum 2).
        #
        cp_seq = DICOM::Sequence.new('300A,0111', :parent => b_item)
        if field.control_points.length < 2
          # When we have 0 or 1 control point, use settings from field, and insert MLC settings if present:
          # First CP:
          cp_item = DICOM::Item.new(:parent => cp_seq)
          # Control Point Index:
          DICOM::Element.new('300A,0112', "0", :parent => cp_item)
          # Nominal Beam Energy:
          DICOM::Element.new('300A,0114', "#{field.energy.to_f}", :parent => cp_item)
          # Dose Rate Set:
          DICOM::Element.new('300A,0115', field.doserate, :parent => cp_item)
          # Gantry Angle:
          DICOM::Element.new('300A,011E', field.gantry_angle, :parent => cp_item)
          # Gantry Rotation Direction:
          DICOM::Element.new('300A,011F', (field.arc_direction.empty? ? 'NONE' : field.arc_direction), :parent => cp_item)
          # Beam Limiting Device Angle:
          DICOM::Element.new('300A,0120', field.collimator_angle, :parent => cp_item)
          # Beam Limiting Device Rotation Direction:
          DICOM::Element.new('300A,0121', 'NONE', :parent => cp_item)
          # Patient Support Angle:
          DICOM::Element.new('300A,0122', field.couch_pedestal, :parent => cp_item)
          # Patient Support Rotation Direction:
          DICOM::Element.new('300A,0123', 'NONE', :parent => cp_item)
          # Table Top Eccentric Angle:
          DICOM::Element.new('300A,0125', field.couch_angle, :parent => cp_item)
          # Table Top Eccentric Rotation Direction:
          DICOM::Element.new('300A,0126', 'NONE', :parent => cp_item)
          # Table Top Vertical Position:
          couch_vert = field.couch_vertical.empty? ? '' : (field.couch_vertical.to_f * 10).to_s
          DICOM::Element.new('300A,0128', couch_vert, :parent => cp_item)
          # Table Top Longitudinal Position:
          couch_long = field.couch_longitudinal.empty? ? '' : (field.couch_longitudinal.to_f * 10).to_s
          DICOM::Element.new('300A,0129', couch_long, :parent => cp_item)
          # Table Top Lateral Position:
          couch_lat = field.couch_lateral.empty? ? '' : (field.couch_lateral.to_f * 10).to_s
          DICOM::Element.new('300A,012A', couch_lat, :parent => cp_item)
          # Isocenter Position (x\y\z):
          if p.site_setup
            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_item)
          else
            logger.warn("No Site Setup record exists for this plan. Unable to provide an isosenter position.")
            DICOM::Element.new('300A,012C', '', :parent => cp_item)
          end
          # Source to Surface Distance:
          add_ssd(field.ssd, cp_item)
          # Cumulative Meterset Weight:
          DICOM::Element.new('300A,0134', '0', :parent => cp_item)
          # Beam Limiting Device Position Sequence:
          if field.control_points.length > 0
            create_beam_limiting_device_positions(cp_item, field.control_points.first, options)
          else
            create_beam_limiting_device_positions_from_field(cp_item, field, options)
          end
          # Referenced Dose Reference Sequence:
          create_referenced_dose_reference(cp_item) if options[:dose_ref]
          # Second CP:
          cp_item = DICOM::Item.new(:parent => cp_seq)
          # Control Point Index:
          DICOM::Element.new('300A,0112', "1", :parent => cp_item)
          # Cumulative Meterset Weight:
          DICOM::Element.new('300A,0134', '1', :parent => cp_item)
        else
          # When we have multiple (2 or more) control points, iterate each control point:
          field.control_points.each { |cp| create_control_point(cp, cp_seq, options) }
          # Make sure that hte cumulative meterset weight of the last control
          # point is '1' (exactly equal to final cumulative meterset weight):
          cp_seq.items.last['300A,0134'].value = '1'
        end
        # Number of Control Points:
        DICOM::Element.new('300A,0110', b_item['300A,0111'].items.length, :parent => b_item)
      end
    end
    # Number of Beams:
    DICOM::Element.new('300A,0080', fg_item['300C,0004'].items.length, :parent => fg_item)
  end
  # Restore the DICOM logger:
  DICOM.logger.level = original_level
  return dcm
end
to_plan() click to toggle source

Returns self.

@return [Plan] self

# File lib/rtp-connect/plan.rb, line 298
def to_plan
  self
end
to_rtp() click to toggle source

Returns self.

@return [Plan] self

# File lib/rtp-connect/plan.rb, line 306
def to_rtp
  self
end
write(file, options={}) click to toggle source

Writes the Plan object, along with its hiearchy of child objects, to a properly formatted RTPConnect ascii file.

@param [String] file a path/file string @param [Hash] options an optional hash parameter @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output

# File lib/rtp-connect/plan.rb, line 317
def write(file, options={})
  f = open_file(file)
  f.write(to_s(options))
  f.close
end

Private Instance Methods

add_angle(item, angle_tag, direction_tag, angle, direction, current_angle) click to toggle source

Adds an angular type value to a Control Point Item, by creating the necessary DICOM elements. Note that the element is only added if there is no 'current' attribute defined, or the given value is different form the current attribute.

@param [DICOM::Item] item the DICOM control point item in which to create the elements @param [String] angle_tag the DICOM tag of the angle element @param [String] direction_tag the DICOM tag of the direction element @param [String, NilClass] angle the collimator angle attribute @param [String, NilClass] direction the collimator rotation direction attribute @param [Symbol] current_angle the instance variable that keeps track of the current value of this attribute

# File lib/rtp-connect/plan_to_dcm.rb, line 383
def add_angle(item, angle_tag, direction_tag, angle, direction, current_angle)
  if !self.send(current_angle) || angle != self.send(current_angle)
    self.send("#{current_angle}=", angle)
    DICOM::Element.new(angle_tag, angle, :parent => item)
    DICOM::Element.new(direction_tag, (direction.empty? ? 'NONE' : direction), :parent => item)
  end
end
add_couch_position(item, tag, value, current) click to toggle source

Adds a Table Top Position element to a Control Point Item. Note that the element is only added if there is no 'current' attribute defined, or the given value is different form the current attribute.

@param [DICOM::Item] item the DICOM control point item in which to create the element @param [String] tag the DICOM tag of the couch position element @param [String, NilClass] value the couch position @param [Symbol] current the instance variable that keeps track of the current value of this attribute

# File lib/rtp-connect/plan_to_dcm.rb, line 400
def add_couch_position(item, tag, value, current)
  if !self.send(current) || value != self.send(current)
    self.send("#{current}=", value)
    DICOM::Element.new(tag, (value.empty? ? '' : value.to_f * 10), :parent => item)
  end
end
add_doserate(value, item) click to toggle source

Adds a Dose Rate Set element to a Control Point Item. Note that the element is only added if there is no 'current' attribute defined, or the given value is different form the current attribute.

@param [String, NilClass] value the doserate attribute @param [DICOM::Item] item the DICOM control point item in which to create an element

# File lib/rtp-connect/plan_to_dcm.rb, line 414
def add_doserate(value, item)
  if !@current_doserate || value != @current_doserate
    @current_doserate = value
    DICOM::Element.new('300A,0115', value, :parent => item)
  end
end
add_energy(value, item) click to toggle source

Adds a Nominal Beam Energy element to a Control Point Item. Note that the element is only added if there is no 'current' attribute defined, or the given value is different form the current attribute.

@param [String, NilClass] value the energy attribute @param [DICOM::Item] item the DICOM control point item in which to create an element

# File lib/rtp-connect/plan_to_dcm.rb, line 428
def add_energy(value, item)
  if !@current_energy || value != @current_energy
    @current_energy = value
    DICOM::Element.new('300A,0114', "#{value.to_f}", :parent => item)
  end
end
add_isosenter(site_setup, item) click to toggle source

Adds an Isosenter element to a Control Point Item. Note that the element is only added if there is a Site Setup record present, and it contains a real (non-empty) value. Also, the element is only added if there is no 'current' attribute defined, or the given value is different form the current attribute.

@param [SiteSetup, NilClass] site_setup the associated site setup record @param [DICOM::Item] item the DICOM control point item in which to create an element

# File lib/rtp-connect/plan_to_dcm.rb, line 443
def add_isosenter(site_setup, item)
  if site_setup
    # Create an element if the value is new or unique:
    if !@current_isosenter
      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)}"
      if iso != @current_isosenter
        @current_isosenter = iso
        DICOM::Element.new('300A,012C', iso, :parent => item)
      end
    end
  else
    # Log a warning if this is the first control point:
    unless @current_isosenter
      logger.warn("No Site Setup record exists for this plan. Unable to provide an isosenter position.")
    end
  end
end
add_ssd(value, item) click to toggle source

Adds a Source to Surface Distance element to a Control Point Item. Note that the element is only added if the SSD attribute contains real (non-empty) value.

@param [String, NilClass] value the SSD attribute @param [DICOM::Item] item the DICOM control point item in which to create an element

# File lib/rtp-connect/plan_to_dcm.rb, line 468
def add_ssd(value, item)
  DICOM::Element.new('300A,0130', "#{value.to_f * 10}", :parent => item) if value && !value.empty?
end
control_point(string) click to toggle source

Creates a control point record from the given string.

@param [String] string a string line containing a control point definition

# File lib/rtp-connect/plan.rb, line 537
def control_point(string)
  cp = ControlPoint.load(string, @current_parent)
  @current_parent = cp
end
create_asym_item(cp, dcm_parent, axis, options={}) click to toggle source

Creates an ASYMX or ASYMY item.

@param [ControlPoint] cp the RTP control point to fetch device parameters from @param [DICOM::Sequence] dcm_parent the DICOM sequence in which to insert the item @param [Symbol] axis the axis for the item (:x or :y) @return [DICOM::Item] the constructed ASYMX or ASYMY item

# File lib/rtp-connect/plan_to_dcm.rb, line 583
def create_asym_item(cp, dcm_parent, axis, options={})
  val1 = cp.send("dcm_collimator_#{axis.to_s}1", options[:scale])
  val2 = cp.send("dcm_collimator_#{axis.to_s}2", options[:scale])
  item = DICOM::Item.new(:parent => dcm_parent)
  # RT Beam Limiting Device Type:
  DICOM::Element.new('300A,00B8', "ASYM#{axis.to_s.upcase}", :parent => item)
  # Leaf/Jaw Positions:
  DICOM::Element.new('300A,011C', "#{val1}\\#{val2}", :parent => item)
  item
end
create_beam_limiting_device_positions(cp_item, cp, options={}) click to toggle source

Creates a beam limiting device positions sequence in the given DICOM object.

@param [DICOM::Item] cp_item the DICOM control point item in which to insert the sequence @param [ControlPoint] cp the RTP control point to fetch device parameters from @return [DICOM::Sequence] the constructed beam limiting device positions sequence

# File lib/rtp-connect/plan_to_dcm.rb, line 559
def create_beam_limiting_device_positions(cp_item, cp, options={})
  dp_seq = DICOM::Sequence.new('300A,011A', :parent => cp_item)
  # The ASYMX item ('backup jaws') doesn't exist on all models:
  if ['SYM', 'ASY'].include?(cp.parent.field_x_mode.upcase)
    dp_item_x = create_asym_item(cp, dp_seq, axis=:x, options)
  end
  # Always create one ASYMY item:
  dp_item_y = create_asym_item(cp, dp_seq, axis=:y, options)
  # MLCX:
  dp_item_mlcx = DICOM::Item.new(:parent => dp_seq)
  # RT Beam Limiting Device Type:
  DICOM::Element.new('300A,00B8', "MLCX", :parent => dp_item_mlcx)
  # Leaf/Jaw Positions:
  DICOM::Element.new('300A,011C', cp.dcm_mlc_positions(options[:scale]), :parent => dp_item_mlcx)
  dp_seq
end
create_beam_limiting_device_positions_from_field(cp_item, field, options={}) click to toggle source

Creates a beam limiting device positions sequence in the given DICOM object.

@param [DICOM::Item] cp_item the DICOM control point item in which to insert the sequence @param [Field] field the RTP treatment field to fetch device parameters from @return [DICOM::Sequence] the constructed beam limiting device positions sequence

# File lib/rtp-connect/plan_to_dcm.rb, line 600
def create_beam_limiting_device_positions_from_field(cp_item, field, options={})
  dp_seq = DICOM::Sequence.new('300A,011A', :parent => cp_item)
  # ASYMX:
  dp_item_x = DICOM::Item.new(:parent => dp_seq)
  DICOM::Element.new('300A,00B8', "ASYMX", :parent => dp_item_x)
  DICOM::Element.new('300A,011C', "#{field.dcm_collimator_x1}\\#{field.dcm_collimator_x2}", :parent => dp_item_x)
  # ASYMY:
  dp_item_y = DICOM::Item.new(:parent => dp_seq)
  DICOM::Element.new('300A,00B8', "ASYMY", :parent => dp_item_y)
  DICOM::Element.new('300A,011C', "#{field.dcm_collimator_y1}\\#{field.dcm_collimator_y2}", :parent => dp_item_y)
  dp_seq
end
create_beam_limiting_devices(beam_item, field) click to toggle source

Creates a beam limiting device sequence in the given DICOM object.

@param [DICOM::Item] beam_item the DICOM beam item in which to insert the sequence @param [Field] field the RTP field to fetch device parameters from @return [DICOM::Sequence] the constructed beam limiting device sequence

# File lib/rtp-connect/plan_to_dcm.rb, line 525
def create_beam_limiting_devices(beam_item, field)
  bl_seq = DICOM::Sequence.new('300A,00B6', :parent => beam_item)
  # The ASYMX item ('backup jaws') doesn't exist on all models:
  if ['SYM', 'ASY'].include?(field.field_x_mode.upcase)
    bl_item_x = DICOM::Item.new(:parent => bl_seq)
    DICOM::Element.new('300A,00B8', "ASYMX", :parent => bl_item_x)
    DICOM::Element.new('300A,00BC', "1", :parent => bl_item_x)
  end
  # The ASYMY item is always created:
  bl_item_y = DICOM::Item.new(:parent => bl_seq)
  # RT Beam Limiting Device Type:
  DICOM::Element.new('300A,00B8', "ASYMY", :parent => bl_item_y)
  # Number of Leaf/Jaw Pairs:
  DICOM::Element.new('300A,00BC', "1", :parent => bl_item_y)
  # MLCX item is only created if leaves are defined:
  # (NB: The RTP file doesn't specify leaf position boundaries, so we
  # have to set these based on a set of known MLC types, their number
  # of leaves, and their leaf boundary positions.)
  if field.control_points.length > 0
    bl_item_mlcx = DICOM::Item.new(:parent => bl_seq)
    DICOM::Element.new('300A,00B8', "MLCX", :parent => bl_item_mlcx)
    num_leaves = field.control_points.first.mlc_leaves.to_i
    DICOM::Element.new('300A,00BC', num_leaves.to_s, :parent => bl_item_mlcx)
    DICOM::Element.new('300A,00BE', "#{RTP.leaf_boundaries(num_leaves).join("\\")}", :parent => bl_item_mlcx)
  end
  bl_seq
end
create_control_point(cp, sequence, options={}) click to toggle source

Creates a control point item in the given control point sequence, based on an RTP control point record.

@param [ControlPoint] cp the RTP ControlPoint record to convert @param [DICOM::Sequence] sequence the DICOM parent sequence of the item to be created @param [Hash] options the options to use for creating the control point @option options [Boolean] :dose_ref if set, a Referenced Dose Reference sequence will be included in the generated control point item @return [DICOM::Item] the constructed control point DICOM item

# File lib/rtp-connect/plan_to_dcm.rb, line 481
def create_control_point(cp, sequence, options={})
  cp_item = DICOM::Item.new(:parent => sequence)
  # Some CP attributes will always be written (CP index, BLD positions & Cumulative meterset weight).
  # The other attributes are only written if they are different from the previous control point.
  # Control Point Index:
  DICOM::Element.new('300A,0112', "#{cp.index}", :parent => cp_item)
  # Beam Limiting Device Position Sequence:
  create_beam_limiting_device_positions(cp_item, cp, options)
  # Source to Surface Distance:
  add_ssd(cp.ssd, cp_item)
  # Cumulative Meterset Weight:
  DICOM::Element.new('300A,0134', cp.monitor_units.to_f, :parent => cp_item)
  # Referenced Dose Reference Sequence:
  create_referenced_dose_reference(cp_item) if options[:dose_ref]
  # Attributes that are only added if they carry an updated value:
  # Nominal Beam Energy:
  add_energy(cp.energy, cp_item)
  # Dose Rate Set:
  add_doserate(cp.doserate, cp_item)
  # Gantry Angle & Rotation Direction:
  add_angle(cp_item, '300A,011E', '300A,011F', cp.gantry_angle, cp.gantry_dir, :current_gantry)
  # Beam Limiting Device Angle & Rotation Direction:
  add_angle(cp_item, '300A,0120', '300A,0121', cp.collimator_angle, cp.collimator_dir, :current_collimator)
  # Patient Support Angle & Rotation Direction:
  add_angle(cp_item, '300A,0122', '300A,0123', cp.couch_pedestal, cp.couch_ped_dir, :current_couch_pedestal)
  # Table Top Eccentric Angle & Rotation Direction:
  add_angle(cp_item, '300A,0125', '300A,0126', cp.couch_angle, cp.couch_dir, :current_couch_angle)
  # Table Top Vertical Position:
  add_couch_position(cp_item, '300A,0128', cp.couch_vertical, :current_couch_vertical)
  # Table Top Longitudinal Position:
  add_couch_position(cp_item, '300A,0129', cp.couch_longitudinal, :current_couch_longitudinal)
  # Table Top Lateral Position:
  add_couch_position(cp_item, '300A,012A', cp.couch_lateral, :current_couch_lateral)
  # Isocenter Position (x\y\z):
  add_isosenter(cp.parent.parent.site_setup, cp_item)
  cp_item
end
create_dose_reference(dcm, description) click to toggle source

Creates a dose reference sequence in the given DICOM object.

@param [DICOM::DObject] dcm the DICOM object in which to insert the sequence @param [String] description the value to use for Dose Reference Description @return [DICOM::Sequence] the constructed dose reference sequence

# File lib/rtp-connect/plan_to_dcm.rb, line 619
def create_dose_reference(dcm, description)
  dr_seq = DICOM::Sequence.new('300A,0010', :parent => dcm)
  dr_item = DICOM::Item.new(:parent => dr_seq)
  # Dose Reference Number:
  DICOM::Element.new('300A,0012', '1', :parent => dr_item)
  # Dose Reference Structure Type:
  DICOM::Element.new('300A,0014', 'SITE', :parent => dr_item)
  # Dose Reference Description:
  DICOM::Element.new('300A,0016', description, :parent => dr_item)
  # Dose Reference Type:
  DICOM::Element.new('300A,0020', 'TARGET', :parent => dr_item)
  dr_seq
end
create_referenced_dose_reference(cp_item) click to toggle source

Creates a referenced dose reference sequence in the given DICOM object.

@param [DICOM::Item] cp_item the DICOM item in which to insert the sequence @return [DICOM::Sequence] the constructed referenced dose reference sequence

# File lib/rtp-connect/plan_to_dcm.rb, line 638
def create_referenced_dose_reference(cp_item)
  # Referenced Dose Reference Sequence:
  rd_seq = DICOM::Sequence.new('300C,0050', :parent => cp_item)
  rd_item = DICOM::Item.new(:parent => rd_seq)
  # Cumulative Dose Reference Coeffecient:
  DICOM::Element.new('300A,010C', '', :parent => rd_item)
  # Referenced Dose Reference Number:
  DICOM::Element.new('300C,0051', '1', :parent => rd_item)
  rd_seq
end
dose_tracking(string) click to toggle source

Creates a dose tracking record from the given string.

@param [String] string a string line containing a dose tracking definition

# File lib/rtp-connect/plan.rb, line 546
def dose_tracking(string)
  dt = DoseTracking.load(string, @current_parent)
  @current_parent = dt
end
extended_plan_def(string) click to toggle source

Creates an extended plan record from the given string.

@param [String] string a string line containing an extended plan definition

# File lib/rtp-connect/plan.rb, line 555
def extended_plan_def(string)
  ep = ExtendedPlan.load(string, @current_parent)
  @current_parent = ep
end
extended_treatment_field(string) click to toggle source

Creates an extended treatment field record from the given string.

@param [String] string a string line containing an extended treatment field definition

# File lib/rtp-connect/plan.rb, line 564
def extended_treatment_field(string)
  ef = ExtendedField.load(string, @current_parent)
  @current_parent = ef
end
open_file(file) click to toggle source

Tests if the path/file is writable, creates any folders if necessary, and opens the file for writing.

@param [String] file a path/file string @raise if the given file cannot be created

# File lib/rtp-connect/plan.rb, line 574
def open_file(file)
  # Check if file already exists:
  if File.exist?(file)
    # Is (the existing file) writable?
    unless File.writable?(file)
      raise "The program does not have permission or resources to create this file: #{file}"
    end
  else
    # File does not exist.
    # Check if this file's path contains a folder that does not exist, and therefore needs to be created:
    folders = file.split(File::SEPARATOR)
    if folders.length > 1
      # Remove last element (which should be the file string):
      folders.pop
      path = folders.join(File::SEPARATOR)
      # Check if this path exists:
      unless File.directory?(path)
        # We need to create (parts of) this path:
        require 'fileutils'
        FileUtils.mkdir_p(path)
      end
    end
  end
  # It has been verified that the file can be created:
  return File.new(file, 'wb:ISO8859-1')
end
prescription_site(string) click to toggle source

Creates a prescription site record from the given string.

@param [String] string a string line containing a prescription site definition

# File lib/rtp-connect/plan.rb, line 605
def prescription_site(string)
  p = Prescription.load(string, @current_parent)
  @current_parent = p
end
reset_cp_current_attributes() click to toggle source

Resets the types of control point attributes that are only written to the first control point item, and for following control point items only when they are different from the 'current' value. When a new field is reached, it is essential to reset these attributes, or else we could risk to start the field with a control point with missing attributes, if one of its first attributes is equal to the last attribute of the previous field.

# File lib/rtp-connect/plan_to_dcm.rb, line 656
def reset_cp_current_attributes
  @current_gantry = nil
  @current_collimator = nil
  @current_couch_pedestal = nil
  @current_couch_angle = nil
  @current_couch_vertical = nil
  @current_couch_longitudinal = nil
  @current_couch_lateral = nil
  @current_isosenter = nil
end
simulation_field(string) click to toggle source

Creates a simulation field record from the given string.

@param [String] string a string line containing a simulation field definition

# File lib/rtp-connect/plan.rb, line 639
def simulation_field(string)
  sf = SimulationField.load(string, @current_parent)
  @current_parent = sf
end
site_setup(string) click to toggle source

Creates a site setup record from the given string.

@param [String] string a string line containing a site setup definition

# File lib/rtp-connect/plan.rb, line 614
def site_setup(string)
  s = SiteSetup.load(string, @current_parent)
  @current_parent = s
end
treatment_field(string) click to toggle source

Creates a treatment field record from the given string.

@param [String] string a string line containing a treatment field definition

# File lib/rtp-connect/plan.rb, line 630
def treatment_field(string)
  f = Field.load(string, @current_parent)
  @current_parent = f
end