Skip to content

Commit 9868cc3

Browse files
authored
Feat/htj2k (RadicalImaging#26)
* feat(htj2k):Write single part and multipart HTJ2K images * Fix the unit tests and lint changes * Moved things arounnd so it can be used by test code
1 parent b6aa9a5 commit 9868cc3

33 files changed

+151
-48
lines changed

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"prettier-eslint": "^13.0.0"
2727
},
2828
"dependencies": {
29+
"@cornerstonejs/codec-openjph": "^1.0.3",
30+
"@cornerstonejs/dicom-codec": "^0.1.5",
2931
"husky": "^7.0.4"
3032
},
3133
"workspaces": [

packages/s3-deploy/lib/S3Ops.mjs

+2-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import path from "path";
77
import copyTo from "./copyTo.mjs";
88

99
const compressedRe = /((\.br)|(\.gz))$/;
10-
const indexRe = /\/index\.[a-zA-Z]*$/;
1110

1211
const octetStream = "application/octet-stream";
1312
const multipartRelated = "multipart/related";
@@ -50,7 +49,7 @@ class S3Ops {
5049
fileName = `${this.group.path}${fileName}`;
5150
}
5251
const indexPos = fileName.lastIndexOf("/index");
53-
if (indexPos!==-1 && fileName.indexOf('/',indexPos+1)==-1) {
52+
if (indexPos !== -1 && fileName.indexOf("/", indexPos + 1) == -1) {
5453
fileName = fileName.substring(0, indexPos);
5554
}
5655
if (fileName[0] == "/") {
@@ -137,7 +136,7 @@ class S3Ops {
137136
console.log("result=", result);
138137
return result?.Contents;
139138
} catch (e) {
140-
console.log("Error sending", Bucket, Key, e);
139+
console.log("Error sending", Bucket, s3Uri, e);
141140
console.log("e=", e);
142141
return null;
143142
}

packages/static-wado-creator/lib/DeleteStudy.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const { JSONReader } = require("@radicalimaging/static-wado-util");
22
const { JSONWriter } = require("@radicalimaging/static-wado-util");
3-
const Tags = require("./dictionary/Tags");
3+
const { Tags } = require("@radicalimaging/static-wado-util");
44

55
module.exports = (options) =>
66
async function (studyInstanceUid) {

packages/static-wado-creator/lib/mkdicomwebConfig.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,26 @@ const { mkdicomwebConfig } = ConfigPoint.register({
2525
description: "Clean the outputs before generating/starting to write new values.",
2626
defaultValue: false,
2727
},
28+
{
29+
key: "-d, --deployments <listvalue...>",
30+
description: "List of deployments from configuration to deploy to. Separated by space.",
31+
defaultValue: undefined,
32+
},
2833
{
2934
key: "-v, --verbose",
3035
description: "Write verbose output",
3136
defaultValue: false,
3237
},
3338
{
3439
key: "-t, --content-type <type>",
35-
description: 'Destination type to compress to (choices: "jls", "lei", "jls-lossy" or DICOM Transfer Syntax UID - default: "jls")',
40+
description: 'Destination type to compress to (choices: "jls", "lei", "jls-lossy", "jhc", "jxl" or DICOM Transfer Syntax UID - default: "jls")',
3641
defaultValue: "jls",
3742
customParser: compressionOptionParser,
3843
},
44+
{
45+
key: "-e, --no-encapsulated-image-frame",
46+
description: "Avoid encapsulating the image frame. Writes with the extension and without multipart",
47+
},
3948
{
4049
key: "-m, --maximum-inline-public-length <value>",
4150
description: "Maximum length of public binary data",
@@ -50,7 +59,7 @@ const { mkdicomwebConfig } = ConfigPoint.register({
5059
key: "-r, --recompress <listvalue...>",
5160
description: "List of types to recompress separated by space",
5261
defaultValue: ["uncompressed", "jp2"],
53-
choices: ["uncompressed", "jp2", "jpeglossless", "rle", "none"],
62+
choices: ["uncompressed", "jp2", "jpeglossless", "rle", "jph", "jls", "true", "none"],
5463
},
5564
{
5665
key: "--recompress-thumb <listvalue...>",

packages/static-wado-creator/lib/model/TagLists.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const hashFactory = require("node-object-hash");
2-
const Tags = require("../dictionary/Tags");
2+
const { Tags } = require("@radicalimaging/static-wado-util");
33

44
/* eslint "no-param-reassign": "off" */
55

packages/static-wado-creator/lib/model/uids.js

+9-10
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const jpeg = "image/jpeg";
1212
const jls = "image/jls";
1313
const jll = "image/jll";
1414
const jxl = "image/x-jxl";
15-
const htj2k = "image/x-htj2k";
15+
const htj2k = "image/jphc";
1616
const jp2 = "image/jp2";
1717
const rle = "image/dicom-rle";
1818

@@ -21,20 +21,19 @@ const uids = {
2121
"1.2.840.10008.1.2.1": uncompressed,
2222
"1.2.840.10008.1.2.1.99": uncompressed,
2323
"1.2.840.10008.1.2.2": uncompressed,
24-
"1.2.840.10008.1.2.4.50": { contentType: jpeg, lossy: true },
24+
"1.2.840.10008.1.2.4.50": { contentType: jpeg, lossy: true, extension: ".jpeg" },
2525
"1.2.840.10008.1.2.4.51": { contentType: jpeg, lossy: true },
2626
"1.2.840.10008.1.2.4.57": { contentType: jpeg },
27-
"1.2.840.10008.1.2.4.70": { contentType: jll },
28-
"1.2.840.10008.1.2.4.140": { contentType: jxl },
27+
"1.2.840.10008.1.2.4.70": { contentType: jll, extension: ".jll" },
28+
"1.2.840.10008.1.2.4.140": { contentType: jxl, extension: ".jxl" },
29+
// Lossy, original JPEG reconstruction mode
2930
"1.2.840.10008.1.2.4.141": { contentType: jxl, lossy: true },
30-
"1.2.840.10008.1.2.4.80": { contentType: jls },
31+
"1.2.840.10008.1.2.4.142": { contentType: jxl, lossy: true },
32+
"1.2.840.10008.1.2.4.80": { contentType: jls, extension: ".jls" },
3133
"1.2.840.10008.1.2.4.81": { contentType: jls, lossy: true },
32-
"1.2.840.10008.1.2.4.90": { contentType: jp2 },
34+
"1.2.840.10008.1.2.4.90": { contentType: jp2, extension: ".jp2" },
3335
"1.2.840.10008.1.2.4.91": { contentType: jp2 },
34-
// From JPEG original data, lossless to JXL
35-
"1.2.840.10008.1.2.4.142": { contentType: jxl, lossy: true },
36-
"1.2.840.10008.1.2.4.150": { contentType: htj2k },
37-
// **
36+
"1.2.840.10008.1.2.4.96": { contentType: htj2k, extension: ".jhc" },
3837
"1.2.840.10008.1.2.5": { contentType: rle },
3938
default: {},
4039
};

packages/static-wado-creator/lib/operation/InstanceDeduplicate.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// TODO review returning type of some methods
22
const { JSONWriter } = require("@radicalimaging/static-wado-util");
3+
const { Tags } = require("@radicalimaging/static-wado-util");
34
const TagLists = require("../model/TagLists");
4-
const Tags = require("../dictionary/Tags");
55

66
const { getValue, setValue } = Tags;
77

packages/static-wado-creator/lib/operation/StudyData.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const path = require("path");
22
const fs = require("fs");
33
const hashFactory = require("node-object-hash");
44
const { JSONReader, JSONWriter } = require("@radicalimaging/static-wado-util");
5-
const Tags = require("../dictionary/Tags");
5+
const { Tags } = require("@radicalimaging/static-wado-util");
66
const TagLists = require("../model/TagLists");
77

88
const { getValue, setValue, getList, setList } = Tags;

packages/static-wado-creator/lib/operation/ThumbnailService.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ const dicomCodec = require("@cornerstonejs/dicom-codec");
44
const staticCS = require("@radicalimaging/static-cs-lite");
55
const fs = require("fs");
66
const { exec } = require("child_process");
7+
const { Tags } = require("@radicalimaging/static-wado-util");
78
const decodeImage = require("./adapter/decodeImage");
89
const { shouldThumbUseTranscoded } = require("./adapter/transcodeImage");
910
const { isVideo } = require("../writer/VideoWriter");
10-
const Tags = require("../dictionary/Tags");
1111

1212
/**
1313
* Return the middle index of given list

packages/static-wado-creator/lib/operation/adapter/transcodeImage.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const dicomCodec = require("@cornerstonejs/dicom-codec");
2-
const Tags = require("../../dictionary/Tags");
2+
const { Tags } = require("@radicalimaging/static-wado-util");
33
const getImageInfo = require("./getImageInfo");
44

55
const transcodeOp = {
@@ -33,6 +33,10 @@ const transcodeDestinationMap = {
3333
transferSyntaxUid: "1.2.840.10008.1.2.4.91",
3434
transcodeOp: transcodeOp.encode,
3535
},
36+
jhc: {
37+
transferSyntaxUid: "1.2.840.10008.1.2.4.96",
38+
transcodeOp: transcodeOp.encode,
39+
},
3640
};
3741

3842
const transcodeSourceMap = {
@@ -64,6 +68,10 @@ const transcodeSourceMap = {
6468
transcodeOp: transcodeOp.decode,
6569
alias: "jp2",
6670
},
71+
"1.2.840.10008.1.2.4.96": {
72+
transcodeOp: transcodeOp.decode,
73+
alias: "jhc",
74+
},
6775
"1.2.840.10008.1.2.5": {
6876
transcodeOp: transcodeOp.decode,
6977
alias: "rle",
@@ -113,14 +121,15 @@ function getTranscoder(transferSyntaxUid, { contentType, verbose }) {
113121
* @param {*} options runner options
114122
*/
115123
function shouldTranscodeImageFrame(id, options) {
116-
if (!options.recompress) {
124+
const { recompress } = options;
125+
if (!recompress) {
117126
return false;
118127
}
119128

120129
function isValidTranscoder() {
121130
const { transferSyntaxUid } = id;
122131
const transcoder = getTranscoder(transferSyntaxUid, options);
123-
return transcoder && transcoder.transferSyntaxUid && options.recompress.includes(transcoder.alias);
132+
return transcoder && transcoder.transferSyntaxUid && (recompress.includes("true") || recompress.includes(transcoder.alias));
124133
}
125134

126135
return isValidTranscoder();

packages/static-wado-creator/lib/operation/getDataSet.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
const { Tags } = require("@radicalimaging/static-wado-util");
12
const getVR = require("./getVR");
23
const getValue = require("./getValue");
3-
const Tags = require("../dictionary/Tags");
44

55
/**
66
* Get dataset.

packages/static-wado-creator/lib/operation/getVR.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const dataDictionary = require("../dictionary/dataDictionary");
1+
const { dataDictionary } = require("@radicalimaging/static-wado-util");
22

33
const getVR = (attr) => {
44
if (attr.vr) {

packages/static-wado-creator/lib/writer/CompleteStudyWriter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const { JSONReader, JSONWriter, Stats } = require("@radicalimaging/static-wado-util");
2+
const { Tags } = require("@radicalimaging/static-wado-util");
23
const StudyData = require("../operation/StudyData");
3-
const Tags = require("../dictionary/Tags");
44

55
/**
66
* CompleteStudyWriter takes the deduplicated data values, all loaded into the study parameter,

packages/static-wado-creator/lib/writer/DeduplicateWriter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const hashFactory = require("node-object-hash");
22

33
const hasher = hashFactory();
44
const { JSONWriter } = require("@radicalimaging/static-wado-util");
5-
const Tags = require("../dictionary/Tags");
5+
const { Tags } = require("@radicalimaging/static-wado-util");
66
const TagLists = require("../model/TagLists");
77

88
async function writeDeduplicatedFile(dir, data, hashValueSrc) {

packages/static-wado-creator/lib/writer/HashDataWriter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const hashFactory = require("node-object-hash");
22

33
const hasher = hashFactory();
44
const path = require("path");
5-
const Tags = require("../dictionary/Tags");
5+
const { Tags } = require("@radicalimaging/static-wado-util");
66
const WriteStream = require("./WriteStream");
77
const WriteMultipart = require("./WriteMultipart");
88
const ExpandUriPath = require("./ExpandUriPath");

packages/static-wado-creator/lib/writer/ImageFrameWriter.js

+25-8
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,41 @@ const ExpandUriPath = require("./ExpandUriPath");
55
const { MultipartHeader, MultipartAttribute } = require("./MultipartHeader");
66

77
const ImageFrameWriter = (options) => {
8-
const { verbose } = options;
8+
const { verbose, encapsulatedImageFrame = true } = options;
99

1010
return async (id, index, imageFrame) => {
1111
const { transferSyntaxUid } = id;
1212
const type = uids[transferSyntaxUid] || uids.default;
13-
const writeStream = WriteStream(id.imageFrameRootPath, `${1 + index}`, {
14-
gzip: type.gzip,
15-
mkdir: true,
16-
});
13+
const extension = type.extension;
1714
let content;
1815
if (imageFrame instanceof Uint8Array) {
1916
content = imageFrame;
2017
} else {
2118
content = Buffer.from(imageFrame.buffer);
2219
}
23-
await WriteMultipart(writeStream, [new MultipartHeader("Content-Type", type.contentType, [new MultipartAttribute("transfer-syntax", transferSyntaxUid)])], content);
24-
await writeStream.close();
25-
if (verbose) console.log("Wrote image frame", id.sopInstanceUid, index + 1);
20+
21+
if (encapsulatedImageFrame || !extension) {
22+
const writeStream = WriteStream(id.imageFrameRootPath, `${1 + index}`, {
23+
gzip: type.gzip,
24+
mkdir: true,
25+
});
26+
await WriteMultipart(
27+
writeStream,
28+
[new MultipartHeader("Content-Type", type.contentType, [new MultipartAttribute("transfer-syntax", transferSyntaxUid)])],
29+
content
30+
);
31+
writeStream.close();
32+
if (verbose) console.log("Wrote encapsulated image frame", id.sopInstanceUid, index + 1);
33+
}
34+
if (extension) {
35+
const writeStreamSingle = WriteStream(id.imageFrameRootPath, `${1 + index}${extension}`, {
36+
gzip: type.gzip,
37+
mkdir: true,
38+
});
39+
await writeStreamSingle.write(content);
40+
writeStreamSingle.close();
41+
if (verbose) console.log("Wrote single part image frame", id.sopInstanceUid, index + 1);
42+
}
2643
const includeSeries = true;
2744
return ExpandUriPath(id, `instances/${id.sopInstanceUid}/frames`, { includeSeries, ...options });
2845
};

packages/static-wado-creator/lib/writer/RawDicomWriter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
const { Tags } = require("@radicalimaging/static-wado-util");
12
const WriteStream = require("./WriteStream");
23
const WriteMultipart = require("./WriteMultipart");
34
const { MultipartHeader } = require("./MultipartHeader");
4-
const Tags = require("../dictionary/Tags");
55

66
const modalitiesToRawWrite = ["SEG", "SR"];
77

packages/static-wado-creator/lib/writer/VideoWriter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** Write raw video file */
2-
const Tags = require("../dictionary/Tags");
2+
const { Tags } = require("@radicalimaging/static-wado-util");
33
const WriteStream = require("./WriteStream");
44

55
const MPEG2 = "mpg";

packages/static-wado-creator/tests/e2e/index.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const fs = require("fs");
22
const { execSync } = require("child_process");
3-
const { JSONReader } = require("@radicalimaging/static-wado-util");
3+
const { JSONReader, Tags } = require("@radicalimaging/static-wado-util");
44
const must = require("must");
55

66
// same level at package folder

packages/static-wado-deploy/lib/DeployGroup.mjs

-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ class DeployGroup {
3838
*/
3939
async store(parentDir = "", name = "") {
4040
const fileName = path.join(this.baseDir, parentDir, name);
41-
// console.log('Doing lstat', fileName);
4241
const lstat = await fs.promises.lstat(fileName);
4342
const relativeName = (name && `${parentDir}/${name}`) || parentDir || "";
4443
console.log("relativeName", relativeName);

packages/static-wado-scp/lib/DcmjsDimseScp.js

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { Stats } = require("@radicalimaging/static-wado-util");
22
const StaticWado = require("@radicalimaging/static-wado-creator");
33
const dcmjsDimse = require("dcmjs-dimse");
44
const dcmjs = require("dcmjs");
5+
const { loadedPlugins } = require("./loadPlugins");
56

67
const { Scp, Dataset } = dcmjsDimse;
78
const { CEchoResponse, CStoreResponse, CFindResponse } = dcmjsDimse.responses;
@@ -103,6 +104,7 @@ class DcmjsDimseScp extends Scp {
103104
const dataset = request.getDataset();
104105
const { QueryRetrieveLevel } = dataset.elements;
105106
console.log("QueryRetrieveLevel", QueryRetrieveLevel);
107+
// TODO - call loadPlugins pon the given level to see if they can be loaded
106108
const queryFunc = loadedPlugins[QueryRetrieveLevel];
107109

108110
if (queryFunc && !this.cfindDisabled) {

packages/static-wado-scp/lib/loadPlugins.js

+2
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ const loadPlugins = (options) => {
1717
});
1818
};
1919

20+
loadPlugins.loadedPlugins = loadedPlugins;
21+
2022
module.exports = loadPlugins;

packages/static-wado-util/lib/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ exports.configDiff = require("./update/configDiff");
1414
exports.configGroup = require("./configGroup.js");
1515
exports.updateConfiguration = require("./update/updateConfiguration");
1616
exports.asyncIterableToBuffer = require("./asyncIterableToBuffer");
17+
exports.Tags = require("./dictionary/Tags");
18+
exports.dataDictionary = require("./dictionary/dataDictionary");
1719

1820
exports.configureProgram = configureProgram;
1921
exports.configureCommands = configureCommands;

packages/static-wado-util/lib/reader/NDJSONReader.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const handleHomeRelative = require("../handleHomeRelative");
55

66
const NDJSONReader = async (dirSrc, name, def) => {
77
const dir = handleHomeRelative(dirSrc);
8-
return new Promise((resolve, reject) => {
8+
return new Promise((resolve) => {
99
try {
1010
const ret = [];
1111
const stream = fs.createReadStream(path.join(dir, name)).pipe(ndjson.parse());
@@ -14,8 +14,7 @@ const NDJSONReader = async (dirSrc, name, def) => {
1414
} catch (err) {
1515
if (def === undefined) {
1616
console.log("Couldn't read", dir, name, err);
17-
reject("Couldn't read");
18-
return;
17+
throw new Error(`Couldn't read ${dir} ${name} ${err}`);
1918
}
2019
resolve(def);
2120
}

packages/static-wado-util/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"dependencies": {
3939
"commander": "^9.0.0",
4040
"config-point": "^0.5.1",
41+
"dcmjs": "^0.29.0",
4142
"json5": "^2.2.1",
4243
"json5-writer": "^0.2.0",
4344
"must": "^0.13.4",

packages/static-wado-creator/tests/unit/asyncIterable.js renamed to packages/static-wado-util/tests/unit/asyncIterable.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const fs = require("fs");
2-
const asyncIteratorToBuffer = require("../../lib/operation/adapter/asyncIterableToBuffer");
2+
const asyncIteratorToBuffer = require("../../lib/asyncIterableToBuffer");
33

44
describe("asyncIterableToBuffer", () => {
55
let dicomp10stream;

0 commit comments

Comments
 (0)