-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- `QIDO-RS.service.js` convert the request query into MongoDB query - Fix the response content-type of `STOW-RS` > application/dicom+json - Add `studies.yaml` for API documentation of `QIDO-RS` - Add date handler for MongoDB query
- Loading branch information
1 parent
2a5f1e5
commit 99a6283
Showing
7 changed files
with
490 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
const _ = require('lodash'); | ||
const mongoose = require('mongoose'); | ||
const moment = require('moment'); | ||
const { | ||
convertAllQueryToDICOMTag, | ||
convertRequestQueryToMongoQuery, | ||
getStudyLevelFields | ||
} = require('./service/QIDO-RS.service'); | ||
const { | ||
logger | ||
} = require('../../../../utils/log'); | ||
|
||
/** | ||
* @openapi | ||
* /dicom-web/studies: | ||
* get: | ||
* description: Query for studies | ||
* parameters: | ||
* - $ref: "#/components/parameters/StudyDate" | ||
* - $ref: "#/components/parameters/StudyTime" | ||
* - $ref: "#/components/parameters/AccessionNumber" | ||
* - $ref: "#/components/parameters/ModalitiesInStudy" | ||
* - $ref: "#/components/parameters/ReferringPhysicianName" | ||
* - $ref: "#/components/parameters/PatientName" | ||
* - $ref: "#/components/parameters/PatientID" | ||
* - $ref: "#/components/parameters/StudyID" | ||
* responses: | ||
* 200: | ||
* description: Query successfully | ||
*/ | ||
|
||
/** | ||
* | ||
* @param {import('http').IncomingMessage} req | ||
* @param {import('http').ServerResponse} res | ||
*/ | ||
module.exports = async function (req, res) { | ||
try { | ||
let limit = req.query.limit || 100 ; | ||
let skip = req.query.offset || 0; | ||
delete req.query["limit"]; | ||
delete req.query["offset"]; | ||
let query = _.cloneDeep(req.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]; | ||
} | ||
|
||
let dicomTagQuery = convertAllQueryToDICOMTag(query); | ||
let studiesJson = await getStudyDicomJson(dicomTagQuery, limit, skip); | ||
res.writeHead(200, { | ||
"Content-Type": "application/dicom+json" | ||
}); | ||
res.end(JSON.stringify(studiesJson.data)); | ||
} catch(e) { | ||
let errorStr = JSON.stringify(e, Object.getOwnPropertyNames(e)); | ||
logger.error(`[QIDO-RS] [Error: ${errorStr}]`); | ||
} | ||
} | ||
|
||
async function getStudyDicomJson(iQuery , limit , skip) { | ||
logger.info(`[QIDO-RS] [Query Study Level]`); | ||
let result = { | ||
data : '' , | ||
status: false | ||
} | ||
try { | ||
iQuery = await convertRequestQueryToMongoQuery(iQuery); | ||
// iQuery = iQuery.$match; | ||
logger.info(`[QIDO-RS] [Query for MongoDB: ${JSON.stringify(iQuery)}]`); | ||
let studyFields = getStudyLevelFields(); | ||
let aggregateQuery = [ | ||
{ | ||
$sort: { | ||
studyUID: 1 | ||
} | ||
}, | ||
iQuery, | ||
{ | ||
$limit: limit + skip | ||
}, | ||
{ | ||
$skip: skip | ||
}, | ||
{ | ||
$group: { | ||
_id: "$0020000D", | ||
modalitiesInStudy: { | ||
$addToSet: "$00080060.Value" | ||
}, | ||
dicomJson: { | ||
$addToSet: "$$ROOT" | ||
} | ||
} | ||
}, | ||
{ | ||
$project: { | ||
...studyFields, | ||
"dicomJson.00080061": { | ||
"vr": "CS", | ||
"Value": { | ||
$reduce: { | ||
input: "$modalitiesInStudy", | ||
initialValue: [], | ||
in: { | ||
$concatArrays : ["$$value", "$$this"] | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
{ | ||
$project: { | ||
dicomJsonObj: { | ||
$mergeObjects: "$$ROOT.dicomJson" | ||
} | ||
} | ||
}, | ||
{ | ||
$replaceWith: "$dicomJsonObj" | ||
}, | ||
{ | ||
$sort: { | ||
"0020000D": 1 | ||
} | ||
} | ||
]; | ||
let docs = await mongoose.model("dicom").aggregate(aggregateQuery).exec(); | ||
result.data = docs.map(v => { | ||
let studyDate = _.get(v , "00080020.Value"); | ||
if (studyDate) { | ||
for (let j in studyDate) { | ||
let studyDateYYYYMMDD = moment(studyDate[j]).format("YYYYMMDD").toString(); | ||
studyDate[j] = studyDateYYYYMMDD; | ||
} | ||
_.set(v , "00080020.Value" , studyDate); | ||
} | ||
return v; | ||
}); | ||
result.status = true; | ||
return result; | ||
} catch (e) { | ||
console.error("get Study DICOM error" , e); | ||
result.data = e; | ||
result.status = false; | ||
return result; | ||
} | ||
} |
162 changes: 162 additions & 0 deletions
162
api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
const _ = require('lodash'); | ||
const { | ||
mongoDateQuery | ||
} = require('../../../../../models/mongodb/service'); | ||
const { | ||
dictionary | ||
} = require('../../../../../models/DICOM/dicom-tags-dic'); | ||
const { | ||
tagsOfRequiredMatching | ||
} = require('../../../../../models/DICOM/dicom-tags-mapping'); | ||
|
||
/** | ||
* Convert All of name(tags, keyword) of queries to tags number | ||
* @param {Object} iParam The request query. | ||
* @returns | ||
*/ | ||
function convertAllQueryToDICOMTag(iParam) { | ||
let keys = Object.keys(iParam); | ||
let newQS = {}; | ||
for (let i = 0; i < keys.length; i++) { | ||
let keyName = keys[i]; | ||
let keyNameSplit = keyName.split('.'); | ||
let newKeyNames = []; | ||
for (let x = 0; x < keyNameSplit.length; x++) { | ||
if (dictionary.keyword[keyNameSplit[x]]) { | ||
newKeyNames.push(dictionary.dicom[keyNameSplit[x]]); | ||
} else if (dictionary.tag[keyNameSplit[x]]) { | ||
newKeyNames.push(keyNameSplit); | ||
} else { | ||
//newKeyNames.push(keyNameSplit); | ||
} | ||
} | ||
// if (newKeyNames.length == 1) { | ||
// continue; | ||
// } | ||
// let studyTags = Object.keys(QIDORetAtt.study); | ||
// let seriesTags = Object.keys(QIDORetAtt.series); | ||
// let instanceTags = Object.keys(QIDORetAtt.instance); | ||
// for (let seriesTag of seriesTags) { | ||
// if (newKeyNames.find(v => v == seriesTag) && !studyTags.includes(seriesTag)) { | ||
// newKeyNames = [ "series", ...newKeyNames] | ||
// } | ||
// } | ||
// for (let instanceTag of instanceTags) { | ||
// if (newKeyNames.find(v => v == instanceTag) && !studyTags.includes(instanceTag) && !seriesTags.includes(instanceTag)) { | ||
// newKeyNames = [ "series", "instance", ...newKeyNames] | ||
// } | ||
// } | ||
newKeyNames.push('Value'); | ||
let retKeyName = newKeyNames.join('.'); | ||
newQS[retKeyName] = iParam[keyName]; | ||
} | ||
return (newQS); | ||
} | ||
//#endregion | ||
|
||
|
||
function checkIsOr(value, keyName) { | ||
if (_.isObject(value) && _.get(value[keyName], "$or")) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* convert value that contains comma to $or query of MongoDB | ||
* @param {string} iKey | ||
* @param {string} iValue | ||
*/ | ||
function commaValue(iKey, iValue) { | ||
let $or = []; | ||
iValue = iValue.split(','); | ||
for (let i = 0; i < iValue.length; i++) { | ||
let obj = {}; | ||
obj[iKey] = iValue[i]; | ||
$or.push(obj); | ||
} | ||
return $or; | ||
} | ||
|
||
async function wildCardFirst(iValue) { | ||
return new Promise((resolve) => { | ||
iValue = iValue.replace(/\*/gi, '.*'); | ||
return resolve(new RegExp(iValue, 'gi')); | ||
}); | ||
} | ||
async function wildCard(iValue) { | ||
return new Promise((resolve) => { | ||
iValue = '^' + iValue; | ||
iValue = iValue.replace(/\*/gi, '.*'); | ||
return resolve(new RegExp(iValue, 'gi')); | ||
}); | ||
} | ||
|
||
/** | ||
* convert all request query object to to $or query and push to $and query | ||
* @param {Object} iQuery | ||
* @returns | ||
*/ | ||
async function convertRequestQueryToMongoQuery(iQuery) { | ||
let queryKey = Object.keys(iQuery); | ||
let mongoQs = { | ||
"$match": { | ||
"$and": [] | ||
} | ||
}; | ||
for (let i = 0; i < queryKey.length; i++) { | ||
let mongoOrs = { | ||
"$or": [] | ||
} | ||
let nowKey = queryKey[i]; | ||
let value = commaValue(nowKey, iQuery[nowKey]); | ||
for (let x = 0; x < value.length; x++) { | ||
let nowValue = value[x][nowKey]; | ||
let wildCardFunc = {}; | ||
wildCardFunc[nowValue.indexOf('*')] = wildCard; | ||
wildCardFunc['0'] = wildCardFirst; | ||
wildCardFunc['-1'] = (value) => { | ||
return value; | ||
} | ||
value[x][nowKey] = await wildCardFunc[nowValue.indexOf('*')](nowValue); | ||
|
||
try { | ||
let keySplit = nowKey.split("."); | ||
let tag = keySplit[ keySplit.length - 2 ]; | ||
let vrOfTag = dictionary.tagVR[tag]; | ||
await vrQueryLookup[vrOfTag.vr](value[x], nowKey); | ||
} catch(e) { | ||
if (!e instanceof TypeError) | ||
console.error(e); | ||
} | ||
|
||
if (checkIsOr(value[x], nowKey)) { | ||
mongoOrs.$or.push(...(_.get(value[x][nowKey], "$or"))); | ||
} else { | ||
mongoOrs.$or.push(value[x]); | ||
} | ||
} | ||
mongoQs.$match.$and.push(mongoOrs); | ||
} | ||
return (mongoQs.$match.$and.length == 0 ? { | ||
$match: {} | ||
} : mongoQs); | ||
} | ||
|
||
function getStudyLevelFields() { | ||
let fields = {}; | ||
for (let tag in tagsOfRequiredMatching.Study) { | ||
fields[`dicomJson.${tag}`] = 1; | ||
} | ||
return fields; | ||
} | ||
|
||
const vrQueryLookup = { | ||
"DA": async(value, tag) => { | ||
let q = await mongoDateQuery(value, tag, false); | ||
} | ||
} | ||
|
||
module.exports.convertAllQueryToDICOMTag = convertAllQueryToDICOMTag; | ||
module.exports.convertRequestQueryToMongoQuery = convertRequestQueryToMongoQuery; | ||
module.exports.getStudyLevelFields = getStudyLevelFields; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,15 @@ | ||
const { app } = require('../../app'); | ||
|
||
//#region QIDO-RS | ||
|
||
app.get('/studies' , require('./controller/QIDO-RS/queryAllStudies')); | ||
|
||
//#endregion | ||
|
||
//#region STOW-RS | ||
|
||
app.post("/studies", require("./controller/STOW-RS/storeInstance")); | ||
|
||
//#endregion | ||
|
||
module.exports = app; |
Oops, something went wrong.