Skip to content

Commit

Permalink
feat: add retrieve study's instances of WADO-RS
Browse files Browse the repository at this point in the history
- Add the WADOZip for type application/zip to response
the zip of instances files
- Add WADO-RS.service for getting path of instances and
writing the multipart payload
  • Loading branch information
Chinlinlee committed May 12, 2022
1 parent 24e1c30 commit 3c83337
Show file tree
Hide file tree
Showing 4 changed files with 301 additions and 0 deletions.
43 changes: 43 additions & 0 deletions api/dicom-web/controller/WADO-RS/retrieveStudyInstances.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const { logger } = require("../../../../utils/log");
const wadoService = require("./service/WADO-RS.service");
const { WADOZip } = require("./service/WADOZip");
const errorResponse = require("../../../../utils/errorResponse/errorResponseMessage");
/**
*
* @param {import("http").IncomingMessage} req
* @param {import("http").ServerResponse} res
*/
module.exports = async function(req, res) {
try {
logger.info(`[WADO-RS] [Get study's instances, study UID: ${req.params.studyUID}] [Request Accept: ${req.headers.accept}]`);
if (req.headers.accept.toLowerCase() === "application/zip") {
let wadoZip = new WADOZip(req.params, res);
let zipResult = await wadoZip.getZipOfStudyDICOMFiles();
if (zipResult.status) {
return res.end();
}
} else if (req.headers.accept.includes("multipart/related")) {
let type = wadoService.getAcceptType(req);
let isSupported = wadoService.supportInstanceMultipartType.indexOf(type) > -1;
if (!isSupported) {
let errorMessage = errorResponse.getNotSupportedErrorMessage(`The type ${type} is not supported, server supported multipart/related; type="application/dicom", multipart/related; type="application/octet-stream" and application/zip`);
res.writeHead(errorMessage.HttpStatus, {
"Content-Type": "application/dicom+json"
});
return res.end(JSON.stringify(errorMessage));
}
await wadoService.multipartFunc[type].getStudyDICOMFiles(req.params, req, res, type);
}
return res.end();
} catch(e) {
let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e));
logger.error(`[WADO-RS] [Error: ${errorStr}]`);
res.writeHead(500, {
"Content-Type": "application/dicom+json"
});
res.end(JSON.stringify({
code: 500,
message: errorStr
}));
}
};
174 changes: 174 additions & 0 deletions api/dicom-web/controller/WADO-RS/service/WADO-RS.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
const _ = require("lodash");
const path = require("path");
const mongoose = require("mongoose");
const { MultipartWriter } = require("../../../../../utils/multipartWriter");
/**
*
* @param {import("http").IncomingMessage} req
*/
function getAcceptType(req) {
return req.headers.accept
.match(/type=(.*)/gi)[0]
.split(/[,;]/)[0]
.substring(5)
.replace(/"/g, "");
}

/**
* Get path list of all study's instances with specific study UID
* @param {Object} iParam
*/
async function getStudyImagesPath(iParam) {
let { studyUID } = iParam;
try {
let query = [
{
$match: {
studyUID: studyUID
}
},
{
$group: {
_id: "$studyUID",
pathList: {
$addToSet: {
studyUID: "$studyUID",
seriesUID: "$seriesUID",
instanceUID: "$instanceUID",
instancePath: "$instancePath"
}
}
}
}
];
let docs = await mongoose.model("dicom").aggregate(query).exec();
let pathList = _.get(docs, "0.pathList", []);
if (pathList.length > 0) {
for (let i = 0; i < pathList.length; i++) {
pathList[i].instancePath = path.join(
process.env.DICOM_STORE_ROOTPATH,
pathList[i].instancePath
);
}
return pathList;
}
return undefined;
} catch (e) {
throw e;
}
}

/**
* Get path list of all study's series' instances with specific study UID
* @param {Object} iParam
* @returns
*/
async function getSeriesImagesPath(iParam) {
let { studyUID, seriesUID } = iParam;
try {
let query = [
{
$match: {
$and: [
{
seriesUID: seriesUID
},
{
studyUID: studyUID
}
]
}
},
{
$group: {
_id: "$seriesUID",
pathList: {
$addToSet: "$instancePath"
}
}
}
];
let docs = await mongoose.model("dicom").aggregate(query);
let pathList = docs.pop().pathList;
if (pathList.length > 0) {
for (let i = 0; i < pathList.length; i++) {
pathList[i] = path.join(
process.env.DICOM_STORE_ROOTPATH,
pathList[i]
);
}
return pathList;
}
return undefined;
} catch (e) {
throw e;
}
}

/**
* Get path
* @param {Object} iParam
*/
async function getInstanceImagePath(iParam) {
let { studyUID, seriesUID, instanceUID } = iParam;
try {
let query = {
$and: [
{
studyUID: studyUID
},
{
seriesUID: seriesUID
},
{
instanceUID: instanceUID
}
]
};
let doc = await mongoose.model("dicom").findOne(query, {
studyUID: 1,
seriesUID: 1,
instanceUID: 1,
instancePath: 1
}).exec();
if (doc) return doc;
return undefined;
} catch (e) {
throw e;
}
}

const multipartFunc = {
"application/dicom": {
getStudyDICOMFiles: async (iParam, req, res, type) => {
let imagesPath = await getStudyImagesPath(iParam);
let multipartWriter = new MultipartWriter(imagesPath, res, req);

return multipartWriter.writeDICOMFiles(type);
},
getSeriesDICOMFiles: async (iParam, req, res, type) => {
let imagesPath = await getSeriesImagesPath(iParam);
let multipartWriter = new MultipartWriter(imagesPath, res, req);
return multipartWriter.writeDICOMFiles(type);
},
getInstanceDICOMFile: async (iParam, req, res, type) => {
let imagePath = await getInstanceImagePath(iParam);
let multipartWriter = new MultipartWriter(imagePath, res, req);
return multipartWriter.writeDICOMFiles(type);
}
}
};

multipartFunc["application/octet-stream"] = {
getStudyDICOMFiles: multipartFunc["application/dicom"]["getStudyDICOMFiles"],
getSeriesDICOMFiles: multipartFunc["application/dicom"]["getSeriesDICOMFiles"],
getInstanceDICOMFile: multipartFunc["application/dicom"]["getInstanceDICOMFile"]
};
const supportInstanceMultipartType = ["application/dicom", "application/octet-stream"];

module.exports.getAcceptType = getAcceptType;
module.exports.getStudyImagesPath = getStudyImagesPath;
module.exports.getSeriesImagesPath = getSeriesImagesPath;
module.exports.getInstanceImagePath = getInstanceImagePath;
module.exports.multipartFunc = multipartFunc;
module.exports.supportInstanceMultipartType = supportInstanceMultipartType;
75 changes: 75 additions & 0 deletions api/dicom-web/controller/WADO-RS/service/WADOZip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const archiver = require("archiver");
const wadoService = require("./WADO-RS.service");
const path = require("path");

class WADOZip {
constructor(iParam, iRes) {
this.requestParams = iParam;
this.studyUID = iParam.studyUID;
this.seriesUID = iParam.seriesUID;
this.instanceUID = iParam.instanceUID;
this.res = iRes;
}

setHeaders(uid) {
this.res.attachment = `${uid}.zip`;
this.res.setHeader('Content-Type', 'application/zip');
this.res.setHeader('Content-Disposition', `attachment; filename=${uid}.zip`);
}

async getZipOfStudyDICOMFiles() {
let imagesPathList = await wadoService.getStudyImagesPath(this.requestParams);
if (imagesPathList) {
this.setHeaders(this.studyUID);

let folders = [];
for (let i = 0; i < imagesPathList.length; i++) {
let imagesFolder = path.dirname(imagesPathList[i]);
if (!folders.includes(imagesFolder)) {
folders.push(imagesFolder);
}
}
return await toZip(this.res, folders);
}
}

async getZipOfSeriesDICOMFiles() {
let seriesPathList = await wadoService.getSeriesImagesPath(
this.studyUID,
this.seriesUID
);

}
}

function toZip(res, folders=[]) {
return new Promise((resolve)=> {
let archive = archiver('zip', {
gzip: true,
zlib: { level: 9 } // Sets the compression level.
});
archive.on('error', function (err) {
console.error(err);
resolve({
status: false,
data: err
});
});
archive.pipe(res);
if (folders.length > 0) {
for (let i = 0; i < folders.length; i++) {
let folderName = path.basename(folders[i]);
//archive.append(null, {name : folderName});
archive.glob("*.dcm", {cwd: folders[i]}, {prefix: folderName});
}
}
archive.finalize().then(()=> {
resolve({
status: true,
data: "success"
});
});
});
}

module.exports.WADOZip = WADOZip;
9 changes: 9 additions & 0 deletions api/dicom-web/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,13 @@ app.post("/studies", require("./controller/STOW-RS/storeInstance"));

//#endregion

//#region WADO-RS

app.get(
"/studies/:studyUID",
require("./controller/WADO-RS/retrieveStudyInstances")
);

//#endregion

module.exports = app;

0 comments on commit 3c83337

Please sign in to comment.