-
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.
- Loading branch information
1 parent
f28cd0b
commit 8f14829
Showing
6 changed files
with
678 additions
and
2 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,49 @@ | ||
const { | ||
SqlQidoRsService: QidoRsService | ||
} = require("./service/QIDO-RS.service"); | ||
const { ApiLogger } = require("@root/utils/logs/api-logger"); | ||
const { Controller } = require("@root/api/controller.class"); | ||
|
||
class QueryAllStudiesController extends Controller { | ||
constructor(req, res) { | ||
super(req, res); | ||
} | ||
|
||
async mainProcess() { | ||
let apiLogger = new ApiLogger(this.request, "QIDO-RS"); | ||
|
||
apiLogger.addTokenValue(); | ||
apiLogger.logger.info(`Query All Studies`); | ||
|
||
try { | ||
|
||
let qidoRsService = new QidoRsService(this.request, this.response, "study"); | ||
|
||
await qidoRsService.getAndResponseDicomJson(); | ||
|
||
} 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 QueryAllStudiesController(req, res); | ||
|
||
await controller.doPipeline(); | ||
}; | ||
|
146 changes: 146 additions & 0 deletions
146
api-sql/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,146 @@ | ||
const _ = require("lodash"); | ||
|
||
const { QidoRsService } = require("@root/api/dicom-web/controller/QIDO-RS/service/QIDO-RS.service"); | ||
const { DicomWebService } = require("@root/api/dicom-web/service/dicom-web.service"); | ||
const { StudyQueryBuilder } = require("./querybuilder"); | ||
const { dictionary } = require("@models/DICOM/dicom-tags-dic"); | ||
const { | ||
DicomWebServiceError, | ||
DicomWebStatusCodes | ||
} = require("@error/dicom-web-service"); | ||
const { StudyModel } = require("@models/sql/models/study.model"); | ||
|
||
|
||
class SqlQidoRsService extends QidoRsService { | ||
/** | ||
* | ||
* @param {import('express').Request} req | ||
* @param {import('express').Response} res | ||
* @param {"study" | "series" | "instance"} level | ||
*/ | ||
constructor(req, res, level = "instance") { | ||
super(req, res, level); | ||
} | ||
|
||
async getAndResponseDicomJson() { | ||
try { | ||
|
||
let dicomWebService = new DicomWebService(this.request, this.response); | ||
|
||
let queryOptions = { | ||
query: this.query, | ||
skip: this.skip_, | ||
limit: this.limit_, | ||
includeFields: this.includeFields_, | ||
retrieveBaseUrl: `${dicomWebService.getBasicURL()}/studies`, | ||
requestParams: this.request.params | ||
}; | ||
|
||
let qidoDicomJsonFactory = new QidoDicomJsonFactory(queryOptions, this.level); | ||
|
||
let dicomJson = await qidoDicomJsonFactory.getDicomJson(); | ||
|
||
let dicomJsonLength = _.get(dicomJson, "length", 0); | ||
if (dicomJsonLength > 0) { | ||
this.response.writeHead(200, { | ||
"Content-Type": "application/dicom+json" | ||
}); | ||
this.response.end(JSON.stringify(dicomJson)); | ||
} else { | ||
this.response.writeHead(204); | ||
this.response.end(); | ||
} | ||
|
||
} catch (e) { | ||
throw e; | ||
} | ||
} | ||
|
||
/** | ||
* @private | ||
*/ | ||
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); | ||
} | ||
} | ||
|
||
class QidoDicomJsonFactory { | ||
|
||
/** | ||
* | ||
* @param {import("../../../../../utils/typeDef/dicom").DicomJsonMongoQueryOptions} queryOptions | ||
* @param {string} level | ||
*/ | ||
constructor(queryOptions, level = "instance") { | ||
this.level = level; | ||
|
||
this.getDicomJsonByLevel = { | ||
"patient": async () => { | ||
// return await getPatientDicomJson(queryOptions); | ||
}, | ||
"study": async () => { | ||
// return await getStudyDicomJson(queryOptions); | ||
let queryBuilder = new StudyQueryBuilder(queryOptions); | ||
queryBuilder.build(); | ||
let studies = await StudyModel.findAll({ | ||
where: queryBuilder.query | ||
}); | ||
console.log(studies); | ||
}, | ||
"series": async () => { | ||
// return await getSeriesDicomJson(queryOptions); | ||
}, | ||
"instance": async () => { | ||
// return await getInstanceDicomJson(queryOptions); | ||
} | ||
}; | ||
} | ||
|
||
async getDicomJson() { | ||
return await this.getDicomJsonByLevel[this.level](); | ||
} | ||
} | ||
|
||
|
||
/** | ||
* 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.keyword[keyNameSplit[x]]); | ||
} else if (dictionary.tag[keyNameSplit[x]]) { | ||
newKeyNames.push(keyNameSplit); | ||
} | ||
} | ||
if (newKeyNames.length === 0) { | ||
throw new DicomWebServiceError( | ||
DicomWebStatusCodes.InvalidArgumentValue, | ||
`Invalid request query: ${keyNameSplit}`, | ||
400 | ||
); | ||
} else if (newKeyNames.length >= 2) { | ||
newKeyNames.push("Value"); | ||
} | ||
let retKeyName = newKeyNames.join("."); | ||
newQS[retKeyName] = iParam[keyName]; | ||
} | ||
return newQS; | ||
} | ||
|
||
module.exports.SqlQidoRsService = SqlQidoRsService; |
170 changes: 170 additions & 0 deletions
170
api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.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,170 @@ | ||
const _ = require("lodash"); | ||
const moment = require("moment"); | ||
const { dictionary } = require("@models/DICOM/dicom-tags-dic"); | ||
const { Op } = require("sequelize"); | ||
|
||
class BaseQueryBuilder { | ||
constructor(queryOptions) { | ||
this.queryOptions = queryOptions; | ||
this.personQuery = []; | ||
} | ||
|
||
comma(key, value) { | ||
let $or = []; | ||
let valueCommaSplit = value.split(","); | ||
for (let i = 0; i < valueCommaSplit.length; i++) { | ||
let obj = {}; | ||
obj[key] = valueCommaSplit[i]; | ||
$or.push(obj); | ||
} | ||
return $or; | ||
} | ||
|
||
getStringQuery(tag, value) { | ||
return { | ||
[`x${tag}`]: value | ||
}; | ||
} | ||
|
||
getStringArrayQuery(tag, value) { | ||
//TODO | ||
} | ||
|
||
getNumberQuery(tag, value) { | ||
return { | ||
[`x${tag}`]: Number(value) | ||
}; | ||
} | ||
|
||
getNumberArrayQuery(tag, value) { | ||
//TODO | ||
} | ||
|
||
getPersonNameQuery(value) { | ||
return { | ||
[Op.or]: { | ||
alphabetic: value, | ||
ideographic: value, | ||
phonetic: value | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* | ||
* @param {string} tag | ||
* @param {string} value | ||
*/ | ||
getDateQuery(tag, value) { | ||
let dashIndex = value.indexOf("-"); | ||
if (dashIndex === 0) { // -YYYYMMDD | ||
return { | ||
[`x${tag}`]: { | ||
[Op.lte]: this.dateStringToSqlDateOnly(value) | ||
} | ||
}; | ||
} else if (dashIndex === value.length - 1) { // YYYYMMDD- | ||
return { | ||
[`x${tag}`]: { | ||
[Op.gte]: this.dateStringToSqlDateOnly(value) | ||
} | ||
}; | ||
} else if (dashIndex > 0) { // YYYYMMDD-YYYYMMDD | ||
return { | ||
[`x${tag}`]: { | ||
[Op.and]: [ | ||
{ [Op.gte]: this.dateStringToSqlDateOnly(value.substring(0, dashIndex)) }, | ||
{ [Op.lte]: this.dateStringToSqlDateOnly(value.substring(dashIndex + 1)) } | ||
] | ||
} | ||
}; | ||
} else { // YYYYMMDD | ||
return { | ||
[`x${tag}`]: this.dateStringToSqlDateOnly(value) | ||
}; | ||
} | ||
} | ||
|
||
dateStringToSqlDateOnly(value) { | ||
return moment(value, "YYYYMMDD").format("YYYY-MM-DD"); | ||
} | ||
|
||
|
||
/** | ||
* | ||
* @param {string} value | ||
* @returns | ||
*/ | ||
getWildCardQuery(value) { | ||
let wildCardIndex = value.indexOf("*"); | ||
let questionIndex = value.indexOf("?"); | ||
|
||
if (wildCardIndex >= 0 || questionIndex >= 0) { | ||
value = value.replace(/\*/gm, "%"); | ||
value = value.replace(/\?/gm, "_"); | ||
} | ||
|
||
return value; | ||
} | ||
|
||
/** | ||
* | ||
* @param {*} q | ||
* @see {@link https://stackoverflow.com/questions/60598225/how-to-merge-javascript-object-containing-symbols "How to merge javascript object containing symbols?"} | ||
*/ | ||
mergeQuery(q) { | ||
_.mergeWith(this.query, q, (a ,b) => { | ||
if (!_.isObject(b)) return b; | ||
|
||
return Array.isArray(a) ? [...a, ...b] : { ...a, ...b }; | ||
}); | ||
} | ||
} | ||
|
||
class StudyQueryBuilder extends BaseQueryBuilder { | ||
constructor(queryOptions) { | ||
super(queryOptions); | ||
this.query = {}; | ||
} | ||
|
||
build() { | ||
for (let key in this.queryOptions.query) { | ||
let commaValue = this.comma(key, this.queryOptions.query[key]); | ||
|
||
for (let i = 0; i < commaValue.length; i++) { | ||
let value = this.getWildCardQuery(commaValue[i][key]); | ||
try { | ||
this[`get${dictionary.tag[key]}`](value); | ||
} catch (e) { | ||
if (e.message.includes("not a function")) break; | ||
} | ||
} | ||
} | ||
console.log(this.query); | ||
} | ||
|
||
getStudyInstanceUID(value) { | ||
let q = this.getStringQuery(dictionary.keyword.StudyInstanceUID, value); | ||
_.merge(this.query, q); | ||
} | ||
|
||
getPatientName(value) { | ||
let q = this.getPersonNameQuery(value); | ||
this.personQuery.push(q); | ||
} | ||
|
||
getPatientID(value) { | ||
let q = this.getStringQuery(dictionary.keyword.PatientID, value); | ||
_.merge(this.query, q); | ||
} | ||
|
||
getStudyDate(value) { | ||
let q = this.getDateQuery(dictionary.keyword.StudyDate, value); | ||
this.mergeQuery(q); | ||
} | ||
|
||
} | ||
|
||
|
||
module.exports.BaseQueryBuilder = BaseQueryBuilder; | ||
module.exports.StudyQueryBuilder = StudyQueryBuilder; |
Oops, something went wrong.