feat(Calibration Tool): add a calibration tool that can manually recalibrate an image and measurements on it (#3136)
* minor bug fix - reading modalities * react error log fix - button props * react warn fix - Typography color prop * calibration tool without persistence * added toolbar button for calibration tool * icon change for calibration tool * important bug fix for calibration - length and calibration rate calculation * fix weird autocorrection * [fix] bugs after merging upstream * [refactor] conform to the new service names
This commit is contained in:
parent
7a36363735
commit
b15988359f
@ -12,12 +12,12 @@ import {
|
|||||||
} from '@cornerstonejs/tools';
|
} from '@cornerstonejs/tools';
|
||||||
import { ServicesManager } from '@ohif/core';
|
import { ServicesManager } from '@ohif/core';
|
||||||
|
|
||||||
import { getEnabledElement as OHIFgetEnabledElement } from './state';
|
|
||||||
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
||||||
import callInputDialog from './utils/callInputDialog';
|
import callInputDialog from './utils/callInputDialog';
|
||||||
import { setColormap } from './utils/colormap/transferFunctionHelpers';
|
import { setColormap } from './utils/colormap/transferFunctionHelpers';
|
||||||
import toggleMPRHangingProtocol from './utils/mpr/toggleMPRHangingProtocol';
|
import toggleMPRHangingProtocol from './utils/mpr/toggleMPRHangingProtocol';
|
||||||
import toggleStackImageSync from './utils/stackSync/toggleStackImageSync';
|
import toggleStackImageSync from './utils/stackSync/toggleStackImageSync';
|
||||||
|
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
|
||||||
|
|
||||||
const commandsModule = ({ servicesManager }) => {
|
const commandsModule = ({ servicesManager }) => {
|
||||||
const {
|
const {
|
||||||
@ -32,10 +32,7 @@ const commandsModule = ({ servicesManager }) => {
|
|||||||
} = (servicesManager as ServicesManager).services;
|
} = (servicesManager as ServicesManager).services;
|
||||||
|
|
||||||
function _getActiveViewportEnabledElement() {
|
function _getActiveViewportEnabledElement() {
|
||||||
const { activeViewportIndex } = viewportGridService.getState();
|
return getActiveViewportEnabledElement(viewportGridService);
|
||||||
const { element } = OHIFgetEnabledElement(activeViewportIndex) || {};
|
|
||||||
const enabledElement = getEnabledElement(element);
|
|
||||||
return enabledElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getToolGroup(toolGroupId) {
|
function _getToolGroup(toolGroupId) {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
volumeLoader,
|
volumeLoader,
|
||||||
imageLoadPoolManager,
|
imageLoadPoolManager,
|
||||||
Settings,
|
Settings,
|
||||||
|
utilities as csUtilities,
|
||||||
} from '@cornerstonejs/core';
|
} from '@cornerstonejs/core';
|
||||||
import { Enums, utilities, ReferenceLinesTool } from '@cornerstonejs/tools';
|
import { Enums, utilities, ReferenceLinesTool } from '@cornerstonejs/tools';
|
||||||
import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader';
|
import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader';
|
||||||
@ -126,6 +127,12 @@ export default async function init({
|
|||||||
);
|
);
|
||||||
hangingProtocolService.registerImageLoadStrategy('nth', nthLoader);
|
hangingProtocolService.registerImageLoadStrategy('nth', nthLoader);
|
||||||
|
|
||||||
|
// add metadata providers
|
||||||
|
metaData.addProvider(
|
||||||
|
csUtilities.calibratedPixelSpacingMetadataProvider.get.bind(
|
||||||
|
csUtilities.calibratedPixelSpacingMetadataProvider
|
||||||
|
)
|
||||||
|
); // this provider is required for Calibration tool
|
||||||
metaData.addProvider(metadataProvider.get.bind(metadataProvider), 9999);
|
metaData.addProvider(metadataProvider.get.bind(metadataProvider), 9999);
|
||||||
|
|
||||||
imageLoadPoolManager.maxNumRequests = {
|
imageLoadPoolManager.maxNumRequests = {
|
||||||
@ -138,9 +145,7 @@ export default async function init({
|
|||||||
|
|
||||||
/* Measurement Service */
|
/* Measurement Service */
|
||||||
const measurementServiceSource = connectToolsToMeasurementService(
|
const measurementServiceSource = connectToolsToMeasurementService(
|
||||||
measurementService,
|
servicesManager
|
||||||
displaySetService,
|
|
||||||
cornerstoneViewportService
|
|
||||||
);
|
);
|
||||||
|
|
||||||
initCineService(cineService);
|
initCineService(cineService);
|
||||||
|
|||||||
@ -25,6 +25,8 @@ import {
|
|||||||
ReferenceLinesTool,
|
ReferenceLinesTool,
|
||||||
} from '@cornerstonejs/tools';
|
} from '@cornerstonejs/tools';
|
||||||
|
|
||||||
|
import CalibrationLineTool from './tools/CalibrationLineTool';
|
||||||
|
|
||||||
export default function initCornerstoneTools(configuration = {}) {
|
export default function initCornerstoneTools(configuration = {}) {
|
||||||
init(configuration);
|
init(configuration);
|
||||||
addTool(PanTool);
|
addTool(PanTool);
|
||||||
@ -48,6 +50,7 @@ export default function initCornerstoneTools(configuration = {}) {
|
|||||||
addTool(CrosshairsTool);
|
addTool(CrosshairsTool);
|
||||||
addTool(SegmentationDisplayTool);
|
addTool(SegmentationDisplayTool);
|
||||||
addTool(ReferenceLinesTool);
|
addTool(ReferenceLinesTool);
|
||||||
|
addTool(CalibrationLineTool);
|
||||||
|
|
||||||
// Modify annotation tools to use dashed lines on SR
|
// Modify annotation tools to use dashed lines on SR
|
||||||
const annotationStyle = {
|
const annotationStyle = {
|
||||||
@ -86,6 +89,7 @@ const toolNames = {
|
|||||||
Crosshairs: CrosshairsTool.toolName,
|
Crosshairs: CrosshairsTool.toolName,
|
||||||
SegmentationDisplay: SegmentationDisplayTool.toolName,
|
SegmentationDisplay: SegmentationDisplayTool.toolName,
|
||||||
ReferenceLines: ReferenceLinesTool.toolName,
|
ReferenceLines: ReferenceLinesTool.toolName,
|
||||||
|
CalibrationLine: CalibrationLineTool.toolName,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { toolNames };
|
export { toolNames };
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { eventTarget } from '@cornerstonejs/core';
|
import { eventTarget } from '@cornerstonejs/core';
|
||||||
import { Enums, annotation } from '@cornerstonejs/tools';
|
import { Enums, annotation } from '@cornerstonejs/tools';
|
||||||
import { DicomMetadataStore, MeasurementService } from '@ohif/core';
|
import { DicomMetadataStore } from '@ohif/core';
|
||||||
|
import { toolNames } from './initCornerstoneTools';
|
||||||
|
import { onCompletedCalibrationLine } from './tools/CalibrationLineTool';
|
||||||
|
|
||||||
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||||
import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
||||||
@ -102,14 +104,25 @@ const initMeasurementService = (
|
|||||||
PlanarFreehandROI.toMeasurement
|
PlanarFreehandROI.toMeasurement
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// On the UI side, the Calibration Line tool will work almost the same as the
|
||||||
|
// Length tool
|
||||||
|
measurementService.addMapping(
|
||||||
|
csTools3DVer1MeasurementSource,
|
||||||
|
'CalibrationLine',
|
||||||
|
Length.matchingCriteria,
|
||||||
|
Length.toAnnotation,
|
||||||
|
Length.toMeasurement
|
||||||
|
);
|
||||||
|
|
||||||
return csTools3DVer1MeasurementSource;
|
return csTools3DVer1MeasurementSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectToolsToMeasurementService = (
|
const connectToolsToMeasurementService = servicesManager => {
|
||||||
measurementService,
|
const {
|
||||||
displaySetService,
|
measurementService,
|
||||||
cornerstoneViewportService
|
displaySetService,
|
||||||
) => {
|
cornerstoneViewportService,
|
||||||
|
} = servicesManager.services;
|
||||||
const csTools3DVer1MeasurementSource = initMeasurementService(
|
const csTools3DVer1MeasurementSource = initMeasurementService(
|
||||||
measurementService,
|
measurementService,
|
||||||
displaySetService,
|
displaySetService,
|
||||||
@ -131,15 +144,38 @@ const connectToolsToMeasurementService = (
|
|||||||
} = annotationAddedEventDetail;
|
} = annotationAddedEventDetail;
|
||||||
const { toolName } = metadata;
|
const { toolName } = metadata;
|
||||||
|
|
||||||
// To force the measurementUID be the same as the annotationUID
|
if (
|
||||||
// Todo: this should be changed when a measurement can include multiple annotations
|
csToolsEvent.type === completedEvt &&
|
||||||
// in the future
|
toolName === toolNames.CalibrationLine
|
||||||
annotationAddedEventDetail.uid = annotationUID;
|
) {
|
||||||
annotationToMeasurement(toolName, annotationAddedEventDetail);
|
// show modal to input the measurement (mm)
|
||||||
|
onCompletedCalibrationLine(servicesManager, csToolsEvent)
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
console.log('calibration applied');
|
||||||
|
},
|
||||||
|
() => true
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
// we don't need the calibration line lingering around, remove the
|
||||||
|
// annotation from the display
|
||||||
|
removeAnnotation(annotationUID);
|
||||||
|
removeMeasurement(csToolsEvent);
|
||||||
|
// this will ensure redrawing of annotations
|
||||||
|
cornerstoneViewportService.resize();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// To force the measurementUID be the same as the annotationUID
|
||||||
|
// Todo: this should be changed when a measurement can include multiple annotations
|
||||||
|
// in the future
|
||||||
|
annotationAddedEventDetail.uid = annotationUID;
|
||||||
|
annotationToMeasurement(toolName, annotationAddedEventDetail);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to update measurement:', error);
|
console.warn('Failed to update measurement:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMeasurement(csToolsEvent) {
|
function updateMeasurement(csToolsEvent) {
|
||||||
try {
|
try {
|
||||||
const annotationModifiedEventDetail = csToolsEvent.detail;
|
const annotationModifiedEventDetail = csToolsEvent.detail;
|
||||||
|
|||||||
134
extensions/cornerstone/src/tools/CalibrationLineTool.ts
Normal file
134
extensions/cornerstone/src/tools/CalibrationLineTool.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { metaData } from '@cornerstonejs/core';
|
||||||
|
import { LengthTool } from '@cornerstonejs/tools';
|
||||||
|
import { calibrateImageSpacing } from '@cornerstonejs/tools/dist/esm/utilities';
|
||||||
|
import callInputDialog from '../utils/callInputDialog';
|
||||||
|
import getActiveViewportEnabledElement from '../utils/getActiveViewportEnabledElement';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calibration Line tool works almost the same as the
|
||||||
|
*/
|
||||||
|
class CalibrationLineTool extends LengthTool {
|
||||||
|
static toolName = 'CalibrationLine';
|
||||||
|
|
||||||
|
_renderingViewport: any;
|
||||||
|
_lengthToolRenderAnnotation = this.renderAnnotation;
|
||||||
|
|
||||||
|
renderAnnotation = (enabledElement, svgDrawingHelper) => {
|
||||||
|
const { viewport } = enabledElement;
|
||||||
|
this._renderingViewport = viewport;
|
||||||
|
return this._lengthToolRenderAnnotation(enabledElement, svgDrawingHelper);
|
||||||
|
};
|
||||||
|
|
||||||
|
_getTextLines(data, targetId) {
|
||||||
|
const [canvasPoint1, canvasPoint2] = data.handles.points.map(p =>
|
||||||
|
this._renderingViewport.worldToCanvas(p)
|
||||||
|
);
|
||||||
|
// for display, round to 2 decimal points
|
||||||
|
const lengthPx =
|
||||||
|
Math.round(calculateLength2(canvasPoint1, canvasPoint2) * 100) / 100;
|
||||||
|
|
||||||
|
const textLines = [`${lengthPx}px`];
|
||||||
|
|
||||||
|
return textLines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateLength2(point1, point2) {
|
||||||
|
const dx = point1[0] - point2[0];
|
||||||
|
const dy = point1[1] - point2[1];
|
||||||
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateLength3(pos1, pos2) {
|
||||||
|
const dx = pos1[0] - pos2[0];
|
||||||
|
const dy = pos1[1] - pos2[1];
|
||||||
|
const dz = pos1[2] - pos2[2];
|
||||||
|
|
||||||
|
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CalibrationLineTool;
|
||||||
|
|
||||||
|
export function onCompletedCalibrationLine(servicesManager, csToolsEvent) {
|
||||||
|
const { uiDialogService, viewportGridService } = servicesManager.services;
|
||||||
|
|
||||||
|
// calculate length (mm) with the current Pixel Spacing
|
||||||
|
const annotationAddedEventDetail = csToolsEvent.detail;
|
||||||
|
const {
|
||||||
|
annotation: { metadata, data: annotationData },
|
||||||
|
} = annotationAddedEventDetail;
|
||||||
|
const { referencedImageId: imageId } = metadata;
|
||||||
|
const enabledElement = getActiveViewportEnabledElement(viewportGridService);
|
||||||
|
const { viewport } = enabledElement;
|
||||||
|
|
||||||
|
const length =
|
||||||
|
Math.round(
|
||||||
|
calculateLength3(
|
||||||
|
annotationData.handles.points[0],
|
||||||
|
annotationData.handles.points[1]
|
||||||
|
) * 100
|
||||||
|
) / 100;
|
||||||
|
|
||||||
|
// calculate the currently applied pixel spacing on the viewport
|
||||||
|
const calibratedPixelSpacing = metaData.get(
|
||||||
|
'calibratedPixelSpacing',
|
||||||
|
imageId
|
||||||
|
);
|
||||||
|
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
|
||||||
|
const currentRowPixelSpacing =
|
||||||
|
calibratedPixelSpacing?.[0] || imagePlaneModule?.rowPixelSpacing || 1;
|
||||||
|
const currentColumnPixelSpacing =
|
||||||
|
calibratedPixelSpacing?.[1] || imagePlaneModule?.columnPixelSpacing || 1;
|
||||||
|
|
||||||
|
const adjustCalibration = newLength => {
|
||||||
|
const spacingScale = newLength / length;
|
||||||
|
const rowSpacing = spacingScale * currentRowPixelSpacing;
|
||||||
|
const colSpacing = spacingScale * currentColumnPixelSpacing;
|
||||||
|
|
||||||
|
// trigger resize of the viewport to adjust the world/pixel mapping
|
||||||
|
calibrateImageSpacing(
|
||||||
|
imageId,
|
||||||
|
viewport.getRenderingEngine(),
|
||||||
|
rowSpacing,
|
||||||
|
colSpacing
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!uiDialogService) {
|
||||||
|
reject('UIDialogService is not initiated');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callInputDialog(
|
||||||
|
uiDialogService,
|
||||||
|
{
|
||||||
|
text: '',
|
||||||
|
label: `${length}`,
|
||||||
|
},
|
||||||
|
(value, id) => {
|
||||||
|
if (id === 'save') {
|
||||||
|
adjustCalibration(Number.parseFloat(value));
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
reject('cancel');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
{
|
||||||
|
dialogTitle: 'Calibration',
|
||||||
|
inputLabel: 'Actual Physical distance (mm)',
|
||||||
|
|
||||||
|
// the input value must be a number
|
||||||
|
validateFunc: val => {
|
||||||
|
try {
|
||||||
|
const v = Number.parseFloat(val);
|
||||||
|
return !isNaN(v) && v !== 0.0;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -9,23 +9,35 @@ import { Input, Dialog } from '@ohif/ui';
|
|||||||
* @param {*} event
|
* @param {*} event
|
||||||
* @param {*} callback
|
* @param {*} callback
|
||||||
* @param {*} isArrowAnnotateInputDialog
|
* @param {*} isArrowAnnotateInputDialog
|
||||||
|
* @param {*} dialogConfig
|
||||||
|
* @param {string?} dialogConfig.dialogTitle - title of the input dialog
|
||||||
|
* @param {string?} dialogConfig.inputLabel - show label above the input
|
||||||
*/
|
*/
|
||||||
function callInputDialog(
|
function callInputDialog(
|
||||||
uiDialogService,
|
uiDialogService,
|
||||||
data,
|
data,
|
||||||
callback,
|
callback,
|
||||||
isArrowAnnotateInputDialog = true
|
isArrowAnnotateInputDialog = true,
|
||||||
|
dialogConfig: any = {}
|
||||||
) {
|
) {
|
||||||
const dialogId = 'enter-annotation';
|
const dialogId = 'dialog-enter-annotation';
|
||||||
const label = data
|
const label = data
|
||||||
? isArrowAnnotateInputDialog
|
? isArrowAnnotateInputDialog
|
||||||
? data.text
|
? data.text
|
||||||
: data.label
|
: data.label
|
||||||
: '';
|
: '';
|
||||||
|
const {
|
||||||
|
dialogTitle = 'Enter your annotation',
|
||||||
|
inputLabel = '',
|
||||||
|
validateFunc = value => true,
|
||||||
|
} = dialogConfig;
|
||||||
|
|
||||||
const onSubmitHandler = ({ action, value }) => {
|
const onSubmitHandler = ({ action, value }) => {
|
||||||
switch (action.id) {
|
switch (action.id) {
|
||||||
case 'save':
|
case 'save':
|
||||||
|
if (typeof validateFunc === 'function' && !validateFunc(value.label))
|
||||||
|
return;
|
||||||
|
|
||||||
callback(value.label, action.id);
|
callback(value.label, action.id);
|
||||||
break;
|
break;
|
||||||
case 'cancel':
|
case 'cancel':
|
||||||
@ -43,7 +55,7 @@ function callInputDialog(
|
|||||||
showOverlay: true,
|
showOverlay: true,
|
||||||
content: Dialog,
|
content: Dialog,
|
||||||
contentProps: {
|
contentProps: {
|
||||||
title: 'Enter your annotation',
|
title: dialogTitle,
|
||||||
value: { label },
|
value: { label },
|
||||||
noCloseButton: true,
|
noCloseButton: true,
|
||||||
onClose: () => uiDialogService.dismiss({ id: dialogId }),
|
onClose: () => uiDialogService.dismiss({ id: dialogId }),
|
||||||
@ -61,6 +73,8 @@ function callInputDialog(
|
|||||||
type="text"
|
type="text"
|
||||||
id="annotation"
|
id="annotation"
|
||||||
containerClassName="mr-2"
|
containerClassName="mr-2"
|
||||||
|
label={inputLabel}
|
||||||
|
labelClassName="text-primary-light"
|
||||||
value={value.label}
|
value={value.label}
|
||||||
onChange={event => {
|
onChange={event => {
|
||||||
event.persist();
|
event.persist();
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { getEnabledElement } from '@cornerstonejs/core';
|
||||||
|
import { IEnabledElement } from '@cornerstonejs/core/dist/esm/types';
|
||||||
|
|
||||||
|
import { getEnabledElement as OHIFgetEnabledElement } from '../state';
|
||||||
|
|
||||||
|
export default function getActiveViewportEnabledElement(
|
||||||
|
viewportGridService
|
||||||
|
): IEnabledElement {
|
||||||
|
const { activeViewportIndex } = viewportGridService.getState();
|
||||||
|
const { element } = OHIFgetEnabledElement(activeViewportIndex) || {};
|
||||||
|
const enabledElement = getEnabledElement(element);
|
||||||
|
return enabledElement;
|
||||||
|
}
|
||||||
@ -83,6 +83,7 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
{ toolName: toolNames.EllipticalROI },
|
{ toolName: toolNames.EllipticalROI },
|
||||||
{ toolName: toolNames.RectangleROI },
|
{ toolName: toolNames.RectangleROI },
|
||||||
{ toolName: toolNames.StackScroll },
|
{ toolName: toolNames.StackScroll },
|
||||||
|
{ toolName: toolNames.CalibrationLine },
|
||||||
],
|
],
|
||||||
// enabled
|
// enabled
|
||||||
// disabled
|
// disabled
|
||||||
|
|||||||
@ -323,6 +323,21 @@ const toolbarButtons = [
|
|||||||
],
|
],
|
||||||
'Invert Colors'
|
'Invert Colors'
|
||||||
),
|
),
|
||||||
|
_createToolButton(
|
||||||
|
'CalibrationLine',
|
||||||
|
'tool-calibration',
|
||||||
|
'Calibration',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
commandName: 'setToolActive',
|
||||||
|
commandOptions: {
|
||||||
|
toolName: 'CalibrationLine',
|
||||||
|
},
|
||||||
|
context: 'CORNERSTONE',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'Calibration Line'
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -39,6 +39,7 @@ function initDefaultToolGroup(
|
|||||||
{ toolName: toolNames.PlanarFreehandROI },
|
{ toolName: toolNames.PlanarFreehandROI },
|
||||||
{ toolName: toolNames.Magnify },
|
{ toolName: toolNames.Magnify },
|
||||||
{ toolName: toolNames.SegmentationDisplay },
|
{ toolName: toolNames.SegmentationDisplay },
|
||||||
|
{ toolName: toolNames.CalibrationLine },
|
||||||
],
|
],
|
||||||
// enabled
|
// enabled
|
||||||
// disabled
|
// disabled
|
||||||
|
|||||||
@ -555,6 +555,21 @@ const toolbarButtons = [
|
|||||||
],
|
],
|
||||||
'Rectangle'
|
'Rectangle'
|
||||||
),
|
),
|
||||||
|
_createToolButton(
|
||||||
|
'CalibrationLine',
|
||||||
|
'tool-calibration',
|
||||||
|
'Calibration',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
commandName: 'setToolActive',
|
||||||
|
commandOptions: {
|
||||||
|
toolName: 'CalibrationLine',
|
||||||
|
},
|
||||||
|
context: 'CORNERSTONE',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'Calibration Line'
|
||||||
|
),
|
||||||
_createActionButton(
|
_createActionButton(
|
||||||
'TagBrowser',
|
'TagBrowser',
|
||||||
'list-bullets',
|
'list-bullets',
|
||||||
|
|||||||
@ -8,6 +8,10 @@ export default function getModalities(Modality, ModalitiesInStudy) {
|
|||||||
Value: [],
|
Value: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Rare case, depending on the DICOM server we are using, but sometimes,
|
||||||
|
// modalities.Value is undefined or null.
|
||||||
|
modalities.Value = modalities.Value || [];
|
||||||
|
|
||||||
if (ModalitiesInStudy) {
|
if (ModalitiesInStudy) {
|
||||||
if (modalities.vr && modalities.vr === ModalitiesInStudy.vr) {
|
if (modalities.vr && modalities.vr === ModalitiesInStudy.vr) {
|
||||||
for (let i = 0; i < ModalitiesInStudy.Value.length; i++) {
|
for (let i = 0; i < ModalitiesInStudy.Value.length; i++) {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ The basic viewer comes with the following default measurement tools:
|
|||||||
- Bidirectional Tool: Creates a measurement of the longest diameter (LD) and longest perpendicular diameter (LPD) in *mm*
|
- Bidirectional Tool: Creates a measurement of the longest diameter (LD) and longest perpendicular diameter (LPD) in *mm*
|
||||||
- Annotation: Used to create a qualitative marker with a freetext label
|
- Annotation: Used to create a qualitative marker with a freetext label
|
||||||
- Ellipse: Measures an elliptical area in *mm<sup>2</sup>* and Hounsfield Units (HU)
|
- Ellipse: Measures an elliptical area in *mm<sup>2</sup>* and Hounsfield Units (HU)
|
||||||
|
- Calibration Tool: Calibrate (or override) the Pixel Spacing Attribute (Physical distance in the patient between the center of each pixel, specified by a numeric pair - adjacent row spacing (delimiter) adjacent column spacing in mm)
|
||||||
|
|
||||||
When a measurement tool is selected from the toolbar, it becomes the `active` tool. Use the caret to expand the measurement tools and select another tool.
|
When a measurement tool is selected from the toolbar, it becomes the `active` tool. Use the caret to expand the measurement tools and select another tool.
|
||||||
<!-- We should add a smaller screenshot for each measurement tool. Maybe have a matrix with 4 identical sized screenshots in a box? Also we should make sure the screenshots have more realistic measurements -->
|
<!-- We should add a smaller screenshot for each measurement tool. Maybe have a matrix with 4 identical sized screenshots in a box? Also we should make sure the screenshots have more realistic measurements -->
|
||||||
|
|||||||
@ -24,6 +24,7 @@ The basic viewer comes with the following default measurement tools:
|
|||||||
- Bidirectional Tool: Creates a measurement of the longest diameter (LD) and longest perpendicular diameter (LPD) in *mm*
|
- Bidirectional Tool: Creates a measurement of the longest diameter (LD) and longest perpendicular diameter (LPD) in *mm*
|
||||||
- Annotation: Used to create a qualitative marker with a freetext label
|
- Annotation: Used to create a qualitative marker with a freetext label
|
||||||
- Ellipse: Measures an elliptical area in *mm<sup>2</sup>* and Hounsfield Units (HU)
|
- Ellipse: Measures an elliptical area in *mm<sup>2</sup>* and Hounsfield Units (HU)
|
||||||
|
- Calibration Tool: Calibrate (or override) the Pixel Spacing Attribute (Physical distance in the patient between the center of each pixel, specified by a numeric pair - adjacent row spacing (delimiter) adjacent column spacing in mm)
|
||||||
|
|
||||||
When a measurement tool is selected from the toolbar, it becomes the `active` tool. Use the caret to expand the measurement tools and select another tool.
|
When a measurement tool is selected from the toolbar, it becomes the `active` tool. Use the caret to expand the measurement tools and select another tool.
|
||||||
<!-- We should add a smaller screenshot for each measurement tool. Maybe have a matrix with 4 identical sized screenshots in a box? Also we should make sure the screenshots have more realistic measurements -->
|
<!-- We should add a smaller screenshot for each measurement tool. Maybe have a matrix with 4 identical sized screenshots in a box? Also we should make sure the screenshots have more realistic measurements -->
|
||||||
|
|||||||
21
platform/ui/src/assets/icons/tool-calibration.svg
Normal file
21
platform/ui/src/assets/icons/tool-calibration.svg
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="color" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="measure-calibrate" transform="translate(0.500000, 4.000000)" stroke="currentColor">
|
||||||
|
<g id="calibrate" transform="translate(0.500000, 11.500000)" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="1.25">
|
||||||
|
<polyline id="Path" points="20 6 23 3 20 0"></polyline>
|
||||||
|
<line x1="1" y1="3" x2="22" y2="3" id="Path"></line>
|
||||||
|
<polyline id="Path" points="3 6 0 3 3 0"></polyline>
|
||||||
|
</g>
|
||||||
|
<rect id="Rectangle" stroke-width="1.25" x="0" y="0" width="24" height="6" rx="2"></rect>
|
||||||
|
<line x1="4.5" y1="5.5" x2="4.5" y2="3.5" id="Line-5" stroke-linecap="round"></line>
|
||||||
|
<line x1="7.5" y1="5.5" x2="7.5" y2="3.5" id="Line-5" stroke-linecap="round"></line>
|
||||||
|
<line x1="10.5" y1="5.5" x2="10.5" y2="3.5" id="Line-5" stroke-linecap="round"></line>
|
||||||
|
<line x1="13.5" y1="5.5" x2="13.5" y2="3.5" id="Line-5" stroke-linecap="round"></line>
|
||||||
|
<line x1="16.5" y1="5.5" x2="16.5" y2="3.5" id="Line-5" stroke-linecap="round"></line>
|
||||||
|
<line x1="19.5" y1="5.5" x2="19.5" y2="3.5" id="Line-5" stroke-linecap="round"></line>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@ -227,6 +227,7 @@ Button.propTypes = {
|
|||||||
/** Button corner roundness */
|
/** Button corner roundness */
|
||||||
rounded: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']),
|
rounded: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']),
|
||||||
variant: PropTypes.oneOf(['text', 'outlined', 'contained', 'disabled']),
|
variant: PropTypes.oneOf(['text', 'outlined', 'contained', 'disabled']),
|
||||||
|
/* color prop must have all the possible keys of variants defined above */
|
||||||
color: PropTypes.oneOf([
|
color: PropTypes.oneOf([
|
||||||
'default',
|
'default',
|
||||||
'primary',
|
'primary',
|
||||||
|
|||||||
@ -82,6 +82,7 @@ import toolReset from './../../assets/icons/tool-reset.svg';
|
|||||||
import toolRectangle from './../../assets/icons/tool-rectangle.svg';
|
import toolRectangle from './../../assets/icons/tool-rectangle.svg';
|
||||||
import toolFusionColor from './../../assets/icons/tool-fusion-color.svg';
|
import toolFusionColor from './../../assets/icons/tool-fusion-color.svg';
|
||||||
import toolCreateThreshold from './../../assets/icons/tool-create-threshold.svg';
|
import toolCreateThreshold from './../../assets/icons/tool-create-threshold.svg';
|
||||||
|
import toolCalibration from './../../assets/icons/tool-calibration.svg';
|
||||||
import editPatient from './../../assets/icons/edit-patient.svg';
|
import editPatient from './../../assets/icons/edit-patient.svg';
|
||||||
import panelGroupMore from './../../assets/icons/panel-group-more.svg';
|
import panelGroupMore from './../../assets/icons/panel-group-more.svg';
|
||||||
import panelGroupOpenClose from './../../assets/icons/panel-group-open-close.svg';
|
import panelGroupOpenClose from './../../assets/icons/panel-group-open-close.svg';
|
||||||
@ -185,6 +186,7 @@ const ICONS = {
|
|||||||
'tool-rectangle': toolRectangle,
|
'tool-rectangle': toolRectangle,
|
||||||
'tool-fusion-color': toolFusionColor,
|
'tool-fusion-color': toolFusionColor,
|
||||||
'tool-create-threshold': toolCreateThreshold,
|
'tool-create-threshold': toolCreateThreshold,
|
||||||
|
'tool-calibration': toolCalibration,
|
||||||
'edit-patient': editPatient,
|
'edit-patient': editPatient,
|
||||||
'icon-mpr': iconMPR,
|
'icon-mpr': iconMPR,
|
||||||
'icon-next-inactive': iconNextInactive,
|
'icon-next-inactive': iconNextInactive,
|
||||||
|
|||||||
@ -133,6 +133,7 @@ Typography.propTypes = {
|
|||||||
'primaryActive',
|
'primaryActive',
|
||||||
'secondary',
|
'secondary',
|
||||||
'error',
|
'error',
|
||||||
|
'primaryActive',
|
||||||
]),
|
]),
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user