toolManager: fixed annotate tool issues and added Text Marker Tool (importing callback functions from Nucleus) and using Metadata classes for OHIF Viewer
This commit is contained in:
1 parent
014dc7730f
commit
a8626d02c0
24 files changed
+516
-242
No files matched your search
-1
@@ -29,7 +29,6 @@ Template.studySeriesQuickSwitch.onCreated(() => {
|
||||
const viewportData = instance.getViewportData(viewportIndex);
|
||||
|
||||
// @TypeSafeStudies
|
||||
|
||||
if (viewportData) {
|
||||
// Finds the current study and return it
|
||||
instance.study = OHIF.viewer.Studies.findBy({
|
||||
|
||||
-1
@@ -41,7 +41,6 @@ Template.studyTimepoint.events({
|
||||
let $studiesTarget = instance.$('.studyTimepoint');
|
||||
|
||||
// @TypeSafeStudies
|
||||
|
||||
if (changed.isQuickSwitch) {
|
||||
// Changes the current quick switch study
|
||||
const study = OHIF.viewer.Studies.findBy({
|
||||
|
||||
@@ -139,6 +139,14 @@ Viewerbase.sortingManager = sortingManager;
|
||||
import { crosshairsSynchronizers } from './lib/crosshairsSynchronizers';
|
||||
Viewerbase.crosshairsSynchronizers = crosshairsSynchronizers;
|
||||
|
||||
// annotateTextUtils.*
|
||||
import { annotateTextUtils } from './lib/annotateTextUtils';
|
||||
Viewerbase.annotateTextUtils = annotateTextUtils;
|
||||
|
||||
// textMarkerUtils.*
|
||||
import { textMarkerUtils } from './lib/textMarkerUtils';
|
||||
Viewerbase.textMarkerUtils = textMarkerUtils;
|
||||
|
||||
// createStacks.*
|
||||
import { createStacks } from './lib/createStacks';
|
||||
Viewerbase.createStacks = createStacks;
|
||||
|
||||
@@ -17,41 +17,50 @@ const stackUpdatedCallbacks = [];
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
function createAndAddStack(stackMap, study, displaySet) {
|
||||
const numImages = displaySet.images.length;
|
||||
const imageIds = [];
|
||||
let imageId;
|
||||
const numImages = displaySet.images.length;
|
||||
const imageIds = [];
|
||||
let imageId;
|
||||
|
||||
displaySet.images.forEach((image, imageIndex) => {
|
||||
const metaData = {
|
||||
instance: image,
|
||||
series: displaySet, // TODO: Check this
|
||||
study: study,
|
||||
numImages: numImages,
|
||||
imageIndex: imageIndex + 1
|
||||
};
|
||||
displaySet.images.forEach((image, imageIndex) => {
|
||||
const metaData = {
|
||||
instance: image,
|
||||
series: displaySet, // TODO: Check this
|
||||
study: study,
|
||||
numImages: numImages,
|
||||
imageIndex: imageIndex + 1
|
||||
};
|
||||
|
||||
const numberOfFrames = image.numberOfFrames;
|
||||
if (numberOfFrames > 1) {
|
||||
OHIF.log.info('Multiframe image detected');
|
||||
for (let i = 0; i < numberOfFrames; i++) {
|
||||
metaData.frame = i;
|
||||
imageId = getImageId(image, i);
|
||||
imageIds.push(imageId);
|
||||
addMetaData(imageId, metaData);
|
||||
}
|
||||
}
|
||||
else {
|
||||
imageId = getImageId(image);
|
||||
imageIds.push(imageId);
|
||||
addMetaData(imageId, metaData);
|
||||
}
|
||||
});
|
||||
const numberOfFrames = image.numberOfFrames;
|
||||
if (numberOfFrames > 1) {
|
||||
OHIF.log.info('Multiframe image detected');
|
||||
for (let i = 0; i < numberOfFrames; i++) {
|
||||
metaData.frame = i;
|
||||
imageId = getImageId(image, i);
|
||||
imageIds.push(imageId);
|
||||
addMetaData(imageId, metaData);
|
||||
}
|
||||
}
|
||||
else {
|
||||
imageId = getImageId(image);
|
||||
imageIds.push(imageId);
|
||||
addMetaData(imageId, metaData);
|
||||
}
|
||||
});
|
||||
|
||||
return imageIds;
|
||||
const stack = {
|
||||
displaySetInstanceUid: displaySet.displaySetInstanceUid,
|
||||
imageIds: imageIds,
|
||||
frameRate: displaySet.frameRate,
|
||||
isClip: displaySet.isClip
|
||||
};
|
||||
|
||||
stackMap[displaySet.displaySetInstanceUid] = stack;
|
||||
|
||||
return imageIds;
|
||||
}
|
||||
|
||||
configuration = {
|
||||
createAndAddStack: createAndAddStack
|
||||
createAndAddStack: createAndAddStack
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -61,64 +70,64 @@ configuration = {
|
||||
* clearStacks and makeAndAddStack should not used outside of loadStudy and removeStudies.
|
||||
*/
|
||||
const StackManager = {
|
||||
/**
|
||||
* Removes all current stacks
|
||||
*/
|
||||
clearStacks() {
|
||||
stackMap = {};
|
||||
},
|
||||
/**
|
||||
* Create a stack from an image set, as well as add in the metadata on a per image bases.
|
||||
* @param study The study who's metadata will be added
|
||||
* @param displaySet The set of images to make the stack from
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
makeAndAddStack(study, displaySet) {
|
||||
return configuration.createAndAddStack(stackMap, study, displaySet, stackUpdatedCallbacks);
|
||||
},
|
||||
/**
|
||||
* Find a stack from the currently created stacks.
|
||||
* @param displaySetInstanceUid The UID of the stack to find.
|
||||
* @returns {*} undefined if not found, otherwise the stack object is returned.
|
||||
*/
|
||||
findStack(displaySetInstanceUid) {
|
||||
return stackMap[displaySetInstanceUid];
|
||||
},
|
||||
/**
|
||||
* Gets the underlying map of displaySetInstanceUid to stack object.
|
||||
* WARNING: Do not change this object. It directly affects the manager.
|
||||
* @returns {{}} map of displaySetInstanceUid -> stack.
|
||||
*/
|
||||
getAllStacks() {
|
||||
return stackMap;
|
||||
},
|
||||
/**
|
||||
* Adds in a callback to be called on a stack being added / updated.
|
||||
* @param callback must accept at minimum one argument,
|
||||
* which is the stack that was added / updated.
|
||||
*/
|
||||
addStackUpdatedCallback(callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new OHIFError('callback must be provided as a function');
|
||||
/**
|
||||
* Removes all current stacks
|
||||
*/
|
||||
clearStacks() {
|
||||
stackMap = {};
|
||||
},
|
||||
/**
|
||||
* Create a stack from an image set, as well as add in the metadata on a per image bases.
|
||||
* @param study The study who's metadata will be added
|
||||
* @param displaySet The set of images to make the stack from
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
makeAndAddStack(study, displaySet) {
|
||||
return configuration.createAndAddStack(stackMap, study, displaySet, stackUpdatedCallbacks);
|
||||
},
|
||||
/**
|
||||
* Find a stack from the currently created stacks.
|
||||
* @param displaySetInstanceUid The UID of the stack to find.
|
||||
* @returns {*} undefined if not found, otherwise the stack object is returned.
|
||||
*/
|
||||
findStack(displaySetInstanceUid) {
|
||||
return stackMap[displaySetInstanceUid];
|
||||
},
|
||||
/**
|
||||
* Gets the underlying map of displaySetInstanceUid to stack object.
|
||||
* WARNING: Do not change this object. It directly affects the manager.
|
||||
* @returns {{}} map of displaySetInstanceUid -> stack.
|
||||
*/
|
||||
getAllStacks() {
|
||||
return stackMap;
|
||||
},
|
||||
/**
|
||||
* Adds in a callback to be called on a stack being added / updated.
|
||||
* @param callback must accept at minimum one argument,
|
||||
* which is the stack that was added / updated.
|
||||
*/
|
||||
addStackUpdatedCallback(callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new OHIFError('callback must be provided as a function');
|
||||
}
|
||||
stackUpdatedCallbacks.push(callback);
|
||||
},
|
||||
/**
|
||||
* Return configuration
|
||||
*/
|
||||
getConfiguration() {
|
||||
return configuration;
|
||||
},
|
||||
/**
|
||||
* Set configuration, in order to provide compatibility
|
||||
* with other systems by overriding this functions
|
||||
* @param {Object} config object with functions to be overrided
|
||||
*
|
||||
* For now, only makeAndAddStack can be overrided
|
||||
*/
|
||||
setConfiguration(config) {
|
||||
configuration = config;
|
||||
}
|
||||
stackUpdatedCallbacks.push(callback);
|
||||
},
|
||||
/**
|
||||
* Return configuration
|
||||
*/
|
||||
getConfiguration() {
|
||||
return configuration;
|
||||
},
|
||||
/**
|
||||
* Set configuration, in order to provide compatibility
|
||||
* with other systems by overriding this functions
|
||||
* @param {Object} config object with functions to be overrided
|
||||
*
|
||||
* For now, only makeAndAddStack can be overrided
|
||||
*/
|
||||
setConfiguration(config) {
|
||||
configuration = config;
|
||||
}
|
||||
};
|
||||
|
||||
export { StackManager };
|
||||
@@ -0,0 +1,122 @@
|
||||
import { viewportUtils } from './viewportUtils';
|
||||
|
||||
const getTextCallback = doneChangingTextCallback => {
|
||||
// This handles the text entry for the annotation tool
|
||||
const keyPressHandler = e => {
|
||||
// If Enter or Esc are pressed, close the dialog
|
||||
if (e.which === 13 || e.which === 27) {
|
||||
closeHandler();
|
||||
}
|
||||
};
|
||||
|
||||
const closeHandler = () => {
|
||||
dialog.get(0).close();
|
||||
doneChangingTextCallback(getTextInput.val());
|
||||
// Reset the text value
|
||||
getTextInput.val('');
|
||||
|
||||
// Reset the focus to the active viewport element
|
||||
// This makes the mobile Safari keyboard close
|
||||
const element = viewportUtils.getActiveViewportElement();
|
||||
$(element).focus();
|
||||
};
|
||||
|
||||
const dialog = $('#annotationDialog');
|
||||
if (dialog.get(0).open === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getTextInput = $('.annotationTextInput');
|
||||
|
||||
// Focus on the text input to open the Safari keyboard
|
||||
getTextInput.focus();
|
||||
|
||||
dialog.get(0).showModal();
|
||||
|
||||
const confirm = dialog.find('.annotationDialogConfirm');
|
||||
confirm.off('click');
|
||||
confirm.on('click', () => {
|
||||
closeHandler();
|
||||
});
|
||||
|
||||
// Use keydown since keypress doesn't handle ESC in Chrome
|
||||
dialog.off('keydown');
|
||||
dialog.on('keydown', keyPressHandler);
|
||||
};
|
||||
|
||||
const changeTextCallback = (data, eventData, doneChangingTextCallback) => {
|
||||
const dialog = $('#relabelAnnotationDialog');
|
||||
if (dialog.get(0).open === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Viewerbase.helpers.isTouchDevice()) {
|
||||
// Center the dialog on screen on touch devices
|
||||
dialog.css({
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
margin: 'auto'
|
||||
});
|
||||
} else {
|
||||
// Place the dialog above the tool that is being relabelled
|
||||
dialog.css({
|
||||
top: eventData.currentPoints.page.y - dialog.outerHeight() - 20,
|
||||
left: eventData.currentPoints.page.x - dialog.outerWidth() / 2
|
||||
});
|
||||
}
|
||||
|
||||
const getTextInput = dialog.find('.annotationTextInput');
|
||||
const confirm = dialog.find('.relabelConfirm');
|
||||
const remove = dialog.find('.relabelRemove');
|
||||
|
||||
getTextInput.val(data.text);
|
||||
|
||||
// Focus on the text input to open the Safari keyboard
|
||||
getTextInput.focus();
|
||||
|
||||
dialog.get(0).showModal();
|
||||
|
||||
confirm.off('click');
|
||||
confirm.on('click', () => {
|
||||
dialog.get(0).close();
|
||||
doneChangingTextCallback(data, getTextInput.val());
|
||||
});
|
||||
|
||||
// If the remove button is clicked, delete this marker
|
||||
remove.off('click');
|
||||
remove.on('click', () => {
|
||||
dialog.get(0).close();
|
||||
doneChangingTextCallback(data, undefined, true);
|
||||
});
|
||||
|
||||
dialog.off('keydown');
|
||||
dialog.on('keydown', keyPressHandler);
|
||||
|
||||
const keyPressHandler = e => {
|
||||
// If Enter is pressed, close the dialog
|
||||
if (e.which === 13) {
|
||||
closeHandler();
|
||||
}
|
||||
};
|
||||
|
||||
const closeHandler = () => {
|
||||
dialog.get(0).close();
|
||||
doneChangingTextCallback(data, getTextInput.val());
|
||||
// Reset the text value
|
||||
getTextInput.val('');
|
||||
|
||||
// Reset the focus to the active viewport element
|
||||
// This makes the mobile Safari keyboard close
|
||||
const element = viewportUtils.getActiveViewportElement();
|
||||
$(element).focus();
|
||||
};
|
||||
};
|
||||
|
||||
const annotateTextUtils = {
|
||||
getTextCallback,
|
||||
changeTextCallback
|
||||
};
|
||||
|
||||
export { annotateTextUtils };
|
||||
@@ -88,13 +88,18 @@ export class LayoutManager {
|
||||
// Generate the additional data based on the appendix
|
||||
const additionalData = [];
|
||||
appendix.forEach((displaySet, index) => {
|
||||
additionalData.push({
|
||||
viewportIndex: currentViewportIndex + index,
|
||||
studyInstanceUid: displaySet.studyInstanceUid,
|
||||
seriesInstanceUid: displaySet.seriesInstanceUid,
|
||||
displaySetInstanceUid: displaySet.displaySetInstanceUid,
|
||||
sopInstanceUid: displaySet.images[0].sopInstanceUid
|
||||
});
|
||||
const { images, studyInstanceUid, seriesInstanceUid, displaySetInstanceUid } = displaySet;
|
||||
const sopInstanceUid = images[0] && images[0].getSOPInstanceUID ? images[0].getSOPInstanceUID() : '';
|
||||
const viewportIndex = currentViewportIndex + index;
|
||||
const data = {
|
||||
viewportIndex,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid
|
||||
};
|
||||
|
||||
additionalData.push(data);
|
||||
});
|
||||
|
||||
// Append the additional data with the viewport data
|
||||
|
||||
@@ -1,40 +1,86 @@
|
||||
import { ImageSet } from './classes/ImageSet';
|
||||
import { isImage } from './isImage';
|
||||
|
||||
const isMultiFrame = instance => {
|
||||
return instance.getRawValue('NumberOfFrames') > 1;
|
||||
};
|
||||
|
||||
const makeDisplaySet = (series, instances) => {
|
||||
const instance = instances[0];
|
||||
|
||||
const imageSet = new ImageSet(instances);
|
||||
const seriesData = series.getData();
|
||||
|
||||
// set appropriate attributes to image set...
|
||||
imageSet.setAttributes({
|
||||
displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID
|
||||
seriesInstanceUid: seriesData.seriesInstanceUid,
|
||||
seriesNumber: seriesData.seriesNumber,
|
||||
seriesDescription: seriesData.seriesDescription,
|
||||
numImageFrames: instances.length,
|
||||
frameRate: instance.getRawValue('x00181063'),
|
||||
modality: seriesData.modality,
|
||||
isMultiFrame: isMultiFrame(instance)
|
||||
});
|
||||
|
||||
// Sort the images in this series
|
||||
imageSet.sortBy((a, b) => {
|
||||
// Sort by instance Number
|
||||
const aInstanceNumber = a.getRawValue('x00200013');
|
||||
const bInstanceNumber = b.getRawValue('x00200013');
|
||||
if (a.instanceNumber && b.instanceNumber &&
|
||||
a.instanceNumber !== b.instanceNumber) {
|
||||
return a.instanceNumber - b.instanceNumber;
|
||||
}
|
||||
});
|
||||
|
||||
// Include the first image instance number (after sorted)
|
||||
imageSet.setAttribute('instanceNumber', imageSet.getImage(0).getRawValue('x00200013'));
|
||||
|
||||
return imageSet;
|
||||
};
|
||||
|
||||
const isSingleImageModality = modality => {
|
||||
return (modality === 'CR' ||
|
||||
modality === 'MG' ||
|
||||
modality === 'DX');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a set of series to be placed in the Study Browser
|
||||
* The series that appear in the Study Browser must represent
|
||||
* Creates a set of series to be placed in the Study Metadata
|
||||
* The series that appear in the Study Metadata must represent
|
||||
* imaging modalities.
|
||||
*
|
||||
* Furthermore, for drag/drop functionality,
|
||||
* it is easiest if the stack objects also contain information about
|
||||
* which study they are linked to.
|
||||
*
|
||||
* @param study The study instance to be used
|
||||
* @returns {Array} An array of series to be placed in the Study Browser
|
||||
* @param study The study instance metadata to be used
|
||||
* @returns {Array} An array of series to be placed in the Study Metadata
|
||||
*/
|
||||
function createStacks(study) {
|
||||
const createStacks = study => {
|
||||
// Define an empty array of display sets
|
||||
var displaySets = [];
|
||||
const displaySets = [];
|
||||
|
||||
if (!study || !study.seriesList) {
|
||||
return displaySets;
|
||||
if (!study || !study.getSeriesCount()) {
|
||||
return displaySets;
|
||||
}
|
||||
|
||||
study.seriesList.forEach(series => {
|
||||
// Loop through the series (SeriesMetadata)
|
||||
study.forEachSeries(series => {
|
||||
// If the series has no instances, skip it
|
||||
if (!series.instances) {
|
||||
if (!series.getInstanceCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search through the instances of this series
|
||||
// Search through the instances (InstanceMedatada object) of this series
|
||||
// Split Multi-frame instances and Single-image modalities
|
||||
// into their own specific display sets. Place the rest of each
|
||||
// series into another display set.
|
||||
let stackableInstances = [];
|
||||
series.instances.forEach(instance => {
|
||||
// All imaging modalities must have a valid value for sopClassUid or rows
|
||||
if (!isImage(instance.sopClassUid) && !instance.rows) {
|
||||
const stackableInstances = [];
|
||||
series.forEachInstance(instance => {
|
||||
// All imaging modalities must have a valid value for sopClassUid (x00080016) or rows (x00280010)
|
||||
if (!isImage(instance.getRawValue('x00080016')) && !instance.getRawValue('x00280010')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,8 +109,8 @@ function createStacks(study) {
|
||||
});
|
||||
|
||||
if (stackableInstances.length) {
|
||||
let displaySet = makeDisplaySet(series, stackableInstances);
|
||||
displaySet.setAttribute('studyInstanceUid', study.studyInstanceUid);
|
||||
const displaySet = makeDisplaySet(series, stackableInstances);
|
||||
displaySet.setAttribute('studyInstanceUid', study.getStudyInstanceUID());
|
||||
displaySets.push(displaySet);
|
||||
}
|
||||
});
|
||||
@@ -72,47 +118,6 @@ function createStacks(study) {
|
||||
return displaySets;
|
||||
};
|
||||
|
||||
function makeDisplaySet(series, instances) {
|
||||
const instance = instances[0];
|
||||
|
||||
const imageSet = new ImageSet(instances);
|
||||
|
||||
// set appropriate attributes to image set...
|
||||
imageSet.setAttributes({
|
||||
displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID
|
||||
seriesInstanceUid: series.seriesInstanceUid,
|
||||
seriesNumber: series.seriesNumber,
|
||||
seriesDescription: series.seriesDescription,
|
||||
numImageFrames: instances.length,
|
||||
frameRate: instance.frameTime,
|
||||
modality: series.modality,
|
||||
isMultiFrame: isMultiFrame(instance)
|
||||
});
|
||||
|
||||
// Sort the images in this series
|
||||
imageSet.sortBy(function(a, b) {
|
||||
if (a.instanceNumber && b.instanceNumber &&
|
||||
a.instanceNumber !== b.instanceNumber) {
|
||||
return a.instanceNumber - b.instanceNumber;
|
||||
}
|
||||
});
|
||||
|
||||
// Include the first image instance number (after sorted)
|
||||
imageSet.setAttribute('instanceNumber', imageSet.getImage(0).instanceNumber);
|
||||
|
||||
return imageSet;
|
||||
}
|
||||
|
||||
function isSingleImageModality(modality) {
|
||||
return (modality === 'CR' ||
|
||||
modality === 'MG' ||
|
||||
modality === 'DX');
|
||||
}
|
||||
|
||||
function isMultiFrame(instance) {
|
||||
return instance.numberOfFrames > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose "createStacks"...
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,13 @@ import { getWADORSImageId } from './getWADORSImageId';
|
||||
* @param instance
|
||||
* @returns {string} The imageId to be used by Cornerstone
|
||||
*/
|
||||
export function getImageId(instance, frame) {
|
||||
export function getImageId(instanceMetadata, frame) {
|
||||
if (!instanceMetadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
const instance = instanceMetadata.getData();
|
||||
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,28 +4,30 @@ import { OHIF } from 'meteor/ohif:core';
|
||||
/**
|
||||
* Obtain an imageId for Cornerstone based on the WADO-RS scheme
|
||||
*
|
||||
* @param instance
|
||||
* @param {object} instance metadata object (InstanceMetadata)
|
||||
* @returns {string} The imageId to be used by Cornerstone
|
||||
*/
|
||||
|
||||
export function getWADORSImageId(instance) {
|
||||
var columnPixelSpacing = 1.0;
|
||||
var rowPixelSpacing = 1.0;
|
||||
export function getWADORSImageId(instanceMetada) {
|
||||
const instance = instanceMetada.getData();
|
||||
let columnPixelSpacing = 1.0;
|
||||
let rowPixelSpacing = 1.0;
|
||||
|
||||
if (instance.pixelSpacing) {
|
||||
var split = instance.pixelSpacing.split('\\');
|
||||
const split = instance.pixelSpacing.split('\\');
|
||||
rowPixelSpacing = parseFloat(split[0]);
|
||||
columnPixelSpacing = parseFloat(split[1]);
|
||||
}
|
||||
|
||||
var windowWidth;
|
||||
var windowCenter;
|
||||
let windowWidth;
|
||||
let windowCenter;
|
||||
|
||||
if (instance.windowWidth && instance.windowCenter) {
|
||||
windowWidth = parseFloat(instance.windowWidth.split('\\')[0]);
|
||||
windowCenter = parseFloat(instance.windowCenter.split('\\')[0]);
|
||||
}
|
||||
|
||||
var image = {
|
||||
const image = {
|
||||
uri: Meteor.absoluteUrl(instance.wadorsuri),
|
||||
//imageId : '',
|
||||
//minPixelValue : 0,
|
||||
@@ -56,7 +58,7 @@ export function getWADORSImageId(instance) {
|
||||
instance: instance
|
||||
};
|
||||
|
||||
var imageId = cornerstoneWADOImageLoader.imageManager.add(image);
|
||||
const imageId = cornerstoneWADOImageLoader.imageManager.add(image);
|
||||
|
||||
OHIF.log.info('WADO-RS ImageID: ' + imageId);
|
||||
return imageId;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getSortedData } from './getSortedData';
|
||||
import { createStacks } from './createStacks';
|
||||
|
||||
const getDisplaySets = (studyMetadata, seriesNumber, iteratorFunction) => {
|
||||
const iteratorFn = typeof iteratorFunction !== 'function' ? getSortedData : iteratorFunction;
|
||||
const iteratorFn = typeof iteratorFunction !== 'function' ? createStacks : iteratorFunction;
|
||||
|
||||
return iteratorFn(studyMetadata, seriesNumber);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Blaze } from 'meteor/blaze';
|
||||
|
||||
import { toolManager } from './toolManager';
|
||||
import { viewportUtils } from './viewportUtils';
|
||||
|
||||
const changeTextCallback = (data, eventData, doneChangingTextCallback) => {
|
||||
// This handles the double-click/long-press event on Spine text marker labels
|
||||
const keyPressHandler = e => {
|
||||
// If Enter or Esc are pressed, close the dialog
|
||||
if (e.which === 13 || e.which === 27) {
|
||||
closeHandler();
|
||||
}
|
||||
};
|
||||
|
||||
// Deactivate textMarker tool after editing a spine label & if spine is not active tool
|
||||
const deactivateAfterEdit = () => {
|
||||
if (toolManager.getActiveTool() !== 'spine') {
|
||||
const element = viewportUtils.getActiveViewportElement();
|
||||
cornerstoneTools.textMarker.deactivate(element, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const closeHandler = () => {
|
||||
dialog.get(0).close();
|
||||
doneChangingTextCallback(data, select.val());
|
||||
deactivateAfterEdit();
|
||||
// Reset the focus to the active viewport element
|
||||
// This makes the mobile Safari keyboard close
|
||||
const element = viewportUtils.getActiveViewportElement();
|
||||
$(element).focus();
|
||||
};
|
||||
|
||||
const dialog = $('#textMarkerRelabelDialog');
|
||||
|
||||
// Is necessary to use Blaze object to not create
|
||||
// circular depencency with helper object (./helpers)
|
||||
if (Blaze._globalHelpers.isTouchDevice()) {
|
||||
// Center the dialog on screen on touch devices
|
||||
dialog.css({
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
margin: 'auto'
|
||||
});
|
||||
dialog.find('.dialog.arrow').hide();
|
||||
} else {
|
||||
// Place the dialog above the tool that is being relabelled
|
||||
// TODO = Switch this to the tool coordinates, but put back into
|
||||
// page coordinates.
|
||||
dialog.css({
|
||||
top: eventData.currentPoints.page.y - dialog.outerHeight() - 20,
|
||||
left: eventData.currentPoints.page.x - dialog.outerWidth() / 2
|
||||
});
|
||||
dialog.find('.dialog.arrow').show();
|
||||
}
|
||||
|
||||
const select = dialog.find('.relabelSelect');
|
||||
const confirm = dialog.find('.relabelConfirm');
|
||||
const remove = dialog.find('.relabelRemove');
|
||||
|
||||
// If the remove button is clicked, delete this marker
|
||||
remove.off('click');
|
||||
remove.on('click', () => {
|
||||
dialog.get(0).close();
|
||||
doneChangingTextCallback(data, undefined, true);
|
||||
deactivateAfterEdit();
|
||||
});
|
||||
|
||||
dialog.get(0).showModal();
|
||||
$('.relabelSelect').val(data.text).trigger('change'); //Update selector to the current
|
||||
|
||||
confirm.off('click');
|
||||
confirm.on('click', () => {
|
||||
closeHandler();
|
||||
});
|
||||
|
||||
// Use keydown since keypress doesn't handle ESC in Chrome
|
||||
dialog.off('keydown');
|
||||
dialog.on('keydown', keyPressHandler);
|
||||
};
|
||||
|
||||
const textMarkerUtils = {
|
||||
changeTextCallback
|
||||
};
|
||||
|
||||
export { textMarkerUtils };
|
||||
@@ -4,6 +4,8 @@ import { OHIF } from 'meteor/ohif:core';
|
||||
import { getFrameOfReferenceUID } from './getFrameOfReferenceUID';
|
||||
import { updateCrosshairsSynchronizer } from './updateCrosshairsSynchronizer';
|
||||
import { crosshairsSynchronizers } from './crosshairsSynchronizers';
|
||||
import { annotateTextUtils } from './annotateTextUtils';
|
||||
import { textMarkerUtils } from './textMarkerUtils';
|
||||
|
||||
let activeTool = 'wwwc';
|
||||
let defaultTool = 'wwwc';
|
||||
@@ -167,16 +169,34 @@ export const toolManager = {
|
||||
...shadowConfig
|
||||
});
|
||||
|
||||
// Set the configuration values for the text annotation (Arrow) tool
|
||||
// Set the configuration values for the Text Marker (Spine Labelling) tool
|
||||
const startFrom = $('#startFrom');
|
||||
const ascending = $('#ascending');
|
||||
const textMarkerConfig = {
|
||||
markers: [ 'L5', 'L4', 'L3', 'L2', 'L1', // Lumbar spine
|
||||
'T12', 'T11', 'T10', 'T9', 'T8', 'T7', // Thoracic spine
|
||||
'T6', 'T5', 'T4', 'T3', 'T2', 'T1',
|
||||
'C7', 'C6', 'C5', 'C4', 'C3', 'C2', 'C1', // Cervical spine
|
||||
],
|
||||
current: startFrom.val(),
|
||||
ascending: ascending.is(':checked'),
|
||||
loop: true,
|
||||
changeTextCallback: textMarkerUtils.changeTextCallback,
|
||||
shadow: shadowConfig.shadow,
|
||||
shadowColor: shadowConfig.shadowColor,
|
||||
shadowOffsetX: shadowConfig.shadowOffsetX,
|
||||
shadowOffsetY: shadowConfig.shadowOffsetY
|
||||
};
|
||||
cornerstoneTools.textMarker.setConfiguration(textMarkerConfig);
|
||||
|
||||
// @TODO: Fix this, needs to import them from somewhere
|
||||
/*const annotateConfig = {
|
||||
getTextCallback: getAnnotationTextCallback,
|
||||
changeTextCallback: changeAnnotationTextCallback,
|
||||
// Set the configuration values for the text annotation (Arrow) tool
|
||||
const annotateConfig = {
|
||||
getTextCallback: annotateTextUtils.getTextCallback,
|
||||
changeTextCallback: annotateTextUtils.changeTextCallback,
|
||||
drawHandles: false,
|
||||
arrowFirst: true
|
||||
};
|
||||
arrowAnnotate.setConfiguration(annotateConfig);*/
|
||||
arrowAnnotate.setConfiguration(annotateConfig);
|
||||
|
||||
const zoomConfig = {
|
||||
minScale: 0.05,
|
||||
|
||||
Reference in new issue
Block a user