From e88504789ad9a7937d5674ff8a2d1eb18eb6c55a Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 16 Apr 2025 18:06:20 -0400 Subject: [PATCH 1/2] feat: Add JPEG XL encoding --- .../lib/operation/adapter/transcodeImage.js | 62 +++++++++---------- .../lib/operation/validateMetadata.js | 4 +- .../controllers/server/commonControllers.mjs | 22 +++++-- 3 files changed, 48 insertions(+), 40 deletions(-) diff --git a/packages/static-wado-creator/lib/operation/adapter/transcodeImage.js b/packages/static-wado-creator/lib/operation/adapter/transcodeImage.js index 0b84f72..ad5d7de 100644 --- a/packages/static-wado-creator/lib/operation/adapter/transcodeImage.js +++ b/packages/static-wado-creator/lib/operation/adapter/transcodeImage.js @@ -15,7 +15,6 @@ const jpegBeforeEncode = (options, encoder) => { const quality = options.quality ?? 99; // First value is to encode as reversible lossless colour return (encoder) => { - console.log("Calling setQuality", quality, !!encoder.setQuality); encoder.setQuality(quality); }; }; @@ -67,6 +66,10 @@ const transcodeDestinationMap = { transcodeOp: transcodeOp.encode, beforeEncode: htj2kBeforeEncode({ lossy: true }), }, + jxl: { + transferSyntaxUid: "1.2.840.10008.1.2.4.110", + transcodeOp: transcodeOp.encode, + }, jpeg: { transferSyntaxUid: "1.2.840.10008.1.2.4.50", transcodeOp: transcodeOp.encode, @@ -115,6 +118,10 @@ const transcodeSourceMap = { transcodeOp: transcodeOp.decode, alias: "jhc", }, + "1.2.840.10008.1.2.4.110": { + transcodeOp: transcodeOp.decode, + alias: "jxl", + }, "1.2.840.10008.1.2.4.202": { transcodeOp: transcodeOp.decode, alias: "jhc", @@ -135,7 +142,7 @@ function getDestinationTranscoder(id) { const destinationTranscoderEntry = transcodeDestinationMap[id] || Object.values(transcodeDestinationMap).find( - (value) => value.transferSyntaxUid === id, + (value) => value.transferSyntaxUid === id ); return destinationTranscoderEntry; } @@ -149,7 +156,7 @@ function getDestinationTranscoder(id) { function getTranscoder( transferSyntaxUid, { contentType: greyContentType, colorContentType }, - samplesPerPixel, + samplesPerPixel ) { const contentType = samplesPerPixel === 3 ? colorContentType : greyContentType; @@ -188,7 +195,7 @@ function shouldTranscodeImageFrame(id, options, samplesPerPixel) { console.verbose( "Not transcoding because no decoder found for", transferSyntaxUid, - samplesPerPixel, + samplesPerPixel ); return false; } @@ -199,7 +206,7 @@ function shouldTranscodeImageFrame(id, options, samplesPerPixel) { "Not transcoding because recompress", recompress, "does not include", - transcoder.alias, + transcoder.alias ); return false; } @@ -262,7 +269,7 @@ function scale(imageFrame, imageInfo) { samplesPerPixel, }; dest.pixelData = new ArrayConstructor( - dest.rows * dest.columns * samplesPerPixel, + dest.rows * dest.columns * samplesPerPixel ); replicate(src, dest); @@ -295,7 +302,7 @@ async function generateLossyImage(id, decoded, options) { ...id, imageFrameRootPath: id.imageFrameRootPath.replace( "frames", - options.alternateName, + options.alternateName ), transferSyntaxUid: transcodeDestinationMap.jhc.transferSyntaxUid, }; @@ -329,13 +336,7 @@ async function generateLossyImage(id, decoded, options) { imageFrame, imageInfo, lossyId.transferSyntaxUid, - encodeOptions, - ); - console.log( - "Encoded alternate", - lossyId.transferSyntaxUid, - "of size", - lossyEncoding.imageFrame.length, + encodeOptions ); return { id: lossyId, imageFrame: lossyEncoding.imageFrame }; @@ -363,7 +364,7 @@ async function transcodeImageFrame( targetIdSrc, imageFrame, dataSet, - options = {}, + options = {} ) { let targetId = targetIdSrc; let result = {}; @@ -386,7 +387,7 @@ async function transcodeImageFrame( const transcoder = getTranscoder( id.transferSyntaxUid, options, - samplesPerPixel, + samplesPerPixel ); // Don't transcode if not required @@ -397,7 +398,7 @@ async function transcodeImageFrame( console.verbose( "Image is already in", targetId.transferSyntaxUid, - "not transcoding", + "not transcoding" ); return { id, @@ -411,6 +412,7 @@ async function transcodeImageFrame( const imageInfo = getImageInfo(dataSet); let done = false; let processResultMsg = ""; + const encodeOptions = { beforeEncode: transcoder.beforeEncode, }; @@ -421,40 +423,34 @@ async function transcodeImageFrame( case transcodeOp.transcode: transcodeLog( options, - `Full transcoding image from \x1b[43m${id.transferSyntaxUid}\x1b[0m to \x1b[43m${targetId.transferSyntaxUid}\x1b[0m`, + `Full transcoding image from \x1b[43m${id.transferSyntaxUid}\x1b[0m to \x1b[43m${targetId.transferSyntaxUid}\x1b[0m` ); decoded = await dicomCodec.decode( imageFrame, imageInfo, - id.transferSyntaxUid, + id.transferSyntaxUid ); result = await dicomCodec.encode( decoded.imageFrame, decoded.imageInfo, targetId.transferSyntaxUid, - encodeOptions, + encodeOptions ); - console.log( - "transcoded image to", - targetId.transferSyntaxUid, - "of size", - result.imageFrame.length, - ); processResultMsg = `Transcoding finished`; break; case transcodeOp.encode: transcodeLog( options, - `Encoding image to \x1b[43m${targetId.transferSyntaxUid}\x1b[0m`, + `Encoding image to \x1b[43m${targetId.transferSyntaxUid}\x1b[0m` ); result = await dicomCodec.encode( imageFrame, imageInfo, targetId.transferSyntaxUid, - encodeOptions, + encodeOptions ); processResultMsg = `Encoding finished`; @@ -462,12 +458,12 @@ async function transcodeImageFrame( case transcodeOp.decode: transcodeLog( options, - `Decoding image from \x1b[43m${id.transferSyntaxUid}\x1b[0m`, + `Decoding image from \x1b[43m${id.transferSyntaxUid}\x1b[0m` ); result = await dicomCodec.decode( imageFrame, imageInfo, - id.transferSyntaxUid, + id.transferSyntaxUid ); processResultMsg = `Decoding finished`; @@ -478,7 +474,7 @@ async function transcodeImageFrame( done = !!result.imageFrame; } catch (e) { - transcodeLog(options, "Failed to transcode image", e); + console.warn("Failed to transcode image", e); } // recover transfer syntax @@ -519,7 +515,7 @@ function transcodeId(id, options, samplesPerPixel) { const { transferSyntaxUid } = getTranscoder( id.transferSyntaxUid, options, - samplesPerPixel, + samplesPerPixel ); targetId.transferSyntaxUid = transferSyntaxUid; @@ -552,7 +548,7 @@ function transcodeMetadata(metadata, id, options) { Tags.setValue( result, Tags.AvailableTransferSyntaxUID, - transcodedId.transferSyntaxUid, + transcodedId.transferSyntaxUid ); console.verbose("Apply available tsuid", transcodeId.transferSyntaxUid); } diff --git a/packages/static-wado-creator/lib/operation/validateMetadata.js b/packages/static-wado-creator/lib/operation/validateMetadata.js index c90a46c..3616c43 100644 --- a/packages/static-wado-creator/lib/operation/validateMetadata.js +++ b/packages/static-wado-creator/lib/operation/validateMetadata.js @@ -8,7 +8,7 @@ function validateMetadata(options) { return function (id, result) { const getValue = Tags.getValue.bind(null, result.metadata); if (verify) { - console.verbose("Validating metadata", id, result); + console.verbose("Validating metadata", id); if (id.studyInstanceUid.length > 64) { throw new Error(`StudyInstanceUID too long: ${id.studyInstanceUid}`); } @@ -36,7 +36,7 @@ function validateMetadata(options) { "\r\n--boundary-response\r\n" + "content-type: application/json\r\n\r\n" + responseMessage + - "\r\n--boundary-response--\r\n", + "\r\n--boundary-response--\r\n" ); } }; diff --git a/packages/static-wado-webserver/lib/controllers/server/commonControllers.mjs b/packages/static-wado-webserver/lib/controllers/server/commonControllers.mjs index 086001e..d3da0e4 100644 --- a/packages/static-wado-webserver/lib/controllers/server/commonControllers.mjs +++ b/packages/static-wado-webserver/lib/controllers/server/commonControllers.mjs @@ -24,11 +24,15 @@ export function defaultPostController(params) { const form = formidable({ multiples: true }); form.on("file", (_formname, file) => { const { filepath, mimetype } = file; - storedInstances.push({ - filepath, - mimetype, - result: storeServices.storeFileInstance(filepath, mimetype, params), - }); + try { + storedInstances.push({ + filepath, + mimetype, + result: storeServices.storeFileInstance(filepath, mimetype, params), + }); + } catch (e) { + console.warn("Unable to even try storing", filepath, e); + } }); form.parse(req, async (err, fields, files) => { @@ -71,6 +75,7 @@ export function defaultPostController(params) { }; } if (!result) { + console.log("Creating new result"); result = { ...itemResult, ReferencedSOPSequence: [], @@ -78,15 +83,22 @@ export function defaultPostController(params) { }; } if (itemResult.ReferencedSOPSequence?.length) { + console.log("Succeeded result"); result.ReferencedSOPSequence.push( itemResult.ReferencedSOPSequence[0] ); } if (itemResult.FailedSOPSequence?.length) { + console.log("Failed result"); result.FailedSOPSequence.push(itemResult.FailedSOPSequence[0]); } } + if (!result) { + console.error("No results for upload"); + res.status(500).send("No results found"); + return; + } if (result.FailedSOPSequence?.length === 0) { delete result.FailedSOPSequence; } From 05cbd4be00d09aadda1514a0f8125ad8d685cdfb Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 16 Apr 2025 18:10:32 -0400 Subject: [PATCH 2/2] Undo unneeded changes fro JPEG XL --- .../controllers/server/commonControllers.mjs | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/packages/static-wado-webserver/lib/controllers/server/commonControllers.mjs b/packages/static-wado-webserver/lib/controllers/server/commonControllers.mjs index d3da0e4..086001e 100644 --- a/packages/static-wado-webserver/lib/controllers/server/commonControllers.mjs +++ b/packages/static-wado-webserver/lib/controllers/server/commonControllers.mjs @@ -24,15 +24,11 @@ export function defaultPostController(params) { const form = formidable({ multiples: true }); form.on("file", (_formname, file) => { const { filepath, mimetype } = file; - try { - storedInstances.push({ - filepath, - mimetype, - result: storeServices.storeFileInstance(filepath, mimetype, params), - }); - } catch (e) { - console.warn("Unable to even try storing", filepath, e); - } + storedInstances.push({ + filepath, + mimetype, + result: storeServices.storeFileInstance(filepath, mimetype, params), + }); }); form.parse(req, async (err, fields, files) => { @@ -75,7 +71,6 @@ export function defaultPostController(params) { }; } if (!result) { - console.log("Creating new result"); result = { ...itemResult, ReferencedSOPSequence: [], @@ -83,22 +78,15 @@ export function defaultPostController(params) { }; } if (itemResult.ReferencedSOPSequence?.length) { - console.log("Succeeded result"); result.ReferencedSOPSequence.push( itemResult.ReferencedSOPSequence[0] ); } if (itemResult.FailedSOPSequence?.length) { - console.log("Failed result"); result.FailedSOPSequence.push(itemResult.FailedSOPSequence[0]); } } - if (!result) { - console.error("No results for upload"); - res.status(500).send("No results found"); - return; - } if (result.FailedSOPSequence?.length === 0) { delete result.FailedSOPSequence; }