dicoms 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ # DicomS: DICOM Series toolkit
2
+
3
+ DicomS is a Ruby toolkit for working with DICOM (CT/MRI) Series
4
+ (image sequences that compose a volume of density information).
5
+
6
+ It can be used through a command line interface
7
+ by using the `dicoms` executable script, or
8
+ from a Ruby program through the 'DicomS' class interface (API).
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'dicoms'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install dicoms
25
+
26
+ ### Requirements
27
+
28
+ * [FFmpeg](https://www.ffmpeg.org/) (the command line tools).
29
+ * [ImageMagick](http://www.imagemagick.org/) (the library whichs is used by RMagick)
30
+
31
+ ## Usage
32
+
33
+ The `dicoms` executable provides the following commands:
34
+
35
+ * Extract images: `dicoms extract DICOM-DIR ...`
36
+ * Generate projected images (on axial, sagittal and coronal planes):
37
+ `dicoms project DICOM-DIR ...`
38
+ * Pack a DICOM series in compact form: `dicoms pack DICOM-DIR ...`
39
+ * Unpack a packed DICOM series: `dicoms unpack PACKED-FILE ...`
40
+
41
+ Use the command to get further help.
42
+
43
+ ## License
44
+
45
+ Copyright (c) 2015 Javier Goizueta
46
+
47
+ This software is licensed under the
48
+ [GNU General Public License](./LICENSE.md) version 3.
@@ -0,0 +1,22 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ require 'rdoc/task'
11
+ Rake::RDocTask.new do |rdoc|
12
+ version = DicomS::VERSION
13
+
14
+ rdoc.rdoc_dir = 'rdoc'
15
+ rdoc.title = "DicomS #{version}"
16
+ rdoc.main = "README.md"
17
+ rdoc.rdoc_files.include('README*')
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ rdoc.markup = 'markdown' if rdoc.respond_to?(:markup)
20
+ end
21
+
22
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dicompack"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dicoms/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dicoms"
8
+ spec.version = DicomS::VERSION
9
+ spec.authors = ["Javier Goizueta"]
10
+ spec.email = ["jgoizueta@gmail.com"]
11
+
12
+ spec.summary = %q{DICOM Series toolkit}
13
+ spec.description = %q{Toolkit for working with DICOM image sequences}
14
+ spec.homepage = "https://gitlab.com/jgoizueta/dicompacker"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'dicom'
22
+ # spec.add_dependency 'dicom', 'mini_magick'
23
+ spec.add_dependency 'rmagick', '~> 2.14'
24
+ spec.add_dependency 'sys_cmd', '>= 0.2.1'
25
+ spec.add_dependency 'modalsettings', '~> 1.0.1'
26
+ spec.add_dependency 'narray', '~> 0.6'
27
+ spec.add_dependency 'thor', '~> 0.19'
28
+
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.10"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "minitest"
33
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+
5
+ require 'dicoms'
6
+ require 'dicoms/cli'
7
+
8
+ DicomS::CLI.start ARGV
@@ -0,0 +1,67 @@
1
+ require 'fileutils'
2
+ require 'dicom'
3
+ require 'modalsettings'
4
+ require 'sys_cmd'
5
+ require 'narray'
6
+
7
+ require "dicoms/version"
8
+ require "dicoms/meta_codec"
9
+ require "dicoms/support"
10
+ require "dicoms/shared_files"
11
+ require "dicoms/shared_settings"
12
+ require "dicoms/progress"
13
+ require "dicoms/command_options"
14
+ require "dicoms/sequence"
15
+ require "dicoms/transfer"
16
+ require "dicoms/extract"
17
+ require "dicoms/pack"
18
+ require "dicoms/unpack"
19
+ require "dicoms/stats"
20
+ require "dicoms/projection"
21
+ require "dicoms/remap"
22
+
23
+ class DicomS
24
+
25
+ def initialize(options = {})
26
+ @settings = Settings[options]
27
+
28
+ if @settings.image_processor
29
+ DICOM.image_processor = @settings.image_processor.to_sym
30
+ end
31
+
32
+ @ffmpeg_options = { 'ffmpeg' => @settings.ffmpeg }
33
+ # TODO: use quality level settings
34
+ # TODO: temporary strategy option (:current_dir, :system_tmp, ...)
35
+ end
36
+
37
+ attr_reader :settings
38
+
39
+ extend Support
40
+ include Support
41
+
42
+ private
43
+
44
+ def meta_codec
45
+ MetaCodec.new
46
+ end
47
+
48
+ # def optimize_dynamic_range(data, output_min, output_max, options = {})
49
+ # minimum, maximum = options[:range]
50
+ # r = (maximum - minimum).to_f
51
+ # data -= minimum
52
+ # data *= (output_max - output_min)/r
53
+ # data += output_min
54
+ # data[data < output_min] = output_min
55
+ # data[data > output_max] = output_max
56
+ # data
57
+ # end
58
+
59
+ def check_command(command)
60
+ unless command.success?
61
+ puts "Error executing:"
62
+ puts " #{command}"
63
+ puts command.error_output
64
+ exit 1
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,241 @@
1
+ require 'thor'
2
+
3
+ class DicomS
4
+ class CLI < Thor
5
+ check_unknown_options!
6
+
7
+ def self.exit_on_failure?
8
+ true
9
+ end
10
+
11
+ desc 'version', "Display DicomS version"
12
+ map %w(-v --version) => :version
13
+ def version
14
+ say "dicoms #{VERSION}"
15
+ end
16
+
17
+ class_option 'verbose', type: :boolean, default: false
18
+ class_option 'settings', type: :string, desc: 'settings (read-only) file'
19
+ class_option 'settings_io', type: :string, desc: 'settings file'
20
+
21
+ desc "pack DICOM-DIR", "pack a DICOM directory"
22
+ option :output, desc: 'output file', aliases: '-o'
23
+ option :tmp, desc: 'temporary directory'
24
+ option :transfer, desc: 'transfer method', aliases: '-t', default: 'sample'
25
+ option :center, desc: 'center (window transfer)', aliases: '-c'
26
+ option :width, desc: 'window (window transfer)', aliases: '-w'
27
+ option :ignore_min, desc: 'ignore minimum (global/first/sample transfer)', aliases: '-i'
28
+ option :samples, desc: 'number of samples (sample transfer)', aliases: '-s'
29
+ option :min, desc: 'minimum value (fixed transfer)'
30
+ option :max, desc: 'maximum value (fixed transfer)'
31
+ option :reorder, desc: 'reorder slices based on instance number'
32
+ def pack(dicom_dir)
33
+ DICOM.logger.level = Logger::FATAL
34
+ strategy_parameters = {
35
+ ignore_min: true
36
+ }
37
+ settings = {} # TODO: ...
38
+ unless File.directory?(dicom_dir)
39
+ raise Error, set_color("Directory not found: #{dicom_dir}", :red)
40
+ say options
41
+ end
42
+ cmd_options = CommandOptions[
43
+ settings: options.settings,
44
+ settings_io: options.settings_io,
45
+ output: options.output,
46
+ tmp: options.tmp,
47
+ reorder: options.reorder,
48
+ dicom_metadata: true
49
+ ]
50
+ packer = DicomS.new(settings)
51
+ packer.pack dicom_dir, cmd_options
52
+ # rescue => raise Error?
53
+ 0
54
+ end
55
+
56
+ desc "unpack dspack", "unpack a dspack file"
57
+ option :output, desc: 'output directory', aliases: '-o'
58
+ option :dicom, desc: 'dicom format output directory', aliases: '-d'
59
+ # TODO: parameters for dicom regeneration
60
+ def unpack(dspack)
61
+ DICOM.logger.level = Logger::FATAL
62
+ unless File.file?(dspack)
63
+ raise Error, set_color("File not found: #{dspack}", :red)
64
+ say options
65
+ end
66
+ settings = {} # TODO: ...
67
+ packer = DicomS.new(settings)
68
+ packer.unpack(
69
+ dspack,
70
+ settings: options.settings,
71
+ settings_io: options.settings_io,
72
+ output: options.output,
73
+ dicom_output: options.dicom
74
+ )
75
+ # rescue => raise Error?
76
+ 0
77
+ end
78
+
79
+ desc "extract DICOM-DIR", "extract images from a set of DICOM files"
80
+ option :output, desc: 'output directory', aliases: '-o'
81
+ option :transfer, desc: 'transfer method', aliases: '-t', default: 'window'
82
+ option :center, desc: 'center (window transfer)', aliases: '-c'
83
+ option :width, desc: 'window (window transfer)', aliases: '-w'
84
+ option :ignore_min, desc: 'ignore minimum (global/first/sample transfer)', aliases: '-i'
85
+ option :samples, desc: 'number of samples (sample transfer)', aliases: '-s'
86
+ option :min, desc: 'minimum value (fixed transfer)'
87
+ option :max, desc: 'maximum value (fixed transfer)'
88
+ option :raw, desc: 'generate raw output', aliases: '-r'
89
+ option :big, desc: 'big-endian raw output'
90
+ option :little, desc: 'little-endian raw output'
91
+ def extract(dicom_dir)000
92
+ DICOM.logger.level = Logger::FATAL
93
+ settings = {} # TODO: ...
94
+ unless File.exists?(dicom_dir)
95
+ raise Error, set_color("Directory not found: #{dicom_dir}", :red)
96
+ say options
97
+ end
98
+
99
+ raw = options.raw
100
+ if options.big
101
+ raw = true
102
+ big_endian = true
103
+ elsif options.little
104
+ raw = true
105
+ little_endian = true
106
+ end
107
+
108
+ packer = DicomS.new(settings)
109
+ packer.extract(
110
+ dicom_dir,
111
+ transfer: DicomS.transfer_options(options),
112
+ output: options.output,
113
+ raw: raw, big_endian: big_endian, little_endian: little_endian
114
+ )
115
+ # rescue => raise Error?
116
+ 0
117
+ end
118
+
119
+ desc "Level stats", "Level limits of one or more DICOM files"
120
+ def stats(dicom_dir)
121
+ DICOM.logger.level = Logger::FATAL
122
+ settings = {} # TODO: ...
123
+ dicoms = DicomS.new(settings)
124
+ stats = dicoms.stats dicom_dir
125
+ puts "Aggregate values for #{stats[:n]} DICOM files:"
126
+ puts " Minimum level: #{stats[:min]}"
127
+ puts " Next minimum level: #{stats[:next_min]}"
128
+ puts " Maximum level: #{stats[:max]}"
129
+ 0
130
+ end
131
+
132
+ desc "projection DICOM-DIR", "extract projected images from a DICOM sequence"
133
+ option :output, desc: 'output directory', aliases: '-o'
134
+ option :axial, desc: 'N for single slice, * all, C center, mip or aap for volumetric aggregation'
135
+ option :sagittal, desc: 'N for single slice, * all, C center, mip or aap for volumetric aggregation'
136
+ option :coronal, desc: 'N for single slice, * all, C center, mip or aap for volumetric aggregation'
137
+ option :transfer, desc: 'transfer method', aliases: '-t', default: 'window'
138
+ # option :byte, desc: 'transfer as bytes', aliases: '-b'
139
+ option :center, desc: 'center (window transfer)', aliases: '-c'
140
+ option :width, desc: 'window (window transfer)', aliases: '-w'
141
+ option :ignore_min, desc: 'ignore minimum (global/first/sample transfer)', aliases: '-i'
142
+ option :samples, desc: 'number of samples (sample transfer)', aliases: '-s'
143
+ option :min, desc: 'minimum value (fixed transfer)'
144
+ option :max, desc: 'maximum value (fixed transfer)'
145
+ option :max_x_pixels, desc: 'maximum number of pixels in the X direction'
146
+ option :max_y_pixels, desc: 'maximum number of pixels in the Y direction'
147
+ option :max_z_pixels, desc: 'maximum number of pixels in the Z direction'
148
+ option :reorder, desc: 'reorder slices based on instance number'
149
+ def projection(dicom_dir)
150
+ DICOM.logger.level = Logger::FATAL
151
+ settings = {} # TODO: ...
152
+ unless File.directory?(dicom_dir)
153
+ raise Error, set_color("Directory not found: #{dicom_dir}", :red)
154
+ say options
155
+ end
156
+ if options.settings_io || options.settings
157
+ cmd_options = CommandOptions[
158
+ settings: options.settings,
159
+ settings_io: options.settings_io,
160
+ output: options.output,
161
+ max_x_pixels: options.max_x_pixels && options.max_x_pixels.to_i,
162
+ max_y_pixels: options.max_y_pixels && options.max_y_pixels.to_i,
163
+ max_z_pixels: options.max_z_pixels && options.max_z_pixels.to_i,
164
+ reorder: options.reorder,
165
+ ]
166
+ else
167
+ cmd_options = CommandOptions[
168
+ transfer: DicomS.transfer_options(options),
169
+ output: options.output,
170
+ axial: options.axial == 'axial' ? 'mip' : options.axial,
171
+ sagittal: options.sagittal == 'sagittal' ? 'mip' : options.sagittal,
172
+ coronal: options.coronal == 'coronal' ? 'mip' : options.coronal,
173
+ max_x_pixels: options.max_x_pixels && options.max_x_pixels.to_i,
174
+ max_y_pixels: options.max_y_pixels && options.max_y_pixels.to_i,
175
+ max_z_pixels: options.max_z_pixels && options.max_z_pixels.to_i,
176
+ reorder: options.reorder,
177
+ ]
178
+ end
179
+ unless cmd_options.axial || options.sagittal || options.coronal
180
+ raise Error, "Must specify at least one projection (axial/sagittal/coronal)"
181
+ end
182
+ packer = DicomS.new(settings)
183
+ packer.projection(dicom_dir, cmd_options)
184
+ # rescue => raise Error?
185
+ 0
186
+ end
187
+
188
+ desc "Remap DICOM-DIR", "convert DICOM pixel values"
189
+ option :output, desc: 'output directory', aliases: '-o'
190
+ option :transfer, desc: 'transfer method', aliases: '-t', default: 'identity'
191
+ option :unsigned, desc: 'transfer as unsigned', aliases: '-u'
192
+ # option :byte, desc: 'transfer as bytes', aliases: '-b'
193
+ option :center, desc: 'center (window transfer)', aliases: '-c'
194
+ option :width, desc: 'window (window transfer)', aliases: '-w'
195
+ option :ignore_min, desc: 'ignore minimum (global/first/sample transfer)', aliases: '-i'
196
+ option :samples, desc: 'number of samples (sample transfer)', aliases: '-s'
197
+ option :min, desc: 'minimum value (fixed transfer)'
198
+ option :max, desc: 'maximum value (fixed transfer)'
199
+ def remap(dicom_dir)
200
+ DICOM.logger.level = Logger::FATAL
201
+ settings = {} # TODO: ...
202
+ unless File.directory?(dicom_dir)
203
+ raise Error, set_color("Directory not found: #{dicom_dir}", :red)
204
+ say options
205
+ end
206
+ packer = DicomS.new(settings)
207
+ packer.remap(
208
+ dicom_dir,
209
+ transfer: DicomS.transfer_options(options),
210
+ output: options.output
211
+ )
212
+ # rescue => raise Error?
213
+ 0
214
+ end
215
+ end
216
+
217
+ class <<self
218
+ def transfer_options(options)
219
+ strategy = options.transfer.to_sym
220
+ params = {}
221
+ params[:output] = :unsigned if options.unsigned
222
+ params[:output] = :byte if options.byte
223
+ params[:center] = options.center.to_f if options.center
224
+ params[:width] = options.width.to_f if options.width
225
+ if options.ignore_min
226
+ params[:ignore_min] = true
227
+ elsif [:global, :first, :sample].include?(strategy)
228
+ params[:ignore_min] = false
229
+ end
230
+ params[:max_files] = options.samples if options.samples
231
+ params[:min] = options[:min].to_f if options[:min]
232
+ params[:max] = options[:max].to_f if options[:max]
233
+
234
+ if params.empty?
235
+ strategy
236
+ else
237
+ [strategy, params]
238
+ end
239
+ end
240
+ end
241
+ end