* Filter segs and fetch correct metadata. * Initiate seg loading logic on series load into viewport. Need to write. * Automatic consumption of SEG functional. * Pull in master. * Fix issues after pulling in master. * Clean up old segmentation handlers. * WIP * WIP * Poppulate viewport with metadata. * Show hide/segmentation in react-vtkjs-viewport. * feat: 🎸 View segmentations in vtkjs viewport * Filter segs and fetch correct metadata. * Initiate seg loading logic on series load into viewport. Need to write. * Automatic consumption of SEG functional. * Pull in master. * Fix issues after pulling in master. * Clean up old segmentation handlers. * WIP * WIP * Poppulate viewport with metadata. * Show hide/segmentation in react-vtkjs-viewport. * feat: 🎸 View segmentations in vtkjs viewport * Rebase on master. * format format format * add basic extension parts * Updated yarn lock * Fetch derived/referenced displaysets in sidepanel * Add buttons, expose seriesDescription, write notes for Danny. * Add growcut tool extention. -> Move to a seperate repo. * Change some text fields. * expose dialog provider * hide draggable area * No pointer events for now; need to revisit * Don't filter seg from the outset for now * fiddling about * fix path to series data * fix logic to determine next available labelmapindex * Ability to swtich between labelmaps * Return a promise so we can make sure our labelmap is loaded before pulling metadata * Better UI to toggle labelmaps * Fix accidental untoggle of active labelmap for ui * Remove brush tool from cornerstone extension * Add cstools in preInit for seg extension * Add buttons for seg magic * Hide the measurements panel for now * Slightly better seg/scissor icons * Add eraser * ability to switch segments * seg color / switcher * Fix vtk labelmap cache. * sidepanel should scroll if there's overflow * Use a fancier range slider * Slightly prettier buttons * Push in progress * A bit more reactive; handle no-meta * Update to vtk.js 11.14.0 and react-vtkjs-viewport 0.6.0 * chore: kickstart preview * Fix conflict issues * Refactor panel * Isolate increment functions * Wip segment table * Hide selector and add scroll to segmentation * Increase width of segment description * Hide not used items * Add new select for segmentations * Align ui * Fix default segmentation * Fix broken import test * Get measurements panel back and add spacing to grouped buttons * Hide brush and selector and refresh when switching segmentations * Add pointer events back and fix broken tests for measurement table * Update segment list when switching humbnails * CR Update: Minor UI adjustments * CR Update: Set active segment * Move components to components folder * Add new settings ui * Integrate configuration * Improve styles * Float numbers * Improve css * Refresh settings menu if side panel changed * Fix settings rendering logic and remove comments * Extract components * Remove not used tools * Remove non used extension * Use style variables and remove comments * Remove non used dep from lock * Remove tools * Remove segmentation from toolbar * Add todo and brushradius from config * Fix packagejson growcut import * Remove webpack config already defined in parent * Update package json description * Change component name to british english to us * Improve prop types * Rename cornerstone label map event * Scope css * Extract and add doc to helper * Update jsdocs for getDerivedDatasets * Add proptypes and cr updates * Update tablistitem proptypes * config is readonly, rollback destructuring * Remove event for side panel * Add jsdocs for util * Extract getimageid to studymetada * Use ohif log instead of console * Remove specific style from side panel * Fix default props for brush radius * Descructure configuration props from settings component to avoid invalid props * Update proptypes to be required (settings) * Update readme * Remove commandsmodule * Get configuration module inside the component * Set selected segment * Rename extension and use modality to filter * Fix for failing tests (#1423) * hello world * Finish rebase for Igor. * Re-fix cornerstoneTools version that got messed up in merge. * Fix reactivity of isDisabled, write implementation for the SEG panel. * Nuke yarn.lock to stack CST to 4.12.0 * Remove debugger statements. * fix: bump cornerstone-tools version to address toFixed issue * test: force tests for MISTER^MR to navigate to study directly * test: remove eraser tool test * test: count is off by one * test: don't save SR state for the time being * test: disable more tests Co-authored-by: James A. Petts <jamesapetts@gmail.com> Co-authored-by: dannyrb <danny.ri.brown@gmail.com> Co-authored-by: igoroctaviano] <igoroctaviano@users.noreply.github.com> Co-authored-by: Mirna Silva <mirna.silva@radicalimaging.com>
277 lines
8.6 KiB
JavaScript
277 lines
8.6 KiB
JavaScript
import OHIF from '@ohif/core';
|
|
import { SimpleDialog } from '@ohif/ui';
|
|
import cornerstone from 'cornerstone-core';
|
|
import csTools from 'cornerstone-tools';
|
|
import merge from 'lodash.merge';
|
|
import initCornerstoneTools from './initCornerstoneTools.js';
|
|
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
|
|
|
/**
|
|
*
|
|
* @param {Object} servicesManager
|
|
* @param {Object} configuration
|
|
* @param {Object|Array} configuration.csToolsConfig
|
|
*/
|
|
export default function init({ servicesManager, configuration }) {
|
|
const { UIDialogService, MeasurementService } = servicesManager.services;
|
|
|
|
const callInputDialog = (data, event, callback) => {
|
|
if (UIDialogService) {
|
|
let dialogId = UIDialogService.create({
|
|
centralize: true,
|
|
isDraggable: false,
|
|
content: SimpleDialog.InputDialog,
|
|
useLastPosition: false,
|
|
showOverlay: true,
|
|
contentProps: {
|
|
title: 'Enter your annotation',
|
|
label: 'New label',
|
|
measurementData: data ? { description: data.text } : {},
|
|
onClose: () => UIDialogService.dismiss({ id: dialogId }),
|
|
onSubmit: value => {
|
|
callback(value);
|
|
UIDialogService.dismiss({ id: dialogId });
|
|
},
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
const { csToolsConfig } = configuration;
|
|
const metadataProvider = OHIF.cornerstone.metadataProvider;
|
|
|
|
cornerstone.metaData.addProvider(
|
|
metadataProvider.get.bind(metadataProvider),
|
|
9999
|
|
);
|
|
|
|
// ~~
|
|
const defaultCsToolsConfig = csToolsConfig || {
|
|
globalToolSyncEnabled: true,
|
|
showSVGCursors: true,
|
|
autoResizeViewports: false,
|
|
};
|
|
|
|
initCornerstoneTools(defaultCsToolsConfig);
|
|
|
|
const toolsGroupedByType = {
|
|
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
|
|
annotations: [
|
|
csTools.ArrowAnnotateTool,
|
|
csTools.BidirectionalTool,
|
|
csTools.LengthTool,
|
|
csTools.AngleTool,
|
|
csTools.FreehandRoiTool,
|
|
csTools.EllipticalRoiTool,
|
|
csTools.DragProbeTool,
|
|
csTools.RectangleRoiTool,
|
|
],
|
|
other: [
|
|
csTools.PanTool,
|
|
csTools.ZoomTool,
|
|
csTools.WwwcTool,
|
|
csTools.WwwcRegionTool,
|
|
csTools.MagnifyTool,
|
|
csTools.StackScrollTool,
|
|
csTools.StackScrollMouseWheelTool,
|
|
csTools.OverlayTool,
|
|
],
|
|
};
|
|
|
|
let tools = [];
|
|
Object.keys(toolsGroupedByType).forEach(toolsGroup =>
|
|
tools.push(...toolsGroupedByType[toolsGroup])
|
|
);
|
|
|
|
/* Measurement Service */
|
|
_connectToolsToMeasurementService(MeasurementService);
|
|
|
|
/* Add extension tools configuration here. */
|
|
const internalToolsConfig = {
|
|
ArrowAnnotate: {
|
|
configuration: {
|
|
getTextCallback: (callback, eventDetails) =>
|
|
callInputDialog(null, eventDetails, callback),
|
|
changeTextCallback: (data, eventDetails, callback) =>
|
|
callInputDialog(data, eventDetails, callback),
|
|
},
|
|
},
|
|
};
|
|
|
|
/* Abstract tools configuration using extension configuration. */
|
|
const parseToolProps = (props, tool) => {
|
|
const { annotations } = toolsGroupedByType;
|
|
// An alternative approach would be to remove the `drawHandlesOnHover` config
|
|
// from the supported configuration properties in `cornerstone-tools`
|
|
const toolsWithHideableHandles = annotations.filter(
|
|
tool => !['RectangleRoiTool', 'EllipticalRoiTool'].includes(tool.name)
|
|
);
|
|
|
|
let parsedProps = { ...props };
|
|
|
|
/**
|
|
* drawHandles - Never/Always show handles
|
|
* drawHandlesOnHover - Only show handles on handle hover (pointNearHandle)
|
|
*
|
|
* Does not apply to tools where handles aren't placed in predictable
|
|
* locations.
|
|
*/
|
|
if (
|
|
configuration.hideHandles !== false &&
|
|
toolsWithHideableHandles.includes(tool)
|
|
) {
|
|
if (props.configuration) {
|
|
parsedProps.configuration.drawHandlesOnHover = true;
|
|
} else {
|
|
parsedProps.configuration = { drawHandlesOnHover: true };
|
|
}
|
|
}
|
|
|
|
return parsedProps;
|
|
};
|
|
|
|
/* Add tools with its custom props through extension configuration. */
|
|
tools.forEach(tool => {
|
|
const toolName = tool.name.replace('Tool', '');
|
|
const externalToolsConfig = configuration.tools || {};
|
|
const externalToolProps = externalToolsConfig[toolName] || {};
|
|
const internalToolProps = internalToolsConfig[toolName] || {};
|
|
const props = merge(
|
|
internalToolProps,
|
|
parseToolProps(externalToolProps, tool)
|
|
);
|
|
csTools.addTool(tool, props);
|
|
});
|
|
|
|
// TODO -> We need a better way to do this with maybe global tool state setting all tools passive.
|
|
const BaseAnnotationTool = csTools.importInternal('base/BaseAnnotationTool');
|
|
tools.forEach(tool => {
|
|
if (tool.prototype instanceof BaseAnnotationTool) {
|
|
// BaseAnnotationTool would likely come from csTools lib exports
|
|
const toolName = new tool().name;
|
|
csTools.setToolPassive(toolName); // there may be a better place to determine name; may not be on uninstantiated class
|
|
}
|
|
});
|
|
|
|
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
|
|
csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
|
|
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });
|
|
csTools.setToolActive('StackScrollMouseWheel', {}); // TODO: Empty options should not be required
|
|
csTools.setToolActive('PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options
|
|
csTools.setToolActive('ZoomTouchPinch', {});
|
|
csTools.setToolEnabled('Overlay', {});
|
|
}
|
|
|
|
const _initMeasurementService = measurementService => {
|
|
/* Initialization */
|
|
const { toAnnotation, toMeasurement } = measurementServiceMappingsFactory(
|
|
measurementService
|
|
);
|
|
const csToolsVer4MeasurementSource = measurementService.createSource(
|
|
'CornerstoneTools',
|
|
'4'
|
|
);
|
|
|
|
/* Matching Criterias */
|
|
const matchingCriteria = {
|
|
valueType: measurementService.VALUE_TYPES.POLYLINE,
|
|
points: 2,
|
|
};
|
|
|
|
/* Mappings */
|
|
measurementService.addMapping(
|
|
csToolsVer4MeasurementSource,
|
|
'Length',
|
|
matchingCriteria,
|
|
toAnnotation,
|
|
toMeasurement
|
|
);
|
|
|
|
return csToolsVer4MeasurementSource;
|
|
};
|
|
|
|
const _connectToolsToMeasurementService = measurementService => {
|
|
const csToolsVer4MeasurementSource = _initMeasurementService(
|
|
measurementService
|
|
);
|
|
const {
|
|
id: sourceId,
|
|
addOrUpdate,
|
|
getAnnotation,
|
|
} = csToolsVer4MeasurementSource;
|
|
|
|
/* Measurement Service Events */
|
|
cornerstone.events.addEventListener(
|
|
cornerstone.EVENTS.ELEMENT_ENABLED,
|
|
event => {
|
|
const {
|
|
MEASUREMENT_ADDED,
|
|
MEASUREMENT_UPDATED,
|
|
} = measurementService.EVENTS;
|
|
|
|
measurementService.subscribe(
|
|
MEASUREMENT_ADDED,
|
|
({ source, measurement }) => {
|
|
if (![sourceId].includes(source.id)) {
|
|
const annotation = getAnnotation('Length', measurement.id);
|
|
|
|
console.log(
|
|
'Measurement Service [Cornerstone]: Measurement added',
|
|
measurement
|
|
);
|
|
console.log('Mapped annotation:', annotation);
|
|
}
|
|
}
|
|
);
|
|
|
|
measurementService.subscribe(
|
|
MEASUREMENT_UPDATED,
|
|
({ source, measurement }) => {
|
|
if (![sourceId].includes(source.id)) {
|
|
const annotation = getAnnotation('Length', measurement.id);
|
|
|
|
console.log(
|
|
'Measurement Service [Cornerstone]: Measurement updated',
|
|
measurement
|
|
);
|
|
console.log('Mapped annotation:', annotation);
|
|
}
|
|
}
|
|
);
|
|
|
|
const addOrUpdateMeasurement = csToolsAnnotation => {
|
|
try {
|
|
const { toolName, toolType, measurementData } = csToolsAnnotation;
|
|
const csTool = toolName || measurementData.toolType || toolType;
|
|
csToolsAnnotation.id = measurementData._measurementServiceId;
|
|
const measurementServiceId = addOrUpdate(csTool, csToolsAnnotation);
|
|
|
|
if (!measurementData._measurementServiceId) {
|
|
addMeasurementServiceId(measurementServiceId, csToolsAnnotation);
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to add or update measurement:', error);
|
|
}
|
|
};
|
|
|
|
const addMeasurementServiceId = (id, csToolsAnnotation) => {
|
|
const { measurementData } = csToolsAnnotation;
|
|
Object.assign(measurementData, { _measurementServiceId: id });
|
|
};
|
|
|
|
[
|
|
csTools.EVENTS.MEASUREMENT_ADDED,
|
|
csTools.EVENTS.MEASUREMENT_MODIFIED,
|
|
].forEach(csToolsEvtName => {
|
|
event.detail.element.addEventListener(
|
|
csToolsEvtName,
|
|
({ detail: csToolsAnnotation }) => {
|
|
console.log(`Cornerstone Element Event: ${csToolsEvtName}`);
|
|
addOrUpdateMeasurement(csToolsAnnotation);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
);
|
|
};
|