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

Support for Tomosynthesis multiframe loading and segmentation. #18

Open
wants to merge 1 commit into
base: gradienthealth/segmentation_mode_sheet_integration
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ async function _loadSegments({ extensionManager, servicesManager, segDisplaySet,
const cachedReferencedVolume = cache.getVolume(segDisplaySet.referencedVolumeId);
imageIds = cachedReferencedVolume.imageIds || cachedReferencedVolume._imageIds;
} else {
imageIds = referencedDisplaySet.instances.map(instance => instance.imageId);
imageIds = utils.getImageIdsFromInstances(referencedDisplaySet.instances);
}

// Todo: what should be defaults here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ export default function PanelSegmentation({
segmentationService.addSegment(segmentationId, { properties: { label } });
};

const onSegmentFocusClick = (segmentationId, segmentIndex) => {
setSegmentationActive(segmentationId);
segmentationService.setActiveSegment(segmentationId, segmentIndex);
CropDisplayAreaService.focusToSegment(segmentationId, segmentIndex);
};

const onSegmentClick = (segmentationId, segmentIndex) => {
setReferencedDisplaySet(segmentationId);
segmentationService.setActiveSegment(segmentationId, segmentIndex);
Expand Down Expand Up @@ -392,6 +398,7 @@ export default function PanelSegmentation({
onSegmentationDownloadRTSS={onSegmentationDownloadRTSS}
storeSegmentation={storeSegmentation}
onSegmentationEdit={onSegmentationEdit}
onSegmentFocusClick={onSegmentFocusClick}
onSegmentClick={onSegmentClick}
onSegmentEdit={onSegmentEdit}
onSegmentAdd={onSegmentAdd}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { cache } from '@cornerstonejs/core';

const generateLabelmaps2DFromImageIdMap = imageIdReferenceMap => {
const generateLabelmaps2DFromImageIdMap = (imageIdReferenceMap: Map<string, string>) => {
const labelmaps2D = [],
referencedImages = [],
segmentsOnLabelmap3D = new Set();
Array.from(imageIdReferenceMap.entries()).forEach((entry, index) => {
referencedImages.push(cache.getImage(entry[0]));

const segmentationImage = cache.getImage(entry[1]);
Array.from(imageIdReferenceMap.values()).forEach((segImageId, index) => {
const segmentationImage = cache.getImage(segImageId);
referencedImages.push(segmentationImage);
const { rows, columns } = segmentationImage;
const pixelData = segmentationImage.getPixelData();
const segmentsOnLabelmap = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import cloneDeep from 'lodash.clonedeep';

import { Types as OhifTypes, ServicesManager, PubSubService } from '@ohif/core';
import { Types as OhifTypes, ServicesManager, PubSubService, utils } from '@ohif/core';
import {
cache,
Enums as csEnums,
Expand Down Expand Up @@ -584,9 +584,7 @@ class SegmentationService extends PubSubService {
} else {
const getSegImageId = (imageId: string): string => `segimage:${segmentationId}:${imageId}`;

const referencedImageIds = referencedDisplaySet.instances.reduce((imageIds, instance) => {
return [...imageIds, instance.imageId];
}, []);
const referencedImageIds = utils.getImageIdsFromInstances(referencedDisplaySet.instances);
const imageIdReferenceMap = new Map();
const segImageIds: string[] = [];

Expand All @@ -612,7 +610,7 @@ class SegmentationService extends PubSubService {

const bytes = new Uint8Array(labelmapBufferArray[0]);
const singleSlicePixelSize = rows * columns;
for (let i = 0; i < referencedDisplaySet.instances.length; i++) {
for (let i = 0; i < segImageIds.length; i++) {
const singleSlicePixelData = new Uint8Array(
bytes.slice(i * singleSlicePixelSize, (i + 1) * singleSlicePixelSize).buffer
);
Expand Down Expand Up @@ -1048,6 +1046,14 @@ class SegmentationService extends PubSubService {
const getSegImageId = (imageId: string): string => `segimage:${segmentationId}:${imageId}`;

const referencedImageIds = displaySet.instances.reduce((imageIds, instance) => {
if (instance.NumberOfFrames > 1) {
const frameImageIds = [];
for (let frame = 1; frame <= instance.NumberOfFrames; frame++) {
frameImageIds.push(`${instance.imageId}&frame=${frame}`);
}

return [...imageIds, ...frameImageIds];
}
return [...imageIds, instance.imageId];
}, []);

Expand Down
2 changes: 1 addition & 1 deletion modes/segmentation/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ function modeFactory({ modeConfiguration }) {
// that is not supported by the mode
const modalitiesArray = modalities.split('\\');
if (modalitiesArray.length === 1) {
return !['SM', 'US', 'MG', 'OT', 'DOC', 'CR'].includes(modalitiesArray[0]);
return !['SM', 'US' /*,'MG'*/, 'OT', 'DOC', 'CR'].includes(modalitiesArray[0]);
}

return true;
Expand Down
15 changes: 15 additions & 0 deletions platform/core/src/utils/getImageIdsFromInstances.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function getImageIdsFromInstances(instances): string[] {
const isMultiFrame = instance => instance.NumberOfFrames > 1;

return instances.reduce((imageIds, instance) => {
if (isMultiFrame(instance)) {
const frameImageIds = [];
for (let frame = 1; frame <= instance.NumberOfFrames; frame++) {
frameImageIds.push(`${instance.imageId}&frame=${frame}`);
}

return [...imageIds, ...frameImageIds];
}
return [...imageIds, instance.imageId];
}, []);
}
3 changes: 3 additions & 0 deletions platform/core/src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
} from './sortStudy';
import { subscribeToNextViewportGridChange } from './subscribeToNextViewportGridChange';
import { splitComma, getSplitParam } from './splitComma';
import getImageIdsFromInstances from './getImageIdsFromInstances';

// Commented out unused functionality.
// Need to implement new mechanism for derived displaySets using the displaySetManager.
Expand Down Expand Up @@ -80,6 +81,7 @@ const utils = {
splitComma,
getSplitParam,
generateAcceptHeader,
getImageIdsFromInstances,
};

export {
Expand Down Expand Up @@ -112,6 +114,7 @@ export {
splitComma,
getSplitParam,
generateAcceptHeader,
getImageIdsFromInstances,
};

export default utils;
19 changes: 19 additions & 0 deletions platform/core/src/utils/isDisplaySetReconstructable.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,20 @@ function isNMReconstructable(multiFrameInstance) {
return imageSubType === 'RECON TOMO' || imageSubType === 'RECON GATED TOMO';
}

function hasUniquePositions(multiFrameInstance) {
const { PerFrameFunctionalGroupsSequence = [] } = multiFrameInstance;

const uniqueImagePositionPatients = new Set();

for (let frame = 0; frame < PerFrameFunctionalGroupsSequence.length; frame++) {
const perFrameSequence = PerFrameFunctionalGroupsSequence[frame];
const imagePositionPatient = perFrameSequence.PlanePositionSequence?.ImagePositionPatient;
uniqueImagePositionPatients.add(imagePositionPatient?.toString());
}

return uniqueImagePositionPatients.size === multiFrameInstance.NumberOfFrames;
}

function processMultiframe(multiFrameInstance) {
// If we don't have the PixelMeasuresSequence, then the pixel spacing and
// slice thickness isn't specified or is changing and we can't reconstruct
Expand All @@ -100,6 +114,11 @@ function processMultiframe(multiFrameInstance) {
return { value: false };
}

if (!hasUniquePositions(multiFrameInstance)) {
console.log('Positions of all the frames are not unique, not reconstructable');
return { value: false };
}

if (multiFrameInstance.Modality.includes('NM') && !isNMReconstructable(multiFrameInstance)) {
return { value: false };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const SegmentationGroup = ({
storeSegmentation,
onSegmentAdd,
onToggleSegmentationVisibility,
onSegmentFocusClick,
onSegmentClick,
onSegmentDelete,
onSegmentEdit,
Expand Down Expand Up @@ -78,6 +79,7 @@ const SegmentationGroup = ({
disableEditing={disableEditing}
isLocked={isLocked}
isVisible={isVisible}
onFocusClick={onSegmentFocusClick}
onClick={onSegmentClick}
onEdit={onSegmentEdit}
onDelete={onSegmentDelete}
Expand Down Expand Up @@ -122,6 +124,7 @@ SegmentationGroup.propTypes = {
onSegmentationDownload: PropTypes.func.isRequired,
onSegmentationDownloadRTSS: PropTypes.func.isRequired,
storeSegmentation: PropTypes.func.isRequired,
onSegmentFocusClick: PropTypes.func,
onSegmentClick: PropTypes.func.isRequired,
onSegmentAdd: PropTypes.func.isRequired,
onSegmentDelete: PropTypes.func.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const SegmentItem = ({
showDelete,
disableEditing,
isLocked = false,
onFocusClick,
onClick,
onEdit,
onDelete,
Expand All @@ -24,12 +25,6 @@ const SegmentItem = ({
}) => {
const [isNumberBoxHovering, setIsNumberBoxHovering] = useState(false);

const onFocusClick = (segmentationId, segmentIndex) => {
CropDisplayAreaService.focusToSegment(segmentationId, segmentIndex).then(() =>
onClick(segmentationId, segmentIndex)
);
};

const cssColor = `rgb(${color[0]},${color[1]},${color[2]})`;

return (
Expand Down Expand Up @@ -183,7 +178,7 @@ const HoveringIcons = ({

return (
<div className="flex items-center">
{createIcon('tool-zoom', onFocusClick)}
{onFocusClick && createIcon('tool-zoom', onFocusClick)}
{!disableEditing && createIcon('row-edit', onEdit)}
{!disableEditing &&
createIcon(
Expand All @@ -210,6 +205,7 @@ SegmentItem.propTypes = {
isActive: PropTypes.bool.isRequired,
isVisible: PropTypes.bool.isRequired,
isLocked: PropTypes.bool,
onFocusClick: PropTypes.func,
onClick: PropTypes.func.isRequired,
onEdit: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const SegmentationGroupTable = ({
onSegmentationDownloadRTSS,
storeSegmentation,
// segment handlers
onSegmentFocusClick,
onSegmentClick,
onSegmentAdd,
onSegmentDelete,
Expand Down Expand Up @@ -126,6 +127,7 @@ const SegmentationGroupTable = ({
storeSegmentation={storeSegmentation}
onSegmentAdd={onSegmentAdd}
onToggleSegmentationVisibility={onToggleSegmentationVisibility}
onSegmentFocusClick={onSegmentFocusClick}
onSegmentClick={onSegmentClick}
onSegmentDelete={onSegmentDelete}
onSegmentEdit={onSegmentEdit}
Expand Down Expand Up @@ -172,6 +174,7 @@ SegmentationGroupTable.propTypes = {
onSegmentationDownload: PropTypes.func.isRequired,
onSegmentationDownloadRTSS: PropTypes.func.isRequired,
storeSegmentation: PropTypes.func.isRequired,
onSegmentFocusClick: PropTypes.func,
onSegmentClick: PropTypes.func.isRequired,
onSegmentAdd: PropTypes.func.isRequired,
onSegmentDelete: PropTypes.func.isRequired,
Expand Down