diff --git a/dimse/c-find.js b/dimse/c-find.js index 1bd5c061..c0ab0304 100644 --- a/dimse/c-find.js +++ b/dimse/c-find.js @@ -12,6 +12,8 @@ const { JsStudyQueryTask } = require("@dimse-study-query-task"); const { JsSeriesQueryTask } = require("@dimse-series-query-task"); const { JsInstanceQueryTask } = require("@dimse-instance-query-task"); const { PATIENT_ROOT_LEVELS, STUDY_ROOT_LEVELS, PATIENT_STUDY_ONLY_LEVELS } = require("./level"); +const { Tag } = require("@dcm4che/data/Tag"); +const { JsMwlQueryTask } = require("./mwlQueryTask"); class JsCFindScp { constructor() { } @@ -59,6 +61,20 @@ class JsCFindScp { return basicModCFindSCP; } + getMwlLevel() { + const cFindScpInject = createCFindSCPInjectProxy(this.getCFindScpInjectProxyMethods(), { + keepAsDaemon: true + }); + + let basicModCFindSCP = new BasicModCFindSCP( + cFindScpInject, + [UID.ModalityWorklistInformationModelFind] + ); + + this.scpObj = basicModCFindSCP; + return basicModCFindSCP; + } + getCFindScpInjectProxyMethods() { /** * @type { import("@java-wrapper/org/github/chinlinlee/dcm777/net/CFindSCPInject").CFindSCPInjectInterface } @@ -83,6 +99,11 @@ class JsCFindScp { }, calculateMatches: async (as, pc, rq, keys) => { try { + let requestSopClassUID = await rq.getString(Tag.AffectedSOPClassUID); + if (requestSopClassUID === UID.ModalityWorklistInformationModelFind ) { + return await (new JsMwlQueryTask(as, pc, rq, keys)).get(); + } + let level = await this.scpObj.getQrLevel(as, pc, rq, keys); if (await level.compareTo(QueryRetrieveLevel2.PATIENT) === 0) { return await (new JsPatientQueryTask(as, pc, rq, keys)).get(); diff --git a/dimse/index.js b/dimse/index.js index fcf9ee3a..7086d08a 100644 --- a/dimse/index.js +++ b/dimse/index.js @@ -57,6 +57,7 @@ class DcmQrScp { await dicomServiceRegistry.addDicomService(new JsCFindScp().getPatientRootLevel()); await dicomServiceRegistry.addDicomService(new JsCFindScp().getStudyRootLevel()); await dicomServiceRegistry.addDicomService(new JsCFindScp().getPatientStudyOnlyLevel()); + await dicomServiceRegistry.addDicomService(new JsCFindScp().getMwlLevel()); // #endregion // #region C-MOVE diff --git a/dimse/mwlQueryTask.js b/dimse/mwlQueryTask.js new file mode 100644 index 00000000..c3708143 --- /dev/null +++ b/dimse/mwlQueryTask.js @@ -0,0 +1,120 @@ +const _ = require("lodash"); +const { PatientQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/PatientQueryTask"); +const { createQueryTaskInjectProxy } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/QueryTaskInject"); +const { Attributes } = require("@dcm4che/data/Attributes"); +const { Tag } = require("@dcm4che/data/Tag"); +const { VR } = require("@dcm4che/data/VR"); +const { Association } = require("@dcm4che/net/Association"); +const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext"); +const { logger } = require("@root/utils/logs/log"); +const { UID } = require("@dcm4che/data/UID"); +const { QueryTaskUtils } = require("./utils"); +const { default: BasicModQueryTask } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/BasicModQueryTask"); +const { MwlItemModel } = require("@models/mongodb/models/mwlitems.model"); + + +class JsMwlQueryTask { + constructor(as, pc, rq, keys) { + /** @type { Association } */ + this.as = as; + /** @type { PresentationContext } */ + this.pc = pc; + /** @type { Attributes } */ + this.rq = rq; + /** @type { Attributes } */ + this.keys = keys; + + this.mwlAttr = null; + this.mwl = null; + } + + async get() { + let mwlQueryTask = await BasicModQueryTask.newInstanceAsync( + this.as, + this.pc, + this.rq, + this.keys, + this.getQueryTaskInjectProxy() + ); + + await this.initCursor(); + + return mwlQueryTask; + } + + getQueryTaskInjectProxy() { + // for creating one + if (!this.matchIteratorProxy) { + this.matchIteratorProxy = new MwlMatchIteratorProxy(this); + } + + return this.matchIteratorProxy.get(); + } + + /** + * + * @param {Attributes} match + * @returns + */ + async basicAdjust(match) { + if (match == null) { + return null; + } + + let filtered = new Attributes(await match.size()); + + await filtered.setNull(Tag.SpecificCharacterSet, VR.CS); + await filtered.addSelected(match, this.keys); + await filtered.supplementEmpty(this.keys); + return filtered; + } + + async initCursor() { + let queryAuditManager = await QueryTaskUtils.getAuditManager(this.as); + let dbQuery = await QueryTaskUtils.getDbQuery(this.keys, "mwl"); + queryAuditManager.onQuery( + UID.ModalityWorklistInformationModelFind, + JSON.stringify(dbQuery), + "UTF-8" + ); + + let returnKeys = await QueryTaskUtils.getReturnKeys(this.keys, "mwl"); + + logger.info(`do DIMSE Modality Work List query: ${JSON.stringify(dbQuery)}`); + this.cursor = await MwlItemModel.getDimseResultCursor({ + ...dbQuery + }, returnKeys); + } +} + +class MwlMatchIteratorProxy { + constructor(mwlQueryTask) { + /** @type {JsMwlQueryTask} */ + this.mwlQueryTask = mwlQueryTask; + } + + get() { + return createQueryTaskInjectProxy(this.getProxyMethods(), { + keepAsDaemon: true + }); + } + + getProxyMethods() { + return { + hasMoreMatches: async () => { + this.mwlQueryTask.mwl = await this.mwlQueryTask.cursor.next(); + return !_.isNull(this.mwlQueryTask.mwl); + }, + nextMatch: async () => { + this.mwlQueryTask.mwlAttr = this.mwlQueryTask.mwl ? await this.mwlQueryTask.mwl.getAttributes() : null; + return this.mwlQueryTask.mwlAttr; + }, + adjust: async (match) => { + return this.mwlQueryTask.basicAdjust(match); + } + }; + } +} + +module.exports.JsMwlQueryTask = JsMwlQueryTask; +module.exports.MwlMatchIteratorProxy = MwlMatchIteratorProxy; \ No newline at end of file diff --git a/dimse/queryBuilder.js b/dimse/queryBuilder.js index b49c84ea..83a51663 100644 --- a/dimse/queryBuilder.js +++ b/dimse/queryBuilder.js @@ -5,15 +5,16 @@ const { queryTagsOfEachLevel } = require("./queryTagsOfEachLevel"); const { StringUtils } = require("@dcm4che/util/StringUtils"); const { intTagToString } = require("./utils"); const { convertRequestQueryToMongoQuery } = require("@root/api/dicom-web/controller/QIDO-RS/service/query-dicom-json-factory"); +const { default: Tag } = require("@dcm4che/data/Tag"); class DimseQueryBuilder { /** * * @param {Attributes} queryKeys - * @param {"patient" | "study" | "series" | "instance"} level + * @param {"patient" | "study" | "series" | "instance" | "mwl"} level */ - constructor(queryKeys, level="patient") { + constructor(queryKeys, level = "patient") { this.queryKeys = queryKeys; this.level = level; } @@ -21,7 +22,20 @@ class DimseQueryBuilder { async toNormalQuery() { const queryTags = queryTagsOfEachLevel[this.level]; let query = {}; - for (let i = 0 ; i < queryTags.length ; i++) { + + let spsKeys = await this.queryKeys.getNestedDataset(Tag.ScheduledProcedureStepSequence); + if (spsKeys != null && + !(await spsKeys.isEmpty()) && + this.level === "mwl" + ) { + let spsQueryTags = queryTagsOfEachLevel.mwlSps; + for (let i = 0; i < spsQueryTags.length; i++) { + let tagStringValues = await StringUtils.maskNull(await spsKeys.getStrings(spsQueryTags[i])); + query[`${intTagToString(Tag.ScheduledProcedureStepSequence)}.Value.${intTagToString(spsQueryTags[i])}.Value`] = tagStringValues.join(","); + } + } + + for (let i = 0; i < queryTags.length; i++) { let tag = queryTags[i]; /** @type {string[]} */ let tagStringValues = await StringUtils.maskNull(await this.queryKeys.getStrings(tag)); diff --git a/dimse/queryTagsOfEachLevel.js b/dimse/queryTagsOfEachLevel.js index aa50060d..aced5b8e 100644 --- a/dimse/queryTagsOfEachLevel.js +++ b/dimse/queryTagsOfEachLevel.js @@ -27,6 +27,53 @@ const queryTagsOfEachLevel = { Tag.SOPInstanceUID, Tag.SOPClassUID, Tag.InstanceNumber + ], + "mwl": [ + Tag.AccessionNumber, + Tag.ReferringPhysicianName, + Tag.ReferencedStudySequence, + Tag.ReferencedPatientSequence, + Tag.PatientName, + Tag.PatientID, + Tag.IssuerOfPatientID, + Tag.IssuerOfPatientIDQualifiersSequence, + Tag.PatientBirthDate, + Tag.PatientSex, + Tag.OtherPatientIDsSequence, + Tag.PatientWeight, + Tag.MedicalAlerts, + Tag.Allergies, + Tag.PregnancyStatus, + Tag.StudyInstanceUID, + Tag.RequestingPhysician, + Tag.RequestedProcedureDescription, + Tag.RequestedProcedureCodeSequence, + Tag.AdmissionID, + Tag.SpecialNeeds, + Tag.CurrentPatientLocation, + Tag.PatientState, + Tag.ScheduledProcedureStepSequence, + Tag.RequestedProcedureID, + Tag.RequestedProcedurePriority, + Tag.PatientTransportArrangements, + Tag.ConfidentialityConstraintOnPatientDataDescription, + Tag.WorklistLabel + ], + "mwlSps": [ + Tag.Modality, + Tag.AnatomicalOrientationType, + Tag.RequestedContrastAgent, + Tag.ScheduledStationAETitle, + Tag.ScheduledProcedureStepStartDate, + Tag.ScheduledProcedureStepStartTime, + Tag.ScheduledPerformingPhysicianName, + Tag.ScheduledProcedureStepDescription, + Tag.ScheduledProtocolCodeSequence, + Tag.ScheduledProcedureStepID, + Tag.ScheduledProcedureStepStatus, + Tag.ScheduledStationName, + Tag.ScheduledProcedureStepLocation, + Tag.PreMedication ] }; diff --git a/models/mongodb/models/mwlitems.model.js b/models/mongodb/models/mwlitems.model.js index ca1de43c..e7bf051b 100644 --- a/models/mongodb/models/mwlitems.model.js +++ b/models/mongodb/models/mwlitems.model.js @@ -5,6 +5,14 @@ const { tagsNeedStore } = require("../../DICOM/dicom-tags-mapping"); const { getVRSchema } = require("../schema/dicomJsonAttribute"); const { IncludeFieldsFactory } = require("../service"); const { dictionary } = require("@models/DICOM/dicom-tags-dic"); +const { raccoonConfig } = require("@root/config-class"); + +let Common; +if (raccoonConfig.dicomDimseConfig.enableDimse) { + require("@models/DICOM/dcm4che/java-instance"); + Common = require("@java-wrapper/org/github/chinlinlee/dcm777/net/common/Common").Common; +} + let mwlItemSchema = new mongoose.Schema( {}, @@ -15,6 +23,12 @@ let mwlItemSchema = new mongoose.Schema( getters: true }, statics: { + getDimseResultCursor: async function (query, keys) { + return mongoose.model("mwlItems").find(query, keys).setOptions({ + strictQuery: false + }) + .cursor(); + }, /** * * @param {import("../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions @@ -71,6 +85,10 @@ let mwlItemSchema = new mongoose.Schema( delete obj._id; delete obj.id; return obj; + }, + getAttributes: async function () { + let jsonStr = JSON.stringify(this.toDicomJson()); + return await Common.getAttributesFromJsonString(jsonStr); } } }