diff --git a/package.json b/package.json index 4213e52..9b59d1e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cod-dicomweb-server", "title": "COD Dicomweb server", - "version": "1.3.17", + "version": "1.3.18", "private": false, "description": "A wadors server proxy that get data from a Cloud Optimized Dicom format.", "main": "dist/umd/main.js", @@ -93,6 +93,7 @@ "dependencies": { "comlink": "4.4.2", "dicom-parser": "1.8.21", - "idb-keyval": "6.2.2" + "idb-keyval": "6.2.2", + "zstddec": "^0.1.0" } } \ No newline at end of file diff --git a/src/classes/CodDicomWebServer.ts b/src/classes/CodDicomWebServer.ts index 28172a4..84ea8d0 100644 --- a/src/classes/CodDicomWebServer.ts +++ b/src/classes/CodDicomWebServer.ts @@ -451,9 +451,10 @@ class CodDicomWebServer { sopInstanceUID: string ): InstanceMetadata | SeriesMetadata { if (type === Enums.RequestType.INSTANCE_METADATA) { - return Object.entries(metadata.cod.instances).find(([key, instance]) => key === sopInstanceUID)?.[1].metadata; + return Object.entries(metadata.cod.instances).find(([key, instance]) => key === sopInstanceUID)?.[1] + .metadata as InstanceMetadata; } else { - return Object.values(metadata.cod.instances).map((instance) => instance.metadata); + return Object.values(metadata.cod.instances).map((instance) => instance.metadata as InstanceMetadata); } } } diff --git a/src/constants/index.ts b/src/constants/index.ts index 1960d07..813a99d 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,8 +1,9 @@ import * as Enums from './enums'; import * as url from './url'; import * as dataRetrieval from './dataRetrieval'; +import * as medatata from './metadata'; -const constants = { Enums, url, dataRetrieval }; +const constants = { Enums, url, dataRetrieval, medatata }; -export { Enums, url, dataRetrieval }; +export { Enums, url, dataRetrieval, medatata }; export default constants; diff --git a/src/constants/metadata.ts b/src/constants/metadata.ts new file mode 100644 index 0000000..b64d543 --- /dev/null +++ b/src/constants/metadata.ts @@ -0,0 +1,8 @@ +/** + * * V1 (1.0) - The metadata is of the type `InstanceMetadata`( Deprecated since cod-dicomweb-server@v1.3.18 ). + * * V2 (2.0) - The metadata is of the type `string`. + */ +export const METADATA_VERSION = { + V1: '1.0', + V2: '2.0' +}; diff --git a/src/metadataManager.ts b/src/metadataManager.ts index 923197a..d448f35 100644 --- a/src/metadataManager.ts +++ b/src/metadataManager.ts @@ -1,14 +1,32 @@ +import { ZSTDDecoder } from 'zstddec'; import { CustomError } from './classes/customClasses'; import { createMetadataJsonUrl } from './classes/utils'; +import { medatata } from './constants'; import { createMetadataFileName, getDirectoryHandle, readFile, writeFile } from './fileAccessSystemUtils'; -import type { JsonMetadata, MetadataUrlCreationParams } from './types'; +import type { InstanceMetadata, JsonMetadata, MetadataUrlCreationParams } from './types'; class MetadataManager { private metadataPromises: Record> = {}; + private decoder?: ZSTDDecoder; + private decoderInitPromise: Promise; - constructor() {} + constructor() { + this.decoder = null; + const decoder = new ZSTDDecoder(); - public addDeidMetadata(jsonMetadata: JsonMetadata, url: string): void { + this.decoderInitPromise = decoder + .init() + .then(() => { + this.decoder = decoder; + return true; + }) + .catch((error) => { + console.error('Failed to initialize ZSTD WASM module:', error); + return false; + }); + } + + public async addDeidMetadata(jsonMetadata: JsonMetadata, url: string): Promise { const { cod } = jsonMetadata; const [studyUID, _, seriesUID] = url.match(/studies\/(.*?)\/metadata/)?.[1].split('/') || []; @@ -19,9 +37,22 @@ class MetadataManager { for (const sopUID in cod.instances) { const instance = cod.instances[sopUID]; - instance.metadata.DeidStudyInstanceUID = { Value: [studyUID] }; - instance.metadata.DeidSeriesInstanceUID = { Value: [seriesUID] }; - instance.metadata.DeidSopInstanceUID = { Value: [sopUID] }; + + // For V2, convert the metadata to InstanceMetadata format. + if (instance.version === medatata.METADATA_VERSION.V2 && typeof instance.metadata === 'string') { + const parsedMetadata = await this.decodeDecompressAndParse(instance.metadata); + + if (!parsedMetadata) { + throw new Error('Failed to decode, decompress, or parse JSON'); + } + + instance.metadata = parsedMetadata; + } + + const instanceMetadata = instance.metadata as InstanceMetadata; + instanceMetadata.DeidStudyInstanceUID = { Value: [studyUID] }; + instanceMetadata.DeidSeriesInstanceUID = { Value: [seriesUID] }; + instanceMetadata.DeidSopInstanceUID = { Value: [sopUID] }; } } @@ -56,9 +87,10 @@ class MetadataManager { } return response.json(); }) - .then((data) => { - this.addDeidMetadata(data, url); - return writeFile(directoryHandle, fileName, data, true).then(() => data); + .then(async (data) => { + await this.addDeidMetadata(data, url); + await writeFile(directoryHandle, fileName, data, true); + return data; }); return await this.metadataPromises[url]; @@ -67,6 +99,26 @@ class MetadataManager { throw error; } } + + private async decodeDecompressAndParse(base64String: string): Promise { + if (!base64String) { + return null; + } + + try { + if (!(await this.decoderInitPromise)) { + throw new Error('WASM Decoder is not initialized. Cannot decompress data.'); + } + + const compressedBytes = Uint8Array.from(atob(base64String), (c) => c.charCodeAt(0)); + const decompressedBytes = this.decoder.decode(compressedBytes); + const jsonString = new TextDecoder().decode(decompressedBytes); + return JSON.parse(jsonString); + } catch (error) { + console.error('Failed to decode, decompress, or parse JSON:', error); + return null; + } + } } export default MetadataManager; diff --git a/src/types/metadata.ts b/src/types/metadata.ts index 841a2ef..a793ed8 100644 --- a/src/types/metadata.ts +++ b/src/types/metadata.ts @@ -8,7 +8,7 @@ type JsonMetadata = { instances: Record< string, { - metadata: InstanceMetadata; + metadata: InstanceMetadata | string; // The metadata will either have url or uri uri: string; url: string; diff --git a/yarn.lock b/yarn.lock index 3c46e0b..1e9d1db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5864,3 +5864,8 @@ yocto-queue@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g== + +zstddec@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.1.0.tgz#7050f3f0e0c3978562d0c566b3e5a427d2bad7ec" + integrity sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg==