Skip to content

Commit

Permalink
Merge pull request #856 from rordenlab/feature/add-wasm-npm-module
Browse files Browse the repository at this point in the history
add wasm build for npm package
  • Loading branch information
neurolabusc authored Aug 26, 2024
2 parents 64e6b43 + db97160 commit b95524c
Show file tree
Hide file tree
Showing 11 changed files with 1,199 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ __pycache__/
/MANIFEST*
/*.egg*/
/dist/

dist
node_modules
dcm2niix.js
dcm2niix.wasm
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# dcm2niix

[![Build status](https://ci.appveyor.com/api/projects/status/7o0xp2fgbhadkgn1?svg=true)](https://ci.appveyor.com/project/neurolabusc/dcm2niix)

## About
Expand All @@ -7,6 +9,10 @@ dcm2niix is designed to convert neuroimaging data from the DICOM format to the N
The DICOM format is the standard image format generated by modern medical imaging devices. However, DICOM is very [complicated](https://github.com/jonclayden/divest) and has been interpreted differently by different vendors. The NIfTI format is popular with scientists, it is very simple and explicit. However, this simplicity also imposes limitations (e.g. it demands equidistant slices). dcm2niix is also able to generate a [BIDS JSON format](https://bids-specification.readthedocs.io/en/stable/) `sidecar` which includes relevant information for brain scientists in a vendor agnostic and human readable form.
The [Neuroimaging DICOM and NIfTI Primer](https://github.com/DataCurationNetwork/data-primers/blob/master/Neuroimaging%20DICOM%20and%20NIfTI%20Data%20Curation%20Primer/neuroimaging-dicom-and-nifti-data-curation-primer.md) provides details.

## JavaScript/WebAssembly

To view the WASM specific README, please click [here](./js/README.md). The rest of this README is for the `dcm2niix` CLI program.

## License

This software is open source. The bulk of the code is covered by the BSD license. Some units are either public domain (nifti*.*, miniz.c) or use the MIT license (ujpeg.cpp). See the license.txt file for more details.
Expand Down
4 changes: 2 additions & 2 deletions console/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ noroi:
g++ $(CFLAGS) -I. $(JSFLAGS) $(JFLAGS) $(LFLAGS) $(UFILES) -DmyNoRois

wasm:
wasm:
emcc -O3 $(UFILES) -s DEMANGLE_SUPPORT=1 -s EXPORTED_RUNTIME_METHODS='["callMain", "ccall", "cwrap", "FS_createDataFile", "FS_readFile", "FS_unlink", "allocateUTF8", "getValue", "stringToUTF8", "setValue"]' -s ALLOW_MEMORY_GROWTH=1 -s WASM=1 -s EXPORT_ES6=1 -s MODULARIZE=1 -s EXPORTED_FUNCTIONS='["_main", "_malloc", "_free"]' -s INVOKE_RUN=0 -o ../js/src/niimath.js
emcc -O3 $(UFILES) -s DEMANGLE_SUPPORT=1 -s EXPORTED_RUNTIME_METHODS='["callMain", "ccall", "cwrap", "FS", "FS_createDataFile", "FS_readFile", "FS_unlink", "allocateUTF8", "getValue", "stringToUTF8", "setValue"]' -s STACK_OVERFLOW_CHECK=2 -s STACK_SIZE=16MB -s ALLOW_MEMORY_GROWTH=1 -s WASM=1 -s EXPORT_ES6=1 -s MODULARIZE=1 -s EXPORTED_FUNCTIONS='["_main", "_malloc", "_free"]' -s FORCE_FILESYSTEM=1 -s INVOKE_RUN=0 -o ../js/src/dcm2niix.js
# STACK_SIZE=16MB is the minimum value found to work with the current codebase when targeting WASM

100 changes: 100 additions & 0 deletions js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# @niivue/dcm2niix

`@niivue/dcm2niix` is a JavaScript + WASM library for converting DICOM files to nifti. This library is intended to be **used in the browser**, not in a Node.js environment.

> All operations are performed using the WASM build of [dcm2niix](https://github.com/rordenlab/dcm2niix). The processing takes place in a separate worker thread, so it won't block the main thread in your application.
## Usage

The `@niivue/dcm2niix` JavaScript library offers an object oriented API for working with the `dcm2niix` CLI. Since `dcm2niix` is a CLI tool, the API implemented in `@niivue/dcm2niix` is just a wrapper around the CLI options and arguments.

## Example

```javascript
// assuming you have an html input element to get directories.
// <input type="file" id="fileInput" webkitdirectory multiple>
import { Dcm2niix } from '@niivue/dcm2niix';

const dcm2niix = new Dcm2niix();
// call the init() method to load the wasm before processing any data
await dcm2niix.init();
// fileInput is the id of the input element with options: webkitdirectory and multiple
fileInput.addEventListener('change', async (event) => {
inputFileList = event.target.files;
});
// inputFileList is the value from the input element with options: webkitdirectory and multiple
const resultFileList = await dcm2niix.input(inputFileList).run()
console.log(resultFileList);
// Do something with the resultFileList (normal browser File Objects)
// perhaps view them with @niivue/niivue :)
```

## Installation

To install `@niivue/dcm2niix` in your project, run the following command:

```bash
npm install @niivue/dcm2niix
```

### To install a local build of the library

Fist, `cd` into the `js` directory of the `dcm2niix` repository.

```bash
# from dcm2niix root directory
cd js
```

To install a local build of the library, run the following command:

```bash
npm run build
```

Then, install the library using the following command:

```bash
npm pack # will create a .tgz file in the root directory
```

Then, install the `@niivue/dcm2niix` library in your application locally using the following command:

```bash
npm install /path/to/niivue-dcm2niix.tgz
```

## Development

First `cd` into the `js` directory of the `dcm2niix` repository.

```bash
# from dcm2niix root directory
cd js
```

To install the dependencies, run the following command:

```bash
npm install
```

To build the library, run the following command

```bash
npm run build
```

To run the tests, run the following command:

```bash
npm run test
```

### Test using a simple demo

To test that the `@niivue/dcm2niix` library is working correctly, you can run the following command:

```bash
npm run demo
```
24 changes: 24 additions & 0 deletions js/esbuild.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const esbuild = require('esbuild');
const fs = require('fs');

esbuild.build({
entryPoints: ['./src/index.js'],
outfile: './dist/index.js',
bundle: true,
format: 'esm',
target: ['es2020'],
minify: false,
define: {
'process.env.NODE_ENV': '"production"',
},
}).then(() => {
// copy worker.js, dcm2niix.wasm, dcm2niix.js to dist folder
// (they do not require any processing by esbuild).
// Technically, none of the files in the src folder require processing by esbuild,
// but it does allow minification (optional), and ES version target specification if needed.
// In the future, if we use Typescript, we can use esbuild to transpile the Typescript to JS.
fs.copyFileSync('./src/worker.js', './dist/worker.js');
fs.copyFileSync('./src/dcm2niix.wasm', './dist/dcm2niix.wasm');
fs.copyFileSync('./src/dcm2niix.js', './dist/dcm2niix.js');
console.log('Build completed!');
}).catch(() => process.exit(1));
75 changes: 75 additions & 0 deletions js/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>dcm2niix WASM Demo</title>
</head>

<body>
<h1>dcm2niix WASM Demo</h1>
<input type="file" id="fileInput" webkitdirectory multiple>
<button id="processButton">Process Image</button>
<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');

let selectedFiles = null;

fileInput.addEventListener('change', async (event) => {
selectedFiles = event.target.files;
console.log(selectedFiles);
});

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

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

try {
console.log('Initializing dcm2niix wasm...');
await dcm2niix.init();
console.log('dcm2niix wasm initialized.');

const t0 = performance.now();

const inputFileList = selectedFiles
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');
downloadLink.href = url;
downloadLink.download = resultFile.name;
downloadLink.textContent = `Download ${resultFile.name}`;
downloadLink.style.display = 'block';
document.body.appendChild(downloadLink);
});


status.textContent = 'Processing complete!';
} catch (error) {
console.error('Processing failed:', error);
status.textContent = 'Processing failed. Please check the console for details.';
} finally {
processButton.disabled = false;
}
});
</script>
</body>

</html>
Loading

0 comments on commit b95524c

Please sign in to comment.