gdcm 1.0.0

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: 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: []