Work from Medken branch

This commit is contained in:
Erik Ziegler 2015-11-26 22:31:29 +01:00
parent bee1e38044
commit 31d34a3fb5
12 changed files with 944 additions and 557 deletions

View File

@ -185,8 +185,7 @@ function thumbnailDragEndHandler(e, target) {
}
// Rerender the viewport using the drag and drop data
rerenderViewportWithNewSeries(element, OHIF.viewer.dragAndDropData);
Session.get('UseHangingProtocol', false);
WindowManager.loadSeriesInViewport(OHIF.viewer.dragAndDropData, element);
return false;
}

View File

@ -1,6 +1,6 @@
<template name="hangingProtocolButtons">
<button id="previousPresentationGroup" type="button"
class="imageViewerCommand btn btn-sm btn-default"
class="btn btn-sm btn-default"
data-container="body"
data-toggle="tooltip"
data-placement="bottom"
@ -9,7 +9,7 @@
<span class="fa fa-step-backward"></span>
</button>
<button id="nextPresentationGroup" type="button"
class="imageViewerCommand btn btn-sm btn-default"
class="btn btn-sm btn-default"
data-container="body"
data-toggle="tooltip"
data-placement="bottom"

View File

@ -1,7 +1,7 @@
Template.hangingProtocolButtons.helpers({
isNextAvailable: function() {
var presentationGroup = Session.get('WindowManagerPresentationGroup');
var numPresentationGroups = WindowManager.getNumPresentationGroups();
var numPresentationGroups = Session.get('WindowManagerNumPresentationGroups');
return presentationGroup < numPresentationGroups;
},
isPreviousAvailable: function() {
@ -9,3 +9,14 @@ Template.hangingProtocolButtons.helpers({
return presentationGroup > 1;
}
});
Template.hangingProtocolButtons.events({
'click #previousPresentationGroup': function(e) {
$(e.currentTarget).tooltip('hide');
WindowManager.previousPresentationGroup();
},
'click #nextPresentationGroup': function(e) {
$(e.currentTarget).tooltip('hide');
WindowManager.nextPresentationGroup();
}
});

View File

@ -138,13 +138,35 @@ function loadSeriesIntoViewport(data, templateData) {
// If there is a saved object containing Cornerstone viewport data
// (e.g. scale, invert, window settings) in the input data, apply it now.
//
// Otherwise, display the loaded image in the viewport element with the
// default viewport settings.
if (data.viewport) {
// Check if there are default viewport settings for this modality
enabledElement.image = image;
enabledElement.viewport = cornerstone.getDefaultViewport(enabledElement.canvas, image);
var modalityViewport = getModalityDefaultViewport(series, enabledElement, image.imageId);
// If no default viewport settings or modality-specific settings exists,
// display the loaded image in the viewport element with no loaded viewport
// settings.
if (modalityViewport) {
cornerstone.displayImage(element, image, modalityViewport);
// Mark that this element should not be fit to the window in the resize listeners
enabledElement.fitToWindow = false;
// Resize the canvas to fit the current viewport element size.
cornerstone.resize(element, false);
} else if (data.viewport) {
cornerstone.displayImage(element, image, data.viewport);
// Resize the canvas to fit the current viewport element size.
cornerstone.resize(element, true);
} else {
cornerstone.displayImage(element, image);
// Resize the canvas to fit the current viewport element size. Fit the displayed
// image to the canvas dimensions.
cornerstone.resize(element, true);
}
// Remove the data for this viewport from the ViewportLoading object
@ -155,10 +177,6 @@ function loadSeriesIntoViewport(data, templateData) {
// (e.g. hide the progress text box)
endLoadingHandler(element);
// Resize the canvas to fit the current viewport element size. Fit the displayed
// image to the canvas dimensions.
cornerstone.resize(element, true);
// Remove the 'empty' class from the viewport to hide any instruction text
element.classList.remove('empty');

View File

@ -1,6 +1,10 @@
ViewerWindows = new Meteor.Collection(null);
ViewerWindows._debugName = 'ViewerWindows';
Template.imageViewerViewports.onCreated(function() {
WindowManager.init();
});
Template.imageViewerViewports.helpers({
height: function() {
var viewportRows = this.viewportRows || 1;
@ -136,10 +140,9 @@ var savedSeriesData,
savedViewportColumns;
Template.imageViewerViewports.events({
'CornerstoneMouseDoubleClick .imageViewerViewport': function(e) {
var container = $('.viewerMain').get(0);
'dblclick .imageViewerViewport': function(e) {
var data;
var contentId = this.contentId || $('#viewer').parents('.tab-pane.active').attr('id');
var contentId = Session.get('activeContentId');
// If there is more than one viewport on screen
// And one of them is double-clicked, it should be rendered alone
@ -159,8 +162,7 @@ Template.imageViewerViewports.events({
};
// Render the imageViewerViewports template with these settings
$('#imageViewerViewports').remove();
UI.renderWithData(Template.imageViewerViewports, data, container);
WindowManager.updateWindows(data);
// Remove the 'zoomed' class from any viewports
$('.imageViewerViewport').removeClass('zoomed');
@ -191,8 +193,7 @@ Template.imageViewerViewports.events({
};
// Render the imageViewerViewports template with these settings
$('#imageViewerViewports').remove();
UI.renderWithData(Template.imageViewerViewports, data, container);
WindowManager.updateWindows(data);
// Add the 'zoomed' class to the lone remaining viewport
$('.imageViewerViewport').eq(0).addClass('zoomed');

View File

@ -33,8 +33,6 @@ Template.layoutChooser.events({
highlightCells(evt.currentTarget);
},
'click .layoutChooser table td': function(evt) {
$('#imageViewerViewports').remove();
var currentCell = $(evt.currentTarget);
var rowIndex = currentCell.closest('tr').index();
var columnIndex = currentCell.index();
@ -42,10 +40,9 @@ Template.layoutChooser.events({
// Add 1 because the indices start from zero
var data = {
viewportRows: rowIndex + 1,
viewportColumns: columnIndex + 1,
viewportColumns: columnIndex + 1
};
var container = $(".viewerMain").get(0);
UI.renderWithData(Template.imageViewerViewports, data, container);
WindowManager.setLayout(data);
}
});

View File

@ -0,0 +1,273 @@
/**
* This is a temporary function which will return a hardcoded hanging protocol as a JavaScript object
* The purpose of this is to act as a stub until we are actually parsing DICOM Hanging Protocol files,
* since Orthanc doesn't seem to support them yet.
*
* Soon we will be using instanceDataToJsObject to produce this object from the HangingProtocol WADO
* instance
*
*/
function getMammoHangingProtocol() {
var tagValues = {
HangingProtocolStorage: '1.2.840.10008.5.1.4.38.1'
};
var protocol = {
sopClassUid: tagValues.HangingProtocolStorage,
sopInstanceUid: '1.2.840.113986.2.664566.21121125.85669.911', // A random Uid
hangingProtocolName: 'MammoCadProtocol',
hangingProtocolDescription: 'Mammography screening protocol',
hangingProtocolLevel: 'SITE',
hangingProtocolCreator: 'Erik',
hangingProtocolCreationDateTime: '20020101104200',
hangingProtocolDefinitionSequence: [{
modality: 'MG',
laterality: '',
procedureCodeSequence: {
codeValue: 98765,
codingSchemeDesignator: '99Local',
codingSchemeVersion: 1.5,
codeMeaning: 'Mammogram'
},
anatomicRegionSequence: {
codeValue: 'T-D1100',
codingSchemeDesignator: 'SNM3',
codeMeaning: 'BREAST'
},
reasonForRequestedProcedureCodeSequence: {
codeValue: 'I67.1',
codingSchemeDesignator: 'I10',
codeMeaning: 'Calcification'
}
}],
hangingProtocolUserIdentificationCodeSequence: [],
hangingProtocolUserGroupName: 'ABC Hospital',
numberOfPriorsReferenced: 1,
imageSetsSequence: [{
imageSetSelectorSequence: [{
ImageSetSelectorUsageFlag: 'NO_MATCH',
SelectorAttribute: '00180015',
SelectorValueNumber: '1',
SelectorAttributeVR: 'CS',
SelectorCSValue: 'BREAST'
}],
timeBasedImageSetsSequence: [{
ImageSetNumber: 1,
ImageSetSelectorCategory: 'RELATIVE_TIME',
RelativeTime: '0\0',
RelativeTimeUnits: 'MINUTES',
ImageSetLabel: 'Current MG Breast'
},
{
ImageSetNumber: 2,
ImageSetSelectorCategory: 'ABSTRACT_PRIOR',
AbstractPriorValue: '1\\1',
ImageSetLabel: 'Prior MG Breast'
}]
}],
// Skip Number of Screens for now,
// Skip Nominal Screen Definition Sequence for now,
// http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.23.3.html
DisplaySetsSequence: [
{
// Left side image (R MLO Current)
ImageSetNumber: 1,
DisplaySetNumber: 1,
DisplaySetPresentationGroup: 1,
DisplaySetPresentationGroupDescription: 'Current Mediolateral only',
ImageBoxesSequence: [{
DisplayEnvironmentSpatialPosition: '0\0.2\0.16667\0',
ImageBoxNumber: 1,
ImageBoxLayoutType: 'STACK'
}],
FilterOperationsSequence: [{
SelectorAttributeVR: 'CS',
SelectorCSValue: 'R CC',
FilterByCategory: 'SERIES_DESCRIPTION',
FilterByOperator: 'MEMBER_OF'
}]
}, {
// Right side image (L MLO Current)
ImageSetNumber: 1,
DisplaySetNumber: 1,
DisplaySetPresentationGroup: 1,
ImageBoxesSequence: [{
ImageBoxNumber: 1,
ImageBoxLayoutType: 'STACK'
}],
FilterOperationsSequence: [{
SelectorAttributeVR: 'CS',
SelectorCSValue: 'L CC',
FilterByCategory: 'SERIES_DESCRIPTION',
FilterByOperator: 'MEMBER_OF'
}]
},
{
// Left side image (R MLO Current)
ImageSetNumber: 1,
DisplaySetNumber: 1,
DisplaySetPresentationGroup: 2,
DisplaySetPresentationGroupDescription: 'Current Mediolateral only',
ImageBoxesSequence: [{
DisplayEnvironmentSpatialPosition: '0\0.2\0.16667\0',
ImageBoxNumber: 1,
ImageBoxLayoutType: 'STACK'
}],
FilterOperationsSequence: [{
SelectorAttributeVR: 'CS',
SelectorCSValue: 'R MLO',
FilterByCategory: 'SERIES_DESCRIPTION',
FilterByOperator: 'MEMBER_OF'
}],
ReformattingOperationType: '',
ReformattingThickness: '',
ReformattingInterval: '',
ReformattingOperationInitialViewDirection: '',
SortingOperationsSequence: [],
DisplaySetPatientOrientation: '',
VOIType: ''
}, {
// Right side image (L MLO Current)
ImageSetNumber: 1,
DisplaySetNumber: 1,
DisplaySetPresentationGroup: 2,
ImageBoxesSequence: [{
ImageBoxNumber: 1,
ImageBoxLayoutType: 'STACK'
}],
FilterOperationsSequence: [{
SelectorAttributeVR: 'CS',
SelectorCSValue: 'L MLO',
FilterByCategory: 'SERIES_DESCRIPTION',
FilterByOperator: 'MEMBER_OF'
}]
}, {
// Testing purposes
// Left side image (R MLO Current)
ImageSetNumber: 1,
DisplaySetNumber: 1,
DisplaySetPresentationGroup: 3,
DisplaySetPresentationGroupDescription: 'Current Mediolateral only',
ImageBoxesSequence: [{
DisplayEnvironmentSpatialPosition: '0\0.2\0.16667\0',
ImageBoxNumber: 1,
ImageBoxLayoutType: 'STACK'
}],
FilterOperationsSequence: [{
SelectorAttributeVR: 'CS',
SelectorCSValue: 'R MLO',
FilterByCategory: 'SERIES_DESCRIPTION',
FilterByOperator: 'MEMBER_OF'
}]
}, {
// Right side image (R CC Current)
ImageSetNumber: 1,
DisplaySetNumber: 1,
DisplaySetPresentationGroup: 3,
ImageBoxesSequence: [{
ImageBoxNumber: 1,
ImageBoxLayoutType: 'STACK'
}],
FilterOperationsSequence: [{
SelectorAttributeVR: 'CS',
SelectorCSValue: 'R CC',
FilterByCategory: 'SERIES_DESCRIPTION',
FilterByOperator: 'MEMBER_OF'
}]
}],
PartialDataDisplayHandling: 'MAINTAIN_LAYOUT',
SynchronizedScrollingSequence: [],
NavigationIndicatorSequence: []
};
return protocol;
}
function findSetsByPresentationGroup(DisplaySetsSequence, DisplaySetPresentationGroup) {
var presentationGroupSets = [];
DisplaySetsSequence.forEach(function(displaySet) {
if (displaySet.DisplaySetPresentationGroup === DisplaySetPresentationGroup) {
presentationGroupSets.push(displaySet);
}
});
return presentationGroupSets;
}
function findStudy(displaySet, studies) {
// Placeholder for now
return studies.find().fetch()[0].studyInstanceUid;
}
/**
* Uses the FilterOperationsSequence information to find
* the relevant series for display in this display set
*
* @param displaySet
* @param study
* @returns {String} The instance Uid of the series to be displayed
*/
function findSeries(displaySet, study) {
var categoryDictionary = {
SERIES_DESCRIPTION: 'seriesDescription'
};
// TODO=Put these inside a loop? We need to support multiple filter operations,
// not just one
var filterBy = displaySet.FilterOperationsSequence[0].FilterByCategory;
var selectorValue = displaySet.FilterOperationsSequence[0].SelectorCSValue;
var filterType = displaySet.FilterOperationsSequence[0].FilterByOperator;
var category = categoryDictionary[filterBy];
var seriesInstanceUid;
study.seriesList.forEach(function(series) {
// TODO = Add other FilterBy operators
if (filterType === 'MEMBER_OF') {
if (series[category] === selectorValue) {
seriesInstanceUid = series.seriesInstanceUid;
return false;
}
}
});
return seriesInstanceUid;
}
/**
* (Work in progress) Uses the information from a DICOM Hanging Protocol
* to identify and display studies and series in the image viewer
*
* @param hangingProtocol
* @param inputData
* @returns {Array} Array of viewport data to be displayed
*/
function applyDICOMHangingProtocol(hangingProtocol, inputData) {
log.info('applyDICOMHangingProtocol');
var presentationGroup = inputData.DisplaySetPresentationGroup || 1;
var studies = ViewerStudies;
var currentDisplaySets = findSetsByPresentationGroup(hangingProtocol.DisplaySetsSequence, presentationGroup);
var viewportData = {
viewports: []
};
currentDisplaySets.forEach(function(displaySet, index) {
// TODO= Find study information by image set properties and
// image set number of this display set
var ImageSetNumber = displaySet.ImageSetNumber;
studyInstanceUid = findStudy(displaySet, studies);
var study = ViewerStudies.findOne({studyInstanceUid: studyInstanceUid});
seriesInstanceUid = findSeries(displaySet, study);
viewportData.viewports[index] = {
seriesInstanceUid: seriesInstanceUid,
studyInstanceUid: studyInstanceUid,
currentImageIdIndex: 0
};
});
return viewportData;
}

View File

@ -0,0 +1,172 @@
/**
* This is a temporary function which will return a hardcoded hanging protocol as a JavaScript object
*/
getMammoHangingProtocolObject = function() {
var protocol = {
// Need to expand this to allow real searches for studies
studiesNeeded: [
'current',
'prior'
],
primaryModality: 'MG',
stages: [{
stage: 1,
rows: 2,
columns: 4,
viewports: [{
seriesDescription: 'RCC',
study: 'prior'
}, {
seriesDescription: 'LCC',
study: 'prior'
}, {
seriesDescription: 'RMLO',
study: 'prior'
}, {
seriesDescription: 'LMLO',
study: 'prior'
}, {
seriesDescription: 'RCC',
study: 'current'
}, {
seriesDescription: 'LCC',
study: 'current'
}, {
seriesDescription: 'RMLO',
study: 'current'
}, {
seriesDescription: 'LMLO',
study: 'current'
}
]
}, {
stage: 2,
rows: 1,
columns: 2,
viewports: [{
seriesDescription: 'RCC',
study: 'current'
}, {
seriesDescription: 'LCC',
study: 'current'
}
]
}, {
stage: 3,
rows: 1,
columns: 2,
viewports: [{
seriesDescription: 'RMLO',
study: 'current'
}, {
seriesDescription: 'LMLO',
study: 'current'
}
]
}, {
stage: 4,
rows: 1,
columns: 2,
viewports: [{
seriesDescription: 'RCC',
study: 'current'
}, {
seriesDescription: 'RCC',
study: 'prior'
}
]
}, {
stage: 5,
rows: 1,
columns: 2,
viewports: [{
seriesDescription: 'LCC',
study: 'current'
}, {
seriesDescription: 'LCC',
study: 'prior'
}
]
}, {
stage: 6,
rows: 1,
columns: 2,
viewports: [{
seriesDescription: 'LMLO',
study: 'current'
}, {
seriesDescription: 'LMLO',
study: 'prior'
}
]
}, {
stage: 7,
rows: 1,
columns: 2,
viewports: [{
seriesDescription: 'RMLO',
study: 'current'
}, {
seriesDescription: 'RMLO',
study: 'prior'
}
]
}, {
stage: 8,
rows: 2,
columns: 4,
viewports: [{
seriesDescription: 'RCC',
study: 'prior',
options: {
includeCADMarkers: true
}
}, {
seriesDescription: 'LCC',
study: 'prior',
options: {
includeCADMarkers: true
}
}, {
seriesDescription: 'RMLO',
study: 'prior',
options: {
includeCADMarkers: true
}
}, {
seriesDescription: 'LMLO',
study: 'prior',
options: {
includeCADMarkers: true
}
}, {
seriesDescription: 'RCC',
study: 'current',
options: {
includeCADMarkers: true
}
}, {
seriesDescription: 'LCC',
study: 'current',
options: {
includeCADMarkers: true
}
}, {
seriesDescription: 'RMLO',
study: 'current',
options: {
includeCADMarkers: true
}
}, {
seriesDescription: 'LMLO',
study: 'current',
options: {
includeCADMarkers: true
}
}
]
}]
};
return protocol;
};

View File

@ -0,0 +1,14 @@
var modalityViewportSettingsFunctions = {};
getModalityDefaultViewport = function(series, enabledElement, imageId) {
var modality = series.modality;
if (!modalityViewportSettingsFunctions[modality]) {
return;
}
return modalityViewportSettingsFunctions[modality](series, enabledElement, imageId);
};
setModalityDefaultViewportFunction = function(modality, fn) {
modalityViewportSettingsFunctions[modality] = fn;
};

File diff suppressed because it is too large Load Diff

View File

@ -142,10 +142,13 @@ Package.onUse(function(api) {
api.addFiles('lib/updateAllViewports.js', 'client');
api.addFiles('lib/exportStudies.js', 'client');
api.addFiles('lib/encodeQueryData.js', 'server');
api.addFiles('lib/modalityDefaultViewport.js', 'client');
api.addFiles('lib/hangingprotocols.js', 'client');
//api.export('accountsConfig', 'client');
api.export('resizeViewportElements', 'client');
api.export('handleResize', 'client');
api.export('addMetaData', 'client');
api.export('resizeViewportElements','client');
api.export('handleResize','client');
api.export('enableHotkeys', 'client');
api.export('enablePrefetchOnElement', 'client');
api.export('displayReferenceLines', 'client');
@ -161,6 +164,10 @@ Package.onUse(function(api) {
api.export('updateAllViewports', 'client');
api.export('exportStudies', 'client');
api.export('encodeQueryData', 'server');
api.export('getMammoHangingProtocolObject', 'client');
api.export('getModalityDefaultViewport', 'client');
api.export('setModalityDefaultViewportFunction', 'client');
api.export('ViewerWindows', 'client');
// Viewer management objects
api.export('toolManager', 'client');
@ -209,4 +216,3 @@ Package.onUse(function(api) {
api.export('Services', 'server');
});

View File

@ -45,4 +45,4 @@ Services.DIMSE.Studies = function(filter) {
var results = DIMSE.retrieveStudies(parameters);
return resultDataToStudies(results);
};
};