All files / extensions/cornerstone/src/utils/presentations getViewportPresentations.ts

92.3% Statements 36/39
66.66% Branches 18/27
100% Functions 3/3
92.1% Lines 35/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152                                                                  651x   651x   651x       651x     651x   651x 130x 91x     39x 40x   40x       17x                       23x 23x   23x               23x 23x     23x       651x 92x                 92x   92x     651x 539x     112x                 651x 651x 651x       651x   651x               651x   651x 651x   651x             651x            
import { usePositionPresentationStore } from '../../stores/usePositionPresentationStore';
import { useLutPresentationStore } from '../../stores/useLutPresentationStore';
import { useSegmentationPresentationStore } from '../../stores/useSegmentationPresentationStore';
import { isDisplaySetOverlayable } from '../isDisplaySetOverlayable';
import type { SegmentationPresentation } from '../../types/Presentation';
 
/**
 * Collects the segmentation presentation items that apply to this viewport.
 *
 * The store is keyed by the exact display set a segmentation was hydrated
 * against, because two display sets can share a frame of reference while being
 * unrelated series - so the frame of reference cannot be a key. But which
 * viewports a hydrated segmentation belongs in is a relation, not a key: it is
 * `isDisplaySetOverlayable`, which lets a segmentation over a reconstructable
 * volume also apply to other co-registered volumes in the same frame of
 * reference. So the keyed lookup is followed by a scan of the remaining entries
 * for items that are overlayable on this viewport's background.
 *
 * The keyed entry wins on conflict: it is the segmentation's own display set,
 * so its recorded type and hydration state are the authoritative ones.
 *
 * Provisional shape: this reads the store imperatively via getState() and
 * rescans on every setViewportData, which is cheap today (one entry per
 * referenced display set) but is not a hash lookup any more. The intended
 * direction is a zustand store with selectors, so the relation is expressed as
 * a memoized selector over the presentation state rather than a scan here.
 */
function getSegmentationPresentation({
  segmentationPresentationId,
  segmentationPresentationStore,
  displaySets,
  displaySetService,
}): SegmentationPresentation | null {
  const keyed = segmentationPresentationStore[segmentationPresentationId];
 
  const backgroundDisplaySet = displaySets?.find(displaySet => !displaySet?.isOverlayDisplaySet);
 
  Iif (!backgroundDisplaySet || !displaySetService) {
    return keyed ?? null;
  }
 
  const bySegmentationId = new Map();
  // The segmentationIds bySegmentationId holds the referenced display set's own
  // entry for.
  const fromReferencedKey = new Set();
 
  for (const [key, items] of Object.entries(segmentationPresentationStore)) {
    if (key === segmentationPresentationId) {
      continue;
    }
 
    for (const item of (items as SegmentationPresentation) ?? []) {
      const derivedDisplaySet = displaySetService.getDisplaySetByUID(item.segmentationId);
 
      if (
        !derivedDisplaySet ||
        !isDisplaySetOverlayable({ displaySet: derivedDisplaySet, backgroundDisplaySet })
      ) {
        continue;
      }
 
      // More than one key can carry an entry for the same segmentation once it
      // reaches co-registered panes: the referenced display set's own key holds
      // the hydration statement, while every other pane it is merely rendered
      // in writes a bookkeeping `hydrated: null` through
      // syncSegmentationPresentation. Object.entries order must not decide
      // which of those a third pane resolves against - a null winning here
      // would silently leave the segmentation out of that pane. So prefer the
      // referenced display set's entry, then any entry that states a hydration
      // at all.
      const isReferencedKey = key === derivedDisplaySet.referencedDisplaySetInstanceUID;
      const existing = bySegmentationId.get(item.segmentationId);
 
      Iif (
        existing &&
        !isReferencedKey &&
        (fromReferencedKey.has(item.segmentationId) || item.hydrated == null)
      ) {
        continue;
      }
 
      if (isReferencedKey) {
        fromReferencedKey.add(item.segmentationId);
      }
 
      bySegmentationId.set(item.segmentationId, item);
    }
  }
 
  for (const item of keyed ?? []) {
    const related = bySegmentationId.get(item.segmentationId);
 
    // The keyed entry wins on type/config, but `hydrated: null` is "no
    // statement", not "not hydrated" - storePresentation writes exactly that
    // for every pane a segmentation is merely *rendered* in, since hydration is
    // owned by the referenced display set's own entry. Letting it overwrite an
    // explicit hydration would erase the relation after one store/restore
    // cycle, and the segmentation would silently vanish from the co-registered
    // panes it reached through this scan.
    const hydrated = item.hydrated == null && related ? related.hydrated : item.hydrated;
 
    bySegmentationId.set(item.segmentationId, { ...item, hydrated });
  }
 
  if (!bySegmentationId.size) {
    return keyed ?? null;
  }
 
  return Array.from(bySegmentationId.values());
}
 
export function getViewportPresentations(
  viewportId: string,
  viewportOptions: AppTypes.ViewportGrid.GridViewportOptions,
  displaySets?: AppTypes.DisplaySet[],
  displaySetService?: AppTypes.DisplaySetService
) {
  const { lutPresentationStore } = useLutPresentationStore.getState();
  const { positionPresentationStore } = usePositionPresentationStore.getState();
  const { segmentationPresentationStore } = useSegmentationPresentationStore.getState();
 
  // NOTE: this is the new viewport state, we should not get the presentationIds from the cornerstoneViewportService
  // since that has the old viewport state
  const { presentationIds } = viewportOptions;
 
  Iif (!presentationIds) {
    return {
      positionPresentation: null,
      lutPresentation: null,
      segmentationPresentation: null,
    };
  }
 
  const { lutPresentationId, positionPresentationId, segmentationPresentationId } = presentationIds;
 
  const positionPresentation = positionPresentationStore[positionPresentationId];
  const lutPresentation = lutPresentationStore[lutPresentationId];
 
  const segmentationPresentation = getSegmentationPresentation({
    segmentationPresentationId,
    segmentationPresentationStore,
    displaySets,
    displaySetService,
  });
 
  return {
    positionPresentation,
    lutPresentation,
    segmentationPresentation,
  };
}