Creating ohif-studies package to deal with studies browsing

This commit is contained in:
Bruno Alves de Faria committed 2017-10-25 22:09:08 -02:00
1 parent 956e6da0b3
commit 0b51a53976
28 files changed
+323 -83

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
OHIF.studies = {};
require('../imports/both');
+1
View File
@@ -0,0 +1 @@
require('../imports/client');
Whitespace-only changes.
@@ -0,0 +1,5 @@
import './list.html';
import './list.js';
import './item.html';
import './item.js';
import './item.styl';
@@ -0,0 +1,21 @@
<template name="studyBrowserItem">
<div class="study-browser-item {{this.settings.studyItemClass}} {{#if isActive}}active{{/if}} {{#if isLoading}}loading{{/if}}" data-uid="{{this.studyInformation.studyInstanceUid}}">
<div class="study-item">
{{#if isLoading}}
{{>loadingText}}
{{/if}}
<div class="study-item-box">
<div class="study-modality">
<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>
</div>
</div>
{{#if this.settings.studyTemplate}}
{{>Template.dynamic template=this.settings.studyTemplate data=(clone this)}}
{{/if}}
</div>
</template>
@@ -0,0 +1,43 @@
import { Template } from 'meteor/templating';
import { $ } from 'meteor/jquery';
Template.studyBrowserItem.onCreated(() => {
const instance = Template.instance();
const modality = instance.data.studyInformation.modality || 'UN';
instance.modalities = modality.replace(/\\/g, ' ');
});
Template.studyBrowserItem.events({
'click .study-browser-item'(event, instance) {
const element = event.currentTarget;
const $element = $(element);
$element.trigger('ohif.studies.study.click', instance.data.studyInformation);
const { settings, studyInformation } = instance.data;
if (settings && typeof settings.studyClickCallback) {
settings.studyClickCallback(studyInformation, element);
}
}
});
Template.studyBrowserItem.helpers({
modalityStyle() {
// Responsively styles the Modality Acronyms for studies
// with more than one modality
const instance = Template.instance();
const numModalities = instance.modalities.split(/\s/g).length;
if (numModalities === 1) {
// If we have only one modality, it should take up the whole div.
return 'font-size: 1em';
} else if (numModalities === 2) {
// If we have two, let them sit side-by-side
return 'font-size: 0.75em';
} else {
// If we have more than two modalities, change the line height to display multiple rows,
// depending on the number of modalities we need to display.
const lineHeight = Math.ceil(numModalities / 2) * 1.2;
return 'line-height: ' + lineHeight + 'em';
}
}
});
@@ -0,0 +1,132 @@
@require '{ohif:design}/app'
$boxBorderColor = transparent
$boxHoverBackgroundColor = #14191E
$boxHoverBorderColor = #2d4660
$boxActiveBorderColor = #131D29
$boxWidth = 50px
$borderThickness = 2px
$boxBorderRadius = 8px
$nestingMargin = 6px
$spacerX = 7px
$spacerY = 12px
.study-browser-item
position: relative
// required transformation to make inner fixed elements relative to this one
transform(scale(1))
.loadingTextDiv
padding: 25px
border-radius: 12px
position: absolute
opacity: 0
z-index: 100
background-color: $boxHoverBackgroundColor
pointer-events: none
margin: 0
&.loading
.loadingTextDiv
height: 100%
left: 0
opacity: 0.75
right: 16px
.study-item-box
opacity: 0.2
&.active
.study-item-box
.study-modality
theme('color', '$primaryBackgroundColor')
&
&:before
&:after
theme('background-color', '$activeColor')
border-color: $boxActiveBorderColor
&:not(.active) .studyTimepointThumbnails
max-height: 0 !important
.studyTimepointThumbnails
overflow: hidden
transition($sidebarTransition)
.study-item-box
border: 1px solid $boxBorderColor
border-radius: 12px
cursor: pointer
padding: $spacerY $spacerX ($spacerY - 1)
position: relative
transition($sidebarTransition)
z-index: 1
&:hover
background-color: $boxHoverBackgroundColor
border-color: $boxHoverBorderColor
&.additional
theme('color', '$textSecondaryColor')
&.additional
theme('color', '$textPrimaryColor')
font-size: 16px
font-weight: normal
height: 91px
line-height: 91px
padding: 0
text-align: center
.study-text
font-size: 13px
left: ($spacerX * 3) + $boxWidth + ($nestingMargin * 3)
line-height: 14px
position: absolute
right: $spacerX
top: $spacerY
.study-date
margin-top: 8px
theme('color', '$textSecondaryColor')
.study-description
margin-top: 8px
theme('color', '$textPrimaryColor')
.study-modality
theme('color', '$textSecondaryColor')
font-size: 20px
line-height: $boxWidth
margin-left: $nestingMargin * 2
margin-top: $nestingMargin * 2
position: relative
.study-modality-text
text-align: center
text-transform: uppercase
height: 100%
&
&:before
&:after
theme('border', '%s solid $primaryBackgroundColor' % $borderThickness)
theme('background-color', '$boxBackgroundColor')
border-radius: $boxBorderRadius
height: $boxWidth + ($borderThickness * 2)
transition($sidebarTransition)
width: $boxWidth + ($borderThickness * 2)
&:before
&:after
display: block
content: ''
position: absolute
&:before
top: -($borderThickness + $nestingMargin)
left: -($borderThickness + $nestingMargin)
z-index: -1
&:after
top: -($borderThickness + ($nestingMargin * 2))
left: -($borderThickness + ($nestingMargin * 2))
z-index: -2
@@ -0,0 +1,7 @@
<template name="studyBrowserList">
<div class="study-browser-list {{this.settings.studyListClass}}">
{{#each studyInformation in studiesInformation}}
{{>studyBrowserItem (clone this studyInformation=studyInformation)}}
{{/each}}
</div>
</template>
Whitespace-only changes.
@@ -0,0 +1 @@
import './browser';
@@ -0,0 +1,2 @@
import './components';
import './lib';
Whitespace-only changes.
@@ -0,0 +1,102 @@
import { Meteor } from 'meteor/meteor';
import { OHIF } from 'meteor/ohif:core';
import 'meteor/ohif:viewerbase';
// Define the StudyMetaDataPromises object. This is used as a cache to store study meta data
// promises and prevent unnecessary subsequent calls to the server
const StudyMetaDataPromises = new Map();
/**
* Retrieves study metadata using a server call
*
* @param {String} studyInstanceUid The UID of the Study to be retrieved
* @returns {Promise} that will be resolved with the metadata or rejected with the error
*/
OHIF.studies.retrieveStudyMetadata = (studyInstanceUid, seriesInstanceUids) => {
// @TODO: Whenever a study metadata request has failed, its related promise will be rejected once and for all
// and further requests for that metadata will always fail. On failure, we probably need to remove the
// corresponding promise from the "StudyMetaDataPromises" map...
// If the StudyMetaDataPromises cache already has a pending or resolved promise related to the
// given studyInstanceUid, then that promise is returned
if (StudyMetaDataPromises.has(studyInstanceUid)) {
return StudyMetaDataPromises.get(studyInstanceUid);
}
const seriesKeys = Array.isArray(seriesInstanceUids) ? '|' + seriesInstanceUids.join('|') : '';
const timingKey = `retrieveStudyMetadata[${studyInstanceUid}${seriesKeys}]`;
OHIF.log.time(timingKey);
// Create a promise to handle the data retrieval
const promise = new Promise((resolve, reject) => {
// If no study metadata is in the cache variable, we need to retrieve it from
// the server with a call.
Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) {
OHIF.log.timeEnd(timingKey);
if (error) {
const errorType = error.error;
let errorMessage = '';
if (errorType === 'server-connection-error') {
errorMessage = 'There was an error connecting to the DICOM server, please verify if it is up and running.';
} else if (errorType === 'server-internal-error') {
errorMessage = `There was an internal error with the DICOM server getting metadeta for ${studyInstanceUid}`;
} else {
errorMessage = `For some reason we could not retrieve the study\'s metadata for ${studyInstanceUid}.`;
}
OHIF.log.error(errorMessage);
OHIF.log.error(error.stack);
reject(`GetStudyMetadata: ${errorMessage}`);
return;
}
// Filter series if seriesInstanceUid exists
if (seriesInstanceUids && seriesInstanceUids.length) {
study.seriesList = study.seriesList.filter(series => seriesInstanceUids.indexOf(series.seriesInstanceUid) > -1);
}
if (!study) {
reject(`GetStudyMetadata: No study data returned from server: ${studyInstanceUid}`);
return;
}
if (window.HipaaLogger && Meteor.user && Meteor.user()) {
window.HipaaLogger.logEvent({
eventType: 'viewed',
userId: Meteor.userId(),
userName: Meteor.user().profile.fullName,
collectionName: 'Study',
recordId: studyInstanceUid,
patientId: study.patientId,
patientName: study.patientName
});
}
// Once the data was retrieved, the series are sorted by series and instance number
OHIF.viewerbase.sortStudy(study);
// Updates WADO-RS metaDataManager
OHIF.viewerbase.updateMetaDataManager(study);
// Transform the study in a StudyMetadata object
const studyMetadata = new OHIF.metadata.StudyMetadata(study);
// Add the display sets to the study
study.displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
study.displaySets.forEach(displaySet => {
OHIF.viewerbase.stackManager.makeAndAddStack(study, displaySet);
});
// Resolve the promise with the final study metadata object
resolve(study);
});
});
// Store the promise in cache
StudyMetaDataPromises.set(studyInstanceUid, promise);
return promise;
};
Whitespace-only changes.
+35
View File
@@ -0,0 +1,35 @@
Package.describe({
name: 'ohif:studies',
summary: 'OHIF Studies Library to deal with studies UI, retrieval and manipulation',
version: '0.0.1'
});
Package.onUse(function(api) {
api.versionsFrom('1.4');
api.use([
'ecmascript',
'templating',
'stylus',
'http'
]);
// Our custom packages
api.use([
'ohif:design',
'ohif:core',
'ohif:log',
'ohif:dicom-services',
'ohif:viewerbase',
'ohif:wadoproxy'
]);
// Client and server imports
api.addFiles('both/main.js', [ 'client', 'server' ]);
// Server imports
api.addFiles('server/main.js', 'server');
// Client imports
api.addFiles('client/main.js', 'client');
});
+1
View File
@@ -0,0 +1 @@
require('../imports/server');