LT-92: Creating checkers for conformance criteria validation
This commit is contained in:
parent
93dc9e94ce
commit
983dd916ac
@ -103,7 +103,7 @@ Template.viewer.onCreated(() => {
|
|||||||
instance.data.measurementApi = new OHIF.measurements.MeasurementApi(instance.data.timepointApi);
|
instance.data.measurementApi = new OHIF.measurements.MeasurementApi(instance.data.timepointApi);
|
||||||
console.warn('>>>>REMOVE THE LINES BELOW');
|
console.warn('>>>>REMOVE THE LINES BELOW');
|
||||||
window.measurementApi = instance.data.measurementApi;
|
window.measurementApi = instance.data.measurementApi;
|
||||||
window.conformanceCriteriaApi = new OHIF.measurements.ConformanceCriteriaApi(window.measurementApi);
|
window.conformanceCriteria = new OHIF.measurements.ConformanceCriteria(window.measurementApi);
|
||||||
const measurementsPromise = instance.data.measurementApi.retrieveMeasurements(patientId, timepointIds);
|
const measurementsPromise = instance.data.measurementApi.retrieveMeasurements(patientId, timepointIds);
|
||||||
measurementsPromise.then(() => {
|
measurementsPromise.then(() => {
|
||||||
Session.set('MeasurementsReady', true);
|
Session.set('MeasurementsReady', true);
|
||||||
|
|||||||
@ -1,64 +0,0 @@
|
|||||||
import { OHIF } from 'meteor/ohif:core';
|
|
||||||
import { _ } from 'meteor/underscore';
|
|
||||||
|
|
||||||
class ConformanceCriteriaApi {
|
|
||||||
|
|
||||||
constructor(measurementApi) {
|
|
||||||
if (measurementApi) {
|
|
||||||
this.measurementApi = measurementApi;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.warnings = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
validate() {
|
|
||||||
this.validateRecist();
|
|
||||||
}
|
|
||||||
|
|
||||||
validateRecist() {
|
|
||||||
const measurements = this.getMeasurements();
|
|
||||||
|
|
||||||
measurements.targets.forEach(measurement => {
|
|
||||||
const image = this.getImageById(measurement.studyInstanceUid, measurement.imageId);
|
|
||||||
console.warn('>>>>modality', image, image.modality);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getMeasurements() {
|
|
||||||
const targets = this.measurementApi.fetch('targets');
|
|
||||||
const nonTargets = this.measurementApi.fetch('nonTargets');
|
|
||||||
return {
|
|
||||||
targets,
|
|
||||||
nonTargets
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getImageById(studyInstanceUid, imageId) {
|
|
||||||
const study = ViewerStudies.findOne({ studyInstanceUid });
|
|
||||||
|
|
||||||
// Stop here if the study was not found
|
|
||||||
if (!study) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let foundImage;
|
|
||||||
_.each(study.displaySets, displaySet => {
|
|
||||||
if (foundImage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_.each(displaySet.images, image => {
|
|
||||||
if (foundImage) {
|
|
||||||
return;
|
|
||||||
} else if (getImageId(image) === imageId) {
|
|
||||||
foundImage = image;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return foundImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
OHIF.measurements.ConformanceCriteriaApi = ConformanceCriteriaApi;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
import './conformanceCriteria';
|
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
|
import { _ } from 'meteor/underscore';
|
||||||
|
import { RecistChecker } from './checkers/RecistChecker';
|
||||||
|
|
||||||
|
class ConformanceCriteria {
|
||||||
|
|
||||||
|
constructor(measurementApi) {
|
||||||
|
if (measurementApi) {
|
||||||
|
this.measurementApi = measurementApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.warnings = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
const data = this.getData();
|
||||||
|
this.validateRecist(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRecist(data) {
|
||||||
|
const recistChecker = new RecistChecker();
|
||||||
|
console.warn('>>>>check', recistChecker.check(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build the data that will be used to do the conformance criteria checks
|
||||||
|
*/
|
||||||
|
getData() {
|
||||||
|
const data = {
|
||||||
|
targets: [],
|
||||||
|
nonTargets: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const fillData = measurementType => {
|
||||||
|
const measurements = this.measurementApi.fetch(measurementType);
|
||||||
|
measurements.forEach(measurement => {
|
||||||
|
const { studyInstanceUid, imageId } = measurement;
|
||||||
|
const metadata = this.getImageMetadata(studyInstanceUid, imageId);
|
||||||
|
data[measurementType].push({
|
||||||
|
measurement,
|
||||||
|
metadata
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
fillData('targets');
|
||||||
|
fillData('nonTargets');
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
getImageMetadata(studyInstanceUid, imageId) {
|
||||||
|
const study = ViewerStudies.findOne({ studyInstanceUid });
|
||||||
|
|
||||||
|
// Stop here if the study was not found
|
||||||
|
if (!study) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let foundImage;
|
||||||
|
_.each(study.displaySets, displaySet => {
|
||||||
|
if (foundImage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_.each(displaySet.images, image => {
|
||||||
|
if (foundImage) {
|
||||||
|
return;
|
||||||
|
} else if (getImageId(image) === imageId) {
|
||||||
|
foundImage = image;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return foundImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
OHIF.measurements.ConformanceCriteria = ConformanceCriteria;
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
export class BaseChecker {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.criterias = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
check(data) {
|
||||||
|
const nonconformity = [];
|
||||||
|
this.criterias.forEach(criteria => {
|
||||||
|
const criteriaResult = criteria.check(data);
|
||||||
|
if (!criteriaResult.passed) {
|
||||||
|
nonconformity.push(criteriaResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return nonconformity;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { BaseChecker } from './BaseChecker';
|
||||||
|
import { MaxTargetPerOrganCriteria } from '../criterias/MaxTargetPerOrganCriteria';
|
||||||
|
import { MaxTargetsCriteria } from '../criterias/MaxTargetsCriteria';
|
||||||
|
|
||||||
|
export class RecistChecker extends BaseChecker {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.criterias.push(new MaxTargetsCriteria(5));
|
||||||
|
this.criterias.push(new MaxTargetPerOrganCriteria(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
check(data) {
|
||||||
|
const nonconformity = [];
|
||||||
|
this.criterias.forEach(criteria => {
|
||||||
|
const criteriaResult = criteria.check(data);
|
||||||
|
if (!criteriaResult.passed) {
|
||||||
|
nonconformity.push(criteriaResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return nonconformity;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
export class BaseCriteria {
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
respond(response, measurements) {
|
||||||
|
const passed = !response;
|
||||||
|
return {
|
||||||
|
passed,
|
||||||
|
message: response,
|
||||||
|
measurements
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import { BaseCriteria } from './BaseCriteria';
|
||||||
|
import { _ } from 'meteor/underscore';
|
||||||
|
|
||||||
|
export class MaxTargetPerOrganCriteria extends BaseCriteria {
|
||||||
|
|
||||||
|
constructor(targetsLimit) {
|
||||||
|
super();
|
||||||
|
this.targetsLimit = targetsLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
check(data) {
|
||||||
|
const targetsPerOrgan = {};
|
||||||
|
let message;
|
||||||
|
let measurements = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < data.targets.length; i++) {
|
||||||
|
const measurement = data.targets[i].measurement;
|
||||||
|
const { location, measurementNumber } = measurement;
|
||||||
|
if (!targetsPerOrgan[location]) {
|
||||||
|
targetsPerOrgan[location] = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
targetsPerOrgan[location].add(measurementNumber);
|
||||||
|
if (targetsPerOrgan[location].size > this.targetsLimit) {
|
||||||
|
measurements.push(measurement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (measurements.length) {
|
||||||
|
message = `Each organ should not have more than ${this.targetsLimit} targets.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.respond(message, measurements);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { BaseCriteria } from './BaseCriteria';
|
||||||
|
import { _ } from 'meteor/underscore';
|
||||||
|
|
||||||
|
export class MaxTargetsCriteria extends BaseCriteria {
|
||||||
|
|
||||||
|
constructor(targetsLimit) {
|
||||||
|
super();
|
||||||
|
this.targetsLimit = targetsLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
check(data) {
|
||||||
|
const measurementNumbers = _.uniq(_.map(data.targets, target => {
|
||||||
|
return target.measurement.measurementNumber;
|
||||||
|
}));
|
||||||
|
|
||||||
|
let message;
|
||||||
|
if (measurementNumbers.length > this.targetsLimit) {
|
||||||
|
message = `The study should not have more than ${this.targetsLimit} targets.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.respond(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1
Packages/ohif-measurements/client/conformance/index.js
Normal file
1
Packages/ohif-measurements/client/conformance/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import './ConformanceCriteria';
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import './api';
|
import './conformance';
|
||||||
import './lib';
|
import './lib';
|
||||||
import './helpers';
|
import './helpers';
|
||||||
import './components';
|
import './components';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user