dicom 0.7 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,208 @@
1
+ # Copyright 2008-2010 Christoffer Lervag
2
+
3
+ module DICOM
4
+
5
+ # This class contains methods that interact with Ruby DICOM's dictionary.
6
+ #
7
+ class DLibrary
8
+
9
+ # A hash containing tags as key and an array as value, where the array contains data element vr and name.
10
+ attr_reader :tags
11
+ # A hash containing UIDs as key and an array as value, where the array contains name and type.
12
+ attr_reader :uid
13
+
14
+ # Creates a DLibrary instance.
15
+ #
16
+ def initialize
17
+ # Load the data elements hash, where the keys are tag strings, and values
18
+ # are two-element arrays [vr, name] (where vr itself is an array of 1-3 elements):
19
+ @tags = Dictionary.load_data_elements
20
+ # Load UID hash (DICOM unique identifiers), where the keys are UID strings,
21
+ # and values are two-element arrays [description, type]:
22
+ @uid = Dictionary.load_uid
23
+ end
24
+
25
+ # Checks whether a given string is a valid transfer syntax or not.
26
+ # Returns true if valid, false if not.
27
+ #
28
+ # === Parameters
29
+ #
30
+ # * <tt>uid</tt> -- String. A DICOM UID value which will be matched against known transfer syntaxes.
31
+ #
32
+ def check_ts_validity(uid)
33
+ result = false
34
+ value = @uid[uid]
35
+ if value
36
+ result = true if value[1] == "Transfer Syntax"
37
+ end
38
+ return result
39
+ end
40
+
41
+ # Extracts, and returns, all transfer syntaxes and SOP Classes from the dictionary,
42
+ # in the form of a transfer syntax hash and a sop class hash.
43
+ #
44
+ # Both hashes have UIDs as keys and their descriptions as values.
45
+ #
46
+ def extract_transfer_syntaxes_and_sop_classes
47
+ transfer_syntaxes = Hash.new
48
+ sop_classes = Hash.new
49
+ @uid.each_pair do |key, value|
50
+ if value[1] == "Transfer Syntax"
51
+ transfer_syntaxes[key] = value[0]
52
+ elsif value[1] == "SOP Class"
53
+ sop_classes[key] = value[0]
54
+ end
55
+ end
56
+ return transfer_syntaxes, sop_classes
57
+ end
58
+
59
+ # Checks if the specified transfer syntax implies the presence of pixel compression.
60
+ # Returns true if pixel compression is implied, false if not.
61
+ #
62
+ # === Parameters
63
+ #
64
+ # * <tt>uid</tt> -- String. A DICOM UID value.
65
+ #
66
+ def get_compression(uid)
67
+ result = false
68
+ if uid
69
+ value = @uid[uid]
70
+ if value
71
+ result = true if value[1] == "Transfer Syntax" and not value[0].include?("Endian")
72
+ end
73
+ end
74
+ return result
75
+ end
76
+
77
+ # Determines, and returns, the name and vr of the data element which the specified tag belongs to.
78
+ # Values are retrieved from the Ruby DICOM dictionary if a match is found.
79
+ #
80
+ # === Notes
81
+ #
82
+ # * Private tags will have their names listed as "Private".
83
+ # * Non-private tags that are not found in the dictionary will be listed as "Unknown".
84
+ #
85
+ # === Parameters
86
+ #
87
+ # * <tt>tag</tt> -- String. A data element tag.
88
+ #
89
+ def get_name_vr(tag)
90
+ if tag.private? and tag.element != GROUP_LENGTH
91
+ name = "Private"
92
+ vr = "UN"
93
+ else
94
+ # Check the dictionary:
95
+ values = @tags[tag]
96
+ if values
97
+ name = values[1]
98
+ vr = values[0][0]
99
+ else
100
+ # For the tags that are not recognised, we need to do some additional testing to see if it is one of the special cases:
101
+ if tag.element == GROUP_LENGTH
102
+ # Group length:
103
+ name = "Group Length"
104
+ vr = "UL"
105
+ elsif tag[0..6] == "0020,31"
106
+ # Source Image ID's (Retired):
107
+ values = @tags["0020,31xx"]
108
+ name = values[1]
109
+ vr = values[0][0]
110
+ elsif tag.group == "1000" and tag.element =~ /\A\h{3}[0-5]\z/
111
+ # Group 1000,xxx[0-5] (Retired):
112
+ new_tag = tag.group + "xx" + tag.element[3..3]
113
+ values = @tags[new_tag]
114
+ elsif tag.group == "1010"
115
+ # Group 1010,xxxx (Retired):
116
+ new_tag = tag.group + "xxxx"
117
+ values = @tags[new_tag]
118
+ elsif tag[0..1] == "50" or tag[0..1] == "60"
119
+ # Group 50xx (Retired) and 60xx:
120
+ new_tag = tag[0..1]+"xx"+tag[4..8]
121
+ values = @tags[new_tag]
122
+ if values
123
+ name = values[1]
124
+ vr = values[0][0]
125
+ end
126
+ elsif tag[0..1] == "7F" and tag[5..6] == "00"
127
+ # Group 7Fxx,00[10,11,20,30,40] (Retired):
128
+ new_tag = tag[0..1]+"xx"+tag[4..8]
129
+ values = @tags[new_tag]
130
+ if values
131
+ name = values[1]
132
+ vr = values[0][0]
133
+ end
134
+ end
135
+ # If none of the above checks yielded a result, the tag is unknown:
136
+ unless name
137
+ name = "Unknown"
138
+ vr = "UN"
139
+ end
140
+ end
141
+ end
142
+ return name, vr
143
+ end
144
+
145
+ # Returns the tag that matches the supplied data element name, by searching the Ruby DICOM dictionary.
146
+ # Returns nil if no match is found.
147
+ #
148
+ # === Parameters
149
+ #
150
+ # * <tt>name</tt> -- String. A data element name.
151
+ #
152
+ def get_tag(name)
153
+ tag = nil
154
+ @tags.each_pair do |key, value|
155
+ tag = key if value[1] == name
156
+ end
157
+ return tag
158
+ end
159
+
160
+ # Returns the description/name of a specified UID (i.e. a transfer syntax or SOP class).
161
+ # Returns nil if no match is found
162
+ #
163
+ # === Parameters
164
+ #
165
+ # * <tt>uid</tt> -- String. A DICOM UID value.
166
+ #
167
+ def get_syntax_description(uid)
168
+ name = nil
169
+ value = @uid[uid]
170
+ name = value[0] if value
171
+ return name
172
+ end
173
+
174
+ # Checks the validity of the specified transfer syntax UID and determines the
175
+ # encoding settings (explicitness & endianness) associated with this value.
176
+ # The results are returned as 3 booleans: validity, explicitness & endianness.
177
+ #
178
+ # === Parameters
179
+ #
180
+ # * <tt>uid</tt> -- String. A DICOM UID value.
181
+ #
182
+ def process_transfer_syntax(uid)
183
+ valid = check_ts_validity(uid)
184
+ case uid
185
+ # Some variations with uncompressed pixel data:
186
+ when IMPLICIT_LITTLE_ENDIAN
187
+ explicit = false
188
+ endian = false
189
+ when EXPLICIT_LITTLE_ENDIAN
190
+ explicit = true
191
+ endian = false
192
+ when "1.2.840.10008.1.2.1.99" # Deflated Explicit VR, Little Endian
193
+ # Note: Has this transfer syntax been tested yet?
194
+ explicit = true
195
+ endian = false
196
+ when EXPLICIT_BIG_ENDIAN
197
+ explicit = true
198
+ endian = true
199
+ else
200
+ # For everything else, assume compressed pixel data, with Explicit VR, Little Endian:
201
+ explicit = true
202
+ endian = false
203
+ end
204
+ return valid, explicit, endian
205
+ end
206
+
207
+ end
208
+ end
@@ -0,0 +1,424 @@
1
+ # Copyright 2008-2010 Christoffer Lervag
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ #
16
+
17
+ # === TODO:
18
+ #
19
+ # * The retrieve file network functionality (get_image() in DClient class) has not been tested.
20
+ # * Make the networking code more intelligent in its handling of unexpected network communication.
21
+ # * Full support for compressed image data.
22
+ # * Read/Write 12 bit image data.
23
+ # * Support for color image data.
24
+ # * Complete support for Big endian (Everything but signed short and signed long has been implemented).
25
+ # * Complete support for multiple frame image data to NArray and RMagick objects (partial support already featured).
26
+ # * Image handling does not take into consideration DICOM tags which specify orientation, samples per pixel and photometric interpretation.
27
+ # * More robust and flexible options for reorienting extracted pixel arrays?
28
+ # * A curious observation: Creating a DLibrary instance is exceptionally slow on my Ruby 1.9.1 install: 0.4 seconds versus ~0.01 seconds on my Ruby 1.8.7 install!
29
+
30
+ module DICOM
31
+
32
+ # The DObject class is the main class for interacting with the DICOM object.
33
+ # Reading from and writing to files is executed from instances of this class.
34
+ #
35
+ # === Inheritance
36
+ #
37
+ # As the DObject class inherits from the SuperItem class, which itself inherits from the SuperParent class,
38
+ # all SuperItem and SuperParent methods are also available to instances of DObject.
39
+ #
40
+ class DObject < SuperItem
41
+
42
+ # An array which contain any notices/warnings/errors that have been recorded for the DObject instance.
43
+ attr_reader :errors
44
+ # A boolean set as false. This attribute is included to provide consistency with other object types for the internal methods which use it.
45
+ attr_reader :parent
46
+ # A boolean which is set as true if a DICOM file has been successfully read & parsed from a file (or binary string).
47
+ attr_reader :read_success
48
+ # The Stream instance associated with this DObject instance (this attribute is mostly used internally).
49
+ attr_reader :stream
50
+ # A boolean which is set as true if a DObject instance has been successfully written to file (or successfully encoded).
51
+ attr_reader :write_success
52
+
53
+ # Creates a DObject instance (DObject is an abbreviation for "DICOM object").
54
+ #
55
+ # The DObject instance holds references to the different types of objects (DataElement, Item, Sequence)
56
+ # that makes up a DICOM object. A DObject is typically buildt by reading and parsing a file or a
57
+ # binary string, but can also be buildt from an empty state by the user.
58
+ #
59
+ # === Parameters
60
+ #
61
+ # * <tt>string</tt> -- A string which specifies either the path of a DICOM file to be loaded, or a binary DICOM string to be parsed. The parameter defaults to nil, in which case an empty DObject instance is created.
62
+ # * <tt>options</tt> -- A hash of parameters.
63
+ #
64
+ # === Options
65
+ #
66
+ # * <tt>:bin</tt> -- Boolean. If set to true, string parameter will be interpreted as a binary DICOM string, and not a path string, which is the default behaviour.
67
+ # * <tt>:syntax</tt> -- String. If a syntax string is specified, the DRead class will be forced to use this transfer syntax when decoding the file/binary string.
68
+ # * <tt>:verbose</tt> -- Boolean. If set to false, the DObject instance will run silently and not output warnings and error messages to the screen. Defaults to true.
69
+ #
70
+ # === Examples
71
+ #
72
+ # # Load a DICOM file:
73
+ # require 'dicom'
74
+ # obj = DICOM::DObject.new("test.dcm")
75
+ # # Read a DICOM file that has already been loaded into memory in a binary string (with a known transfer syntax):
76
+ # obj = DICOM::DObject.new(binary_string, :bin => true, :syntax => string_transfer_syntax)
77
+ # # Create an empty DICOM object & choose non-verbose behaviour:
78
+ # obj = DICOM::DObject.new(nil, :verbose => false)
79
+ #
80
+ def initialize(string=nil, options={})
81
+ # Process option values, setting defaults for the ones that are not specified:
82
+ # Default verbosity is true if verbosity hasn't been specified (nil):
83
+ @verbose = (options[:verbose] == false ? false : true)
84
+ # Initialization of variables that DObject share with other parent elements:
85
+ initialize_parent
86
+ # Messages (errors, warnings or notices) will be accumulated in an array:
87
+ @errors = Array.new
88
+ # Structural information (default values):
89
+ @explicit = true
90
+ @file_endian = false
91
+ # Control variables:
92
+ @read_success = false
93
+ # Initialize a Stream instance which is used for encoding/decoding:
94
+ @stream = Stream.new(nil, @file_endian)
95
+ # The DObject instance is the top of the hierarchy and unlike other elements it has no parent:
96
+ @parent = nil
97
+ # For convenience, call the read method if a string has been supplied:
98
+ if string.is_a?(String) and string != ""
99
+ @file = string unless options[:bin]
100
+ read(string, options)
101
+ end
102
+ end
103
+
104
+ # Encodes the DICOM object into a series of binary string segments with a specified maximum length.
105
+ #
106
+ # Returns the encoded binary strings in an array.
107
+ #
108
+ # === Parameters
109
+ #
110
+ # * <tt>max_size</tt> -- An integer (Fixnum) which specifies the maximum allowed size of the binary data strings which will be encoded.
111
+ #
112
+ # === Examples
113
+ #
114
+ # encoded_strings = obj.encode_segments(16384)
115
+ #
116
+ def encode_segments(max_size)
117
+ w = DWrite.new(self, transfer_syntax, file_name=nil)
118
+ w.encode_segments(max_size)
119
+ # Write process succesful?
120
+ @write_success = w.success
121
+ # If any messages has been recorded, send these to the message handling method:
122
+ add_msg(w.msg) if w.msg.length > 0
123
+ return w.segments
124
+ end
125
+
126
+ # Gathers key information about the DObject as well as some system data, and prints this information to the screen.
127
+ #
128
+ # This information includes properties like encoding, byte order, modality and various image properties.
129
+ #
130
+ #--
131
+ # FIXME: Perhaps this method should be split up in one or two separate methods which just builds the information arrays,
132
+ # and a third method for printing this to the screen.
133
+ #
134
+ def information
135
+ sys_info = Array.new
136
+ info = Array.new
137
+ # Version of Ruby DICOM used:
138
+ sys_info << "Ruby DICOM version: #{VERSION}"
139
+ # System endian:
140
+ if CPU_ENDIAN
141
+ cpu = "Big Endian"
142
+ else
143
+ cpu = "Little Endian"
144
+ end
145
+ sys_info << "Byte Order (CPU): #{cpu}"
146
+ # File path/name:
147
+ info << "File: #{@file}"
148
+ # Modality:
149
+ sop_class_uid = self["0008,0016"]
150
+ if sop_class_uid
151
+ modality = LIBRARY.get_syntax_description(sop_class_uid.value) || "Unknown UID!"
152
+ else
153
+ modality = "SOP Class not specified!"
154
+ end
155
+ info << "Modality: #{modality}"
156
+ # Meta header presence (Simply check for the presence of the transfer syntax data element), VR and byte order:
157
+ transfer_syntax = self["0002,0010"]
158
+ if transfer_syntax
159
+ syntax_validity, explicit, endian = LIBRARY.process_transfer_syntax(transfer_syntax.value)
160
+ if syntax_validity
161
+ meta_comment = ""
162
+ explicit_comment = ""
163
+ encoding_comment = ""
164
+ else
165
+ meta_comment = " (But unknown/invalid transfer syntax: #{transfer_syntax})"
166
+ explicit_comment = " (Assumed)"
167
+ encoding_comment = " (Assumed)"
168
+ end
169
+ if explicit
170
+ explicitness = "Explicit"
171
+ else
172
+ explicitness = "Implicit"
173
+ end
174
+ if endian
175
+ encoding = "Big Endian"
176
+ else
177
+ encoding = "Little Endian"
178
+ end
179
+ else
180
+ meta = "No"
181
+ explicitness = (@explicit == true ? "Explicit" : "Implicit")
182
+ encoding = (@file_endian == true ? "Big Endian" : "Little Endian")
183
+ explicit_comment = " (Assumed)"
184
+ encoding_comment = " (Assumed)"
185
+ end
186
+ meta = "Yes#{meta_comment}"
187
+ explicit = "#{explicitness}#{explicit_comment}"
188
+ encoding = "#{encoding}#{encoding_comment}"
189
+ info << "Value Representation: #{explicit}"
190
+ info << "Byte Order (File): #{encoding}"
191
+ # Pixel data:
192
+ pixels = self[PIXEL_TAG]
193
+ unless pixels
194
+ info << "Pixel Data: No"
195
+ else
196
+ info << "Pixel Data: Yes"
197
+ # Image size:
198
+ cols = self["0028,0011"] || "Columns missing"
199
+ rows = self["0028,0010"] || "Rows missing"
200
+ info << "Image Size: #{cols.value}*#{rows.value}"
201
+ # Frames:
202
+ frames = self["0028,0008"] || "1"
203
+ if frames != "1"
204
+ # Encapsulated or 3D pixel data:
205
+ if pixels.is_a?(DataElement)
206
+ frames = frames.value + " (3D Pixel Data)"
207
+ else
208
+ frames = frames.value + " (Encapsulated Multiframe Image)"
209
+ end
210
+ end
211
+ info << "Number of frames: #{frames}"
212
+ # Color:
213
+ colors = self["0028,0004"] || "Not specified"
214
+ info << "Photometry: #{colors.value}"
215
+ # Compression:
216
+ if transfer_syntax
217
+ compression = LIBRARY.get_compression(transfer_syntax.value)
218
+ if compression
219
+ compression = LIBRARY.get_syntax_description(transfer_syntax.value) || "Unknown UID!"
220
+ else
221
+ compression = "No"
222
+ end
223
+ else
224
+ compression = "No (Assumed)"
225
+ end
226
+ info << "Compression: #{compression}"
227
+ # Pixel bits (allocated):
228
+ bits = self["0028,0100"] || "Not specified"
229
+ info << "Bits per Pixel: #{bits.value}"
230
+ end
231
+ # Print the DICOM object's key properties:
232
+ separator = "-------------------------------------------"
233
+ puts "\n"
234
+ puts "System Properties:"
235
+ puts separator
236
+ puts sys_info
237
+ puts "\n"
238
+ puts "DICOM Object Properties:"
239
+ puts separator
240
+ puts info
241
+ puts separator
242
+ return info
243
+ end
244
+
245
+ # Prints information of interest related to the DICOM object.
246
+ # Calls the print() method of SuperParent as well as the information() method of DObject.
247
+ #
248
+ def print_all
249
+ puts ""
250
+ print(:value_max => 30)
251
+ information
252
+ end
253
+
254
+ # Returns a DICOM object by reading and parsing the specified file.
255
+ # This is accomplished by initializing the DRead class, which loads DICOM information to arrays.
256
+ #
257
+ # === Notes
258
+ #
259
+ # This method is called automatically when initializing the DObject class with a file parameter,
260
+ # and in practice should not be called by users.
261
+ #
262
+ #--
263
+ # FIXME: It should be considered whether this should be a private method.
264
+ #
265
+ def read(string, options={})
266
+ r = DRead.new(self, string, options)
267
+ # If reading failed, and no transfer syntax was detected, we will make another attempt at reading the file while forcing explicit (little endian) decoding.
268
+ # This will help for some rare cases where the DICOM file is saved (erroneously, Im sure) with explicit encoding without specifying the transfer syntax tag.
269
+ unless r.success or exists?("0002,0010")
270
+ # Clear the existing DObject tags:
271
+ @tags = Hash.new
272
+ r_explicit = DRead.new(self, string, :bin => options[:bin], :syntax => EXPLICIT_LITTLE_ENDIAN)
273
+ # Only extract information from this new attempt if it was successful:
274
+ r = r_explicit if r_explicit.success
275
+ end
276
+ # Store the data to the instance variables if the readout was a success:
277
+ if r.success
278
+ @read_success = true
279
+ # Update instance variables based on the properties of the DICOM object:
280
+ @explicit = r.explicit
281
+ @file_endian = r.file_endian
282
+ @signature = r.signature
283
+ @stream.endian = @file_endian
284
+ else
285
+ @read_success = false
286
+ end
287
+ # If any messages has been recorded, send these to the message handling method:
288
+ add_msg(r.msg) if r.msg.length > 0
289
+ end
290
+
291
+ # Returns the transfer syntax string of the DObject.
292
+ #
293
+ # If a transfer syntax has not been defined in the DObject, a default tansfer syntax is assumed and returned.
294
+ #
295
+ def transfer_syntax
296
+ return value("0002,0010") || IMPLICIT_LITTLE_ENDIAN
297
+ end
298
+
299
+ # Changes the transfer syntax DataElement of the DObject instance, and performs re-encoding of all
300
+ # numerical values if a switch of endianness is implied.
301
+ #
302
+ # === Restrictions
303
+ #
304
+ # This method does not change the compressed state of the pixel data element. Changing the transfer syntax between
305
+ # an uncompressed and compressed state will NOT change the pixel data accordingly (this must be taken care of manually).
306
+ #
307
+ # === Parameters
308
+ #
309
+ # * <tt>new_syntax</tt> -- The new transfer syntax string which will be applied to the DObject.
310
+ #
311
+ def transfer_syntax=(new_syntax)
312
+ valid, new_explicit, new_endian = LIBRARY.process_transfer_syntax(new_syntax)
313
+ if valid
314
+ # Get the old transfer syntax and write the new one to the DICOM object:
315
+ old_syntax = transfer_syntax
316
+ valid, old_explicit, old_endian = LIBRARY.process_transfer_syntax(old_syntax)
317
+ if exists?("0002,0010")
318
+ self["0002,0010"].value = new_syntax
319
+ else
320
+ add(DataElement.new("0002,0010", new_syntax))
321
+ end
322
+ # Update our Stream instance with the new encoding:
323
+ @stream.endian = new_endian
324
+ # Determine if re-encoding is needed:
325
+ if old_endian != new_endian
326
+ # Re-encode all Data Elements with number values:
327
+ encode_children(old_endian)
328
+ else
329
+ add_msg("New transfer syntax #{new_syntax} does not change encoding: No re-encoding needed.")
330
+ end
331
+ else
332
+ raise "Invalid transfer syntax specified: #{new_syntax}"
333
+ end
334
+ end
335
+
336
+ # Passes the DObject to the DWrite class, which traverses the data element
337
+ # structure and encodes a proper DICOM binary string, which is finally written to the specified file.
338
+ #
339
+ # === Parameters
340
+ #
341
+ # * <tt>file_name</tt> -- A string which identifies the path & name of the DICOM file which is to be written to disk.
342
+ # * <tt>options</tt> -- A hash of parameters.
343
+ #
344
+ # === Options
345
+ #
346
+ # * <tt>:add_meta</tt> -- Boolean. If set to false, no manipulation of the DICOM object's meta group will be performed before the DObject is written to file.
347
+ #
348
+ # === Examples
349
+ #
350
+ # obj.write(path + "test.dcm")
351
+ #
352
+ def write(file_name, options={})
353
+ insert_missing_meta unless options[:add_meta] == false
354
+ w = DWrite.new(self, transfer_syntax, file_name, options)
355
+ w.write
356
+ # Write process succesful?
357
+ @write_success = w.success
358
+ # If any messages has been recorded, send these to the message handling method:
359
+ add_msg(w.msg) if w.msg.length > 0
360
+ end
361
+
362
+
363
+ # Following methods are private:
364
+ private
365
+
366
+
367
+ # Adds one or more status messages to the instance array holding messages, and if the verbose instance variable
368
+ # is true, the status message(s) are printed to the screen as well.
369
+ #
370
+ # === Parameters
371
+ #
372
+ # * <tt>msg</tt> -- Status message string, or an array containing one or more status message strings.
373
+ #
374
+ def add_msg(msg)
375
+ puts msg if @verbose
376
+ @errors << msg
377
+ @errors.flatten
378
+ end
379
+
380
+ # Adds any missing meta group (0002,xxxx) data elements to the DICOM object,
381
+ # to ensure that a valid DICOM object will be written to file.
382
+ #
383
+ def insert_missing_meta
384
+ # File Meta Information Version:
385
+ DataElement.new("0002,0001", [0,1], :parent => self) unless exists?("0002,0001")
386
+ # Media Storage SOP Class UID:
387
+ DataElement.new("0002,0002", value("0008,0016"), :parent => self) unless exists?("0002,0002")
388
+ # Media Storage SOP Instance UID:
389
+ DataElement.new("0002,0003", value("0008,0018"), :parent => self) unless exists?("0002,0003")
390
+ # Transfer Syntax UID:
391
+ DataElement.new("0002,0010", transfer_syntax, :parent => self) unless exists?("0002,0010")
392
+ # Implementation Class UID:
393
+ DataElement.new("0002,0012", UID, :parent => self) unless exists?("0002,0012")
394
+ # Implementation Version Name:
395
+ DataElement.new("0002,0013", NAME, :parent => self) unless exists?("0002,0013")
396
+ # Source Application Entity Title:
397
+ DataElement.new("0002,0016", SOURCE_APP_TITLE, :parent => self) unless exists?("0002,0016")
398
+ # Group length:
399
+ # Remove old group length (if it exists) before creating a new one:
400
+ remove("0002,0000")
401
+ DataElement.new("0002,0000", meta_group_length, :parent => self)
402
+ end
403
+
404
+ # Determines and returns the length of the meta group in the DObject instance.
405
+ #
406
+ def meta_group_length
407
+ group_length = 0
408
+ meta_elements = group(META_GROUP)
409
+ tag = 4
410
+ vr = 2
411
+ meta_elements.each do |element|
412
+ case element.vr
413
+ when "OB","OW","OF","SQ","UN","UT"
414
+ length = 6
415
+ else
416
+ length = 2
417
+ end
418
+ group_length += tag + vr + length + element.bin.length
419
+ end
420
+ return group_length
421
+ end
422
+
423
+ end
424
+ end