dcmtk 1.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 02e561669b448629495a372ce64dcc1fb60f2b79f2d8d3be5ec0994c8f398378
4
+ data.tar.gz: d0883775324f3e65da156289a5a839cbd19e511ada8a3c0ac0b7dbd49f1d1757
5
+ SHA512:
6
+ metadata.gz: 585a11d3adecc9ee835cd4b5b473ad2553d3015d99054c7cc933fb1db1c32eee7fea880abb4d100164d00f2d8c61831b30edb41d2584f2994df6bb25f17e061c
7
+ data.tar.gz: 73cb9ff4b1750a1fb460ce2e0be672f5891c22e56c8c90c2ecc5a99d3d642185d8c87e921ba59d40af5e1a2ec09b117e66cc00813d4f0e7549e4e429cfff4fe5
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining
2
+ a copy of this software and associated documentation files (the
3
+ "Software"), to deal in the Software without restriction, including
4
+ without limitation the rights to use, copy, modify, merge, publish,
5
+ distribute, sublicense, and/or sell copies of the Software, and to
6
+ permit persons to whom the Software is furnished to do so, subject to
7
+ the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be
10
+ included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # DCMTK
2
+
3
+ A Ruby wrapper for [DCMTK tools](https://dicom.offis.de/dcmtk.php.en) - specifically `dcmj2pnm` or `dcm2img` for converting DICOM medical files to standard image formats.
4
+
5
+ ## Information
6
+
7
+ Inspired by MiniMagick Ruby gem, this gem provides a DSL for working with DCMTK command-line tools.
8
+
9
+ ## Requirements
10
+
11
+ DCMTK command-line tools must be installed. You can check if you have it installed by running:
12
+
13
+ ```sh
14
+ $ dcmj2pnm --version
15
+ $dcmtk: dcmj2pnm v3.6.7 2022-04-22
16
+ ```
17
+
18
+ ### Installation
19
+
20
+ On macOS:
21
+ ```sh
22
+ brew install dcmtk
23
+ ```
24
+
25
+ On Ubuntu/Debian:
26
+ ```sh
27
+ apt-get install dcmtk
28
+ ```
29
+
30
+ ## Gem Installation
31
+
32
+ Add the gem to your Gemfile:
33
+
34
+ ```rb
35
+ gem "dcmtk"
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ Let's first see a basic example - converting a DICOM file to PNG:
41
+
42
+ ```rb
43
+ require "dcmtk"
44
+
45
+ package = DCMTK::Package.open("scan.dcm")
46
+ package.convert(format: :png)
47
+ package.write("output.png")
48
+ ```
49
+
50
+ `DCMTK::Package.open` makes a copy of the package, and further methods modify that copy (the original stays untouched). The writing part is necessary because the copy is just temporary, it gets garbage collected when we lose reference to the package.
51
+
52
+ On the other hand, if we want the original package to actually *get* modified, we can use `DCMTK::Package.new`:
53
+
54
+ ```rb
55
+ package = DCMTK::Package.new("scan.dcm")
56
+ package.convert(format: :png)
57
+ # Output file is created at scan.png (same name, different extension)
58
+ ```
59
+
60
+ ### Supported Formats
61
+
62
+ The gem supports converting DICOM files to these formats:
63
+
64
+ | Format | Symbol | dcmj2pnm Flag |
65
+ |--------|--------|---------------|
66
+ | PNG | `:png` | `+on` |
67
+ | BMP | `:bmp` | `+ob` |
68
+ | JPEG | `:jpeg`| `+oj` |
69
+
70
+ ```rb
71
+ # Convert to different formats
72
+ package.convert(format: :png) # PNG output
73
+ package.convert(format: :bmp) # BMP output
74
+ package.convert(format: :jpeg) # JPEG output
75
+ ```
76
+
77
+ ### Configuration
78
+
79
+ ```rb
80
+ DCMTK.configure do |config|
81
+ config.timeout = 5
82
+ config.whiny = true # raise errors on non-zero exit codes
83
+ config.cli = "dcmj2pnm" # force specific CLI (see below)
84
+ end
85
+ ```
86
+
87
+ ### CLI Tool Selection
88
+
89
+ By default, the gem auto-detects which CLI tool to use:
90
+ - If `dcm2img` is found in PATH, it will be used
91
+ - Otherwise, falls back to `dcmj2pnm`
92
+
93
+ You can force a specific CLI tool:
94
+
95
+ ```rb
96
+ # Force dcmj2pnm
97
+ DCMTK.configure do |config|
98
+ config.cli = "dcmj2pnm"
99
+ end
100
+
101
+ # Force dcm2img
102
+ DCMTK.configure do |config|
103
+ config.cli = "dcm2img"
104
+ end
105
+
106
+ # Check which CLI is being used
107
+ DCMTK.convert_cli #=> "dcm2img" or "dcmj2pnm"
108
+ ```
109
+
110
+ ### Logging
111
+
112
+ You can choose to log DCMTK commands and their execution times:
113
+
114
+ ```rb
115
+ DCMTK.logger.level = Logger::DEBUG
116
+ ```
117
+ ```
118
+ D, [2024-01-15T12:07:39.240238 #59063] DEBUG -- : [0.11s] dcmj2pnm +on /tmp/dcmtk20240115-59063-8yvk5s.dcm /tmp/output.png
119
+ ```
120
+
121
+ In Rails you'll probably want to set `DCMTK.logger = Rails.logger`.
122
+
123
+ ### Metal
124
+
125
+ If you want to be close to the metal, you can use DCMTK's command-line tools directly:
126
+
127
+ ```rb
128
+ DCMTK::Tool::Convert.new do |convert|
129
+ convert.format(:png)
130
+ convert << "input.dcm"
131
+ convert << "output.png"
132
+ end #=> `dcmj2pnm +on input.dcm output.png`
133
+
134
+ # OR
135
+
136
+ convert = DCMTK::Tool::Convert.new
137
+ convert.format(:jpeg)
138
+ convert << "input.dcm"
139
+ convert << "output.jpg"
140
+ convert.call #=> `dcmj2pnm +oj input.dcm output.jpg`
141
+ ```
142
+
143
+ You can also pass additional flags using method_missing:
144
+
145
+ ```rb
146
+ DCMTK::Tool::Convert.new do |convert|
147
+ convert.format(:png)
148
+ convert.verbose # adds --verbose flag
149
+ convert << "input.dcm"
150
+ convert << "output.png"
151
+ end
152
+ ```
153
+
154
+ ## Troubleshooting
155
+
156
+ ### Errors being raised when they shouldn't
157
+
158
+ If you're using the tool directly, you can pass `whiny: false` value to the constructor:
159
+
160
+ ```rb
161
+ DCMTK::Tool::Convert.new(whiny: false) do |convert|
162
+ convert.help
163
+ end
164
+ ```
165
+
166
+ ### Command not found
167
+
168
+ Make sure DCMTK tools are installed and either `dcm2img` or `dcmj2pnm` is in your PATH:
169
+
170
+ ```sh
171
+ which dcm2img
172
+ which dcmj2pnm
173
+ ```
174
+
175
+ ## License
176
+
177
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ $:.unshift 'lib'
@@ -0,0 +1,64 @@
1
+ require 'dcmtk/utilities'
2
+ require 'logger'
3
+
4
+ module DCMTK
5
+ module Configuration
6
+ ##
7
+ # If you don't want commands to take too long, you can set a timeout (in
8
+ # seconds).
9
+ #
10
+ # @return [Integer]
11
+ #
12
+ attr_accessor :timeout
13
+ ##
14
+ # Logger for debug output, default is `Logger.new(STDOUT)`, but
15
+ # you can override it, for example if you want the logs to be written to
16
+ # a file.
17
+ #
18
+ # @return [Logger]
19
+ #
20
+ attr_accessor :logger
21
+
22
+ ##
23
+ # If set to `true`, it will raise errors on non-zero exit codes.
24
+ # Defaults to `true`.
25
+ #
26
+ # @return [Boolean]
27
+ #
28
+ attr_accessor :whiny
29
+
30
+ ##
31
+ # Instructs DCMTK how to execute the shell commands. Available
32
+ # APIs are "open3" (default) and "posix-spawn" (requires the "posix-spawn"
33
+ # gem).
34
+ #
35
+ # @return [String]
36
+ #
37
+ attr_accessor :shell_api
38
+
39
+ ##
40
+ # Force a specific CLI command for conversion.
41
+ # When nil (default), auto-detects: dcm2img if available, otherwise dcmj2pnm.
42
+ #
43
+ # @return [String, nil]
44
+ #
45
+ attr_accessor :cli
46
+
47
+ def self.extended(base)
48
+ base.whiny = true
49
+ base.shell_api = "open3"
50
+ base.logger = Logger.new($stdout).tap { |l| l.level = Logger::INFO }
51
+ end
52
+
53
+ ##
54
+ # @yield [self]
55
+ # @example
56
+ # DCMTK.configure do |config|
57
+ # config.timeout = 5
58
+ # end
59
+ #
60
+ def configure
61
+ yield self
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,157 @@
1
+ require 'tempfile'
2
+ require 'stringio'
3
+ require 'pathname'
4
+ require 'fileutils'
5
+
6
+ require 'dcmtk/utilities'
7
+
8
+ module DCMTK
9
+ class Package
10
+
11
+ ##
12
+ # This is the primary loading method used by all of the other class
13
+ # methods.
14
+ #
15
+ # Use this to pass in a stream object. Must respond to #read(size) or be a
16
+ # binary string object (BLOB)
17
+ #
18
+ # @param stream [#read, String] Some kind of stream object that needs
19
+ # to be read or is a binary String blob
20
+ # @param ext [String] A manual extension to use for reading the file. Not
21
+ # required, but if you are having issues, give this a try.
22
+ # @return [DCMTK::Package]
23
+ #
24
+ def self.read(stream, ext = nil)
25
+ if stream.is_a?(String)
26
+ stream = StringIO.new(stream)
27
+ end
28
+
29
+ create(ext) { |file| IO.copy_stream(stream, file) }
30
+ end
31
+
32
+ ##
33
+ # Opens a specific file either on the local file system.
34
+ # Use this if you don't want to overwrite file.
35
+ #
36
+ # Extension is either guessed from the path or you can specify it as a
37
+ # second parameter.
38
+ #
39
+ # @param path [String] Either a local file path
40
+ # @param ext [String] Specify the extension you want to read it as
41
+ # @param options [Hash] Specify options for the open method
42
+ # @return [DCMTK::Package] The loaded file
43
+ #
44
+ def self.open(path, ext = nil, options = {})
45
+ options, ext = ext, nil if ext.is_a?(Hash)
46
+
47
+ # Don't use Kernel#open, but reuse its logic
48
+ openable =
49
+ if path.respond_to?(:open)
50
+ path
51
+ else
52
+ options = { binmode: true }.merge(options)
53
+ Pathname(path)
54
+ end
55
+
56
+ ext ||= File.extname(openable.to_s)
57
+ ext.sub!(/:.*/, '') # hack for filenames that include a colon
58
+
59
+ openable.open(**options) { |file| read(file, ext) }
60
+ end
61
+
62
+ ##
63
+ # Used to create a new file object data-copy.
64
+ #
65
+ # Takes an extension in a block and can be used to build a new file
66
+ # object. Used by both {.open} and {.read} to create a new object. Ensures
67
+ # we have a good tempfile.
68
+ #
69
+ # @param ext [String] Specify the extension you want to read it as
70
+ # @yield [Tempfile] You can #write bits to this object to create the new
71
+ # file
72
+ # @return [DCMTK::Package] The created file
73
+ #
74
+ def self.create(ext = nil, &block)
75
+ tempfile = DCMTK::Utilities.tempfile(ext.to_s.downcase, &block)
76
+
77
+ new(tempfile.path, tempfile)
78
+ end
79
+
80
+ attr_reader :path
81
+ attr_reader :tempfile
82
+
83
+ def initialize(input_path, tempfile = nil)
84
+ @path = input_path.to_s
85
+ @tempfile = tempfile
86
+ end
87
+
88
+ def to_blob
89
+ File.binread(path)
90
+ end
91
+
92
+ ##
93
+ # Converts the DICOM file to the specified image format.
94
+ #
95
+ # @param format [Symbol] The output format (:png, :bmp, :jpeg)
96
+ # @return [self]
97
+ #
98
+ def convert(format: :png)
99
+ ext = ".#{format}"
100
+
101
+ if @tempfile
102
+ new_tempfile = DCMTK::Utilities.tempfile(ext)
103
+ new_path = new_tempfile.path
104
+ else
105
+ new_path = Pathname(path).sub_ext(ext).to_s
106
+ end
107
+
108
+ input_path = path.dup
109
+
110
+ DCMTK::Tool::Convert.new do |convert|
111
+ convert.format(format)
112
+ convert << input_path
113
+ convert << new_path
114
+ end
115
+
116
+ if @tempfile
117
+ destroy!
118
+ @tempfile = new_tempfile
119
+ else
120
+ File.delete(path) unless path == new_path
121
+ end
122
+
123
+ path.replace new_path
124
+
125
+ self
126
+ rescue DCMTK::Error => e
127
+ new_tempfile.unlink if new_tempfile && @tempfile != new_tempfile
128
+ raise e
129
+ end
130
+
131
+ ##
132
+ # Writes the temporary file out to either a file location (by passing in a
133
+ # String) or by passing in a Stream that you can #write(chunk) to
134
+ # repeatedly
135
+ #
136
+ # @param output_to [String, Pathname, #read] Some kind of stream object
137
+ # that needs to be read or a file path as a String
138
+ #
139
+ def write(output_to)
140
+ case output_to
141
+ when String, Pathname
142
+ FileUtils.copy_file path, output_to unless path == output_to.to_s
143
+ else
144
+ IO.copy_stream File.open(path, "rb"), output_to
145
+ end
146
+ end
147
+
148
+ ##
149
+ # Destroys the tempfile (created by {.open}) if it exists.
150
+ #
151
+ def destroy!
152
+ if @tempfile
153
+ @tempfile.unlink
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,78 @@
1
+ require "timeout"
2
+ require "benchmark"
3
+
4
+ module DCMTK
5
+ ##
6
+ # Sends commands to the shell (more precisely, it sends commands directly to
7
+ # the operating system).
8
+ #
9
+ # @private
10
+ #
11
+ class Shell
12
+
13
+ def run(command, options = {})
14
+ stdout, stderr, status = execute(command, stdin: options[:stdin])
15
+
16
+ if status != 0 && options.fetch(:whiny, DCMTK.whiny)
17
+ fail DCMTK::Error, "`#{command.join(" ")}` failed with error:\n#{stderr}"
18
+ end
19
+
20
+ $stderr.print(stderr) unless options[:stderr] == false
21
+
22
+ [stdout, stderr, status]
23
+ end
24
+
25
+ def execute(command, options = {})
26
+ stdout, stderr, status =
27
+ log(command.join(" ")) do
28
+ send("execute_#{DCMTK.shell_api.gsub("-", "_")}", command, options)
29
+ end
30
+
31
+ [stdout, stderr, status.exitstatus]
32
+ rescue Errno::ENOENT, IOError
33
+ ["", "executable not found: \"#{command.first}\"", 127]
34
+ end
35
+
36
+ private
37
+
38
+ def execute_open3(command, options = {})
39
+ require "open3"
40
+
41
+ # We would ideally use Open3.capture3, but it wouldn't allow us to
42
+ # terminate the command after timing out.
43
+ Open3.popen3(*command) do |in_w, out_r, err_r, thread|
44
+ [in_w, out_r, err_r].each(&:binmode)
45
+ stdout_reader = Thread.new { out_r.read }
46
+ stderr_reader = Thread.new { err_r.read }
47
+ begin
48
+ in_w.write options[:stdin].to_s
49
+ rescue Errno::EPIPE
50
+ end
51
+ in_w.close
52
+
53
+ unless thread.join(DCMTK.timeout)
54
+ Process.kill("TERM", thread.pid) rescue nil
55
+ Process.waitpid(thread.pid) rescue nil
56
+ raise Timeout::Error, "DCMTK command timed out: #{command}"
57
+ end
58
+
59
+ [stdout_reader.value, stderr_reader.value, thread.value]
60
+ end
61
+ end
62
+
63
+ def execute_posix_spawn(command, options = {})
64
+ require "posix-spawn"
65
+ child = POSIX::Spawn::Child.new(*command, input: options[:stdin].to_s, timeout: DCMTK.timeout)
66
+ [child.out, child.err, child.status]
67
+ rescue POSIX::Spawn::TimeoutExceeded
68
+ raise Timeout::Error, "DCMTK command timed out: #{command}"
69
+ end
70
+
71
+ def log(command, &block)
72
+ value = nil
73
+ duration = Benchmark.realtime { value = block.call }
74
+ DCMTK.logger.debug "[%.2fs] %s" % [duration, command]
75
+ value
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,30 @@
1
+ module DCMTK
2
+ class Tool
3
+ class Convert < DCMTK::Tool
4
+ FORMATS = {
5
+ png: '+on', # PNG format
6
+ bmp: '+ob', # BMP format
7
+ jpeg: '+oj' # JPEG format
8
+ }.freeze
9
+
10
+ def initialize(*args)
11
+ super(DCMTK.convert_cli, *args)
12
+ end
13
+
14
+ ##
15
+ # Sets the output format for the conversion.
16
+ #
17
+ # @param type [Symbol] The format type (:png, :bmp, :jpeg)
18
+ # @return [self]
19
+ # @raise [ArgumentError] If the format is not supported
20
+ #
21
+ def format(type)
22
+ flag = FORMATS.fetch(type.to_sym) do
23
+ raise ArgumentError, "Unsupported format: #{type}. Use: #{FORMATS.keys.join(', ')}"
24
+ end
25
+ self << flag
26
+ self
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/dcmtk/tool.rb ADDED
@@ -0,0 +1,144 @@
1
+ require "dcmtk/shell"
2
+
3
+ module DCMTK
4
+ ##
5
+ # Abstract class that wraps command-line tools. It shouldn't be used directly,
6
+ # but through one of its subclasses. Use
7
+ # this class if you want to be closer to the metal and execute DCMTK
8
+ # commands directly, but still with a nice Ruby interface.
9
+ #
10
+ class Tool
11
+ ##
12
+ # Aside from classic instantiation, it also accepts a block, and then
13
+ # executes the command in the end.
14
+ #
15
+ # @example
16
+ # version = DCMTK::Tool::Convert.new { |b| b.version }
17
+ # puts version
18
+ #
19
+ # @return [DCMTK::Tool, String] If no block is given, returns an
20
+ # instance of the tool, if block is given, returns the output of the
21
+ # command.
22
+ #
23
+ def self.new(*args)
24
+ instance = super(*args)
25
+
26
+ if block_given?
27
+ yield instance
28
+ instance.call
29
+ else
30
+ instance
31
+ end
32
+ end
33
+
34
+ # @private
35
+ attr_reader :name, :args
36
+
37
+ # @param name [String]
38
+ # @param options [Hash]
39
+ # @option options [Boolean] :whiny Whether to raise errors on non-zero
40
+ # exit codes.
41
+ # @example
42
+ # DCMTK::Tool::Convert.new(whiny: false) do |convert|
43
+ # convert.help # returns exit status 1, which would otherwise throw an error
44
+ # end
45
+ def initialize(name, options = {})
46
+ @name = name
47
+ @args = []
48
+ @whiny = options.is_a?(Hash) ? options.fetch(:whiny, DCMTK.whiny) : options
49
+ end
50
+
51
+ ##
52
+ # Executes the command that has been built up.
53
+ #
54
+ # @example
55
+ # convert = DCMTK::Tool::Convert.new
56
+ # convert.format(:png)
57
+ # convert << "path/to/file.dcm"
58
+ # convert << "output.png"
59
+ # convert.call # executes `dcmj2pnm +on path/to/file.dcm output.png`
60
+ #
61
+ # @example
62
+ # convert = DCMTK::Tool::Convert.new
63
+ # # build the command
64
+ # convert.call do |stdout, stderr, status|
65
+ # # ...
66
+ # end
67
+ #
68
+ # @yield [Array] Optionally yields stdout, stderr, and exit status
69
+ #
70
+ # @return [String] Returns the output of the command
71
+ #
72
+ def call(*args)
73
+ options = args[-1].is_a?(Hash) ? args.pop : {}
74
+ whiny = args.fetch(0, @whiny)
75
+
76
+ options[:whiny] = whiny
77
+ options[:stderr] = false if block_given?
78
+
79
+ shell = DCMTK::Shell.new
80
+ stdout, stderr, status = shell.run(command, options)
81
+ yield stdout, stderr, status if block_given?
82
+
83
+ stdout.chomp("\n")
84
+ end
85
+
86
+ ##
87
+ # The currently built-up command.
88
+ #
89
+ # @return [Array<String>]
90
+ #
91
+ # @example
92
+ # convert = DCMTK::Tool::Convert.new
93
+ # convert.format(:png)
94
+ # convert << "input.dcm"
95
+ # convert << "output.png"
96
+ # convert.command #=> ["dcmj2pnm", "+on", "input.dcm", "output.png"]
97
+ #
98
+ def command
99
+ [*executable, *args]
100
+ end
101
+
102
+ def executable
103
+ exe = [name]
104
+ exe
105
+ end
106
+
107
+ ##
108
+ # Appends raw options, useful for appending file paths.
109
+ #
110
+ # @return [self]
111
+ #
112
+ def <<(arg)
113
+ args << arg.to_s
114
+ self
115
+ end
116
+
117
+ ##
118
+ # Merges a list of raw options.
119
+ #
120
+ # @return [self]
121
+ #
122
+ def merge!(new_args)
123
+ new_args.each { |arg| self << arg }
124
+ self
125
+ end
126
+
127
+ ##
128
+ # Any undefined method will be transformed into a CLI option
129
+ #
130
+ # @example
131
+ # convert = DCMTK::Tool.new("dcmj2pnm")
132
+ # convert.verbose
133
+ # convert.command.join(" ") # => "dcmj2pnm --verbose"
134
+ #
135
+ def method_missing(name, *args)
136
+ option = "--#{name.to_s.tr('_', '-')}"
137
+ self << option
138
+ self.merge!(args)
139
+ self
140
+ end
141
+ end
142
+ end
143
+
144
+ require "dcmtk/tool/convert"
@@ -0,0 +1,35 @@
1
+ require "tempfile"
2
+
3
+ module DCMTK
4
+ # @private
5
+ module Utilities
6
+
7
+ module_function
8
+
9
+ ##
10
+ # Cross-platform way of finding an executable in the $PATH.
11
+ #
12
+ # @example
13
+ # DCMTK::Utilities.which('ruby') #=> "/usr/bin/ruby"
14
+ #
15
+ def which(cmd)
16
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
17
+ ENV.fetch('PATH').split(File::PATH_SEPARATOR).each do |path|
18
+ exts.each do |ext|
19
+ exe = File.join(path, "#{cmd}#{ext}")
20
+ return exe if File.executable? exe
21
+ end
22
+ end
23
+ nil
24
+ end
25
+
26
+ def tempfile(extension)
27
+ Tempfile.new(["dcmtk", extension]).tap do |tempfile|
28
+ tempfile.binmode
29
+ yield tempfile if block_given?
30
+ tempfile.close
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ module DCMTK
2
+ ##
3
+ # @return [Gem::Version]
4
+ #
5
+ def self.version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 1
11
+ MINOR = 0
12
+ TINY = 1
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
16
+ end
17
+ end
data/lib/dcmtk.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'dcmtk/version'
2
+ require 'dcmtk/configuration'
3
+
4
+ module DCMTK
5
+ extend DCMTK::Configuration
6
+
7
+ ##
8
+ # Returns the CLI command to use for conversion.
9
+ # Uses configured cli if set, otherwise auto-detects (dcm2img preferred, dcmj2pnm fallback).
10
+ # Result is cached after first call.
11
+ #
12
+ # @return [String]
13
+ def self.convert_cli
14
+ return cli if cli
15
+ return @convert_cli if defined?(@convert_cli)
16
+
17
+ @convert_cli = DCMTK::Utilities.which('dcm2img') ? 'dcm2img' : 'dcmj2pnm'
18
+ end
19
+
20
+ ##
21
+ # Returns DCMTK CLI tool version.
22
+ #
23
+ # @return [String]
24
+ def self.cli_version
25
+ output = DCMTK::Tool::Convert.new { |c| c.version }
26
+ output[/\d+\.\d+\.\d+/]
27
+ end
28
+
29
+ class Error < RuntimeError; end
30
+
31
+ end
32
+
33
+ require 'dcmtk/tool'
34
+ require 'dcmtk/package'
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dcmtk
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - sanzstez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-01-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: posix-spawn
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Ruby adapter for DCMTK tools for DICOM medical files.
56
+ email:
57
+ - sanzstez@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - lib/dcmtk.rb
66
+ - lib/dcmtk/configuration.rb
67
+ - lib/dcmtk/package.rb
68
+ - lib/dcmtk/shell.rb
69
+ - lib/dcmtk/tool.rb
70
+ - lib/dcmtk/tool/convert.rb
71
+ - lib/dcmtk/utilities.rb
72
+ - lib/dcmtk/version.rb
73
+ homepage: https://github.com/sanzstez/dcmtk
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '2.0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements:
92
+ - You must have DCMTK tools installed (dcmj2pnm)
93
+ rubygems_version: 3.5.22
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Ruby adapter for DCMTK tools for DICOM medical files.
97
+ test_files: []