LT-92: Creating checkers for conformance criteria validation

This commit is contained in:
Bruno Alves de Faria 2017-01-12 15:43:44 -02:00
parent 93dc9e94ce
commit 983dd916ac
11 changed files with 200 additions and 67 deletions

View File

@ -103,7 +103,7 @@ Template.viewer.onCreated(() => {
instance.data.measurementApi = new OHIF.measurements.MeasurementApi(instance.data.timepointApi);
console.warn('>>>>REMOVE THE LINES BELOW');
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);
measurementsPromise.then(() => {
Session.set('MeasurementsReady', true);

View File

@ -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;

View File

@ -1 +0,0 @@
import './conformanceCriteria';

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,14 @@
export class BaseCriteria {
constructor() {}
respond(response, measurements) {
const passed = !response;
return {
passed,
message: response,
measurements
};
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1 @@
import './ConformanceCriteria';

View File

@ -1,4 +1,4 @@
import './api';
import './conformance';
import './lib';
import './helpers';
import './components';