All files / dicomImageLoader/src/imageLoader/wadors loadImageFromRegistry.ts

25.64% Statements 10/39
11.76% Branches 2/17
40% Functions 2/5
25.64% Lines 10/39

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          128x   128x       4080x 4080x     128x                                                                                             4080x     4080x 4080x               4080x 4080x                                            
import { Enums, metaData } from '@cornerstonejs/core';
import { Enums as MetadataEnums } from '@cornerstonejs/metadata';
import createImage from '../createImage';
import type { DICOMLoaderIImage, DICOMLoaderImageOptions } from '../../types';
 
const { ImageQualityStatus } = Enums;
const COMPRESSED_FRAME_DATA =
  MetadataEnums.MetadataModules.COMPRESSED_FRAME_DATA;
 
/** 0-based frame index from a WADO-RS frame imageId (…/frames/N). Defaults to 0. */
function getWadorsFrameIndex(imageId: string): number {
  const match = imageId.match(/\/frames\/(\d+)/);
  return match ? Math.max(0, Number(match[1]) - 1) : 0;
}
 
const asByteArray = (data) =>
  data instanceof ArrayBuffer ? new Uint8Array(data) : data;
 
/** Concatenate encapsulated fragments (array) or pass a single buffer through. */
function concatPixelData(pixelData) {
  if (!Array.isArray(pixelData)) {
    return asByteArray(pixelData);
  }
  if (pixelData.length === 0) {
    return undefined;
  }
  if (pixelData.length === 1) {
    return asByteArray(pixelData[0]);
  }
 
  let totalLength = 0;
  for (const fragment of pixelData) {
    totalLength += asByteArray(fragment).length;
  }
  const result = new Uint8Array(totalLength);
  let offset = 0;
  for (const fragment of pixelData) {
    const view = asByteArray(fragment);
    result.set(view, offset);
    offset += view.length;
  }
  return result;
}
 
/**
 * If a full Part 10 instance has been registered into the NATURALIZED metadata
 * registry (e.g. by a full-instance prefetch via `addDicomPart10Instance` /
 * `prefetchPart10Instance`), serve this WADO-RS frame's compressed pixel data
 * from that registry instead of issuing a per-frame `/frames/N` request.
 *
 * Returns `undefined` when the registry has no data for this frame, in which
 * case the caller must fall back to the normal network path. This keeps the
 * registry the single uniform source: WADO-URI already consults it via
 * `loadImageFromNaturalizedMetadata`, and this makes WADO-RS consult it too.
 *
 * Decode is identical to the network path (same `createImage` + worker
 * decoder); only the origin of the compressed bytes differs.
 */
export function loadImageFromCompressedFrameRegistry(
  imageId: string,
  options: DICOMLoaderImageOptions = {}
): Promise<DICOMLoaderIImage> | undefined {
  const frameIndex = getWadorsFrameIndex(imageId);
 
  let frameData;
  try {
    frameData = metaData.getTyped(COMPRESSED_FRAME_DATA, imageId, {
      frameIndex,
    });
  } catch (error) {
    // A misbehaving provider must never break normal per-frame loading.
    return undefined;
  }
 
  Eif (!frameData?.pixelData) {
    return undefined;
  }
 
  const { pixelData, transferSyntaxUid } = frameData;
  const concatenated = concatPixelData(pixelData);
  if (!concatenated?.length || !transferSyntaxUid) {
    return undefined;
  }
 
  const start = Date.now();
  return createImage(imageId, concatenated, transferSyntaxUid, options).then(
    (image) => {
      const out = image as DICOMLoaderIImage;
      out.imageQualityStatus = ImageQualityStatus.FULL_RESOLUTION;
      out.transferSyntaxUID = transferSyntaxUid;
      out.loadTimeInMS = Date.now() - start;
      return out;
    }
  );
}
 
export default loadImageFromCompressedFrameRegistry;