/src/dcmtk/dcmdata/libsrc/dcdatset.cc
Line | Count | Source |
1 | | /* |
2 | | * |
3 | | * Copyright (C) 1994-2026, OFFIS e.V. |
4 | | * All rights reserved. See COPYRIGHT file for details. |
5 | | * |
6 | | * This software and supporting documentation were developed by |
7 | | * |
8 | | * OFFIS e.V. |
9 | | * R&D Division Health |
10 | | * Escherweg 2 |
11 | | * D-26121 Oldenburg, Germany |
12 | | * |
13 | | * |
14 | | * Module: dcmdata |
15 | | * |
16 | | * Author: Gerd Ehlers, Andreas Barth |
17 | | * |
18 | | * Purpose: Implementation of class DcmDataset |
19 | | * |
20 | | */ |
21 | | |
22 | | |
23 | | #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ |
24 | | |
25 | | #include "dcmtk/ofstd/ofstream.h" |
26 | | #include "dcmtk/ofstd/ofstack.h" |
27 | | #include "dcmtk/ofstd/ofstd.h" |
28 | | |
29 | | #include "dcmtk/dcmdata/dcjson.h" |
30 | | #include "dcmtk/dcmdata/dcdatset.h" |
31 | | #include "dcmtk/dcmdata/dcxfer.h" |
32 | | #include "dcmtk/dcmdata/dcvrus.h" |
33 | | #include "dcmtk/dcmdata/dcpixel.h" |
34 | | #include "dcmtk/dcmdata/dcdeftag.h" |
35 | | #include "dcmtk/dcmdata/dcistrma.h" /* for class DcmInputStream */ |
36 | | #include "dcmtk/dcmdata/dcistrmf.h" /* for class DcmInputFileStream */ |
37 | | #include "dcmtk/dcmdata/dcistrms.h" /* for class DcmStdinStream */ |
38 | | #include "dcmtk/dcmdata/dcostrma.h" /* for class DcmOutputStream */ |
39 | | #include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */ |
40 | | #include "dcmtk/dcmdata/dcostrms.h" /* for class DcmStdoutStream */ |
41 | | #include "dcmtk/dcmdata/dcwcache.h" /* for class DcmWriteCache */ |
42 | | |
43 | | |
44 | | // ******************************** |
45 | | |
46 | | |
47 | | DcmDataset::DcmDataset() |
48 | 0 | : DcmItem(DCM_ItemTag, DCM_UndefinedLength), |
49 | 0 | OriginalXfer(EXS_Unknown), |
50 | | // the default transfer syntax is explicit VR with local endianness |
51 | 0 | CurrentXfer((gLocalByteOrder == EBO_BigEndian) ? EXS_BigEndianExplicit : EXS_LittleEndianExplicit) |
52 | 0 | { |
53 | 0 | } |
54 | | |
55 | | |
56 | | DcmDataset& DcmDataset::operator=(const DcmDataset& obj) |
57 | 0 | { |
58 | 0 | if (this != &obj) |
59 | 0 | { |
60 | | // copy parent's member variables |
61 | 0 | DcmItem::operator=(obj); |
62 | | // copy DcmDataset's member variables |
63 | 0 | OriginalXfer = obj.OriginalXfer; |
64 | 0 | CurrentXfer = obj.CurrentXfer; |
65 | 0 | } |
66 | 0 | return *this; |
67 | 0 | } |
68 | | |
69 | | |
70 | | DcmDataset::DcmDataset(const DcmDataset &old) |
71 | 0 | : DcmItem(old), |
72 | 0 | OriginalXfer(old.OriginalXfer), |
73 | 0 | CurrentXfer(old.CurrentXfer) |
74 | 0 | { |
75 | 0 | } |
76 | | |
77 | | |
78 | | OFCondition DcmDataset::copyFrom(const DcmObject& rhs) |
79 | 0 | { |
80 | 0 | if (this != &rhs) |
81 | 0 | { |
82 | 0 | if (rhs.ident() != ident()) return EC_IllegalCall; |
83 | 0 | *this = OFstatic_cast(const DcmDataset &, rhs); |
84 | 0 | } |
85 | 0 | return EC_Normal; |
86 | 0 | } |
87 | | |
88 | | |
89 | | DcmDataset::~DcmDataset() |
90 | 0 | { |
91 | 0 | } |
92 | | |
93 | | |
94 | | // ******************************** |
95 | | |
96 | | |
97 | | OFCondition DcmDataset::clear() |
98 | 0 | { |
99 | 0 | OFCondition result = DcmItem::clear(); |
100 | | // TODO: should we also reset OriginalXfer and CurrentXfer? |
101 | 0 | setLengthField(DCM_UndefinedLength); |
102 | 0 | return result; |
103 | 0 | } |
104 | | |
105 | | DcmEVR DcmDataset::ident() const |
106 | 0 | { |
107 | 0 | return EVR_dataset; |
108 | 0 | } |
109 | | |
110 | | |
111 | | E_TransferSyntax DcmDataset::getOriginalXfer() const |
112 | 0 | { |
113 | 0 | return OriginalXfer; |
114 | 0 | } |
115 | | |
116 | | |
117 | | E_TransferSyntax DcmDataset::getCurrentXfer() const |
118 | 0 | { |
119 | 0 | return CurrentXfer; |
120 | 0 | } |
121 | | |
122 | | |
123 | | void DcmDataset::updateOriginalXfer() |
124 | 0 | { |
125 | 0 | DcmStack resultStack; |
126 | | /* Check for pixel data element on main dataset level only. */ |
127 | | /* Icon images and other nested pixel data elements are not checked. */ |
128 | 0 | if (search(DCM_PixelData, resultStack, ESM_fromHere, OFFalse).good()) |
129 | 0 | { |
130 | 0 | if (resultStack.top()->ident() == EVR_PixelData) |
131 | 0 | { |
132 | | /* determine the transfer syntax of the original and current representation */ |
133 | 0 | E_TransferSyntax repType = EXS_Unknown; |
134 | 0 | const DcmRepresentationParameter *repParam = NULL; |
135 | 0 | DcmPixelData *pixelData = OFstatic_cast(DcmPixelData *, resultStack.top()); |
136 | 0 | pixelData->getOriginalRepresentationKey(OriginalXfer, repParam); |
137 | 0 | pixelData->getCurrentRepresentationKey(repType, repParam); |
138 | | /* check whether we also need to change the current transfer syntax */ |
139 | 0 | if (repType == EXS_LittleEndianExplicit /* default */) |
140 | 0 | { |
141 | | /* only change the value if not already uncompressed */ |
142 | 0 | if ((CurrentXfer != EXS_LittleEndianImplicit) && |
143 | 0 | (CurrentXfer != EXS_LittleEndianExplicit) && |
144 | 0 | (CurrentXfer != EXS_BigEndianExplicit)) |
145 | 0 | { |
146 | 0 | CurrentXfer = repType; |
147 | 0 | } |
148 | 0 | } |
149 | 0 | else if (repType != EXS_Unknown) |
150 | 0 | { |
151 | 0 | CurrentXfer = repType; |
152 | 0 | } |
153 | 0 | } else { |
154 | | /* something is fishy with the pixel data element (wrong class) */ |
155 | 0 | DCMDATA_WARN("DcmDataset: Wrong class for pixel data element, cannot update original transfer syntax"); |
156 | 0 | } |
157 | 0 | } |
158 | | /* if no pixel data was found, update only in case of unknown representation */ |
159 | 0 | else |
160 | 0 | { |
161 | 0 | if (OriginalXfer == EXS_Unknown) |
162 | 0 | { |
163 | | /* this is also the default in DcmPixelData::getOriginalRepresentationKey() */ |
164 | 0 | OriginalXfer = EXS_LittleEndianExplicit; |
165 | 0 | } |
166 | 0 | if (CurrentXfer == EXS_Unknown) |
167 | 0 | { |
168 | | /* this is also the default in DcmPixelData::getCurrentRepresentationKey() */ |
169 | 0 | CurrentXfer = EXS_LittleEndianExplicit; |
170 | 0 | } |
171 | 0 | } |
172 | 0 | } |
173 | | |
174 | | |
175 | | void DcmDataset::removeInvalidGroups(const OFBool cmdSet) |
176 | 0 | { |
177 | 0 | DcmStack stack; |
178 | 0 | DcmObject *object = NULL; |
179 | | /* check for data or command set */ |
180 | 0 | if (cmdSet) |
181 | 0 | { |
182 | | /* iterate over all elements */ |
183 | 0 | while (nextObject(stack, OFTrue).good()) |
184 | 0 | { |
185 | 0 | object = stack.top(); |
186 | | /* in command sets, only group 0x0000 is allowed */ |
187 | 0 | if (object->getGTag() != 0x0000) |
188 | 0 | { |
189 | 0 | DCMDATA_DEBUG("DcmDataset::removeInvalidGroups() removing element " |
190 | 0 | << object->getTag() << " from command set"); |
191 | 0 | stack.pop(); |
192 | | /* remove element from command set and free memory */ |
193 | 0 | delete OFstatic_cast(DcmItem *, stack.top())->remove(object); |
194 | 0 | } |
195 | 0 | } |
196 | 0 | } else { |
197 | | /* iterate over all elements */ |
198 | 0 | while (nextObject(stack, OFTrue).good()) |
199 | 0 | { |
200 | 0 | object = stack.top(); |
201 | | /* in data sets, group 0x0000 to 0x0003, 0x0005, 0x0007 and 0xFFFF are not allowed */ |
202 | 0 | if ((object->getGTag() == 0x0000) || (object->getGTag() == 0x0002) || |
203 | 0 | !object->getTag().hasValidGroup()) |
204 | 0 | { |
205 | 0 | DCMDATA_DEBUG("DcmDataset::removeInvalidGroups() removing element " |
206 | 0 | << object->getTag() << " from data set"); |
207 | 0 | stack.pop(); |
208 | | /* remove element from data set and free memory */ |
209 | 0 | delete OFstatic_cast(DcmItem *, stack.top())->remove(object); |
210 | 0 | } |
211 | | /* in sequence items, also group 0x0006 is not allowed */ |
212 | 0 | else if ((stack.card() > 2) && (object->getGTag() == 0x0006)) |
213 | 0 | { |
214 | 0 | DCMDATA_DEBUG("DcmDataset::removeInvalidGroups() removing element " |
215 | 0 | << object->getTag() << " from sequence item"); |
216 | 0 | stack.pop(); |
217 | | /* remove element from data set and free memory */ |
218 | 0 | delete OFstatic_cast(DcmItem *, stack.top())->remove(object); |
219 | 0 | } |
220 | 0 | } |
221 | 0 | } |
222 | 0 | } |
223 | | |
224 | | |
225 | | // ******************************** |
226 | | |
227 | | |
228 | | Uint32 DcmDataset::calcElementLength(const E_TransferSyntax xfer, |
229 | | const E_EncodingType enctype) |
230 | 0 | { |
231 | 0 | return DcmItem::getLength(xfer, enctype); |
232 | 0 | } |
233 | | |
234 | | |
235 | | // ******************************** |
236 | | |
237 | | |
238 | | OFBool DcmDataset::canWriteXfer(const E_TransferSyntax newXfer, |
239 | | const E_TransferSyntax oldXfer) |
240 | 0 | { |
241 | 0 | if (newXfer == EXS_Unknown) |
242 | 0 | return OFFalse; |
243 | | |
244 | | /* Check stream compression for this transfer syntax */ |
245 | 0 | DcmXfer xf(newXfer); |
246 | 0 | if (xf.getStreamCompression() == ESC_unsupported) |
247 | 0 | return OFFalse; |
248 | | |
249 | 0 | return DcmItem::canWriteXfer(newXfer, (OriginalXfer == EXS_Unknown) ? oldXfer : OriginalXfer); |
250 | 0 | } |
251 | | |
252 | | |
253 | | // ******************************** |
254 | | |
255 | | |
256 | | void DcmDataset::print(STD_NAMESPACE ostream &out, |
257 | | const size_t flags, |
258 | | const int level, |
259 | | const char *pixelFileName, |
260 | | size_t *pixelCounter) |
261 | 0 | { |
262 | 0 | out << OFendl; |
263 | 0 | if (flags & DCMTypes::PF_useANSIEscapeCodes) |
264 | 0 | out << DCMDATA_ANSI_ESCAPE_CODE_COMMENT; |
265 | 0 | printNestingLevel(out, flags, level); |
266 | 0 | out << "# Dicom-Data-Set" << OFendl; |
267 | 0 | if (flags & DCMTypes::PF_useANSIEscapeCodes) |
268 | 0 | out << DCMDATA_ANSI_ESCAPE_CODE_COMMENT; |
269 | 0 | printNestingLevel(out, flags, level); |
270 | 0 | out << "# Used TransferSyntax: " << DcmXfer(CurrentXfer).getXferName(); |
271 | 0 | if (flags & DCMTypes::PF_useANSIEscapeCodes) |
272 | 0 | out << DCMDATA_ANSI_ESCAPE_CODE_RESET; |
273 | 0 | out << OFendl; |
274 | 0 | if (!elementList->empty()) |
275 | 0 | { |
276 | 0 | DcmObject *dO; |
277 | 0 | elementList->seek(ELP_first); |
278 | 0 | do { |
279 | 0 | dO = elementList->get(); |
280 | 0 | dO->print(out, flags, level + 1, pixelFileName, pixelCounter); |
281 | 0 | } while (elementList->seek(ELP_next)); |
282 | 0 | } |
283 | 0 | } |
284 | | |
285 | | |
286 | | // ******************************** |
287 | | |
288 | | |
289 | | OFCondition DcmDataset::writeXML(STD_NAMESPACE ostream &out, |
290 | | const size_t flags) |
291 | 0 | { |
292 | 0 | OFCondition l_error = EC_Normal; |
293 | | /* the Native DICOM Model as defined for Application Hosting needs special handling */ |
294 | 0 | if (flags & DCMTypes::XF_useNativeModel) |
295 | 0 | { |
296 | | /* write XML start tag */ |
297 | 0 | out << "<NativeDicomModel xml:space=\"preserve\""; |
298 | 0 | if (flags & DCMTypes::XF_useXMLNamespace) |
299 | 0 | out << " xmlns=\"" << NATIVE_DICOM_MODEL_XML_NAMESPACE_URI << "\""; |
300 | 0 | out << ">" << OFendl; |
301 | 0 | } else { |
302 | | /* DCMTK-specific output format (default) */ |
303 | 0 | OFString xmlString; |
304 | 0 | DcmXfer xfer(CurrentXfer); |
305 | | /* write XML start tag */ |
306 | 0 | out << "<data-set xfer=\"" << xfer.getXferID() << "\""; |
307 | 0 | out << " name=\"" << OFStandard::convertToMarkupString(xfer.getXferName(), xmlString) << "\""; |
308 | 0 | if (flags & DCMTypes::XF_useXMLNamespace) |
309 | 0 | out << " xmlns=\"" << DCMTK_XML_NAMESPACE_URI << "\""; |
310 | 0 | out << ">" << OFendl; |
311 | 0 | } |
312 | | /* write dataset content */ |
313 | 0 | if (!elementList->empty()) |
314 | 0 | { |
315 | | /* write content of all children */ |
316 | 0 | DcmObject *dO; |
317 | 0 | elementList->seek(ELP_first); |
318 | 0 | do { |
319 | 0 | dO = elementList->get(); |
320 | 0 | l_error = dO->writeXML(out, flags & ~DCMTypes::XF_useXMLNamespace); |
321 | 0 | } while (l_error.good() && elementList->seek(ELP_next)); |
322 | 0 | } |
323 | 0 | if (l_error.good()) |
324 | 0 | { |
325 | | /* write XML end tag (depending on output format) */ |
326 | 0 | if (flags & DCMTypes::XF_useNativeModel) |
327 | 0 | { |
328 | 0 | out << "</NativeDicomModel>" << OFendl; |
329 | 0 | } else { |
330 | 0 | out << "</data-set>" << OFendl; |
331 | 0 | } |
332 | 0 | } |
333 | 0 | return l_error; |
334 | 0 | } |
335 | | |
336 | | |
337 | | // ******************************** |
338 | | |
339 | | |
340 | | OFCondition DcmDataset::writeJson(STD_NAMESPACE ostream &out, |
341 | | DcmJsonFormat &format) |
342 | 0 | { |
343 | 0 | return writeJsonExt(out, format, OFFalse, OFFalse); // omit braces |
344 | 0 | } |
345 | | |
346 | | |
347 | | // ******************************** |
348 | | |
349 | | |
350 | | OFCondition DcmDataset::read(DcmInputStream &inStream, |
351 | | const E_TransferSyntax xfer, |
352 | | const E_GrpLenEncoding glenc, |
353 | | const Uint32 maxReadLength) |
354 | 0 | { |
355 | 0 | return DcmDataset::readUntilTag(inStream, xfer, glenc, maxReadLength, DCM_UndefinedTagKey); |
356 | 0 | } |
357 | | |
358 | | OFCondition DcmDataset::readUntilTag(DcmInputStream &inStream, |
359 | | const E_TransferSyntax xfer, |
360 | | const E_GrpLenEncoding glenc, |
361 | | const Uint32 maxReadLength, |
362 | | const DcmTagKey &stopParsingAtElement) |
363 | 0 | { |
364 | | /* check if the stream variable reported an error */ |
365 | 0 | errorFlag = inStream.status(); |
366 | | /* if the stream did not report an error but the stream */ |
367 | | /* is empty, set the error flag correspondingly */ |
368 | 0 | if (errorFlag.good() && inStream.eos()) |
369 | 0 | errorFlag = EC_EndOfStream; |
370 | | /* else if the stream did not report an error but the transfer */ |
371 | | /* state does not equal ERW_ready, go ahead and do something */ |
372 | 0 | else if (errorFlag.good() && getTransferState() != ERW_ready) |
373 | 0 | { |
374 | | /* if the transfer state is ERW_init, go ahead and check the transfer syntax which was passed */ |
375 | 0 | if (getTransferState() == ERW_init) |
376 | 0 | { |
377 | 0 | if (dcmAutoDetectDatasetXfer.get()) |
378 | 0 | { |
379 | 0 | DCMDATA_DEBUG("DcmDataset::read() automatic detection of transfer syntax is enabled"); |
380 | | /* To support incorrectly encoded datasets detect the transfer syntax from the stream. */ |
381 | | /* This is possible for given unknown and plain big or little endian transfer syntaxes. */ |
382 | 0 | switch (xfer) |
383 | 0 | { |
384 | 0 | case EXS_Unknown: |
385 | 0 | case EXS_LittleEndianImplicit: |
386 | 0 | case EXS_LittleEndianExplicit: |
387 | 0 | case EXS_BigEndianExplicit: |
388 | 0 | case EXS_BigEndianImplicit: |
389 | 0 | DCMDATA_DEBUG("DcmDataset::read() trying to detect transfer syntax of uncompressed data set"); |
390 | 0 | OriginalXfer = checkTransferSyntax(inStream); |
391 | 0 | if ((xfer != EXS_Unknown) && (OriginalXfer != xfer)) |
392 | 0 | DCMDATA_WARN("DcmDataset: Wrong transfer syntax specified, detecting from data set"); |
393 | 0 | break; |
394 | 0 | default: |
395 | 0 | DCMDATA_DEBUG("DcmDataset::read() data set seems to be compressed, so transfer syntax is not detected"); |
396 | 0 | OriginalXfer = xfer; |
397 | 0 | break; |
398 | 0 | } |
399 | 0 | } |
400 | 0 | else /* default behavior */ |
401 | 0 | { |
402 | | /* If the transfer syntax which was passed equals EXS_Unknown we want to */ |
403 | | /* determine the transfer syntax from the information in the stream itself. */ |
404 | | /* If the transfer syntax is given, we want to use it. */ |
405 | 0 | if (xfer == EXS_Unknown) |
406 | 0 | { |
407 | 0 | DCMDATA_DEBUG("DcmDataset::read() trying to detect transfer syntax of data set (because it is unknown)"); |
408 | 0 | OriginalXfer = checkTransferSyntax(inStream); |
409 | 0 | } else |
410 | 0 | OriginalXfer = xfer; |
411 | 0 | } |
412 | | /* dump information on debug level */ |
413 | 0 | DCMDATA_DEBUG("DcmDataset::read() TransferSyntax=\"" |
414 | 0 | << DcmXfer(OriginalXfer).getXferName() << "\""); |
415 | 0 | CurrentXfer = OriginalXfer; |
416 | | /* check stream compression for this transfer syntax */ |
417 | 0 | DcmXfer xf(OriginalXfer); |
418 | 0 | E_StreamCompression sc = xf.getStreamCompression(); |
419 | 0 | switch (sc) |
420 | 0 | { |
421 | 0 | case ESC_none: |
422 | | // nothing to do |
423 | 0 | break; |
424 | 0 | case ESC_unsupported: |
425 | | // stream compressed transfer syntax that we cannot create; bail out. |
426 | 0 | if (errorFlag.good()) |
427 | 0 | errorFlag = EC_UnsupportedEncoding; |
428 | 0 | break; |
429 | 0 | default: |
430 | | // supported stream compressed transfer syntax, install filter |
431 | 0 | errorFlag = inStream.installCompressionFilter(sc); |
432 | 0 | break; |
433 | 0 | } |
434 | 0 | } |
435 | | /* pass processing the task to class DcmItem */ |
436 | 0 | if (errorFlag.good()) |
437 | 0 | errorFlag = DcmItem::readUntilTag(inStream, OriginalXfer, glenc, maxReadLength, stopParsingAtElement); |
438 | |
|
439 | 0 | } |
440 | | |
441 | | /* if the error flag shows ok or that the end of the stream was encountered, */ |
442 | | /* we have read information for this particular data set or command; in this */ |
443 | | /* case, we need to do something for the current dataset object */ |
444 | 0 | if (errorFlag.good() || errorFlag == EC_EndOfStream) |
445 | 0 | { |
446 | | /* perform some final checks on dataset level */ |
447 | 0 | errorFlag = doPostReadChecks(); |
448 | |
|
449 | 0 | if (errorFlag.good()) |
450 | 0 | { |
451 | | /* set the error flag to ok */ |
452 | 0 | errorFlag = EC_Normal; |
453 | | |
454 | | /* take care of group length (according to what is specified */ |
455 | | /* in glenc) and padding elements (don't change anything) */ |
456 | 0 | computeGroupLengthAndPadding(glenc, EPD_noChange, OriginalXfer); |
457 | | |
458 | | /* and set the transfer state to ERW_ready to indicate that the data set is complete */ |
459 | 0 | setTransferState(ERW_ready); |
460 | 0 | } |
461 | 0 | } |
462 | | |
463 | | /* dump information if required */ |
464 | 0 | DCMDATA_TRACE("DcmDataset::read() returns error = " << errorFlag.text()); |
465 | | |
466 | | /* return result flag */ |
467 | 0 | return errorFlag; |
468 | 0 | } |
469 | | |
470 | | |
471 | | // ******************************** |
472 | | |
473 | | |
474 | | OFCondition DcmDataset::write(DcmOutputStream &outStream, |
475 | | const E_TransferSyntax oxfer, |
476 | | const E_EncodingType enctype /* = EET_UndefinedLength */, |
477 | | DcmWriteCache *wcache) |
478 | 0 | { |
479 | 0 | return write(outStream, oxfer, enctype, wcache, EGL_recalcGL); |
480 | 0 | } |
481 | | |
482 | | |
483 | | OFCondition DcmDataset::write(DcmOutputStream &outStream, |
484 | | const E_TransferSyntax oxfer, |
485 | | const E_EncodingType enctype, |
486 | | DcmWriteCache *wcache, |
487 | | const E_GrpLenEncoding glenc, |
488 | | const E_PaddingEncoding padenc, |
489 | | const Uint32 padlen, |
490 | | const Uint32 subPadlen, |
491 | | Uint32 instanceLength) |
492 | 0 | { |
493 | | /* if the transfer state of this is not initialized, this is an illegal call */ |
494 | 0 | if (getTransferState() == ERW_notInitialized) |
495 | 0 | errorFlag = EC_IllegalCall; |
496 | 0 | else |
497 | 0 | { |
498 | | /* check if the stream reported an error so far; if not, we can go ahead and write some data to it */ |
499 | 0 | errorFlag = outStream.status(); |
500 | |
|
501 | 0 | if (errorFlag.good() && getTransferState() != ERW_ready) |
502 | 0 | { |
503 | | /* Determine the transfer syntax which shall be used. Either we use the one which was passed, */ |
504 | | /* or (if it's an unknown transfer syntax) we use the one which is contained in OriginalXfer. */ |
505 | 0 | E_TransferSyntax newXfer = oxfer; |
506 | 0 | if (newXfer == EXS_Unknown) |
507 | 0 | newXfer = OriginalXfer; |
508 | | |
509 | | /* if this function was called for the first time for the dataset object, the transferState is still */ |
510 | | /* set to ERW_init. In this case, we need to take care of group length and padding elements according */ |
511 | | /* to the strategies which are specified in glenc and padenc. Additionally, we need to set the element */ |
512 | | /* list pointer of this data set to the fist element and we need to set the transfer state to ERW_inWork */ |
513 | | /* so that this scenario will only be executed once for this data set object. */ |
514 | 0 | if (getTransferState() == ERW_init) |
515 | 0 | { |
516 | | |
517 | | /* Check stream compression for this transfer syntax */ |
518 | 0 | DcmXfer xf(newXfer); |
519 | 0 | E_StreamCompression sc = xf.getStreamCompression(); |
520 | 0 | switch (sc) |
521 | 0 | { |
522 | 0 | case ESC_none: |
523 | | // nothing to do |
524 | 0 | break; |
525 | 0 | case ESC_unsupported: |
526 | | // stream compressed transfer syntax that we cannot create; bail out. |
527 | 0 | if (errorFlag.good()) |
528 | 0 | errorFlag = EC_UnsupportedEncoding; |
529 | 0 | break; |
530 | 0 | default: |
531 | | // supported stream compressed transfer syntax, install filter |
532 | 0 | errorFlag = outStream.installCompressionFilter(sc); |
533 | 0 | break; |
534 | 0 | } |
535 | | |
536 | | /* take care of group length and padding elements, according to what is specified in glenc and padenc */ |
537 | 0 | computeGroupLengthAndPadding(glenc, padenc, newXfer, enctype, padlen, subPadlen, instanceLength); |
538 | 0 | elementList->seek(ELP_first); |
539 | 0 | setTransferState(ERW_inWork); |
540 | 0 | } |
541 | | |
542 | | /* if the transfer state is set to ERW_inWork, we need to write the information which */ |
543 | | /* is included in this data set's element list into the buffer which was passed. */ |
544 | 0 | if (getTransferState() == ERW_inWork) |
545 | 0 | { |
546 | | // Remember that elementList->get() can be NULL if buffer was full after |
547 | | // writing the last item but before writing the sequence delimitation. |
548 | 0 | if (!elementList->empty() && (elementList->get() != NULL)) |
549 | 0 | { |
550 | | /* as long as everything is ok, go through all elements of this data */ |
551 | | /* set and write the corresponding information to the buffer */ |
552 | 0 | DcmObject *dO; |
553 | 0 | do |
554 | 0 | { |
555 | 0 | dO = elementList->get(); |
556 | 0 | errorFlag = dO->write(outStream, newXfer, enctype, wcache); |
557 | 0 | } while (errorFlag.good() && elementList->seek(ELP_next)); |
558 | 0 | } |
559 | | |
560 | | /* if all the information in this has been written to the */ |
561 | | /* buffer set this data set's transfer state to ERW_ready */ |
562 | 0 | if (errorFlag.good()) |
563 | 0 | { |
564 | 0 | setTransferState(ERW_ready); |
565 | 0 | CurrentXfer = newXfer; |
566 | 0 | } |
567 | 0 | } |
568 | 0 | } |
569 | 0 | } |
570 | | |
571 | | /* return the corresponding result value */ |
572 | 0 | return errorFlag; |
573 | 0 | } |
574 | | |
575 | | |
576 | | // ******************************** |
577 | | |
578 | | |
579 | | OFCondition DcmDataset::writeSignatureFormat(DcmOutputStream &outStream, |
580 | | const E_TransferSyntax oxfer, |
581 | | const E_EncodingType enctype, |
582 | | DcmWriteCache *wcache) |
583 | 0 | { |
584 | 0 | if (getTransferState() == ERW_notInitialized) |
585 | 0 | errorFlag = EC_IllegalCall; |
586 | 0 | else |
587 | 0 | { |
588 | 0 | E_TransferSyntax newXfer = oxfer; |
589 | 0 | if (newXfer == EXS_Unknown) |
590 | 0 | newXfer = OriginalXfer; |
591 | |
|
592 | 0 | errorFlag = outStream.status(); |
593 | 0 | if (errorFlag.good() && getTransferState() != ERW_ready) |
594 | 0 | { |
595 | 0 | if (getTransferState() == ERW_init) |
596 | 0 | { |
597 | 0 | computeGroupLengthAndPadding(EGL_recalcGL, EPD_noChange, newXfer, enctype, 0, 0, 0); |
598 | 0 | elementList->seek(ELP_first); |
599 | 0 | setTransferState(ERW_inWork); |
600 | 0 | } |
601 | 0 | if (getTransferState() == ERW_inWork) |
602 | 0 | { |
603 | | // elementList->get() can be NULL if buffer was full after |
604 | | // writing the last item but before writing the sequence delimitation. |
605 | 0 | if (!elementList->empty() && (elementList->get() != NULL)) |
606 | 0 | { |
607 | 0 | DcmObject *dO; |
608 | 0 | do { |
609 | 0 | dO = elementList->get(); |
610 | 0 | errorFlag = dO->writeSignatureFormat(outStream, newXfer, enctype, wcache); |
611 | 0 | } while (errorFlag.good() && elementList->seek(ELP_next)); |
612 | 0 | } |
613 | 0 | if (errorFlag.good()) |
614 | 0 | { |
615 | 0 | setTransferState(ERW_ready); |
616 | 0 | CurrentXfer = newXfer; |
617 | 0 | } |
618 | 0 | } |
619 | 0 | } |
620 | 0 | } |
621 | 0 | return errorFlag; |
622 | 0 | } |
623 | | |
624 | | |
625 | | // ******************************** |
626 | | |
627 | | |
628 | | OFCondition DcmDataset::loadFile(const OFFilename &fileName, |
629 | | const E_TransferSyntax readXfer, |
630 | | const E_GrpLenEncoding groupLength, |
631 | | const Uint32 maxReadLength) |
632 | 0 | { |
633 | 0 | return DcmDataset::loadFileUntilTag(fileName, readXfer, groupLength, maxReadLength, DCM_UndefinedTagKey); |
634 | 0 | } |
635 | | |
636 | | OFCondition DcmDataset::loadFileUntilTag(const OFFilename &fileName, |
637 | | const E_TransferSyntax readXfer, |
638 | | const E_GrpLenEncoding groupLength, |
639 | | const Uint32 maxReadLength, |
640 | | const DcmTagKey &stopParsingAtElement) |
641 | 0 | { |
642 | 0 | OFCondition l_error = EC_InvalidFilename; |
643 | | /* check parameters first */ |
644 | 0 | if (!fileName.isEmpty()) |
645 | 0 | { |
646 | 0 | if (fileName.isStandardStream()) |
647 | 0 | { |
648 | | /* use stdin stream */ |
649 | 0 | DcmStdinStream inStream; |
650 | | /* apply configured nesting depth limit */ |
651 | 0 | if (getMaxNestingDepth() > 0) |
652 | 0 | inStream.setMaxNestingDepth(getMaxNestingDepth()); |
653 | | |
654 | | /* clear this object */ |
655 | 0 | l_error = clear(); |
656 | 0 | if (l_error.good()) |
657 | 0 | { |
658 | | /* initialize transfer */ |
659 | 0 | transferInit(); |
660 | |
|
661 | 0 | do |
662 | 0 | { |
663 | | /* fill the buffer from stdin */ |
664 | 0 | inStream.fillBuffer(); |
665 | | /* and read the buffer content into the DICOM dataset */ |
666 | 0 | l_error = readUntilTag(inStream, readXfer, groupLength, maxReadLength, stopParsingAtElement); |
667 | 0 | } while (l_error == EC_StreamNotifyClient); /* repeat until we're at the end of the stream, or an error occurs */ |
668 | | |
669 | | /* end transfer */ |
670 | 0 | transferEnd(); |
671 | 0 | } |
672 | |
|
673 | 0 | } else { |
674 | | /* open file for input */ |
675 | 0 | DcmInputFileStream fileStream(fileName); |
676 | | /* apply configured nesting depth limit */ |
677 | 0 | if (getMaxNestingDepth() > 0) |
678 | 0 | fileStream.setMaxNestingDepth(getMaxNestingDepth()); |
679 | | |
680 | | /* check stream status */ |
681 | 0 | l_error = fileStream.status(); |
682 | |
|
683 | 0 | if (l_error.good()) |
684 | 0 | { |
685 | | /* clear this object */ |
686 | 0 | l_error = clear(); |
687 | 0 | if (l_error.good()) |
688 | 0 | { |
689 | | /* read data from file */ |
690 | 0 | transferInit(); |
691 | 0 | l_error = readUntilTag(fileStream, readXfer, groupLength, maxReadLength, stopParsingAtElement); |
692 | 0 | transferEnd(); |
693 | 0 | } |
694 | 0 | } |
695 | |
|
696 | 0 | } |
697 | 0 | } |
698 | 0 | return l_error; |
699 | 0 | } |
700 | | |
701 | | |
702 | | OFCondition DcmDataset::saveFile(const OFFilename &fileName, |
703 | | const E_TransferSyntax writeXfer, |
704 | | const E_EncodingType encodingType, |
705 | | const E_GrpLenEncoding groupLength, |
706 | | const E_PaddingEncoding padEncoding, |
707 | | const Uint32 padLength, |
708 | | const Uint32 subPadLength) |
709 | 0 | { |
710 | 0 | OFCondition l_error = EC_InvalidFilename; |
711 | | /* check parameters first */ |
712 | 0 | if (!fileName.isEmpty()) |
713 | 0 | { |
714 | 0 | DcmWriteCache wcache; |
715 | 0 | DcmOutputStream *outStream = NULL; |
716 | 0 | DcmOutputFileStream *fileStream = NULL; |
717 | |
|
718 | 0 | if (fileName.isStandardStream()) |
719 | 0 | { |
720 | | /* use stdout stream */ |
721 | 0 | outStream = new DcmStdoutStream(fileName); |
722 | 0 | } else { |
723 | | /* open file for output */ |
724 | 0 | fileStream = new DcmOutputFileStream(fileName); |
725 | 0 | outStream = fileStream; |
726 | 0 | } |
727 | | |
728 | | /* check stream status */ |
729 | 0 | l_error = outStream->status(); |
730 | 0 | if (l_error.good()) |
731 | 0 | { |
732 | | /* write data to file */ |
733 | 0 | transferInit(); |
734 | 0 | l_error = write(*outStream, writeXfer, encodingType, &wcache, groupLength, padEncoding, padLength, subPadLength); |
735 | 0 | transferEnd(); |
736 | 0 | } |
737 | 0 | if (l_error.good() && fileStream) l_error = fileStream->fclose(); |
738 | 0 | delete outStream; |
739 | 0 | } |
740 | 0 | return l_error; |
741 | 0 | } |
742 | | |
743 | | |
744 | | // ******************************** |
745 | | |
746 | | |
747 | | OFCondition DcmDataset::chooseRepresentation(const E_TransferSyntax repType, |
748 | | const DcmRepresentationParameter *repParam) |
749 | 0 | { |
750 | 0 | OFCondition l_error = EC_Normal; |
751 | 0 | OFBool pixelDataEncountered = OFFalse; |
752 | 0 | OFBool pixelURLEncountered = OFFalse; |
753 | 0 | OFStack<DcmStack> pixelStack; |
754 | 0 | DcmXfer torep(repType); |
755 | 0 | DcmXfer fromrep(CurrentXfer); |
756 | |
|
757 | 0 | DcmStack resultStack; |
758 | 0 | resultStack.push(this); |
759 | | |
760 | | // check if we are attempting to compress or decompress, but the image contains |
761 | | // floating point or double floating point pixel data, which our codecs don't |
762 | | // support |
763 | 0 | if ((tagExists(DCM_FloatPixelData, OFTrue) || tagExists(DCM_DoubleFloatPixelData, OFTrue)) && |
764 | 0 | (fromrep.isPixelDataCompressed() || torep.isPixelDataCompressed())) |
765 | 0 | { |
766 | 0 | DCMDATA_ERROR("DcmDataset: Unable to compress/decompress floating point pixel data, cannot change representation"); |
767 | 0 | return EC_CannotChangeRepresentation; |
768 | 0 | } |
769 | | |
770 | | // Check if we are attempting to convert a data set containing a pixel data |
771 | | // provider URL in the top-level data set (i.e., from "Image Pixel Module"). |
772 | | // In that case, we only continue if the target transfer syntax also requires |
773 | | // a pixel data provider URL. |
774 | 0 | if (tagExists(DCM_PixelDataProviderURL, OFFalse /*searchIntoSub*/)) |
775 | 0 | { |
776 | 0 | if (fromrep.usesReferencedPixelData()) |
777 | 0 | { |
778 | 0 | if (torep.usesReferencedPixelData()) |
779 | 0 | { |
780 | | // remember that we found a pixel data provider URL element |
781 | 0 | pixelURLEncountered = OFTrue; |
782 | 0 | } else { |
783 | 0 | DCMDATA_ERROR("DcmDataset: Unable to " << (torep.isPixelDataCompressed() ? "compress" : "decompress") |
784 | 0 | << " image containing a pixel data provider URL in the top-level data set, cannot change representation"); |
785 | 0 | return EC_CannotChangeRepresentation; |
786 | 0 | } |
787 | 0 | } else { |
788 | | // ignore the pixel data provider URL since the source transfer syntax |
789 | | // does not allow it (not one of the JPIP referenced transfer syntaxes) |
790 | 0 | DCMDATA_WARN("DcmDataset: Ignoring pixel data provider URL in a data set with transfer syntax \"" |
791 | 0 | << fromrep.getXferName() << "\""); |
792 | 0 | } |
793 | 0 | } |
794 | | |
795 | | // Now, search for all pixel data elements in this data set. If successful, |
796 | | // the 'resultStack' contains at least two pointers: one to the pixel data |
797 | | // element and one to the item or data set this element is contained in. |
798 | 0 | while (search(DCM_PixelData, resultStack, ESM_afterStackTop, OFTrue).good() && l_error.good()) |
799 | 0 | { |
800 | 0 | pixelDataEncountered = OFTrue; |
801 | 0 | if (resultStack.top()->ident() == EVR_PixelData) |
802 | 0 | { |
803 | | // if there are both pixel data and pixel data provider URL elements |
804 | | // in the top-level data set, return with an error |
805 | 0 | if (pixelURLEncountered && (resultStack.elem(1)->ident() == EVR_dataset)) |
806 | 0 | { |
807 | 0 | DCMDATA_ERROR("DcmDataset: Found both pixel data and pixel data provider URL in the top-level data set," |
808 | 0 | << " cannot change representation"); |
809 | 0 | l_error = EC_CannotChangeRepresentation; |
810 | 0 | } else { |
811 | | // check whether changing the encoding of the pixel data element would work |
812 | 0 | DcmPixelData *pixelData = OFstatic_cast(DcmPixelData *, resultStack.top()); |
813 | 0 | if (!pixelData->canChooseRepresentation(repType, repParam)) |
814 | 0 | l_error = EC_CannotChangeRepresentation; |
815 | 0 | pixelStack.push(resultStack); |
816 | 0 | } |
817 | 0 | } else { |
818 | | // something is fishy with the pixel data element (wrong class) |
819 | 0 | DCMDATA_ERROR("DcmDataset: Wrong class for pixel data element, cannot change representation"); |
820 | 0 | l_error = EC_CannotChangeRepresentation; |
821 | 0 | } |
822 | 0 | } |
823 | | |
824 | | // if there are no pixel data elements in the data set, issue a warning |
825 | 0 | if (!pixelDataEncountered) |
826 | 0 | { |
827 | 0 | if (torep.isPixelDataCompressed() && fromrep.isPixelDataUncompressed()) |
828 | 0 | { |
829 | 0 | DCMDATA_WARN("DcmDataset: No pixel data present, nothing to compress"); |
830 | 0 | } |
831 | 0 | if (torep.isPixelDataUncompressed() && fromrep.isPixelDataCompressed()) |
832 | 0 | { |
833 | 0 | DCMDATA_WARN("DcmDataset: No pixel data present, nothing to decompress"); |
834 | 0 | } |
835 | 0 | } |
836 | | |
837 | | // then call the method doing the real work for all pixel data elements found |
838 | 0 | while (l_error.good() && (pixelStack.size() > 0)) |
839 | 0 | { |
840 | 0 | l_error = OFstatic_cast(DcmPixelData *, pixelStack.top().top())-> |
841 | 0 | chooseRepresentation(repType, repParam, pixelStack.top()); |
842 | |
|
843 | | #ifdef PIXELSTACK_MEMORY_LEAK_WORKAROUND |
844 | | // On certain platforms there seems to be a memory leak at this point |
845 | | // since for some reason pixelStack.pop() does not completely destruct |
846 | | // the DcmStack object taken from the stack. |
847 | | // The following work-around should solve this issue. |
848 | | pixelStack.top().clear(); |
849 | | #endif |
850 | |
|
851 | 0 | pixelStack.pop(); |
852 | 0 | } |
853 | | |
854 | | // store current transfer syntax (if conversion was successful) |
855 | 0 | if (l_error.good()) |
856 | 0 | CurrentXfer = repType; |
857 | 0 | return l_error; |
858 | 0 | } |
859 | | |
860 | | |
861 | | OFBool DcmDataset::hasRepresentation(const E_TransferSyntax repType, |
862 | | const DcmRepresentationParameter *repParam) |
863 | 0 | { |
864 | 0 | OFBool result = OFTrue; |
865 | 0 | DcmStack resultStack; |
866 | |
|
867 | 0 | while(search(DCM_PixelData, resultStack, ESM_afterStackTop, OFTrue).good() && result) |
868 | 0 | { |
869 | 0 | if (resultStack.top()->ident() == EVR_PixelData) |
870 | 0 | { |
871 | 0 | DcmPixelData *pixelData = OFstatic_cast(DcmPixelData *, resultStack.top()); |
872 | 0 | result = pixelData->hasRepresentation(repType, repParam); |
873 | 0 | } |
874 | 0 | else |
875 | 0 | result = OFFalse; |
876 | 0 | } |
877 | 0 | return result; |
878 | 0 | } |
879 | | |
880 | | |
881 | | void DcmDataset::removeAllButCurrentRepresentations() |
882 | 0 | { |
883 | 0 | DcmStack resultStack; |
884 | |
|
885 | 0 | while(search(DCM_PixelData, resultStack, ESM_afterStackTop, OFTrue).good()) |
886 | 0 | { |
887 | 0 | if (resultStack.top()->ident() == EVR_PixelData) |
888 | 0 | { |
889 | 0 | DcmPixelData *pixelData = OFstatic_cast(DcmPixelData *, resultStack.top()); |
890 | 0 | pixelData->removeAllButCurrentRepresentations(); |
891 | 0 | } |
892 | 0 | } |
893 | 0 | } |
894 | | |
895 | | |
896 | | void DcmDataset::removeAllButOriginalRepresentations() |
897 | 0 | { |
898 | 0 | DcmStack resultStack; |
899 | |
|
900 | 0 | while(search(DCM_PixelData, resultStack, ESM_afterStackTop, OFTrue).good()) |
901 | 0 | { |
902 | 0 | if (resultStack.top()->ident() == EVR_PixelData) |
903 | 0 | { |
904 | 0 | DcmPixelData *pixelData = OFstatic_cast(DcmPixelData *, resultStack.top()); |
905 | 0 | pixelData->removeAllButOriginalRepresentations(); |
906 | 0 | } |
907 | 0 | } |
908 | 0 | } |
909 | | |
910 | | |
911 | | // ******************************** |
912 | | |
913 | | |
914 | | OFCondition DcmDataset::doPostReadChecks() |
915 | 0 | { |
916 | 0 | DcmElement* pixData = NULL; |
917 | 0 | DcmXfer xf(OriginalXfer); |
918 | 0 | OFCondition result = EC_Normal; |
919 | 0 | if (findAndGetElement(DCM_PixelData, pixData).good()) |
920 | 0 | { |
921 | 0 | Uint32 valueLength = pixData->getLengthField(); |
922 | 0 | if (xf.usesEncapsulatedFormat()) |
923 | 0 | { |
924 | 0 | if (valueLength != DCM_UndefinedLength) |
925 | 0 | { |
926 | 0 | if (dcmUseExplLengthPixDataForEncTS.get() == OFFalse /* default case */) |
927 | 0 | { |
928 | | /* length of top-level dataset's Pixel Data is explicitly */ |
929 | | /* defined but we have a transfer syntax requiring */ |
930 | | /* encapsulated pixel data (always encoded with undefined */ |
931 | | /* length). Print and return an error. */ |
932 | 0 | DCMDATA_ERROR("Found explicit length Pixel Data in top-level " |
933 | 0 | << "data set with transfer syntax " << xf.getXferName() |
934 | 0 | << ": Only undefined length permitted"); |
935 | 0 | result = EC_PixelDataExplLengthIllegal; |
936 | 0 | } |
937 | 0 | else |
938 | 0 | { |
939 | | /* Only print warning if requested by related OFGlobal, */ |
940 | | /* and behave like as we have the same case as for an */ |
941 | | /* icon image, which is always uncompressed (see above). */ |
942 | 0 | DCMDATA_WARN("Found explicit length Pixel Data in top-level " |
943 | 0 | << "data set with transfer syntax " << xf.getXferName() |
944 | 0 | << ": Only undefined length permitted (ignored on explicit request)"); |
945 | 0 | } |
946 | 0 | } |
947 | 0 | } |
948 | 0 | } |
949 | |
|
950 | 0 | return result; |
951 | 0 | } |
952 | | |
953 | | // ******************************** |
954 | | |
955 | | void DcmDataset::initializeXfer(const E_TransferSyntax xfer) |
956 | 0 | { |
957 | 0 | OriginalXfer = xfer; |
958 | 0 | CurrentXfer = xfer; |
959 | 0 | } |