WIP reorganization
This commit is contained in:
1 parent
6706dfe634
commit
054e37dfb7
266 files changed
+98
-620
No files matched your search
+16
@@ -0,0 +1,16 @@
|
||||
export default function(handles, image={}) {
|
||||
// Calculate the long axis length
|
||||
const dx = (handles.start.x - handles.end.x) * (image.columnPixelSpacing || 1);
|
||||
const dy = (handles.start.y - handles.end.y) * (image.rowPixelSpacing || 1);
|
||||
const length = Math.sqrt(dx * dx + dy * dy) || 0;
|
||||
|
||||
// Calculate the short axis length
|
||||
const wx = (handles.perpendicularStart.x - handles.perpendicularEnd.x) * (image.columnPixelSpacing || 1);
|
||||
const wy = (handles.perpendicularStart.y - handles.perpendicularEnd.y) * (image.rowPixelSpacing || 1);
|
||||
const width = Math.sqrt(wx * wx + wy * wy) || 0;
|
||||
|
||||
return {
|
||||
longestDiameter: length.toFixed(1),
|
||||
shortestDiameter: width.toFixed(1)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Get the key for the handle which is selected
|
||||
export default function(handles) {
|
||||
let selectedHandleKey;
|
||||
Object.keys(handles).every(handleKey => {
|
||||
const handle = handles[handleKey];
|
||||
if (handle.selected) {
|
||||
selectedHandleKey = handleKey;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return selectedHandleKey;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import getSelectedHandleKey from './getSelectedHandleKey';
|
||||
import repositionBidirectionalArmHandle from './repositionBidirectionalArmHandle';
|
||||
import getLongestAndShortestDiameters from './getLongestAndShortestDiameters';
|
||||
|
||||
OHIF.lesiontracker.bidirectional = {
|
||||
toolType: 'bidirectional',
|
||||
inverseKeyMap: {
|
||||
start: 'end',
|
||||
end: 'start',
|
||||
perpendicularStart: 'perpendicularEnd',
|
||||
perpendicularEnd: 'perpendicularStart'
|
||||
},
|
||||
perpendicularKeyMap: {
|
||||
start: 'perpendicularStart',
|
||||
end: 'perpendicularEnd',
|
||||
perpendicularStart: 'start',
|
||||
perpendicularEnd: 'end'
|
||||
},
|
||||
getSelectedHandleKey,
|
||||
repositionBidirectionalArmHandle,
|
||||
getLongestAndShortestDiameters
|
||||
};
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { _ } from 'meteor/underscore';
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
// Return the newPosition for the handle based on the mmStep and handles
|
||||
export default function(image, handles, handleKey, mmStep, mmLimit=1) {
|
||||
if (handleKey === 'textBox') return;
|
||||
|
||||
const lib = OHIF.lesiontracker.bidirectional;
|
||||
|
||||
// Defines how much the arm will increase/decrease
|
||||
const columnPixelSpacing = (image && image.columnPixelSpacing) || 1;
|
||||
const rowPixelSpacing = (image && image.rowPixelSpacing) || 1;
|
||||
const stepX = mmStep * (1 / columnPixelSpacing);
|
||||
const stepY = mmStep * (1 / rowPixelSpacing);
|
||||
|
||||
// Get the line angle and its handles
|
||||
const keyA = handleKey;
|
||||
const keyB = lib.inverseKeyMap[handleKey];
|
||||
const handleA = handles[keyA];
|
||||
const handleB = handles[keyB];
|
||||
const angle = Math.atan2(handleA.y - handleB.y, handleA.x - handleB.x);
|
||||
|
||||
// Calculate the new position of the handle
|
||||
const newPosition = {
|
||||
x: handleA.x + Math.cos(angle) * stepX,
|
||||
y: handleA.y + Math.sin(angle) * stepY
|
||||
};
|
||||
|
||||
if (mmStep < 0) {
|
||||
// Get the perpendicular handles
|
||||
const keyC = lib.perpendicularKeyMap[keyA];
|
||||
const keyD = lib.perpendicularKeyMap[keyB];
|
||||
const handleC = handles[keyC];
|
||||
const handleD = handles[keyD];
|
||||
|
||||
// Create the line segment for the arm being resized
|
||||
const lineAB = {
|
||||
start: _.pick(handleA, ['x', 'y']),
|
||||
end: _.pick(handleB, ['x', 'y'])
|
||||
};
|
||||
|
||||
// Create the line segment for the perpendicular arm
|
||||
const lineCD = {
|
||||
start: _.pick(handleC, ['x', 'y']),
|
||||
end: _.pick(handleD, ['x', 'y'])
|
||||
};
|
||||
|
||||
// Get the intersection point between the arms
|
||||
const intersection = cornerstoneMath.lineSegment.intersectLine(lineAB, lineCD);
|
||||
|
||||
// Keep the minimum distance of 0.1 mm to the intersection point
|
||||
const dx = (intersection.x - newPosition.x) * columnPixelSpacing;
|
||||
const dy = (intersection.y - newPosition.y) * rowPixelSpacing;
|
||||
const distance = Math.sqrt((dx * dx) + (dy * dy));
|
||||
const newAngle = Math.atan2(newPosition.y - intersection.y, newPosition.x - intersection.x);
|
||||
if (angle.toFixed(8) !== newAngle.toFixed(8) || distance < Math.abs(mmLimit)) {
|
||||
Object.assign(newPosition, {
|
||||
x: intersection.x - Math.cos(angle) * mmLimit * Math.sign(stepX),
|
||||
y: intersection.y - Math.sin(angle) * mmLimit * Math.sign(stepY)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return newPosition;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
const removeToolDataWithMeasurementId = (imageId, toolType, measurementId) => {
|
||||
OHIF.log.info('removeToolDataWithMeasurementId');
|
||||
const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
|
||||
|
||||
// Find any related toolData
|
||||
if (!toolState[imageId] || !toolState[imageId][toolType]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolData = toolState[imageId][toolType].data;
|
||||
if (!toolData.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search toolData for entries linked to the specified Measurement
|
||||
const toRemove = [];
|
||||
toolData.forEach(function(measurement, index) {
|
||||
if (measurement.id === measurementId ||
|
||||
measurement._id === measurementId) {
|
||||
toRemove.push(index);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
OHIF.log.info('Removing Indices: ');
|
||||
OHIF.log.info(toRemove);
|
||||
|
||||
// If any toolData entries need to be removed, splice them from
|
||||
// the toolData array
|
||||
toRemove.forEach(function(index) {
|
||||
toolData.splice(index, 1);
|
||||
});
|
||||
|
||||
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(toolState);
|
||||
};
|
||||
|
||||
OHIF.lesiontracker.clearMeasurementTimepointData = (measurementId, timepointId) => {
|
||||
const data = Measurements.findOne(measurementId);
|
||||
|
||||
// Clear the Measurement data for this timepoint
|
||||
const imageId = data.timepoints[timepointId].imageId;
|
||||
const toolType = data.toolType;
|
||||
removeToolDataWithMeasurementId(imageId, toolType, measurementId);
|
||||
|
||||
// Update any viewports that are currently displaying this imageId
|
||||
const enabledElements = cornerstone.getEnabledElementsByImageId(imageId);
|
||||
enabledElements.forEach(enabledElement => cornerstone.updateImage(enabledElement.element));
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { _ } from 'meteor/underscore';
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
OHIF.lesiontracker.configureTargetToolsHandles = () => {
|
||||
const toggleLabel = (measurementData, eventData, doneCallback) => {
|
||||
delete measurementData.isCreating;
|
||||
|
||||
if (OHIF.lesiontracker.removeMeasurementIfInvalid(measurementData, eventData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getHandlePosition = key => _.pick(measurementData.handles[key], ['x', 'y']);
|
||||
const start = getHandlePosition('start');
|
||||
const end = getHandlePosition('end');
|
||||
const getDirection = axis => start[axis] < end[axis] ? 1 : -1;
|
||||
const position = OHIF.cornerstone.pixelToPage(eventData.element, end);
|
||||
|
||||
OHIF.measurements.toggleLabelButton({
|
||||
measurement: measurementData,
|
||||
element: eventData.element,
|
||||
measurementApi: OHIF.viewer.measurementApi,
|
||||
position: position,
|
||||
direction: {
|
||||
x: getDirection('x'),
|
||||
y: getDirection('y')
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const callbackConfig = {
|
||||
// TODO: Check the position for these, the Add Label button position seems very awkward
|
||||
getMeasurementLocationCallback: toggleLabel,
|
||||
changeMeasurementLocationCallback: toggleLabel,
|
||||
};
|
||||
|
||||
// TODO: Reconcile this with the configuration in toolManager it would be better to have this
|
||||
// all in one place.
|
||||
const appendConfig = toolType => {
|
||||
const tool = cornerstoneTools[toolType];
|
||||
const toolConfig = tool.getConfiguration();
|
||||
const config = Object.assign({}, toolConfig, callbackConfig);
|
||||
|
||||
tool.setConfiguration(config);
|
||||
};
|
||||
|
||||
// Append the callback configuration to bidirectional tool
|
||||
appendConfig('bidirectional');
|
||||
|
||||
// Append the callback configuration to CR and UN tools
|
||||
appendConfig('targetCR');
|
||||
appendConfig('targetUN');
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
// StudyList-related functions
|
||||
import './studylist/studylistModification.js';
|
||||
|
||||
// Bidirectional tool utility functions
|
||||
import './bidirectional';
|
||||
|
||||
// Library functions
|
||||
import './pixelSpacingAutorunCheck.js';
|
||||
import './removeMeasurementIfInvalid.js';
|
||||
import './toggleLesionTrackerTools.js';
|
||||
import './clearMeasurementTimepointData.js';
|
||||
import './configureTargetToolsHandles.js';
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Session } from 'meteor/session';
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
OHIF.lesiontracker.pixelSpacingAutorunCheck = () => {
|
||||
OHIF.log.info('lesionTool button change autorun');
|
||||
|
||||
// Get oncology tools
|
||||
const $oncologyTools = $('button#lesion, button#nonTarget');
|
||||
|
||||
// TODO: Set activeViewport for empty viewport element
|
||||
const activeViewportIndex = window.store.getState().viewports.activeViewport;
|
||||
if (activeViewportIndex === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = $('.imageViewerViewport').get(activeViewportIndex);
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
let enabledElement;
|
||||
try {
|
||||
enabledElement = cornerstone.getEnabledElement(element);
|
||||
} catch(error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check value of rowPixelSpacing & columnPixelSpacing to define as unavailable
|
||||
if (!enabledElement ||
|
||||
!enabledElement.image ||
|
||||
!enabledElement.image.rowPixelSpacing ||
|
||||
!enabledElement.image.columnPixelSpacing) {
|
||||
// Disable Lesion Buttons
|
||||
$oncologyTools.prop('disabled', true);
|
||||
} else {
|
||||
// Enable Lesion Buttons
|
||||
$oncologyTools.prop('disabled', false);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import { _ } from 'meteor/underscore';
|
||||
|
||||
OHIF.lesiontracker.removeMeasurementIfInvalid = (measurementData, eventData) => {
|
||||
const handles = measurementData.handles;
|
||||
const start = _.pick(handles.start, ['x', 'y']);
|
||||
const end = _.pick(handles.end, ['x', 'y']);
|
||||
const element = eventData.element;
|
||||
const toolType = measurementData.toolType;
|
||||
if (_.isEqual(start, end)) {
|
||||
cornerstoneTools.removeToolState(element, toolType, measurementData);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Router } from 'meteor/clinical:router';
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
Meteor.startup(function() {
|
||||
if (!OHIF.studylist) return;
|
||||
|
||||
OHIF.studylist.callbacks.dblClickOnStudy = dblClickOnStudy;
|
||||
OHIF.studylist.callbacks.middleClickOnStudy = dblClickOnStudy;
|
||||
|
||||
OHIF.studylist.timepointApi = new OHIF.measurements.TimepointApi();
|
||||
});
|
||||
|
||||
/**
|
||||
* Lesion Tracker method including Timepoints / other studies
|
||||
*/
|
||||
const dblClickOnStudy = data => {
|
||||
// Find the relevant timepoint given the clicked-on study
|
||||
const timepointApi = OHIF.studylist.timepointApi;
|
||||
if (!timepointApi) {
|
||||
OHIF.log.warn('No timepoint api on dbl-clicked study?');
|
||||
return;
|
||||
}
|
||||
|
||||
const timepoint = timepointApi.study(data.studyInstanceUid)[0];
|
||||
if (timepoint) {
|
||||
Router.go('viewerTimepoint', { timepointId: timepoint.timepointId });
|
||||
} else {
|
||||
Router.go('viewerStudies', { studyInstanceUids: data.studyInstanceUid });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
|
||||
/**
|
||||
* Show / hide lesion tracker tools
|
||||
*/
|
||||
|
||||
let previousStates;
|
||||
let previousActiveTool;
|
||||
|
||||
let toolsShown = true;
|
||||
|
||||
OHIF.lesiontracker.toggleLesionTrackerTools = () => {
|
||||
const toolManager = Viewerbase.toolManager;
|
||||
|
||||
if (toolsShown === true) {
|
||||
// Save the current settings for later
|
||||
previousStates = toolManager.getToolDefaultStates();
|
||||
previousActiveTool = toolManager.getActiveTool();
|
||||
|
||||
// Hide the tools (set them all to disabled)
|
||||
const toolDefaultStates = {
|
||||
activate: ['deleteLesionKeyboardTool'],
|
||||
deactivate: [],
|
||||
enable: [],
|
||||
disable: ['bidirectional', 'nonTarget', 'length', 'targetCR', 'targetUN']
|
||||
};
|
||||
|
||||
toolManager.setToolDefaultStates(toolDefaultStates);
|
||||
|
||||
// Using setActiveTool with no arguments activates the
|
||||
// default tool on all available viewports
|
||||
toolManager.setActiveTool();
|
||||
|
||||
toolsShown = false;
|
||||
} else {
|
||||
// Show the tools (reload previous states)
|
||||
toolManager.setToolDefaultStates(previousStates);
|
||||
|
||||
// Using setActiveTool with no elements specified activates
|
||||
// the specified tool on all available viewports
|
||||
toolManager.setActiveTool(previousActiveTool);
|
||||
|
||||
toolsShown = true;
|
||||
}
|
||||
};
|
||||
|
||||
OHIF.lesiontracker.toggleLesionTrackerToolsButtons = (isEnabled) => {
|
||||
const toolManager = Viewerbase.toolManager;
|
||||
const toolStates = previousStates || toolManager.getToolDefaultStates();
|
||||
|
||||
if (isEnabled) {
|
||||
toolStates.disabledToolButtons = [];
|
||||
OHIF.lesiontracker.toggleLesionTrackerToolsHotKeys(true);
|
||||
} else {
|
||||
toolStates.disabledToolButtons = ['bidirectional', 'nonTarget', 'targetCR', 'targetUN',
|
||||
'toggleHUD', 'toggleTrial', 'toolbarSectionEntry', 'toggleMeasurements'];
|
||||
OHIF.lesiontracker.toggleLesionTrackerToolsHotKeys(false);
|
||||
}
|
||||
|
||||
// Reload the updated previous or default states
|
||||
toolManager.setToolDefaultStates(toolStates);
|
||||
|
||||
// Reset the active tool if disabled
|
||||
if (!isEnabled) {
|
||||
toolManager.setActiveTool();
|
||||
}
|
||||
};
|
||||
|
||||
OHIF.lesiontracker.toggleLesionTrackerToolsHotKeys = (isEnabled) => {
|
||||
// The hotkey can also be an array (e.g. ["NUMPAD0", "0"])
|
||||
OHIF.viewer.defaultHotkeys = OHIF.viewer.defaultHotkeys || {};
|
||||
|
||||
if (isEnabled) {
|
||||
OHIF.viewer.defaultHotkeys.toggleLesionTrackerTools = 'O';
|
||||
OHIF.viewer.defaultHotkeys.bidirectional = 'T'; // Target
|
||||
OHIF.viewer.defaultHotkeys.nonTarget = 'N'; // Non-target
|
||||
} else {
|
||||
OHIF.viewer.defaultHotkeys.toggleLesionTrackerTools = null;
|
||||
OHIF.viewer.defaultHotkeys.bidirectional = null; // Target
|
||||
OHIF.viewer.defaultHotkeys.nonTarget = null; // Non-target
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user