@radicalimaging/static-wado-creator 0.6.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  const dicomCodec = require("@cornerstonejs/dicom-codec");
2
- const Tags = require("../../dictionary/Tags");
2
+ const { Tags } = require("@radicalimaging/static-wado-util");
3
3
  const getImageInfo = require("./getImageInfo");
4
4
 
5
5
  const transcodeOp = {
@@ -15,7 +15,7 @@ const transcodeOp = {
15
15
  const transcodeDestinationMap = {
16
16
  lei: {
17
17
  transferSyntaxUid: "1.2.840.10008.1.2.1",
18
- transcodeOp: transcodeOp.none
18
+ transcodeOp: transcodeOp.none,
19
19
  },
20
20
  jls: {
21
21
  transferSyntaxUid: "1.2.840.10008.1.2.4.80",
@@ -33,6 +33,10 @@ const transcodeDestinationMap = {
33
33
  transferSyntaxUid: "1.2.840.10008.1.2.4.91",
34
34
  transcodeOp: transcodeOp.encode,
35
35
  },
36
+ jhc: {
37
+ transferSyntaxUid: "3.2.840.10008.1.2.4.96",
38
+ transcodeOp: transcodeOp.encode,
39
+ },
36
40
  };
37
41
 
38
42
  const transcodeSourceMap = {
@@ -50,11 +54,11 @@ const transcodeSourceMap = {
50
54
  },
51
55
  "1.2.840.10008.1.2.4.57": {
52
56
  transcodeOp: transcodeOp.decode,
53
- alias: "jpeglossless",
57
+ alias: "jls",
54
58
  },
55
59
  "1.2.840.10008.1.2.4.70": {
56
60
  transcodeOp: transcodeOp.decode,
57
- alias: "jpeglossless",
61
+ alias: "jls",
58
62
  },
59
63
  "1.2.840.10008.1.2.4.90": {
60
64
  transcodeOp: transcodeOp.decode,
@@ -64,6 +68,10 @@ const transcodeSourceMap = {
64
68
  transcodeOp: transcodeOp.decode,
65
69
  alias: "jp2",
66
70
  },
71
+ "3.2.840.10008.1.2.4.96": {
72
+ transcodeOp: transcodeOp.decode,
73
+ alias: "jhc",
74
+ },
67
75
  "1.2.840.10008.1.2.5": {
68
76
  transcodeOp: transcodeOp.decode,
69
77
  alias: "rle",
@@ -71,9 +79,9 @@ const transcodeSourceMap = {
71
79
  };
72
80
 
73
81
  /**
74
- * Get an existing destination transcoder from destination map.
82
+ * Get an existing destination transcoder from destination map.
75
83
  *
76
- * @param {*} id Destination type to compress to
84
+ * @param {*} id Destination type to compress to
77
85
  * @returns A partial transcoder definition. Otherwise it returns undefined.
78
86
  */
79
87
  function getDestinationTranscoder(id) {
@@ -92,14 +100,14 @@ function getDestinationTranscoder(id) {
92
100
  */
93
101
  function getTranscoder(transferSyntaxUid, { contentType, verbose }) {
94
102
  if (verbose) {
95
- console.log(`getTranscoder for ${transferSyntaxUid}`);
103
+ console.log('getTranscoder for', transferSyntaxUid, 'source to', contentType);
96
104
  }
97
105
  const sourceTranscoder = transcodeSourceMap[transferSyntaxUid];
98
106
  const destinationTranscoder = getDestinationTranscoder(contentType);
99
107
  if (!sourceTranscoder || !destinationTranscoder) {
100
108
  return undefined;
101
109
  }
102
-
110
+
103
111
  return {
104
112
  transferSyntaxUid: destinationTranscoder.transferSyntaxUid,
105
113
  transcodeOp: sourceTranscoder.transcodeOp | destinationTranscoder.transcodeOp, // eslint-disable-line no-bitwise
@@ -107,24 +115,29 @@ function getTranscoder(transferSyntaxUid, { contentType, verbose }) {
107
115
  };
108
116
  }
109
117
 
110
-
111
118
  /**
112
119
  * Tell whether given id contain transferSyntaxUid which can be transcoded or not.
113
120
  * @param {*} id object containing transferSyntaxUid
114
121
  * @param {*} options runner options
115
122
  */
116
123
  function shouldTranscodeImageFrame(id, options) {
117
- if (!options.recompress) {
124
+ const { recompress } = options;
125
+ if (!recompress) {
126
+ if( options.verbose ) console.log("Not transcoding because recompress not set");
118
127
  return false;
119
128
  }
120
129
 
121
130
  function isValidTranscoder() {
122
131
  const { transferSyntaxUid } = id;
123
132
  const transcoder = getTranscoder(transferSyntaxUid, options);
124
- return transcoder && transcoder.transferSyntaxUid && options.recompress.includes(transcoder.alias);
133
+ return transcoder && transcoder.transferSyntaxUid && (recompress.includes("true") || recompress.includes(transcoder.alias));
125
134
  }
126
135
 
127
- return isValidTranscoder();
136
+ const ret = isValidTranscoder();
137
+ if( !ret && options.verbose ) {
138
+ console.log("Not transcoding");
139
+ }
140
+ return ret;
128
141
  }
129
142
 
130
143
  /**
@@ -171,6 +184,7 @@ async function transcodeImageFrame(id, targetIdSrc, imageFrame, dataSet, options
171
184
  let result = {};
172
185
 
173
186
  if (!shouldTranscodeImageFrame(id, options)) {
187
+ console.log("Shouldn't transcode");
174
188
  return {
175
189
  id,
176
190
  imageFrame,
@@ -182,6 +196,7 @@ async function transcodeImageFrame(id, targetIdSrc, imageFrame, dataSet, options
182
196
 
183
197
  // last chance to prevent transcoding
184
198
  if (targetId.transferSyntaxUid !== transcoder.transferSyntaxUid) {
199
+ console.log("Image is already in", targetId.transferSyntaxUid, "not transcoding");
185
200
  return {
186
201
  id,
187
202
  imageFrame,
@@ -189,9 +204,8 @@ async function transcodeImageFrame(id, targetIdSrc, imageFrame, dataSet, options
189
204
  };
190
205
  }
191
206
 
192
- if (options.verbose) {
193
- console.log("Transcoding to", transcoder.transferSyntaxUid);
194
- }
207
+ if( options.verbose ) console.log("Transcoding to", transcoder.transferSyntaxUid);
208
+
195
209
  const imageInfo = getImageInfo(dataSet);
196
210
  let done = false;
197
211
  let processResultMsg = "";
@@ -289,7 +303,10 @@ function transcodeMetadata(metadata, id, options) {
289
303
  const result = { ...metadata };
290
304
 
291
305
  if (result[Tags.AvailableTransferSyntaxUID]) {
292
- Tags.setValue(result,Tags.AvailableTransferSyntaxUID, transcodedId.transferSyntaxUid);
306
+ Tags.setValue(result, Tags.AvailableTransferSyntaxUID, transcodedId.transferSyntaxUid);
307
+ if( this.verbose ) {
308
+ console.log("Apply available tsuid", transcodeId.transferSyntaxUid);
309
+ }
293
310
  }
294
311
 
295
312
  return result;
@@ -1,6 +1,6 @@
1
+ const { Tags } = require("@radicalimaging/static-wado-util");
1
2
  const getVR = require("./getVR");
2
3
  const getValue = require("./getValue");
3
- const Tags = require("../dictionary/Tags");
4
4
 
5
5
  /**
6
6
  * Get dataset.
@@ -1,4 +1,4 @@
1
- const dataDictionary = require("../dictionary/dataDictionary");
1
+ const { dataDictionary } = require("@radicalimaging/static-wado-util");
2
2
 
3
3
  const getVR = (attr) => {
4
4
  if (attr.vr) {
@@ -29,6 +29,7 @@ module.exports = function adaptProgramOpts(programOpts, defaults) {
29
29
  prependBulkDataUri,
30
30
  expandBulkDataUri,
31
31
  verbose,
32
+ scpPort,
32
33
  } = programOpts;
33
34
 
34
35
  return assignDefined(
@@ -50,6 +51,7 @@ module.exports = function adaptProgramOpts(programOpts, defaults) {
50
51
  pathInstances,
51
52
  removeDeduplicatedInstances,
52
53
  storeMultipartBulkData,
54
+ scpPort,
53
55
  prependBulkDataUri,
54
56
  expandBulkDataUri,
55
57
  verbose,
@@ -1,13 +1,13 @@
1
1
  const { program } = require("@radicalimaging/static-wado-util");
2
2
  const { getDestinationTranscoder } = require("../operation/adapter/transcodeImage");
3
3
 
4
- module.exports = function compressionOptionParser(value) {
4
+ module.exports = function compressionOptionParser(value) {
5
5
  const destination = getDestinationTranscoder(value);
6
6
  if (destination) {
7
7
  console.log("Found destination transferSyntax ", destination.transferSyntaxUid, "for ", value);
8
8
  } else {
9
9
  console.log("No transcoder destination for ", value);
10
10
  throw new program.InvalidArgumentError(`No transcoder destination for ${value}`);
11
- }
11
+ }
12
12
  return destination.transferSyntaxUid;
13
- }
13
+ };
@@ -1,8 +1,6 @@
1
- const { Stats } = require("@radicalimaging/static-wado-util");
2
- const { JSONReader } = require("@radicalimaging/static-wado-util");
3
- const JSONWriter = require("./JSONWriter");
1
+ const { JSONReader, JSONWriter, Stats } = require("@radicalimaging/static-wado-util");
2
+ const { Tags } = require("@radicalimaging/static-wado-util");
4
3
  const StudyData = require("../operation/StudyData");
5
- const Tags = require("../dictionary/Tags");
6
4
 
7
5
  /**
8
6
  * CompleteStudyWriter takes the deduplicated data values, all loaded into the study parameter,
@@ -47,12 +45,12 @@ const CompleteStudyWriter = (options) => {
47
45
  const studyQuery = await studyData.writeMetadata();
48
46
 
49
47
  const allStudies = await JSONReader(options.directoryName, "studies/index.json.gz", []);
50
- const studyUID = Tags.getValue(studyQuery,Tags.StudyInstanceUID);
48
+ const studyUID = Tags.getValue(studyQuery, Tags.StudyInstanceUID);
51
49
  if (!studyUID) {
52
50
  console.error("studyQuery=", studyQuery);
53
51
  throw new Error("Study query has null studyUID");
54
52
  }
55
- const studyIndex = allStudies.findIndex((item) => Tags.getValue(item,Tags.StudyInstanceUID) == studyUID);
53
+ const studyIndex = allStudies.findIndex((item) => Tags.getValue(item, Tags.StudyInstanceUID) == studyUID);
56
54
  if (studyIndex == -1) {
57
55
  allStudies.push(studyQuery);
58
56
  } else {
@@ -1,8 +1,8 @@
1
1
  const hashFactory = require("node-object-hash");
2
2
 
3
3
  const hasher = hashFactory();
4
- const JSONWriter = require("./JSONWriter");
5
- const Tags = require("../dictionary/Tags");
4
+ const { JSONWriter } = require("@radicalimaging/static-wado-util");
5
+ const { Tags } = require("@radicalimaging/static-wado-util");
6
6
  const TagLists = require("../model/TagLists");
7
7
 
8
8
  async function writeDeduplicatedFile(dir, data, hashValueSrc) {
@@ -2,7 +2,7 @@ const hashFactory = require("node-object-hash");
2
2
 
3
3
  const hasher = hashFactory();
4
4
  const path = require("path");
5
- const Tags = require("../dictionary/Tags");
5
+ const { Tags } = require("@radicalimaging/static-wado-util");
6
6
  const WriteStream = require("./WriteStream");
7
7
  const WriteMultipart = require("./WriteMultipart");
8
8
  const ExpandUriPath = require("./ExpandUriPath");
@@ -47,7 +47,7 @@ HashDataWriter.createHashPath = (data, options = {}) => {
47
47
  const { mimeType } = options;
48
48
  const isRaw = ArrayBuffer.isView(data);
49
49
  const extension = isRaw ? (mimeType && extensions[mimeType]) || "" : ".json";
50
- const hashValue = Tags.getValue(data,Tags.DeduppedHash) || hasher.hash(data);
50
+ const hashValue = Tags.getValue(data, Tags.DeduppedHash) || hasher.hash(data);
51
51
 
52
52
  return {
53
53
  // Use string concat as this value is used for the BulkDataURI which needs forward slashes
@@ -5,24 +5,43 @@ const ExpandUriPath = require("./ExpandUriPath");
5
5
  const { MultipartHeader, MultipartAttribute } = require("./MultipartHeader");
6
6
 
7
7
  const ImageFrameWriter = (options) => {
8
- const { verbose } = options;
8
+ const { verbose, encapsulatedImageFrame = true } = options;
9
9
 
10
10
  return async (id, index, imageFrame) => {
11
11
  const { transferSyntaxUid } = id;
12
12
  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;
17
14
  let content;
18
15
  if (imageFrame instanceof Uint8Array) {
19
16
  content = imageFrame;
20
17
  } else {
21
18
  content = Buffer.from(imageFrame.buffer);
22
19
  }
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) {
33
+ console.log("Wrote encapsulated image frame", id.sopInstanceUid, index + 1, type.contentType);
34
+ }
35
+ }
36
+ if (extension) {
37
+ const writeStreamSingle = WriteStream(id.imageFrameRootPath, `${1 + index}${extension}`, {
38
+ gzip: type.gzip,
39
+ mkdir: true,
40
+ });
41
+ await writeStreamSingle.write(content);
42
+ writeStreamSingle.close();
43
+ if (verbose) console.log("Wrote single part image frame", id.sopInstanceUid, index + 1, extension);
44
+ }
26
45
  const includeSeries = true;
27
46
  return ExpandUriPath(id, `instances/${id.sopInstanceUid}/frames`, { includeSeries, ...options });
28
47
  };
@@ -0,0 +1,36 @@
1
+ const { Tags } = require("@radicalimaging/static-wado-util");
2
+ const WriteStream = require("./WriteStream");
3
+ const WriteMultipart = require("./WriteMultipart");
4
+ const { MultipartHeader } = require("./MultipartHeader");
5
+
6
+ const modalitiesToRawWrite = ["SEG", "SR"];
7
+
8
+ const defaultSelector = (instance) => {
9
+ const modality = instance.metadata[Tags.Modality]?.Value?.[0];
10
+ return modalitiesToRawWrite.indexOf(modality) !== -1;
11
+ };
12
+
13
+ /** Writes out raw DICOM files, encapsulated in multipart and gzipped - currently just DICOM SR and SEG */
14
+ const RawDicomWriter =
15
+ () =>
16
+ async (id, instance, content, options = { gzip: true }) => {
17
+ if (!instance) {
18
+ return undefined;
19
+ }
20
+ const selector = options.selector || defaultSelector;
21
+ if (!selector(instance)) {
22
+ return undefined;
23
+ }
24
+ const contentType = options.contentType || "application/dicom";
25
+ const writeStream = WriteStream(id.sopInstanceRootPath, "index.mht", {
26
+ gzip: options.gzip,
27
+ mkdir: true,
28
+ });
29
+ const { length } = content;
30
+ const buffer = content.slice(0, length);
31
+ await WriteMultipart(writeStream, [new MultipartHeader("Content-Type", contentType, [])], buffer);
32
+ await writeStream.close();
33
+ };
34
+ RawDicomWriter.defaultSelector = defaultSelector;
35
+
36
+ module.exports = RawDicomWriter;
@@ -1,5 +1,5 @@
1
1
  /** Write raw video file */
2
- const Tags = require("../dictionary/Tags");
2
+ const { Tags } = require("@radicalimaging/static-wado-util");
3
3
  const WriteStream = require("./WriteStream");
4
4
 
5
5
  const MPEG2 = "mpg";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radicalimaging/static-wado-creator",
3
- "version": "0.6.4",
3
+ "version": "1.1.0",
4
4
  "description": "Library to convert DICOMP10 to DICOMweb format. Original upstream: https://github.com/chafey/dicomp10-to-dicomweb-js",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -53,21 +53,22 @@
53
53
  "build": "echo \"No build yet\" && exit 0",
54
54
  "link:exec": "npm link",
55
55
  "lint": "npx eslint --color \"*(bin|lib)/**/*.{js,mjs}\"",
56
- "lint:fix": "npx eslint --fix --color \"*(bin|lib)/**/*.{js,mjs}\""
57
- },
56
+ "lint:fix": "npx eslint --fix --color \"*(bin|lib)/**/*.{js,mjs}\""
57
+ },
58
58
  "jest": {
59
59
  "verbose": true
60
60
  },
61
61
  "dependencies": {
62
62
  "@cornerstonejs/dicom-codec": "^0.1.3",
63
- "@radicalimaging/static-cs-lite": "^0.0.2",
63
+ "@radicalimaging/static-cs-lite": "^0.0.3",
64
64
  "@radicalimaging/static-wado-util": "^0.6.2",
65
65
  "canvas": "^2.9.1",
66
- "config-point": "^0.5.0",
66
+ "config-point": "^0.5.1",
67
+ "dcmjs": "^0.28.0",
67
68
  "dicom-parser": "^1.8.13",
68
69
  "glob": "^7.2.0",
69
- "uuid": "^8.3.2",
70
- "node-object-hash": "^2.3.10"
70
+ "node-object-hash": "^2.3.10",
71
+ "uuid": "^8.3.2"
71
72
  },
72
73
  "devDependencies": {
73
74
  "must": "^0.13.4"
@@ -1,111 +0,0 @@
1
- const dataDictionary = require("./dataDictionary");
2
- const dcmjs = require("dcmjs");
3
-
4
- const { naturalizeDataset, denaturalizeDataset} = dcmjs.data.DicomMetaDictionary
5
-
6
- /** Find the actual tag for a private value */
7
- const findPrivate = (item, tagObject, create) => {
8
- if( typeof tagObject === "string" ) return tagObject;
9
- if( typeof tagObject === "number" ) return `00000000${tagObject.toString(16)}`.slice(-8);
10
- const {creator,tag} = tagObject;
11
- if( !creator ) return tag;
12
- const start = tag.substring(0,4);
13
- const end = tag.substring(6,8);
14
- // Should technically go all the way up, but for now only test 0x30 items
15
- let assignPosition;
16
- for(let offset = 0x10; offset < 0x40; offset++) {
17
- const testTag = `${start}00${offset.toString(16)}`
18
- const testCreator = item[testTag];
19
- if( testCreator===undefined && assignPosition===undefined ) assignPosition = offset;
20
- if( testCreator && testCreator.Value && testCreator.Value[0]===creator ) {
21
- return `${start}${offset.toString(16)}${end}`
22
- }
23
- }
24
- if( create ) {
25
- if( !assignPosition ) throw new Error(`Couldn't find any assign positions for ${creator} ${tag} in ${item}`);
26
- const creatorTag = `${start}00${assignPosition.toString(16)}`
27
- item[creatorTag] = { Value: [creator], vr: 'CS' };
28
- return `${start}${assignPosition.toString(16)}${end}`
29
- }
30
- };
31
-
32
- const DeduppedCreator = "dedupped";
33
-
34
- const Tags = {
35
- // Raw tags have the x before them, not parsed yet
36
- RawMinTag: "x00000000",
37
- RawFirstBodyTag: "x00080000",
38
- RawTransferSyntaxUID: "x00020010",
39
-
40
- // This one isn't defined in the dataDictionary
41
- AvailableTransferSyntaxUID: "00083002",
42
-
43
- DeduppedCreator,
44
-
45
- // The references to extract data included in this object, 1..n values
46
- DeduppedRef: { creator: DeduppedCreator, tag: "00091010"},
47
-
48
- // The hash value of THIS object
49
- DeduppedHash: {creator: DeduppedCreator, tag: "00091011"},
50
-
51
- // Type of hash instance
52
- DeduppedType: {creator: DeduppedCreator, tag: "00091012"},
53
- InstanceType: "instance",
54
- InfoType: "info",
55
-
56
- naturalizeDataset, denaturalizeDataset,
57
-
58
- setValue: (item, tag, value) => {
59
- const actualTag = findPrivate(item,tag,true);
60
- item[actualTag] = { Value: Array.isArray(value) ? value : [value] };
61
- return actualTag;
62
- },
63
-
64
- setList: (item, tag, value) => {
65
- const actualTag = findPrivate(item,tag,true);
66
- item[actualTag] = {Value: Array.isArray(value) ? value : [value]};
67
- return actualTag;
68
- },
69
-
70
- pushList: (item, tag, value) => {
71
- const actualTag = findPrivate(item,tag,true);
72
- if( !item[actualTag] ) item[actualTag] = { Value: [] };
73
- item[actualTag].Value.push(value);
74
- return actualTag;
75
- },
76
-
77
- getValue: (item, tag) => {
78
- const actualTag = findPrivate(item,tag);
79
- if( !actualTag ) return undefined;
80
- const value = item[actualTag];
81
- if( value===undefined ) return undefined;
82
- if( Array.isArray(value) ) return value.length==1 ? value[0] : (value.length===0 ? "" : value);
83
- if( value.Value ) return value.Value.length==1 ? value.Value[0] : (value.Value.length===0 ? "" : value.Value);
84
- return value;
85
- },
86
-
87
- getList: (item, tag) => {
88
- const actualTag = findPrivate(item,tag);
89
- if( !actualTag ) return undefined;
90
- const value = item[actualTag];
91
- if( value===undefined ) return undefined;
92
- if( Array.isArray(value) ) return value;
93
- if( value.Value ) return value.Value;
94
- return [value];
95
- },
96
-
97
- removeValue: (item,tag) => {
98
- const actualTag = findPrivate(item,tag);
99
- if( actualTag ) delete item[actualTag];
100
- // TODO, remove creator tag if not used any longer
101
- },
102
-
103
- findPrivate,
104
- };
105
-
106
- Object.keys(dataDictionary).forEach((key) => {
107
- const value = dataDictionary[key];
108
- Tags[value.name] = key;
109
- });
110
-
111
- module.exports = Tags;