Skip to content

Commit

Permalink
feat: use custom dcm2jpg wrapper
Browse files Browse the repository at this point in the history
- Use node-java-bridge to new dcm2jpg instance will cause OOM
- Create custom dcm2jpg wrapper(java) to execute dcm2jpg
- `Dcm2JpgExecutor` would new `Dcm2Jpg`,
set options, and convert to image file
  • Loading branch information
Chinlinlee committed Apr 6, 2023
1 parent 215072c commit fcdaedc
Show file tree
Hide file tree
Showing 28 changed files with 1,075 additions and 174 deletions.
26 changes: 16 additions & 10 deletions api/WADO-URI/service/WADO-URI.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ const mongoose = require("mongoose");
const fs = require("fs");
const _ = require("lodash");
const renderedService = require("../../dicom-web/controller/WADO-RS/service/rendered.service");
const { jsDcm2Jpeg ,JsDcm2Jpeg } = require("../../../models/DICOM/dcm4che/Dcm2Jpeg");
const { Dcm2JpgExecutor } = require("../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor");
const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions");
const sharp = require('sharp');
const Magick = require("../../../models/magick");
const { NotFoundInstanceError, InvalidFrameNumberError } = require("../../../error/dicom-instance");
Expand Down Expand Up @@ -147,24 +148,29 @@ class WadoUriService {
throw new InvalidFrameNumberError(`Invalid Frame Number, total ${instanceTotalFrameNumber}, but requested ${frameNumber}`);
}

let otherOptions = {
frameNumber
};
/** @type {Dcm2JpgExecutor$Dcm2JpgOptions} */
let options = await Dcm2JpgExecutor$Dcm2JpgOptions.newInstanceAsync();
options.frameNumber = frameNumber;

if (windowCenter && windowWidth) {
_.set(otherOptions, "windowCenter", windowCenter);
_.set(otherOptions, "windowWidth", windowWidth);
options.windowCenter = windowCenter;
options.windowWidth = windowWidth;
}

let dicomFilename = instanceFramesObj.instancePath;
let jpegFile = dicomFilename.replace(/\.dcm\b/gi , `.${frameNumber-1}.jpg`);

let getFrameImageStatus = await jsDcm2Jpeg.getFrameImage(instanceFramesObj.instancePath, otherOptions);
let getFrameImageStatus = await Dcm2JpgExecutor.convertDcmToJpgFromFilename(
dicomFilename,
jpegFile,
options
);

if (getFrameImageStatus.status) {
let imagePath = getFrameImageStatus.imagePath;

return {
imageSharp: sharp(imagePath),
magick: new Magick(imagePath)
imageSharp: sharp(jpegFile),
magick: new Magick(jpegFile)
};
}

Expand Down
35 changes: 21 additions & 14 deletions api/dicom-web/controller/STOW-RS/service/dicom-jpeg-generator.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
const fs = require("fs");
const { jsDcm2Jpeg, JsDcm2Jpeg } = require("../../../../../models/DICOM/dcm4che/Dcm2Jpeg");
const { Dcm2JpgExecutor } = require("../../../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor");
const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions");
const { logger } = require("../../../../../utils/log");
const dicomToJpegTask = require("../../../../../models/mongodb/models/dicomToJpegTask");
const colorette = require("colorette");

/**
* @typedef JsDcm2JpegTask
* @property {JsDcm2Jpeg} jsDcm2Jpeg
* @property {Dcm2JpgExecutor$Dcm2JpgOptions} jsDcm2Jpeg
* @property {string} jpegFilename
*/

Expand Down Expand Up @@ -39,7 +40,7 @@ class DicomJpegGenerator {

await this.insertEndTask_();

} catch(e) {
} catch (e) {

let errorMessage = JSON.stringify(e, Object.getOwnPropertyNames(e));
await this.insertErrorTask_(errorMessage);
Expand All @@ -55,22 +56,28 @@ class DicomJpegGenerator {
/** @type {JsDcm2JpegTask[]} */
let jsDcm2JpegArr = new Array();

for(let i =1 ; i <= this.dicomJsonModel.getFrameNumber(); i++) {
for (let i = 1; i <= this.dicomJsonModel.getFrameNumber(); i++) {

/** @type { Dcm2JpgExecutor$Dcm2JpgOptions } */
let opt = await Dcm2JpgExecutor$Dcm2JpgOptions.newInstanceAsync();
let windowCenter = this.dicomJsonModel.getWindowCenter();
let windowWidth = this.dicomJsonModel.getWindowCenter();

if (windowCenter && windowWidth) {
opt.windowCenter = windowCenter;
opt.windowWidth = windowWidth;
}

opt.frameNumber = i;
jsDcm2JpegArr.push({
jsDcm2JpegOption: {
...JsDcm2Jpeg.defaultOptions,
windowCenter: this.dicomJsonModel.getWindowCenter(),
windowWidth: this.dicomJsonModel.getWindowWidth(),
frameNumber: i
},
jpegFilename: `${this.jpegFilename}.${i-1}.jpg`
jsDcm2JpegOption: opt,
jpegFilename: `${this.jpegFilename}.${i - 1}.jpg`
});

if (i % DCMTK_GENERATE_JPEG_EVERY_N_STEP === 0) {
await Promise.allSettled(
jsDcm2JpegArr.map(async (j) =>
jsDcm2Jpeg.convert(this.dicomInstanceFilename, j.jpegFilename, j.jsDcm2JpegOption)
Dcm2JpgExecutor.convertDcmToJpgFromFilename(this.dicomInstanceFilename, j.jpegFilename, j.jsDcm2JpegOption)
)
);
jsDcm2JpegArr = new Array();
Expand All @@ -95,7 +102,7 @@ class DicomJpegGenerator {
};

await dicomToJpegTask.insertOrUpdate(startTaskObj);
}
}

/**
* @private
Expand Down Expand Up @@ -127,7 +134,7 @@ class DicomJpegGenerator {
};
await dicomToJpegTask.insertOrUpdate(errorTaskObj);
}

}

module.exports.DicomJpegGenerator = DicomJpegGenerator;
17 changes: 12 additions & 5 deletions api/dicom-web/controller/WADO-RS/service/rendered.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ const path = require("path");
const mongoose = require("mongoose");
const fs = require("fs");
const sharp = require("sharp");
const { jsDcm2Jpeg } = require("../../../../../models/DICOM/dcm4che/Dcm2Jpeg");
const { Dcm2JpgExecutor } = require("../../../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor");
const { Dcm2JpgExecutor$Dcm2JpgOptions } = require("../../../../../models/DICOM/dcm4che/wrapper/org/github/chinlinlee/dcm2jpg/Dcm2JpgExecutor$Dcm2JpgOptions");
const Magick = require("../../../../../models/magick");
const _ = require("lodash");

Expand Down Expand Up @@ -152,12 +153,18 @@ async function getInstanceFrameObj(iParam, otherFields={}) {
async function postProcessFrameImage(req, frameNumber, instanceFramesObj) {
try {

let getFrameImageStatus = await jsDcm2Jpeg.getFrameImage(instanceFramesObj.instancePath, {frameNumber});
let dicomFilename = instanceFramesObj.instancePath;
let jpegFile = dicomFilename.replace(/\.dcm\b/gi , `.${frameNumber-1}.jpg`);

let getFrameImageStatus = await Dcm2JpgExecutor.convertDcmToJpgFromFilename(
dicomFilename,
jpegFile,
await Dcm2JpgExecutor$Dcm2JpgOptions.newInstanceAsync()
);

if (getFrameImageStatus.status) {
let imagePath = getFrameImageStatus.imagePath;
let imageSharp = sharp(imagePath);
let magick = new Magick(imagePath);
let imageSharp = sharp(jpegFile);
let magick = new Magick(jpegFile);
handleImageQuality(
req.query,
magick
Expand Down
135 changes: 0 additions & 135 deletions models/DICOM/dcm4che/Dcm2Jpeg.js

This file was deleted.

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { JavaClass, InterfaceProxyOptions, JavaInterfaceProxy } from "java-bridge";
import { BufferedImage as java_awt_image_BufferedImage } from "./../../../../java/awt/image/BufferedImage";
import { File as java_io_File } from "./../../../../java/io/File";
/**
* This class just defines types, you should import {@link Dcm2Jpg$ReadImage} instead of this.
* This was generated by java-bridge.
* You should probably not edit this.
*/
export declare class Dcm2Jpg$ReadImageClass extends JavaClass {
/**
* @param var0 original type: 'java.io.File'
* @return original return type: 'java.awt.image.BufferedImage'
*/
apply(var0: java_io_File | null): Promise<java_awt_image_BufferedImage | null>;
/**
* @param var0 original type: 'java.io.File'
* @return original return type: 'java.awt.image.BufferedImage'
*/
applySync(var0: java_io_File | null): java_awt_image_BufferedImage | null;
}
/**
* This interface just defines types for creating proxies,
* you should use {@link createDcm2Jpg$ReadImageProxy} for actually creating the proxies.
*
* Optional methods in here may still be required by java.
* This is caused by typescript not allowing to have both optional and
* non-optional signatures for the same interface member.
*
* This was generated by java-bridge.
* You should probably not edit this.
*/
export interface Dcm2Jpg$ReadImageInterface {
/**
* @param var0 original type: 'java.io.File'
* @return original return type: 'java.awt.image.BufferedImage'
*/
apply(var0: java_io_File | null): java_awt_image_BufferedImage | null;
}
/**
* Create a proxy for the {@link Dcm2Jpg$ReadImage} interface.
* All required methods in {@link Dcm2Jpg$ReadImageInterface} must be implemented.
*
* @param methods the methods to implement
* @param opts the proxy options
* @return the proxy
*/
export declare function createDcm2Jpg$ReadImageProxy(methods: Dcm2Jpg$ReadImageInterface, opts?: InterfaceProxyOptions): JavaInterfaceProxy<Dcm2Jpg$ReadImageInterface>;
declare const Dcm2Jpg$ReadImage_base: typeof Dcm2Jpg$ReadImageClass;
/**
* Class org.github.chinlinlee.dcm2jpg.Dcm2Jpg$ReadImage.
*
* This actually imports the java class for further use.
* The class {@link Dcm2Jpg$ReadImageClass} only defines types, this is the class you should actually import.
* Please note that this statement imports the underlying java class at runtime, which may take a while.
* This was generated by java-bridge.
* You should probably not edit this.
*/
export declare class Dcm2Jpg$ReadImage extends Dcm2Jpg$ReadImage_base {
/**
* Private constructor to prevent instantiation
* as this is either an abstract class or an interface
*/
private constructor();
}
export default Dcm2Jpg$ReadImage;
//# sourceMappingURL=Dcm2Jpg$ReadImage.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit fcdaedc

Please sign in to comment.