Removing ohif-cornerstone as a dependency for ohif-viewerbase (Final step)

This commit is contained in:
Eloízio Salgado 2017-01-24 14:35:56 -02:00 committed by Emanuel F. Oliveira
parent b866e6e6c7
commit c2c8a43c3a
18 changed files with 436 additions and 370 deletions

View File

@ -6,6 +6,7 @@ import { _ } from 'meteor/underscore';
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core';
import 'meteor/ohif:cornerstone';
import 'meteor/ohif:viewerbase';
import 'meteor/ohif:metadata';
@ -15,6 +16,15 @@ Meteor.startup(() => {
Session.set('MeasurementsReady', false);
OHIF.viewer.stackImagePositionOffsetSynchronizer = new OHIF.viewerbase.StackImagePositionOffsetSynchronizer();
// Create the synchronizer used to update reference lines
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer('CornerstoneNewImage', cornerstoneTools.updateImageSynchronizer);
OHIF.viewer.metadataProvider = OHIF.cornerstone.metadataProvider;
// Metadata configuration
const metadataProvider = OHIF.viewer.metadataProvider;
cornerstoneTools.metaData.addProvider(metadataProvider.provider.bind(metadataProvider));
});
Template.viewer.onCreated(() => {

View File

@ -4,6 +4,7 @@ import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';
import { OHIF } from 'meteor/ohif:core';
import 'meteor/ohif:cornerstone';
import 'meteor/ohif:viewerbase';
import 'meteor/ohif:metadata';
@ -31,6 +32,15 @@ Meteor.startup(() => {
};
OHIF.viewer.stackImagePositionOffsetSynchronizer = new OHIF.viewerbase.StackImagePositionOffsetSynchronizer();
// Create the synchronizer used to update reference lines
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer('CornerstoneNewImage', cornerstoneTools.updateImageSynchronizer);
OHIF.viewer.metadataProvider = OHIF.cornerstone.metadataProvider;
// Metadata configuration
const metadataProvider = OHIF.viewer.metadataProvider;
cornerstoneTools.metaData.addProvider(metadataProvider.provider.bind(metadataProvider));
});

View File

@ -8,7 +8,8 @@ const OHIF = {
log: {},
ui: {},
utils: {},
viewer: {}
viewer: {},
cornerstone: {}
};
// Expose the OHIF object to the client if it is on development mode

View File

@ -0,0 +1,4 @@
import { Cornerstone } from '../namespace';
import { MetadataProvider } from './lib/classes/MetadataProvider';
Cornerstone.metadataProvider = new MetadataProvider();

View File

@ -0,0 +1,257 @@
import { parsingUtils } from '../parsingUtils';
export class MetadataProvider {
constructor() {
this.metadataLookup = {};
}
/**
* Cornerstone Metadata provider to store image meta data
* Data from instances, series, and studies are associated with
* imageIds to facilitate usage of this information by Cornerstone's Tools
*
* e.g. the imagePlane metadata object contains instance information about
* row/column pixel spacing, patient position, and patient orientation. It
* is used in CornerstoneTools to position reference lines and orientation markers.
*
* @param {String} imageId The Cornerstone ImageId
* @param {Object} data An object containing instance, series, and study metadata
*/
addMetadata(imageId, data) {
const instanceMetadata = data.instance;
const seriesMetadata = data.series;
const studyMetadata = data.study;
const imageIndex = data.imageIndex;
const numImages = data.numImages;
const metadata = {};
metadata.study = {
patientId: studyMetadata.patientId,
studyInstanceUid: studyMetadata.studyInstanceUid,
studyDate: studyMetadata.studyDate,
studyTime: studyMetadata.studyTime,
studyDescription: studyMetadata.studyDescription,
institutionName: studyMetadata.institutionName,
patientHistory: studyMetadata.patientHistory
};
metadata.series = {
seriesDescription: seriesMetadata.seriesDescription,
seriesNumber: seriesMetadata.seriesNumber,
modality: seriesMetadata.modality,
seriesInstanceUid: seriesMetadata.seriesInstanceUid,
numImages: numImages
};
metadata.instance = instanceMetadata;
metadata.patient = {
name: studyMetadata.patientName,
id: studyMetadata.patientId,
birthDate: studyMetadata.patientBirthDate,
sex: studyMetadata.patientSex
};
// If there is sufficient information, populate
// the imagePlane object for easier use in the Viewer
metadata.imagePlane = this.getImagePlane(instanceMetadata);
// Add the metadata to the imageId lookup object
this.metadataLookup[imageId] = metadata;
}
/**
* Adds a set of metadata to the Cornerstone metadata provider given a specific
* imageId, type, and dataset
*
* @param imageId
* @param type (e.g. series, instance, tagDisplay)
* @param data
*/
addSpecificMetadata(imageId, type, data) {
const metadata = {};
metadata[type] = data;
this.metadataLookup[imageId] = $.extend(this.metadataLookup[imageId], metadata);
}
getFromDataSet(dataSet, type, tag) {
if (!dataSet) {
return;
}
const fn = dataSet[type];
if (!fn) {
return;
}
return fn.call(dataSet, tag);
}
/**
* Updates the related metadata for missing fields given a specified image
*
* @param image
*/
updateMetadata(image) {
const imageMetadata = this.metadataLookup[image.imageId];
if (!imageMetadata) {
return;
}
imageMetadata.instance.rows = imageMetadata.instance.rows || image.rows;
imageMetadata.instance.columns = imageMetadata.instance.columns || image.columns;
imageMetadata.instance.sopClassUid = imageMetadata.instance.sopClassUid || this.getFromDataSet(image.data, 'string', 'x00080016');
imageMetadata.instance.sopInstanceUid = imageMetadata.instance.sopInstanceUid || this.getFromDataSet(image.data, 'string', 'x00080018');
imageMetadata.instance.pixelSpacing = imageMetadata.instance.pixelSpacing || this.getFromDataSet(image.data, 'string', 'x00280030');
imageMetadata.instance.frameOfReferenceUID = imageMetadata.instance.frameOfReferenceUID || this.getFromDataSet(image.data, 'string', 'x00200052');
imageMetadata.instance.imageOrientationPatient = imageMetadata.instance.imageOrientationPatient || this.getFromDataSet(image.data, 'string', 'x00200037');
imageMetadata.instance.imagePositionPatient = imageMetadata.instance.imagePositionPatient || this.getFromDataSet(image.data, 'string', 'x00200032');
imageMetadata.instance.sliceThickness = imageMetadata.instance.sliceThickness || this.getFromDataSet(image.data, 'string', 'x00180050');
imageMetadata.instance.sliceLocation = imageMetadata.instance.sliceLocation || this.getFromDataSet(image.data, 'string', 'x00201041');
imageMetadata.instance.tablePosition = imageMetadata.instance.tablePosition || this.getFromDataSet(image.data, 'string', 'x00189327');
imageMetadata.instance.spacingBetweenSlices = imageMetadata.instance.spacingBetweenSlices || this.getFromDataSet(image.data, 'string', 'x00180088');
imageMetadata.instance.lossyImageCompression = imageMetadata.instance.lossyImageCompression || this.getFromDataSet(image.data, 'string', 'x00282110');
imageMetadata.instance.lossyImageCompressionRatio = imageMetadata.instance.lossyImageCompressionRatio || this.getFromDataSet(image.data, 'string', 'x00282112');
imageMetadata.instance.frameIncrementPointer = imageMetadata.instance.frameIncrementPointer || this.getFromDataSet(image.data, 'string', 'x00280009');
imageMetadata.instance.frameTime = imageMetadata.instance.frameTime || this.getFromDataSet(image.data, 'string', 'x00181063');
imageMetadata.instance.frameTimeVector = imageMetadata.instance.frameTimeVector || this.getFromDataSet(image.data, 'string', 'x00181065');
if (image.data && !imageMetadata.instance.multiframeMetadata) {
imageMetadata.instance.multiframeMetadata = this.getMultiframeModuleMetadata(image.data);
}
imageMetadata.imagePlane = imageMetadata.imagePlane || this.getImagePlane(imageMetadata.instance);
}
/**
* Constructs and returns the imagePlane given the metadata instance
*
* @param metadataInstance The metadata instance (InstanceMetadata class) containing information to construct imagePlane
* @returns imagePlane The constructed imagePlane to be used in viewer easily
*/
getImagePlane(instance) {
if (!instance.rows || !instance.columns || !instance.pixelSpacing ||
!instance.frameOfReferenceUID || !instance.imageOrientationPatient ||
!instance.imagePositionPatient) {
return;
}
const imageOrientation = instance.imageOrientationPatient.split('\\');
const imagePosition = instance.imagePositionPatient.split('\\');
let columnPixelSpacing = 1.0;
let rowPixelSpacing = 1.0;
if (instance.pixelSpacing) {
const split = instance.pixelSpacing.split('\\');
rowPixelSpacing = parseFloat(split[0]);
columnPixelSpacing = parseFloat(split[1]);
}
return {
frameOfReferenceUID:
instance.frameOfReferenceUID,
rows:
instance.rows,
columns:
instance.columns,
rowCosines:
new cornerstoneMath.Vector3(parseFloat(imageOrientation[0]), parseFloat(imageOrientation[1]), parseFloat(imageOrientation[2])),
columnCosines:
new cornerstoneMath.Vector3(parseFloat(imageOrientation[3]), parseFloat(imageOrientation[4]), parseFloat(imageOrientation[5])),
imagePositionPatient:
new cornerstoneMath.Vector3(parseFloat(imagePosition[0]), parseFloat(imagePosition[1]), parseFloat(imagePosition[2])),
rowPixelSpacing:
rowPixelSpacing,
columnPixelSpacing:
columnPixelSpacing,
};
}
/**
* This function extracts miltiframe information from a dicomParser.DataSet object.
*
* @param dataSet {Object} An instance of dicomParser.DataSet object where multiframe information can be found.
* @return {Object} An object containing multiframe image metadata (frameIncrementPointer, frameTime, frameTimeVector, etc).
*/
getMultiframeModuleMetadata(dataSet) {
const imageInfo = {
isMultiframeImage: false,
frameIncrementPointer: null,
numberOfFrames: 0,
frameTime: 0,
frameTimeVector: null,
averageFrameRate: 0 // backwards compatibility only... it might be useless in the future
};
let frameTime;
if (parsingUtils.isValidDataSet(dataSet)) {
// (0028,0008) = Number of Frames
const numberOfFrames = dataSet.intString('x00280008', -1);
if (numberOfFrames > 0) {
// set multi-frame image indicator
imageInfo.isMultiframeImage = true;
imageInfo.numberOfFrames = numberOfFrames;
// (0028,0009) = Frame Increment Pointer
const frameIncrementPointer = parsingUtils.attributeTag(dataSet, 'x00280009') || '';
if (frameIncrementPointer === 'x00181065') {
// Frame Increment Pointer points to Frame Time Vector (0018,1065) field
const frameTimeVector = parsingUtils.floatArray(dataSet, 'x00181065');
if (frameTimeVector instanceof Array && frameTimeVector.length > 0) {
imageInfo.frameIncrementPointer = 'frameTimeVector';
imageInfo.frameTimeVector = frameTimeVector;
frameTime = frameTimeVector.reduce((a, b) => a + b) / frameTimeVector.length;
imageInfo.averageFrameRate = 1000 / frameTime;
}
} else if (frameIncrementPointer === 'x00181063' || frameIncrementPointer === '') {
// Frame Increment Pointer points to Frame Time (0018,1063) field or is not defined (for addtional flexibility).
// Yet another value is possible for this field (5200,9230 for Multi-frame Functional Groups)
// but that case is currently not supported.
frameTime = dataSet.floatString('x00181063', -1);
if (frameTime > 0) {
imageInfo.frameIncrementPointer = 'frameTime';
imageInfo.frameTime = frameTime;
imageInfo.averageFrameRate = 1000 / frameTime;
}
}
}
}
return imageInfo;
}
/**
* Looks up metadata for Cornerstone Tools given a specified type and imageId
* A type may be, e.g. 'study', or 'patient', or 'imagePlane'. These types
* are keys in the stored metadata objects.
*
* @param type
* @param imageId
* @returns {Object} Relevant metadata of the specified type
*/
provider(type, imageId) {
const imageMetadata = this.metadataLookup[imageId];
if (!imageMetadata) {
return;
}
if (imageMetadata.hasOwnProperty(type)) {
return imageMetadata[type];
}
}
};

View File

@ -1,4 +1,4 @@
import {dicomParser} from 'meteor/ohif:cornerstone';
import { dicomParser } from 'meteor/ohif:cornerstone';
/**
* A small set of utilities to help parsing DICOM element values.

View File

@ -0,0 +1,13 @@
/**
* Import namespace...
*/
import { OHIF, Cornerstone } from './namespace.js';
/**
* Import scripts that will populate the Cornerstone namespace as a side effect only import. This is effectively the public API...
*/
import './client/'; // which is actually: import './client/index.js';
export { OHIF, Cornerstone };

View File

@ -0,0 +1,25 @@
/**
* Import main dependency
*/
import { OHIF } from 'meteor/ohif:core';
/**
* Append Cornerstone namespace
*/
const Cornerstone = {
...OHIF.cornerstone
};
/**
* Append Cornerstone namespace to OHIF namespace
*/
OHIF.cornerstone = Cornerstone;
/**
* Export relevant objects
*/
export { OHIF, Cornerstone };

View File

@ -7,8 +7,12 @@ Package.describe({
Package.onUse(function(api) {
api.versionsFrom('1.4');
api.use('ecmascript');
api.use('jquery');
api.use('ohif:core');
api.addFiles('client/cornerstone.js', 'client', {
bare: true
});
@ -42,4 +46,6 @@ Package.onUse(function(api) {
api.export('cornerstoneTools', 'client');
api.export('cornerstoneWADOImageLoader', 'client');
api.export('dicomParser', 'client');
api.mainModule('main.js', 'client');
});

View File

@ -14,7 +14,6 @@ import { updateCrosshairsSynchronizer } from '../../../lib/updateCrosshairsSynch
import { toolManager } from '../../../lib/toolManager';
import { updateOrientationMarkers } from '../../../lib/updateOrientationMarkers';
import { getInstanceClassDefaultViewport } from '../../../lib/instanceClassSpecificViewport';
import { updateMetaData } from '../../../lib/metaDataProvider';
import { OHIFError } from '../../../lib/classes/OHIFError';
const allCornerstoneEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownActivate ' +
@ -110,7 +109,7 @@ const loadDisplaySetIntoViewport = (data, templateData) => {
// The ViewportLoading object relates the viewport elements with whichever
// image is currently being loaded into them. This is useful so that we can
// place progress (download %) for each image inside the proper viewports.
ViewportLoading[viewportIndex] = imageId;
window.ViewportLoading[viewportIndex] = imageId;
// Enable Cornerstone for the viewport element
const options = {
@ -159,6 +158,14 @@ const loadDisplaySetIntoViewport = (data, templateData) => {
}
}
// Additional tasks for metadata provider. If using your own
// metadata provider, this may not be necessary.
// updateMetadata is important, though, to update image metadata that
// for any reason was missing some information such as rows, columns,
// sliceThickness, etc (See MetadataProvider class from ohif-cornerstone package)
const metadataProvider = OHIF.viewer.metadataProvider;
const isUpdateMetadataDefined = metadataProvider && typeof metadataProvider.updateMetadata === 'function';
// loadAndCacheImage configurable callbacks
const callbacks = imageViewerViewportData.callbacks;
@ -188,8 +195,10 @@ const loadDisplaySetIntoViewport = (data, templateData) => {
enabledElement.image = image;
enabledElement.viewport = cornerstone.getDefaultViewport(enabledElement.canvas, image);
// Update the metaData for missing fields
updateMetaData(image);
if (isUpdateMetadataDefined) {
// Update the metaData for missing fields
metadataProvider.updateMetadata(image);
}
// Check if there are default viewport settings for this sopClassUid
if (!displaySet.images || !displaySet.images.length) {
@ -230,7 +239,7 @@ const loadDisplaySetIntoViewport = (data, templateData) => {
// Remove the data for this viewport from the ViewportLoading object
// This will stop the loading percentage complete from being displayed.
delete ViewportLoading[viewportIndex];
delete window.ViewportLoading[viewportIndex];
// Call the handler function that represents the end of the image loading phase
// (e.g. hide the progress text box)
@ -330,8 +339,10 @@ const loadDisplaySetIntoViewport = (data, templateData) => {
// since this callback function is called multiple times (eg: when a tool is
// enabled/disabled -> cornerstone[toolName].tool.enable)
// Update the metaData for missing fields
updateMetaData(eventData.enabledElement.image);
if(isUpdateMetadataDefined) {
// Update the metaData for missing fields
metadataProvider.updateMetadata(eventData.enabledElement.image);
}
// Update the templateData with the new imageId
// This allows the template helpers to update reactively
@ -507,43 +518,9 @@ const setDisplaySet = (data, displaySetInstanceUid, templateData) => {
loadDisplaySetIntoViewport(data, templateData);
};
/**
* This function searches an object to return the keys that contain a specific value
*
* @param object {object} The object to be searched
* @param value The value to be found
*
* @returns {array} The keys for which the object has the specified value
*/
const getKeysByValue = (object, value) => {
// http://stackoverflow.com/questions/9907419/javascript-object-get-key-by-value
return Object.keys(object).filter(key => object[key] === value);
};
Meteor.startup(function() {
// On Meteor startup, define the global objects used to store loading imageIds
// by viewport / thumbnail element
ViewportLoading = {};
// Whenever the CornerstoneImageLoadProgress is fired, identify which viewports
// the "in-progress" image is to be displayed in. Then pass the percent complete
// via the Meteor Session to the other templates to be displayed in the relevant viewports.
$(cornerstone).on('CornerstoneImageLoadProgress', function(e, eventData) {
viewportIndices = getKeysByValue(ViewportLoading, eventData.imageId);
viewportIndices.forEach(function(viewportIndex) {
Session.set('CornerstoneLoadProgress' + viewportIndex, eventData.percentComplete);
});
const encodedId = OHIF.string.encodeId(eventData.imageId);
Session.set('CornerstoneThumbnailLoadProgress' + encodedId, eventData.percentComplete);
});
const config = {
magnifySize: 300,
magnificationLevel: 3
};
cornerstoneTools.magnify.setConfiguration(config);
Meteor.startup(() => {
window.ViewportLoading = window.ViewportLoading || {};
toolManager.configureLoadProcess();
});
Template.imageViewerViewport.onRendered(function() {

View File

@ -3,36 +3,41 @@ import { Template } from 'meteor/templating';
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core';
Meteor.startup(function() {
cornerstoneTools.loadHandlerManager.setStartLoadHandler(startLoadingHandler);
cornerstoneTools.loadHandlerManager.setEndLoadHandler(doneLoadingHandler);
cornerstoneTools.loadHandlerManager.setErrorLoadingHandler(errorLoadingHandler);
Meteor.startup(() => {
// This checking is necessary because cornerstoneTools may not have some tools available.
// Example: when an app defines its own cornerstone's lib versions, so it
// uses only ohif-viewerbase and not ohif-cornerstone and those libs are added later.
if (cornerstoneTools.loadHandlerManager) {
cornerstoneTools.loadHandlerManager.setStartLoadHandler(startLoadingHandler);
cornerstoneTools.loadHandlerManager.setEndLoadHandler(doneLoadingHandler);
cornerstoneTools.loadHandlerManager.setErrorLoadingHandler(errorLoadingHandler);
}
});
var loadHandlerTimeout;
let loadHandlerTimeout;
const startLoadingHandler = function(element) {
const startLoadingHandler = element => {
clearTimeout(loadHandlerTimeout);
loadHandlerTimeout = setTimeout(function() {
loadHandlerTimeout = setTimeout(() => {
console.log('startLoading');
var elem = $(element);
const elem = $(element);
elem.siblings('.imageViewerErrorLoadingIndicator').css('display', 'none');
elem.find('canvas').not('.magnifyTool').addClass('faded');
elem.siblings('.imageViewerLoadingIndicator').css('display', 'block');
}, OHIF.viewer.loadIndicatorDelay);
};
const doneLoadingHandler = function(element) {
const doneLoadingHandler = element => {
clearTimeout(loadHandlerTimeout);
var elem = $(element);
const elem = $(element);
elem.siblings('.imageViewerErrorLoadingIndicator').css('display', 'none');
elem.find('canvas').not('.magnifyTool').removeClass('faded');
elem.siblings('.imageViewerLoadingIndicator').css('display', 'none');
};
const errorLoadingHandler = function(element, imageId, error, source) {
const errorLoadingHandler = (element, imageId, error, source) => {
clearTimeout(loadHandlerTimeout);
var elem = $(element);
const elem = $(element);
// Could probably chain all of these, but this is more readable
elem.find('canvas').not('.magnifyTool').removeClass('faded');
@ -43,14 +48,14 @@ const errorLoadingHandler = function(element, imageId, error, source) {
return;
}
var errorLoadingIndicator = elem.siblings('.imageViewerErrorLoadingIndicator');
const errorLoadingIndicator = elem.siblings('.imageViewerErrorLoadingIndicator');
errorLoadingIndicator.css('display', 'block');
// This is just used to expand upon some error messages that are sent
// when things fail. An example is a network error throwing the error
// which is only described as "network".
var errorDetails = {
network: "A network error has occurred"
const errorDetails = {
network: 'A network error has occurred'
// We need to expand this further when we see more obscure error messages
};
@ -58,17 +63,17 @@ const errorLoadingHandler = function(element, imageId, error, source) {
error = errorDetails[error];
}
errorLoadingIndicator.find('.description').text("An error has occurred while loading image: " + imageId);
errorLoadingIndicator.find('.description').text(`An error has occurred while loading image: ${imageId}`);
if (error) {
errorLoadingIndicator.find('.details').text("Details: " + error);
errorLoadingIndicator.find('.details').text(`Details: ${error}`);
}
};
Template.loadingIndicator.helpers({
'percentComplete'() {
var percentComplete = Session.get('CornerstoneLoadProgress' + this.viewportIndex);
const percentComplete = Session.get('CornerstoneLoadProgress' + this.viewportIndex);
if (percentComplete && percentComplete !== 100) {
return percentComplete + '%';
return `${percentComplete}%`;
}
}
});

View File

@ -10,9 +10,6 @@ import { ResizeViewportManager } from '../../../lib/classes/ResizeViewportManage
import { LayoutManager } from '../../../lib/classes/LayoutManager';
Meteor.startup(() => {
// Create the synchronizer used to update reference lines
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer('CornerstoneNewImage', cornerstoneTools.updateImageSynchronizer);
window.ResizeViewportManager = window.ResizeViewportManager || new ResizeViewportManager();
});

View File

@ -76,16 +76,6 @@ import { getInstanceClassDefaultViewport, setInstanceClassDefaultViewportFunctio
Viewerbase.getInstanceClassDefaultViewport = getInstanceClassDefaultViewport;
Viewerbase.setInstanceClassDefaultViewportFunction = setInstanceClassDefaultViewportFunction;
// setMammogramViewportAlignment
import { setMammogramViewportAlignment } from './lib/setMammogramViewportAlignment';
Viewerbase.setMammogramViewportAlignment = setMammogramViewportAlignment;
// addMetaData, addSpecificMetadata, updateMetaData
import { addMetaData, addSpecificMetadata, updateMetaData } from './lib/metaDataProvider';
Viewerbase.addMetaData = addMetaData;
Viewerbase.addSpecificMetadata = addSpecificMetadata;
Viewerbase.updateMetaData = updateMetaData;
// displayReferenceLines
import { displayReferenceLines } from './lib/displayReferenceLines';
Viewerbase.displayReferenceLines = displayReferenceLines;

View File

@ -1,6 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
import { getImageId } from './getImageId';
import { addMetaData } from './metaDataProvider';
import { OHIFError } from './classes/OHIFError';
let stackMap = {};
@ -17,6 +16,7 @@ const stackUpdatedCallbacks = [];
* @return {Array} Array with image IDs
*/
function createAndAddStack(stackMap, study, displaySet) {
const metadataProvider = OHIF.viewer.metadataProvider;
const numImages = displaySet.images.length;
const imageIds = [];
let imageId;
@ -38,13 +38,13 @@ function createAndAddStack(stackMap, study, displaySet) {
metaData.frame = i;
imageId = getImageId(image, i);
imageIds.push(imageId);
addMetaData(imageId, metaData);
metadataProvider.addMetadata(imageId, metaData);
}
}
else {
imageId = getImageId(image);
imageIds.push(imageId);
addMetaData(imageId, metaData);
metadataProvider.addMetadata(imageId, metaData);
}
});

View File

@ -1,266 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { parsingUtils } from './parsingUtils';
const metaDataLookup = {};
/**
* Cornerstone MetaData provider to store image meta data
* Data from instances, series, and studies are associated with
* imageIds to facilitate usage of this information by Cornerstone's Tools
*
* e.g. the imagePlane metaData object contains instance information about
* row/column pixel spacing, patient position, and patient orientation. It
* is used in CornerstoneTools to position reference lines and orientation markers.
*
* @param {String} imageId The Cornerstone ImageId
* @param {Object} data An object containing instance, series, and study metaData
*/
const addMetaData = (imageId, data) => {
let instanceMetaData = data.instance;
let seriesMetaData = data.series;
let studyMetaData = data.study;
let imageIndex = data.imageIndex;
let numImages = data.numImages;
let metaData = {};
metaData.study = {
patientId: studyMetaData.patientId,
studyInstanceUid: studyMetaData.studyInstanceUid,
studyDate: studyMetaData.studyDate,
studyTime: studyMetaData.studyTime,
studyDescription: studyMetaData.studyDescription,
institutionName: studyMetaData.institutionName,
patientHistory: studyMetaData.patientHistory
};
metaData.series = {
seriesDescription: seriesMetaData.seriesDescription,
seriesNumber: seriesMetaData.seriesNumber,
modality: seriesMetaData.modality,
seriesInstanceUid: seriesMetaData.seriesInstanceUid,
numImages: numImages
};
metaData.instance = instanceMetaData;
metaData.patient = {
name: studyMetaData.patientName,
id: studyMetaData.patientId,
birthDate: studyMetaData.patientBirthDate,
sex: studyMetaData.patientSex
};
// If there is sufficient information, populate
// the imagePlane object for easier use in the Viewer
metaData.imagePlane = getImagePlane(instanceMetaData);
// Add the metaData to the imageId lookup object
metaDataLookup[imageId] = metaData;
};
/**
* Adds a set of metaData to the Cornerstone metaData provider given a specific
* imageId, type, and dataset
*
* @param imageId
* @param type (e.g. series, instance, tagDisplay)
* @param data
*/
const addSpecificMetadata = (imageId, type, data) => {
let metaData = {};
metaData[type] = data;
metaDataLookup[imageId] = $.extend(metaDataLookup[imageId], metaData);
};
function getFromDataSet(dataSet, type, tag) {
if (!dataSet) {
return;
}
const fn = dataSet[type];
if (!fn) {
return;
}
return fn.call(dataSet, tag);
}
/**
* Updates the related metaData for missing fields given a specified image
*
* @param image
*/
const updateMetaData = image => {
const imageMetaData = metaDataLookup[image.imageId];
if (!imageMetaData) {
return;
}
imageMetaData.instance.rows = imageMetaData.instance.rows || image.rows;
imageMetaData.instance.columns = imageMetaData.instance.columns || image.columns;
imageMetaData.instance.sopClassUid = imageMetaData.instance.sopClassUid || getFromDataSet(image.data, 'string', 'x00080016');
imageMetaData.instance.sopInstanceUid = imageMetaData.instance.sopInstanceUid || getFromDataSet(image.data, 'string', 'x00080018');
imageMetaData.instance.pixelSpacing = imageMetaData.instance.pixelSpacing || getFromDataSet(image.data, 'string', 'x00280030');
imageMetaData.instance.frameOfReferenceUID = imageMetaData.instance.frameOfReferenceUID || getFromDataSet(image.data, 'string', 'x00200052');
imageMetaData.instance.imageOrientationPatient = imageMetaData.instance.imageOrientationPatient || getFromDataSet(image.data, 'string', 'x00200037');
imageMetaData.instance.imagePositionPatient = imageMetaData.instance.imagePositionPatient || getFromDataSet(image.data, 'string', 'x00200032');
imageMetaData.instance.sliceThickness = imageMetaData.instance.sliceThickness || getFromDataSet(image.data, 'string', 'x00180050');
imageMetaData.instance.sliceLocation = imageMetaData.instance.sliceLocation || getFromDataSet(image.data, 'string', 'x00201041');
imageMetaData.instance.tablePosition = imageMetaData.instance.tablePosition || getFromDataSet(image.data, 'string', 'x00189327');
imageMetaData.instance.spacingBetweenSlices = imageMetaData.instance.spacingBetweenSlices || getFromDataSet(image.data, 'string', 'x00180088');
imageMetaData.instance.lossyImageCompression = imageMetaData.instance.lossyImageCompression || getFromDataSet(image.data, 'string', 'x00282110');
imageMetaData.instance.lossyImageCompressionRatio = imageMetaData.instance.lossyImageCompressionRatio || getFromDataSet(image.data, 'string', 'x00282112');
imageMetaData.instance.frameIncrementPointer = imageMetaData.instance.frameIncrementPointer || getFromDataSet(image.data, 'string', 'x00280009');
imageMetaData.instance.frameTime = imageMetaData.instance.frameTime || getFromDataSet(image.data, 'string', 'x00181063');
imageMetaData.instance.frameTimeVector = imageMetaData.instance.frameTimeVector || getFromDataSet(image.data, 'string', 'x00181065');
if (image.data && !imageMetaData.instance.multiframeMetadata) {
imageMetaData.instance.multiframeMetadata = getMultiframeModuleMetaData(image.data);
}
imageMetaData.imagePlane = imageMetaData.imagePlane || getImagePlane(imageMetaData.instance);
};
/**
* Constructs and returns the imagePlane given the metadata instance
*
* @param metadataInstance The metadata instance (InstanceMetadata class) containing information to construct imagePlane
* @returns imagePlane The constructed imagePlane to be used in viewer easily
*/
function getImagePlane(instance) {
if (!instance.rows || !instance.columns || !instance.pixelSpacing ||
!instance.frameOfReferenceUID || !instance.imageOrientationPatient ||
!instance.imagePositionPatient) {
return;
}
const imageOrientation = instance.imageOrientationPatient.split('\\');
const imagePosition = instance.imagePositionPatient.split('\\');
let columnPixelSpacing = 1.0;
let rowPixelSpacing = 1.0;
if (instance.pixelSpacing) {
const split = instance.pixelSpacing.split('\\');
rowPixelSpacing = parseFloat(split[0]);
columnPixelSpacing = parseFloat(split[1]);
}
return {
frameOfReferenceUID:
instance.frameOfReferenceUID,
rows:
instance.rows,
columns:
instance.columns,
rowCosines:
new cornerstoneMath.Vector3(parseFloat(imageOrientation[0]), parseFloat(imageOrientation[1]), parseFloat(imageOrientation[2])),
columnCosines:
new cornerstoneMath.Vector3(parseFloat(imageOrientation[3]), parseFloat(imageOrientation[4]), parseFloat(imageOrientation[5])),
imagePositionPatient:
new cornerstoneMath.Vector3(parseFloat(imagePosition[0]), parseFloat(imagePosition[1]), parseFloat(imagePosition[2])),
rowPixelSpacing:
rowPixelSpacing,
columnPixelSpacing:
columnPixelSpacing,
};
}
/**
* This function extracts miltiframe information from a dicomParser.DataSet object.
*
* @param dataSet {Object} An instance of dicomParser.DataSet object where multiframe information can be found.
* @return {Object} An object containing multiframe image metadata (frameIncrementPointer, frameTime, frameTimeVector, etc).
*/
function getMultiframeModuleMetaData(dataSet) {
let numberOfFrames,
frameIncrementPointer,
frameTime,
frameTimeVector,
imageInfo = {
isMultiframeImage: false,
frameIncrementPointer: null,
numberOfFrames: 0,
frameTime: 0,
frameTimeVector: null,
averageFrameRate: 0 // backwards compatibility only... it might be useless in the future
};
if (parsingUtils.isValidDataSet(dataSet)) {
// (0028,0008) = Number of Frames
numberOfFrames = dataSet.intString('x00280008', -1);
if (numberOfFrames > 0) {
// set multi-frame image indicator
imageInfo.isMultiframeImage = true;
imageInfo.numberOfFrames = numberOfFrames;
// (0028,0009) = Frame Increment Pointer
frameIncrementPointer = parsingUtils.attributeTag(dataSet, 'x00280009') || '';
if (frameIncrementPointer === 'x00181065') {
// Frame Increment Pointer points to Frame Time Vector (0018,1065) field
frameTimeVector = parsingUtils.floatArray(dataSet, 'x00181065');
if (frameTimeVector instanceof Array && frameTimeVector.length > 0) {
imageInfo.frameIncrementPointer = 'frameTimeVector';
imageInfo.frameTimeVector = frameTimeVector;
frameTime = frameTimeVector.reduce((a, b) => a + b) / frameTimeVector.length;
imageInfo.averageFrameRate = 1000 / frameTime;
}
} else if (frameIncrementPointer === 'x00181063' || frameIncrementPointer === '') {
// Frame Increment Pointer points to Frame Time (0018,1063) field or is not defined (for addtional flexibility).
// Yet another value is possible for this field (5200,9230 for Multi-frame Functional Groups)
// but that case is currently not supported.
frameTime = dataSet.floatString('x00181063', -1);
if (frameTime > 0) {
imageInfo.frameIncrementPointer = 'frameTime';
imageInfo.frameTime = frameTime;
imageInfo.averageFrameRate = 1000 / frameTime;
}
}
}
}
return imageInfo;
}
/**
* Looks up metaData for Cornerstone Tools given a specified type and imageId
* A type may be, e.g. 'study', or 'patient', or 'imagePlane'. These types
* are keys in the stored metaData objects.
*
* @param type
* @param imageId
* @returns {Object} Relevant metaData of the specified type
*/
function provider(type, imageId) {
let imageMetaData = metaDataLookup[imageId];
if (!imageMetaData) {
return;
}
if (imageMetaData.hasOwnProperty(type)) {
return imageMetaData[type];
}
}
Meteor.startup(function() {
cornerstoneTools.metaData.addProvider(provider);
});
/**
* Export relevant symbols
*/
export { addMetaData, addSpecificMetadata, updateMetaData };

View File

@ -1,24 +1,28 @@
import { Meteor } from 'meteor/meteor';
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core';
import { setInstanceClassDefaultViewportFunction } from './instanceClassSpecificViewport';
import { addSpecificMetadata } from './metaDataProvider';
const setMammogramViewportAlignment = (series, enabledElement, imageId) => {
// Don't apply the MG viewport alignment to other series types
let viewTypes = ['MLO', 'CC', 'LM', 'ML', 'XCCL'];
let requiresInversion = ['LM']; // Tomo series are flipped
const viewTypes = ['MLO', 'CC', 'LM', 'ML', 'XCCL'];
const requiresInversion = ['LM']; // Tomo series are flipped
let instance = cornerstoneTools.metaData.get('instance', imageId);
const instance = cornerstoneTools.metaData.get('instance', imageId);
if (!instance) {
return;
}
let laterality = instance.laterality;
let element = enabledElement.element;
let position;
const element = enabledElement.element;
let left = $(enabledElement.canvas).offset().left;
let right = left + enabledElement.canvas.width;
const left = $(enabledElement.canvas).offset().left;
const right = left + enabledElement.canvas.width;
const metadataProvider = OHIF.viewer.metadataProvider;
let laterality = instance.laterality;
let position;
if (viewTypes.indexOf(instance.viewPosition) < 0) {
return;
@ -26,14 +30,14 @@ const setMammogramViewportAlignment = (series, enabledElement, imageId) => {
// Check if we should flip the laterality
if (requiresInversion.indexOf(instance.viewPosition) > -1) {
if (laterality === "R") {
laterality = "L";
} else if (laterality === "L") {
laterality = "R";
if (laterality === 'R') {
laterality = 'L';
} else if (laterality === 'L') {
laterality = 'R';
}
}
if (laterality === "R") {
if (laterality === 'R') {
// Set X translation to Canvas max in image pixels - image width
// This places it on the right side of the screen
position = cornerstone.pageToPixel(element, right, 0);
@ -41,7 +45,7 @@ const setMammogramViewportAlignment = (series, enabledElement, imageId) => {
enabledElement.viewport.translation.x += position.x - enabledElement.image.width;
}
addSpecificMetadata(imageId, 'tagDisplay', {
metadataProvider.addSpecificMetadata(imageId, 'tagDisplay', {
side: 'L'
});
@ -53,7 +57,7 @@ const setMammogramViewportAlignment = (series, enabledElement, imageId) => {
enabledElement.viewport.translation.x += position.x;
}
addSpecificMetadata(imageId, 'tagDisplay', {
metadataProvider.addSpecificMetadata(imageId, 'tagDisplay', {
side: 'R'
});
}
@ -62,7 +66,7 @@ const setMammogramViewportAlignment = (series, enabledElement, imageId) => {
};
Meteor.startup(function() {
setInstanceClassDefaultViewportFunction("1.2.840.10008.5.1.4.1.1.1.2", setMammogramViewportAlignment);
setInstanceClassDefaultViewportFunction('1.2.840.10008.5.1.4.1.1.1.2', setMammogramViewportAlignment);
});
export { setMammogramViewportAlignment };

View File

@ -122,7 +122,8 @@ export const toolManager = {
configureTools() {
// Get Cornerstone Tools
const { panMultiTouch, textStyle, toolStyle, toolColors,
length, arrowAnnotate, zoom, ellipticalRoi } = cornerstoneTools;
length, arrowAnnotate, zoom, ellipticalRoi,
textMarker, magnify } = cornerstoneTools;
// Set the configuration for the multitouch pan tool
const multiTouchPanConfig = {
@ -187,7 +188,7 @@ export const toolManager = {
shadowOffsetX: shadowConfig.shadowOffsetX,
shadowOffsetY: shadowConfig.shadowOffsetY
};
cornerstoneTools.textMarker.setConfiguration(textMarkerConfig);
textMarker.setConfiguration(textMarkerConfig);
// Set the configuration values for the text annotation (Arrow) tool
const annotateConfig = {
@ -203,6 +204,38 @@ export const toolManager = {
maxScale: 10
};
zoom.setConfiguration(zoomConfig);
const magnifyConfig = {
magnifySize: 300,
magnificationLevel: 3
};
magnify.setConfiguration(magnifyConfig);
},
/**
* This function searches an object to return the keys that contain a specific value
*
* @param object {object} The object to be searched
* @param value The value to be found
*
* @returns {array} The keys for which the object has the specified value
*/
getKeysByValue(object, value) {
// http://stackoverflow.com/questions/9907419/javascript-object-get-key-by-value
return Object.keys(object).filter(key => object[key] === value);
},
configureLoadProcess() {
// Whenever the CornerstoneImageLoadProgress is fired, identify which viewports
// the "in-progress" image is to be displayed in. Then pass the percent complete
// via the Meteor Session to the other templates to be displayed in the relevant viewports.
$(cornerstone).on('CornerstoneImageLoadProgress', (e, eventData) => {
viewportIndices = this.getKeysByValue(window.ViewportLoading, eventData.imageId);
viewportIndices.forEach(viewportIndex => {
Session.set('CornerstoneLoadProgress' + viewportIndex, eventData.percentComplete);
});
const encodedId = OHIF.string.encodeId(eventData.imageId);
Session.set('CornerstoneThumbnailLoadProgress' + encodedId, eventData.percentComplete);
});
},
setGestures(newGestures) {
gestures = newGestures;

View File

@ -1,23 +1,23 @@
/**
* Import main dependency...
* Import main dependency
*/
import { OHIF } from 'meteor/ohif:core';
/**
* Create Viewerbase namespace...
* Create Viewerbase namespace
*/
const Viewerbase = {};
/**
* Append Viewerbase namespace to OHIF namespace...
* Append Viewerbase namespace to OHIF namespace
*/
OHIF.viewerbase = Viewerbase;
/**
* Export relevant objects...
* Export relevant objects
*/
export { OHIF, Viewerbase };