dicom-curate 0.20.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -37199,6 +37199,45 @@ async function shouldProcessFileItem(s3Item, fileAnomalies) {
|
|
|
37199
37199
|
return true;
|
|
37200
37200
|
}
|
|
37201
37201
|
}
|
|
37202
|
+
async function shouldProcessFileNode(filePath, fileName, fileSize, fileAnomalies) {
|
|
37203
|
+
const allExcludedFiletypes = [
|
|
37204
|
+
...DEFAULT_EXCLUDED_FILETYPES,
|
|
37205
|
+
...excludedFiletypes
|
|
37206
|
+
];
|
|
37207
|
+
try {
|
|
37208
|
+
if (allExcludedFiletypes.some(
|
|
37209
|
+
(excluded) => fileName.toLowerCase() === excluded.toLowerCase()
|
|
37210
|
+
)) {
|
|
37211
|
+
fileAnomalies.push(`Skipped excluded file: ${fileName}`);
|
|
37212
|
+
return false;
|
|
37213
|
+
}
|
|
37214
|
+
if (fileSize < 132) {
|
|
37215
|
+
fileAnomalies.push(
|
|
37216
|
+
`Skipped very small file: ${fileName} (${fileSize} bytes)`
|
|
37217
|
+
);
|
|
37218
|
+
return false;
|
|
37219
|
+
}
|
|
37220
|
+
const fs = await import("fs/promises");
|
|
37221
|
+
const fh = await fs.open(filePath, "r");
|
|
37222
|
+
try {
|
|
37223
|
+
const buffer = Buffer.alloc(4);
|
|
37224
|
+
await fh.read(buffer, 0, 4, 128);
|
|
37225
|
+
const dicomSignature = buffer.toString("ascii");
|
|
37226
|
+
if (dicomSignature === "DICM") {
|
|
37227
|
+
return true;
|
|
37228
|
+
}
|
|
37229
|
+
} finally {
|
|
37230
|
+
await fh.close();
|
|
37231
|
+
}
|
|
37232
|
+
fileAnomalies.push(`Skipped file without DICOM signature: ${fileName}`);
|
|
37233
|
+
return false;
|
|
37234
|
+
} catch (error2) {
|
|
37235
|
+
fileAnomalies.push(
|
|
37236
|
+
`Unable to determine file validity - processing anyway: ${fileName} - ${error2}`
|
|
37237
|
+
);
|
|
37238
|
+
return true;
|
|
37239
|
+
}
|
|
37240
|
+
}
|
|
37202
37241
|
fixupNodeWorkerEnvironment().then(() => {
|
|
37203
37242
|
globalThis.addEventListener("message", (event) => {
|
|
37204
37243
|
switch (event.data.request) {
|
|
@@ -37376,12 +37415,13 @@ async function scanDirectoryNode(dirPath) {
|
|
|
37376
37415
|
if (entry.isFile() && keepScanning) {
|
|
37377
37416
|
const filePath = path.join(currentPath, entry.name);
|
|
37378
37417
|
const stats = await fs.stat(filePath);
|
|
37379
|
-
const fileBuffer = await fs.readFile(filePath);
|
|
37380
|
-
const file = new File([new Uint8Array(fileBuffer)], entry.name, {
|
|
37381
|
-
type: "application/dicom"
|
|
37382
|
-
});
|
|
37383
37418
|
const fileAnomalies = [];
|
|
37384
|
-
if (await
|
|
37419
|
+
if (await shouldProcessFileNode(
|
|
37420
|
+
filePath,
|
|
37421
|
+
entry.name,
|
|
37422
|
+
stats.size,
|
|
37423
|
+
fileAnomalies
|
|
37424
|
+
)) {
|
|
37385
37425
|
globalThis.postMessage({
|
|
37386
37426
|
response: "file",
|
|
37387
37427
|
fileIndex: fileIndex++,
|
|
@@ -159,6 +159,50 @@
|
|
|
159
159
|
return true;
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Node-specific file validation that reads only the bytes needed
|
|
164
|
+
* instead of loading the entire file into memory.
|
|
165
|
+
*/
|
|
166
|
+
async function shouldProcessFileNode(filePath, fileName, fileSize, fileAnomalies) {
|
|
167
|
+
const allExcludedFiletypes = [
|
|
168
|
+
...DEFAULT_EXCLUDED_FILETYPES,
|
|
169
|
+
...excludedFiletypes,
|
|
170
|
+
];
|
|
171
|
+
try {
|
|
172
|
+
// Check if the file is in the list of excluded files
|
|
173
|
+
if (allExcludedFiletypes.some((excluded) => fileName.toLowerCase() === excluded.toLowerCase())) {
|
|
174
|
+
fileAnomalies.push(`Skipped excluded file: ${fileName}`);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
// Check filesize - (valid) DICOM files are at least 132 bytes (128-byte preamble + 4-byte signature)
|
|
178
|
+
if (fileSize < 132) {
|
|
179
|
+
fileAnomalies.push(`Skipped very small file: ${fileName} (${fileSize} bytes)`);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
// Check for DICOM signature "DICM" at offset 128 by reading only 4 bytes
|
|
183
|
+
const fs = await import('fs/promises');
|
|
184
|
+
const fh = await fs.open(filePath, 'r');
|
|
185
|
+
try {
|
|
186
|
+
const buffer = Buffer.alloc(4);
|
|
187
|
+
await fh.read(buffer, 0, 4, 128);
|
|
188
|
+
const dicomSignature = buffer.toString('ascii');
|
|
189
|
+
if (dicomSignature === 'DICM') {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
await fh.close();
|
|
195
|
+
}
|
|
196
|
+
// Don't parse file without DICOM signature
|
|
197
|
+
fileAnomalies.push(`Skipped file without DICOM signature: ${fileName}`);
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
fileAnomalies.push(`Unable to determine file validity - processing anyway: ${fileName} - ${error}`);
|
|
202
|
+
// If vetting process fails, let the parser decide
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
162
206
|
fixupNodeWorkerEnvironment().then(() => {
|
|
163
207
|
globalThis.addEventListener('message', (event) => {
|
|
164
208
|
switch (event.data.request) {
|
|
@@ -359,12 +403,8 @@
|
|
|
359
403
|
if (entry.isFile() && keepScanning) {
|
|
360
404
|
const filePath = path.join(currentPath, entry.name);
|
|
361
405
|
const stats = await fs.stat(filePath);
|
|
362
|
-
const fileBuffer = await fs.readFile(filePath);
|
|
363
|
-
const file = new File([new Uint8Array(fileBuffer)], entry.name, {
|
|
364
|
-
type: 'application/dicom',
|
|
365
|
-
});
|
|
366
406
|
const fileAnomalies = [];
|
|
367
|
-
if (await
|
|
407
|
+
if (await shouldProcessFileNode(filePath, entry.name, stats.size, fileAnomalies)) {
|
|
368
408
|
// Send file to processing pipeline
|
|
369
409
|
globalThis.postMessage({
|
|
370
410
|
response: 'file',
|