cod-dicomweb-server
Version:
A wadors server proxy that get data from a Cloud Optimized Dicom format.
105 lines (104 loc) • 4.21 kB
JavaScript
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';
class MetadataManager {
metadataPromises = {};
decoder;
decoderInitPromise;
constructor() {
this.decoder = null;
const decoder = new ZSTDDecoder();
this.decoderInitPromise = decoder
.init()
.then(() => {
this.decoder = decoder;
return true;
})
.catch((error) => {
console.error('Failed to initialize ZSTD WASM module:', error);
return false;
});
}
async addDeidMetadata(jsonMetadata, url) {
const { cod } = jsonMetadata;
const [studyUID, _, seriesUID] = url.match(/studies\/(.*?)\/metadata/)?.[1].split('/') || [];
if (!cod || !studyUID || !seriesUID) {
console.warn('Missing required metadata properties: cod, studyUID, or seriesUID');
return;
}
for (const sopUID in cod.instances) {
const instance = cod.instances[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;
instanceMetadata.DeidStudyInstanceUID = { Value: [studyUID] };
instanceMetadata.DeidSeriesInstanceUID = { Value: [seriesUID] };
instanceMetadata.DeidSopInstanceUID = { Value: [sopUID] };
}
}
getMetadataFromCache(url) {
return this.metadataPromises[url];
}
async getMetadata(params, headers) {
const url = createMetadataJsonUrl(params);
if (!url) {
throw new CustomError('Error creating metadata json url');
}
const cachedMetadata = this.getMetadataFromCache(url);
if (cachedMetadata) {
return await cachedMetadata;
}
const directoryHandle = await getDirectoryHandle();
const fileName = createMetadataFileName(url);
const locallyCachedMetadata = (await readFile(directoryHandle, fileName, { isJson: true }));
if (locallyCachedMetadata) {
return locallyCachedMetadata;
}
try {
this.metadataPromises[url] = fetch(url, { headers })
.then((response) => {
if (!response.ok) {
throw new CustomError(`Failed to fetch metadata: ${response.statusText}`);
}
return response.json();
})
.then(async (data) => {
await this.addDeidMetadata(data, url);
await writeFile(directoryHandle, fileName, data, true);
return data;
});
return await this.metadataPromises[url];
}
catch (error) {
console.error(error);
throw error;
}
}
async decodeDecompressAndParse(base64String) {
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;