All files / extensions/cornerstone/src/utils interleaveTopToBottom.ts

96.72% Statements 59/61
71.42% Branches 5/7
100% Functions 13/13
96.66% Lines 58/60

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 153 154 155 156 157 158            62x 62x                             4x       4x 4x 4x   4x         4x 2x 2x       4x 4x               4x 40x 40x   40x 52x 52x 52x       40x 40x                                 4x 4x 10x 10x 10x       4x   4x 40x 40x 52x       4x 3x     1x   1x 2x         1x 1x 2x   2x         2x         1x     1x 1x 94x   94x 6674x 188x 94x         1x 1x   1x 94x   94x       1x     1x     1x   1x    
import { cache, imageLoadPoolManager, Enums } from '@cornerstonejs/core';
import zip from 'lodash.zip';
import compact from 'lodash.compact';
import flatten from 'lodash.flatten';
 
// Map of volumeId and SeriesInstanceId
const volumeIdMapsToLoad = new Map<string, string>();
const viewportIdVolumeInputArrayMap = new Map<string, unknown[]>();
 
/**
 * This function caches the volumeIds until all the volumes inside the
 * hanging protocol are initialized. Then it goes through the imageIds
 * of the volumes, and interleave them, in order for the volumes to be loaded
 * together from middle to the start and the end.
 * @param {Object} {viewportData, displaySetMatchDetails}
 * @returns
 */
export default function interleaveTopToBottom({
  data: { viewportId, volumeInputArray },
  displaySetsMatchDetails,
  viewportMatchDetails: matchDetails,
}) {
  viewportIdVolumeInputArrayMap.set(viewportId, volumeInputArray);
 
  // Based on the volumeInputs store the volumeIds and SeriesInstanceIds
  // to keep track of the volumes being loaded
  for (const volumeInput of volumeInputArray) {
    const { volumeId } = volumeInput;
    const volume = cache.getVolume(volumeId);
 
    Iif (!volume) {
      return;
    }
 
    // if the volumeUID is not in the volumeUIDs array, add it
    if (!volumeIdMapsToLoad.has(volumeId)) {
      const { metadata } = volume;
      volumeIdMapsToLoad.set(volumeId, metadata.SeriesInstanceUID);
    }
  }
 
  const filteredMatchDetails = [];
  const displaySetsToLoad = new Set();
 
  // Check all viewports that have a displaySet to be loaded. In some cases
  // (eg: line chart viewports which is not a Cornerstone viewport) the
  // displaySet is created on the client and there are no instances to be
  // downloaded. For those viewports the displaySet may have the `skipLoading`
  // option set to true otherwise it may block the download of all other
  // instances resulting in blank viewports.
  Array.from(matchDetails.values()).forEach(curMatchDetails => {
    const { displaySetsInfo } = curMatchDetails;
    let numDisplaySetsToLoad = 0;
 
    displaySetsInfo.forEach(({ displaySetInstanceUID, displaySetOptions }) => {
      if (!displaySetOptions?.options?.skipLoading) {
        numDisplaySetsToLoad++;
        displaySetsToLoad.add(displaySetInstanceUID);
      }
    });
 
    if (numDisplaySetsToLoad) {
      filteredMatchDetails.push(curMatchDetails);
    }
  });
 
  /**
   * The following is checking if all the viewports that were matched in the HP has been
   * successfully created their cornerstone viewport or not. Todo: This can be
   * improved by not checking it, and as soon as the matched DisplaySets have their
   * volume loaded, we start the loading, but that comes at the cost of viewports
   * not being created yet (e.g., in a 10 viewport ptCT fusion, when one ct viewport and one
   * pt viewport are created we have a guarantee that the volumes are created in the cache
   * but the rest of the viewports (fusion, mip etc.) are not created yet. So
   * we can't initiate setting the volumes for those viewports. One solution can be
   * to add an event when a viewport is created (not enabled element event) and then
   * listen to it and as the other viewports are created we can set the volumes for them
   * since volumes are already started loading.
   */
  const uniqueViewportVolumeDisplaySetUIDs = new Set();
  viewportIdVolumeInputArrayMap.forEach((volumeInputArray, viewportId) => {
    volumeInputArray.forEach(volumeInput => {
      const { volumeId } = volumeInput;
      uniqueViewportVolumeDisplaySetUIDs.add(volumeId);
    });
  });
 
  const uniqueMatchedDisplaySetUIDs = new Set();
 
  matchDetails.forEach(matchDetail => {
    const { displaySetsInfo } = matchDetail;
    displaySetsInfo.forEach(({ displaySetInstanceUID }) => {
      uniqueMatchedDisplaySetUIDs.add(displaySetInstanceUID);
    });
  });
 
  if (uniqueViewportVolumeDisplaySetUIDs.size !== uniqueMatchedDisplaySetUIDs.size) {
    return;
  }
 
  const volumeIds = Array.from(volumeIdMapsToLoad.keys()).slice();
  // get volumes from cache
  const volumes = volumeIds.map(volumeId => {
    return cache.getVolume(volumeId);
  });
 
  // iterate over all volumes, and get their imageIds, and interleave
  // the imageIds and save them in AllRequests for later use
  const AllRequests = [];
  volumes.forEach(volume => {
    const requests = volume.getImageLoadRequests();
 
    Iif (!requests?.[0]?.imageId) {
      return;
    }
 
    // reverse the requests
    AllRequests.push(requests.reverse());
  });
 
  // flatten the AllRequests array, which will result in a list of all the
  // imageIds for all the volumes but interleaved
  const interleavedRequests = compact(flatten(zip(...AllRequests)));
 
  // set the finalRequests to the imageLoadPoolManager
  const finalRequests = [];
  interleavedRequests.forEach(request => {
    const { imageId } = request;
 
    AllRequests.forEach(volumeRequests => {
      const volumeImageIdRequest = volumeRequests.find(req => req.imageId === imageId);
      if (volumeImageIdRequest) {
        finalRequests.push(volumeImageIdRequest);
      }
    });
  });
 
  const requestType = Enums.RequestType.Prefetch;
  const priority = 0;
 
  finalRequests.forEach(({ callLoadImage, additionalDetails, imageId, imageIdIndex, options }) => {
    const callLoadImageBound = callLoadImage.bind(null, imageId, imageIdIndex, options);
 
    imageLoadPoolManager.addRequest(callLoadImageBound, requestType, additionalDetails, priority);
  });
 
  // clear the volumeIdMapsToLoad
  volumeIdMapsToLoad.clear();
 
  // copy the viewportIdVolumeInputArrayMap
  const viewportIdVolumeInputArrayMapCopy = new Map(viewportIdVolumeInputArrayMap);
 
  // reset the viewportIdVolumeInputArrayMap
  viewportIdVolumeInputArrayMap.clear();
 
  return viewportIdVolumeInputArrayMapCopy;
}