Skip to content

Commit

Permalink
Merge pull request #872 from rordenlab/wasm/refactor-for-drop
Browse files Browse the repository at this point in the history
refactor file handling in WASM wrapper
  • Loading branch information
neurolabusc authored Oct 3, 2024
2 parents d633868 + 2d98d4f commit 15b5356
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 23 deletions.
84 changes: 77 additions & 7 deletions js/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,91 @@
<h1>dcm2niix WASM Demo</h1>
<input type="file" id="fileInput" webkitdirectory multiple>
<button id="processButton">Process Image</button>
<div id="dropTarget" style="margin: 2rem; color: white; width: 100px; height: 100px; background-color: red;">
Drop files here
</div>
<p id="status">Please select a dicom folder to process.</p>
<a id="downloadLink" style="display: none;">Download Processed Image(s)</a>

<script type="module">
import { Dcm2niix } from './dist/index.js';
const dcm2niix = new Dcm2niix();

const fileInput = document.getElementById('fileInput');
const processButton = document.getElementById('processButton');
const status = document.getElementById('status');
const downloadLink = document.getElementById('downloadLink');

const dropTarget = document.getElementById('dropTarget');
dropTarget.addEventListener('dragover', (event) => {
event.preventDefault();
dropTarget.style.backgroundColor = 'green';
});

dropTarget.addEventListener('dragleave', (event) => {
event.preventDefault();
dropTarget.style.backgroundColor = 'red';
});

dropTarget.ondrop = handleDrop;

async function handleDrop(e) {
e.preventDefault(); // prevent navigation to open file
const items = e.dataTransfer.items;
const files = [];
for (let i = 0; i < items.length; i++) {
const item = items[i].webkitGetAsEntry();
if (item) {
await traverseFileTree(item, '', files);
}
}
const dcm2niix = new Dcm2niix();
await dcm2niix.init()
const resultFileList = await dcm2niix.inputFromDropItems(files).run()

resultFileList.forEach((resultFile, index) => {
let url = URL.createObjectURL(resultFile);
const downloadLink = document.createElement('a');
downloadLink.href = url;
downloadLink.download = resultFile.name;
downloadLink.textContent = `Download ${resultFile.name}`;
downloadLink.style.display = 'block';
document.body.appendChild(downloadLink);
});
status.textContent = 'Processing complete!';
}

async function traverseFileTree(item, path = '', fileArray) {
return new Promise((resolve) => {
if (item.isFile) {
item.file(file => {
file.fullPath = path + file.name;
// IMPORTANT: _webkitRelativePath is required for dcm2niix to work.
// We need to add this property so we can parse multiple directories correctly.
// the "webkitRelativePath" property on File objects is read-only, so we can't set it directly, hence the underscore.
file._webkitRelativePath = path + file.name;
fileArray.push(file);
resolve();
});
} else if (item.isDirectory) {
const dirReader = item.createReader();
const readAllEntries = () => {
dirReader.readEntries(entries => {
if (entries.length > 0) {
const promises = [];
for (const entry of entries) {
promises.push(traverseFileTree(entry, path + item.name + '/', fileArray));
}
Promise.all(promises).then(readAllEntries);
} else {
resolve();
}
});
};
readAllEntries();
}
});
}

let selectedFiles = null;

fileInput.addEventListener('change', async (event) => {
Expand All @@ -30,26 +104,22 @@ <h1>dcm2niix WASM Demo</h1>
});

processButton.addEventListener('click', async () => {
// if (!selectedFile) return;

status.textContent = 'Processing...';
// processButton.disabled = true;

try {
console.log('Initializing dcm2niix wasm...');
const dcm2niix = new Dcm2niix();
await dcm2niix.init();
console.log('dcm2niix wasm initialized.');

const t0 = performance.now();

const inputFileList = selectedFiles
const resultFileList = await dcm2niix.input(inputFileList).run()
const resultFileList = await dcm2niix.input(inputFileList).run()
console.log(resultFileList);

const t1 = performance.now();
console.log("dcm2niix wasm took " + (t1 - t0) + " milliseconds.")

// Create a download link for each file in resultFileList
resultFileList.forEach((resultFile, index) => {
let url = URL.createObjectURL(resultFile);
const downloadLink = document.createElement('a');
Expand Down
2 changes: 1 addition & 1 deletion js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@niivue/dcm2niix",
"version": "0.1.1",
"version": "0.1.1-dev.2",
"main": "dist/index.js",
"module": "dist/index.js",
"exports": {
Expand Down
46 changes: 31 additions & 15 deletions js/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,40 @@ export class Dcm2niix {
});
}

input(fileList) {
return new Processor({ worker: this.worker, fileList });
conformFileList(fileObjectOrArray) {
// prepare files with their relative paths.
// annoyingly, safari strips the webkitRelativePath property when sending files to the worker.
// fileList is a FileList object, not an array, so we need to convert it to an array.
// filesWithRelativePaths is an array of objects with the file and webkitRelativePath properties.
// Now we can use the webkitRelativePath property in the worker.
// This is important for dcm2niix to work with nested dicom directories in safari.
const filesWithRelativePaths = Array.from(fileObjectOrArray).map((file) => ({
file,
// need to check for both webkitRelativePath and _webkitRelativePath.
// _webkitRelativePath is used in case the file was not from a webkitdirectory file input element (e.g. from a drop event).
// IMPORTANT: it is up to other developers to ensure that the special _webkitRelativePath property is set correctly when using drop events.
webkitRelativePath: file.webkitRelativePath || file._webkitRelativePath || ''
}));
return filesWithRelativePaths
}

input(fileListObject) {
const conformedFileList = this.conformFileList(fileListObject);
return new Processor({ worker: this.worker, fileList: conformedFileList });
}

inputFromWebkitDirectory(fileListObject) {
const conformedFileList = this.conformFileList(fileListObject);
return new Processor({ worker: this.worker, fileList: conformedFileList });
}

inputFromDropItems(dataTransferItemArray) {
const conformedFileList = this.conformFileList(dataTransferItemArray);
return new Processor({ worker: this.worker, fileList: conformedFileList });
}
}

class Processor {

constructor({ worker, fileList }) {
this.worker = worker;
this.fileList = fileList;
Expand Down Expand Up @@ -281,19 +308,8 @@ class Processor {
if (this.worker === null) {
reject(new Error('Worker not initialized. Did you await the init() method?'));
}
// prepare files with their relative paths.
// annoyingly, safari strips the webkitRelativePath property when sending files to the worker.
// fileList is a FileList object, not an array, so we need to convert it to an array.
// filesWithRelativePaths is an array of objects with the file and webkitRelativePath properties.
// Now we can use the webkitRelativePath property in the worker.
// This is important for dcm2niix to work with nested dicom directories in safari.
const filesWithRelativePaths = Array.from(this.fileList).map((file) => ({
file,
webkitRelativePath: file.webkitRelativePath || ''
}));

// send files and commands to the worker
this.worker.postMessage({ fileList: filesWithRelativePaths, cmd: args });
this.worker.postMessage({ fileList: this.fileList, cmd: args });
});
}
}

0 comments on commit 15b5356

Please sign in to comment.