rtkit 0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +10 -0
- data/COPYING +674 -0
- data/README.rdoc +107 -0
- data/lib/rtkit.rb +68 -0
- data/lib/rtkit/beam.rb +346 -0
- data/lib/rtkit/bin_image.rb +578 -0
- data/lib/rtkit/bin_matcher.rb +241 -0
- data/lib/rtkit/bin_volume.rb +263 -0
- data/lib/rtkit/collimator.rb +157 -0
- data/lib/rtkit/collimator_setup.rb +143 -0
- data/lib/rtkit/constants.rb +215 -0
- data/lib/rtkit/contour.rb +213 -0
- data/lib/rtkit/control_point.rb +371 -0
- data/lib/rtkit/coordinate.rb +83 -0
- data/lib/rtkit/data_set.rb +264 -0
- data/lib/rtkit/dose.rb +70 -0
- data/lib/rtkit/dose_distribution.rb +206 -0
- data/lib/rtkit/dose_volume.rb +280 -0
- data/lib/rtkit/frame.rb +164 -0
- data/lib/rtkit/image.rb +372 -0
- data/lib/rtkit/image_series.rb +290 -0
- data/lib/rtkit/logging.rb +158 -0
- data/lib/rtkit/methods.rb +105 -0
- data/lib/rtkit/mixins/image_parent.rb +40 -0
- data/lib/rtkit/patient.rb +229 -0
- data/lib/rtkit/pixel_data.rb +237 -0
- data/lib/rtkit/plan.rb +259 -0
- data/lib/rtkit/plane.rb +165 -0
- data/lib/rtkit/roi.rb +388 -0
- data/lib/rtkit/rt_dose.rb +237 -0
- data/lib/rtkit/rt_image.rb +179 -0
- data/lib/rtkit/ruby_extensions.rb +165 -0
- data/lib/rtkit/selection.rb +189 -0
- data/lib/rtkit/series.rb +77 -0
- data/lib/rtkit/setup.rb +198 -0
- data/lib/rtkit/slice.rb +184 -0
- data/lib/rtkit/staple.rb +305 -0
- data/lib/rtkit/structure_set.rb +442 -0
- data/lib/rtkit/study.rb +214 -0
- data/lib/rtkit/variables.rb +23 -0
- data/lib/rtkit/version.rb +6 -0
- metadata +159 -0
@@ -0,0 +1,189 @@
|
|
1
|
+
module RTKIT
|
2
|
+
|
3
|
+
# Contains DICOM data and methods related to a Selection of
|
4
|
+
# pixels (indices) from the binary 2D NArray of a BinImage instance.
|
5
|
+
#
|
6
|
+
# === Relations
|
7
|
+
#
|
8
|
+
# * The Selection belongs to a BinImage.
|
9
|
+
# * A Contour has many Coordinates.
|
10
|
+
#
|
11
|
+
class Selection
|
12
|
+
|
13
|
+
# The BinImage that the Selection belongs to.
|
14
|
+
attr_reader :bin_image
|
15
|
+
# An array of (general) indices.
|
16
|
+
attr_reader :indices
|
17
|
+
# An NArray of (general) indices.
|
18
|
+
#attr_reader :indices_narr
|
19
|
+
|
20
|
+
# Creates a new Selection instance from an Array (or NArray) of (general) indices.
|
21
|
+
# Returns the Selection instance.
|
22
|
+
#
|
23
|
+
# === Parameters
|
24
|
+
#
|
25
|
+
# * <tt>arr</tt> -- An Array/NArray of general indices (Integers).
|
26
|
+
# * <tt>slice</tt> -- The BinImage instance that this Selection belongs to.
|
27
|
+
#
|
28
|
+
def self.create_from_array(indices, bin_image)
|
29
|
+
raise ArgumentError, "Invalid argument 'indices'. Expected Array/NArray, got #{indices.class}." unless [NArray, Array].include?(indices.class)
|
30
|
+
raise ArgumentError, "Invalid argument 'bin_image'. Expected BinImage, got #{bin_image.class}." unless bin_image.is_a?(BinImage)
|
31
|
+
raise ArgumentError, "Invalid argument 'indices'. Expected Array to contain only integers, got #{indices.collect{|i| i.class}.uniq}." if indices.is_a?(Array) and not indices.collect{|i| i.class}.uniq == [Fixnum]
|
32
|
+
# Create the Selection:
|
33
|
+
s = self.new(bin_image)
|
34
|
+
# Set the indices:
|
35
|
+
s.add_indices(indices)
|
36
|
+
return s
|
37
|
+
end
|
38
|
+
|
39
|
+
# Creates a new Selection instance.
|
40
|
+
#
|
41
|
+
# === Parameters
|
42
|
+
#
|
43
|
+
# * <tt>bin_image</tt> -- The BinImage instance that this Selection belongs to.
|
44
|
+
#
|
45
|
+
def initialize(bin_image)
|
46
|
+
raise ArgumentError, "Invalid argument 'bin_image'. Expected BinImage, got #{bin_image.class}." unless bin_image.is_a?(BinImage)
|
47
|
+
@bin_image = bin_image
|
48
|
+
@indices = Array.new
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns true if the argument is an instance with attributes equal to self.
|
52
|
+
#
|
53
|
+
def ==(other)
|
54
|
+
if other.respond_to?(:to_selection)
|
55
|
+
other.send(:state) == state
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
alias_method :eql?, :==
|
60
|
+
|
61
|
+
# Adds an array of (general) indices to this Selection.
|
62
|
+
#
|
63
|
+
def add_indices(indices)
|
64
|
+
raise ArgumentError, "Invalid argument 'indices'. Expected Array/NArray, got #{indices.class}." unless [NArray, Array].include?(indices.class)
|
65
|
+
raise ArgumentError, "Invalid argument 'indices'. Expected Array to contain only integers, got #{indices.collect{|i| i.class}.uniq}." if indices.is_a?(Array) and not indices.collect{|i| i.class}.uniq == [Fixnum]
|
66
|
+
indices = indices.to_a if indices.is_a?(NArray)
|
67
|
+
@indices += indices
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns an array of column indices.
|
71
|
+
# Returns an empty array if the instance contains no indices.
|
72
|
+
#
|
73
|
+
def columns
|
74
|
+
return @indices.collect {|index| index % @bin_image.columns}
|
75
|
+
end
|
76
|
+
|
77
|
+
# Generates a Fixnum hash value for this instance.
|
78
|
+
#
|
79
|
+
def hash
|
80
|
+
state.hash
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the length (number of indices) of this selection.
|
84
|
+
#
|
85
|
+
def length
|
86
|
+
return @indices.length
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns an array of row indices.
|
90
|
+
# Returns an empty array if the instance contains no indices.
|
91
|
+
#
|
92
|
+
def rows
|
93
|
+
return @indices.collect {|index| index / @bin_image.columns}
|
94
|
+
end
|
95
|
+
|
96
|
+
# Shifts the indices of this selection by the specified number of columns and rows.
|
97
|
+
# Positive arguments increases the column and row indices.
|
98
|
+
#
|
99
|
+
# === Restrictions
|
100
|
+
#
|
101
|
+
# NB! No out of bounds check is performed for indices
|
102
|
+
# that are shifted past the image boundary.
|
103
|
+
#
|
104
|
+
def shift(delta_col, delta_row)
|
105
|
+
raise ArgumentError, "Invalid argument 'delta_col'. Expected Integer, got #{delta_col.class}." unless delta_col.is_a?(Integer)
|
106
|
+
raise ArgumentError, "Invalid argument 'delta_row'. Expected Integer, got #{delta_row.class}." unless delta_row.is_a?(Integer)
|
107
|
+
new_columns = @indices.collect {|index| index % @bin_image.columns + delta_col}
|
108
|
+
new_rows = @indices.collect {|index| index / @bin_image.columns + delta_row}
|
109
|
+
# Set new indices:
|
110
|
+
@indices = Array.new(new_rows.length) {|i| new_columns[i] + new_rows[i] * @bin_image.columns}
|
111
|
+
end
|
112
|
+
|
113
|
+
# Shifts the indices of this selection by the specified number of columns and rows,
|
114
|
+
# virtually 'crops' the original image by 2*columns and 2*rows, and adapts the indices
|
115
|
+
# to this virtually cropped image.
|
116
|
+
#
|
117
|
+
# === Notes
|
118
|
+
#
|
119
|
+
# Negative arguments decreases the column and row indices and
|
120
|
+
# crops at the end of the columns and rows.
|
121
|
+
#
|
122
|
+
# Positive arguments increases the column and row indices and
|
123
|
+
# crops at the start of the columns and rows.
|
124
|
+
#
|
125
|
+
# === Restrictions
|
126
|
+
#
|
127
|
+
# NB! No out of bounds check is performed for indices
|
128
|
+
# that are shifted past the image boundary.
|
129
|
+
#
|
130
|
+
def shift_and_crop(delta_col, delta_row)
|
131
|
+
raise ArgumentError, "Invalid argument 'delta_col'. Expected Integer, got #{delta_col.class}." unless delta_col.is_a?(Integer)
|
132
|
+
raise ArgumentError, "Invalid argument 'delta_row'. Expected Integer, got #{delta_row.class}." unless delta_row.is_a?(Integer)
|
133
|
+
new_columns = @indices.collect {|index| index % @bin_image.columns - delta_col.abs}
|
134
|
+
new_rows = @indices.collect {|index| index / @bin_image.columns - delta_row.abs}
|
135
|
+
# Set new indices:
|
136
|
+
@indices = Array.new(new_rows.length) {|i| new_columns[i] + new_rows[i] * (@bin_image.columns - delta_col.abs * 2)}
|
137
|
+
end
|
138
|
+
|
139
|
+
# Shifts the indices of this selection by the specified number of columns.
|
140
|
+
# A positive argument increases the column indices.
|
141
|
+
#
|
142
|
+
# === Restrictions
|
143
|
+
#
|
144
|
+
# NB! No out of bounds check is performed for indices
|
145
|
+
# that are shifted past the image boundary.
|
146
|
+
#
|
147
|
+
def shift_columns(delta)
|
148
|
+
raise ArgumentError, "Invalid argument 'delta'. Expected Integer, got #{delta.class}." unless delta.is_a?(Integer)
|
149
|
+
new_columns = @indices.collect {|index| index % @bin_image.columns + delta}
|
150
|
+
new_rows = rows
|
151
|
+
# Set new indices:
|
152
|
+
@indices = Array.new(new_columns.length) {|i| new_columns[i] + new_rows[i] * @bin_image.columns}
|
153
|
+
end
|
154
|
+
|
155
|
+
# Shifts the indices of this selection by the specified number of rows.
|
156
|
+
# A positive argument increases the row indices.
|
157
|
+
#
|
158
|
+
# === Restrictions
|
159
|
+
#
|
160
|
+
# NB! No out of bounds check is performed for indices
|
161
|
+
# that are shifted past the image boundary.
|
162
|
+
#
|
163
|
+
def shift_rows(delta)
|
164
|
+
raise ArgumentError, "Invalid argument 'delta'. Expected Integer, got #{delta.class}." unless delta.is_a?(Integer)
|
165
|
+
new_columns = columns
|
166
|
+
new_rows = @indices.collect {|index| index / @bin_image.columns + delta}
|
167
|
+
# Set new indices:
|
168
|
+
@indices = Array.new(new_rows.length) {|i| new_columns[i] + new_rows[i] * @bin_image.columns}
|
169
|
+
end
|
170
|
+
|
171
|
+
# Returns self.
|
172
|
+
#
|
173
|
+
def to_selection
|
174
|
+
self
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
|
181
|
+
# Returns the attributes of this instance in an array (for comparison purposes).
|
182
|
+
#
|
183
|
+
def state
|
184
|
+
[@indices]
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
data/lib/rtkit/series.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
module RTKIT
|
2
|
+
|
3
|
+
# Contains the DICOM data and methods related to a series.
|
4
|
+
#
|
5
|
+
# === Relations
|
6
|
+
#
|
7
|
+
# * A Series belongs to a Study.
|
8
|
+
# * Some types of Series (e.g. CT, MR) have many Image instances.
|
9
|
+
#
|
10
|
+
class Series
|
11
|
+
|
12
|
+
# The SOP Class UID.
|
13
|
+
attr_reader :class_uid
|
14
|
+
# The Series Date.
|
15
|
+
attr_reader :date
|
16
|
+
# The Series Description.
|
17
|
+
attr_reader :description
|
18
|
+
# The Modality of the Series.
|
19
|
+
attr_reader :modality
|
20
|
+
# The Series's Study reference.
|
21
|
+
attr_reader :study
|
22
|
+
# The Series Instance UID.
|
23
|
+
attr_reader :series_uid
|
24
|
+
# The Series Time.
|
25
|
+
attr_reader :time
|
26
|
+
|
27
|
+
# Creates a new Series instance. The Series Instance UID string is used to uniquely identify a Series.
|
28
|
+
#
|
29
|
+
# === Parameters
|
30
|
+
#
|
31
|
+
# * <tt>series_uid</tt> -- The Series Instance UID string.
|
32
|
+
# * <tt>modality</tt> -- The Modality string of the Series, e.g. 'CT' or 'RTSTRUCT'.
|
33
|
+
# * <tt>study</tt> -- The Study instance that this Series belongs to.
|
34
|
+
# * <tt>options</tt> -- A hash of parameters.
|
35
|
+
#
|
36
|
+
# === Options
|
37
|
+
#
|
38
|
+
# * <tt>:class_uid</tt> -- String. The SOP Class UID (DICOM tag '0008,0016').
|
39
|
+
# * <tt>:date</tt> -- String. The Series Date (DICOM tag '0008,0021').
|
40
|
+
# * <tt>:time</tt> -- String. The Series Time (DICOM tag '0008,0031').
|
41
|
+
# * <tt>:description</tt> -- String. The Series Description (DICOM tag '0008,103E').
|
42
|
+
#
|
43
|
+
def initialize(series_uid, modality, study, options={})
|
44
|
+
raise ArgumentError, "Invalid argument 'uid'. Expected String, got #{series_uid.class}." unless series_uid.is_a?(String)
|
45
|
+
raise ArgumentError, "Invalid argument 'modality'. Expected String, got #{modality.class}." unless modality.is_a?(String)
|
46
|
+
raise ArgumentError, "Invalid argument 'study'. Expected Study, got #{study.class}." unless study.is_a?(Study)
|
47
|
+
# Key attributes:
|
48
|
+
@series_uid = series_uid
|
49
|
+
@modality = modality
|
50
|
+
@study = study
|
51
|
+
# Optional attributes:
|
52
|
+
@class_uid = options[:class_uid]
|
53
|
+
@date = options[:date]
|
54
|
+
@time = options[:time]
|
55
|
+
@description = options[:description]
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns true if the series is of a modality which means it contains multiple images (CT, MR, RTImage, RTDose).
|
59
|
+
# Returns false if not.
|
60
|
+
#
|
61
|
+
def image_modality?
|
62
|
+
if IMAGE_MODALITIES.include?(@modality)
|
63
|
+
return true
|
64
|
+
else
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the unique identifier string, which for an ImageSeries is the Series Instance UID,
|
70
|
+
# and for the other types of Series (e.g. StructureSet, Plan, etc) it is the SOP Instance UID.
|
71
|
+
#
|
72
|
+
def uid
|
73
|
+
return @sop_uid || @series_uid
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
data/lib/rtkit/setup.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
module RTKIT
|
2
|
+
|
3
|
+
# Contains DICOM data and methods related to a patient Setup item, defined in a Plan.
|
4
|
+
#
|
5
|
+
# === Relations
|
6
|
+
#
|
7
|
+
# * A Plan has a Setup.
|
8
|
+
#
|
9
|
+
class Setup
|
10
|
+
|
11
|
+
# Patient setup number (Integer).
|
12
|
+
attr_reader :number
|
13
|
+
# Table top lateral setup displacement (Float).
|
14
|
+
attr_reader :offset_lateral
|
15
|
+
# Table top longitudinal setup displacement (Float).
|
16
|
+
attr_reader :offset_longitudinal
|
17
|
+
# Table top vertical setup displacement (Float).
|
18
|
+
attr_reader :offset_vertical
|
19
|
+
# The Plan that the Setup is defined for.
|
20
|
+
attr_reader :plan
|
21
|
+
# Patient position (orientation).
|
22
|
+
attr_reader :position
|
23
|
+
# Setup technique.
|
24
|
+
attr_reader :technique
|
25
|
+
|
26
|
+
# Creates a new Setup instance from the patient setup item of the RTPlan file.
|
27
|
+
# Returns the Setup instance.
|
28
|
+
#
|
29
|
+
# === Parameters
|
30
|
+
#
|
31
|
+
# * <tt>setup_item</tt> -- The patient setup item from the DObject of a RTPlan file.
|
32
|
+
# * <tt>plan</tt> -- The Plan instance that this Setup belongs to.
|
33
|
+
#
|
34
|
+
def self.create_from_item(setup_item, plan)
|
35
|
+
raise ArgumentError, "Invalid argument 'setup_item'. Expected DICOM::Item, got #{setup_item.class}." unless setup_item.is_a?(DICOM::Item)
|
36
|
+
raise ArgumentError, "Invalid argument 'plan'. Expected Plan, got #{plan.class}." unless plan.is_a?(Plan)
|
37
|
+
options = Hash.new
|
38
|
+
# Values from the Patient Setup Item:
|
39
|
+
position = setup_item.value(PATIENT_POSITION) || ''
|
40
|
+
number = setup_item.value(PATIENT_SETUP_NUMBER).to_i
|
41
|
+
options[:technique] = setup_item.value(SETUP_TECHNIQUE)
|
42
|
+
options[:offset_vertical] = setup_item.value(OFFSET_VERTICAL).to_f
|
43
|
+
options[:offset_longitudinal] = setup_item.value(OFFSET_LONG).to_f
|
44
|
+
options[:offset_lateral] = setup_item.value(OFFSET_LATERAL).to_f
|
45
|
+
# Create the Setup instance:
|
46
|
+
s = self.new(position, number, plan, options)
|
47
|
+
return s
|
48
|
+
end
|
49
|
+
|
50
|
+
# Creates a new Setup instance.
|
51
|
+
#
|
52
|
+
# === Parameters
|
53
|
+
#
|
54
|
+
# * <tt>position</tt> -- String. The patient position (orientation).
|
55
|
+
# * <tt>number</tt> -- Integer. The Setup number.
|
56
|
+
# * <tt>plan</tt> -- The Plan instance that this Beam belongs to.
|
57
|
+
# * <tt>options</tt> -- A hash of parameters.
|
58
|
+
#
|
59
|
+
# === Options
|
60
|
+
#
|
61
|
+
# * <tt>:technique</tt> -- String. Setup technique.
|
62
|
+
# * <tt>:offset_vertical</tt> -- Float. Table top vertical setup displacement.
|
63
|
+
# * <tt>:offset_longitudinal</tt> -- Float. Table top longitudinal setup displacement.
|
64
|
+
# * <tt>:offset_lateral</tt> -- Float. Table top lateral setup displacement.
|
65
|
+
#
|
66
|
+
def initialize(position, number, plan, options={})
|
67
|
+
raise ArgumentError, "Invalid argument 'plan'. Expected Plan, got #{plan.class}." unless plan.is_a?(Plan)
|
68
|
+
# Set values:
|
69
|
+
self.position = position
|
70
|
+
self.number = number
|
71
|
+
# Set options:
|
72
|
+
self.technique = options[:technique] if options[:technique]
|
73
|
+
self.offset_vertical = options[:offset_vertical] if options[:offset_vertical]
|
74
|
+
self.offset_longitudinal = options[:offset_longitudinal] if options[:offset_longitudinal]
|
75
|
+
self.offset_lateral = options[:offset_lateral] if options[:offset_lateral]
|
76
|
+
# Set references:
|
77
|
+
@plan = plan
|
78
|
+
# Register ourselves with the Plan:
|
79
|
+
@plan.add_setup(self)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns true if the argument is an instance with attributes equal to self.
|
83
|
+
#
|
84
|
+
def ==(other)
|
85
|
+
if other.respond_to?(:to_setup)
|
86
|
+
other.send(:state) == state
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
alias_method :eql?, :==
|
91
|
+
|
92
|
+
# Generates a Fixnum hash value for this instance.
|
93
|
+
#
|
94
|
+
def hash
|
95
|
+
state.hash
|
96
|
+
end
|
97
|
+
|
98
|
+
# Sets a new patient setup number.
|
99
|
+
#
|
100
|
+
# === Parameters
|
101
|
+
#
|
102
|
+
# * <tt>value</tt> -- Float. The patient setup number.
|
103
|
+
#
|
104
|
+
def number=(value)
|
105
|
+
raise ArgumentError, "Invalid argument 'value'. Expected Integer, got #{value.class}." unless value.is_a?(Integer)
|
106
|
+
@number = value
|
107
|
+
end
|
108
|
+
|
109
|
+
# Sets a new table top lateral setup displacement.
|
110
|
+
#
|
111
|
+
# === Parameters
|
112
|
+
#
|
113
|
+
# * <tt>value</tt> -- Float. The table top lateral setup displacement.
|
114
|
+
#
|
115
|
+
def offset_lateral=(value)
|
116
|
+
raise ArgumentError, "Invalid argument 'value'. Expected Float, got #{value.class}." unless value.is_a?(Float)
|
117
|
+
@offset_lateral = value
|
118
|
+
end
|
119
|
+
|
120
|
+
# Sets a new table top longitudinal setup displacement.
|
121
|
+
#
|
122
|
+
# === Parameters
|
123
|
+
#
|
124
|
+
# * <tt>value</tt> -- Float. The table top longitudinal setup displacement.
|
125
|
+
#
|
126
|
+
def offset_longitudinal=(value)
|
127
|
+
raise ArgumentError, "Invalid argument 'value'. Expected Float, got #{value.class}." unless value.is_a?(Float)
|
128
|
+
@offset_longitudinal = value
|
129
|
+
end
|
130
|
+
|
131
|
+
# Sets a new table top vertical setup displacement.
|
132
|
+
#
|
133
|
+
# === Parameters
|
134
|
+
#
|
135
|
+
# * <tt>value</tt> -- Float. The table top vertical setup displacement.
|
136
|
+
#
|
137
|
+
def offset_vertical=(value)
|
138
|
+
raise ArgumentError, "Invalid argument 'value'. Expected Float, got #{value.class}." unless value.is_a?(Float)
|
139
|
+
@offset_vertical = value
|
140
|
+
end
|
141
|
+
|
142
|
+
# Sets a new patient position.
|
143
|
+
#
|
144
|
+
# === Parameters
|
145
|
+
#
|
146
|
+
# * <tt>value</tt> -- String. The patient position.
|
147
|
+
#
|
148
|
+
def position=(value)
|
149
|
+
raise ArgumentError, "Invalid argument 'value'. Expected String, got #{value.class}." unless value.is_a?(String)
|
150
|
+
@position = value
|
151
|
+
end
|
152
|
+
|
153
|
+
# Sets a new setup technique.
|
154
|
+
#
|
155
|
+
# === Parameters
|
156
|
+
#
|
157
|
+
# * <tt>value</tt> -- String. The setup technique.
|
158
|
+
#
|
159
|
+
def technique=(value)
|
160
|
+
raise ArgumentError, "Invalid argument 'value'. Expected String, got #{value.class}." unless value.is_a?(String)
|
161
|
+
@technique = value
|
162
|
+
end
|
163
|
+
|
164
|
+
# Creates and returns a Patient Setup Sequence Item from the attributes of the Setup.
|
165
|
+
#
|
166
|
+
def to_item
|
167
|
+
item = DICOM::Item.new
|
168
|
+
item.add(DICOM::Element.new(ROI_COLOR, @color))
|
169
|
+
s = DICOM::Sequence.new(CONTOUR_SQ)
|
170
|
+
item.add(s)
|
171
|
+
item.add(DICOM::Element.new(REF_ROI_NUMBER, @number.to_s))
|
172
|
+
# Add Contour items to the Contour Sequence (one or several items per Slice):
|
173
|
+
@slices.each do |slice|
|
174
|
+
slice.contours.each do |contour|
|
175
|
+
s.add_item(contour.to_item)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
return item
|
179
|
+
end
|
180
|
+
|
181
|
+
# Returns self.
|
182
|
+
#
|
183
|
+
def to_setup
|
184
|
+
self
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
|
191
|
+
# Returns the attributes of this instance in an array (for comparison purposes).
|
192
|
+
#
|
193
|
+
def state
|
194
|
+
[@number, @offset_lateral, @offset_longitudinal, @offset_vertical, @position, @technique]
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|