1 /*
2 *
3 * Copyright (C) 2001-2020, 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: dcmjpeg
15 *
16 * Author: Marco Eichelberg, Norbert Olges
17 *
18 * Purpose: abstract codec class for JPEG encoders.
19 *
20 */
21
22 #include "dcmtk/config/osconfig.h"
23 #include "dcmtk/dcmjpeg/djcodece.h"
24
25 // ofstd includes
26 #include "dcmtk/ofstd/oflist.h"
27 #include "dcmtk/ofstd/ofstd.h"
28
29 // dcmdata includes
30 #include "dcmtk/dcmdata/dcdatset.h" /* for class DcmDataset */
31 #include "dcmtk/dcmdata/dcdeftag.h" /* for tag constants */
32 #include "dcmtk/dcmdata/dcovlay.h" /* for class DcmOverlayData */
33 #include "dcmtk/dcmdata/dcpixseq.h" /* for class DcmPixelSequence */
34 #include "dcmtk/dcmdata/dcpxitem.h" /* for class DcmPixelItem */
35 #include "dcmtk/dcmdata/dcuid.h" /* for dcmGenerateUniqueIdentifer()*/
36 #include "dcmtk/dcmdata/dcvrcs.h" /* for class DcmCodeString */
37 #include "dcmtk/dcmdata/dcvrds.h" /* for class DcmDecimalString */
38 #include "dcmtk/dcmdata/dcvrlt.h" /* for class DcmLongText */
39 #include "dcmtk/dcmdata/dcvrst.h" /* for class DcmShortText */
40 #include "dcmtk/dcmdata/dcvrus.h" /* for class DcmUnsignedShort */
41 #include "dcmtk/dcmdata/dcswap.h" /* for swapIfNecessary */
42
43 // dcmjpeg includes
44 #include "dcmtk/dcmjpeg/djcparam.h" /* for class DJCodecParameter */
45 #include "dcmtk/dcmjpeg/djencabs.h" /* for class DJEncoder */
46
47 // dcmimgle includes
48 #include "dcmtk/dcmimgle/dcmimage.h" /* for class DicomImage */
49
50 #define INCLUDE_CMATH
51 #include "dcmtk/ofstd/ofstdinc.h"
52
53
DJCodecEncoder()54 DJCodecEncoder::DJCodecEncoder()
55 : DcmCodec()
56 {
57 }
58
59
~DJCodecEncoder()60 DJCodecEncoder::~DJCodecEncoder()
61 {
62 }
63
64
canChangeCoding(const E_TransferSyntax oldRepType,const E_TransferSyntax newRepType) const65 OFBool DJCodecEncoder::canChangeCoding(
66 const E_TransferSyntax oldRepType,
67 const E_TransferSyntax newRepType) const
68 {
69 E_TransferSyntax myXfer = supportedTransferSyntax();
70 DcmXfer newRep(newRepType);
71 DcmXfer oldRep(oldRepType);
72 if (oldRep.isNotEncapsulated() && (newRepType == myXfer)) return OFTrue; // compress requested
73
74 // we don't support re-coding for now
75 return OFFalse;
76 }
77
78
decode(const DcmRepresentationParameter *,DcmPixelSequence *,DcmPolymorphOBOW &,const DcmCodecParameter *,const DcmStack &,OFBool &) const79 OFCondition DJCodecEncoder::decode(
80 const DcmRepresentationParameter * /* fromRepParam */,
81 DcmPixelSequence * /* pixSeq */,
82 DcmPolymorphOBOW& /* uncompressedPixelData */,
83 const DcmCodecParameter * /* cp */,
84 const DcmStack& /* objStack */,
85 OFBool& /* removeOldRep */ ) const
86 {
87 // we are an encoder only
88 return EC_IllegalCall;
89 }
90
91
decodeFrame(const DcmRepresentationParameter *,DcmPixelSequence *,const DcmCodecParameter *,DcmItem *,Uint32,Uint32 &,void *,Uint32,OFString &) const92 OFCondition DJCodecEncoder::decodeFrame(
93 const DcmRepresentationParameter * /* fromParam */ ,
94 DcmPixelSequence * /* fromPixSeq */ ,
95 const DcmCodecParameter * /* cp */ ,
96 DcmItem * /* dataset */ ,
97 Uint32 /* frameNo */ ,
98 Uint32& /* startFragment */ ,
99 void * /* buffer */ ,
100 Uint32 /* bufSize */ ,
101 OFString& /* decompressedColorModel */ ) const
102 {
103 // we are an encoder only
104 return EC_IllegalCall;
105 }
106
107
encode(const E_TransferSyntax,const DcmRepresentationParameter *,DcmPixelSequence *,const DcmRepresentationParameter *,DcmPixelSequence * &,const DcmCodecParameter *,DcmStack &,OFBool &) const108 OFCondition DJCodecEncoder::encode(
109 const E_TransferSyntax /* fromRepType */,
110 const DcmRepresentationParameter * /* fromRepParam */,
111 DcmPixelSequence * /* fromPixSeq */,
112 const DcmRepresentationParameter * /* toRepParam */,
113 DcmPixelSequence * & /* toPixSeq */,
114 const DcmCodecParameter * /* cp */,
115 DcmStack & /* objStack */,
116 OFBool& /* removeOldRep */) const
117 {
118 // we don't support re-coding for now
119 return EC_IllegalCall;
120 }
121
122
encode(const Uint16 *,const Uint32,const DcmRepresentationParameter * toRepParam,DcmPixelSequence * & pixSeq,const DcmCodecParameter * cp,DcmStack & objStack,OFBool & removeOldRep) const123 OFCondition DJCodecEncoder::encode(
124 const Uint16 * /* pixelData */,
125 const Uint32 /* length */,
126 const DcmRepresentationParameter * toRepParam,
127 DcmPixelSequence * & pixSeq,
128 const DcmCodecParameter *cp,
129 DcmStack & objStack,
130 OFBool& removeOldRep) const
131 {
132 OFCondition result = EC_Normal;
133
134 // this codec may modify the DICOM header such that the previous pixel
135 // representation is not valid anymore. Indicate this to the caller
136 // to trigger removal.
137 removeOldRep = OFTrue;
138
139 // assume we can cast the codec parameter to what we need
140 const DJCodecParameter *djcp = OFreinterpret_cast(const DJCodecParameter*, cp);
141
142 // if true lossless mode is enabled, and we're supposed to do lossless compression,
143 // call the "true lossless encoding"-engine
144 if (isLosslessProcess() && (djcp->getTrueLosslessMode()))
145 return encodeTrueLossless(toRepParam, pixSeq, cp, objStack);
146
147 DcmStack localStack(objStack);
148 (void)localStack.pop(); // pop pixel data element from stack
149 DcmObject *dataset = localStack.pop(); // this is the item in which the pixel data is located
150
151 if ((!dataset)||((dataset->ident()!= EVR_dataset) && (dataset->ident()!= EVR_item))) result = EC_InvalidTag;
152 else
153 {
154 double compressionRatio = 0.0;
155 EP_Interpretation photometricInterpretation = DcmJpegHelper::getPhotometricInterpretation(OFreinterpret_cast(DcmItem*, dataset));
156 switch (photometricInterpretation)
157 {
158 case EPI_Monochrome1:
159 case EPI_Monochrome2:
160 // monochrome image
161 result = encodeMonochromeImage(OFreinterpret_cast(DcmItem*, dataset), toRepParam, pixSeq, djcp, compressionRatio);
162 break;
163 case EPI_PaletteColor:
164 case EPI_RGB:
165 case EPI_HSV:
166 case EPI_ARGB:
167 case EPI_CMYK:
168 case EPI_YBR_Partial_422:
169 // color image except YCbCr which receives special treatment
170 result = encodeColorImage(OFFalse, OFreinterpret_cast(DcmItem*, dataset), toRepParam, pixSeq, djcp, compressionRatio);
171 break;
172 case EPI_YBR_Full:
173 case EPI_YBR_Full_422:
174 // YCbCr color image
175 result = encodeColorImage(OFTrue, OFreinterpret_cast(DcmItem*, dataset), toRepParam, pixSeq, djcp, compressionRatio);
176 break;
177 case EPI_Unknown:
178 // unknown color model - bail out
179 result = EJ_UnsupportedPhotometricInterpretation;
180 break;
181 case EPI_Missing:
182 // photometric interpretation missing. If ACR-NEMA compatibility is activated, we treat this as MONOCHOME2, otherwise we report an error
183 if (djcp->getAcrNemaCompatibility())
184 result = encodeMonochromeImage(OFreinterpret_cast(DcmItem*, dataset), toRepParam, pixSeq, djcp, compressionRatio);
185 else result = EJ_UnsupportedPhotometricInterpretation;
186 break;
187 }
188
189 // the following operations do not affect the Image Pixel Module
190 // but other modules such as SOP Common. We only perform these
191 // changes if we're on the main level of the dataset,
192 // which should always identify itself as dataset, not as item.
193 if (dataset->ident() == EVR_dataset)
194 {
195
196 // update image type
197 if (result.good()) result = DcmCodec::updateImageType(OFreinterpret_cast(DcmItem*, dataset));
198
199 // determine compressed bit depth passed to JPEG codec
200 Uint16 compressedBits = OFstatic_cast(Uint16, djcp->getForcedBitDepth());
201 if (result.good())
202 {
203 if (compressedBits == 0)
204 {
205 result = OFreinterpret_cast(DcmItem*, dataset)->findAndGetUint16(DCM_BitsStored, compressedBits);
206 }
207 }
208
209 // update derivation description
210 if (result.good()) result = updateDerivationDescription(OFreinterpret_cast(DcmItem*, dataset), toRepParam,
211 djcp, OFstatic_cast(Uint8, compressedBits), compressionRatio);
212
213 if (result.good())
214 {
215 // lossy process - create new UID unless mode is EUC_never and we're not converting to Secondary Capture
216 // (pseudo-lossless mode may also result in lossy compression, so treat it the same way)
217 if (djcp->getConvertToSC() || (djcp->getUIDCreation() != EUC_never))
218 result = DcmCodec::newInstance(OFreinterpret_cast(DcmItem*, dataset), "DCM", "121320", "Uncompressed predecessor");
219
220 // update lossy compression ratio
221 if (result.good()) result = updateLossyCompressionRatio(OFreinterpret_cast(DcmItem*, dataset), compressionRatio);
222 }
223
224 // convert to Secondary Capture if requested by user.
225 // This method creates a new SOP class UID, so it should be executed
226 // after the call to newInstance() which creates a Source Image Sequence.
227 if (result.good() && djcp->getConvertToSC()) result = DcmCodec::convertToSecondaryCapture(OFreinterpret_cast(DcmItem*, dataset));
228 }
229 }
230 return result;
231 }
232
233
determineDecompressedColorModel(const DcmRepresentationParameter *,DcmPixelSequence *,const DcmCodecParameter *,DcmItem *,OFString &) const234 OFCondition DJCodecEncoder::determineDecompressedColorModel(
235 const DcmRepresentationParameter * /* fromParam */,
236 DcmPixelSequence * /* fromPixSeq */,
237 const DcmCodecParameter * /* cp */,
238 DcmItem * /* dataset */,
239 OFString & /* decompressedColorModel */) const
240 {
241 return EC_IllegalCall;
242 }
243
244
encodeColorImage(OFBool YBRmode,DcmItem * dataset,const DcmRepresentationParameter * toRepParam,DcmPixelSequence * & pixSeq,const DJCodecParameter * cp,double & compressionRatio) const245 OFCondition DJCodecEncoder::encodeColorImage(
246 OFBool YBRmode,
247 DcmItem *dataset,
248 const DcmRepresentationParameter * toRepParam,
249 DcmPixelSequence * & pixSeq,
250 const DJCodecParameter *cp,
251 double& compressionRatio) const
252 {
253 OFCondition result = EC_Normal;
254 DcmOffsetList offsetList;
255 DcmPixelSequence *pixelSequence = NULL;
256 DcmPixelItem *offsetTable = NULL;
257 unsigned short bitsPerSample = 0;
258 compressionRatio = 0.0; // initialize if something goes wrong
259 size_t compressedSize = 0;
260 double uncompressedSize = 0.0;
261 Uint16 compressedBits = OFstatic_cast(Uint16, cp->getForcedBitDepth());
262
263 // Check if image is continuous-tone, bail out otherwise.
264 // We check the value of BitsStored, which is not affected by any transformation such as MLUT.
265 Uint16 bitsStored = 0;
266 result = dataset->findAndGetUint16(DCM_BitsStored, bitsStored);
267 if (result.bad()) return result;
268
269 if ((bitsStored > 16) && isLosslessProcess())
270 {
271 DCMJPEG_WARN("Cannot lossless compress image with " << bitsStored << " bits/sample: JPEG supports max. 16 bits.");
272 return EJ_UnsupportedBitDepth;
273 }
274
275 if (bitsStored < 2)
276 {
277 DCMJPEG_WARN("Cannot compress image with " << bitsStored << " bit/sample: JPEG requires at least 2 bits.");
278 return EJ_UnsupportedBitDepth;
279 }
280
281 // initialize settings with defaults for RGB mode
282 OFBool monochromeMode = OFFalse;
283 unsigned long flags = 0; // flags for initialization of DicomImage
284 EP_Interpretation interpr = EPI_RGB;
285 Uint16 samplesPerPixel = 3;
286 const char *photometricInterpretation = "RGB";
287 if ((cp->getCompressionColorSpaceConversion() == ECC_lossyYCbCr) && (!isLosslessProcess()))
288 {
289 if (cp->getWriteYBR422()) photometricInterpretation = "YBR_FULL_422";
290 else photometricInterpretation = "YBR_FULL";
291 }
292
293 // check mode and adjust settings
294 if (cp->getCompressionColorSpaceConversion() == ECC_monochrome)
295 {
296 monochromeMode = OFTrue;
297 flags = 0;
298 interpr = EPI_Monochrome2;
299 samplesPerPixel = 1;
300 photometricInterpretation = "MONOCHROME2";
301 }
302 else if (YBRmode)
303 {
304 monochromeMode = OFFalse;
305 flags = CIF_KeepYCbCrColorModel; // keep YCbCr color model
306 interpr = EPI_YBR_Full;
307 if (cp->getWriteYBR422()) photometricInterpretation = "YBR_FULL_422";
308 else photometricInterpretation = "YBR_FULL";
309 }
310
311 // integrate DicomImage flags transported by DJCodecParameter into "flags"-variable
312 if (cp->getAcceptWrongPaletteTags())
313 flags |= CIF_WrongPaletteAttributeTags;
314 if (cp->getAcrNemaCompatibility())
315 flags |= CIF_AcrNemaCompatibility;
316
317 // create dcmimage object. Will fail if dcmimage has not been activated in main().
318 // transfer syntax can be any uncompressed one.
319 DicomImage *dimage = NULL;
320 if (monochromeMode)
321 {
322 DicomImage colorimage(dataset, EXS_LittleEndianImplicit, flags); // read all frames
323 if (colorimage.getStatus() == EIS_Normal) dimage = colorimage.createMonochromeImage();
324 if (dimage) dimage->setNoVoiTransformation();
325 }
326 else
327 {
328 dimage = new DicomImage(dataset, EXS_LittleEndianImplicit, flags); // read all frames
329 }
330
331 if (dimage == NULL) result = EC_MemoryExhausted;
332 else if (dimage->getStatus() != EIS_Normal)
333 {
334 DCMJPEG_WARN("Color encoder: " << DicomImage::getString(dimage->getStatus()));
335 result = EC_IllegalCall;
336 }
337
338 // don't render overlays
339 if (result.good())
340 {
341 dimage->hideAllOverlays();
342 }
343
344 // create initial pixel sequence
345 if (result.good())
346 {
347 pixelSequence = new DcmPixelSequence(DCM_PixelSequenceTag);
348 if (pixelSequence == NULL) result = EC_MemoryExhausted;
349 else
350 {
351 // create empty offset table
352 offsetTable = new DcmPixelItem(DCM_PixelItemTag);
353 if (offsetTable == NULL) result = EC_MemoryExhausted;
354 else pixelSequence->insert(offsetTable);
355 }
356 }
357
358 // select bit depth
359 if (result.good())
360 {
361 if (compressedBits == 0)
362 {
363 result = OFreinterpret_cast(DcmItem*, dataset)->findAndGetUint16(DCM_BitsStored, compressedBits);
364 }
365 }
366
367 // create codec instance
368 if (result.good())
369 {
370 DJEncoder *jpeg = createEncoderInstance(toRepParam, cp, OFstatic_cast(Uint8, compressedBits));
371 if (jpeg)
372 {
373 // render and compress each frame
374 bitsPerSample = jpeg->bitsPerSample();
375 size_t frameCount = dimage->getFrameCount();
376 unsigned short bytesPerSample = jpeg->bytesPerSample();
377 unsigned short columns = OFstatic_cast(unsigned short, dimage->getWidth());
378 unsigned short rows = OFstatic_cast(unsigned short, dimage->getHeight());
379 Uint8 *jpegData = NULL;
380 Uint32 jpegLen = 0;
381 const void *frame = NULL;
382
383 // compute original image size in bytes, ignoring any padding bits.
384 uncompressedSize = OFstatic_cast(double, columns * rows * dimage->getDepth() * frameCount * samplesPerPixel) / 8.0;
385 for (unsigned long i=0; (i<frameCount) && (result.good()); i++)
386 {
387 frame = dimage->getOutputData(bitsPerSample, i, 0);
388 if (frame == NULL) result = EC_MemoryExhausted;
389 else
390 {
391 // compress frame
392 jpegData = NULL;
393 if (bytesPerSample == 1)
394 {
395 result = jpeg->encode(columns, rows, interpr, samplesPerPixel, OFreinterpret_cast(Uint8*, OFconst_cast(void*, frame)), jpegData, jpegLen);
396 } else {
397 result = jpeg->encode(columns, rows, interpr, samplesPerPixel, OFreinterpret_cast(Uint16*, OFconst_cast(void*, frame)), jpegData, jpegLen);
398 }
399
400 // store frame
401 if (result.good())
402 {
403 result = pixelSequence->storeCompressedFrame(offsetList, jpegData, jpegLen, cp->getFragmentSize());
404 }
405
406 // delete block of JPEG data
407 delete[] jpegData;
408 compressedSize += jpegLen;
409 }
410 }
411 delete jpeg;
412 } else result = EC_MemoryExhausted;
413 }
414
415 // store pixel sequence if everything was successful
416 if (result.good()) pixSeq = pixelSequence;
417 else
418 {
419 delete pixelSequence;
420 pixSeq = NULL;
421 }
422
423 if ((result.good()) && (cp->getCreateOffsetTable()))
424 {
425 // create offset table
426 result = offsetTable->createOffsetTable(offsetList);
427 }
428
429 if (result.good())
430 {
431 // adapt attributes in image pixel module
432 if (result.good()) result = dataset->putAndInsertUint16(DCM_SamplesPerPixel, samplesPerPixel);
433 if (result.good()) result = dataset->putAndInsertString(DCM_PhotometricInterpretation, photometricInterpretation);
434 if (result.good())
435 {
436 if (bitsPerSample > 8)
437 result = dataset->putAndInsertUint16(DCM_BitsAllocated, 16);
438 else
439 result = dataset->putAndInsertUint16(DCM_BitsAllocated, 8);
440 }
441 if (result.good()) result = dataset->putAndInsertUint16(DCM_BitsStored, bitsPerSample);
442 if (result.good()) result = dataset->putAndInsertUint16(DCM_HighBit, OFstatic_cast(Uint16, bitsPerSample-1));
443 if (result.good()) result = dataset->putAndInsertUint16(DCM_PixelRepresentation, 0);
444 if (result.good())
445 {
446 if (monochromeMode) delete dataset->remove(DCM_PlanarConfiguration);
447 else result = dataset->putAndInsertUint16(DCM_PlanarConfiguration, 0);
448 }
449 delete dataset->remove(DCM_SmallestImagePixelValue);
450 delete dataset->remove(DCM_LargestImagePixelValue);
451 delete dataset->remove(DCM_RedPaletteColorLookupTableDescriptor);
452 delete dataset->remove(DCM_GreenPaletteColorLookupTableDescriptor);
453 delete dataset->remove(DCM_BluePaletteColorLookupTableDescriptor);
454 delete dataset->remove(DCM_RedPaletteColorLookupTableData);
455 delete dataset->remove(DCM_GreenPaletteColorLookupTableData);
456 delete dataset->remove(DCM_BluePaletteColorLookupTableData);
457 delete dataset->remove(DCM_PixelPaddingValue);
458 delete dataset->remove(DCM_PixelPaddingRangeLimit);
459 delete dataset->remove(DCM_SmallestPixelValueInSeries);
460 delete dataset->remove(DCM_LargestPixelValueInSeries);
461 delete dataset->remove(DCM_PaletteColorLookupTableUID);
462 delete dataset->remove(DCM_SegmentedRedPaletteColorLookupTableData);
463 delete dataset->remove(DCM_SegmentedGreenPaletteColorLookupTableData);
464 delete dataset->remove(DCM_SegmentedBluePaletteColorLookupTableData);
465 }
466 if (compressedSize > 0) compressionRatio = OFstatic_cast(double, uncompressedSize) / OFstatic_cast(double, compressedSize);
467
468 delete dimage;
469 return result;
470 }
471
472
encodeTrueLossless(const DcmRepresentationParameter * toRepParam,DcmPixelSequence * & pixSeq,const DcmCodecParameter * cp,DcmStack & objStack) const473 OFCondition DJCodecEncoder::encodeTrueLossless(
474 const DcmRepresentationParameter * toRepParam,
475 DcmPixelSequence * & pixSeq,
476 const DcmCodecParameter *cp,
477 DcmStack & objStack) const
478 {
479 OFCondition result = EC_Normal;
480 // assume we can cast the codec parameter to what we need
481 DJCodecParameter *djcp = OFreinterpret_cast(DJCodecParameter*, OFconst_cast(DcmCodecParameter*, cp));
482 // get dataset from stack
483 DcmStack localStack(objStack);
484 (void)localStack.pop();
485 DcmObject *dataset = localStack.pop();
486
487 // check whether dataset was on top of the stack
488 if ((!dataset)||((dataset->ident()!= EVR_dataset) && (dataset->ident()!= EVR_item)))
489 return EC_InvalidTag;
490 else
491 {
492 DcmItem *datsetItem = OFreinterpret_cast(DcmItem*, dataset);
493 double compressionRatio = 0.0;
494 const Uint16* pixelData;
495 size_t length = 0;
496 Uint16 bitsAllocated = 0;
497 Uint16 bitsStored = 0;
498 Uint16 bytesAllocated = 0;
499 Uint16 samplesPerPixel = 0;
500 Uint16 planarConfiguration = 0;
501 Uint16 columns = 0;
502 Uint16 rows = 0;
503 Sint32 numberOfFrames = 1;
504 EP_Interpretation interpr = EPI_Unknown;
505 Uint8 *jpegData = NULL;
506 Uint32 jpegLen = 0;
507 OFBool byteSwapped = OFFalse; // true if we have byte-swapped the original pixel data
508 OFBool planConfSwitched = OFFalse; // true if planar configuration was toggled
509 DcmOffsetList offsetList;
510 OFString photometricInterpretation;
511 DcmElement *dummyElem;
512
513 // get relevant attributes for encoding from dataset
514 result = datsetItem->findAndGetUint16(DCM_BitsStored, bitsStored);
515 if (result.good()) result = datsetItem->findAndGetUint16(DCM_BitsAllocated, bitsAllocated);
516 if (result.good()) result = datsetItem->findAndGetUint16(DCM_SamplesPerPixel, samplesPerPixel);
517 if (result.good()) result = datsetItem->findAndGetUint16(DCM_Columns, columns);
518 if (result.good()) result = datsetItem->findAndGetUint16(DCM_Rows, rows);
519 if (result.good()) result = datsetItem->findAndGetOFString(DCM_PhotometricInterpretation, photometricInterpretation);
520 if (result.good()) result = datsetItem->findAndGetUint16Array(DCM_PixelData, pixelData, NULL, OFFalse);
521 if (result.good()) result = datsetItem->findAndGetElement(DCM_PixelData, dummyElem);
522 if (result.good()) length = dummyElem->getLength();
523 if (result.good())
524 {
525 result = datsetItem->findAndGetSint32(DCM_NumberOfFrames, numberOfFrames);
526 if (result.bad() || numberOfFrames < 1) numberOfFrames = 1;
527 result = EC_Normal;
528 }
529 if (result.bad())
530 {
531 DCMJPEG_ERROR("True lossless encoder: Unable to get relevant attributes from dataset");
532 return result;
533 }
534
535 // check, whether bit depth is supported
536 if (bitsAllocated == 8)
537 bytesAllocated = 1;
538 else if (bitsAllocated == 16)
539 bytesAllocated = 2;
540 else
541 {
542 DCMJPEG_ERROR("True lossless encoder: Only 8 or 16 bits allocated supported");
543 return EC_IllegalParameter;
544 }
545
546 // make sure that all the descriptive attributes have sensible values
547 if ((columns < 1)||(rows < 1)||(samplesPerPixel < 1))
548 {
549 DCMJPEG_ERROR("True lossless encoder: Invalid attribute values in pixel module");
550 return EC_CannotChangeRepresentation;
551 }
552
553 /* Set and check photometric interpretation (up to now: EPI_RGB)
554 * Only photometric interpretations, that are explicitly "supported" by the
555 * IJG lib are set. For all others "unknown" is set. Some are even rejected here.
556 */
557 if (photometricInterpretation == "MONOCHROME1")
558 interpr = EPI_Monochrome1;
559 else if (photometricInterpretation == "MONOCHROME2")
560 interpr = EPI_Monochrome2;
561 else if (photometricInterpretation == "YBR_FULL")
562 interpr = EPI_YBR_Full;
563 // some photometric interpretations are not supported:
564 else if ( (photometricInterpretation == "YBR_FULL_422") ||
565 (photometricInterpretation == "YBR_PARTIAL_422") ||
566 (photometricInterpretation == "YBR_PARTIAL_420") ||
567 (photometricInterpretation == "YBR_ICT") ||
568 (photometricInterpretation == "YBR_RCT") )
569 {
570 DCMJPEG_ERROR("True lossless encoder: Photometric interpretation not supported: " << photometricInterpretation);
571 return EC_IllegalParameter;
572 }
573 else // Palette, HSV, ARGB, CMYK
574 interpr = EPI_Unknown;
575
576 // IJG libs need "color by pixel", transform if required
577 if (result.good() && (samplesPerPixel > 1) )
578 {
579 result = datsetItem->findAndGetUint16(DCM_PlanarConfiguration, planarConfiguration);
580 if ( result.good() && (planarConfiguration == 1) )
581 {
582 if (bytesAllocated == 1)
583 result = togglePlanarConfiguration8(OFreinterpret_cast(Uint8*, OFconst_cast(Uint16*, pixelData)), length, samplesPerPixel, OFstatic_cast(Uint16, 1) /* switch to "by pixel"*/);
584 else
585 result = togglePlanarConfiguration16(OFconst_cast(Uint16*, pixelData), length/2 /*16 bit*/, samplesPerPixel, OFstatic_cast(Uint16, 1) /* switch to "by pixel"*/);
586 planConfSwitched = OFTrue;
587 }
588 }
589 if (result.bad())
590 {
591 DCMJPEG_ERROR("True lossless encoder: Unable to change Planar Configuration from 'by plane' to 'by pixel' for encoding");
592 return result;
593 }
594
595 // check whether enough raw data is available for encoding
596 if (bytesAllocated * samplesPerPixel * columns * rows * OFstatic_cast(size_t,numberOfFrames) > length)
597 {
598 DCMJPEG_ERROR("True lossless encoder: Cannot change representation, not enough data");
599 return EC_CannotChangeRepresentation;
600 }
601
602 // byte swap pixel data to little endian if bits allocated is 8
603 if ((gLocalByteOrder == EBO_BigEndian) && (bitsAllocated == 8))
604 {
605 result = swapIfNecessary(EBO_LittleEndian, gLocalByteOrder, OFconst_cast(Uint16*, pixelData), OFstatic_cast(Uint32, length), sizeof(Uint16));
606 if ( result.bad() )
607 {
608 DCMJPEG_ERROR("True lossless encoder: Unable to swap bytes to respect local byte ordering");
609 return EC_CannotChangeRepresentation;
610 }
611 byteSwapped = OFTrue;
612 }
613
614 // create initial pixel sequence with empty offset table
615 DcmPixelSequence *pixelSequence = NULL;
616 DcmPixelItem *offsetTable = NULL;
617 if (result.good())
618 {
619 pixelSequence = new DcmPixelSequence(DCM_PixelSequenceTag);
620 if (pixelSequence == NULL) result = EC_MemoryExhausted;
621 else
622 {
623 // create empty offset table
624 offsetTable = new DcmPixelItem(DCM_PixelItemTag);
625 if (offsetTable == NULL) result = EC_MemoryExhausted;
626 else pixelSequence->insert(offsetTable);
627 }
628 }
629
630 // prepare some variables for encoding
631 size_t frameCount = OFstatic_cast(size_t, numberOfFrames);
632 size_t frameSize = columns * rows * samplesPerPixel * bytesAllocated;
633 const Uint8 *framePointer = OFreinterpret_cast(const Uint8 *, pixelData);
634 size_t compressedSize = 0;
635
636 // create encoder corresponding to bit depth (8 or 16 bit)
637 DJEncoder *jpeg = createEncoderInstance(toRepParam, djcp, OFstatic_cast(Uint8, bitsAllocated));
638 if (jpeg)
639 {
640 // main loop for compression: compress each frame
641 for (unsigned int i=0; i<frameCount && result.good(); i++)
642 {
643 if (bitsAllocated == 8)
644 {
645 jpeg->encode(columns, rows, interpr, samplesPerPixel, OFconst_cast(Uint8*, framePointer), jpegData, jpegLen);
646 }
647 else if (bitsAllocated == 16)
648 {
649 jpeg->encode(columns, rows, interpr, samplesPerPixel, OFreinterpret_cast(Uint16*, OFconst_cast(Uint8*, framePointer)), jpegData, jpegLen);
650 }
651 // update variables
652 compressedSize+=jpegLen;
653 framePointer+=frameSize;
654 if (jpegLen == 0)
655 {
656 DCMJPEG_ERROR("True lossless encoder: Error encoding frame");
657 result = EC_CannotChangeRepresentation;
658 }
659 else
660 {
661 result = pixelSequence->storeCompressedFrame(offsetList, jpegData, jpegLen, djcp->getFragmentSize());
662 }
663 // free memory
664 delete[] jpegData;
665 }
666 }
667 else
668 {
669 DCMJPEG_ERROR("True lossless encoder: Cannot allocate encoder instance");
670 result = EC_IllegalCall;
671 }
672 if (result.good())
673 {
674 compressionRatio = OFstatic_cast(double, bytesAllocated * samplesPerPixel * columns * rows * numberOfFrames) / OFstatic_cast(double, compressedSize);
675 pixSeq = pixelSequence;
676 }
677 else
678 delete pixelSequence;
679 delete jpeg; // encoder no longer in use
680
681 if ((result.good()) && (djcp->getCreateOffsetTable()))
682 {
683 // create offset table
684 result = offsetTable->createOffsetTable(offsetList);
685 }
686
687 // the following operations do not affect the Image Pixel Module
688 // but other modules such as SOP Common. We only perform these
689 // changes if we're on the main level of the datsetItem,
690 // which should always identify itself as datsetItem, not as item.
691
692 // update derivation description reflecting the JPEG compression applied
693 result = updateDerivationDescription(datsetItem, toRepParam, djcp, OFstatic_cast(Uint8, bitsAllocated), compressionRatio);
694
695 if ( (datsetItem->ident() == EVR_dataset) && result.good() )
696 {
697 // convert to Secondary Capture if requested by user.
698 // This method creates a new SOP class UID, so it should be executed
699 // after the call to newInstance() which creates a Source Image Sequence.
700 if ( djcp->getConvertToSC() || (djcp->getUIDCreation() == EUC_always) )
701 {
702 if (djcp->getConvertToSC())
703 {
704 result = DcmCodec::convertToSecondaryCapture(datsetItem);
705 }
706 // update image type (set to DERIVED)
707 if (result.good())
708 result = DcmCodec::updateImageType(datsetItem);
709 if (result.good())
710 result = DcmCodec::newInstance(OFreinterpret_cast(DcmItem*, datsetItem), "DCM", "121320", "Uncompressed predecessor");
711 }
712 }
713 // switch _original_ pixel data back to "color by plane", if required
714 if (planConfSwitched)
715 {
716 if (bytesAllocated == 1)
717 result = togglePlanarConfiguration8(OFreinterpret_cast(Uint8*, OFconst_cast(Uint16*, pixelData)), length, samplesPerPixel, OFstatic_cast(Uint16, 0) /*switch to "by plane"*/);
718 else
719 result = togglePlanarConfiguration16(OFconst_cast(Uint16*, pixelData), length/2, samplesPerPixel, OFstatic_cast(Uint16, 0) /*switch to "by plane"*/);
720 if (result.good())
721 {
722 // update Planar Configuration in dataset
723 result = updatePlanarConfiguration(datsetItem, 0 /* update to "by pixel" */);
724 }
725 else
726 {
727 DCMJPEG_ERROR("True lossless encoder: Cannot switch back to original planar configuration of the pixel data");
728 result = EC_CannotChangeRepresentation;
729 }
730 }
731 // byte swap pixel data back to local endian if necessary
732 if (byteSwapped)
733 swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, OFconst_cast(Uint16*, pixelData), OFstatic_cast(Uint32, length), sizeof(Uint16));
734 }
735 return result;
736 }
737
738
appendCompressionRatio(OFString & arg,double ratio)739 void DJCodecEncoder::appendCompressionRatio(
740 OFString& arg,
741 double ratio)
742 {
743 char buf[64];
744 OFStandard::ftoa(buf, sizeof(buf), ratio, OFStandard::ftoa_uppercase, 0, 5);
745 arg += buf;
746 }
747
748
updateLossyCompressionRatio(DcmItem * dataset,double ratio) const749 OFCondition DJCodecEncoder::updateLossyCompressionRatio(
750 DcmItem *dataset,
751 double ratio) const
752 {
753 if (dataset == NULL) return EC_IllegalCall;
754
755 // set Lossy Image Compression to "01" (see DICOM part 3, C.7.6.1.1.5)
756 OFCondition result = dataset->putAndInsertString(DCM_LossyImageCompression, "01");
757 if (result.bad()) return result;
758
759 // set Lossy Image Compression Ratio
760 OFString s;
761 const char *oldRatio = NULL;
762 if ((dataset->findAndGetString(DCM_LossyImageCompressionRatio, oldRatio)).good() && oldRatio)
763 {
764 s = oldRatio;
765 s += "\\";
766 }
767 appendCompressionRatio(s, ratio);
768
769 result = dataset->putAndInsertString(DCM_LossyImageCompressionRatio, s.c_str());
770 if (result.bad()) return result;
771
772 // count VM of lossy image compression ratio
773 size_t i;
774 size_t s_vm = 0;
775 size_t s_sz = s.size();
776 for (i = 0; i < s_sz; ++i)
777 if (s[i] == '\\') ++s_vm;
778
779 // set Lossy Image Compression Method
780 const char *oldMethod = NULL;
781 OFString m;
782 if ((dataset->findAndGetString(DCM_LossyImageCompressionMethod, oldMethod)).good() && oldMethod)
783 {
784 m = oldMethod;
785 m += "\\";
786 }
787
788 // count VM of lossy image compression method
789 size_t m_vm = 0;
790 size_t m_sz = m.size();
791 for (i = 0; i < m_sz; ++i)
792 if (m[i] == '\\') ++m_vm;
793
794 // make sure that VM of Compression Method is not smaller than VM of Compression Ratio
795 while (m_vm++ < s_vm) m += "\\";
796
797 m += "ISO_10918_1";
798 return dataset->putAndInsertString(DCM_LossyImageCompressionMethod, m.c_str());
799 }
800
801
updateDerivationDescription(DcmItem * dataset,const DcmRepresentationParameter * toRepParam,const DJCodecParameter * cp,Uint8 bitsPerSample,double ratio) const802 OFCondition DJCodecEncoder::updateDerivationDescription(
803 DcmItem *dataset,
804 const DcmRepresentationParameter * toRepParam,
805 const DJCodecParameter *cp,
806 Uint8 bitsPerSample,
807 double ratio) const
808 {
809 OFString derivationDescription;
810
811 // create new Derivation Description
812 createDerivationDescription(toRepParam, cp, bitsPerSample, ratio, derivationDescription);
813
814 // append old Derivation Description, if any
815 const char *oldDerivation = NULL;
816 if ((dataset->findAndGetString(DCM_DerivationDescription, oldDerivation)).good() && oldDerivation)
817 {
818 derivationDescription += " [";
819 derivationDescription += oldDerivation;
820 derivationDescription += "]";
821 if (derivationDescription.length() > 1024)
822 {
823 // ST is limited to 1024 characters, cut off tail
824 derivationDescription.erase(1020);
825 derivationDescription += "...]";
826 }
827 }
828
829 OFCondition result = dataset->putAndInsertString(DCM_DerivationDescription, derivationDescription.c_str());
830 if (result.good())
831 {
832 // assume we can cast the codec parameter to what we need
833 DJCodecParameter *djcp = OFconst_cast(DJCodecParameter*, cp);
834
835 if (!isLosslessProcess() || !djcp->getTrueLosslessMode())
836 result = DcmCodec::insertCodeSequence(dataset, DCM_DerivationCodeSequence, "DCM", "113040", "Lossy Compression");
837
838 }
839 return result;
840 }
841
842
adjustOverlays(DcmItem * dataset,DicomImage & image) const843 OFCondition DJCodecEncoder::adjustOverlays(
844 DcmItem *dataset,
845 DicomImage& image) const
846 {
847 if (dataset == NULL) return EC_IllegalCall;
848
849 unsigned int overlayCount = image.getOverlayCount();
850 if (overlayCount > 0)
851 {
852 Uint16 group = 0;
853 DcmStack stack;
854 size_t bytesAllocated = 0;
855 Uint8 *buffer = NULL;
856 unsigned int width = 0;
857 unsigned int height = 0;
858 long unsigned int frames = 0;
859 DcmElement *elem = NULL;
860 OFCondition result = EC_Normal;
861
862 // adjust overlays (prior to grayscale compression)
863 for (unsigned int i=0; i < overlayCount; i++)
864 {
865 // check if current overlay is embedded in pixel data
866 group = OFstatic_cast(Uint16, image.getOverlayGroupNumber(i));
867 stack.clear();
868 if ((dataset->search(DcmTagKey(group, 0x3000), stack, ESM_fromHere, OFFalse)).bad())
869 {
870 // separate Overlay Data not found. Assume overlay is embedded.
871 bytesAllocated = image.create6xxx3000OverlayData(buffer, i, width, height, frames);
872 if (bytesAllocated > 0)
873 {
874 elem = new DcmOverlayData(DcmTagKey(group, 0x3000)); // DCM_OverlayData
875 if (elem)
876 {
877 result = elem->putUint8Array(buffer, OFstatic_cast(Uint32, bytesAllocated));
878 delete[] buffer;
879 if (result.good())
880 {
881 dataset->insert(elem, OFTrue /*replaceOld*/);
882 // DCM_OverlayBitsAllocated
883 result = dataset->putAndInsertUint16(DcmTagKey(group, 0x0100), 1);
884 // DCM_OverlayBitPosition
885 if (result.good()) result = dataset->putAndInsertUint16(DcmTagKey(group, 0x0102), 0);
886 }
887 else
888 {
889 delete elem;
890 return result;
891 }
892 }
893 else
894 {
895 delete[] buffer;
896 return EC_MemoryExhausted;
897 }
898 }
899 else return EC_IllegalCall;
900 }
901 }
902 }
903 return EC_Normal;
904 }
905
906
encodeMonochromeImage(DcmItem * dataset,const DcmRepresentationParameter * toRepParam,DcmPixelSequence * & pixSeq,const DJCodecParameter * cp,double & compressionRatio) const907 OFCondition DJCodecEncoder::encodeMonochromeImage(
908 DcmItem *dataset,
909 const DcmRepresentationParameter * toRepParam,
910 DcmPixelSequence * & pixSeq,
911 const DJCodecParameter *cp,
912 double& compressionRatio) const
913 {
914 OFCondition result = EC_Normal;
915 DcmOffsetList offsetList;
916 DcmPixelSequence *pixelSequence = NULL;
917 DcmPixelItem *offsetTable = NULL;
918 unsigned short bitsPerSample = 0;
919 compressionRatio = 0.0; // initialize if something goes wrong
920 size_t compressedSize = 0;
921 double uncompressedSize = 0.0;
922 unsigned long flags = 0; // flags for initialization of DicomImage
923
924 // variables needed if VOI mode is 0
925 double minRange = 0.0;
926 double maxRange = 0.0;
927 double minUsed = 0.0;
928 double maxUsed = 0.0;
929 double rescaleSlope = 1.0;
930 double rescaleIntercept = 0.0;
931 double voiFactor = 1.0;
932 double voiOffset = 0.0;
933 double windowCenter = 0.0;
934 double windowWidth = 0.0;
935 OFBool deleteVOILUT = OFFalse;
936 OFBool mode_XA = OFFalse; // true if the current SOP class uses the X-Ray Image Module
937 OFBool mode_CT = OFFalse; // true if the current SOP class uses the CT Image Module
938
939 // Modes of operation (if VOI mode is 0)
940 OFBool mode_usePixelValues = cp->getUsePixelValues();
941 OFBool mode_useModalityRescale = cp->getUseModalityRescale();
942
943 //create flags for DicomImage corresponding to DJCodecParameter options
944 if (cp->getAcceptWrongPaletteTags())
945 flags |= CIF_WrongPaletteAttributeTags;
946 if (cp->getAcrNemaCompatibility())
947 flags |= CIF_AcrNemaCompatibility;
948
949 // Check if image is continuous-tone, bail out otherwise.
950 // We check the value of BitsStored, which is not affected by any transformation such as MLUT.
951 Uint16 bitsStored = 0;
952 result = dataset->findAndGetUint16(DCM_BitsStored, bitsStored);
953 if (result.bad()) return result;
954
955 if ((bitsStored > 16) && isLosslessProcess())
956 {
957 DCMJPEG_WARN("Cannot lossless compress image with " << bitsStored << " bits/sample: JPEG supports max. 16 bits.");
958 return EJ_UnsupportedBitDepth;
959 }
960
961 if (bitsStored < 2)
962 {
963 DCMJPEG_WARN("Cannot compress image with " << bitsStored << " bit/sample: JPEG requires at least 2 bits.");
964 return EJ_UnsupportedBitDepth;
965 }
966
967 // create DicomImage object. Will fail if dcmimage has not been activated in main().
968 // transfer syntax can be any uncompressed one.
969 DicomImage dimage(dataset, EXS_LittleEndianImplicit, flags); // read all frames
970 if (dimage.getStatus() != EIS_Normal)
971 {
972 DCMJPEG_WARN("Monochrome encoder: " << DicomImage::getString(dimage.getStatus()));
973 result = EC_IllegalCall;
974 }
975
976 // don't render overlays
977 dimage.hideAllOverlays();
978
979 // actual pixel depth of source image which can be different from Bits Stored
980 // e. g. when Modality LUT (if enabled) shifts pixel values to a smaller
981 // range or if the pixel values itself do not make use of the "Bits Stored"
982 // full range available.
983 int pixelDepth = dimage.getDepth();
984
985 // create overlay data for embedded overlays
986 if (result.good()) result = adjustOverlays(dataset, dimage);
987
988 // VOI transformations should only be applied on the dataset level, not
989 // in nested items such as the Icon Image Sequence where we don't expect
990 // a VOI window or LUT to be present
991 size_t windowType = 0;
992 if (dataset->ident() == EVR_dataset)
993 {
994 windowType = cp->getWindowType();
995 }
996
997 // set VOI transformation
998 if (result.good())
999 {
1000 switch (windowType)
1001 {
1002 case 0: // no VOI transformation
1003 {
1004 // disable correction of polarity or any other presentation LUT transformation
1005 dimage.setPresentationLutShape(ESP_Identity);
1006
1007 // disable VOI transformation
1008 dimage.setNoVoiTransformation();
1009
1010 // look up SOP Class UID, if any
1011 const char *classUID = NULL;
1012 dataset->findAndGetString(DCM_SOPClassUID, classUID);
1013
1014 // SOP Class specifics.
1015 if (classUID && ! cp->getConvertToSC())
1016 {
1017 // these three SOP classes use the X-Ray Image Module in which the meaning
1018 // of the Modality LUT transformation is "inversed" and, therefore,
1019 // needs special handling. This is not an issue if we're converting to
1020 // secondary capture anyway.
1021 if ((0 == strcmp(classUID, UID_XRayAngiographicImageStorage)) ||
1022 (0 == strcmp(classUID, UID_XRayRadiofluoroscopicImageStorage)) ||
1023 (0 == strcmp(classUID, UID_RETIRED_XRayAngiographicBiPlaneImageStorage)))
1024 {
1025 mode_XA = OFTrue;
1026 mode_useModalityRescale = OFFalse; // inverse definition of Modality LUT Module
1027 }
1028
1029 // CT is also a special case because the Modality LUT is required here
1030 // to convert to Hounsfield units. Again, this is not an issue if we're
1031 // converting to SC anyway.
1032 if (0 == strcmp(classUID, UID_CTImageStorage))
1033 {
1034 mode_CT = OFTrue;
1035 mode_useModalityRescale = OFTrue; // required for Hounsfield units
1036 }
1037 }
1038
1039 // query image range and extreme values
1040 if (result.good())
1041 {
1042 // technically possible min/max values
1043 if (! dimage.getMinMaxValues(minRange, maxRange, 1)) result = EC_IllegalCall;
1044 if (maxRange <= minRange) result = EC_IllegalCall;
1045 }
1046
1047 if (result.good())
1048 {
1049 // actually present min/max values in pixel data
1050 if (! dimage.getMinMaxValues(minUsed, maxUsed, 0)) result = EC_IllegalCall;
1051 if (maxUsed < minUsed) result = EC_IllegalCall;
1052 }
1053 }
1054 break;
1055 case 1: // use the n-th VOI window from the image file
1056 {
1057 size_t windowParameter = cp->getWindowParameter();
1058 if ((windowParameter < 1) || (windowParameter > dimage.getWindowCount())) result = EC_IllegalCall;
1059 if (!dimage.setWindow(OFstatic_cast(unsigned long, windowParameter - 1))) result = EC_IllegalCall;
1060 }
1061 break;
1062 case 2: // use the n-th VOI look up table from the image file
1063 {
1064 size_t windowParameter = cp->getWindowParameter();
1065 if ((windowParameter < 1) || (windowParameter > dimage.getVoiLutCount())) result = EC_IllegalCall;
1066 if (!dimage.setVoiLut(OFstatic_cast(unsigned long, windowParameter - 1))) result = EC_IllegalCall;
1067 }
1068 break;
1069 case 3: // Compute VOI window using min-max algorithm
1070 if (!dimage.setMinMaxWindow(0)) result = EC_IllegalCall;
1071 break;
1072 case 4: // Compute VOI window using Histogram algorithm, ignoring n percent
1073 {
1074 size_t windowParameter = cp->getWindowParameter();
1075 if (!dimage.setHistogramWindow(OFstatic_cast(double, windowParameter)/100.0)) result = EC_IllegalCall;
1076 }
1077 break;
1078 case 5: // Compute VOI window using center r and width s
1079 {
1080 double winCenter=0.0, winWidth=0.0;
1081 cp->getVOIWindow(winCenter, winWidth);
1082 if (!dimage.setWindow(winCenter, winWidth)) result = EC_IllegalCall;
1083 }
1084 break;
1085 case 6: // Compute VOI window using min-max algorithm ignoring extremes
1086 if (!dimage.setMinMaxWindow(1)) result = EC_IllegalCall;
1087 break;
1088 case 7: // Compute region of interest VOI window
1089 {
1090 size_t left_pos=0, top_pos=0, width=0, height=0;
1091 cp->getROI(left_pos, top_pos, width, height);
1092 if (!dimage.setRoiWindow(OFstatic_cast(unsigned long, left_pos), OFstatic_cast(unsigned long, top_pos),
1093 OFstatic_cast(unsigned long, width), OFstatic_cast(unsigned long, height))) result = EC_IllegalCall;
1094 }
1095 break;
1096 default: // includes case 0, which must not occur here
1097 result = EC_IllegalCall;
1098 break;
1099 }
1100 }
1101
1102 // create initial pixel sequence
1103 if (result.good())
1104 {
1105 pixelSequence = new DcmPixelSequence(DCM_PixelSequenceTag);
1106 if (pixelSequence == NULL) result = EC_MemoryExhausted;
1107 else
1108 {
1109 // create empty offset table
1110 offsetTable = new DcmPixelItem(DCM_PixelItemTag);
1111 if (offsetTable == NULL) result = EC_MemoryExhausted;
1112 else pixelSequence->insert(offsetTable);
1113 }
1114 }
1115
1116 // select bit depth
1117 Uint16 compressedBits = OFstatic_cast(Uint16, cp->getForcedBitDepth());
1118 if (result.good())
1119 {
1120 if (compressedBits == 0)
1121 {
1122 result = OFreinterpret_cast(DcmItem*, dataset)->findAndGetUint16(DCM_BitsStored, compressedBits);
1123 }
1124 }
1125
1126 // create codec instance
1127 if (result.good())
1128 {
1129 DJEncoder *jpeg = createEncoderInstance(toRepParam, cp, OFstatic_cast(Uint8, compressedBits));
1130 if (jpeg)
1131 {
1132 bitsPerSample = jpeg->bitsPerSample();
1133
1134 if (windowType == 0)
1135 {
1136 // perform image computations
1137 if (mode_usePixelValues)
1138 {
1139 double z = minUsed;
1140 if (z >= 0) z = floor(z); else z = ceil(z-1023.0);
1141 double rangeUsed = maxUsed - minUsed + 1; // range that must be covered by window
1142
1143 // compute optimum window width
1144 if (pixelDepth < bitsPerSample) windowWidth = 1 << pixelDepth;
1145 else windowWidth = 1 << bitsPerSample;
1146 while (windowWidth < rangeUsed) windowWidth *= 2;
1147
1148 windowCenter = windowWidth * 0.5;
1149 if ((z + windowWidth) > maxUsed) windowCenter += z; else windowCenter += minUsed;
1150 dimage.setWindow(windowCenter, windowWidth);
1151 }
1152
1153 // perform image computations
1154 if (mode_useModalityRescale)
1155 {
1156 // Use Rescale Slope/Intercept to map the decompressed image data
1157 // back to the original value range, keeping all VOI transformations valid.
1158 if (mode_usePixelValues)
1159 {
1160 rescaleSlope = (windowWidth-1)/((1<<bitsPerSample)-1);
1161 rescaleIntercept = windowCenter - (windowWidth * 0.5);
1162 }
1163 else
1164 {
1165 rescaleSlope = (maxRange - minRange)/((1<<bitsPerSample)-1);
1166 rescaleIntercept = minRange;
1167 }
1168 }
1169 else
1170 {
1171 // Don't use Rescale Slope/Intercept (remove or encode as identity transformation).
1172 // Modify window centers/widths encoded in image, remove VOI LUTs.
1173 deleteVOILUT = OFTrue;
1174 if (mode_usePixelValues)
1175 {
1176 voiFactor = ((1<<bitsPerSample)-1)/(windowWidth-1); // 1/rescaleSlope
1177 voiOffset = (windowWidth * 0.5) - windowCenter; // - rescaleIntercept
1178 }
1179 else
1180 {
1181 voiFactor = ((1<<bitsPerSample)-1)/(maxRange - minRange); // 1/rescaleSlope
1182 voiOffset = -minRange; // - rescaleIntercept
1183 }
1184 }
1185 }
1186
1187 // render and compress each frame
1188 size_t frameCount = dimage.getFrameCount();
1189 unsigned short bytesPerSample = jpeg->bytesPerSample();
1190 unsigned short columns = OFstatic_cast(unsigned short, dimage.getWidth());
1191 unsigned short rows = OFstatic_cast(unsigned short, dimage.getHeight());
1192 Uint8 *jpegData = NULL;
1193 Uint32 jpegLen = 0;
1194 const void *frame = NULL;
1195
1196 // compute original image size in bytes, ignoring any padding bits.
1197 Uint16 samplesPerPixel = 0;
1198 if ((dataset->findAndGetUint16(DCM_SamplesPerPixel, samplesPerPixel)).bad()) samplesPerPixel = 1;
1199 uncompressedSize = OFstatic_cast(double, columns * rows * pixelDepth * frameCount * samplesPerPixel) / 8.0;
1200 for (size_t i=0; (i<frameCount) && (result.good()); i++)
1201 {
1202 frame = dimage.getOutputData(bitsPerSample, OFstatic_cast(unsigned long, i), 0);
1203 if (frame == NULL) result = EC_MemoryExhausted;
1204 else
1205 {
1206 // compress frame
1207 jpegData = NULL;
1208 if (bytesPerSample == 1)
1209 {
1210 result = jpeg->encode(columns, rows, EPI_Monochrome2, 1, OFreinterpret_cast(Uint8*, OFconst_cast(void*, frame)), jpegData, jpegLen);
1211 } else {
1212 result = jpeg->encode(columns, rows, EPI_Monochrome2, 1, OFreinterpret_cast(Uint16*, OFconst_cast(void*, frame)), jpegData, jpegLen);
1213 }
1214
1215 // store frame
1216 if (result.good())
1217 {
1218 result = pixelSequence->storeCompressedFrame(offsetList, jpegData, jpegLen, cp->getFragmentSize());
1219 }
1220
1221 // delete block of JPEG data
1222 delete[] jpegData;
1223 compressedSize += jpegLen;
1224 }
1225 }
1226 delete jpeg;
1227 } else result = EC_MemoryExhausted;
1228 }
1229
1230 // store pixel sequence if everything went well.
1231 if (result.good()) pixSeq = pixelSequence;
1232 else
1233 {
1234 delete pixelSequence;
1235 pixSeq = NULL;
1236 }
1237
1238 if ((result.good()) && (cp->getCreateOffsetTable()))
1239 {
1240 // create offset table
1241 result = offsetTable->createOffsetTable(offsetList);
1242 }
1243
1244 if (result.good())
1245 {
1246 // adapt attributes in image pixel module
1247 if (result.good()) result = dataset->putAndInsertUint16(DCM_SamplesPerPixel, 1);
1248 if (result.good() && (windowType != 0)) result = dataset->putAndInsertString(DCM_PhotometricInterpretation, "MONOCHROME2");
1249 if (result.good())
1250 {
1251 if (bitsPerSample > 8)
1252 result = dataset->putAndInsertUint16(DCM_BitsAllocated, 16);
1253 else
1254 result = dataset->putAndInsertUint16(DCM_BitsAllocated, 8);
1255 }
1256 if (result.good()) result = dataset->putAndInsertUint16(DCM_BitsStored, bitsPerSample);
1257 if (result.good()) result = dataset->putAndInsertUint16(DCM_HighBit, OFstatic_cast(Uint16, bitsPerSample-1));
1258 if (result.good()) result = dataset->putAndInsertUint16(DCM_PixelRepresentation, 0);
1259 delete dataset->remove(DCM_PlanarConfiguration);
1260 delete dataset->remove(DCM_SmallestImagePixelValue);
1261 delete dataset->remove(DCM_LargestImagePixelValue);
1262 delete dataset->remove(DCM_RedPaletteColorLookupTableDescriptor);
1263 delete dataset->remove(DCM_GreenPaletteColorLookupTableDescriptor);
1264 delete dataset->remove(DCM_BluePaletteColorLookupTableDescriptor);
1265 delete dataset->remove(DCM_RedPaletteColorLookupTableData);
1266 delete dataset->remove(DCM_GreenPaletteColorLookupTableData);
1267 delete dataset->remove(DCM_BluePaletteColorLookupTableData);
1268 delete dataset->remove(DCM_PixelPaddingValue);
1269 delete dataset->remove(DCM_SmallestPixelValueInSeries);
1270 delete dataset->remove(DCM_LargestPixelValueInSeries);
1271 delete dataset->remove(DCM_PaletteColorLookupTableUID);
1272 delete dataset->remove(DCM_SegmentedRedPaletteColorLookupTableData);
1273 delete dataset->remove(DCM_SegmentedGreenPaletteColorLookupTableData);
1274 delete dataset->remove(DCM_SegmentedBluePaletteColorLookupTableData);
1275
1276 // check if either Modality LUT Sequence or Rescale Slope are present
1277 DcmStack stack;
1278 OFBool foundModalityLUT = OFFalse;
1279 if ((dataset->search(DCM_ModalityLUTSequence, stack, ESM_fromHere, OFFalse)).good()) foundModalityLUT = OFTrue;
1280 if (! foundModalityLUT)
1281 {
1282 stack.clear();
1283 if ((dataset->search(DCM_RescaleSlope, stack, ESM_fromHere, OFFalse)).good()) foundModalityLUT = OFTrue;
1284 }
1285
1286 // delete old Modality transformation
1287 delete dataset->remove(DCM_ModalityLUTSequence);
1288 delete dataset->remove(DCM_RescaleIntercept);
1289 delete dataset->remove(DCM_RescaleSlope);
1290 delete dataset->remove(DCM_RescaleType);
1291
1292 // update Modality LUT Module and Pixel Intensity Relationship
1293 if (windowType == 0)
1294 {
1295 if (mode_XA) // XA needs special handling
1296 {
1297 // XA Mode: set Pixel Intensity Relationship to "DISP", no Modality LUT
1298 if (result.good()) result = dataset->putAndInsertString(DCM_PixelIntensityRelationship, "DISP");
1299 }
1300 /* else if we had a modality LUT before, a LUT is inserted again.
1301 or if specific rescale slope/intercept has been computed, use that in image
1302 */
1303 else if (foundModalityLUT || rescaleSlope != 1.0 || rescaleIntercept != 0.0)
1304 {
1305 char buf[64];
1306 OFStandard::ftoa(buf, sizeof(buf), rescaleIntercept, OFStandard::ftoa_uppercase, 0, 6);
1307 if (result.good()) result = dataset->putAndInsertString(DCM_RescaleIntercept, buf);
1308 OFStandard::ftoa(buf, sizeof(buf), rescaleSlope, OFStandard::ftoa_uppercase, 0, 6);
1309 if (result.good()) result = dataset->putAndInsertString(DCM_RescaleSlope, buf);
1310 if (result.good())
1311 {
1312 if (mode_CT) result = dataset->putAndInsertString(DCM_RescaleType, "HU"); // Hounsfield units
1313 else result = dataset->putAndInsertString(DCM_RescaleType, "US"); // unspecified
1314 }
1315 }
1316 }
1317 else
1318 {
1319 // if we had found a Modality LUT Transformation, create a identity LUT transformation
1320 if (foundModalityLUT)
1321 {
1322 if (result.good()) result = dataset->putAndInsertString(DCM_RescaleIntercept, "0");
1323 if (result.good()) result = dataset->putAndInsertString(DCM_RescaleSlope, "1");
1324 if (result.good()) result = dataset->putAndInsertString(DCM_RescaleType, "US"); // unspecified
1325 }
1326
1327 // Adjust Pixel Intensity Relationship (needed for XA). If present, set value to "DISP".
1328 stack.clear();
1329 if ((dataset->search(DCM_PixelIntensityRelationship, stack, ESM_fromHere, OFFalse)).good())
1330 {
1331 if (result.good()) result = dataset->putAndInsertString(DCM_PixelIntensityRelationship, "DISP");
1332 }
1333 }
1334
1335 // Adjust VOI LUT and Presentation LUT transformation
1336 if (windowType == 0)
1337 {
1338 if (deleteVOILUT) delete dataset->remove(DCM_VOILUTSequence);
1339
1340 // Adjust window center/width
1341 if (result.good()) result = correctVOIWindows(dataset, voiOffset, voiFactor);
1342
1343 // Don't modify Presentation LUT transformations (if any)
1344 }
1345 else
1346 {
1347 delete dataset->remove(DCM_VOILUTSequence);
1348 delete dataset->remove(DCM_WindowCenter);
1349 delete dataset->remove(DCM_WindowWidth);
1350 delete dataset->remove(DCM_WindowCenterWidthExplanation);
1351
1352 // Adjust Presentation LUT Transformation.
1353 stack.clear();
1354 OFBool foundPresentationLUT = OFFalse;
1355 if ((dataset->search(DCM_PresentationLUTSequence, stack, ESM_fromHere, OFFalse)).good()) foundPresentationLUT = OFTrue;
1356 if (! foundPresentationLUT)
1357 {
1358 stack.clear();
1359 if ((dataset->search(DCM_PresentationLUTShape, stack, ESM_fromHere, OFFalse)).good()) foundPresentationLUT = OFTrue;
1360 }
1361
1362 // delete old Presentation LUT transformation
1363 delete dataset->remove(DCM_PresentationLUTSequence);
1364 delete dataset->remove(DCM_PresentationLUTShape);
1365
1366 // if we had found a Presentation LUT Transformation, create a new identity transformation
1367 if (foundPresentationLUT)
1368 {
1369 if (result.good()) result = dataset->putAndInsertString(DCM_PresentationLUTShape, "IDENTITY");
1370 }
1371 }
1372 }
1373
1374 // determine compression ratio
1375 if (compressedSize > 0) compressionRatio = OFstatic_cast(double, uncompressedSize) / OFstatic_cast(double, compressedSize);
1376 return result;
1377 }
1378
1379
correctVOIWindows(DcmItem * dataset,double voiOffset,double voiFactor)1380 OFCondition DJCodecEncoder::correctVOIWindows(
1381 DcmItem *dataset,
1382 double voiOffset,
1383 double voiFactor)
1384 {
1385 if (voiOffset == 0.0 && voiFactor == 1.0) return EC_Normal;
1386
1387 OFCondition result = EC_Normal;
1388 DcmElement *center = NULL;
1389 DcmElement *width = NULL;
1390 DcmElement *explanation = NULL;
1391
1392 DcmStack stack;
1393 if ((dataset->search(DCM_WindowCenter, stack, ESM_fromHere, OFFalse)).good())
1394 {
1395 center = OFreinterpret_cast(DcmElement*, stack.top());
1396 }
1397 stack.clear();
1398 if ((dataset->search(DCM_WindowWidth, stack, ESM_fromHere, OFFalse)).good())
1399 {
1400 width = OFreinterpret_cast(DcmElement*, stack.top());
1401 }
1402 stack.clear();
1403 if ((dataset->search(DCM_WindowCenterWidthExplanation, stack, ESM_fromHere, OFFalse)).good())
1404 {
1405 explanation = OFreinterpret_cast(DcmElement*, stack.top());
1406 }
1407
1408 OFString newCenter;
1409 OFString newWidth;
1410 OFString newExplanation;
1411 Float64 currentCenter = 0.0;
1412 Float64 currentWidth = 0.0;
1413 OFString currentExplanation;
1414 double tempCenter = 0.0;
1415 double tempWidth = 0.0;
1416 char buf[64];
1417
1418 if (center && width)
1419 {
1420 size_t numWindows = center->getVM();
1421 // iterate over all defined VOI windows
1422 for (size_t i=0; i<numWindows; i++)
1423 {
1424 if (((center->getFloat64(currentCenter,OFstatic_cast(Uint32, i))).good()) && ((width->getFloat64(currentWidth,OFstatic_cast(Uint32, i))).good()))
1425 {
1426 // found one pair of values, adapt them to value range shifted pixel data
1427 tempCenter = (currentCenter+voiOffset)*voiFactor;
1428 tempWidth = currentWidth * voiFactor;
1429 // add this window to the attribute values that are later replacing old windows
1430 OFStandard::ftoa(buf, sizeof(buf), tempCenter, OFStandard::ftoa_uppercase, 0, 6);
1431 if (!newCenter.empty()) newCenter += "\\";
1432 newCenter += buf;
1433 OFStandard::ftoa(buf, sizeof(buf), tempWidth, OFStandard::ftoa_uppercase, 0, 6);
1434 if (!newWidth.empty()) newWidth += "\\";
1435 newWidth += buf;
1436
1437 if (!newExplanation.empty()) newExplanation += "\\";
1438 if (explanation && ((explanation->getOFString(currentExplanation,OFstatic_cast(Uint32, i))).good()))
1439 newExplanation += currentExplanation;
1440 else
1441 {
1442 newExplanation += "center=";
1443 OFStandard::ftoa(buf, sizeof(buf), tempCenter, OFStandard::ftoa_uppercase, 0, 6);
1444 newExplanation += buf;
1445 newExplanation += "/width=";
1446 OFStandard::ftoa(buf, sizeof(buf), tempWidth, OFStandard::ftoa_uppercase, 0, 6);
1447 newExplanation += buf;
1448 }
1449 }
1450 }
1451 }
1452
1453 // remove old windows
1454 delete dataset->remove(DCM_WindowCenter);
1455 delete dataset->remove(DCM_WindowWidth);
1456 delete dataset->remove(DCM_WindowCenterWidthExplanation);
1457
1458 // and insert newly computed ones if necessary
1459 if (!newCenter.empty())
1460 {
1461 if (result.good()) result = dataset->putAndInsertString(DCM_WindowCenter, newCenter.c_str());
1462 if (result.good()) result = dataset->putAndInsertString(DCM_WindowWidth, newWidth.c_str());
1463 if (result.good()) result = dataset->putAndInsertString(DCM_WindowCenterWidthExplanation, newExplanation.c_str());
1464 }
1465
1466 return result;
1467 }
1468
1469
togglePlanarConfiguration8(Uint8 * pixelData,const size_t numValues,const Uint16 samplesPerPixel,const Uint16 oldPlanarConfig)1470 OFCondition DJCodecEncoder::togglePlanarConfiguration8(
1471 Uint8 *pixelData,
1472 const size_t numValues,
1473 const Uint16 samplesPerPixel,
1474 const Uint16 oldPlanarConfig)
1475 {
1476 if (pixelData == NULL)
1477 return EC_IllegalParameter;
1478 // allocate target buffer
1479 Uint8* px8 = new Uint8[numValues];
1480 if (!px8)
1481 return EC_MemoryExhausted;
1482 size_t numPixels = numValues / samplesPerPixel;
1483 if (oldPlanarConfig == 1) // change from "by plane" to "by pixel"
1484 {
1485 for (size_t n=0; n < numPixels; n++)
1486 {
1487 for (Uint16 s=0; s < samplesPerPixel; s++)
1488 px8[n*samplesPerPixel+s] = pixelData[n+numPixels*s];
1489 }
1490 }
1491 else //change from "by pixel" to "by plane"
1492 {
1493 for (size_t n=0; n < numPixels; n++)
1494 {
1495 for (Uint16 s=0; s < samplesPerPixel; s++)
1496 px8[n+numPixels*s] = pixelData[n*samplesPerPixel+s];
1497 }
1498 }
1499 // copy filled buffer to pixel data and free memory
1500 memcpy(pixelData, px8, OFstatic_cast(size_t, numValues));
1501 delete[] px8;
1502 return EC_Normal;
1503 }
1504
1505
togglePlanarConfiguration16(Uint16 * pixelData,const size_t numValues,const Uint16 samplesPerPixel,const Uint16 oldPlanarConfig)1506 OFCondition DJCodecEncoder::togglePlanarConfiguration16(
1507 Uint16 *pixelData,
1508 const size_t numValues, //number of 16-bit components
1509 const Uint16 samplesPerPixel,
1510 const Uint16 oldPlanarConfig)
1511 {
1512 if (pixelData == NULL)
1513 return EC_IllegalParameter;
1514 // allocate target buffer
1515 Uint16* px16 = new Uint16[numValues];
1516 if (!px16)
1517 return EC_MemoryExhausted;
1518 size_t numPixels = numValues / samplesPerPixel;
1519 if (oldPlanarConfig == 1) // change from "by plane" to "by pixel"
1520 {
1521 for (size_t n=0; n < numPixels; n++)
1522 {
1523 for (Uint16 s=0; s < samplesPerPixel; s++)
1524 px16[n*samplesPerPixel+s] = pixelData[n+numPixels*s];
1525 }
1526 }
1527 else //change from "by pixel" to "by plane"
1528 {
1529 for (size_t n=0; n < numPixels; n++)
1530 {
1531 for (Uint16 s=0; s < samplesPerPixel; s++)
1532 px16[n+numPixels*s] = pixelData[n*samplesPerPixel+s];
1533 }
1534 }
1535 // copy filled buffer to pixel data and free memory
1536 memcpy(pixelData, px16, OFstatic_cast(size_t, numValues*2));
1537 delete[] px16;
1538 return EC_Normal;
1539 }
1540
1541
updatePlanarConfiguration(DcmItem * item,const Uint16 newPlanConf) const1542 OFCondition DJCodecEncoder::updatePlanarConfiguration(
1543 DcmItem *item,
1544 const Uint16 newPlanConf) const
1545 {
1546 if ( (item == NULL) || (newPlanConf) > 1)
1547 return EC_IllegalParameter;
1548 return item->putAndInsertUint16(DCM_PlanarConfiguration, newPlanConf);
1549 }
1550