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:
md-prog 2023-02-22 21:01:36 -05:00 committed by GitHub
parent 7a36363735
commit b15988359f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 288 additions and 22 deletions

View File

@ -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) {

View File

@ -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);

View File

@ -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 };

View File

@ -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;

View 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;
}
},
}
);
});
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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

View File

@ -323,6 +323,21 @@ const toolbarButtons = [
], ],
'Invert Colors' 'Invert Colors'
), ),
_createToolButton(
'CalibrationLine',
'tool-calibration',
'Calibration',
[
{
commandName: 'setToolActive',
commandOptions: {
toolName: 'CalibrationLine',
},
context: 'CORNERSTONE',
},
],
'Calibration Line'
),
], ],
}, },
}, },

View File

@ -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

View File

@ -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',

View File

@ -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++) {

View File

@ -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 -->

View File

@ -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 -->

View 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

View File

@ -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',

View File

@ -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,

View File

@ -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,