Class: DicomS::Sequence

Inherits:
Object
  • Object
show all
Includes:
Support
Defined in:
lib/dicoms/sequence.rb

Overview

DICOM series containing a sequence of CT/MRI image slices.

The following metadata about the series is computed:

  • min: pixel value corresponding to the minimum output level

  • max: pixel value corresponding to the maximum output level

  • rescaled: 1 means that min and max are photometrically rescaled values; 0 means they are internal raw values.

  • slope, intercept: photometric rescaling parameters.

  • lim_min, lim_max: (raw) output enconding range limits

  • bits: bit-depth of the original pixel values

  • signed: 1-pixels values are signed 0-unsigned

  • firstx: first X coordinate (slice number) of the ROI

  • lastx: lastt X coordinate (slice number) of the ROI

  • firsty: first Y coordinate of the ROI

  • lasty: last Y coordinate of the ROI

  • lastz:lastt Z coordinate of the ROI

  • study_id, series_id: identification of the patient’s study/series

  • dx, dy, dz: voxel spacing (dx, dy is pixel spacing; dz is the slice spacing)

  • x, y, z: image position (in RCS; see below)

  • xaxis, yaxis, zaxis: each is a three-component vector defining an axis of the image orientation (see below) (encoded as comma-separated string)

  • slice_z: slice position

  • nx: number of columns

  • ny: number of rows

  • nz: number of slices

  • reverse_x 1 means the X axis is reversed (from RCS)

  • reverse_y 1 means the Y axis is reversed (from RCS)

  • reverse_< 1 means the Z axis is reversed (from RCS)

  • axial_sx scale factor: horizontal reduction of axial projections

  • axial_sy scale factor: vertical reduction of axial projections

  • coronal_sx scale factor: horizontal reduction of coronal projections

  • coronal_sy scale factor: vertical reduction of coronal projections

  • sagittal_sx scale factor: horizontal reduction of sagittal projections

  • sagittal_sy scale factor: vertical reduction of sagittal projections

Orientation of RCS (patient coordinate system) in relation to the DICOM sequence reference. This is given as three vectors xaxis yaxis and zaxis.

The RCS system consists of the axes X, Y, Z:

  • X increases from Right to Left of the patient

  • Y increases from the Anterior to the Posterior side

  • Z increases from the Inferior to the Superior side

(Inferior/Superior are sometimes referred to as Bottom/Top or Feet/Head)

The xaxis vector is an unitary vector in the X direction projected in the DICOM reference system x, y, z axes. Similarly, yaxis and zaxis are unitary vectors in the Y and Z directions projected into the DICO reference system.

The DICOM reference uses these axes:

  • x left to right pixel matrix column

  • y top to bottom pixel matrix row

  • z first to last slice

The most common orientation for CT is:

  * xaxis: 1,0,0

* yaxis: 0,1,0
* zaxis: 0,0,-1

In this case, the X and Y axes are coincident with x an y of the DICOM reference and slices are ordered in decreasing Z value.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Support

#assign_dicom_pixels, #cast_metadata, #decode_vector, #define_transfer, #dicom?, #dicom_bit_depth, #dicom_compression, #dicom_element_value, #dicom_name_pattern, #dicom_narray, #dicom_rescale_intercept, #dicom_rescale_slope, #dicom_signed?, #dicom_stored_bits, #dicom_window_center, #dicom_window_width, #encode_vector, #find_dicom_files, #keeping_path, #normalized_path, #output_file_name, #pixel_value_range, #single_dicom_metadata

Constructor Details

#initialize(dicom_directory, options = {}) ⇒ Sequence

Returns a new instance of Sequence.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/dicoms/sequence.rb', line 73

def initialize(dicom_directory, options = {})
  @roi = options[:roi]
  @files = find_dicom_files(dicom_directory)
  @visitors = Array(options[:visit])
  @visited = Array.new(@files.size)
   = nil
  @strategy = options[:transfer]
   = Settings[version: 'DSPACK1']

  # TODO: reuse existing metadata in options (via settings)

  if options[:reorder]
    # don't trust the file ordering; use the instance number to ordering
    # this requires reading all the files in advance
    compute_metadata! true
  else
    # if information about the series size (nx, ny, nz)
    # and the axis orientation (reverse_x, reverse_y, reverse_z)
    # is available here we can set the @roi now and avoid
    # processing slices outside it. (in that case the
    # metadata won't consider those slices, e.g. for minimum/maximum
    # pixel values)
    if options[:nx] && options[:ny] && options[:nz] &&
       options.to_h.has_key?(:reverse_x) &&
       options.to_h.has_key?(:reverse_y) &&
       options.to_h.has_key?(:reverse_z)
      .merge!(
        nx: options[:nx], ny: options[:ny], nz: options[:nz],
        reverse_x: options[:reverse_x],
        reverse_y: options[:reverse_y],
        reverse_z: options[:reverse_z]
      )
      set_cropping_volume!
      cropping_set = true
    end
    compute_metadata!
  end

  set_cropping_volume! unless cropping_set
end

Instance Attribute Details

#filesObject (readonly)

Returns the value of attribute files.



114
115
116
# File 'lib/dicoms/sequence.rb', line 114

def files
  @files
end

#image_croppingObject (readonly)

Returns the value of attribute image_cropping.



116
117
118
# File 'lib/dicoms/sequence.rb', line 116

def image_cropping
  @image_cropping
end

#metadataObject

Returns the value of attribute metadata.



115
116
117
# File 'lib/dicoms/sequence.rb', line 115

def 
  
end

#strategyObject (readonly)

Returns the value of attribute strategy.



114
115
116
# File 'lib/dicoms/sequence.rb', line 114

def strategy
  @strategy
end

Instance Method Details

#check_seriesObject

Check if the images belong to a single series



207
208
209
210
# File 'lib/dicoms/sequence.rb', line 207

def check_series
  visit_all
  @visited.reject { |name, study, series, instance| study == .study_id && series == .series_id }.empty?
end

#dicom(i) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/dicoms/sequence.rb', line 128

def dicom(i)
  # TODO: support caching strategies for reading as DICOM objects:
  #       no-caching, max-size cache, ...
  dicom = DICOM::DObject.read(@files[i])
  sop_class = dicom['0002,0002'].value
  unless sop_class == '1.2.840.10008.5.1.4.1.1.2'
    raise "Unsopported SOP Class #{sop_class}"
  end
  # TODO: require known SOP Class:
  # (in tag 0002,0002, Media Storage SOP Class UID)

  visit dicom, i, *@visitors
  dicom
end

#dicom_image(dicom) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/dicoms/sequence.rb', line 165

def dicom_image(dicom)
  if dicom.is_a?(Magick::Image)
    image = dicom
  else
    if @strategy
      image = @strategy.image(dicom, .min, .max)
    else
      image = dicom.image
    end
  end
  if DICOM.image_processor == :mini_magick
    image.format('jpg')
  end
  if @image_cropping
    @image_cropping.inspect
    firstx, lastx, firsty, lasty = @image_cropping
    image.crop! firstx, firsty, lastx-firstx+1, lasty-firsty+1
  end
  image
end

#dicom_pixels(dicom, options = {}) ⇒ Object

To use the pixels to be directly saved to an image, use the ‘:unsigned` option to obtain usigned intensity values. If the pixels are to be assigned as Dicom pixels (’Dicom#pixels=‘) they don’t need to be unsigned.



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/dicoms/sequence.rb', line 190

def dicom_pixels(dicom, options = {})
  if @strategy
    pixels = @strategy.pixels(dicom, .min, .max)
  else
    pixels = dicom_narray(dicom, level: false, remap: true)
  end
  if @image_cropping
    firstx, lastx, firsty, lasty = @image_cropping
    pixels = pixels[firstx..lastx, firsty..lasty]
  end
  if options[:unsigned] && .lim_min < 0
    pixels.add! -.lim_min
  end
  pixels
end

#each(&blk) ⇒ Object



151
152
153
154
155
156
# File 'lib/dicoms/sequence.rb', line 151

def each(&blk)
  (0...@files.size).each do |i|
    dicom = dicom(i)
    visit dicom, i, blk
  end
end

#firstObject



143
144
145
# File 'lib/dicoms/sequence.rb', line 143

def first
  dicom(0)
end

#lastObject



147
148
149
# File 'lib/dicoms/sequence.rb', line 147

def last
  dicom(size-1)
end

#needs_reordering?Boolean

Returns:

  • (Boolean)


219
220
221
222
223
# File 'lib/dicoms/sequence.rb', line 219

def needs_reordering?
  visit_all
  @visited.sort_by! { |name, study, series, instance| [study, series, instance] }
  @files != @visited.map { |name, study, series, instance| name }
end

#reorder!Object



212
213
214
215
216
217
# File 'lib/dicoms/sequence.rb', line 212

def reorder!
  # This may invalidate dz and the cropped selection of slices
  visit_all
  @visited.sort_by! { |name, study, series, instance| [study, series, instance] }
  @files = @visited.map { |name, study, series, instance| name }
end

#save_jpg(dicom, filename) ⇒ Object



158
159
160
161
162
163
# File 'lib/dicoms/sequence.rb', line 158

def save_jpg(dicom, filename)
  keeping_path do
    image = dicom_image(dicom)
    image.write(filename)
  end
end

#sizeObject



124
125
126
# File 'lib/dicoms/sequence.rb', line 124

def size
  @files.size
end

#transferObject



118
119
120
# File 'lib/dicoms/sequence.rb', line 118

def transfer
  @strategy
end