Skip to content

Commit

Permalink
feat(UPS-RS): add create and get workitem APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
Chinlinlee committed May 20, 2023
1 parent 2695ccd commit 4d55216
Show file tree
Hide file tree
Showing 13 changed files with 715 additions and 3 deletions.
56 changes: 56 additions & 0 deletions api/dicom-web/controller/UPS-RS/create-workItems.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const _ = require("lodash");
const {
CreateWorkItemService
} = require("./service/create-workItem.service");
const { ApiLogger } = require("../../../../utils/logs/api-logger");
const { Controller } = require("../../../controller.class");
const { DicomWebServiceError } = require("@error/dicom-web-service");

class CreateWorkItemController extends Controller {
constructor(req, res) {
super(req, res);
}

async mainProcess() {
let apiLogger = new ApiLogger(this.request, "UPS-RS");

apiLogger.addTokenValue();
apiLogger.logger.info("Create workItem");

try {
let workItemService = new CreateWorkItemService(this.request, this.response);
let workItem = await workItemService.createUps();
apiLogger.logger.info(`Create workItem ${workItem.upsInstanceUID} successful`);
return this.response.status(201).send();
} catch (e) {
let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e));
apiLogger.logger.error(errorStr);

if (e instanceof DicomWebServiceError) {
return this.response.status(e.code).send({
status: e.status,
message: e.message
});
}

this.response.writeHead(500, {
"Content-Type": "application/dicom+json"
});
this.response.end(JSON.stringify({
code: 500,
message: "An Server Exception Occurred"
}));
}
}
}

/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
module.exports = async function (req, res) {
let controller = new CreateWorkItemController(req, res);

await controller.doPipeline();
};
46 changes: 46 additions & 0 deletions api/dicom-web/controller/UPS-RS/get-workItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const {
GetWorkItemService
} = require("./service/get-workItem.service");
const { ApiLogger } = require("../../../../utils/logs/api-logger");
const { Controller } = require("../../../controller.class");

class GetWorkItemController extends Controller {
constructor(req, res) {
super(req, res);
}

async mainProcess() {
let apiLogger = new ApiLogger(this.request, "UPS-RS");

apiLogger.addTokenValue();
apiLogger.logger.info(`Get workItem, query: ${this.queryToString()}`);

try {
let getWorkItemService = new GetWorkItemService(this.request, this.response);
let workItems = await getWorkItemService.getUps();
return this.response.set("Content-Type", "application/dicom+json").status(200).json(workItems);
} catch (e) {
let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e));
apiLogger.logger.error(errorStr);

this.response.writeHead(500, {
"Content-Type": "application/dicom+json"
});
this.response.end(JSON.stringify({
code: 500,
message: errorStr
}));
}
}
}

/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
module.exports = async function (req, res) {
let controller = new GetWorkItemController(req, res);

await controller.doPipeline();
};
92 changes: 92 additions & 0 deletions api/dicom-web/controller/UPS-RS/service/create-workItem.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const _ = require("lodash");
const workItemModel = require("@models/mongodb/models/workItems");
const patientModel = require("@models/mongodb/models/patient");
const { UIDUtils } = require("@dcm4che/util/UIDUtils");
const {
DicomWebServiceError,
DicomWebStatusCodes
} = require("@error/dicom-web-service");
const { DicomJsonModel } = require("@models/DICOM/dicom-json-model");

class CreateWorkItemService {
constructor(req, res) {
this.request = req;
this.response = res;
this.requestWorkItem = /** @type {Object[]} */(this.request.body).pop();
/** @type {DicomJsonModel} */
this.requestWorkItem = new DicomJsonModel(this.requestWorkItem);
}

async createUps() {
let uid = _.get(this.request, "query.workitem",
await UIDUtils.createUID()
);
_.set(this.requestWorkItem.dicomJson, "upsInstanceUID", uid);
_.set(this.requestWorkItem.dicomJson, "00080018", {
vr: "UI",
Value: [
uid
]
});
let workListLabel = this.requestWorkItem.getString("00741202");
if (!workListLabel) {
_.set(this.requestWorkItem, "00741202", {
vr: "LO",
Value: [
"RACCOON"
]
});
}

if (this.requestWorkItem.getString("00741000") !== "SCHEDULED") {
throw new DicomWebServiceError(
DicomWebStatusCodes.UPSNotScheduled,
`Refused: The provided value of UPS State was not "SCHEDULED"`,
400
);
}

let patient = await this.findOneOrCreatePatient();

let workItem = new workItemModel(this.requestWorkItem.dicomJson);

if (await this.isUpsExist(uid)) {
throw new DicomWebServiceError(
DicomWebStatusCodes.DuplicateSOPinstance,
`SOP Instance UID that was already allocated to another SOP Instance`,
400
);
}
await workItem.save();


//TODO: subscription
return workItem;
}

async findOneOrCreatePatient() {
let patientId = this.requestWorkItem.getString("00100020");
_.set(this.requestWorkItem.dicomJson, "patientID", patientId);

/** @type {patientModel | null} */
let patient = await patientModel.findOne({
"00100020.Value": patientId
});

if (!patient) {
/** @type {patientModel} */
let patientObj = new patientModel(this.requestWorkItem.dicomJson);
patient = await patientObj.save();
}

return patient;
}

async isUpsExist(uid) {
return await workItemModel.findOne({
upsInstanceUID: uid
});
}
}

module.exports.CreateWorkItemService = CreateWorkItemService;
58 changes: 58 additions & 0 deletions api/dicom-web/controller/UPS-RS/service/get-workItem.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const _ = require("lodash");
const workItemsModel = require("@models/mongodb/models/workItems");
const {
convertAllQueryToDICOMTag,
convertRequestQueryToMongoQuery
} = require("../../QIDO-RS/service/QIDO-RS.service");

class GetWorkItemService {
constructor(req, res) {
this.request = req;
this.response = res;
this.query = {};

/**
* @private
*/
this.limit_ = parseInt(this.request.query.limit) || 100;
delete this.request.query["limit"];

/**
* @private
*/
this.skip_ = parseInt(this.request.query.offset) || 0;
delete this.request.query["offset"];


this.initQuery_();
}

async getUps() {
let mongoQuery = (await convertRequestQueryToMongoQuery(this.query)).$match;

let queryOptions = {
query: mongoQuery,
skip: this.skip_,
limit: this.limit_,
requestParams: this.request.params
};

let docs = await workItemsModel.getDicomJson(queryOptions);

return docs;
}

initQuery_() {
let query = _.cloneDeep(this.request.query);
let queryKeys = Object.keys(query).sort();
for (let i = 0; i < queryKeys.length; i++) {
let queryKey = queryKeys[i];
if (!query[queryKey]) delete query[queryKey];
}

this.query = convertAllQueryToDICOMTag(query);
}

}

module.exports.GetWorkItemService = GetWorkItemService;
66 changes: 66 additions & 0 deletions api/dicom-web/ups-rs.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_11
* https://dicom.nema.org/medical/dicom/2019a/output/chtml/part18/sect_6.9.html
* @author Chin-Lin, Lee <[email protected]>
*/
const express = require("express");
const Joi = require("joi");
const { validateParams, intArrayJoi, validateByJoi } = require("../validator");
const router = express();
const GLOBAL_SUBSCRIPTION_UIDS = [
"1.2.840.10008.5.1.4.34.5",
"1.2.840.10008.5.1.4.34.5.1"
];

//#region UPS-RS

/**
* @openapi
* /dicom-web/workitems:
* post:
* tags:
* - UPS-RS
* description: >
* This transaction creates a Workitem on the target Worklist. It corresponds to the UPS DIMSE N-CREATE operation.
* See [Create Workitem Transaction](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_11.4)
* parameters:
* - $ref: "#/components/parameters/workitem"
* responses:
* "201":
* description: The workitem create successfully
*/
router.post("/workitems",
validateParams({
workitem: Joi.string().invalid(...GLOBAL_SUBSCRIPTION_UIDS).optional()
}, "query", {
allowUnknown: false
}),
validateByJoi(Joi.array().items(Joi.object()).min(1).max(1), "body"),
require("./controller/UPS-RS/create-workItems")
);


/**
* @openapi
* /dicom-web/workitems:
* get:
* tags:
* - UPS-RS
* description: >
* This transaction retrieves a Workitem. It corresponds to the UPS DIMSE N-GET operation.
* See [Retrieve Workitem Transaction](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_11.5)
* responses:
* "200":
* description: Query successfully
* content:
* "application/dicom+json":
* schema:
* type: array
*/
router.get("/workitems",
require("./controller/UPS-RS/get-workItem")
);

//#endregion

module.exports = router;
44 changes: 44 additions & 0 deletions docs/swagger/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,42 @@
}
}
},
"/dicom-web/workitems": {
"post": {
"tags": [
"UPS-RS"
],
"description": "This transaction creates a Workitem on the target Worklist. It corresponds to the UPS DIMSE N-CREATE operation. See [Create Workitem Transaction](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_11.4)\n",
"parameters": [
{
"$ref": "#/components/parameters/workitem"
}
],
"responses": {
"201": {
"description": "The workitem create successfully"
}
}
},
"get": {
"tags": [
"UPS-RS"
],
"description": "This transaction retrieves a Workitem. It corresponds to the UPS DIMSE N-GET operation. See [Retrieve Workitem Transaction](https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_11.5)\n",
"responses": {
"200": {
"description": "Query successfully",
"content": {
"application/dicom+json": {
"schema": {
"type": "array"
}
}
}
}
}
}
},
"/dicom-web/studies/{studyUID}/bulkdata": {
"get": {
"tags": [
Expand Down Expand Up @@ -1219,6 +1255,14 @@
"type": "string"
}
},
"workitem": {
"name": "workitem",
"description": "workitem instance UID",
"in": "query",
"schema": {
"type": "string"
}
},
"frameNumbers": {
"name": "frameNumbers",
"description": "comma separated list of one or more non duplicate frame numbers",
Expand Down
8 changes: 8 additions & 0 deletions docs/swagger/parameters/ups.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
components:
parameters:
"workitem":
name: "workitem"
description: workitem instance UID
in: query
schema:
type: string
Loading

0 comments on commit 4d55216

Please sign in to comment.