feat(findingSite): Allow customizing the measurement panel and show site/finding (#3155)
* feat: Allow customizing the measurements panel. Also includes default display of the site/finding values from the DICOM SR object. Update fixes PR review comments - move the recordInteraction to COmmandsManager.run PR fixes fix: Dependency updates to allow right click to be recognized PR fixes * Use newer CSWIL * Make both the base measurements and the tracking measurements show codes * Fix the right click context menu bringing up menu
This commit is contained in:
1 parent
dd22e37e4c
commit
8e9d7bd2bb
10 files changed
+152
-48
No files matched your search
@@ -35,7 +35,7 @@ function getFilteredCornerstoneToolState(
|
||||
);
|
||||
const toolData = imageIdSpecificToolState[toolType].data;
|
||||
|
||||
let finding;
|
||||
let { finding } = measurementDataI;
|
||||
const findingSites = [];
|
||||
|
||||
// NOTE -> We use the CORNERSTONEJS coding schemeDesignator which we have
|
||||
@@ -56,6 +56,10 @@ function getFilteredCornerstoneToolState(
|
||||
}
|
||||
}
|
||||
|
||||
if (measurementDataI.findingSites) {
|
||||
findingSites.push(...measurementDataI.findingSites);
|
||||
}
|
||||
|
||||
const measurement = Object.assign({}, annotation, {
|
||||
finding,
|
||||
findingSites,
|
||||
@@ -73,7 +77,7 @@ function getFilteredCornerstoneToolState(
|
||||
for (let i = 0; i < framesOfReference.length; i++) {
|
||||
const frameOfReference = framesOfReference[i];
|
||||
|
||||
const frameOfReferenceAnnotations = annotationManager.getFrameOfReferenceAnnotations(
|
||||
const frameOfReferenceAnnotations = annotationManager.getAnnotations(
|
||||
frameOfReference
|
||||
);
|
||||
|
||||
|
||||
@@ -11,6 +11,25 @@ const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
||||
|
||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
||||
|
||||
const convertCode = (codingValues, code) => {
|
||||
if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') return;
|
||||
const ref = `${code.CodingSchemeDesignator}:${code.CodeValue}`;
|
||||
const ret = { ...codingValues[ref], ref, ...code, text: code.CodeMeaning };
|
||||
return ret;
|
||||
};
|
||||
|
||||
const convertSites = (codingValues, sites) => {
|
||||
if (!sites || !sites.length) return;
|
||||
const ret = [];
|
||||
// Do as a loop to convert away from Proxy instances
|
||||
for (let i = 0; i < sites.length; i++) {
|
||||
// Deal with irregular conversion from dcmjs
|
||||
const site = convertCode(codingValues, sites[i][0] || sites[i]);
|
||||
if (site) ret.push(site);
|
||||
}
|
||||
return (ret.length && ret) || undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hydrates a structured report, for default viewports.
|
||||
*
|
||||
@@ -20,8 +39,16 @@ export default function hydrateStructuredReport(
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
const dataSource = extensionManager.getActiveDataSource()[0];
|
||||
const { measurementService, displaySetService } = servicesManager.services;
|
||||
const {
|
||||
measurementService,
|
||||
displaySetService,
|
||||
customizationService,
|
||||
} = servicesManager.services;
|
||||
|
||||
const codingValues = customizationService.getCustomization(
|
||||
'codingValues',
|
||||
{}
|
||||
);
|
||||
const displaySet = displaySetService.getDisplaySetByUID(
|
||||
displaySetInstanceUID
|
||||
);
|
||||
@@ -171,6 +198,15 @@ export default function hydrateStructuredReport(
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
||||
);
|
||||
annotation.data.label = getLabelFromDCMJSImportedToolData(toolData);
|
||||
annotation.data.finding = convertCode(
|
||||
codingValues,
|
||||
toolData.finding?.[0]
|
||||
);
|
||||
annotation.data.findingSites = convertSites(
|
||||
codingValues,
|
||||
toolData.findingSites
|
||||
);
|
||||
annotation.data.site = annotation.data.findingSites?.[0];
|
||||
|
||||
const matchingMapping = mappings.find(
|
||||
m => m.annotationType === annotationType
|
||||
|
||||
@@ -222,16 +222,6 @@ function commandsModule({ servicesManager, commandsManager }) {
|
||||
updatedMeasurement.findingSites = [code];
|
||||
}
|
||||
}
|
||||
// TODO - remove this once measurement items customization is ready
|
||||
const allCodes = [];
|
||||
if (textLabel) allCodes.push(textLabel);
|
||||
if (updatedMeasurement.finding) {
|
||||
allCodes.push(updatedMeasurement.finding.CodeMeaning);
|
||||
}
|
||||
(updatedMeasurement.findingSites || []).forEach(it =>
|
||||
allCodes.push(it.CodeMeaning)
|
||||
);
|
||||
updatedMeasurement.label = allCodes.join(', ');
|
||||
}
|
||||
measurementService.update(
|
||||
updatedMeasurement.uid,
|
||||
|
||||
@@ -218,6 +218,7 @@ export default function PanelMeasurementTable({
|
||||
>
|
||||
<MeasurementTable
|
||||
title="Measurements"
|
||||
servicesManager={servicesManager}
|
||||
data={displayMeasurements}
|
||||
onClick={jumpToImage}
|
||||
onEdit={onMeasurementItemEditHandler}
|
||||
@@ -248,14 +249,46 @@ function _getMappedMeasurements(measurementService) {
|
||||
return mappedMeasurements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the measurements to the display text.
|
||||
* Adds finding and site inforamtion to the displayText and/or label,
|
||||
* and provides as 'displayText' and 'label', while providing the original
|
||||
* values as baseDisplayText and baseLabel
|
||||
*/
|
||||
function _mapMeasurementToDisplay(measurement, index, types) {
|
||||
const { displayText, uid, label, type, selected } = measurement;
|
||||
const {
|
||||
displayText: baseDisplayText,
|
||||
uid,
|
||||
label: baseLabel,
|
||||
type,
|
||||
selected,
|
||||
findingSites,
|
||||
finding,
|
||||
} = measurement;
|
||||
|
||||
const firstSite = findingSites?.[0];
|
||||
const label = baseLabel || finding?.text || firstSite?.text || '(empty)';
|
||||
let displayText = baseDisplayText || [];
|
||||
if (findingSites) {
|
||||
const siteText = [];
|
||||
findingSites.forEach(site => {
|
||||
if (site?.text !== label) siteText.push(site.text);
|
||||
});
|
||||
displayText = [...siteText, ...displayText];
|
||||
}
|
||||
if (finding && finding?.text !== label) {
|
||||
displayText = [finding.text, ...displayText];
|
||||
}
|
||||
|
||||
return {
|
||||
uid,
|
||||
label: label || '(empty)',
|
||||
label,
|
||||
baseLabel,
|
||||
measurementType: type,
|
||||
displayText: displayText || [],
|
||||
displayText,
|
||||
baseDisplayText,
|
||||
isActive: selected,
|
||||
finding,
|
||||
findingSites,
|
||||
};
|
||||
}
|
||||
@@ -129,7 +129,8 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
|
||||
setMeasurementsUpdated(Date.now().toString());
|
||||
if (evt === added) {
|
||||
debounce(() => {
|
||||
measurementsPanelRef.current.scrollTop = measurementsPanelRef.current.scrollHeight;
|
||||
measurementsPanelRef.current.scrollTop =
|
||||
measurementsPanelRef.current.scrollHeight;
|
||||
}, 300)();
|
||||
}
|
||||
}).unsubscribe
|
||||
@@ -261,6 +262,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
|
||||
<MeasurementTable
|
||||
title="Measurements"
|
||||
data={displayMeasurementsWithoutFindings}
|
||||
servicesManager={servicesManager}
|
||||
onClick={jumpToImage}
|
||||
onEdit={onMeasurementItemEditHandler}
|
||||
/>
|
||||
@@ -268,6 +270,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
|
||||
<MeasurementTable
|
||||
title="Additional Findings"
|
||||
data={additionalFindings}
|
||||
servicesManager={servicesManager}
|
||||
onClick={jumpToImage}
|
||||
onEdit={onMeasurementItemEditHandler}
|
||||
/>
|
||||
@@ -326,13 +329,40 @@ function _mapMeasurementToDisplay(measurement, types, displaySetService) {
|
||||
);
|
||||
}
|
||||
|
||||
const { displayText } = measurement;
|
||||
const {
|
||||
displayText: baseDisplayText,
|
||||
uid,
|
||||
label: baseLabel,
|
||||
type,
|
||||
selected,
|
||||
findingSites,
|
||||
finding,
|
||||
} = measurement;
|
||||
|
||||
const firstSite = findingSites?.[0];
|
||||
const label = baseLabel || finding?.text || firstSite?.text || '(empty)';
|
||||
let displayText = baseDisplayText || [];
|
||||
if (findingSites) {
|
||||
const siteText = [];
|
||||
findingSites.forEach(site => {
|
||||
if (site?.text !== label) siteText.push(site.text);
|
||||
});
|
||||
displayText = [...siteText, ...displayText];
|
||||
}
|
||||
if (finding && finding?.text !== label) {
|
||||
displayText = [finding.text, ...displayText];
|
||||
}
|
||||
|
||||
return {
|
||||
uid: measurement.uid,
|
||||
label: measurement.label || '(empty)',
|
||||
measurementType: measurement.type,
|
||||
displayText: displayText || [],
|
||||
isActive: measurement.selected,
|
||||
uid,
|
||||
label,
|
||||
baseLabel,
|
||||
measurementType: type,
|
||||
displayText,
|
||||
baseDisplayText,
|
||||
isActive: selected,
|
||||
finding,
|
||||
findingSites,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ const findingsContextMenu = {
|
||||
|
||||
{
|
||||
id: 'orientationSelectionSubMenu',
|
||||
selector: ({ nearbyToolData }) => false,
|
||||
items: [
|
||||
{
|
||||
customizationType: '@ohif/contextMenuAnnotationCode',
|
||||
@@ -66,7 +67,7 @@ const findingsContextMenu = {
|
||||
|
||||
{
|
||||
id: 'findingSelectionSubMenu',
|
||||
selector: ({ nearbyToolData }) => !!nearbyToolData,
|
||||
selector: ({ nearbyToolData }) => false,
|
||||
items: [
|
||||
{
|
||||
customizationType: '@ohif/contextMenuAnnotationCode',
|
||||
|
||||
Reference in new issue
Block a user