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 { _ } from 'meteor/underscore';
|
||||
import { RecistChecker } from './checkers/RecistChecker';
|
||||
import { CriteriaEvaluator } from './CriteriaEvaluator';
|
||||
import * as recistBaselineEvaluation from './evaluations/recistBaseline.json';
|
||||
|
||||
class ConformanceCriteria {
|
||||
|
||||
@ -17,8 +18,8 @@ class ConformanceCriteria {
|
||||
}
|
||||
|
||||
validateRecist(data) {
|
||||
const recistChecker = new RecistChecker();
|
||||
console.warn('>>>>check', recistChecker.check(data));
|
||||
const recistBaselineEvaluator = new CriteriaEvaluator(recistBaselineEvaluation);
|
||||
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 {
|
||||
|
||||
constructor() {}
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
generateResponse(message, measurements) {
|
||||
const passed = !message;
|
||||
|
||||
@ -2,9 +2,8 @@ import { BaseCriterion } from './BaseCriterion';
|
||||
|
||||
export class MaxTargetPerOrganCriterion extends BaseCriterion {
|
||||
|
||||
constructor(targetsLimit) {
|
||||
super();
|
||||
this.targetsLimit = targetsLimit;
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
evaluate(data) {
|
||||
@ -20,13 +19,13 @@ export class MaxTargetPerOrganCriterion extends BaseCriterion {
|
||||
}
|
||||
|
||||
targetsPerOrgan[location].add(measurementNumber);
|
||||
if (targetsPerOrgan[location].size > this.targetsLimit) {
|
||||
if (targetsPerOrgan[location].size > this.options.limit) {
|
||||
measurements.push(measurement);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -3,9 +3,8 @@ import { _ } from 'meteor/underscore';
|
||||
|
||||
export class MaxTargetsCriterion extends BaseCriterion {
|
||||
|
||||
constructor(targetsLimit) {
|
||||
super();
|
||||
this.targetsLimit = targetsLimit;
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
evaluate(data) {
|
||||
@ -14,8 +13,8 @@ export class MaxTargetsCriterion extends BaseCriterion {
|
||||
}));
|
||||
|
||||
let message;
|
||||
if (measurementNumbers.length > this.targetsLimit) {
|
||||
message = `The study should not have more than ${this.targetsLimit} targets.`;
|
||||
if (measurementNumbers.length > this.options.limit) {
|
||||
message = `The study should not have more than ${this.options.limit} targets.`;
|
||||
}
|
||||
|
||||
return this.generateResponse(message);
|
||||
|
||||
@ -3,8 +3,7 @@ import { BaseCriterion } from './BaseCriterion';
|
||||
export class MeasurementsLengthCriterion extends BaseCriterion {
|
||||
|
||||
constructor(options) {
|
||||
super();
|
||||
this.options = options;
|
||||
super(options);
|
||||
}
|
||||
|
||||
evaluate(data) {
|
||||
|
||||
@ -6,13 +6,13 @@ import { _ } from 'meteor/underscore';
|
||||
* Check if a modality is allowed or restricted
|
||||
* Options:
|
||||
* method (string): allow, restrict
|
||||
* measurementTypes (string[]): list of measurement types that will be evaluated
|
||||
* modalities (string[]): list of allowed/restricted modalities
|
||||
*/
|
||||
export class ModalityCriterion extends BaseCriterion {
|
||||
|
||||
constructor(options) {
|
||||
super();
|
||||
this.options = options;
|
||||
super(options);
|
||||
}
|
||||
|
||||
evaluate(data) {
|
||||
|
||||
@ -2,8 +2,8 @@ import { BaseCriterion } from './BaseCriterion';
|
||||
|
||||
export class NonTargetResponseCriterion extends BaseCriterion {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
evaluate(data) {
|
||||
|
||||
@ -2,8 +2,8 @@ import { BaseCriterion } from './BaseCriterion';
|
||||
|
||||
export class TargetTypeCriterion extends BaseCriterion {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
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