LT-92: Restructuring the criteria in JSON format
This commit is contained in:
parent
b8c397a9b4
commit
ada2d1e284
@ -1,6 +1,7 @@
|
|||||||
import { OHIF } from 'meteor/ohif:core';
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
import { _ } from 'meteor/underscore';
|
import { _ } from 'meteor/underscore';
|
||||||
import { RecistChecker } from './checkers/RecistChecker';
|
import { CriteriaEvaluator } from './CriteriaEvaluator';
|
||||||
|
import * as recistBaselineEvaluation from './evaluations/recistBaseline.json';
|
||||||
|
|
||||||
class ConformanceCriteria {
|
class ConformanceCriteria {
|
||||||
|
|
||||||
@ -17,8 +18,8 @@ class ConformanceCriteria {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validateRecist(data) {
|
validateRecist(data) {
|
||||||
const recistChecker = new RecistChecker();
|
const recistBaselineEvaluator = new CriteriaEvaluator(recistBaselineEvaluation);
|
||||||
console.warn('>>>>check', recistChecker.check(data));
|
console.warn('>>>>check', recistBaselineEvaluator.evaluate(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
import * as Criteria from './criteria';
|
||||||
|
import { _ } from 'meteor/underscore';
|
||||||
|
|
||||||
|
export class CriteriaEvaluator {
|
||||||
|
|
||||||
|
constructor(criteriaObject) {
|
||||||
|
this.criteria = [];
|
||||||
|
|
||||||
|
_.each(criteriaObject, (optionsObject, criterionkey) => {
|
||||||
|
const Criterion = Criteria[`${criterionkey}Criterion`];
|
||||||
|
if (optionsObject instanceof Array) {
|
||||||
|
_.each(optionsObject, options => {
|
||||||
|
const criterion = new Criterion(options);
|
||||||
|
this.criteria.push(criterion);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.criteria.push(new Criterion(optionsObject));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate(data) {
|
||||||
|
const nonconformity = [];
|
||||||
|
this.criteria.forEach(criterion => {
|
||||||
|
const criterionResult = criterion.evaluate(data);
|
||||||
|
if (!criterionResult.passed) {
|
||||||
|
nonconformity.push(criterionResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return nonconformity;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,18 +0,0 @@
|
|||||||
export class BaseChecker {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.criteria = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
check(data) {
|
|
||||||
const nonconformity = [];
|
|
||||||
this.criteria.forEach(criterion => {
|
|
||||||
const criterionResult = criterion.evaluate(data);
|
|
||||||
if (!criterionResult.passed) {
|
|
||||||
nonconformity.push(criterionResult);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return nonconformity;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
import { BaseChecker } from './BaseChecker';
|
|
||||||
import { MaxTargetPerOrganCriterion } from '../criteria/MaxTargetPerOrgan';
|
|
||||||
import { MaxTargetsCriterion } from '../criteria/MaxTargets';
|
|
||||||
import { MeasurementsLengthCriterion } from '../criteria/MeasurementsLength';
|
|
||||||
import { ModalityCriterion } from '../criteria/Modality';
|
|
||||||
import { NonTargetResponseCriterion } from '../criteria/NonTargetResponse';
|
|
||||||
import { TargetTypeCriterion } from '../criteria/TargetType';
|
|
||||||
|
|
||||||
export class RecistChecker extends BaseChecker {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
const addCriterion = criterion => this.criteria.push(criterion);
|
|
||||||
|
|
||||||
addCriterion(new MaxTargetsCriterion(5));
|
|
||||||
addCriterion(new MaxTargetPerOrganCriterion(2));
|
|
||||||
addCriterion(new ModalityCriterion(this.getModalityOptions()));
|
|
||||||
addCriterion(new NonTargetResponseCriterion());
|
|
||||||
addCriterion(new TargetTypeCriterion());
|
|
||||||
addCriterion(new MeasurementsLengthCriterion(this.getExtranodalLengthOptions()));
|
|
||||||
addCriterion(new MeasurementsLengthCriterion(this.getExtranodalXrayLengthOptions()));
|
|
||||||
addCriterion(new MeasurementsLengthCriterion(this.getNodalLengthOptions()));
|
|
||||||
}
|
|
||||||
|
|
||||||
getModalityOptions() {
|
|
||||||
return {
|
|
||||||
method: 'restrict',
|
|
||||||
modalities: ['US']
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getExtranodalLengthOptions() {
|
|
||||||
return {
|
|
||||||
longAxis: 10,
|
|
||||||
longAxisSliceThicknessMultiplier: 2,
|
|
||||||
modalityIn: ['CT', 'MR'],
|
|
||||||
locationNotIn: ['Lymph Node'],
|
|
||||||
message: 'Extranodal lesions must be >= 10mm long axis AND ' +
|
|
||||||
'>= double the acquisition slice thickness by CT and MR'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getExtranodalXrayLengthOptions() {
|
|
||||||
return {
|
|
||||||
shortAxis: 20,
|
|
||||||
longAxis: 20,
|
|
||||||
modalityIn: ['PX', 'XA'],
|
|
||||||
locationNotIn: ['Lymph Node'],
|
|
||||||
message: 'Extranodal lesions must be >= 20mm on chest x-ray ' +
|
|
||||||
'(although x-rays rarely used for clinical trial assessment)'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getNodalLengthOptions() {
|
|
||||||
return {
|
|
||||||
shortAxis: 15,
|
|
||||||
shortAxisSliceThicknessMultiplier: 2,
|
|
||||||
modalityIn: ['CT', 'MR'],
|
|
||||||
locationIn: ['Lymph Node'],
|
|
||||||
message: 'Nodal lesions must be >= 15mm short axis AND ' +
|
|
||||||
'>= double the acquisition slice thickness by CT and MR'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,6 +1,8 @@
|
|||||||
export class BaseCriterion {
|
export class BaseCriterion {
|
||||||
|
|
||||||
constructor() {}
|
constructor(options) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
generateResponse(message, measurements) {
|
generateResponse(message, measurements) {
|
||||||
const passed = !message;
|
const passed = !message;
|
||||||
|
|||||||
@ -2,9 +2,8 @@ import { BaseCriterion } from './BaseCriterion';
|
|||||||
|
|
||||||
export class MaxTargetPerOrganCriterion extends BaseCriterion {
|
export class MaxTargetPerOrganCriterion extends BaseCriterion {
|
||||||
|
|
||||||
constructor(targetsLimit) {
|
constructor(options) {
|
||||||
super();
|
super(options);
|
||||||
this.targetsLimit = targetsLimit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate(data) {
|
evaluate(data) {
|
||||||
@ -20,13 +19,13 @@ export class MaxTargetPerOrganCriterion extends BaseCriterion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
targetsPerOrgan[location].add(measurementNumber);
|
targetsPerOrgan[location].add(measurementNumber);
|
||||||
if (targetsPerOrgan[location].size > this.targetsLimit) {
|
if (targetsPerOrgan[location].size > this.options.limit) {
|
||||||
measurements.push(measurement);
|
measurements.push(measurement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (measurements.length) {
|
if (measurements.length) {
|
||||||
message = `Each organ should not have more than ${this.targetsLimit} targets.`;
|
message = `Each organ should not have more than ${this.options.limit} targets.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.generateResponse(message, measurements);
|
return this.generateResponse(message, measurements);
|
||||||
|
|||||||
@ -3,9 +3,8 @@ import { _ } from 'meteor/underscore';
|
|||||||
|
|
||||||
export class MaxTargetsCriterion extends BaseCriterion {
|
export class MaxTargetsCriterion extends BaseCriterion {
|
||||||
|
|
||||||
constructor(targetsLimit) {
|
constructor(options) {
|
||||||
super();
|
super(options);
|
||||||
this.targetsLimit = targetsLimit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate(data) {
|
evaluate(data) {
|
||||||
@ -14,8 +13,8 @@ export class MaxTargetsCriterion extends BaseCriterion {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
let message;
|
let message;
|
||||||
if (measurementNumbers.length > this.targetsLimit) {
|
if (measurementNumbers.length > this.options.limit) {
|
||||||
message = `The study should not have more than ${this.targetsLimit} targets.`;
|
message = `The study should not have more than ${this.options.limit} targets.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.generateResponse(message);
|
return this.generateResponse(message);
|
||||||
|
|||||||
@ -3,8 +3,7 @@ import { BaseCriterion } from './BaseCriterion';
|
|||||||
export class MeasurementsLengthCriterion extends BaseCriterion {
|
export class MeasurementsLengthCriterion extends BaseCriterion {
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super();
|
super(options);
|
||||||
this.options = options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate(data) {
|
evaluate(data) {
|
||||||
|
|||||||
@ -6,13 +6,13 @@ import { _ } from 'meteor/underscore';
|
|||||||
* Check if a modality is allowed or restricted
|
* Check if a modality is allowed or restricted
|
||||||
* Options:
|
* Options:
|
||||||
* method (string): allow, restrict
|
* method (string): allow, restrict
|
||||||
|
* measurementTypes (string[]): list of measurement types that will be evaluated
|
||||||
* modalities (string[]): list of allowed/restricted modalities
|
* modalities (string[]): list of allowed/restricted modalities
|
||||||
*/
|
*/
|
||||||
export class ModalityCriterion extends BaseCriterion {
|
export class ModalityCriterion extends BaseCriterion {
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super();
|
super(options);
|
||||||
this.options = options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate(data) {
|
evaluate(data) {
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { BaseCriterion } from './BaseCriterion';
|
|||||||
|
|
||||||
export class NonTargetResponseCriterion extends BaseCriterion {
|
export class NonTargetResponseCriterion extends BaseCriterion {
|
||||||
|
|
||||||
constructor() {
|
constructor(options) {
|
||||||
super();
|
super(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate(data) {
|
evaluate(data) {
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { BaseCriterion } from './BaseCriterion';
|
|||||||
|
|
||||||
export class TargetTypeCriterion extends BaseCriterion {
|
export class TargetTypeCriterion extends BaseCriterion {
|
||||||
|
|
||||||
constructor() {
|
constructor(options) {
|
||||||
super();
|
super(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate(data) {
|
evaluate(data) {
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
export { MaxTargetPerOrganCriterion } from './MaxTargetPerOrgan';
|
||||||
|
export { MaxTargetsCriterion } from './MaxTargets';
|
||||||
|
export { MeasurementsLengthCriterion } from './MeasurementsLength';
|
||||||
|
export { ModalityCriterion } from './Modality';
|
||||||
|
export { NonTargetResponseCriterion } from './NonTargetResponse';
|
||||||
|
export { TargetTypeCriterion } from './TargetType';
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"MaxTargetPerOrgan": {
|
||||||
|
"limit": 2
|
||||||
|
},
|
||||||
|
"MaxTargets": {
|
||||||
|
"limit": 5
|
||||||
|
},
|
||||||
|
"MeasurementsLength": [{
|
||||||
|
"longAxis": 10,
|
||||||
|
"longAxisSliceThicknessMultiplier": 2,
|
||||||
|
"modalityIn": ["CT", "MR"],
|
||||||
|
"locationNotIn": ["Lymph Node"],
|
||||||
|
"message": "Extranodal lesions must be >= 10mm long axis AND >= double the acquisition slice thickness by CT and MR"
|
||||||
|
}, {
|
||||||
|
"shortAxis": 20,
|
||||||
|
"longAxis": 20,
|
||||||
|
"modalityIn": ["PX", "XA"],
|
||||||
|
"locationNotIn": ["Lymph Node"],
|
||||||
|
"message": "Extranodal lesions must be >= 20mm on chest x-ray (although x-rays rarely used for clinical trial assessment)"
|
||||||
|
}, {
|
||||||
|
"shortAxis": 15,
|
||||||
|
"shortAxisSliceThicknessMultiplier": 2,
|
||||||
|
"modalityIn": ["CT", "MR"],
|
||||||
|
"locationIn": ["Lymph Node"],
|
||||||
|
"message": "Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR"
|
||||||
|
}],
|
||||||
|
"NonTargetResponse": {},
|
||||||
|
"TargetType": {}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user