@radicalimaging/static-wado-creator 0.6.5 → 1.2.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.
package/lib/rejectMain.js CHANGED
@@ -1,12 +1,12 @@
1
1
  const StaticWado = require("./StaticWado");
2
2
  const adaptProgramOpts = require("./util/adaptProgramOpts");
3
3
 
4
- module.exports = function rejectMain(options, program) {
4
+ module.exports = function rejectMain(args, options) {
5
5
  const finalOptions = adaptProgramOpts(options, {
6
6
  ...this,
7
7
  isGroup: true,
8
8
  isStudyData: true,
9
9
  });
10
10
  const importer = new StaticWado(finalOptions);
11
- return importer.reject(program.args);
11
+ return importer.reject(args);
12
12
  };
@@ -10,49 +10,30 @@ const assignDefined = (dest, src) => {
10
10
 
11
11
  module.exports = function adaptProgramOpts(programOpts, defaults) {
12
12
  const {
13
- maximumInlinePublicLength,
14
- maximumInlinePrivateLength,
15
13
  group: isGroup,
16
14
  instances: isInstanceMetadata,
17
15
  deduplicate: isDeduplicate,
18
16
  study: isStudyData,
19
17
  clean: isClean,
20
- recompress,
21
- recompressThumb,
22
- contentType,
23
- colourContentType,
24
18
  dir: rootDir,
25
- pathDeduplicated,
26
- pathInstances,
27
- removeDeduplicatedInstances,
28
- storeMultipartBulkData,
29
- prependBulkDataUri,
30
- expandBulkDataUri,
31
- verbose,
19
+ delete: deleteInstances,
20
+ singlePartImage,
21
+ encapsulatedImage,
32
22
  } = programOpts;
33
23
 
34
24
  return assignDefined(
35
25
  { ...defaults },
36
26
  {
37
- maximumInlinePublicLength,
38
- maximumInlinePrivateLength,
39
- isGroup,
40
- isInstanceMetadata,
41
- isDeduplicate,
27
+ ...programOpts,
42
28
  isStudyData,
43
29
  isClean,
44
- recompress,
45
- recompressThumb,
46
- contentType,
47
- colourContentType,
30
+ isInstanceMetadata,
31
+ isDeduplicate,
32
+ isGroup,
48
33
  rootDir,
49
- pathDeduplicated,
50
- pathInstances,
51
- removeDeduplicatedInstances,
52
- storeMultipartBulkData,
53
- prependBulkDataUri,
54
- expandBulkDataUri,
55
- verbose,
34
+ encapsulatedImage: encapsulatedImage ?? singlePartImage !== true,
35
+ singlePartImage: singlePartImage ?? encapsulatedImage === false,
36
+ delete: deleteInstances,
56
37
  }
57
38
  );
58
39
  };
@@ -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,
@@ -28,9 +26,12 @@ const CompleteStudyWriter = (options) => {
28
26
  } else {
29
27
  console.log("Not writing new deduplicated data because it is clean:", studyData.studyInstanceUid);
30
28
  }
29
+ await studyData.deleteInstancesReferenced();
31
30
  }
32
31
 
33
32
  if (!options.isStudyData) {
33
+ console.verbose("Not configured to write study metadata", studyData.studyInstanceUid);
34
+ if (options.notifications) this.notificationService.notifyStudy(studyData.studyInstanceUid);
34
35
  delete this.studyData;
35
36
  Stats.StudyStats.summarize();
36
37
  return;
@@ -38,11 +39,12 @@ const CompleteStudyWriter = (options) => {
38
39
 
39
40
  const isDirtyMetadata = await studyData.dirtyMetadata();
40
41
  if (!isDirtyMetadata) {
41
- console.log("Study metadata", studyData.studyInstanceUid, "has clean metadata, not writing");
42
+ console.log("Study metadata", studyData.studyInstanceUid, "is clean.");
42
43
  delete this.studyData;
43
44
  Stats.StudyStats.summarize(`Study metadata ${studyData.studyInstanceUid} has clean metadata, not writing`);
44
45
  return;
45
46
  }
47
+ console.log("Writing study metadata", studyData.studyInstanceUid);
46
48
 
47
49
  const studyQuery = await studyData.writeMetadata();
48
50
 
@@ -59,6 +61,7 @@ const CompleteStudyWriter = (options) => {
59
61
  allStudies[studyIndex] = studyQuery;
60
62
  }
61
63
  await JSONWriter(options.directoryName, "studies", allStudies);
64
+ if (options.notifications) this.notificationService.notifyStudy(studyData.studyInstanceUid);
62
65
  delete this.studyData;
63
66
  Stats.StudyStats.summarize(`Wrote study metadata/query files for ${studyData.studyInstanceUid}`);
64
67
  }
@@ -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) {
@@ -24,7 +24,10 @@ const DeduplicateWriter = (options) =>
24
24
  const studyData = await this.completeStudy.getCurrentStudyData(this, id);
25
25
 
26
26
  if (options.isDeduplicate) {
27
+ if (options.verbose) console.log("Writing single instance", id.studyInstanceUid);
27
28
  await perInstanceWriter(id, data);
29
+ } else if (options.verbose) {
30
+ console.log("Not writing single instance deduplicated");
28
31
  }
29
32
  studyData.addDeduplicated(data, id.filename);
30
33
  };
@@ -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");
@@ -20,7 +20,6 @@ const HashDataWriter =
20
20
  (options) =>
21
21
  async (id, key, data, additionalOptions = {}) => {
22
22
  const isRaw = ArrayBuffer.isView(data);
23
- const { storeMultipartBulkData } = options;
24
23
  const { mimeType } = additionalOptions;
25
24
  // If the file has an extension, it should be directly accessible as that file type.
26
25
  const gzip = !isRaw || (data.length > 1024 && !mimeType);
@@ -31,7 +30,7 @@ const HashDataWriter =
31
30
  mkdir: true,
32
31
  gzip,
33
32
  });
34
- if (isRaw && storeMultipartBulkData) {
33
+ if (isRaw) {
35
34
  await WriteMultipart(writeStream, [new MultipartHeader("Content-Type", "application/octet-stream")], rawData);
36
35
  } else {
37
36
  await writeStream.write(rawData);
@@ -5,24 +5,40 @@ const ExpandUriPath = require("./ExpandUriPath");
5
5
  const { MultipartHeader, MultipartAttribute } = require("./MultipartHeader");
6
6
 
7
7
  const ImageFrameWriter = (options) => {
8
- const { verbose } = options;
9
-
8
+ const { encapsulatedImage, singlePartImage } = options;
10
9
  return async (id, index, imageFrame) => {
11
10
  const { transferSyntaxUid } = id;
12
11
  const type = uids[transferSyntaxUid] || uids.default;
13
- const writeStream = WriteStream(id.imageFrameRootPath, `${1 + index}`, {
14
- gzip: type.gzip,
15
- mkdir: true,
16
- });
12
+ const extension = type.extension;
17
13
  let content;
18
14
  if (imageFrame instanceof Uint8Array) {
19
15
  content = imageFrame;
20
16
  } else {
21
17
  content = Buffer.from(imageFrame.buffer);
22
18
  }
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);
19
+
20
+ if (encapsulatedImage || !extension) {
21
+ const writeStream = WriteStream(id.imageFrameRootPath, `${1 + index}`, {
22
+ gzip: type.gzip,
23
+ mkdir: true,
24
+ });
25
+ await WriteMultipart(
26
+ writeStream,
27
+ [new MultipartHeader("Content-Type", type.contentType, [new MultipartAttribute("transfer-syntax", transferSyntaxUid)])],
28
+ content
29
+ );
30
+ await writeStream.close();
31
+ console.verbose("Wrote encapsulated image frame", id.sopInstanceUid, index + 1, type.contentType);
32
+ }
33
+ if (extension && singlePartImage) {
34
+ const writeStreamSingle = WriteStream(id.imageFrameRootPath, `${1 + index}${extension}`, {
35
+ gzip: type.gzip,
36
+ mkdir: true,
37
+ });
38
+ await writeStreamSingle.write(content);
39
+ await writeStreamSingle.close();
40
+ console.verbose("Wrote single part image frame", id.sopInstanceUid, index + 1, extension);
41
+ }
26
42
  const includeSeries = true;
27
43
  return ExpandUriPath(id, `instances/${id.sopInstanceUid}/frames`, { includeSeries, ...options });
28
44
  };
@@ -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,21 +1,14 @@
1
1
  const WriteStream = require("./WriteStream");
2
2
 
3
- const ThumbnailWriter = (options) => {
4
- const { verbose } = options;
3
+ const ThumbnailWriter = () => async (filePath, fileName, thumbBuffer) => {
4
+ const writeStream = WriteStream(filePath, fileName, {
5
+ mkdir: true,
6
+ });
5
7
 
6
- return async (filePath, fileName, thumbBuffer) => {
7
- const writeStream = WriteStream(filePath, fileName, {
8
- mkdir: true,
9
- });
8
+ await writeStream.write(thumbBuffer);
10
9
 
11
- await writeStream.write(thumbBuffer);
12
-
13
- await writeStream.close();
14
-
15
- if (verbose) {
16
- console.log("Wrote thumbnail frame", filePath, fileName);
17
- }
18
- };
10
+ console.verbose("Wrote thumbnail frame", filePath, fileName);
11
+ return writeStream.close();
19
12
  };
20
13
 
21
14
  module.exports = ThumbnailWriter;
@@ -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";
@@ -2,6 +2,8 @@ const fs = require("fs");
2
2
  const zlib = require("zlib");
3
3
  const path = require("path");
4
4
 
5
+ let writeCount = 0;
6
+
5
7
  /** Create an optionally gzipped stream,
6
8
  * where the write operations are performed in order executed,
7
9
  * and don't require synchronization, only the 'close' operation
@@ -14,6 +16,14 @@ const WriteStream = (dir, nameSrc, options = {}) => {
14
16
 
15
17
  const tempName = path.join(dir, `tempFile-${Math.round(Math.random() * 1000000000)}`);
16
18
  const finalName = path.join(dir, name);
19
+ writeCount++;
20
+ if (writeCount > 10) {
21
+ console.log("Write count", tempName, finalName, writeCount);
22
+ if (writeCount > 100) {
23
+ console.error("Too many open writes", new Error());
24
+ throw new Error(`Write count too high ${writeCount} destination ${finalName}`);
25
+ }
26
+ }
17
27
  const rawStream = fs.createWriteStream(tempName);
18
28
  const closePromise = new Promise((resolve) => {
19
29
  rawStream.on("close", () => {
@@ -33,7 +43,8 @@ const WriteStream = (dir, nameSrc, options = {}) => {
33
43
  async function close() {
34
44
  await this.writeStream.end();
35
45
  await this.closePromise;
36
- await fs.rename(tempName, finalName, () => true); // console.log('Renamed', tempName,finalName));
46
+ await fs.rename(tempName, finalName, () => true);
47
+ writeCount--;
37
48
  }
38
49
 
39
50
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radicalimaging/static-wado-creator",
3
- "version": "0.6.5",
3
+ "version": "1.2.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"
@@ -42,29 +42,25 @@
42
42
  "lib"
43
43
  ],
44
44
  "bin": {
45
- "mkdicomweb": "bin/mkdicomweb.js",
46
- "mkdicomwebdeduplicated": "bin/mkdicomwebdeduplicated.js",
47
- "mkdicomwebdeduplicatedgroup": "bin/mkdicomwebdeduplicatedgroup.js",
48
- "mkdicomwebinstances": "bin/mkdicomwebinstances.js",
49
- "mkdicomwebstudy": "bin/mkdicomwebstudy.js"
45
+ "mkdicomweb": "bin/mkdicomweb.js"
50
46
  },
51
47
  "scripts": {
52
48
  "test": "jest --config ./jest.config.js",
53
49
  "build": "echo \"No build yet\" && exit 0",
54
- "link:exec": "npm link",
55
- "lint": "npx eslint --color \"*(bin|lib)/**/*.{js,mjs}\"",
56
- "lint:fix": "npx eslint --fix --color \"*(bin|lib)/**/*.{js,mjs}\""
50
+ "link:exec": "npm install -g && npm link",
51
+ "lint": "npx eslint --color \"**/*.{js,mjs,cjs}\"",
52
+ "lint:fix": "npx eslint --fix --color \"**/*.{js,mjs,cjs}\""
57
53
  },
58
54
  "jest": {
59
55
  "verbose": true
60
56
  },
61
57
  "dependencies": {
62
- "@cornerstonejs/dicom-codec": "^0.1.3",
63
- "@radicalimaging/static-cs-lite": "^0.0.2",
64
- "@radicalimaging/static-wado-util": "^0.6.2",
65
- "canvas": "^2.9.1",
66
- "config-point": "0.5.1",
67
- "dcmjs": "^0.27.0",
58
+ "@cornerstonejs/dicom-codec": ">=0.1.6",
59
+ "@radicalimaging/static-cs-lite": ">=1.2.0",
60
+ "@radicalimaging/static-wado-util": ">=1.2.0",
61
+ "canvas": "^2.11.0",
62
+ "config-point": ">=0.5.1",
63
+ "dcmjs": "^0.29.5",
68
64
  "dicom-parser": "^1.8.13",
69
65
  "glob": "^7.2.0",
70
66
  "node-object-hash": "^2.3.10",
@@ -1,119 +0,0 @@
1
- const dcmjs = require("dcmjs");
2
- const dataDictionary = require("./dataDictionary");
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
- RawSopInstanceUID: "x00080018",
40
-
41
- // This one isn't defined in the dataDictionary
42
- AvailableTransferSyntaxUID: "00083002",
43
-
44
- DeduppedCreator,
45
-
46
- // The references to extract data included in this object, 1..n values
47
- DeduppedRef: { creator: DeduppedCreator, tag: "00091010" },
48
-
49
- // The hash value of THIS object
50
- DeduppedHash: { creator: DeduppedCreator, tag: "00091011" },
51
-
52
- // Type of hash instance
53
- DeduppedType: { creator: DeduppedCreator, tag: "00091012" },
54
- InstanceType: "instance",
55
- InfoType: "info",
56
-
57
- naturalizeDataset,
58
- denaturalizeDataset,
59
-
60
- setValue: (item, tag, value) => {
61
- const actualTag = findPrivate(item, tag, true);
62
- item[actualTag] = { Value: Array.isArray(value) ? value : [value] };
63
- return actualTag;
64
- },
65
-
66
- setList: (item, tag, value) => {
67
- const actualTag = findPrivate(item, tag, true);
68
- item[actualTag] = { Value: Array.isArray(value) ? value : [value] };
69
- return actualTag;
70
- },
71
-
72
- pushList: (item, tag, value) => {
73
- const actualTag = findPrivate(item, tag, true);
74
- if (!item[actualTag]) item[actualTag] = { Value: [] };
75
- item[actualTag].Value.push(value);
76
- return actualTag;
77
- },
78
-
79
- getValue: (item, tag) => {
80
- const actualTag = findPrivate(item, tag);
81
- if (!actualTag) return undefined;
82
- const value = item[actualTag];
83
- if (value === undefined) return undefined;
84
- if (Array.isArray(value)) {
85
- if (value.length == 1) return value[0];
86
- return value.length === 0 ? "" : value;
87
- }
88
- if (value.Value) {
89
- if (value.Value.length == 1) return value.Value[0];
90
- return value.Value.length === 0 ? "" : value.Value;
91
- }
92
- return value;
93
- },
94
-
95
- getList: (item, tag) => {
96
- const actualTag = findPrivate(item, tag);
97
- if (!actualTag) return undefined;
98
- const value = item[actualTag];
99
- if (value === undefined) return undefined;
100
- if (Array.isArray(value)) return value;
101
- if (value.Value) return value.Value;
102
- return [value];
103
- },
104
-
105
- removeValue: (item, tag) => {
106
- const actualTag = findPrivate(item, tag);
107
- if (actualTag) delete item[actualTag];
108
- // TODO, remove creator tag if not used any longer
109
- },
110
-
111
- findPrivate,
112
- };
113
-
114
- Object.keys(dataDictionary).forEach((key) => {
115
- const value = dataDictionary[key];
116
- Tags[value.name] = key;
117
- });
118
-
119
- module.exports = Tags;