Skip to content

Commit

Permalink
feat(WIP): query
Browse files Browse the repository at this point in the history
  • Loading branch information
Chinlinlee committed Aug 1, 2023
1 parent f28cd0b commit 8f14829
Show file tree
Hide file tree
Showing 6 changed files with 678 additions and 2 deletions.
49 changes: 49 additions & 0 deletions api-sql/dicom-web/controller/QIDO-RS/queryAllStudies.js
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 api-sql/dicom-web/controller/QIDO-RS/service/QIDO-RS.service.js
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 api-sql/dicom-web/controller/QIDO-RS/service/querybuilder.js
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;
Loading

0 comments on commit 8f14829

Please sign in to comment.