Skip to content

Commit 1e5803e

Browse files
Added support for COD metadata v2.0
1 parent 9c74ba5 commit 1e5803e

7 files changed

Lines changed: 86 additions & 17 deletions

File tree

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "cod-dicomweb-server",
33
"title": "COD Dicomweb server",
4-
"version": "1.3.17",
4+
"version": "1.3.18",
55
"private": false,
66
"description": "A wadors server proxy that get data from a Cloud Optimized Dicom format.",
77
"main": "dist/umd/main.js",
@@ -93,6 +93,7 @@
9393
"dependencies": {
9494
"comlink": "4.4.2",
9595
"dicom-parser": "1.8.21",
96-
"idb-keyval": "6.2.2"
96+
"idb-keyval": "6.2.2",
97+
"zstddec": "^0.1.0"
9798
}
9899
}

src/classes/CodDicomWebServer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,10 @@ class CodDicomWebServer {
451451
sopInstanceUID: string
452452
): InstanceMetadata | SeriesMetadata {
453453
if (type === Enums.RequestType.INSTANCE_METADATA) {
454-
return Object.entries(metadata.cod.instances).find(([key, instance]) => key === sopInstanceUID)?.[1].metadata;
454+
return Object.entries(metadata.cod.instances).find(([key, instance]) => key === sopInstanceUID)?.[1]
455+
.metadata as InstanceMetadata;
455456
} else {
456-
return Object.values(metadata.cod.instances).map((instance) => instance.metadata);
457+
return Object.values(metadata.cod.instances).map((instance) => instance.metadata as InstanceMetadata);
457458
}
458459
}
459460
}

src/constants/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import * as Enums from './enums';
22
import * as url from './url';
33
import * as dataRetrieval from './dataRetrieval';
4+
import * as medatata from './metadata';
45

5-
const constants = { Enums, url, dataRetrieval };
6+
const constants = { Enums, url, dataRetrieval, medatata };
67

7-
export { Enums, url, dataRetrieval };
8+
export { Enums, url, dataRetrieval, medatata };
89
export default constants;

src/constants/metadata.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* * V1 (1.0) - The metadata is in the type `InstanceMetadata`( Deprecated since cod-dicomweb-server@v1.3.18 ).
3+
* * V2 (2.0) - The metadata is in the type `string`.
4+
*/
5+
export const METADATA_VERSION = {
6+
V1: '1.0',
7+
V2: '2.0'
8+
};

src/metadataManager.ts

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
1+
import { ZSTDDecoder } from 'zstddec';
2+
13
import { CustomError } from './classes/customClasses';
24
import { createMetadataJsonUrl } from './classes/utils';
5+
import { medatata } from './constants';
36
import { createMetadataFileName, getDirectoryHandle, readFile, writeFile } from './fileAccessSystemUtils';
4-
import type { JsonMetadata, MetadataUrlCreationParams } from './types';
7+
import type { InstanceMetadata, JsonMetadata, MetadataUrlCreationParams } from './types';
58

69
class MetadataManager {
710
private metadataPromises: Record<string, Promise<JsonMetadata>> = {};
11+
private decoder?: ZSTDDecoder;
12+
private decoderInitPromise: Promise<boolean>;
13+
14+
constructor() {
15+
this.decoder = null;
16+
const decoder = new ZSTDDecoder();
817

9-
constructor() {}
18+
this.decoderInitPromise = decoder
19+
.init()
20+
.then(() => {
21+
this.decoder = decoder;
22+
return true;
23+
})
24+
.catch((error) => {
25+
console.error('Failed to initialize ZSTD WASM module:', error);
26+
return false;
27+
});
28+
}
1029

11-
public addDeidMetadata(jsonMetadata: JsonMetadata, url: string): void {
30+
public async addDeidMetadata(jsonMetadata: JsonMetadata, url: string): Promise<void> {
1231
const { cod } = jsonMetadata;
1332
const [studyUID, _, seriesUID] = url.match(/studies\/(.*?)\/metadata/)?.[1].split('/') || [];
1433

@@ -19,9 +38,22 @@ class MetadataManager {
1938

2039
for (const sopUID in cod.instances) {
2140
const instance = cod.instances[sopUID];
22-
instance.metadata.DeidStudyInstanceUID = { Value: [studyUID] };
23-
instance.metadata.DeidSeriesInstanceUID = { Value: [seriesUID] };
24-
instance.metadata.DeidSopInstanceUID = { Value: [sopUID] };
41+
42+
// For V2, convert the metadata to InstanceMetadata format.
43+
if (instance.version === medatata.METADATA_VERSION.V2 && typeof instance.metadata === 'string') {
44+
const parsedMetadata = await this.decodeDecompressAndParse(instance.metadata);
45+
46+
if (!parsedMetadata) {
47+
throw new Error('Failed to decode, decompress, or parse JSON');
48+
}
49+
50+
instance.metadata = parsedMetadata;
51+
}
52+
53+
const instanceMetadata = instance.metadata as InstanceMetadata;
54+
instanceMetadata.DeidStudyInstanceUID = { Value: [studyUID] };
55+
instanceMetadata.DeidSeriesInstanceUID = { Value: [seriesUID] };
56+
instanceMetadata.DeidSopInstanceUID = { Value: [sopUID] };
2557
}
2658
}
2759

@@ -56,9 +88,10 @@ class MetadataManager {
5688
}
5789
return response.json();
5890
})
59-
.then((data) => {
60-
this.addDeidMetadata(data, url);
61-
return writeFile(directoryHandle, fileName, data, true).then(() => data);
91+
.then(async (data) => {
92+
await this.addDeidMetadata(data, url);
93+
await writeFile(directoryHandle, fileName, data, true);
94+
return data;
6295
});
6396

6497
return await this.metadataPromises[url];
@@ -67,6 +100,26 @@ class MetadataManager {
67100
throw error;
68101
}
69102
}
103+
104+
private async decodeDecompressAndParse(base64String: string): Promise<InstanceMetadata> {
105+
if (!base64String) {
106+
return null;
107+
}
108+
109+
try {
110+
if (!(await this.decoderInitPromise)) {
111+
throw new Error('WASM Decoder is not initialized. Cannot decompress data.');
112+
}
113+
114+
const compressedBytes = Uint8Array.from(atob(base64String), (c) => c.charCodeAt(0));
115+
const decompressedBytes = this.decoder.decode(compressedBytes);
116+
const jsonString = new TextDecoder().decode(decompressedBytes);
117+
return JSON.parse(jsonString);
118+
} catch (error) {
119+
console.error('Failed to decode, decompress, or parse JSON:', error);
120+
return null;
121+
}
122+
}
70123
}
71124

72125
export default MetadataManager;

src/types/metadata.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ type JsonMetadata = {
88
instances: Record<
99
string,
1010
{
11-
metadata: InstanceMetadata;
11+
metadata: InstanceMetadata | string;
1212
// The metadata will either have url or uri
1313
uri: string;
1414
url: string;
@@ -22,7 +22,7 @@ type JsonMetadata = {
2222
original_path: string;
2323
dependencies: string[];
2424
diff_hash_dupe_paths: [string];
25-
version: string;
25+
version: '1.0' | '2.0';
2626
modified_datetime: string;
2727
}
2828
>;

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5864,3 +5864,8 @@ yocto-queue@^1.0.0:
58645864
version "1.1.1"
58655865
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110"
58665866
integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==
5867+
5868+
zstddec@^0.1.0:
5869+
version "0.1.0"
5870+
resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.1.0.tgz#7050f3f0e0c3978562d0c566b3e5a427d2bad7ec"
5871+
integrity sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg==

0 commit comments

Comments
 (0)