diff --git a/LesionTracker/client/components/viewer/viewer.js b/LesionTracker/client/components/viewer/viewer.js index c73d12380..7533f9276 100644 --- a/LesionTracker/client/components/viewer/viewer.js +++ b/LesionTracker/client/components/viewer/viewer.js @@ -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); diff --git a/Packages/ohif-measurements/client/api/conformanceCriteria.js b/Packages/ohif-measurements/client/api/conformanceCriteria.js deleted file mode 100644 index 371c386cb..000000000 --- a/Packages/ohif-measurements/client/api/conformanceCriteria.js +++ /dev/null @@ -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; diff --git a/Packages/ohif-measurements/client/api/index.js b/Packages/ohif-measurements/client/api/index.js deleted file mode 100644 index a4711b398..000000000 --- a/Packages/ohif-measurements/client/api/index.js +++ /dev/null @@ -1 +0,0 @@ -import './conformanceCriteria'; diff --git a/Packages/ohif-measurements/client/conformance/ConformanceCriteria.js b/Packages/ohif-measurements/client/conformance/ConformanceCriteria.js new file mode 100644 index 000000000..c3285f94b --- /dev/null +++ b/Packages/ohif-measurements/client/conformance/ConformanceCriteria.js @@ -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; diff --git a/Packages/ohif-measurements/client/conformance/checkers/BaseChecker.js b/Packages/ohif-measurements/client/conformance/checkers/BaseChecker.js new file mode 100644 index 000000000..73ce84f9d --- /dev/null +++ b/Packages/ohif-measurements/client/conformance/checkers/BaseChecker.js @@ -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; + } + +} diff --git a/Packages/ohif-measurements/client/conformance/checkers/RecistChecker.js b/Packages/ohif-measurements/client/conformance/checkers/RecistChecker.js new file mode 100644 index 000000000..8f6f314f5 --- /dev/null +++ b/Packages/ohif-measurements/client/conformance/checkers/RecistChecker.js @@ -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; + } + +} diff --git a/Packages/ohif-measurements/client/conformance/criterias/BaseCriteria.js b/Packages/ohif-measurements/client/conformance/criterias/BaseCriteria.js new file mode 100644 index 000000000..773e3ae85 --- /dev/null +++ b/Packages/ohif-measurements/client/conformance/criterias/BaseCriteria.js @@ -0,0 +1,14 @@ +export class BaseCriteria { + + constructor() {} + + respond(response, measurements) { + const passed = !response; + return { + passed, + message: response, + measurements + }; + } + +} diff --git a/Packages/ohif-measurements/client/conformance/criterias/MaxTargetPerOrganCriteria.js b/Packages/ohif-measurements/client/conformance/criterias/MaxTargetPerOrganCriteria.js new file mode 100644 index 000000000..9e4353c18 --- /dev/null +++ b/Packages/ohif-measurements/client/conformance/criterias/MaxTargetPerOrganCriteria.js @@ -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); + } + +} diff --git a/Packages/ohif-measurements/client/conformance/criterias/MaxTargetsCriteria.js b/Packages/ohif-measurements/client/conformance/criterias/MaxTargetsCriteria.js new file mode 100644 index 000000000..b9268ff14 --- /dev/null +++ b/Packages/ohif-measurements/client/conformance/criterias/MaxTargetsCriteria.js @@ -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); + } + +} diff --git a/Packages/ohif-measurements/client/conformance/index.js b/Packages/ohif-measurements/client/conformance/index.js new file mode 100644 index 000000000..4f89dd19e --- /dev/null +++ b/Packages/ohif-measurements/client/conformance/index.js @@ -0,0 +1 @@ +import './ConformanceCriteria'; diff --git a/Packages/ohif-measurements/client/index.js b/Packages/ohif-measurements/client/index.js index ef45b52ad..976b7fc4a 100644 --- a/Packages/ohif-measurements/client/index.js +++ b/Packages/ohif-measurements/client/index.js @@ -1,4 +1,4 @@ -import './api'; +import './conformance'; import './lib'; import './helpers'; import './components';