Updated Trial Criteria selection, added groundwork for allowing multiple criteria (LT-188)
This commit is contained in:
parent
9970b8795b
commit
c4ad6b04b5
@ -0,0 +1,18 @@
|
|||||||
|
<template name="irRCDescription">
|
||||||
|
<h4>irRC</h4>
|
||||||
|
<h5>Baseline Checks</h5>
|
||||||
|
<ul>
|
||||||
|
<li>Target lesions must be >= 10 X 10 mm AND >= double the acquisition slice thickness by CT and MR</li>
|
||||||
|
<li>Up to a max of 5 target lesions per organ</li>
|
||||||
|
<li>Up to a max of 10 target lesions total</li>
|
||||||
|
<li>Non-targets can only be assessed as 'present'</li>
|
||||||
|
<li>Target lesions must have measurements (cannot be assessed as CR, UN/NE, EX)</li>
|
||||||
|
</ul>
|
||||||
|
<h5>New Lesion Checks for Follow-ups</h5>
|
||||||
|
<ul>
|
||||||
|
<li>New target lesions must be >= 5 X 5 mm AND >= double the acquisition slice thickness by CT and MR</li>
|
||||||
|
<li>Up to a max of 5 target lesions per organ</li>
|
||||||
|
<li>Up to a max of 10 target lesions total</li>
|
||||||
|
</ul>
|
||||||
|
<!--Time Point Measurement Total = SPD target lesions + SPD new lesions (SPD = sum of product of long axis and short axis diameters)-->
|
||||||
|
</template>
|
||||||
@ -9,58 +9,23 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h4>Select a Trial Criteria Type</h4>
|
<h3>Select one or more Trial Criteria types:</h3>
|
||||||
<label for="recistCriteria" class="trialCriteriaLabel">
|
{{ #each trialCriteriaTypes}}
|
||||||
<input type="radio" name="trialCriteria" checked="true" id="recistCriteria" value='RECIST' class="trialCriteria"> RECIST 1.1
|
<label for="{{id}}Criteria" class="trialCriteriaLabel">
|
||||||
</label>
|
<!-- TODO: Change to checkbox for multiple criteria !-->
|
||||||
<label for="irRCCriteria" class="trialCriteriaLabel">
|
<input type="radio" name="trialCriteria" id="{{id}}Criteria" value='{{id}}' class="trialCriteria" checked={{selected}}>
|
||||||
<input type="radio" name="trialCriteria" id="irRCCriteria" value='irRC' class="trialCriteria"> irRC
|
{{name}}
|
||||||
</label>
|
</label>
|
||||||
|
{{ /each }}
|
||||||
|
<div>
|
||||||
|
<h3>Selected Trial Criteria:</h3>
|
||||||
|
{{ #each trialCriteriaTypes}}
|
||||||
|
{{ #if selected }}
|
||||||
|
<div class="trialCriteriaPanel panel fade in" id="{{id}}Description">
|
||||||
|
{{> Template.dynamic template=descriptionTemplate }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{{ /if }}
|
||||||
<br/>
|
{{ /each }}
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<h4>Trial Criteria Descriptions</h4>
|
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
|
||||||
<li role="presentation" class="active">
|
|
||||||
<a href="#RECISTDescription" aria-controls="home" role="tab" data-toggle="tab">RECIST 1.1</a>
|
|
||||||
</li>
|
|
||||||
<li role="presentation">
|
|
||||||
<a href="#irRCDescription" aria-controls="profile" role="tab" data-toggle="tab">irRC</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="tab-content">
|
|
||||||
<div role="tabpanel" class="tab-pane fade in active" id="RECISTDescription">
|
|
||||||
<h5>Baseline Checks</h5>
|
|
||||||
<ul>
|
|
||||||
<li>Extranodal lesions must be >= 10 mm long axis AND >= double the acquisition slice thickness by CT and MR</li>
|
|
||||||
<li>Extranodal lesions must be >= 20 mm on chest x-ray (although x-rays rarely used for clinical trial assessment)</li>
|
|
||||||
<li>Nodal lesions must be >= 15 mm short axis AND >= double the acquisition slice thickness by CT and MR</li>
|
|
||||||
<li>Up to a max of 2 target lesions per organ</li>
|
|
||||||
<li>Up to a max of 5 target lesions total</li>
|
|
||||||
<li>Non-targets can only be assessed as 'present'</li>
|
|
||||||
<li>Target lesions must have measurements (cannot be assessed as CR, UN/NE, EX)</li>
|
|
||||||
</ul>
|
|
||||||
<!-- Time Point Measurement Total = Sum of long axis measurements for extranodal target lesion + short axis measurements for nodal lesions</li>-->
|
|
||||||
</div>
|
|
||||||
<div role="tabpanel" class="tab-pane fade" id="irRCDescription">
|
|
||||||
<h5>Baseline Checks</h5>
|
|
||||||
<ul>
|
|
||||||
<li>Target lesions must be >= 10 X 10 mm AND >= double the acquisition slice thickness by CT and MR</li>
|
|
||||||
<li>Up to a max of 5 target lesions per organ</li>
|
|
||||||
<li>Up to a max of 10 target lesions total</li>
|
|
||||||
<li>Non-targets can only be assessed as 'present'</li>
|
|
||||||
<li>Target lesions must have measurements (cannot be assessed as CR, UN/NE, EX)</li>
|
|
||||||
</ul>
|
|
||||||
<h5>New Lesion Checks for Follow-ups</h5>
|
|
||||||
<ul>
|
|
||||||
<li>New target lesions must be >= 5 X 5 mm AND >= double the acquisition slice thickness by CT and MR</li>
|
|
||||||
<li>Up to a max of 5 target lesions per organ</li>
|
|
||||||
<li>Up to a max of 10 target lesions total</li>
|
|
||||||
</ul>
|
|
||||||
<!--Time Point Measurement Total = SPD target lesions + SPD new lesions (SPD = sum of product of long axis and short axis diameters)-->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,24 @@
|
|||||||
Session.setDefault('TrialResponseAssessmentCriteria', 'RECIST');
|
TrialCriteriaTypes = new Meteor.Collection(null);
|
||||||
|
|
||||||
|
TrialCriteriaTypes.insert({
|
||||||
|
id: 'RECIST',
|
||||||
|
name: 'RECIST 1.1',
|
||||||
|
descriptionTemplate: 'recistDescription',
|
||||||
|
selected: true
|
||||||
|
});
|
||||||
|
|
||||||
|
TrialCriteriaTypes.insert({
|
||||||
|
id: 'irRC',
|
||||||
|
name: 'irRC',
|
||||||
|
descriptionTemplate: 'irRCDescription',
|
||||||
|
selected: false
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.optionsModal.helpers({
|
||||||
|
trialCriteriaTypes: function() {
|
||||||
|
return TrialCriteriaTypes.find();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Template.optionsModal.events({
|
Template.optionsModal.events({
|
||||||
/**
|
/**
|
||||||
@ -8,16 +28,24 @@ Template.optionsModal.events({
|
|||||||
* @param e The 'change' event on the selected radio button
|
* @param e The 'change' event on the selected radio button
|
||||||
*/
|
*/
|
||||||
'change input.trialCriteria': function(e) {
|
'change input.trialCriteria': function(e) {
|
||||||
// Get the Trial Criteria type that the selected radio button represents
|
var isChecked = e.currentTarget.checked;
|
||||||
var radioButton = $(e.currentTarget);
|
|
||||||
var criteriaType = radioButton.val();
|
|
||||||
|
|
||||||
// Set this as the current Trial Response Assessment Criteria
|
// Set "Selected" to false for the entire collection
|
||||||
// TODO: Update when we have more trial-level support
|
// TODO: Remove this when we allow multiple criteria
|
||||||
// (Currently this information is stored in Session, later this will change)
|
TrialCriteriaTypes.update({}, {
|
||||||
Session.set('TrialResponseAssessmentCriteria', criteriaType);
|
$set: {
|
||||||
|
selected: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
multi: true
|
||||||
|
});
|
||||||
|
|
||||||
log.info('Trial Criteria changed to: ' + criteriaType);
|
// Set the current Criteria in the collection to selected
|
||||||
|
TrialCriteriaTypes.update(this._id, {
|
||||||
|
$set: {
|
||||||
|
selected: isChecked
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* When the Clear Study/Timepoint Associations button is clicked, we
|
* When the Clear Study/Timepoint Associations button is clicked, we
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
<template name="recistDescription">
|
||||||
|
<h4>RECIST 1.1</h4>
|
||||||
|
<h5>Baseline Checks</h5>
|
||||||
|
<ul>
|
||||||
|
<li>Extranodal lesions must be >= 10 mm long axis AND >= double the acquisition slice thickness by CT and MR</li>
|
||||||
|
<li>Extranodal lesions must be >= 20 mm on chest x-ray (although x-rays rarely used for clinical trial assessment)</li>
|
||||||
|
<li>Nodal lesions must be >= 15 mm short axis AND >= double the acquisition slice thickness by CT and MR</li>
|
||||||
|
<li>Up to a max of 2 target lesions per organ</li>
|
||||||
|
<li>Up to a max of 5 target lesions total</li>
|
||||||
|
<li>Non-targets can only be assessed as 'present'</li>
|
||||||
|
<li>Target lesions must have measurements (cannot be assessed as CR, UN/NE, EX)</li>
|
||||||
|
</ul>
|
||||||
|
<!-- Time Point Measurement Total = Sum of long axis measurements for extranodal target lesion + short axis measurements for nodal lesions</li>-->
|
||||||
|
</template>
|
||||||
@ -321,11 +321,16 @@ function irRC(image) {
|
|||||||
* Retrieve trial criteria constraints based on the image that measurements appear upon
|
* Retrieve trial criteria constraints based on the image that measurements appear upon
|
||||||
* If no image is specified, it is assumed that group or per Organ level criteria are desired.
|
* If no image is specified, it is assumed that group or per Organ level criteria are desired.
|
||||||
*
|
*
|
||||||
* @param criteriaType A valid Trial Criteria set name (e.g. 'RECIST' or 'irRC')
|
* @param criteriaTypes An array of valid Trial Criteria set names (e.g. ['RECIST', 'irRC'])
|
||||||
|
* NOTE: Multiple criteria are not yet supported
|
||||||
|
*
|
||||||
* @param imageId A Cornerstone Image ID
|
* @param imageId A Cornerstone Image ID
|
||||||
* @returns {*} An Object of Trial Criteria that can be used to validate measurements' conformance
|
* @returns {*} An Object of Trial Criteria that can be used to validate measurements' conformance
|
||||||
*/
|
*/
|
||||||
getTrialCriteriaConstraints = function(criteriaType, imageId) {
|
getTrialCriteriaConstraints = function(criteriaTypes, imageId) {
|
||||||
|
// TODO: update this when we allow multiple criteria
|
||||||
|
var allCriteria = [];
|
||||||
|
criteriaTypes.forEach(function(criteriaType) {
|
||||||
if (!TrialCriteriaConstraints[criteriaType]) {
|
if (!TrialCriteriaConstraints[criteriaType]) {
|
||||||
throw 'No such Trial Criteria defined: ' + criteriaType;
|
throw 'No such Trial Criteria defined: ' + criteriaType;
|
||||||
}
|
}
|
||||||
@ -386,5 +391,8 @@ getTrialCriteriaConstraints = function(criteriaType, imageId) {
|
|||||||
criteria = TrialCriteriaConstraints[criteriaType](image);
|
criteria = TrialCriteriaConstraints[criteriaType](image);
|
||||||
|
|
||||||
// Return the relevant criteria given the current timepoint type
|
// Return the relevant criteria given the current timepoint type
|
||||||
return criteria[timepointType];
|
allCriteria.push(criteria[timepointType]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return allCriteria[0];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -70,13 +70,9 @@ function assessGroupOfMeasurements(constraints) {
|
|||||||
type: type
|
type: type
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the criteria type so we can calculate total lesion burden
|
|
||||||
var criteriaType = Session.get('TrialResponseAssessmentCriteria');
|
|
||||||
|
|
||||||
// Calculate some simple group-level Measurement statistics for validation
|
// Calculate some simple group-level Measurement statistics for validation
|
||||||
var testStructure = {
|
var testStructure = {
|
||||||
totalNumberOfLesions: Measurements.find().count(),
|
totalNumberOfLesions: Measurements.find().count()
|
||||||
totalLesionBurden: calculateTotalLesionBurden(criteriaType)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run the conformance checks with the validate.js library
|
// Run the conformance checks with the validate.js library
|
||||||
@ -220,8 +216,12 @@ function assessSingleMeasurement(constraints, measurementData) {
|
|||||||
function validateSingleMeasurement(measurementData) {
|
function validateSingleMeasurement(measurementData) {
|
||||||
// Obtain the name of the current TrialResponseAssessmentCriteria that
|
// Obtain the name of the current TrialResponseAssessmentCriteria that
|
||||||
// we are using.
|
// we are using.
|
||||||
var criteriaType = Session.get('TrialResponseAssessmentCriteria');
|
var criteriaTypes = TrialCriteriaTypes.find({
|
||||||
var currentConstraints = getTrialCriteriaConstraints(criteriaType, measurementData.imageId);
|
selected: true
|
||||||
|
}).map(function(criteria) {
|
||||||
|
return criteria.id;
|
||||||
|
});
|
||||||
|
var currentConstraints = getTrialCriteriaConstraints(criteriaTypes, measurementData.imageId);
|
||||||
|
|
||||||
// If we have no relevant constraints, stop here
|
// If we have no relevant constraints, stop here
|
||||||
if (!currentConstraints) {
|
if (!currentConstraints) {
|
||||||
@ -264,12 +264,16 @@ function validateSingleMeasurement(measurementData) {
|
|||||||
function validateGroups() {
|
function validateGroups() {
|
||||||
log.info('validateGroups');
|
log.info('validateGroups');
|
||||||
|
|
||||||
// Obtain the name of the current TrialResponseAssessmentCriteria that
|
// Obtain the names of the current TrialResponseAssessmentCriteria that
|
||||||
// we are using.
|
// we are using.
|
||||||
var criteriaType = Session.get('TrialResponseAssessmentCriteria');
|
var criteriaTypes = TrialCriteriaTypes.find({
|
||||||
|
selected: true
|
||||||
|
}).map(function(criteria) {
|
||||||
|
return criteria.id;
|
||||||
|
});
|
||||||
|
|
||||||
// Criteria for the specific image are retrieved from the general set of criteria.
|
// Criteria for the specific image are retrieved from the general set of criteria.
|
||||||
var currentConstraints = getTrialCriteriaConstraints(criteriaType);
|
var currentConstraints = getTrialCriteriaConstraints(criteriaTypes);
|
||||||
if (!currentConstraints) {
|
if (!currentConstraints) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -297,10 +301,13 @@ function validateGroups() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function validateAll() {
|
function validateAll() {
|
||||||
log.info('validateAll');
|
// Obtain the names of the current TrialResponseAssessmentCriteria that
|
||||||
// Obtain the name of the current TrialResponseAssessmentCriteria that
|
|
||||||
// we are using.
|
// we are using.
|
||||||
var criteriaType = Session.get('TrialResponseAssessmentCriteria');
|
var criteriaTypes = TrialCriteriaTypes.find({
|
||||||
|
selected: true
|
||||||
|
}).map(function(criteria) {
|
||||||
|
return criteria.id;
|
||||||
|
});
|
||||||
|
|
||||||
Measurements.find().forEach(function(measurement) {
|
Measurements.find().forEach(function(measurement) {
|
||||||
Object.keys(measurement.timepoints).forEach(function(timepointId) {
|
Object.keys(measurement.timepoints).forEach(function(timepointId) {
|
||||||
@ -311,7 +318,7 @@ function validateAll() {
|
|||||||
currentMeasurement._id = measurement._id;
|
currentMeasurement._id = measurement._id;
|
||||||
|
|
||||||
// Criteria for the specific image are retrieved from the general set of criteria.
|
// Criteria for the specific image are retrieved from the general set of criteria.
|
||||||
var currentConstraints = getTrialCriteriaConstraints(criteriaType, currentMeasurement.imageId);
|
var currentConstraints = getTrialCriteriaConstraints(criteriaTypes, currentMeasurement.imageId);
|
||||||
if (!currentConstraints) {
|
if (!currentConstraints) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -333,8 +340,6 @@ var validationTimeout = 400;
|
|||||||
* @param measurementData Input measurement data from CornerstoneTools
|
* @param measurementData Input measurement data from CornerstoneTools
|
||||||
*/
|
*/
|
||||||
function validateDelayed(measurementData) {
|
function validateDelayed(measurementData) {
|
||||||
log.info('validateAllDelayed');
|
|
||||||
|
|
||||||
// Erase any currently-waiting validation call
|
// Erase any currently-waiting validation call
|
||||||
clearTimeout(validationTimeout);
|
clearTimeout(validationTimeout);
|
||||||
|
|
||||||
|
|||||||
@ -71,6 +71,9 @@ Package.onUse(function(api) {
|
|||||||
api.addFiles('client/components/optionsModal/optionsModal.styl', 'client');
|
api.addFiles('client/components/optionsModal/optionsModal.styl', 'client');
|
||||||
api.addFiles('client/components/optionsModal/optionsModal.js', 'client');
|
api.addFiles('client/components/optionsModal/optionsModal.js', 'client');
|
||||||
|
|
||||||
|
api.addFiles('client/components/recistDescription/recistDescription.html', 'client');
|
||||||
|
api.addFiles('client/components/irRCDescription/irRCDescription.html', 'client');
|
||||||
|
|
||||||
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.html', 'client');
|
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.html', 'client');
|
||||||
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.js', 'client');
|
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.js', 'client');
|
||||||
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.styl', 'client');
|
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.styl', 'client');
|
||||||
@ -201,6 +204,7 @@ Package.onUse(function(api) {
|
|||||||
api.export('ValidationErrors', 'client');
|
api.export('ValidationErrors', 'client');
|
||||||
api.export('LesionLocations', 'client');
|
api.export('LesionLocations', 'client');
|
||||||
api.export('LocationResponses', 'client');
|
api.export('LocationResponses', 'client');
|
||||||
|
api.export('TrialCriteriaTypes', 'client');
|
||||||
|
|
||||||
// Export collections spanning both client and server
|
// Export collections spanning both client and server
|
||||||
api.export('ImageMeasurements', [ 'client', 'server' ]);
|
api.export('ImageMeasurements', [ 'client', 'server' ]);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user