/src/dcmtk/dcmjpls/libsrc/djcodecd.cc
Line | Count | Source |
1 | | /* |
2 | | * |
3 | | * Copyright (C) 2007-2025, 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: dcmjpls |
15 | | * |
16 | | * Author: Martin Willkomm, Marco Eichelberg, Uli Schlachter |
17 | | * |
18 | | * Purpose: codec classes for JPEG-LS decoders. |
19 | | * |
20 | | */ |
21 | | |
22 | | #include "dcmtk/config/osconfig.h" |
23 | | #include "dcmtk/dcmjpls/djcodecd.h" |
24 | | |
25 | | #include "dcmtk/ofstd/ofstream.h" /* for ofstream */ |
26 | | #include "dcmtk/ofstd/ofcast.h" /* for casts */ |
27 | | #include "dcmtk/ofstd/offile.h" /* for class OFFile */ |
28 | | #include "dcmtk/ofstd/ofstd.h" /* for class OFStandard */ |
29 | | #include "dcmtk/dcmdata/dcdatset.h" /* for class DcmDataset */ |
30 | | #include "dcmtk/dcmdata/dcdeftag.h" /* for tag constants */ |
31 | | #include "dcmtk/dcmdata/dcpixseq.h" /* for class DcmPixelSequence */ |
32 | | #include "dcmtk/dcmdata/dcpxitem.h" /* for class DcmPixelItem */ |
33 | | #include "dcmtk/dcmdata/dcvrpobw.h" /* for class DcmPolymorphOBOW */ |
34 | | #include "dcmtk/dcmdata/dcswap.h" /* for swapIfNecessary() */ |
35 | | #include "dcmtk/dcmdata/dcuid.h" /* for dcmGenerateUniqueIdentifer()*/ |
36 | | #include "dcmtk/dcmjpls/djcparam.h" /* for class DJLSCodecParameter */ |
37 | | #include "djerror.h" /* for private class DJLSError */ |
38 | | |
39 | | // JPEG-LS library (CharLS) includes |
40 | | #include "intrface.h" |
41 | | |
42 | | E_TransferSyntax DJLSLosslessDecoder::supportedTransferSyntax() const |
43 | 0 | { |
44 | 0 | return EXS_JPEGLSLossless; |
45 | 0 | } |
46 | | |
47 | | E_TransferSyntax DJLSNearLosslessDecoder::supportedTransferSyntax() const |
48 | 0 | { |
49 | 0 | return EXS_JPEGLSLossy; |
50 | 0 | } |
51 | | |
52 | | // -------------------------------------------------------------------------- |
53 | | |
54 | | DJLSDecoderBase::DJLSDecoderBase() |
55 | 2 | : DcmCodec() |
56 | 2 | { |
57 | 2 | } |
58 | | |
59 | | |
60 | | DJLSDecoderBase::~DJLSDecoderBase() |
61 | 2 | { |
62 | 2 | } |
63 | | |
64 | | |
65 | | OFBool DJLSDecoderBase::canChangeCoding( |
66 | | const E_TransferSyntax oldRepType, |
67 | | const E_TransferSyntax newRepType) const |
68 | 0 | { |
69 | | // this codec only handles conversion from JPEG-LS to uncompressed. |
70 | |
|
71 | 0 | DcmXfer newRep(newRepType); |
72 | 0 | if (newRep.usesNativeFormat() && (oldRepType == supportedTransferSyntax())) |
73 | 0 | return OFTrue; |
74 | | |
75 | 0 | return OFFalse; |
76 | 0 | } |
77 | | |
78 | | |
79 | | Uint16 DJLSDecoderBase::decodedBitsAllocated( |
80 | | Uint16 /* bitsAllocated */, |
81 | | Uint16 bitsStored) const |
82 | 0 | { |
83 | | // this codec does not support images with less than 2 bits per sample |
84 | 0 | if (bitsStored < 2) return 0; |
85 | | |
86 | | // for images with 2..8 bits per sample, BitsAllocated will be 8 |
87 | 0 | if (bitsStored <= 8) return 8; |
88 | | |
89 | | // for images with 9..16 bits per sample, BitsAllocated will be 16 |
90 | 0 | if (bitsStored <= 16) return 16; |
91 | | |
92 | | // this codec does not support images with more than 16 bits per sample |
93 | 0 | return 0; |
94 | 0 | } |
95 | | |
96 | | |
97 | | OFCondition DJLSDecoderBase::decode( |
98 | | const DcmRepresentationParameter * /* fromRepParam */, |
99 | | DcmPixelSequence * pixSeq, |
100 | | DcmPolymorphOBOW& uncompressedPixelData, |
101 | | const DcmCodecParameter * cp, |
102 | | const DcmStack& objStack, |
103 | | OFBool& removeOldRep) const |
104 | 0 | { |
105 | | |
106 | | // this codec may modify the DICOM header such that the previous pixel |
107 | | // representation is not valid anymore, e.g. in the case of color images |
108 | | // where the planar configuration can change. Indicate this to the caller |
109 | | // to trigger removal. |
110 | 0 | removeOldRep = OFTrue; |
111 | | |
112 | | // retrieve pointer to dataset from parameter stack |
113 | 0 | DcmStack localStack(objStack); |
114 | 0 | (void)localStack.pop(); // pop pixel data element from stack |
115 | 0 | DcmObject *dobject = localStack.pop(); // this is the item in which the pixel data is located |
116 | 0 | if ((!dobject)||((dobject->ident()!= EVR_dataset) && (dobject->ident()!= EVR_item))) return EC_InvalidTag; |
117 | 0 | DcmItem *dataset = OFstatic_cast(DcmItem *, dobject); |
118 | 0 | OFBool numberOfFramesPresent = OFFalse; |
119 | | |
120 | | // determine properties of uncompressed dataset |
121 | 0 | Uint16 imageSamplesPerPixel = 0; |
122 | 0 | if (dataset->findAndGetUint16(DCM_SamplesPerPixel, imageSamplesPerPixel).bad()) return EC_TagNotFound; |
123 | | |
124 | | // we only handle one or three samples per pixel |
125 | 0 | if ((imageSamplesPerPixel != 3) && (imageSamplesPerPixel != 1)) return EC_InvalidTag; |
126 | | |
127 | 0 | Uint16 imageRows = 0; |
128 | 0 | if (dataset->findAndGetUint16(DCM_Rows, imageRows).bad()) return EC_TagNotFound; |
129 | 0 | if (imageRows < 1) return EC_InvalidTag; |
130 | | |
131 | 0 | Uint16 imageColumns = 0; |
132 | 0 | if (dataset->findAndGetUint16(DCM_Columns, imageColumns).bad()) return EC_TagNotFound; |
133 | 0 | if (imageColumns < 1) return EC_InvalidTag; |
134 | | |
135 | | // number of frames is an optional attribute - we don't mind if it isn't present. |
136 | 0 | Sint32 imageFrames = 0; |
137 | 0 | if (dataset->findAndGetSint32(DCM_NumberOfFrames, imageFrames).good()) numberOfFramesPresent = OFTrue; |
138 | |
|
139 | 0 | if (imageFrames >= OFstatic_cast(Sint32, pixSeq->card())) |
140 | 0 | imageFrames = pixSeq->card() - 1; // limit number of frames to number of pixel items - 1 |
141 | 0 | if (imageFrames < 1) |
142 | 0 | imageFrames = 1; // default in case the number of frames attribute is absent or contains garbage |
143 | |
|
144 | 0 | Uint16 imageBitsStored = 0; |
145 | 0 | if (dataset->findAndGetUint16(DCM_BitsStored, imageBitsStored).bad()) return EC_TagNotFound; |
146 | | |
147 | 0 | Uint16 imageBitsAllocated = 0; |
148 | 0 | if (dataset->findAndGetUint16(DCM_BitsAllocated, imageBitsAllocated).bad()) return EC_TagNotFound; |
149 | | |
150 | 0 | Uint16 imageHighBit = 0; |
151 | 0 | if (dataset->findAndGetUint16(DCM_HighBit, imageHighBit).bad()) return EC_TagNotFound; |
152 | | |
153 | | //we only support up to 16 bits per sample |
154 | 0 | if ((imageBitsStored < 1) || (imageBitsStored > 16)) return EC_JLSUnsupportedBitDepth; |
155 | | |
156 | | // determine the number of bytes per sample (bits allocated) for the de-compressed object. |
157 | 0 | Uint16 bytesPerSample = 1; |
158 | 0 | if (imageBitsStored > 8) bytesPerSample = 2; |
159 | 0 | else if (imageBitsAllocated > 8) bytesPerSample = 2; |
160 | | |
161 | | // compute size of uncompressed frame, in bytes |
162 | 0 | Uint32 frameSize = bytesPerSample * imageRows * imageColumns * imageSamplesPerPixel; |
163 | | |
164 | | // check for overflow |
165 | 0 | if (imageRows != 0 && frameSize / imageRows != (OFstatic_cast(Uint32, bytesPerSample) * imageColumns * imageSamplesPerPixel)) |
166 | 0 | { |
167 | 0 | DCMJPLS_WARN("cannot decompress image because uncompressed representation would exceed maximum possible size of PixelData attribute"); |
168 | 0 | return EC_ElemLengthExceeds32BitField; |
169 | 0 | } |
170 | | |
171 | | // compute size of pixel data attribute, in bytes |
172 | 0 | Uint32 totalSize = frameSize * imageFrames; |
173 | | |
174 | | // check for overflow |
175 | 0 | if (totalSize == 0xFFFFFFFF || (frameSize != 0 && totalSize / frameSize != OFstatic_cast(Uint32, imageFrames))) |
176 | 0 | { |
177 | 0 | DCMJPLS_WARN("cannot decompress image because uncompressed representation would exceed maximum possible size of PixelData attribute"); |
178 | 0 | return EC_ElemLengthExceeds32BitField; |
179 | 0 | } |
180 | | |
181 | 0 | if (totalSize & 1) totalSize++; // align on 16-bit word boundary |
182 | | |
183 | | // assume we can cast the codec parameter to what we need |
184 | 0 | const DJLSCodecParameter *djcp = OFreinterpret_cast(const DJLSCodecParameter *, cp); |
185 | | |
186 | | // determine planar configuration for uncompressed data |
187 | 0 | OFString imageSopClass; |
188 | 0 | OFString imagePhotometricInterpretation; |
189 | 0 | dataset->findAndGetOFString(DCM_SOPClassUID, imageSopClass); |
190 | 0 | dataset->findAndGetOFString(DCM_PhotometricInterpretation, imagePhotometricInterpretation); |
191 | | |
192 | | // allocate space for uncompressed pixel data element |
193 | 0 | Uint16 *pixeldata16 = NULL; |
194 | 0 | OFCondition result = uncompressedPixelData.createUint16Array(totalSize/sizeof(Uint16), pixeldata16); |
195 | 0 | if (result.bad()) return result; |
196 | | |
197 | 0 | Uint8 *pixeldata8 = OFreinterpret_cast(Uint8 *, pixeldata16); |
198 | 0 | Sint32 currentFrame = 0; |
199 | 0 | Uint32 currentItem = 1; // item 0 contains the offset table |
200 | 0 | OFBool done = OFFalse; |
201 | 0 | OFBool forceSingleFragmentPerFrame = djcp->getForceSingleFragmentPerFrame(); |
202 | |
|
203 | 0 | while (result.good() && !done) |
204 | 0 | { |
205 | 0 | DCMJPLS_DEBUG("JPEG-LS decoder processes frame " << (currentFrame+1)); |
206 | |
|
207 | 0 | result = decodeFrameNoSwap(pixSeq, djcp, dataset, currentFrame, currentItem, pixeldata8, frameSize, |
208 | 0 | imageFrames, imageColumns, imageRows, imageSamplesPerPixel, bytesPerSample); |
209 | | |
210 | | // check if we should enforce "one fragment per frame" while |
211 | | // decompressing a multi-frame image even if stream suspension occurs |
212 | 0 | if ((result == EC_JLSInvalidCompressedData) && forceSingleFragmentPerFrame) |
213 | 0 | { |
214 | | // frame is incomplete. Nevertheless skip to next frame. |
215 | | // This permits decompression of faulty multi-frame images. |
216 | 0 | DCMJPLS_WARN("JPEG-LS bitstream invalid or incomplete, ignoring (but image is likely to be incomplete)"); |
217 | 0 | result = EC_Normal; |
218 | 0 | } |
219 | |
|
220 | 0 | if (result.good()) |
221 | 0 | { |
222 | | // increment frame number, check if we're finished |
223 | 0 | if (++currentFrame == imageFrames) done = OFTrue; |
224 | 0 | pixeldata8 += frameSize; |
225 | 0 | } |
226 | 0 | } |
227 | | |
228 | | // Number of Frames might have changed in case the previous value was wrong |
229 | 0 | if (result.good() && (numberOfFramesPresent || (imageFrames > 1))) |
230 | 0 | { |
231 | 0 | char numBuf[20]; |
232 | 0 | OFStandard::snprintf(numBuf, sizeof(numBuf), "%ld", OFstatic_cast(long, imageFrames)); |
233 | 0 | result = ((DcmItem *)dataset)->putAndInsertString(DCM_NumberOfFrames, numBuf); |
234 | 0 | } |
235 | |
|
236 | 0 | if (result.good()) |
237 | 0 | { |
238 | | // decompression is complete, finally adjust byte order if necessary |
239 | 0 | if (bytesPerSample == 1) // we're writing bytes into words |
240 | 0 | { |
241 | 0 | result = swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, pixeldata16, OFstatic_cast(Uint32, totalSize), sizeof(Uint16)); |
242 | 0 | } |
243 | 0 | } |
244 | |
|
245 | 0 | if (result.good() && (dataset->ident() == EVR_dataset)) |
246 | 0 | { |
247 | 0 | DcmItem *ditem = OFreinterpret_cast(DcmItem*, dataset); |
248 | | |
249 | | // the following operations do not affect the Image Pixel Module |
250 | | // but other modules such as SOP Common. We only perform these |
251 | | // changes if we're on the main level of the dataset, |
252 | | // which should always identify itself as dataset, not as item. |
253 | 0 | if (djcp->getUIDCreation() == EJLSUC_always) |
254 | 0 | { |
255 | | // create new SOP instance UID |
256 | 0 | result = DcmCodec::newInstance(ditem, NULL, NULL, NULL); |
257 | 0 | } |
258 | | |
259 | | // set Lossy Image Compression to "01" (see DICOM part 3, C.7.6.1.1.5) |
260 | 0 | if (result.good() && (supportedTransferSyntax() == EXS_JPEGLSLossy)) result = ditem->putAndInsertString(DCM_LossyImageCompression, "01"); |
261 | |
|
262 | 0 | } |
263 | |
|
264 | 0 | return result; |
265 | 0 | } |
266 | | |
267 | | |
268 | | OFCondition DJLSDecoderBase::decodeFrame( |
269 | | const DcmRepresentationParameter * /* fromParam */, |
270 | | DcmPixelSequence *fromPixSeq, |
271 | | const DcmCodecParameter *cp, |
272 | | DcmItem *dataset, |
273 | | Uint32 frameNo, |
274 | | Uint32& currentItem, |
275 | | void * buffer, |
276 | | Uint32 bufSize, |
277 | | OFString& decompressedColorModel) const |
278 | 0 | { |
279 | 0 | OFCondition result = EC_Normal; |
280 | | |
281 | | // assume we can cast the codec parameter to what we need |
282 | 0 | const DJLSCodecParameter *djcp = OFreinterpret_cast(const DJLSCodecParameter *, cp); |
283 | | |
284 | | // determine properties of uncompressed dataset |
285 | 0 | Uint16 imageSamplesPerPixel = 0; |
286 | 0 | if (dataset->findAndGetUint16(DCM_SamplesPerPixel, imageSamplesPerPixel).bad()) return EC_TagNotFound; |
287 | | // we only handle one or three samples per pixel |
288 | 0 | if ((imageSamplesPerPixel != 3) && (imageSamplesPerPixel != 1)) return EC_InvalidTag; |
289 | | |
290 | 0 | Uint16 imageRows = 0; |
291 | 0 | if (dataset->findAndGetUint16(DCM_Rows, imageRows).bad()) return EC_TagNotFound; |
292 | 0 | if (imageRows < 1) return EC_InvalidTag; |
293 | | |
294 | 0 | Uint16 imageColumns = 0; |
295 | 0 | if (dataset->findAndGetUint16(DCM_Columns, imageColumns).bad()) return EC_TagNotFound; |
296 | 0 | if (imageColumns < 1) return EC_InvalidTag; |
297 | | |
298 | 0 | Uint16 imageBitsStored = 0; |
299 | 0 | if (dataset->findAndGetUint16(DCM_BitsStored, imageBitsStored).bad()) return EC_TagNotFound; |
300 | | |
301 | 0 | Uint16 imageBitsAllocated = 0; |
302 | 0 | if (dataset->findAndGetUint16(DCM_BitsAllocated, imageBitsAllocated).bad()) return EC_TagNotFound; |
303 | | |
304 | | //we only support up to 16 bits per sample |
305 | 0 | if ((imageBitsStored < 1) || (imageBitsStored > 16)) return EC_JLSUnsupportedBitDepth; |
306 | | |
307 | | // determine the number of bytes per sample (bits allocated) for the de-compressed object. |
308 | 0 | Uint16 bytesPerSample = 1; |
309 | 0 | if (imageBitsStored > 8) bytesPerSample = 2; |
310 | 0 | else if (imageBitsAllocated > 8) bytesPerSample = 2; |
311 | | |
312 | | // number of frames is an optional attribute - we don't mind if it isn't present. |
313 | 0 | Sint32 imageFrames = 0; |
314 | 0 | dataset->findAndGetSint32(DCM_NumberOfFrames, imageFrames).good(); |
315 | |
|
316 | 0 | if (imageFrames >= OFstatic_cast(Sint32, fromPixSeq->card())) |
317 | 0 | imageFrames = fromPixSeq->card() - 1; // limit number of frames to number of pixel items - 1 |
318 | 0 | if (imageFrames < 1) |
319 | 0 | imageFrames = 1; // default in case the number of frames attribute is absent or contains garbage |
320 | | |
321 | | // if the user has provided this information, we trust him. |
322 | | // If the user has passed a zero, try to find out ourselves. |
323 | 0 | if (currentItem == 0) |
324 | 0 | { |
325 | 0 | result = determineStartFragment(frameNo, imageFrames, fromPixSeq, currentItem); |
326 | 0 | } |
327 | |
|
328 | 0 | if (result.good()) |
329 | 0 | { |
330 | | // We got all the data we need from the dataset, let's start decoding |
331 | 0 | DCMJPLS_DEBUG("starting to decode frame " << frameNo << " with fragment " << currentItem); |
332 | 0 | result = decodeFrame(fromPixSeq, djcp, dataset, frameNo, currentItem, buffer, bufSize, |
333 | 0 | imageFrames, imageColumns, imageRows, imageSamplesPerPixel, bytesPerSample); |
334 | 0 | } |
335 | |
|
336 | 0 | if (result.good()) |
337 | 0 | { |
338 | | // retrieve color model from given dataset |
339 | 0 | result = dataset->findAndGetOFString(DCM_PhotometricInterpretation, decompressedColorModel); |
340 | 0 | } |
341 | |
|
342 | 0 | return result; |
343 | 0 | } |
344 | | |
345 | | OFCondition DJLSDecoderBase::decodeFrameNoSwap( |
346 | | DcmPixelSequence * fromPixSeq, |
347 | | const DJLSCodecParameter *cp, |
348 | | DcmItem *dataset, |
349 | | Uint32 frameNo, |
350 | | Uint32& currentItem, |
351 | | void * buffer, |
352 | | Uint32 bufSize, |
353 | | Sint32 imageFrames, |
354 | | Uint16 imageColumns, |
355 | | Uint16 imageRows, |
356 | | Uint16 imageSamplesPerPixel, |
357 | | Uint16 bytesPerSample) |
358 | 0 | { |
359 | 0 | DcmPixelItem *pixItem = NULL; |
360 | 0 | Uint8 * jlsData = NULL; |
361 | 0 | Uint8 * jlsFragmentData = NULL; |
362 | 0 | Uint32 fragmentLength = 0; |
363 | 0 | size_t compressedSize = 0; |
364 | 0 | Uint32 fragmentsForThisFrame = 0; |
365 | 0 | OFCondition result = EC_Normal; |
366 | 0 | OFBool ignoreOffsetTable = cp->ignoreOffsetTable(); |
367 | | |
368 | | // compute the number of JPEG-LS fragments we need in order to decode the next frame |
369 | 0 | fragmentsForThisFrame = computeNumberOfFragments(imageFrames, frameNo, currentItem, ignoreOffsetTable, fromPixSeq); |
370 | 0 | if (fragmentsForThisFrame == 0) result = EC_JLSCannotComputeNumberOfFragments; |
371 | | |
372 | | // determine planar configuration for uncompressed data |
373 | 0 | OFString imageSopClass; |
374 | 0 | OFString imagePhotometricInterpretation; |
375 | 0 | dataset->findAndGetOFString(DCM_SOPClassUID, imageSopClass); |
376 | 0 | dataset->findAndGetOFString(DCM_PhotometricInterpretation, imagePhotometricInterpretation); |
377 | 0 | Uint16 imagePlanarConfiguration = 0; // 0 is color-by-pixel, 1 is color-by-plane |
378 | |
|
379 | 0 | if (imageSamplesPerPixel > 1) |
380 | 0 | { |
381 | | // get planar configuration from dataset |
382 | 0 | imagePlanarConfiguration = 2; // invalid value |
383 | | // warn on invalid value; should we also warn on missing attribute or value? |
384 | 0 | if (dataset->findAndGetUint16(DCM_PlanarConfiguration, imagePlanarConfiguration).good() && (imagePlanarConfiguration != 0)) |
385 | 0 | DCMJPLS_WARN("invalid value for PlanarConfiguration " << DCM_PlanarConfiguration << ", should be \"0\""); |
386 | |
|
387 | 0 | switch (cp->getPlanarConfiguration()) |
388 | 0 | { |
389 | 0 | case EJLSPC_restore: |
390 | | // determine auto default if not found or invalid |
391 | 0 | if (imagePlanarConfiguration > 1) |
392 | 0 | imagePlanarConfiguration = determinePlanarConfiguration(imageSopClass, imagePhotometricInterpretation); |
393 | 0 | break; |
394 | 0 | case EJLSPC_auto: |
395 | 0 | imagePlanarConfiguration = determinePlanarConfiguration(imageSopClass, imagePhotometricInterpretation); |
396 | 0 | break; |
397 | 0 | case EJLSPC_colorByPixel: |
398 | 0 | imagePlanarConfiguration = 0; |
399 | 0 | break; |
400 | 0 | case EJLSPC_colorByPlane: |
401 | 0 | imagePlanarConfiguration = 1; |
402 | 0 | break; |
403 | 0 | } |
404 | 0 | } |
405 | | |
406 | | // get the size of all the fragments |
407 | 0 | if (result.good()) |
408 | 0 | { |
409 | | // Don't modify the original values for now |
410 | 0 | Uint32 fragmentsForThisFrame2 = fragmentsForThisFrame; |
411 | 0 | Uint32 currentItem2 = currentItem; |
412 | |
|
413 | 0 | while (result.good() && fragmentsForThisFrame2--) |
414 | 0 | { |
415 | 0 | result = fromPixSeq->getItem(pixItem, currentItem2++); |
416 | 0 | if (result.good() && pixItem) |
417 | 0 | { |
418 | 0 | fragmentLength = pixItem->getLength(); |
419 | 0 | if (result.good()) |
420 | 0 | compressedSize += fragmentLength; |
421 | 0 | } |
422 | 0 | } /* while */ |
423 | 0 | } |
424 | | |
425 | | // get the compressed data |
426 | 0 | if (result.good()) |
427 | 0 | { |
428 | 0 | Uint32 offset = 0; |
429 | 0 | jlsData = new Uint8[compressedSize]; |
430 | |
|
431 | 0 | while (result.good() && fragmentsForThisFrame--) |
432 | 0 | { |
433 | 0 | result = fromPixSeq->getItem(pixItem, currentItem++); |
434 | 0 | if (result.good() && pixItem) |
435 | 0 | { |
436 | 0 | fragmentLength = pixItem->getLength(); |
437 | 0 | result = pixItem->getUint8Array(jlsFragmentData); |
438 | 0 | if (result.good() && jlsFragmentData) |
439 | 0 | { |
440 | 0 | memcpy(&jlsData[offset], jlsFragmentData, fragmentLength); |
441 | 0 | offset += fragmentLength; |
442 | 0 | } |
443 | 0 | } |
444 | 0 | } /* while */ |
445 | 0 | } |
446 | |
|
447 | 0 | if (result.good()) |
448 | 0 | { |
449 | 0 | JlsParameters params; |
450 | 0 | JLS_ERROR err; |
451 | |
|
452 | 0 | err = JpegLsReadHeader(jlsData, compressedSize, ¶ms); |
453 | 0 | result = DJLSError::convert(err); |
454 | |
|
455 | 0 | if (result.good()) |
456 | 0 | { |
457 | 0 | if (params.width != imageColumns) result = EC_JLSImageDataMismatch; |
458 | 0 | else if (params.height != imageRows) result = EC_JLSImageDataMismatch; |
459 | 0 | else if (params.components != imageSamplesPerPixel) result = EC_JLSImageDataMismatch; |
460 | 0 | else if ((bytesPerSample == 1) && (params.bitspersample > 8)) result = EC_JLSImageDataMismatch; |
461 | 0 | else if ((bytesPerSample == 2) && (params.bitspersample <= 8)) result = EC_JLSImageDataMismatch; |
462 | 0 | } |
463 | |
|
464 | 0 | if (!result.good()) |
465 | 0 | { |
466 | 0 | delete[] jlsData; |
467 | 0 | } |
468 | 0 | else |
469 | 0 | { |
470 | 0 | err = JpegLsDecode(buffer, bufSize, jlsData, compressedSize, ¶ms); |
471 | 0 | result = DJLSError::convert(err); |
472 | 0 | delete[] jlsData; |
473 | |
|
474 | 0 | if (result.good() && imageSamplesPerPixel == 3) |
475 | 0 | { |
476 | 0 | if (params.colorTransform != 0) |
477 | 0 | { |
478 | 0 | DCMJPLS_WARN("Color Transformation " << params.colorTransform << " is a non-standard HP/JPEG-LS extension"); |
479 | 0 | } |
480 | 0 | if (imagePlanarConfiguration == 1 && params.ilv != ILV_NONE) |
481 | 0 | { |
482 | | // The dataset says this should be planarConfiguration == 1, but |
483 | | // it isn't -> convert it. |
484 | 0 | DCMJPLS_DEBUG("different planar configuration in JPEG-LS bitstream, converting to \"1\""); |
485 | 0 | if (bytesPerSample == 1) |
486 | 0 | result = createPlanarConfiguration1Byte(OFreinterpret_cast(Uint8*, buffer), imageColumns, imageRows); |
487 | 0 | else |
488 | 0 | result = createPlanarConfiguration1Word(OFreinterpret_cast(Uint16*, buffer), imageColumns, imageRows); |
489 | 0 | } |
490 | 0 | else if (imagePlanarConfiguration == 0 && params.ilv != ILV_SAMPLE && params.ilv != ILV_LINE) |
491 | 0 | { |
492 | | // The dataset says this should be planarConfiguration == 0, but |
493 | | // it isn't -> convert it. |
494 | 0 | DCMJPLS_DEBUG("different planar configuration in JPEG-LS bitstream, converting to \"0\""); |
495 | 0 | if (bytesPerSample == 1) |
496 | 0 | result = createPlanarConfiguration0Byte(OFreinterpret_cast(Uint8*, buffer), imageColumns, imageRows); |
497 | 0 | else |
498 | 0 | result = createPlanarConfiguration0Word(OFreinterpret_cast(Uint16*, buffer), imageColumns, imageRows); |
499 | 0 | } |
500 | 0 | } |
501 | | |
502 | | // update planar configuration if we are decoding a color image |
503 | 0 | if (result.good() && (imageSamplesPerPixel > 1)) |
504 | 0 | { |
505 | 0 | dataset->putAndInsertUint16(DCM_PlanarConfiguration, imagePlanarConfiguration); |
506 | 0 | } |
507 | 0 | } |
508 | 0 | } |
509 | |
|
510 | 0 | return result; |
511 | 0 | } |
512 | | |
513 | | OFCondition DJLSDecoderBase::decodeFrame( |
514 | | DcmPixelSequence * fromPixSeq, |
515 | | const DJLSCodecParameter *cp, |
516 | | DcmItem *dataset, |
517 | | Uint32 frameNo, |
518 | | Uint32& currentItem, |
519 | | void * buffer, |
520 | | Uint32 bufSize, |
521 | | Sint32 imageFrames, |
522 | | Uint16 imageColumns, |
523 | | Uint16 imageRows, |
524 | | Uint16 imageSamplesPerPixel, |
525 | | Uint16 bytesPerSample) |
526 | 0 | { |
527 | 0 | OFCondition result = decodeFrameNoSwap(fromPixSeq, cp, dataset, frameNo, currentItem, buffer, bufSize, imageFrames, imageColumns, imageRows, imageSamplesPerPixel, bytesPerSample); |
528 | 0 | if (result.good()) |
529 | 0 | { |
530 | | // decompression is complete, finally adjust byte order if necessary |
531 | 0 | if (bytesPerSample == 1) // we're writing bytes into words |
532 | 0 | { |
533 | 0 | if ((gLocalByteOrder == EBO_BigEndian) && (bufSize & 1)) |
534 | 0 | { |
535 | 0 | DCMJPLS_WARN("Size of frame buffer is odd, cannot correct byte order for last pixel value"); |
536 | 0 | } |
537 | 0 | result = swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, buffer, bufSize, sizeof(Uint16)); |
538 | 0 | } |
539 | 0 | } |
540 | 0 | return result; |
541 | 0 | } |
542 | | |
543 | | OFCondition DJLSDecoderBase::encode( |
544 | | const Uint16 * /* pixelData */, |
545 | | const Uint32 /* length */, |
546 | | const DcmRepresentationParameter * /* toRepParam */, |
547 | | DcmPixelSequence * & /* pixSeq */, |
548 | | const DcmCodecParameter * /* cp */, |
549 | | DcmStack & /* objStack */, |
550 | | OFBool& /* removeOldRep */) const |
551 | 0 | { |
552 | | // we are a decoder only |
553 | 0 | return EC_IllegalCall; |
554 | 0 | } |
555 | | |
556 | | |
557 | | OFCondition DJLSDecoderBase::encode( |
558 | | const E_TransferSyntax /* fromRepType */, |
559 | | const DcmRepresentationParameter * /* fromRepParam */, |
560 | | DcmPixelSequence * /* fromPixSeq */, |
561 | | const DcmRepresentationParameter * /* toRepParam */, |
562 | | DcmPixelSequence * & /* toPixSeq */, |
563 | | const DcmCodecParameter * /* cp */, |
564 | | DcmStack & /* objStack */, |
565 | | OFBool& /* removeOldRep */) const |
566 | 0 | { |
567 | | // we don't support re-coding for now. |
568 | 0 | return EC_IllegalCall; |
569 | 0 | } |
570 | | |
571 | | |
572 | | OFCondition DJLSDecoderBase::determineDecompressedColorModel( |
573 | | const DcmRepresentationParameter * /* fromParam */, |
574 | | DcmPixelSequence * /* fromPixSeq */, |
575 | | const DcmCodecParameter * /* cp */, |
576 | | DcmItem * dataset, |
577 | | OFString & decompressedColorModel) const |
578 | 0 | { |
579 | 0 | OFCondition result = EC_IllegalParameter; |
580 | 0 | if (dataset != NULL) |
581 | 0 | { |
582 | | // retrieve color model from given dataset |
583 | 0 | result = dataset->findAndGetOFString(DCM_PhotometricInterpretation, decompressedColorModel); |
584 | 0 | if (result == EC_TagNotFound) |
585 | 0 | { |
586 | 0 | DCMJPLS_WARN("mandatory element PhotometricInterpretation " << DCM_PhotometricInterpretation << " is missing"); |
587 | 0 | result = EC_MissingAttribute; |
588 | 0 | } |
589 | 0 | else if (result.bad()) |
590 | 0 | { |
591 | 0 | DCMJPLS_WARN("cannot retrieve value of element PhotometricInterpretation " << DCM_PhotometricInterpretation << ": " << result.text()); |
592 | 0 | } |
593 | 0 | else if (decompressedColorModel.empty()) |
594 | 0 | { |
595 | 0 | DCMJPLS_WARN("no value for mandatory element PhotometricInterpretation " << DCM_PhotometricInterpretation); |
596 | 0 | result = EC_MissingValue; |
597 | 0 | } |
598 | 0 | } |
599 | 0 | return result; |
600 | 0 | } |
601 | | |
602 | | |
603 | | Uint16 DJLSDecoderBase::determinePlanarConfiguration( |
604 | | const OFString& sopClassUID, |
605 | | const OFString& photometricInterpretation) |
606 | 0 | { |
607 | | // Hardcopy Color Image always requires color-by-plane |
608 | 0 | if (sopClassUID == UID_RETIRED_HardcopyColorImageStorage) return 1; |
609 | | |
610 | | // The 1996 Ultrasound Image IODs require color-by-plane if color model is YBR_FULL. |
611 | 0 | if (photometricInterpretation == "YBR_FULL") |
612 | 0 | { |
613 | 0 | if ((sopClassUID == UID_UltrasoundMultiframeImageStorage) |
614 | 0 | ||(sopClassUID == UID_UltrasoundImageStorage)) return 1; |
615 | 0 | } |
616 | | |
617 | | // default for all other cases |
618 | 0 | return 0; |
619 | 0 | } |
620 | | |
621 | | Uint32 DJLSDecoderBase::computeNumberOfFragments( |
622 | | Sint32 numberOfFrames, |
623 | | Uint32 currentFrame, |
624 | | Uint32 startItem, |
625 | | OFBool ignoreOffsetTable, |
626 | | DcmPixelSequence * pixSeq) |
627 | 0 | { |
628 | |
|
629 | 0 | unsigned long numItems = pixSeq->card(); |
630 | 0 | DcmPixelItem *pixItem = NULL; |
631 | | |
632 | | // We first check the simple cases, that is, a single-frame image, |
633 | | // the last frame of a multi-frame image and the standard case where we do have |
634 | | // a single fragment per frame. |
635 | 0 | if ((numberOfFrames <= 1) || (currentFrame + 1 == OFstatic_cast(Uint32, numberOfFrames))) |
636 | 0 | { |
637 | | // single-frame image or last frame. All remaining fragments belong to this frame |
638 | 0 | return (numItems - startItem); |
639 | 0 | } |
640 | 0 | if (OFstatic_cast(Uint32, numberOfFrames + 1) == numItems) |
641 | 0 | { |
642 | | // multi-frame image with one fragment per frame |
643 | 0 | return 1; |
644 | 0 | } |
645 | | |
646 | 0 | OFCondition result = EC_Normal; |
647 | 0 | if (! ignoreOffsetTable) |
648 | 0 | { |
649 | | // We do have a multi-frame image with multiple fragments per frame, and we are |
650 | | // not working on the last frame. Let's check the offset table if present. |
651 | 0 | result = pixSeq->getItem(pixItem, 0); |
652 | 0 | if (result.good() && pixItem) |
653 | 0 | { |
654 | 0 | Uint32 offsetTableLength = pixItem->getLength(); |
655 | 0 | if (offsetTableLength == (OFstatic_cast(Uint32, numberOfFrames) * 4)) |
656 | 0 | { |
657 | | // offset table is non-empty and contains one entry per frame |
658 | 0 | Uint8 *offsetData = NULL; |
659 | 0 | result = pixItem->getUint8Array(offsetData); |
660 | 0 | if (result.good() && offsetData) |
661 | 0 | { |
662 | | // now we can access the offset table |
663 | 0 | Uint32 *offsetData32 = OFreinterpret_cast(Uint32 *, offsetData); |
664 | | |
665 | | // extract the offset for the NEXT frame. This offset is guaranteed to exist |
666 | | // because the "last frame/single frame" case is handled above. |
667 | 0 | Uint32 offset = offsetData32[currentFrame+1]; |
668 | | |
669 | | // convert to local endian byte order (always little endian in file) |
670 | 0 | swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, &offset, sizeof(Uint32), sizeof(Uint32)); |
671 | | |
672 | | // determine index of start fragment for next frame |
673 | 0 | Uint32 byteCount = 0; |
674 | 0 | Uint32 fragmentIndex = 1; |
675 | 0 | while ((byteCount < offset) && (fragmentIndex < numItems)) |
676 | 0 | { |
677 | 0 | pixItem = NULL; |
678 | 0 | result = pixSeq->getItem(pixItem, fragmentIndex++); |
679 | 0 | if (result.good() && pixItem) |
680 | 0 | { |
681 | 0 | byteCount += pixItem->getLength() + 8; // add 8 bytes for item tag and length |
682 | 0 | if ((byteCount == offset) && (fragmentIndex > startItem)) |
683 | 0 | { |
684 | | // bingo, we have found the offset for the next frame |
685 | 0 | return fragmentIndex - startItem; |
686 | 0 | } |
687 | 0 | } |
688 | 0 | else break; /* something went wrong, break out of while loop */ |
689 | 0 | } /* while */ |
690 | 0 | } |
691 | 0 | } |
692 | 0 | } |
693 | 0 | } |
694 | | |
695 | | // So we have a multi-frame image with multiple fragments per frame and the |
696 | | // offset table is empty or wrong. Our last chance is to peek into the JPEG-LS |
697 | | // bitstream and identify the start of the next frame. |
698 | 0 | Uint32 nextItem = startItem; |
699 | 0 | Uint8 *fragmentData = NULL; |
700 | 0 | while (++nextItem < numItems) |
701 | 0 | { |
702 | 0 | pixItem = NULL; |
703 | 0 | result = pixSeq->getItem(pixItem, nextItem); |
704 | 0 | if (result.good() && pixItem) |
705 | 0 | { |
706 | 0 | fragmentData = NULL; |
707 | 0 | result = pixItem->getUint8Array(fragmentData); |
708 | 0 | if (result.good() && fragmentData && (pixItem->getLength() > 3)) |
709 | 0 | { |
710 | 0 | if (isJPEGLSStartOfImage(fragmentData)) |
711 | 0 | { |
712 | | // found a JPEG-LS SOI marker. Assume that this is the start of the next frame. |
713 | 0 | return (nextItem - startItem); |
714 | 0 | } |
715 | 0 | } |
716 | 0 | else break; /* something went wrong, break out of while loop */ |
717 | 0 | } |
718 | 0 | else break; /* something went wrong, break out of while loop */ |
719 | 0 | } |
720 | | |
721 | | // We're bust. No way to determine the number of fragments per frame. |
722 | 0 | return 0; |
723 | 0 | } |
724 | | |
725 | | |
726 | | OFBool DJLSDecoderBase::isJPEGLSStartOfImage(Uint8 *fragmentData) |
727 | 0 | { |
728 | | // A valid JPEG-LS bitstream will always start with an SOI marker FFD8, followed |
729 | | // by either an SOF55 (FFF7), COM (FFFE) or APPn (FFE0-FFEF) marker. |
730 | 0 | if ((*fragmentData++) != 0xFF) return OFFalse; |
731 | 0 | if ((*fragmentData++) != 0xD8) return OFFalse; |
732 | 0 | if ((*fragmentData++) != 0xFF) return OFFalse; |
733 | 0 | if ((*fragmentData == 0xF7)||(*fragmentData == 0xFE)||((*fragmentData & 0xF0) == 0xE0)) |
734 | 0 | { |
735 | 0 | return OFTrue; |
736 | 0 | } |
737 | 0 | return OFFalse; |
738 | 0 | } |
739 | | |
740 | | |
741 | | OFCondition DJLSDecoderBase::createPlanarConfiguration1Byte( |
742 | | Uint8 *imageFrame, |
743 | | Uint16 columns, |
744 | | Uint16 rows) |
745 | 0 | { |
746 | 0 | if (imageFrame == NULL) return EC_IllegalCall; |
747 | | |
748 | 0 | unsigned long numPixels = columns * rows; |
749 | 0 | if (numPixels == 0) return EC_IllegalCall; |
750 | | |
751 | 0 | Uint8 *buf = new Uint8[3*numPixels + 3]; |
752 | 0 | if (buf) |
753 | 0 | { |
754 | 0 | memcpy(buf, imageFrame, (size_t)(3*numPixels)); |
755 | 0 | Uint8 *s = buf; // source |
756 | 0 | Uint8 *r = imageFrame; // red plane |
757 | 0 | Uint8 *g = imageFrame + numPixels; // green plane |
758 | 0 | Uint8 *b = imageFrame + (2*numPixels); // blue plane |
759 | 0 | for (unsigned long i=numPixels; i; i--) |
760 | 0 | { |
761 | 0 | *r++ = *s++; |
762 | 0 | *g++ = *s++; |
763 | 0 | *b++ = *s++; |
764 | 0 | } |
765 | 0 | delete[] buf; |
766 | 0 | } else return EC_MemoryExhausted; |
767 | 0 | return EC_Normal; |
768 | 0 | } |
769 | | |
770 | | |
771 | | OFCondition DJLSDecoderBase::createPlanarConfiguration1Word( |
772 | | Uint16 *imageFrame, |
773 | | Uint16 columns, |
774 | | Uint16 rows) |
775 | 0 | { |
776 | 0 | if (imageFrame == NULL) return EC_IllegalCall; |
777 | | |
778 | 0 | unsigned long numPixels = columns * rows; |
779 | 0 | if (numPixels == 0) return EC_IllegalCall; |
780 | | |
781 | 0 | Uint16 *buf = new Uint16[3*numPixels + 3]; |
782 | 0 | if (buf) |
783 | 0 | { |
784 | 0 | memcpy(buf, imageFrame, (size_t)(3*numPixels*sizeof(Uint16))); |
785 | 0 | Uint16 *s = buf; // source |
786 | 0 | Uint16 *r = imageFrame; // red plane |
787 | 0 | Uint16 *g = imageFrame + numPixels; // green plane |
788 | 0 | Uint16 *b = imageFrame + (2*numPixels); // blue plane |
789 | 0 | for (unsigned long i=numPixels; i; i--) |
790 | 0 | { |
791 | 0 | *r++ = *s++; |
792 | 0 | *g++ = *s++; |
793 | 0 | *b++ = *s++; |
794 | 0 | } |
795 | 0 | delete[] buf; |
796 | 0 | } else return EC_MemoryExhausted; |
797 | 0 | return EC_Normal; |
798 | 0 | } |
799 | | |
800 | | OFCondition DJLSDecoderBase::createPlanarConfiguration0Byte( |
801 | | Uint8 *imageFrame, |
802 | | Uint16 columns, |
803 | | Uint16 rows) |
804 | 0 | { |
805 | 0 | if (imageFrame == NULL) return EC_IllegalCall; |
806 | | |
807 | 0 | unsigned long numPixels = columns * rows; |
808 | 0 | if (numPixels == 0) return EC_IllegalCall; |
809 | | |
810 | 0 | Uint8 *buf = new Uint8[3*numPixels + 3]; |
811 | 0 | if (buf) |
812 | 0 | { |
813 | 0 | memcpy(buf, imageFrame, (size_t)(3*numPixels)); |
814 | 0 | Uint8 *t = imageFrame; // target |
815 | 0 | Uint8 *r = buf; // red plane |
816 | 0 | Uint8 *g = buf + numPixels; // green plane |
817 | 0 | Uint8 *b = buf + (2*numPixels); // blue plane |
818 | 0 | for (unsigned long i=numPixels; i; i--) |
819 | 0 | { |
820 | 0 | *t++ = *r++; |
821 | 0 | *t++ = *g++; |
822 | 0 | *t++ = *b++; |
823 | 0 | } |
824 | 0 | delete[] buf; |
825 | 0 | } else return EC_MemoryExhausted; |
826 | 0 | return EC_Normal; |
827 | 0 | } |
828 | | |
829 | | |
830 | | OFCondition DJLSDecoderBase::createPlanarConfiguration0Word( |
831 | | Uint16 *imageFrame, |
832 | | Uint16 columns, |
833 | | Uint16 rows) |
834 | 0 | { |
835 | 0 | if (imageFrame == NULL) return EC_IllegalCall; |
836 | | |
837 | 0 | unsigned long numPixels = columns * rows; |
838 | 0 | if (numPixels == 0) return EC_IllegalCall; |
839 | | |
840 | 0 | Uint16 *buf = new Uint16[3*numPixels + 3]; |
841 | 0 | if (buf) |
842 | 0 | { |
843 | 0 | memcpy(buf, imageFrame, (size_t)(3*numPixels*sizeof(Uint16))); |
844 | 0 | Uint16 *t = imageFrame; // target |
845 | 0 | Uint16 *r = buf; // red plane |
846 | 0 | Uint16 *g = buf + numPixels; // green plane |
847 | 0 | Uint16 *b = buf + (2*numPixels); // blue plane |
848 | 0 | for (unsigned long i=numPixels; i; i--) |
849 | 0 | { |
850 | 0 | *t++ = *r++; |
851 | 0 | *t++ = *g++; |
852 | 0 | *t++ = *b++; |
853 | 0 | } |
854 | 0 | delete[] buf; |
855 | 0 | } else return EC_MemoryExhausted; |
856 | 0 | return EC_Normal; |
857 | 0 | } |