Skip to content

Commit

Permalink
feat: implement patient query of C-Find
Browse files Browse the repository at this point in the history
  • Loading branch information
Chinlinlee committed Aug 20, 2023
1 parent 6cf73a0 commit 1a99d45
Show file tree
Hide file tree
Showing 120 changed files with 5,263 additions and 4,850 deletions.
10 changes: 6 additions & 4 deletions dimse/c-find.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const { default: QueryRetrieveLevel2 } = require("@dcm4che/net/service/QueryRetr
const { default: EnumSet } = require("@java-wrapper/java/util/EnumSet");
const { default: BasicModCFindSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/BasicModCFindSCP");
const { createCFindSCPInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/CFindSCPInject");

const { JsPatientQueryTask } = require("./patientQueryTask");


class JsCFindScp {
Expand All @@ -29,16 +29,18 @@ class JsCFindScp {
*/
onDimseRQ: async (as, pc, dimse, rq, keys) => {
if (await dimse.compareTo(Dimse.C_FIND_RQ) !== 0) {
console.log("no reg");
throw new Error(Status.UnrecognizedOperation);
}
await cFindScpInjectProxyMethods.calculateMatches(as, pc, rq, keys);
let queryTask = await cFindScpInjectProxyMethods.calculateMatches(as, pc, rq, keys);
let applicationEntity = await as.getApplicationEntity();
let device = await applicationEntity.getDevice();
await device.execute(queryTask);
},
calculateMatches: async (as, pc, rq, keys) => {
try {
let level = await basicModCFindSCP.getQrLevel(as, pc, rq, keys);
if (await level.compareTo(QueryRetrieveLevel2.PATIENT) === 0) {
console.log("do patient query");
return await (new JsPatientQueryTask(as, pc, rq, keys)).get();
}
} catch(e) {
console.error(e);
Expand Down
10 changes: 1 addition & 9 deletions dimse/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,4 @@ class DcmQrScp {
process.stdin.resume();

let dcmQrScp = new DcmQrScp();
dcmQrScp.start();

process.on('uncaughtException', (err, origin) => {
console.log(
process.stderr.fd,
`Caught exception: ${err}\n` +
`Exception origin: ${origin}`
);
});
dcmQrScp.start();
145 changes: 145 additions & 0 deletions dimse/patientQueryTask.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
const _ = require("lodash");
const { default: PatientQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/PatientQueryTask");
const { PatientQueryTaskInjectInterface, createPatientQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/PatientQueryTaskInject");
const { createQueryTaskInjectProxy, QueryTaskInjectInterface } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject");
const { default: Attributes } = require("@dcm4che/data/Attributes");
const { default: Tag } = require("@dcm4che/data/Tag");
const { default: VR } = require("@dcm4che/data/VR");
const { DimseQueryBuilder } = require("./queryBuilder");
const patientModel = require("@models/mongodb/models/patient");


class JsPatientQueryTask {
constructor(as, pc, rq, keys) {
this.as = as;
this.pc = pc;
this.rq = rq;
this.keys = keys;

this.patientAttr = null;
this.init = false;
this.cursor = null;
this.patient = null;
}

async get() {
let patientQueryTask = await PatientQueryTask.newInstanceAsync(
this.as,
this.pc,
this.rq,
this.keys,
this.getQueryTaskInjectProxy(),
this.getPatientQueryTaskInjectProxy()
);
return patientQueryTask;
}

getQueryTaskInjectProxy() {
/** @type { QueryTaskInjectInterface } */
this.queryTaskInjectMethods = {
hasMoreMatches: () => {
return !_.isNull(this.patientAttr);
},
nextMatch: async () => {
let tempRecord = this.patientAttr;
await this.patientQueryTaskInjectMethods.wrappedFindNextPatient();
return tempRecord;
},
adjust: async (match) => {
let basicAd = await this.basicAdjust(match);
await basicAd.remove(Tag.DirectoryRecordType);

if (await this.keys.contains(Tag.SOPClassUID)) {
await basicAd.setString(Tag.SOPClassUID, VR.UI, await match.getString(Tag.ReferencedSOPClassUIDInFile));
}

if (await this.keys.contains(Tag.SOPInstanceUID)) {
await basicAd.setString(Tag.SOPInstanceUID, VR.UI, await match.getString(Tag.ReferencedSOPInstanceUIDInFile));
}

await basicAd.setString(Tag.QueryRetrieveLevel, VR.CS, await this.keys.getString(Tag.QueryRetrieveLevel));

return basicAd;
}
};

if (!this.queryTaskInjectProxy) {
this.queryTaskInjectProxy = createQueryTaskInjectProxy(this.queryTaskInjectMethods);
}

return this.queryTaskInjectProxy;
}

getPatientQueryTaskInjectProxy() {

/** @type { PatientQueryTaskInjectInterface }*/
this.patientQueryTaskInjectMethods = {
wrappedFindNextPatient: async () => {
await this.patientQueryTaskInjectMethods.findNextPatient();
},
getPatient: async () => {
let queryBuilder = new DimseQueryBuilder(this.keys, "patient");
let normalQuery = await queryBuilder.toNormalQuery();
let mongoQuery = await queryBuilder.getMongoQuery(normalQuery);

if (_.isNull(this.patientAttr) && !this.init) {
let returnKeys = this.getReturnKeys(normalQuery);

this.cursor = await patientModel.getDimseResultCursor({
...mongoQuery.$match
}, returnKeys);

this.patient = await this.cursor.next();
this.patientAttr = this.patient ? await this.patient.getAttributes() : null;
} else {
this.patient = await this.cursor.next();
this.patientAttr = this.patient ? await this.patient.getAttributes() : null;
}

},
findNextPatient: async () => {
await this.patientQueryTaskInjectMethods.getPatient();
return !_.isNull(this.patientAttr);
}
};

if (!this.patientQueryTaskProxy) {
this.patientQueryTaskProxy = createPatientQueryTaskInjectProxy(this.patientQueryTaskInjectMethods);
}

return this.patientQueryTaskProxy;
}

/**
*
* @param {Attributes} match
* @returns
*/
async basicAdjust(match) {
if (match == null) {
return null;
}

let filtered = new Attributes(await match.size());

if (!await this.keys.contains(Tag.SpecificCharacterSet)) {
let ss = await match.getStrings(Tag.SpecificCharacterSet);
if (!ss)
await filtered.setString(Tag.SpecificCharacterSet, VR.CS, ss);
}
await filtered.addSelected(match, this.keys);
await filtered.supplementEmpty(this.keys);
return filtered;
}

getReturnKeys(query) {
let returnKeys = {};
let queryKeys = Object.keys(query);
for (let i = 0; i < queryKeys.length; i++) {
returnKeys[queryKeys[i].split(".").shift()] = 1;
}
return returnKeys;
}
}

module.exports.JsPatientQueryTask = JsPatientQueryTask;
52 changes: 52 additions & 0 deletions dimse/queryBuilder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const _ = require("lodash");

const { default: Attributes } = require("@dcm4che/data/Attributes");
const { queryTagsOfEachLevel } = require("./queryTagsOfEachLevel");
// const { default: ElementDictionary } = require("@dcm4che/data/ElementDictionary");
const { default: StringUtils } = require("@dcm4che/util/StringUtils");
const { intTagToString } = require("./utils");
const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service");

// const dict = ElementDictionary.getStandardElementDictionarySync();

class DimseQueryBuilder {

/**
*
* @param {Attributes} queryKeys
* @param {"patient" | "study" | "series" | "instance"} level
*/
constructor(queryKeys, level="patient") {
this.queryKeys = queryKeys;
this.level = level;
}

async toNormalQuery() {
const queryTags = queryTagsOfEachLevel[this.level];
let query = {};
for (let i = 0 ; i < queryTags.length ; i++) {
let tag = queryTags[i];
/** @type {string[]} */
let tagStringValues = await StringUtils.maskNull(await this.queryKeys.getStrings(tag));
query[`${intTagToString(tag)}.Value`] = tagStringValues.join(",");
}
return query;
}

cleanEmptyQuery(query) {
let clonedQuery = _.cloneDeep(query);
for (let key in query) {
if (!query[key])
delete clonedQuery[key];
}
return clonedQuery;
}

async getMongoQuery(query) {
return await convertRequestQueryToMongoQuery(
this.cleanEmptyQuery(query)
);
}
}

module.exports.DimseQueryBuilder = DimseQueryBuilder;
33 changes: 33 additions & 0 deletions dimse/queryTagsOfEachLevel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const { default: Tag } = require("@dcm4che/data/Tag");

const queryTagsOfEachLevel = {
"patient": [
Tag.PatientName,
Tag.PatientID,
Tag.PatientBirthDate
],
"study": [
Tag.PatientID,
Tag.StudyInstanceUID,
Tag.StudyDate,
Tag.StudyTime,
Tag.AccessionNumber
],
"series": [
Tag.PatientID,
Tag.StudyInstanceUID,
Tag.SeriesInstanceUID,
Tag.Modality,
Tag.SeriesNumber
],
"instance": [
Tag.PatientID,
Tag.StudyInstanceUID,
Tag.SeriesInstanceUID,
Tag.SOPInstanceUID,
Tag.SOPClassUID,
Tag.InstanceNumber
]
};

module.exports.queryTagsOfEachLevel = queryTagsOfEachLevel;
9 changes: 9 additions & 0 deletions dimse/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
*
* @param {number} tag
*/
function intTagToString(tag) {
return tag.toString(16).padStart(8, "0");
}

module.exports.intTagToString = intTagToString;
Binary file not shown.
20 changes: 10 additions & 10 deletions models/DICOM/dcm4che/wrapper/java/net/DatagramPacket.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,6 @@ export declare class DatagramPacketClass extends JavaClass {
* @return original return type: 'int'
*/
getOffsetSync(): number;
/**
* @param var0 original type: 'int'
* @return original return type: 'void'
*/
setPort(var0: java_lang_Integer | number): Promise<void>;
/**
* @param var0 original type: 'int'
* @return original return type: 'void'
*/
setPortSync(var0: java_lang_Integer | number): void;
/**
* @param var0 original type: 'byte[]'
* @return original return type: 'void'
Expand Down Expand Up @@ -123,6 +113,16 @@ export declare class DatagramPacketClass extends JavaClass {
* @return original return type: 'java.net.SocketAddress'
*/
getSocketAddressSync(): java_net_SocketAddress | null;
/**
* @param var0 original type: 'int'
* @return original return type: 'void'
*/
setPort(var0: java_lang_Integer | number): Promise<void>;
/**
* @param var0 original type: 'int'
* @return original return type: 'void'
*/
setPortSync(var0: java_lang_Integer | number): void;
/**
* @param var0 original type: 'long'
* @param var1 original type: 'int'
Expand Down
Loading

0 comments on commit 1a99d45

Please sign in to comment.