dicoms 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.md +598 -0
- data/README.md +48 -0
- data/Rakefile +22 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/dicoms.gemspec +33 -0
- data/exe/dicoms +8 -0
- data/lib/dicoms.rb +67 -0
- data/lib/dicoms/cli.rb +241 -0
- data/lib/dicoms/command_options.rb +40 -0
- data/lib/dicoms/extract.rb +87 -0
- data/lib/dicoms/meta_codec.rb +131 -0
- data/lib/dicoms/pack.rb +82 -0
- data/lib/dicoms/progress.rb +80 -0
- data/lib/dicoms/projection.rb +422 -0
- data/lib/dicoms/remap.rb +46 -0
- data/lib/dicoms/sequence.rb +415 -0
- data/lib/dicoms/shared_files.rb +61 -0
- data/lib/dicoms/shared_settings.rb +111 -0
- data/lib/dicoms/stats.rb +30 -0
- data/lib/dicoms/support.rb +349 -0
- data/lib/dicoms/transfer.rb +339 -0
- data/lib/dicoms/unpack.rb +209 -0
- data/lib/dicoms/version.rb +3 -0
- metadata +200 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class DicomS
|
5
|
+
|
6
|
+
# Shared files can be concurrently accessed by diferent processes.
|
7
|
+
# They should never be large files, and update operations (reading,
|
8
|
+
# then writing) should always be quick, because processes trying to
|
9
|
+
# read the file while an update is going on are blocked.
|
10
|
+
#
|
11
|
+
# Example
|
12
|
+
#
|
13
|
+
# counter = SharedFile.new('counter')
|
14
|
+
#
|
15
|
+
# counter.update do |contents|
|
16
|
+
# contents.to_i + 1
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# counter = counter.read.to_i
|
20
|
+
#
|
21
|
+
class SharedFile
|
22
|
+
def initialize(name, options = {})
|
23
|
+
@name = name
|
24
|
+
raise "A directory exists with that name" if File.directory?(@name)
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :name
|
28
|
+
|
29
|
+
def exists?
|
30
|
+
File.exists?(@name)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Update a file safely.
|
34
|
+
def update(&blk)
|
35
|
+
File.open(@name, File::RDWR|File::CREAT, 0644) do |file|
|
36
|
+
file.flock File::LOCK_EX
|
37
|
+
if blk.arity == 1
|
38
|
+
new_contents = blk.call(file.read)
|
39
|
+
else
|
40
|
+
new_contents = blk.call
|
41
|
+
end
|
42
|
+
file.rewind
|
43
|
+
file.write new_contents
|
44
|
+
file.flush
|
45
|
+
file.truncate file.pos
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Read a file safely
|
50
|
+
def read
|
51
|
+
File.open(@name, "r") do |file|
|
52
|
+
file.flock File::LOCK_SH # this blocks until available
|
53
|
+
file.read
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def write(contents)
|
58
|
+
update { contents }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class DicomS
|
5
|
+
|
6
|
+
# Shared Settings are Settings stored in a SharedFile so
|
7
|
+
# they can be used concurrently from different processes.
|
8
|
+
class SharedSettings
|
9
|
+
def initialize(name, options = {})
|
10
|
+
@file = SharedFile.new(name)
|
11
|
+
@format = options[:format]
|
12
|
+
@compact = options[:compact]
|
13
|
+
unless @format
|
14
|
+
if File.extname(@file.name) == '.json'
|
15
|
+
@format = :json
|
16
|
+
else
|
17
|
+
@format = :yaml
|
18
|
+
end
|
19
|
+
end
|
20
|
+
contents = options[:initial_contents]
|
21
|
+
if contents && !@file.exists?
|
22
|
+
# Create with given initial contents
|
23
|
+
write contents
|
24
|
+
end
|
25
|
+
contents = options[:replace_contents]
|
26
|
+
write contents if contents
|
27
|
+
end
|
28
|
+
|
29
|
+
# Read a shared file and obtain a Settings object
|
30
|
+
#
|
31
|
+
# counter = shared_settings.read.counter
|
32
|
+
#
|
33
|
+
def read
|
34
|
+
decode @file.read
|
35
|
+
end
|
36
|
+
|
37
|
+
# To make sure contents are not changed between reading and writing
|
38
|
+
# use update:
|
39
|
+
#
|
40
|
+
# shared_settings.update do |data|
|
41
|
+
# # modify data and return modified data
|
42
|
+
# data.counter += 1
|
43
|
+
# data
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
def update(&blk)
|
47
|
+
@file.update do |data|
|
48
|
+
encode blk.call(decode(data))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Use this only if the contents written is independet of
|
53
|
+
# the previous content (i.e. no need to read, the change the data
|
54
|
+
# and write it back)
|
55
|
+
#
|
56
|
+
# shared_settings.write Setting[counter: 0
|
57
|
+
#
|
58
|
+
def write(data)
|
59
|
+
@file.write encode(data)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def encode(data)
|
65
|
+
if data.is_a?(Settings) || data.is_a?(Hash)
|
66
|
+
# Specially for YAML, we don't want to use symbols for
|
67
|
+
# hash keys because if read with languages other than
|
68
|
+
# Ruby that may cause some troubles.
|
69
|
+
data = stringify_keys(data.to_h)
|
70
|
+
end
|
71
|
+
case @format
|
72
|
+
when :json
|
73
|
+
if @compact
|
74
|
+
JSON.dump data
|
75
|
+
else
|
76
|
+
JSON.pretty_generate data
|
77
|
+
end
|
78
|
+
when :yaml
|
79
|
+
data.to_yaml
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def decode(data)
|
84
|
+
case @format
|
85
|
+
when :json
|
86
|
+
data = JSON.load(data)
|
87
|
+
when :yaml
|
88
|
+
data = YAML.load(data)
|
89
|
+
end
|
90
|
+
if data.is_a?(Hash)
|
91
|
+
Settings[data]
|
92
|
+
else
|
93
|
+
data
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Convert symbolic keys to strings in a hash recursively
|
98
|
+
def stringify_keys(data)
|
99
|
+
if data.is_a?(Hash)
|
100
|
+
Hash[
|
101
|
+
data.map do |k, v|
|
102
|
+
[k.respond_to?(:to_sym) ? k.to_s : k, stringify_keys(v)]
|
103
|
+
end
|
104
|
+
]
|
105
|
+
else
|
106
|
+
data
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
data/lib/dicoms/stats.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
class DicomS
|
2
|
+
def stats(dicom_directory, options = {})
|
3
|
+
# TODO: compute histogram of levels
|
4
|
+
dicom_files = find_dicom_files(dicom_directory)
|
5
|
+
if dicom_files.empty?
|
6
|
+
raise "ERROR: no se han encontrado archivos DICOM en: \n #{dicom_directory}"
|
7
|
+
end
|
8
|
+
|
9
|
+
mins = []
|
10
|
+
maxs = []
|
11
|
+
next_mins = []
|
12
|
+
n = 0
|
13
|
+
|
14
|
+
dicom_files.each do |file|
|
15
|
+
n += 1
|
16
|
+
d = DICOM::DObject.read(file)
|
17
|
+
data = dicom_narray(d)
|
18
|
+
min = data.min
|
19
|
+
mins << min
|
20
|
+
maxs << data.max
|
21
|
+
next_mins << data[data > min].min
|
22
|
+
end
|
23
|
+
{
|
24
|
+
n: n,
|
25
|
+
min: mins.min,
|
26
|
+
next_min: next_mins.min,
|
27
|
+
max: maxs.max
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,349 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
|
3
|
+
class DicomS
|
4
|
+
USE_SLICE_Z = false
|
5
|
+
METADATA_TYPES = {
|
6
|
+
# Note: for axisx, axisy, axisz decode_vector should be used
|
7
|
+
dx: :to_f, dy: :to_f, dz: :to_f,
|
8
|
+
nx: :to_i, ny: :to_i, nz: :to_i,
|
9
|
+
max: :to_i, min: :to_i,
|
10
|
+
lim_min: :to_i, lim_max: :to_i,
|
11
|
+
rescaled: :to_i, # 0-false 1-true
|
12
|
+
slope: :to_f, intercept: :to_f,
|
13
|
+
bits: :to_i,
|
14
|
+
signed: :to_i, # 0-false 1-true
|
15
|
+
firstx: :to_i, firsty: :to_i, firstz: :to_i,
|
16
|
+
lastx: :to_i, lasty: :to_i, lastz: :to_i,
|
17
|
+
study_id: :to_s, series_id: :to_i,
|
18
|
+
x: :to_f, y: :to_f, z: :to_f,
|
19
|
+
slize_z: :to_f,
|
20
|
+
reverse_x: :to_i, # 0-false 1-true
|
21
|
+
reverse_y: :to_i, # 0-false 1-true
|
22
|
+
reverse_z: :to_i, # 0-false 1-true
|
23
|
+
axial_sx: :to_f,
|
24
|
+
axial_sy: :to_f,
|
25
|
+
coronal_sx: :to_f,
|
26
|
+
coronal_sy: :to_f,
|
27
|
+
sagittal_sx: :to_f,
|
28
|
+
sagittal_sy: :to_f
|
29
|
+
}
|
30
|
+
|
31
|
+
module Support
|
32
|
+
def cast_metadata(metadata)
|
33
|
+
metadata = Hash[metadata.to_h.to_a.map { |key, value|
|
34
|
+
key = key.to_s.downcase.to_sym
|
35
|
+
trans = METADATA_TYPES[key]
|
36
|
+
value = value.send(trans) if trans
|
37
|
+
[key, value]
|
38
|
+
}]
|
39
|
+
Settings[metadata]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Code that use images should be wrapped with this.
|
43
|
+
#
|
44
|
+
# Reason: if RMagick is used by DICOM to handle images,
|
45
|
+
# then the first time it is needed, 'rmagick' will be required.
|
46
|
+
# This has the effect of placing the path of ImageMagick
|
47
|
+
# in front of the PATH.
|
48
|
+
# On Windows, ImageMagick includes FFMPeg in its path and we
|
49
|
+
# may require a later version than the bundled with IM,
|
50
|
+
# so we keep the original path rbefore RMagick alters it.
|
51
|
+
# We may be less dependant on the FFMpeg version is we avoid
|
52
|
+
# using the start_number option by renumbering the extracted
|
53
|
+
# images...
|
54
|
+
def keeping_path
|
55
|
+
path = ENV['PATH']
|
56
|
+
yield
|
57
|
+
ensure
|
58
|
+
ENV['PATH'] = path
|
59
|
+
end
|
60
|
+
|
61
|
+
# Replace ALT_SEPARATOR in pathname (Windows)
|
62
|
+
def normalized_path(path)
|
63
|
+
if File::ALT_SEPARATOR
|
64
|
+
path.gsub(File::ALT_SEPARATOR, File::SEPARATOR)
|
65
|
+
else
|
66
|
+
path
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def dicom?(file)
|
71
|
+
ok = false
|
72
|
+
if File.file?(file)
|
73
|
+
File.open(file, 'rb') do |data|
|
74
|
+
data.seek 128, IO::SEEK_SET # skip preamble
|
75
|
+
ok = (data.read(4) == 'DICM')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
ok
|
79
|
+
end
|
80
|
+
|
81
|
+
# Find DICOM files in a directory;
|
82
|
+
# Return the file names in an array.
|
83
|
+
# DICOM files with a numeric part in the name are returned first, ordered
|
84
|
+
# by the numeric value.
|
85
|
+
# DICOM files with non-numeric names are returned last ordered by name.
|
86
|
+
def find_dicom_files(dicom_directory)
|
87
|
+
# TODO: look recursively inside nested directories
|
88
|
+
if File.directory?(dicom_directory)
|
89
|
+
dicom_directory = normalized_path(dicom_directory)
|
90
|
+
files = Dir.glob(File.join(dicom_directory, '*')).select{|f| dicom?(f)}
|
91
|
+
elsif File.file?(dicom_directory) && dicom?(dicom_directory)
|
92
|
+
files = [dicom_directory]
|
93
|
+
else
|
94
|
+
files = []
|
95
|
+
end
|
96
|
+
non_numeric = []
|
97
|
+
numeric_files = []
|
98
|
+
files.each do |name|
|
99
|
+
base = File.basename(name)
|
100
|
+
match = /\d+/.match(base)
|
101
|
+
if match
|
102
|
+
number = match[0]
|
103
|
+
if base =~ /\AI\d\d\d\d\d\d\d\Z/
|
104
|
+
# funny scheme found in some DICOMS:
|
105
|
+
# the I is followed by the instance number (unpadded), then right
|
106
|
+
# padded with zeros, then increased (which affects the last digit)
|
107
|
+
# while it coincides with some prior value.
|
108
|
+
match = /I(\d\d\d\d)/.match(base)
|
109
|
+
number = match[1]
|
110
|
+
number = number[0...-1] while number.size > 1 && number[-1] == '0'
|
111
|
+
number_zeros = name[-1].to_i
|
112
|
+
number << '0'*number_zeros
|
113
|
+
end
|
114
|
+
numeric_files << [number, name]
|
115
|
+
else
|
116
|
+
non_numeric << name
|
117
|
+
end
|
118
|
+
end
|
119
|
+
numeric_files.sort_by{ |text, name| text.to_i }.map(&:last) + non_numeric.sort
|
120
|
+
end
|
121
|
+
|
122
|
+
def single_dicom_metadata(dicom)
|
123
|
+
# 0028,0030 Pixel Spacing:
|
124
|
+
dx, dy = dicom.pixel_spacing.value.split('\\').map(&:to_f)
|
125
|
+
# 0020,0032 Image Position (Patient):
|
126
|
+
x, y, z = dicom.image_position_patient.value.split('\\').map(&:to_f)
|
127
|
+
# 0020,0037 Image Orientation (Patient):
|
128
|
+
xx, xy, xz, yx, yy, yz = dicom.image_orientation_patient.value.split('\\').map(&:to_f)
|
129
|
+
if USE_SLICE_Z
|
130
|
+
# according to http://www.vtk.org/Wiki/VTK/FAQ#The_spacing_in_my_DICOM_files_are_wrong
|
131
|
+
# this is not reliable
|
132
|
+
# 0020,1041 Slice Location:
|
133
|
+
slice_z = dicom.slice_location.value.to_f
|
134
|
+
else
|
135
|
+
slice_z = z
|
136
|
+
end
|
137
|
+
|
138
|
+
# 0028,0011 Columns :
|
139
|
+
nx = dicom.num_cols # dicom.columns.value.to_i
|
140
|
+
# 0028,0010 Rows:
|
141
|
+
ny = dicom.num_rows # dicom.rows.value.to_i
|
142
|
+
|
143
|
+
unless dicom.samples_per_pixel.value.to_i == 1
|
144
|
+
raise "Invalid DICOM format"
|
145
|
+
end
|
146
|
+
Settings[
|
147
|
+
dx: dx, dy: dy, x: x, y: y, z: z,
|
148
|
+
slice_z: slice_z, nx: nx, ny: ny,
|
149
|
+
xaxis: encode_vector([xx,xy,xz]),
|
150
|
+
yaxis: encode_vector([yx,yy,yz])
|
151
|
+
# TODO: + min, max (original values corresponding to 0, 255)
|
152
|
+
]
|
153
|
+
end
|
154
|
+
|
155
|
+
def encode_vector(v)
|
156
|
+
v.to_a*','
|
157
|
+
end
|
158
|
+
|
159
|
+
def decode_vector(v)
|
160
|
+
Vector[*v.split(',').map(&:to_f)]
|
161
|
+
end
|
162
|
+
|
163
|
+
def output_file_name(dir, prefix, name, ext = '.jpg')
|
164
|
+
File.join dir, "#{prefix}#{File.basename(name,'.dcm')}#{ext}"
|
165
|
+
end
|
166
|
+
|
167
|
+
def dicom_name_pattern(name, output_dir)
|
168
|
+
dir = File.dirname(name)
|
169
|
+
file = File.basename(name)
|
170
|
+
number_pattern = /\d+/
|
171
|
+
match = number_pattern.match(file)
|
172
|
+
raise "Invalid DICOM file name" unless match
|
173
|
+
number = match[0]
|
174
|
+
file = file.sub(number_pattern, "%d")
|
175
|
+
if match.begin(0) == 0
|
176
|
+
# ffmpeg has troubles with filename patterns starting with digits, so we'll add a prefix
|
177
|
+
prefix = "d-"
|
178
|
+
else
|
179
|
+
prefix = nil
|
180
|
+
end
|
181
|
+
pattern = output_file_name(output_dir, prefix, file)
|
182
|
+
[prefix, pattern, number]
|
183
|
+
end
|
184
|
+
|
185
|
+
def define_transfer(options, *defaults)
|
186
|
+
strategy, params = Array(options[:transfer])
|
187
|
+
|
188
|
+
unless defaults.first.is_a?(Hash)
|
189
|
+
default_strategy = defaults.shift.to_sym
|
190
|
+
end
|
191
|
+
defautl_strategy ||= :sample
|
192
|
+
default_params = defaults.shift || {}
|
193
|
+
raise "Invalid number of parametrs" unless defaults.empty?
|
194
|
+
Transfer.strategy strategy || default_strategy, default_params.merge((params || {}).to_h)
|
195
|
+
end
|
196
|
+
|
197
|
+
def pixel_value_range(num_bits, signed)
|
198
|
+
num_values = (1 << num_bits) # 2**num_bits
|
199
|
+
if signed
|
200
|
+
[-num_values/2, num_values/2-1]
|
201
|
+
else
|
202
|
+
[0, num_values-1]
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def dicom_element_value(dicom, tag, options = {})
|
207
|
+
if dicom.exists?(tag)
|
208
|
+
value = dicom[tag].value
|
209
|
+
if options[:first]
|
210
|
+
if value.is_a?(String)
|
211
|
+
value = value.split('\\').first
|
212
|
+
elsif value.is_a?(Array)
|
213
|
+
value = value.first
|
214
|
+
end
|
215
|
+
end
|
216
|
+
value = value.send(options[:convert]) if options[:convert]
|
217
|
+
value
|
218
|
+
else
|
219
|
+
options[:default]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# WL (window level)
|
224
|
+
def dicom_window_center(dicom)
|
225
|
+
# dicom.window_center.value.to_i
|
226
|
+
dicom_element_value(dicom, '0028,1050', convert: :to_f, first: true)
|
227
|
+
end
|
228
|
+
|
229
|
+
# WW (window width)
|
230
|
+
def dicom_window_width(dicom)
|
231
|
+
# dicom.window_center.value.to_i
|
232
|
+
dicom_element_value(dicom, '0028,1051', convert: :to_f, first: true)
|
233
|
+
end
|
234
|
+
|
235
|
+
def dicom_rescale_intercept(dicom)
|
236
|
+
dicom_element_value(dicom, '0028,1052', convert: :to_f, default: 0)
|
237
|
+
end
|
238
|
+
|
239
|
+
def dicom_rescale_slope(dicom)
|
240
|
+
dicom_element_value(dicom, '0028,1053', convert: :to_f, default: 1)
|
241
|
+
end
|
242
|
+
|
243
|
+
def dicom_bit_depth(dicom)
|
244
|
+
# dicom.send(:bit_depth)
|
245
|
+
dicom_element_value dicom, '0028,0100', convert: :to_i
|
246
|
+
end
|
247
|
+
|
248
|
+
def dicom_signed?(dicom)
|
249
|
+
# dicom.send(:signed_pixels?)
|
250
|
+
case dicom_element_value(dicom, '0028,0103', convert: :to_i)
|
251
|
+
when 1
|
252
|
+
true
|
253
|
+
when 0
|
254
|
+
false
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def dicom_stored_bits(dicom)
|
259
|
+
# dicom.bits_stored.value.to_i
|
260
|
+
dicom_element_value dicom, '0028,0101', convert: :to_i
|
261
|
+
end
|
262
|
+
|
263
|
+
def dicom_narray(dicom, options = {})
|
264
|
+
if dicom.compression?
|
265
|
+
img = dicom.image
|
266
|
+
pixels = dicom.export_pixels(img, dicom.send(:photometry))
|
267
|
+
na = NArray.to_na(pixels).reshape!(dicom.num_cols, dicom.num_rows)
|
268
|
+
bits = dicom_bit_depth(dicom)
|
269
|
+
signed = dicom_signed?(dicom)
|
270
|
+
stored_bits = dicom_stored_bits(dicom)
|
271
|
+
if stored_bits != Magick::MAGICKCORE_QUANTUM_DEPTH
|
272
|
+
use_float = stored_bits < Magick::MAGICKCORE_QUANTUM_DEPTH
|
273
|
+
if use_float
|
274
|
+
na = na.to_type(NArray::SFLOAT)
|
275
|
+
na.mul! 2.0**(stored_bits - Magick::MAGICKCORE_QUANTUM_DEPTH)
|
276
|
+
na = na.to_type(NArray::INT)
|
277
|
+
else
|
278
|
+
na.mul! (1 << (stored_bits - Magick::MAGICKCORE_QUANTUM_DEPTH))
|
279
|
+
end
|
280
|
+
end
|
281
|
+
if remap = options[:remap] || level = options[:level]
|
282
|
+
intercept = dicom_rescale_intercept(dicom)
|
283
|
+
slope = dicom_rescale_slope(dicom)
|
284
|
+
if intercept != 0 || slope != 1
|
285
|
+
na.mul! slope
|
286
|
+
na.add! intercept
|
287
|
+
end
|
288
|
+
if level
|
289
|
+
if level.is_a?(Array)
|
290
|
+
center, width = level
|
291
|
+
else
|
292
|
+
center = dicom_window_center(dicom)
|
293
|
+
width = dicom_window_width(dicom)
|
294
|
+
end
|
295
|
+
if center && width
|
296
|
+
low = center - width/2
|
297
|
+
high = center + width/2
|
298
|
+
na[na < low] = low
|
299
|
+
na[na > high] = high
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Now we limit the output values range.
|
304
|
+
# Note that we don't use:
|
305
|
+
# min, max = pixel_value_range(bits, signed)
|
306
|
+
# because thats the limits for the stored values, but not for
|
307
|
+
# the representation values we're computing here (which are
|
308
|
+
# typically signed even if the storage is unsigned)
|
309
|
+
# We coud use this, but that would have to be
|
310
|
+
# min, max = pixel_value_range(stored_bits, false)
|
311
|
+
# min = -max
|
312
|
+
# but that requires some reviewing.
|
313
|
+
# Maybe this shold be parameterized.
|
314
|
+
min, max = -65535, 65535
|
315
|
+
min_pixel_value = na.min
|
316
|
+
if min
|
317
|
+
if min_pixel_value < min
|
318
|
+
offset = min_pixel_value.abs
|
319
|
+
na.add! offset
|
320
|
+
end
|
321
|
+
end
|
322
|
+
max_pixel_value = na.max
|
323
|
+
if max
|
324
|
+
if max_pixel_value > max
|
325
|
+
factor = (max_pixel_value.to_f/max.to_f).ceil
|
326
|
+
na.div! factor
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
na
|
332
|
+
else
|
333
|
+
dicom.narray options
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
def assign_dicom_pixels(dicom, pixels)
|
338
|
+
if dicom.compression?
|
339
|
+
dicom.delete DICOM::PIXEL_TAG
|
340
|
+
end
|
341
|
+
dicom.pixels = pixels
|
342
|
+
end
|
343
|
+
|
344
|
+
def dicom_compression(dicom)
|
345
|
+
ts = DICOM::LIBRARY.uid(dicom.transfer_syntax)
|
346
|
+
ts.name if ts.compressed_pixels?
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|