Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor file handling in WASM wrapper #872

Merged
merged 1 commit into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 });
});
}
}
Loading