gdcm 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 854120bce512df7e3a80fb22685e825f45f06a92598bbafe5d99aa19faca89ad
4
+ data.tar.gz: 916dce2f8f78d51513972d186aa392fd192b0d8f7a995ca77bb57d6d2e4dee94
5
+ SHA512:
6
+ metadata.gz: 6514e8369d16786dddb8056f16e5ec6d14aacdf8ae7c54f4027006feace229d1bf9ba2eba3726f46be1ab70bc548c09daaa7e587face7404b4cc4bbc7076ada7
7
+ data.tar.gz: e343afd083dde1ad00d03e1d082694293293b7ffac4aef77e9c2ff5a33d82fe425448b7e107ff41bcc1e3c44c6ae50ef15bfdd50b3aec3c189c8f04411d7875f
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,170 @@
1
+ # GDCM
2
+
3
+ A ruby wrapper for [GDCM tools](http://gdcm.sourceforge.net/)
4
+
5
+ ## Information
6
+
7
+ Inspired by MiniMagick ruby gem, this realization was created based on same DSL structure (but for GDCM tools).
8
+
9
+ ## Requirements
10
+
11
+ GDCM command-line tool has to be installed. You can
12
+ check if you have it installed by running
13
+
14
+ ```sh
15
+ $ gdcminfo --version
16
+ gdcminfo: gdcm 3.0.10
17
+ ```
18
+
19
+ ## Installation
20
+
21
+ Add the gem to your Gemfile:
22
+
23
+ ```rb
24
+ gem "gdcm"
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Let's first see a basic example.
30
+
31
+ ```rb
32
+ require "gdcm"
33
+
34
+ package = GDCM::Package.open("original.dcm")
35
+
36
+ package.convert do |convert|
37
+ convert.raw
38
+ convert.verbose
39
+ end
40
+
41
+ package.path
42
+ package.write "output.dcm"
43
+ ```
44
+
45
+ `GDCM::Package.open` makes a copy of the package, and further methods modify
46
+ that copy (the original stays untouched). The writing part is necessary because
47
+ the copy is just temporary, it gets garbage collected when we lose reference
48
+ to the package.
49
+
50
+
51
+ On the other hand, if we want the original package to actually *get* modified,
52
+ we can use `GDCM::Package.new`.
53
+
54
+ ```rb
55
+ package = GDCM::Package.new("original.dcm")
56
+ package.path
57
+
58
+ package.convert do |convert|
59
+ convert.raw
60
+ convert.verbose
61
+ end
62
+ # Not calling #write, because it's not a copy
63
+ ```
64
+
65
+ ### Attributes
66
+
67
+
68
+ To get the all information about the package, GDCM gives you a handy method
69
+ which returns the output from `gdcminfo` in hash format:
70
+
71
+ ```rb
72
+ package.info.data #=>
73
+ #{"MediaStorage"=>"1.2.840.10008.5.1.4.1.1.77.1.5.1",
74
+ # "TransferSyntax"=>"1.2.840.10008.1.2.4.70",
75
+ # "NumberOfDimensions"=>"2",
76
+ # "Dimensions"=>"(4000,4000,1)",
77
+ # "SamplesPerPixel"=>"3",
78
+ # "BitsAllocated"=>"8",
79
+ # "BitsStored"=>"8",
80
+ # "HighBit"=>"7",
81
+ # "PixelRepresentation"=>"0",
82
+ # "ScalarType found"=>"UINT8",
83
+ # "PhotometricInterpretation"=>"RGB",
84
+ # "PlanarConfiguration"=>"0",
85
+ # "Origin"=>"(0,0,0)",
86
+ # "Spacing"=>"(1,1,1)",
87
+ # "DirectionCosines"=>"(1,0,0,0,1,0)",
88
+ # "Rescale Intercept/Slope"=>"(0,1)",
89
+ # "Orientation Label"=>"AXIAL"}
90
+ ```
91
+
92
+
93
+ ### Configuration
94
+
95
+ ```rb
96
+ GDCM.configure do |config|
97
+ config.timeout = 5
98
+ end
99
+ ```
100
+
101
+ ### Package validation
102
+
103
+ By default, GDCM validates package each time it's opening them. It
104
+ validates them by running `gdcminfo` on them, and see if GDCM tools finds
105
+ them valid. This adds slight overhead to the whole processing. Sometimes it's
106
+ safe to assume that all input and output packages are valid by default and turn
107
+ off validation:
108
+
109
+ ```rb
110
+ GDCM.configure do |config|
111
+ config.validate_on_create = false
112
+ end
113
+ ```
114
+
115
+ You can test whether an package is valid:
116
+
117
+ ```rb
118
+ package.valid?
119
+ package.validate! # raises GDCM::Invalid if package is invalid
120
+ ```
121
+
122
+ ### Logging
123
+
124
+ You can choose to log GDCM commands and their execution times:
125
+
126
+ ```rb
127
+ GDCM.logger.level = Logger::DEBUG
128
+ ```
129
+ ```
130
+ D, [2022-04-11T12:07:39.240238 #59063] DEBUG -- : [0.11s] gdcminfo /var/folders/4d/k113_9r544nfj8k0bfxtjx0m0000gn/T/gdcm20220411-59063-8yvk5s.dcm
131
+ ```
132
+
133
+ In Rails you'll probably want to set `GDCM.logger = Rails.logger`.
134
+
135
+ ### Metal
136
+
137
+ If you want to be close to the metal, you can use GDCM's command-line
138
+ tools directly.
139
+
140
+ ```rb
141
+ GDCM::Tool::Convert.new do |convert|
142
+ convert.raw
143
+ convert.verbose
144
+ convert << "input.dcm"
145
+ convert << "output.dcm"
146
+ end #=> `gdcmconv --raw --verbose input.dcm output.dcm`
147
+
148
+ # OR
149
+
150
+ convert = GDCM::Tool::Convert.new
151
+ convert.raw
152
+ convert.verbose
153
+ convert << "input.jpg"
154
+ convert << "output.jpg"
155
+ convert.call #=> `gdcmconv --raw --verbose input.dcm output.dcm`
156
+ ```
157
+
158
+ ## Troubleshooting
159
+
160
+ ### Errors being raised when they shouldn't
161
+
162
+
163
+ If you're using the tool directly, you can pass `whiny: false` value to the
164
+ constructor:
165
+
166
+ ```rb
167
+ GDCM::Tool::Identify.new(whiny: false) do |b|
168
+ b.help
169
+ end
170
+ ```
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ $:.unshift 'lib'
@@ -0,0 +1,75 @@
1
+ require 'gdcm/utilities'
2
+ require 'logger'
3
+
4
+ module GDCM
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
+ # When get to `true`, it outputs each command to STDOUT in their shell
15
+ # version.
16
+ #
17
+ # @return [Boolean]
18
+ #
19
+ attr_reader :debug
20
+ ##
21
+ # Logger for {#debug}, default is `GDCM::Logger.new(STDOUT)`, but
22
+ # you can override it, for example if you want the logs to be written to
23
+ # a file.
24
+ #
25
+ # @return [Logger]
26
+ #
27
+ attr_accessor :logger
28
+
29
+ ##
30
+ # If set to `true`, it will `identify` every newly created image, and raise
31
+ # `MiniMagick::Invalid` if the image is not valid. Useful for validating
32
+ # user input, although it adds a bit of overhead. Defaults to `true`.
33
+ #
34
+ # @return [Boolean]
35
+ #
36
+ attr_accessor :validate_on_create
37
+ ##
38
+ # If set to `true`, it will `identify` every image that gets written (with
39
+ # {GDCM::Image#write}), and raise `GDCM::Invalid` if the image
40
+ # is not valid. Useful for validating that processing was sucessful,
41
+ # although it adds a bit of overhead. Defaults to `true`.
42
+ #
43
+ # @return [Boolean]
44
+
45
+ #
46
+ attr_accessor :whiny
47
+
48
+ ##
49
+ # Instructs GDCM how to execute the shell commands. Available
50
+ # APIs are "open3" (default) and "posix-spawn" (requires the "posix-spawn"
51
+ # gem).
52
+ #
53
+ # @return [String]
54
+ #
55
+ attr_accessor :shell_api
56
+
57
+ def self.extended(base)
58
+ base.validate_on_create = true
59
+ base.whiny = true
60
+ base.shell_api = "open3"
61
+ base.logger = Logger.new($stdout).tap { |l| l.level = Logger::INFO }
62
+ end
63
+
64
+ ##
65
+ # @yield [self]
66
+ # @example
67
+ # GDCM.configure do |config|
68
+ # config.timeout = 5
69
+ # end
70
+ #
71
+ def configure
72
+ yield self
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,43 @@
1
+ module GDCM
2
+ class Package
3
+ class Info
4
+ attr_reader :package_instance
5
+
6
+ def initialize package_instance
7
+ @package_instance = package_instance
8
+ end
9
+
10
+ def data
11
+ if meta.respond_to?(:lines)
12
+ meta.lines.each_with_object({}) do |line, memo|
13
+ case line
14
+ when /^MediaStorage is (?<media_storage>[\d.]+)/
15
+ memo['MediaStorage'] = $~[:media_storage]
16
+ when /^TransferSyntax is (?<transfer_syntax>[\d.]+)/
17
+ memo['TransferSyntax'] = $~[:transfer_syntax]
18
+ else
19
+ key, _, value = line.partition(/:[\s]*/).map(&:strip)
20
+
21
+ memo[key] = value
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def meta
28
+ @meta ||= identify
29
+ end
30
+
31
+ def clear
32
+ @meta = nil
33
+ end
34
+
35
+ def identify
36
+ GDCM::Tool::Identify.new do |builder|
37
+ yield builder if block_given?
38
+ builder << package_instance.path
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,174 @@
1
+ require 'tempfile'
2
+ require 'stringio'
3
+ require 'pathname'
4
+
5
+ require 'gdcm/package/info'
6
+ require 'gdcm/utilities'
7
+
8
+ module GDCM
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 [GDCM::Image]
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 [GDCM::Image] 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
+ # @param validate [Boolean] If false, skips validation of the created
71
+ # file. Defaults to true.
72
+ # @yield [Tempfile] You can #write bits to this object to create the new
73
+ # file
74
+ # @return [GDCM::Image] The created file
75
+ #
76
+ def self.create(ext = nil, validate = GDCM.validate_on_create, &block)
77
+ tempfile = GDCM::Utilities.tempfile(ext.to_s.downcase, &block)
78
+
79
+ new(tempfile.path, tempfile).tap do |file|
80
+ file.validate! if validate
81
+ end
82
+ end
83
+
84
+ attr_reader :path
85
+ attr_reader :tempfile
86
+ attr_reader :meta
87
+
88
+ def initialize(input_path, tempfile = nil)
89
+ @path = input_path.to_s
90
+ @tempfile = tempfile
91
+ end
92
+
93
+ def info
94
+ @info ||= GDCM::Package::Info.new(self)
95
+ end
96
+
97
+ def to_blob
98
+ File.binread(path)
99
+ end
100
+
101
+ def valid?
102
+ validate!
103
+ true
104
+ rescue GDCM::Invalid
105
+ false
106
+ end
107
+
108
+ def validate!
109
+ identify
110
+ rescue GDCM::Error => error
111
+ raise GDCM::Invalid, error.message
112
+ end
113
+
114
+ def identify
115
+ info.identify
116
+ end
117
+
118
+ def convert
119
+ if @tempfile
120
+ new_tempfile = GDCM::Utilities.tempfile(".dcm")
121
+ new_path = new_tempfile.path
122
+ else
123
+ new_path = Pathname(path).sub_ext(".dcm").to_s
124
+ end
125
+
126
+ input_path = path.dup
127
+
128
+ GDCM::Tool::Convert.new do |convert|
129
+ yield convert if block_given?
130
+ convert << input_path
131
+ convert << new_path
132
+ end
133
+
134
+ if @tempfile
135
+ destroy!
136
+ @tempfile = new_tempfile
137
+ else
138
+ File.delete(path) unless path == new_path
139
+ end
140
+
141
+ path.replace new_path
142
+ info.clear
143
+
144
+ self
145
+ end
146
+
147
+ ##
148
+ # Writes the temporary file out to either a file location (by passing in a
149
+ # String) or by passing in a Stream that you can #write(chunk) to
150
+ # repeatedly
151
+ #
152
+ # @param output_to [String, Pathname, #read] Some kind of stream object
153
+ # that needs to be read or a file path as a String
154
+ #
155
+ def write(output_to)
156
+ case output_to
157
+ when String, Pathname
158
+ FileUtils.copy_file path, output_to unless path == output_to.to_s
159
+ else
160
+ IO.copy_stream File.open(path, "rb"), output_to
161
+ end
162
+ end
163
+
164
+ ##
165
+ # Destroys the tempfile (created by {.open}) if it exists.
166
+ #
167
+ def destroy!
168
+ if @tempfile
169
+ FileUtils.rm_f @tempfile.path.sub(/mpc$/, "cache") if @tempfile.path.end_with?(".mpc")
170
+ @tempfile.unlink
171
+ end
172
+ end
173
+ end
174
+ end
data/lib/gdcm/shell.rb ADDED
@@ -0,0 +1,78 @@
1
+ require "timeout"
2
+ require "benchmark"
3
+
4
+ module GDCM
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, GDCM.whiny)
17
+ fail GDCM::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_#{GDCM.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(GDCM.timeout)
54
+ Process.kill("TERM", thread.pid) rescue nil
55
+ Process.waitpid(thread.pid) rescue nil
56
+ raise Timeout::Error, "GDCM 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: GDCM.timeout)
66
+ [child.out, child.err, child.status]
67
+ rescue POSIX::Spawn::TimeoutExceeded
68
+ raise Timeout::Error, "GDCM command timed out: #{command}"
69
+ end
70
+
71
+ def log(command, &block)
72
+ value = nil
73
+ duration = Benchmark.realtime { value = block.call }
74
+ GDCM.logger.debug "[%.2fs] %s" % [duration, command]
75
+ value
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,9 @@
1
+ module GDCM
2
+ class Tool
3
+ class Convert < GDCM::Tool
4
+ def initialize(*args)
5
+ super("gdcmconv", *args)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module GDCM
2
+ class Tool
3
+ class Identify < GDCM::Tool
4
+ def initialize(*args)
5
+ super("gdcminfo", *args)
6
+ end
7
+ end
8
+ end
9
+ end
data/lib/gdcm/tool.rb ADDED
@@ -0,0 +1,217 @@
1
+ require "gdcm/shell"
2
+
3
+ module GDCM
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 GDCM
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 = GDCM::Tool::Identify.new { |b| b.version }
17
+ # puts version
18
+ #
19
+ # @return [GDCM::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
+ # GDCM::Tool::Identify.new(whiny: false) do |identify|
43
+ # identify.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, GDCM.whiny) : options
49
+ end
50
+
51
+ ##
52
+ # Executes the command that has been built up.
53
+ #
54
+ # @example
55
+ # convert = GDCM::Tool::Convert.new
56
+ # convert.resize("500x500")
57
+ # convert << "path/to/file.dcm"
58
+ # convert.call # executes `convert --resize 500x500 path/to/file.dcm`
59
+ #
60
+ # @example
61
+ # convert = GDCM::Tool::Convert.new
62
+ # # build the command
63
+ # convert.call do |stdout, stderr, status|
64
+ # # ...
65
+ # end
66
+ #
67
+ # @yield [Array] Optionally yields stdout, stderr, and exit status
68
+ #
69
+ # @return [String] Returns the output of the command
70
+ #
71
+ def call(*args)
72
+ options = args[-1].is_a?(Hash) ? args.pop : {}
73
+ whiny = args.fetch(0, @whiny)
74
+
75
+ options[:whiny] = whiny
76
+ options[:stderr] = false if block_given?
77
+
78
+ shell = GDCM::Shell.new
79
+ stdout, stderr, status = shell.run(command, options)
80
+ yield stdout, stderr, status if block_given?
81
+
82
+ stdout.chomp("\n")
83
+ end
84
+
85
+ ##
86
+ # The currently built-up command.
87
+ #
88
+ # @return [Array<String>]
89
+ #
90
+ # @example
91
+ # convert = GDCM::Tool::Convert.new
92
+ # convert.resize "500x500"
93
+ # convert.contrast
94
+ # convert.command #=> ["convert", "--resize", "500x500", "--contrast"]
95
+ #
96
+ def command
97
+ [*executable, *args]
98
+ end
99
+
100
+ def executable
101
+ exe = [name]
102
+ exe
103
+ end
104
+
105
+ ##
106
+ # Appends raw options, useful for appending file paths.
107
+ #
108
+ # @return [self]
109
+ #
110
+ def <<(arg)
111
+ args << arg.to_s
112
+ self
113
+ end
114
+
115
+ ##
116
+ # Merges a list of raw options.
117
+ #
118
+ # @return [self]
119
+ #
120
+ def merge!(new_args)
121
+ new_args.each { |arg| self << arg }
122
+ self
123
+ end
124
+
125
+ ##
126
+ # Changes the last operator to its "plus" form.
127
+ #
128
+ # @example
129
+ # GDCM::Tool::Convert.new do |convert|
130
+ # convert.antialias.+
131
+ # convert.distort.+("Perspective", "0,0,4,5 89,0,45,46")
132
+ # end
133
+ # # executes `convert +antialias +distort Perspective '0,0,4,5 89,0,45,46'`
134
+ #
135
+ # @return [self]
136
+ #
137
+ def +(*values)
138
+ args[-1] = args[-1].sub(/^-/, '+')
139
+ self.merge!(values)
140
+ self
141
+ end
142
+
143
+ ##
144
+ # Create an GDCM stack in the command (surround.
145
+ #
146
+ # @example
147
+ # GDCM::Tool::Convert.new do |convert|
148
+ # convert << "1.dcm"
149
+ # convert.stack do |stack|
150
+ # stack << "2.dcm"
151
+ # stack.rotate(30)
152
+ # end
153
+ # convert.append.+
154
+ # convert << "3.dcm"
155
+ # end
156
+ # # executes `convert 1.dcm \( 2.dcm --rotate 30 \) +append 3.dcm`
157
+ #
158
+ def stack(*args)
159
+ self << "("
160
+ args.each do |value|
161
+ case value
162
+ when Hash then value.each { |key, value| send(key, *value) }
163
+ when String then self << value
164
+ end
165
+ end
166
+ yield self if block_given?
167
+ self << ")"
168
+ end
169
+
170
+ ##
171
+ # Adds GDCM's pseudo-filename `-` for standard input.
172
+ #
173
+ # @example
174
+ # identify = GDCM::Tool::Identify.new
175
+ # identify.stdin
176
+ # identify.call(stdin: content)
177
+ # # executes `identify -` with the given standard input
178
+ #
179
+ def stdin
180
+ self << "-"
181
+ end
182
+
183
+ ##
184
+ # Adds GDCM's pseudo-filename `-` for standard output.
185
+ #
186
+ # @example
187
+ # content = GDCM::Tool::Convert.new do |convert|
188
+ # convert << "1.dcm"
189
+ # convert.auto_orient
190
+ # convert.stdout
191
+ # end
192
+ # # executes `convert 1.dcm --auto-orient -` which returns file contents
193
+ #
194
+ def stdout
195
+ self << "-"
196
+ end
197
+
198
+ ##
199
+ # Any undefined method will be transformed into a CLI option
200
+ #
201
+ # @example
202
+ # convert = GDCM::Tool.new("convert")
203
+ # convert.adaptive_blur("...")
204
+ # convert.foo_bar
205
+ # convert.command.join(" ") # => "convert --adaptive-blur ... --foo-bar"
206
+ #
207
+ def method_missing(name, *args)
208
+ option = "--#{name.to_s.tr('_', '-')}"
209
+ self << option
210
+ self.merge!(args)
211
+ self
212
+ end
213
+ end
214
+ end
215
+
216
+ require "gdcm/tool/convert"
217
+ require "gdcm/tool/identify"
@@ -0,0 +1,35 @@
1
+ require "tempfile"
2
+
3
+ module GDCM
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
+ # GDCM::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(["gdcm", 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 GDCM
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 = 0
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
16
+ end
17
+ end
data/lib/gdcm.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'gdcm/version'
2
+ require 'gdcm/configuration'
3
+
4
+ module GDCM
5
+ extend GDCM::Configuration
6
+
7
+ ##
8
+ # Returns GDCM's version.
9
+ #
10
+ # @return [String]
11
+ def self.cli_version
12
+ output = GDCM::Tool::Identify.new(&:version)
13
+ output[/\d+\.\d+\.\d+(-\d+)?/]
14
+ end
15
+
16
+ class Error < RuntimeError; end
17
+ class Invalid < StandardError; end
18
+
19
+ end
20
+
21
+ require 'gdcm/tool'
22
+ require 'gdcm/package'
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gdcm
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - sanzstez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-04-11 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 GDCM 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/gdcm.rb
66
+ - lib/gdcm/configuration.rb
67
+ - lib/gdcm/package.rb
68
+ - lib/gdcm/package/info.rb
69
+ - lib/gdcm/shell.rb
70
+ - lib/gdcm/tool.rb
71
+ - lib/gdcm/tool/convert.rb
72
+ - lib/gdcm/tool/identify.rb
73
+ - lib/gdcm/utilities.rb
74
+ - lib/gdcm/version.rb
75
+ homepage: https://github.com/sanzstez/gdcm
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '2.0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements:
94
+ - You must have GDCM tools installed
95
+ rubygems_version: 3.0.9
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Ruby adapter for GDCM tools for DICOM medical files.
99
+ test_files: []