Creating timepoint browser

This commit is contained in:
Bruno Alves de Faria 2017-10-12 10:10:43 -03:00
parent eb372bed03
commit fd9cfed8e9
23 changed files with 204 additions and 132 deletions

View File

@ -39,6 +39,7 @@ ohif:core
ohif:header
ohif:cornerstone
ohif:viewerbase
ohif:studies
ohif:study-list
ohif:dicom-services
ohif:hanging-protocols
@ -63,4 +64,4 @@ zuuk:stale-session
dangrossman:bootstrap-daterangepicker
johdirr:meteor-git-rev
u2622:persistent-session@0.4.4
shell-server@0.2.3
shell-server@0.2.3

View File

@ -100,6 +100,7 @@ ohif:metadata@0.0.1
ohif:polyfill@0.0.1
ohif:select-tree@0.0.1
ohif:servers@0.0.1
ohif:studies@0.0.1
ohif:study-list@0.0.1
ohif:themes@0.0.1
ohif:themes-common@0.0.1

View File

@ -0,0 +1,3 @@
<body>
<div id="notificationArea" class="notification-area"></div>
</body>

View File

@ -1,6 +1,8 @@
<template name="viewerSection">
<div class="viewerSection">
<div class="sidebarMenu sidebar-left {{#if leftSidebarOpen}}sidebar-open{{/if}}">
{{>timepointBrowserList (extend timepointBrowserData scrollableClass='p-a-1')}}
<hr>
{{>studyTimepointBrowser (clone this)}}
</div>
<div class="mainContent {{#if leftSidebarOpen}}sidebar-left-open{{/if}} {{#if rightSidebarOpen}}sidebar-right-open{{/if}}">

View File

@ -1,19 +1,31 @@
import { Template } from 'meteor/templating';
Template.viewerSection.events({
'transitionend .sidebarMenu'(event) {
if (!event.target.classList.contains('sidebarMenu')) {
return;
}
window.ResizeViewportManager.handleResize();
}
});
'transitionend .sidebarMenu'(event) {
if (!event.target.classList.contains('sidebarMenu')) {
return;
}
window.ResizeViewportManager.handleResize();
}
});
Template.viewerSection.helpers({
leftSidebarOpen() {
return Template.instance().data.state.get('leftSidebar');
return Template.instance().data.state.get('leftSidebar');
},
rightSidebarOpen() {
return Template.instance().data.state.get('rightSidebar');
return Template.instance().data.state.get('rightSidebar');
},
timepointBrowserData() {
const instance = Template.instance();
const { timepointApi } = instance.data;
const timepoints = timepointApi.all();
return {
timepointApi,
timepoints
};
}
});
});

View File

@ -1,6 +1,6 @@
<template name="scrollArea">
<div class="scroll-area {{this.class}}">
<div class="scrollable {{#if instance.config.scrollX}}scroll-x{{/if}} {{#if instance.config.scrollY}}scroll-y{{/if}}">
<div class="scrollable {{this.scrollableClass}} {{#if instance.config.scrollX}}scroll-x{{/if}} {{#if instance.config.scrollY}}scroll-y{{/if}}">
{{>Template.contentBlock}}
</div>
</div>

View File

@ -46,6 +46,9 @@ function makeDraggable(element, options) {
return;
}
// Stop the dragging if it's not the primary button
if (e.button !== 0) return;
// Stop the dragging if the element is being resized
if ($(element).hasClass('resizing')) {
return;

View File

@ -1,8 +1,8 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';
import { Tracker } from 'meteor/tracker';
import { Blaze } from 'meteor/blaze';
import { $ } from 'meteor/jquery';
import { _ } from 'meteor/underscore';
import { OHIF } from 'meteor/ohif:core';
const Notifications = {
@ -29,7 +29,7 @@ Notifications.dismiss = id => {
if ($note.length) {
$note.addClass('out').one('transitionend', () => removeView(id, view));
} else {
removeView(id, view)
removeView(id, view);
}
};

View File

@ -1,6 +1,8 @@
import './dialog/nonTargetMeasurement.html';
import './dialog/nonTargetMeasurement.js';
import './timepointBrowser';
import './trialOptionsModal/irRCDescription.html';
import './trialOptionsModal/recistDescription.html';

View File

@ -0,0 +1,3 @@
import './list.html';
import './item.html';
import './item.js';

View File

@ -0,0 +1,16 @@
<template name="timepointBrowserItem">
<div class="timepointEntry p-y-2">
<div class="timepointHeader">
<div class="timepointDetails clearfix">
<div class="timepointFollowupTitle pull-left">
{{this.timepointApi.title timepoint}}
</div>
<div class="expandIcon pull-right">
<i class="fa fa-chevron-down"></i>
</div>
<div class="timepointDate pull-right m-r-1">{{formatDA this.timepoint.earliestDate 'D-MMM-YYYY'}}</div>
</div>
<!-- <div class="timepointModalities">{{modalitiesSummary timepoint}}</div> -->
</div>
</div>
</template>

View File

@ -0,0 +1,28 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { OHIF } from 'meteor/ohif:core';
Template.timepointBrowserItem.onCreated(() => {
const instance = Template.instance();
const { timepoint, timepointApi } = instance.data;
instance.loading = new ReactiveVar(false);
instance.loaded = !!(timepoint.studiesData && timepoint.studiesData.length);
instance.autorun(() => {
if (instance.loaded || !instance.loading.get()) return;
const filter = { studyInstanceUid: timepoint.studyInstanceUids };
Meteor.call('StudyListSearch', filter, (error, studiesData) => {
if (error) {
const text = 'An error has occurred while retrieving studies information';
OHIF.ui.notifications.danger({ text });
OHIF.log.error(error);
} else {
timepoint.studiesData = studiesData;
timepointApi.timepoints.update(timepoint._id, { $set: studiesData });
}
});
});
});

View File

@ -0,0 +1,7 @@
<template name="timepointBrowserList">
{{#scrollArea class=(concat 'full-height ' this.class) scrollableClass=this.scrollableClass}}
{{#each timepoint in this.timepoints}}
{{>timepointBrowserItem (clone this timepoint=timepoint)}}
{{/each}}
{{/scrollArea}}
</template>

View File

@ -21,6 +21,7 @@ Package.onUse(function(api) {
// Our custom packages
api.use('ohif:design');
api.use('ohif:core');
api.use('ohif:studies');
api.use('ohif:study-list');
api.use('ohif:cornerstone');
api.use('ohif:measurements');

View File

@ -1,31 +1,5 @@
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
const StudiesData = new SimpleSchema({
studyInstanceUid: {
type: String,
label: 'Study Instance Uid'
},
description: {
type: String,
label: 'Study Description',
optional: true
},
date: {
type: Date,
label: 'Study Date'
},
modality: {
type: String,
label: 'Study Modality'
},
loaded: {
type: Boolean,
label: 'Defines if the Study is already loaded',
optional: true,
defaultValue: false
}
});
export const schema = new SimpleSchema({
patientId: {
type: String,
@ -64,8 +38,8 @@ export const schema = new SimpleSchema({
optional: true
},
studiesData: {
type: [StudiesData],
label: 'Studies minimal data to allow lazy loading',
type: [Object],
label: 'Studies data to allow lazy loading',
optional: true
}
});

View File

@ -9,8 +9,8 @@
<div class="study-modality-text" style="{{modalityStyle}}">{{instance.modalities}}</div>
</div>
<div class="study-text">
<div class="study-date">{{formatDA this.studyInformation.date 'D-MMM-YYYY'}}</div>
<div class="study-description">{{this.studyInformation.description}}</div>
<div class="study-date">{{formatDA this.studyInformation.studyDate 'D-MMM-YYYY'}}</div>
<div class="study-description">{{this.studyInformation.studyDescription}}</div>
</div>
</div>
</div>

View File

@ -9,8 +9,8 @@ Template.studyBrowserItem.onCreated(() => {
instance.loaded = false;
instance.loading = new ReactiveVar(false);
const modality = instance.data.studyInformation.modality || 'UN';
instance.modalities = modality.replace(/\\/g, ' ');
const modalities = instance.data.studyInformation.modalities || 'UN';
instance.modalities = modalities.replace(/\\/g, ' ');
});
Template.studyBrowserItem.events({

View File

@ -1,3 +1,4 @@
import { moment } from 'meteor/momentjs:moment';
import { OHIF } from 'meteor/ohif:core';
/**
@ -7,10 +8,10 @@ import { OHIF } from 'meteor/ohif:core';
* @returns {Array} An array of Study MetaData objects
*/
function resultDataToStudies(resultData) {
var studies = [];
const studies = [];
resultData.forEach(function(studyRaw) {
var study = studyRaw.toObject();
const study = studyRaw.toObject();
studies.push({
studyInstanceUid: study[0x0020000D],
// 00080005 = SpecificCharacterSet
@ -43,7 +44,15 @@ OHIF.studies.services.DIMSE.Studies = function(filter) {
filterStudyDate = `${dateFrom}-${dateTo}`;
}
var parameters = {
// Build the StudyInstanceUID parameter
let studyUids = filter.studyInstanceUid || '';
if (studyUids) {
studyUids = Array.isArray(studyUids) ? studyUids.join() : studyUids;
studyUids = studyUids.replace(/[^0-9.]+/g, '\\');
}
const parameters = {
0x0020000D: studyUids,
0x00100010: filter.patientName,
0x00100020: filter.patientId,
0x00080050: filter.accessionNumber,
@ -54,6 +63,6 @@ OHIF.studies.services.DIMSE.Studies = function(filter) {
0x00080061: filter.modalitiesInStudy
};
var results = DIMSE.retrieveStudies(parameters);
const results = DIMSE.retrieveStudies(parameters);
return resultDataToStudies(results);
};

View File

@ -8,14 +8,14 @@ import { OHIF } from 'meteor/ohif:core';
* @returns {string} The formatted date string
*/
function dateToString(date) {
if(!date) return "";
var year = date.getFullYear().toString();
var month = (date.getMonth() + 1).toString();
var day = date.getDate().toString();
year = "0".repeat(4-year.length).concat(year);
month= "0".repeat(2-month.length).concat(month);
day = "0".repeat(2-day.length).concat(day);
return "".concat(year,month,day);
if (!date) return '';
let year = date.getFullYear().toString();
let month = (date.getMonth() + 1).toString();
let day = date.getDate().toString();
year = '0'.repeat(4 - year.length).concat(year);
month = '0'.repeat(2 - month.length).concat(month);
day = '0'.repeat(2 - day.length).concat(day);
return ''.concat(year, month, day);
}
/**
@ -27,13 +27,13 @@ function dateToString(date) {
* @returns {string} The URL with encoded filter query data
*/
function filterToQIDOURL(server, filter) {
var commaSeparatedFields = [
const commaSeparatedFields = [
'00081030', // Study Description
'00080060' //Modality
// Add more fields here if you want them in the Study List
// Add more fields here if you want them in the result
].join(',');
var parameters = {
const parameters = {
PatientName: filter.patientName,
PatientID: filter.patientId,
AccessionNumber: filter.accessionNumber,
@ -45,8 +45,17 @@ function filterToQIDOURL(server, filter) {
// build the StudyDate range parameter
if (filter.studyDateFrom || filter.studyDateTo) {
var date = "".concat(dateToString(new Date(filter.studyDateFrom)), "-", dateToString(new Date(filter.studyDateTo)));
parameters.StudyDate = date;
const dateFrom = dateToString(new Date(filter.studyDateFrom));
const dateTo = dateToString(new Date(filter.studyDateTo));
parameters.StudyDate = `${dateFrom}-${dateTo}`;
}
// Build the StudyInstanceUID parameter
if (filter.studyInstanceUid) {
let studyUids = filter.studyInstanceUid;
studyUids = Array.isArray(studyUids) ? studyUids.join() : studyUids;
studyUids = studyUids.replace(/[^0-9.]+/g, '\\');
parameters.StudyInstanceUID = studyUids;
}
return server.qidoRoot + '/studies?' + encodeQueryData(parameters);
@ -59,43 +68,39 @@ function filterToQIDOURL(server, filter) {
* @returns {Array} An array of Study MetaData objects
*/
function resultDataToStudies(resultData) {
var studies = [];
const studies = [];
if (!resultData || !resultData.length) {
return;
}
if (!resultData || !resultData.length) return;
resultData.forEach(function(study) {
studies.push({
studyInstanceUid: DICOMWeb.getString(study['0020000D']),
// 00080005 = SpecificCharacterSet
studyDate: DICOMWeb.getString(study['00080020']),
studyTime: DICOMWeb.getString(study['00080030']),
accessionNumber: DICOMWeb.getString(study['00080050']),
referringPhysicianName: DICOMWeb.getString(study['00080090']),
// 00081190 = URL
patientName: DICOMWeb.getName(study['00100010']),
patientId: DICOMWeb.getString(study['00100020']),
patientBirthdate: DICOMWeb.getString(study['00100030']),
patientSex: DICOMWeb.getString(study['00100040']),
studyId: DICOMWeb.getString(study['00200010']),
numberOfStudyRelatedSeries: DICOMWeb.getString(study['00201206']),
numberOfStudyRelatedInstances: DICOMWeb.getString(study['00201208']),
studyDescription: DICOMWeb.getString(study['00081030']),
// modality: DICOMWeb.getString(study['00080060']),
// modalitiesInStudy: DICOMWeb.getString(study['00080061']),
modalities: DICOMWeb.getString(DICOMWeb.getModalities(study['00080060'], study['00080061']))
});
});
resultData.forEach(study => studies.push({
studyInstanceUid: DICOMWeb.getString(study['0020000D']),
// 00080005 = SpecificCharacterSet
studyDate: DICOMWeb.getString(study['00080020']),
studyTime: DICOMWeb.getString(study['00080030']),
accessionNumber: DICOMWeb.getString(study['00080050']),
referringPhysicianName: DICOMWeb.getString(study['00080090']),
// 00081190 = URL
patientName: DICOMWeb.getName(study['00100010']),
patientId: DICOMWeb.getString(study['00100020']),
patientBirthdate: DICOMWeb.getString(study['00100030']),
patientSex: DICOMWeb.getString(study['00100040']),
studyId: DICOMWeb.getString(study['00200010']),
numberOfStudyRelatedSeries: DICOMWeb.getString(study['00201206']),
numberOfStudyRelatedInstances: DICOMWeb.getString(study['00201208']),
studyDescription: DICOMWeb.getString(study['00081030']),
// modality: DICOMWeb.getString(study['00080060']),
// modalitiesInStudy: DICOMWeb.getString(study['00080061']),
modalities: DICOMWeb.getString(DICOMWeb.getModalities(study['00080060'], study['00080061']))
}));
return studies;
}
OHIF.studies.services.QIDO.Studies = function(server, filter) {
var url = filterToQIDOURL(server, filter);
OHIF.studies.services.QIDO.Studies = (server, filter) => {
const url = filterToQIDOURL(server, filter);
try {
var result = DICOMWeb.getJSON(url, server.requestOptions);
const result = DICOMWeb.getJSON(url, server.requestOptions);
return resultDataToStudies(result.data);
} catch (error) {

View File

@ -2,49 +2,52 @@ import { OHIF } from 'meteor/ohif:core';
import { remoteGetValue } from '../../lib/remoteGetValue';
function resultDataToStudies(resultData) {
var studies = [];
const studies = [];
resultData.forEach(function(study) {
studies.push({
studyInstanceUid: remoteGetValue(study['0020,000d']),
// 00080005 = SpecificCharacterSet
studyDate: remoteGetValue(study['0008,0020']),
studyTime: remoteGetValue(study['0008,0030']),
accessionNumber: remoteGetValue(study['0008,0050']),
referringPhysicianName: remoteGetValue(study['0008,0090']),
// 00081190 = URL
patientName: remoteGetValue(study['0010,0010']),
patientId: remoteGetValue(study['0010,0020']),
patientBirthdate: remoteGetValue(study['0010,0030']),
patientSex: remoteGetValue(study['0010,0040']),
studyId: remoteGetValue(study['0020,0010']),
numberOfStudyRelatedSeries: parseFloat(remoteGetValue(study['0020,1206'])),
numberOfStudyRelatedInstances: parseFloat(remoteGetValue(study['0020,1208'])),
studyDescription: remoteGetValue(study['0008,1030']),
modalities: remoteGetValue(study['0008,0061'])
resultData.forEach(function(study) {
studies.push({
studyInstanceUid: remoteGetValue(study['0020,000d']),
// 00080005 = SpecificCharacterSet
studyDate: remoteGetValue(study['0008,0020']),
studyTime: remoteGetValue(study['0008,0030']),
accessionNumber: remoteGetValue(study['0008,0050']),
referringPhysicianName: remoteGetValue(study['0008,0090']),
// 00081190 = URL
patientName: remoteGetValue(study['0010,0010']),
patientId: remoteGetValue(study['0010,0020']),
patientBirthdate: remoteGetValue(study['0010,0030']),
patientSex: remoteGetValue(study['0010,0040']),
studyId: remoteGetValue(study['0020,0010']),
numberOfStudyRelatedSeries: parseFloat(remoteGetValue(study['0020,1206'])),
numberOfStudyRelatedInstances: parseFloat(remoteGetValue(study['0020,1208'])),
studyDescription: remoteGetValue(study['0008,1030']),
modalities: remoteGetValue(study['0008,0061'])
});
});
});
return studies;
return studies;
}
OHIF.studies.services.REMOTE.Studies = function(server, filter) {
var parameters = {
PatientName: filter.patientName ? filter.patientName : "",
const parameters = {
studyInstanceUID: filter.studyInstanceUid || '',
PatientName: filter.patientName ? filter.patientName : '',
PatientID: filter.patientId,
AccessionNumber: filter.accessionNumber ? filter.accessionNumber : "",
StudyDescription: "",
StudyDate : "",
StudyTime : "",
ReferringPhysicianName : "",
PatientBirthDate : "",
PatientSex : "",
StudyID : "",
NumberOfStudyRelatedSeries : "",
NumberOfStudyRelatedInstances : "",
ModalitiesInStudy : ""
AccessionNumber: filter.accessionNumber ? filter.accessionNumber : '',
StudyDescription: '',
StudyDate: '',
StudyTime: '',
ReferringPhysicianName: '',
PatientBirthDate: '',
PatientSex: '',
StudyID: '',
NumberOfStudyRelatedSeries: '',
NumberOfStudyRelatedInstances: '',
ModalitiesInStudy: ''
};
var remote = new OrthancRemote(server.root, server.sourceAE);
var data = remote.findStudies(server.modality, parameters);
const remote = new OrthancRemote(server.root, server.sourceAE);
const data = remote.findStudies(server.modality, parameters);
return resultDataToStudies(data.results);
};

View File

@ -35,6 +35,7 @@ Package.onUse(function(api) {
api.use('ohif:dicom-services');
api.use('ohif:viewerbase');
api.use('ohif:wadoproxy');
api.use('ohif:studies');
// Client and server imports
api.addFiles('both/index.js', [ 'client', 'server' ]);

View File

@ -1 +1,2 @@
import './methods';
import './publications.js';

View File

@ -7,8 +7,8 @@
"qidoRoot": "http://localhost:8042/dicom-web",
"wadoRoot": "http://localhost:8042/dicom-web",
"qidoSupportsIncludeField": false,
"imageRendering": "wadouri",
"thumbnailRendering": "wadouri",
"imageRendering": "wadors",
"thumbnailRendering": "wadors",
"requestOptions": {
"auth": "orthanc:orthanc",
"logRequests": true,