Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat measurement visibility #2773

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion extensions/cornerstone/src/OHIFCornerstoneViewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,14 @@ function OHIFCornerstoneViewport({
isOverlayVisible={true}
loadingIndicatorComponent={ViewportLoadingIndicator}
viewportOverlayComponent={props => {
const { imageIndex } = props;
const instances = displaySet.images || displaySet.other;
const image = instances && imageIndex > 0 &&
imageIndex <= instances.length && instances[imageIndex - 1];
const viewportProps = { ...props, cine, isPlaying, frameRate, image };
return (
<ViewportOverlay
{...props}
{...viewportProps}
activeTools={ToolBarService.getActiveTools()}
/>
);
Expand Down
111 changes: 18 additions & 93 deletions extensions/cornerstone/src/ViewportOverlay.js
Original file line number Diff line number Diff line change
@@ -1,95 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core';
import classnames from 'classnames';
import ConfigPoint from 'config-point'
import { ViewportOverlay } from '@ohif/ui'

const ViewportOverlay = ({
imageId,
scale,
windowWidth,
windowCenter,
imageIndex,
stackSize,
activeTools,
}) => {
const topLeft = 'top-viewport left-viewport';
const topRight = 'top-viewport right-viewport-scrollbar';
const bottomRight = 'bottom-viewport right-viewport-scrollbar';
const bottomLeft = 'bottom-viewport left-viewport';
const overlay = 'absolute pointer-events-none';

const isZoomActive = activeTools.includes('Zoom');
const isWwwcActive = activeTools.includes('Wwwc');

if (!imageId) {
return null;
const { cornerstoneViewportOverlayConfig } = ConfigPoint.register({
cornerstoneViewportOverlayConfig: {
configBase: 'viewportOverlayConfig',
}

// TODO: this component should be presentational only. Right now it has a weird dependency on Cornerstone
const generalImageModule =
cornerstone.metaData.get('generalImageModule', imageId) || {};
const { instanceNumber } = generalImageModule;

return (
<div className="text-primary-light">
<div
data-cy={'viewport-overlay-top-left'}
className={classnames(overlay, topLeft)}
>
{isZoomActive && (
<div className="flex flex-row">
<span className="mr-1">Zoom:</span>
<span className="font-light">{scale.toFixed(2)}x</span>
</div>
)}
{isWwwcActive && (
<div className="flex flex-row">
<span className="mr-1">W:</span>
<span className="ml-1 mr-2 font-light">
{windowWidth.toFixed(0)}
</span>
<span className="mr-1">L:</span>
<span className="ml-1 font-light">{windowCenter.toFixed(0)}</span>
</div>
)}
</div>
<div
data-cy={'viewport-overlay-top-right'}
className={classnames(overlay, topRight)}
>
{stackSize > 1 && (
<div className="flex flex-row">
<span className="mr-1">I:</span>
<span className="font-light">
{`${instanceNumber} (${imageIndex}/${stackSize})`}
</span>
</div>
)}
</div>
<div
data-cy={'viewport-overlay-bottom-right'}
className={classnames(overlay, bottomRight)}
></div>
<div
data-cy={'viewport-overlay-bottom-left'}
className={classnames(overlay, bottomLeft)}
></div>
</div>
);
};

ViewportOverlay.propTypes = {
scale: PropTypes.number.isRequired,
windowWidth: PropTypes.number.isRequired,
windowCenter: PropTypes.number.isRequired,
imageId: PropTypes.string.isRequired,
imageIndex: PropTypes.number.isRequired,
stackSize: PropTypes.number.isRequired,
activeTools: PropTypes.arrayOf(PropTypes.string),
};

ViewportOverlay.defaultProps = {
activeTools: [],
};

export default ViewportOverlay;
});

/**
* Default to using the cornerstoneViewportOverlayConfig.
* @param {*} props
* @returns Viewport overlay for cornerstone
*/
const CornerstoneViewportOverlay = props => ViewportOverlay({
viewportOverlayConfig: cornerstoneViewportOverlayConfig,
...props,
});

export default CornerstoneViewportOverlay;
131 changes: 103 additions & 28 deletions extensions/cornerstone/src/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const TOOL_TYPES_WITH_CONTEXT_MENU = [
'SREllipticalRoi',
];

const removeUndefined = updates =>
Object.keys(updates).forEach((key) => (updates[key] === undefined) && delete updates[key]);


const _refreshViewports = () =>
cs.getEnabledElements().forEach(({ element }) => cs.updateImage(element));

Expand Down Expand Up @@ -68,40 +72,58 @@ export default function init({
UIDialogService,
MeasurementService,
DisplaySetService,
ToolBarService,
UserAuthenticationService,
} = servicesManager.services;
const tools = getTools();

console.log(servicesManager.services);

/* Measurement Service */
const measurementServiceSource = _connectToolsToMeasurementService(
MeasurementService,
DisplaySetService
);

const onRightClick = event => {
const showContextMenu = contextMenuProps => {
if (!UIDialogService) {
console.warn('Unable to show dialog; no UI Dialog Service available.');
return;
}
const { event, subMenu } = contextMenuProps;

const onGetMenuItems = defaultMenuItems => {
const onGetMenuItems = (menus, props) => {
const { element, currentPoints } = event.detail;
const nearbyToolData = commandsManager.runCommand('getNearbyToolData', {
element,
canvasCoordinates: currentPoints.canvas,
availableToolTypes: TOOL_TYPES_WITH_CONTEXT_MENU,
});

let menuItems = [];
if (nearbyToolData) {
defaultMenuItems.forEach(item => {
item.value = nearbyToolData;
menuItems.push(item);
const { subMenu } = props;
const nearbyToolData = contextMenuProps.nearbyToolData ||
commandsManager.runCommand('getNearbyToolData', {
element,
canvasCoordinates: currentPoints.canvas,
availableToolTypes: TOOL_TYPES_WITH_CONTEXT_MENU,
});

const menuItems = [];
const defaultSite = ContextMenuMeasurements.getFindingSite({ ...props, nearbyToolData });
const subProps = { props, nearbyToolData, event, defaultSite, };
const menu = subMenu ?
menus.find(menu => menu.id === subMenu)
: menus.find(menu => !menu.selector || menu.selector(subProps));
if (!menu) {
console.log("No menu found", subMenu);
return undefined;
}
menu.items.forEach(item => {
const toAdd = { ...item, value: nearbyToolData };
if (!item.action) {
toAdd.action = (value) => {
props.onClose();
const action = props[`on${item.actionType}`];
if (action) {
action.call(null, toAdd, value, subProps);
} else {
console.warn("No action defined for", item);
}
}
}
menuItems.push(toAdd);
});

return menuItems;
};
Expand All @@ -114,14 +136,22 @@ export default function init({
isDraggable: false,
preservePosition: false,
defaultPosition: _getDefaultPosition(event.detail),
event,
content: ContextMenuMeasurements,
onClickOutside: () => {
UIDialogService.dismiss({ id: 'context-menu' });
CONTEXT_MENU_OPEN = false;
},
contentProps: {
onGetMenuItems,
event,
subMenu,
eventData: event.detail,

/**
* Remove a measurement.
* @param {object} item to be removed
*/
onDelete: item => {
const { tool: measurementData, toolType } = item.value;

Expand Down Expand Up @@ -164,6 +194,52 @@ export default function init({
CONTEXT_MENU_OPEN = false;
UIDialogService.dismiss({ id: 'context-menu' });
},

/**
* Displays a sub-menu, removing this menu
* @param {*} item
* @param {*} obj
* @param {*} props
*/
onSubMenu: (item, value, subProps) => {
if (!value.subMenu) {
console.warn("No submenu defined for", item, value, subProps);
return;
}
showContextMenu({ ...contextMenuProps, subMenu: value.subMenu });
},

/** Applies site selection details to this annotation or overall image */
onSiteSelection: (item, value) => {
const { tool: measurementData } = item.value;

const { color, findingSite, finding, findingUpdates } = value;
const findingText = (finding || findingSite) ? `${finding?.text || ''} ${findingSite?.text || ''}` : undefined;
const updates = {
...findingUpdates,
color,
findingText,
findingSite,
finding
};
removeUndefined(updates);

const measurement = MeasurementService.getMeasurement(
measurementData.id
);

const updatedMeasurement = Object.assign({}, measurement, updates);

console.log("updatedMeasurement=", updatedMeasurement);

MeasurementService.update(
updatedMeasurement.id,
updatedMeasurement,
true
);
},

/** Sets the label on a measurement. */
onSetLabel: item => {
const { tool: measurementData } = item.value;

Expand Down Expand Up @@ -198,17 +274,12 @@ export default function init({
});
};

const onTouchPress = event => {
if (!UIDialogService) {
console.warn('Unable to show dialog; no UI Dialog Service available.');
return;
}
const onRightClick = event => {
showContextMenu({ event, content: ContextMenuMeasurements, nearbyToolData: undefined });
};

UIDialogService.create({
eventData: event.detail,
content: ContextMenuMeasurements,
contentProps: { isTouchEvent: true },
});
const onTouchPress = event => {
showContextMenu({ event, content: ContextMenuMeasurements, nearbyToolData: undefined, isTouchEvent: true });
};

const resetContextMenu = () => {
Expand Down Expand Up @@ -292,7 +363,7 @@ export default function init({
// THIS
// is a way for extensions that "depend" on this extension to notify it of
// new cornerstone enabled elements so it's commands continue to work.
const handleOhifCornerstoneEnabledElementEvent = function(evt) {
const handleOhifCornerstoneEnabledElementEvent = function (evt) {
const { context, viewportIndex, enabledElement } = evt.detail;

setEnabledElement(viewportIndex, enabledElement, context);
Expand Down Expand Up @@ -445,7 +516,7 @@ const _connectToolsToMeasurementService = (
MeasurementService.subscribe(
MEASUREMENT_UPDATED,
({ source, measurement, notYetUpdatedAtSource }) => {
const { id, label } = measurement;
const { id, label, color, visible, active } = measurement;

if (
source.name == 'CornerstoneTools' &&
Expand All @@ -457,7 +528,11 @@ const _connectToolsToMeasurementService = (
const cornerstoneMeasurement = getCornerstoneMeasurementById(id);

if (cornerstoneMeasurement) {
// side effect, cs should provide api to prevent changing by ref.
cornerstoneMeasurement.label = label;
cornerstoneMeasurement.color = color;
cornerstoneMeasurement.visible = visible;
cornerstoneMeasurement.active = active;
if (cornerstoneMeasurement.hasOwnProperty('text')) {
// Deal with the weird case of ArrowAnnotate.
cornerstoneMeasurement.text = label;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const ArrowAnnotate = {
label: measurementData.text,
description: measurementData.description,
unit: measurementData.unit,
active: measurementData.active,
visible: measurementData.visible,
text: measurementData.text,
type: getValueTypeFromToolType(tool),
points: getPointsFromHandles(measurementData.handles),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const Bidirectional = {
label: measurementData.label,
description: measurementData.description,
unit: measurementData.unit,
active: measurementData.active,
visible: measurementData.visible,
shortestDiameter: measurementData.shortestDiameter,
longestDiameter: measurementData.longestDiameter,
type: getValueTypeFromToolType(tool),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ const EllipticalRoi = {
label: measurementData.label,
description: measurementData.description,
unit: measurementData.unit,
active: measurementData.active,
visible: measurementData.visible,
area: cachedStats && cachedStats.area,
mean: cachedStats && cachedStats.mean,
stdDev: cachedStats && cachedStats.stdDev,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import getPointsFromHandles from './utils/getPointsFromHandles';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';

const Length = {
toAnnotation: (measurement, definition) => {},
toAnnotation: (measurement, definition) => {
console.log('toAnnotation', measurement, definition);
},

/**
* Maps cornerstone annotation event data to measurement service format.
Expand Down Expand Up @@ -51,6 +53,9 @@ const Length = {
label: measurementData.label,
description: measurementData.description,
unit: measurementData.unit,
color: measurementData.color,
active: measurementData.active,
visible: measurementData.visible,
length: measurementData.length,
type: getValueTypeFromToolType(tool),
points: getPointsFromHandles(measurementData.handles),
Expand Down
Loading