diff --git a/.eslintrc.strict.cjs b/.eslintrc.strict.cjs index 9faaabc1..15cc1b91 100644 --- a/.eslintrc.strict.cjs +++ b/.eslintrc.strict.cjs @@ -1,6 +1,6 @@ /** * ESLint config that approximates DeepSource + SonarQube rules. - * Run with: bun run lint:strict (or: bunx eslint --config .eslintrc.strict.cjs src/) + * Run with: pnpm run lint:strict (or: pnpm exec eslint --config .eslintrc.strict.cjs src/) * * DeepSource's JavaScript analyzer uses ESLint under the hood and supports * style_guide (e.g. "standard"). SonarQube rules are available via eslint-plugin-sonarjs. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e5cd8e2d..a184a6d1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,6 +38,7 @@ jobs: - name: Bump version and cut a release env: + HUSKY: 0 GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} GIT_AUTHOR_NAME: ${{ vars.RELEASE_GIT_AUTHOR_NAME }} GIT_AUTHOR_EMAIL: ${{ vars.RELEASE_GIT_AUTHOR_EMAIL }} diff --git a/.husky/pre-commit b/.husky/pre-commit index c5c5e4a2..a9b867ad 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,3 +1,3 @@ #!/usr/bin/env sh # Block commit if typecheck or lint fails -bun run typecheck && bun run lint +pnpm run typecheck && pnpm run lint diff --git a/README.md b/README.md index db806465..e79f57d8 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ The configuration can be changed at build-time using the `REACT_APP_CONFIG` envi When `enableServerSelection` is enabled in config, users can switch the active DICOMweb server at runtime via the header. - **Full URLs**: Paste the complete server URL (e.g. `https://healthcare.googleapis.com/v1/projects/.../dicomWeb`). -- **Path-only (GCP Healthcare)**: Paste a GCP DICOM store path without the domain (e.g. `/projects/my-project/locations/us-central1/datasets/my-dataset/dicomStores/my-store/dicomWeb`). The app prepends `https://healthcare.googleapis.com/v1` automatically. +- **Path-only (GCP Healthcare)**: Paste a GCP DICOM store path without the domain (e.g. `/projects/my-project/locations/us-central1/datasets/my-dataset/dicomStores/my-store`). The app prepends `https://healthcare.googleapis.com/v1` and appends `/dicomWeb` automatically. Authorization is re-applied when switching servers, so a page reload is not needed after changing the active server. diff --git a/bunfig.toml b/bunfig.toml deleted file mode 100644 index 9c5cd558..00000000 --- a/bunfig.toml +++ /dev/null @@ -1,2 +0,0 @@ -[test] -preload = ["./src/test/bun-preload.ts"] diff --git a/package.json b/package.json index 5926bd07..eb33dc64 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "classnames": "^2.2.6", "dcmjs": "^0.35.0", "detect-browser": "^5.2.1", - "dicom-microscopy-viewer": "^0.48.21", + "dicom-microscopy-viewer": "^0.48.22", "dicomweb-client": "0.10.3", "oidc-client": "^1.11.5", "ol": "^10.7.0", @@ -82,7 +82,6 @@ "eslint": "^8.57.0", "eslint-plugin-sonarjs": "^0.25.0", "gh-pages": "^5.0.0", - "happy-dom": "^20.8.7", "husky": "^9.1.7", "react-scripts": "5.0.0", "react-test-renderer": "^18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03911632..d4179929 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,8 +35,8 @@ importers: specifier: ^5.2.1 version: 5.3.0 dicom-microscopy-viewer: - specifier: ^0.48.21 - version: 0.48.21 + specifier: ^0.48.22 + version: 0.48.22 dicomweb-client: specifier: 0.10.3 version: 0.10.3 @@ -152,9 +152,6 @@ importers: gh-pages: specifier: ^5.0.0 version: 5.0.0 - happy-dom: - specifier: ^20.8.7 - version: 20.10.1 husky: specifier: ^9.1.7 version: 9.1.7 @@ -1736,9 +1733,6 @@ packages: '@types/node@14.18.63': resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} - '@types/node@25.9.1': - resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} - '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1816,9 +1810,6 @@ packages: '@types/uuid@8.3.4': resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} - '@types/whatwg-mimetype@3.0.2': - resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} - '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -2417,10 +2408,6 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer-image-size@0.6.4: - resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==} - engines: {node: '>=4.0'} - builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -3033,8 +3020,8 @@ packages: engines: {node: '>= 4.2.1'} hasBin: true - dicom-microscopy-viewer@0.48.21: - resolution: {integrity: sha512-hOtT0DWvGk9yCVwLBfWzh8oeK1UG0eZRA5mOXdebMQ49LrbqR82WQPb8DXGqHBgeS/znx2d6KNfOfJkhpeIg7Q==} + dicom-microscopy-viewer@0.48.22: + resolution: {integrity: sha512-IkFwh9lyAjhb6HXbSvZZu0gc462CLGN6h+6iWaZASsnaskRj3Mlrq9QOgzQJ/iYZP/aoaL87UAWn8Q2ZV+sceQ==} dicomweb-client@0.10.3: resolution: {integrity: sha512-/fHNEAYiz8j+9TNOrNJ0k+hYqirbOT85B7vM7I4VkY8DeDQb4BDUeL3RX6huDVtn6ZQlR91dI+2tejLc5c99wA==} @@ -3183,10 +3170,6 @@ packages: entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - entities@7.0.1: - resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} - engines: {node: '>=0.12'} - env-ci@9.1.1: resolution: {integrity: sha512-Im2yEWeF4b2RAMAaWvGioXk6m0UNaIjD8hj28j2ij5ldnIFrDQT0+pzDvpbRkcjurhXhf/AsBKv8P2rtmGi9Aw==} engines: {node: ^16.14 || >=18} @@ -3831,10 +3814,6 @@ packages: engines: {node: '>=0.4.7'} hasBin: true - happy-dom@20.10.1: - resolution: {integrity: sha512-awPoqPjx8CgjapJllyDlgzgVHjBExcitKK5ZJkxwhQJyQpHFkyS2bEcqCm7IeW20cQvuCI0cz2Ifq79CJKqtiw==} - engines: {node: '>=20.0.0'} - hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -7289,9 +7268,6 @@ packages: underscore@1.13.8: resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -7509,10 +7485,6 @@ packages: whatwg-mimetype@2.3.0: resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==} - whatwg-mimetype@3.0.0: - resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} - engines: {node: '>=12'} - whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} @@ -9696,10 +9668,6 @@ snapshots: '@types/node@14.18.63': {} - '@types/node@25.9.1': - dependencies: - undici-types: 7.24.6 - '@types/normalize-package-data@2.4.4': {} '@types/parse-json@4.0.2': {} @@ -9779,8 +9747,6 @@ snapshots: '@types/uuid@8.3.4': {} - '@types/whatwg-mimetype@3.0.2': {} - '@types/ws@8.18.1': dependencies: '@types/node': 14.18.63 @@ -10543,10 +10509,6 @@ snapshots: buffer-from@1.1.2: {} - buffer-image-size@0.6.4: - dependencies: - '@types/node': 14.18.63 - builtin-modules@3.3.0: {} bytes@3.1.2: {} @@ -11183,7 +11145,7 @@ snapshots: transitivePeerDependencies: - supports-color - dicom-microscopy-viewer@0.48.21: + dicom-microscopy-viewer@0.48.22: dependencies: '@cornerstonejs/codec-charls': 1.2.3 '@cornerstonejs/codec-libjpeg-turbo-8bit': 1.2.2 @@ -11326,8 +11288,6 @@ snapshots: entities@2.2.0: {} - entities@7.0.1: {} - env-ci@9.1.1: dependencies: execa: 7.2.0 @@ -12244,19 +12204,6 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - happy-dom@20.10.1: - dependencies: - '@types/node': 25.9.1 - '@types/whatwg-mimetype': 3.0.2 - '@types/ws': 8.18.1 - buffer-image-size: 0.6.4 - entities: 7.0.1 - whatwg-mimetype: 3.0.0 - ws: 8.21.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - hard-rejection@2.1.0: {} harmony-reflect@1.6.2: {} @@ -16102,8 +16049,6 @@ snapshots: underscore@1.13.8: {} - undici-types@7.24.6: {} - unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: @@ -16349,8 +16294,6 @@ snapshots: whatwg-mimetype@2.3.0: {} - whatwg-mimetype@3.0.0: {} - whatwg-url@7.1.0: dependencies: lodash.sortby: 4.7.0 diff --git a/public/index.html b/public/index.html index 223aa297..3782625f 100644 --- a/public/index.html +++ b/public/index.html @@ -61,8 +61,8 @@ You can add webfonts, meta tags, or analytics to this file. The build step will place the bundled scripts into the tag. - To begin the development, run `npm start` or `bun run start`. - To create a production bundle, use `npm run build` or `bun run build`. + To begin the development, run `pnpm start`. + To create a production bundle, use `pnpm run build`. --> diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 2e04f000..2337f056 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -727,7 +727,7 @@ class Header extends React.Component { {this.state.serverSelectionMode === 'custom' && ( { greenDescriptor: cpLUTItem.GreenPaletteColorLookupTableDescriptor, blueDescriptor: cpLUTItem.BluePaletteColorLookupTableDescriptor, - redData: - cpLUTItem.RedPaletteColorLookupTableData !== null && - cpLUTItem.RedPaletteColorLookupTableData !== undefined - ? new Uint16Array(cpLUTItem.RedPaletteColorLookupTableData) - : undefined, + // Pass the LUT data through as retrieved. The element size of + // Palette Color Lookup Table Data is governed by the third + // value of the descriptor (bits per entry), not by the VR, so + // dicom-microscopy-viewer reinterprets the bytes accordingly. + // In particular, conformant Presentation States encode 8-bit + // entries (descriptor [n, first, 8]) byte-packed inside the + // OW element; eagerly wrapping in a Uint16Array here would + // halve the entry count and break the LUT. + redData: cpLUTItem.RedPaletteColorLookupTableData ?? undefined, greenData: - cpLUTItem.GreenPaletteColorLookupTableData !== null && - cpLUTItem.GreenPaletteColorLookupTableData !== undefined - ? new Uint16Array( - cpLUTItem.GreenPaletteColorLookupTableData, - ) - : undefined, + cpLUTItem.GreenPaletteColorLookupTableData ?? undefined, blueData: - cpLUTItem.BluePaletteColorLookupTableData !== null && - cpLUTItem.BluePaletteColorLookupTableData !== undefined - ? new Uint16Array(cpLUTItem.BluePaletteColorLookupTableData) - : undefined, + cpLUTItem.BluePaletteColorLookupTableData ?? undefined, redSegmentedData: - cpLUTItem.SegmentedRedPaletteColorLookupTableData !== null && - cpLUTItem.SegmentedRedPaletteColorLookupTableData !== - undefined - ? new Uint16Array( - cpLUTItem.SegmentedRedPaletteColorLookupTableData, - ) - : undefined, + cpLUTItem.SegmentedRedPaletteColorLookupTableData ?? + undefined, greenSegmentedData: - cpLUTItem.SegmentedGreenPaletteColorLookupTableData !== - null && - cpLUTItem.SegmentedGreenPaletteColorLookupTableData !== - undefined - ? new Uint16Array( - cpLUTItem.SegmentedGreenPaletteColorLookupTableData, - ) - : undefined, + cpLUTItem.SegmentedGreenPaletteColorLookupTableData ?? + undefined, blueSegmentedData: - cpLUTItem.SegmentedBluePaletteColorLookupTableData !== null && - cpLUTItem.SegmentedBluePaletteColorLookupTableData !== - undefined - ? new Uint16Array( - cpLUTItem.SegmentedBluePaletteColorLookupTableData, - ) - : undefined, + cpLUTItem.SegmentedBluePaletteColorLookupTableData ?? + undefined, }) } diff --git a/src/test/bun-preload.ts b/src/test/bun-preload.ts deleted file mode 100644 index e785de67..00000000 --- a/src/test/bun-preload.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Registers a minimal browser-like global for `bun test` (jsdom is Jest-only here). - * Official CI still uses `npm test` / craco. - * - * happy-dom types intentionally do not match lib.dom; use loose assignments so - * this file typechecks under CRA `tsc` while Bun still runs it as preload. - */ -import { GlobalWindow } from 'happy-dom' - -const w = new GlobalWindow({ url: 'http://localhost/' }) - -const gt = globalThis as Record -gt.window = w -gt.document = w.document - -globalThis.getComputedStyle = ((element: unknown, _pseudoElt?: string | null) => - w.getComputedStyle( - element as never, - )) as unknown as typeof globalThis.getComputedStyle - -globalThis.matchMedia = w.matchMedia.bind( - w, -) as unknown as typeof globalThis.matchMedia - -/** rc-table / antd use global `Element` in `instanceof` checks */ -const domCtors = [ - 'Element', - 'HTMLElement', - 'HTMLDivElement', - 'HTMLTableElement', - 'HTMLTableSectionElement', - 'HTMLTableRowElement', - 'HTMLTableCellElement', - 'Node', - 'DocumentFragment', - 'Text', - 'Comment', - 'Event', - 'CustomEvent', - 'MouseEvent', -] as const -for (const name of domCtors) { - const ctor = (w as unknown as Record)[name] - if (typeof ctor === 'function') { - gt[name] = ctor - } -} - -if (typeof globalThis.requestAnimationFrame !== 'function') { - const handles = new Map>() - let nextRafId = 1 - globalThis.requestAnimationFrame = (cb: FrameRequestCallback): number => { - const handle = w.setTimeout(() => { - cb(Date.now()) - }, 0) - const id = nextRafId++ - handles.set(id, handle) - return id - } - globalThis.cancelAnimationFrame = (id: number): void => { - const handle = handles.get(id) - if (handle !== undefined) { - w.clearTimeout(handle) - handles.delete(id) - } - } -} diff --git a/src/utils/__tests__/url.test.ts b/src/utils/__tests__/url.test.ts new file mode 100644 index 00000000..32da4b36 --- /dev/null +++ b/src/utils/__tests__/url.test.ts @@ -0,0 +1,63 @@ +import { GCP_HEALTHCARE_V1_BASE, normalizeServerUrl } from '../url' + +const storePath = + '/projects/idc-sandbox-000/locations/us-central1/datasets/fedorov-dev-healthcare/dicomStores/sardana-lut-test' + +const expectedUrl = `${GCP_HEALTHCARE_V1_BASE}${storePath}/dicomWeb` + +describe('normalizeServerUrl', () => { + it('prepends the GCP base for path-only store URLs', () => { + expect(normalizeServerUrl(`${storePath}/dicomWeb`)).toBe(expectedUrl) + }) + + it('appends /dicomWeb when a GCP store path omits it', () => { + expect(normalizeServerUrl(storePath)).toBe(expectedUrl) + expect( + normalizeServerUrl( + 'projects/idc-sandbox-000/locations/us-central1/datasets/fedorov-dev-healthcare/dicomStores/sardana-lut-test', + ), + ).toBe(expectedUrl) + }) + + it('appends /dicomWeb for full GCP URLs that omit it', () => { + expect( + normalizeServerUrl(`https://healthcare.googleapis.com/v1${storePath}`), + ).toBe(expectedUrl) + }) + + it('does not duplicate /dicomWeb when already present', () => { + const fullUrl = `https://healthcare.googleapis.com/v1${storePath}/dicomWeb` + expect(normalizeServerUrl(fullUrl)).toBe(fullUrl) + expect(normalizeServerUrl(`${storePath}/dicomWeb/`)).toBe(expectedUrl) + }) + + it('deduplicates repeated /dicomWeb segments', () => { + expect(normalizeServerUrl(`${storePath}/dicomWeb/dicomWeb`)).toBe( + expectedUrl, + ) + expect( + normalizeServerUrl( + `https://healthcare.googleapis.com/v1${storePath}/dicomWeb/dicomWeb`, + ), + ).toBe(expectedUrl) + }) + + it('normalizes /dicomWeb casing to the canonical GCP suffix', () => { + expect(normalizeServerUrl(`${storePath}/dicomweb`)).toBe(expectedUrl) + expect(normalizeServerUrl(`${storePath}/DICOMWEB`)).toBe(expectedUrl) + }) + + it('preserves query parameters on full URLs', () => { + expect( + normalizeServerUrl( + `https://healthcare.googleapis.com/v1${storePath}?alt=json`, + ), + ).toBe(`${expectedUrl}?alt=json`) + }) + + it('does not append /dicomWeb for non-GCP servers', () => { + const proxyUrl = + 'https://proxy.imaging.datacommons.cancer.gov/current/viewer-only-no-downloads-see-tinyurl-dot-com-slash-3j3d9jyp/dicomWeb' + expect(normalizeServerUrl(proxyUrl)).toBe(proxyUrl) + }) +}) diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 6eda6eba..5727ce5a 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -20,7 +20,7 @@ export class Logger { public config: LoggerConfig constructor() { - // Get logger config from global config (browser only; Bun/Jest may run without window) + // Get logger config from global config (browser only; Jest may run without window) const globalConfig = typeof window !== 'undefined' ? window.config?.logger : undefined let configLevel = 'DEBUG' diff --git a/src/utils/url.tsx b/src/utils/url.tsx index 497e436d..e148233f 100644 --- a/src/utils/url.tsx +++ b/src/utils/url.tsx @@ -1,16 +1,55 @@ export const GCP_HEALTHCARE_V1_BASE = 'https://healthcare.googleapis.com/v1' +const GCP_DICOM_WEB_SUFFIX = '/dicomWeb' + +/** GCP store resource path with optional repeated /dicomWeb suffix (any casing). */ +const GCP_STORE_PATH_PATTERN = /^(.+\/dicomStores\/[^/]+)(?:\/dicomWeb)*$/i + +/** + * Ensure a pathname ends with exactly one /dicomWeb when it targets a GCP DICOM store. + * Leaves non-GCP paths unchanged. + */ +const ensureGcpDicomWebOnPath = (pathname: string): string => { + const trimmed = pathname.replace(/\/+$/, '') + if (!trimmed.includes('/dicomStores/')) { + return trimmed + } + + const match = trimmed.match(GCP_STORE_PATH_PATTERN) + if (match) { + return `${match[1]}${GCP_DICOM_WEB_SUFFIX}` + } + + return trimmed +} + +/** Normalize GCP DICOMweb base URLs: add /dicomWeb when missing, dedupe when repeated. */ +const ensureGcpDicomWebSuffix = (url: string): string => { + if (url.startsWith('http://') || url.startsWith('https://')) { + try { + const parsed = new URL(url) + parsed.pathname = ensureGcpDicomWebOnPath(parsed.pathname) + return parsed.toString() + } catch (_TypeError) { + return ensureGcpDicomWebOnPath(url) + } + } + + return ensureGcpDicomWebOnPath(url) +} + /** * Normalize server URL. Path-only input (no domain) is prepended with GCP Healthcare v1 base - * so users can paste GCP DICOM store paths without the full domain. + * so users can paste GCP DICOM store paths without the full domain. GCP store paths that omit + * the DICOMweb endpoint get /dicomWeb appended automatically. */ export const normalizeServerUrl = (input: string): string => { const trimmed = input.trim() if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) { - return trimmed + return ensureGcpDicomWebSuffix(trimmed) } const path = trimmed.startsWith('/') ? trimmed : `/${trimmed}` - return `${GCP_HEALTHCARE_V1_BASE}${path}` + return ensureGcpDicomWebSuffix(`${GCP_HEALTHCARE_V1_BASE}${path}`) } /** diff --git a/types/dicom-microscopy-viewer/index.d.ts b/types/dicom-microscopy-viewer/index.d.ts index e13032fd..49f8432b 100644 --- a/types/dicom-microscopy-viewer/index.d.ts +++ b/types/dicom-microscopy-viewer/index.d.ts @@ -691,12 +691,12 @@ declare module 'dicom-microscopy-viewer' { RedPaletteColorLookupTableDescriptor: number[] GreenPaletteColorLookupTableDescriptor: number[] BluePaletteColorLookupTableDescriptor: number[] - RedPaletteColorLookupTableData?: Uint16Array - GreenPaletteColorLookupTableData?: Uint16Array - BluePaletteColorLookupTableData?: Uint16Array - SegmentedRedPaletteColorLookupTableData?: Uint16Array - SegmentedGreenPaletteColorLookupTableData?: Uint16Array - SegmentedBluePaletteColorLookupTableData?: Uint16Array + RedPaletteColorLookupTableData?: Uint8Array|Uint16Array|ArrayBuffer + GreenPaletteColorLookupTableData?: Uint8Array|Uint16Array|ArrayBuffer + BluePaletteColorLookupTableData?: Uint8Array|Uint16Array|ArrayBuffer + SegmentedRedPaletteColorLookupTableData?: Uint8Array|Uint16Array|ArrayBuffer + SegmentedGreenPaletteColorLookupTableData?: Uint8Array|Uint16Array|ArrayBuffer + SegmentedBluePaletteColorLookupTableData?: Uint8Array|Uint16Array|ArrayBuffer PaletteColorLookupTableUID?: string }> SoftcopyVOILUTSequence: Array<{ @@ -813,12 +813,12 @@ declare module 'dicom-microscopy-viewer' { redDescriptor: number[] greenDescriptor: number[] blueDescriptor: number[] - redData?: Unit8Array|Unit16Array - greenData?: Unit8Array|Unit16Array - blueData?: Unit8Array|Unit16Array - redSegmentedData?: Unit8Array|Unit16Array - greenSegmentedData?: Unit8Array|Unit16Array - blueSegmentedData?: Unit8Array|Unit16Array + redData?: Uint8Array|Uint16Array|ArrayBuffer|number[] + greenData?: Uint8Array|Uint16Array|ArrayBuffer|number[] + blueData?: Uint8Array|Uint16Array|ArrayBuffer|number[] + redSegmentedData?: Uint8Array|Uint16Array|ArrayBuffer|number[] + greenSegmentedData?: Uint8Array|Uint16Array|ArrayBuffer|number[] + blueSegmentedData?: Uint8Array|Uint16Array|ArrayBuffer|number[] } export interface BuildPaletteColorLookupTableOptions {