cornerstone-nifti-image-loader
Version:
Cornerstone ImageLoader for NIfTI
394 lines (356 loc) • 18.7 kB
HTML
<html lang="en">
<head>
<meta name="charset" content="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- twitter bootstrap CSS stylesheet - included to make things pretty,
not needed or used by cornerstone -->
<link href="bootstrap.min.css" rel="stylesheet">
<link href="cornerstone.min.css" rel="stylesheet">
<link href="orientation-check.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
<header id="main-header">
<h1>NIFTI loading</h1>
<h2><small class="text-muted">Example of displaying a NIFTI image using Cornerstone</small></h2>
<p class="lead">
Enter a URL for a NIFTI file to view it using cornerstone.
<button id='toggle-more-info' class="btn btn-outline-info" type="button">
More info
</button>
</p>
</header>
<div id="more-info" class="collapse">
<p>
This example illustrates how to use the cornerstone-nifti-image-loader to get a NIFTI
file and display it in your web browser using cornerstone.
</p>
<p>
Use the hash fragment (<code>#</code>) to specify which frame (slice) to display
from an object (defaults to the first frame if not specified). Example:
<dl class="row">
<dt class="col-sm-3 col-md-5 text-right">URL: <code>brain.nii</code></dt>
<dd class="col-sm-9 col-md-7">Loads the first frame (slice) of the data</dd>
<dt class="col-sm-3 col-md-5 text-right">URL: <code>brain.nii#15</code></dt>
<dd class="col-sm-9 col-md-7">Loads the 15th frame (slice) of the data</dd>
</dl>
</p>
<strong>If you get an HTTP error and your URL is correct, it is probably because the server is not configured to
allow <a href="http://en.wikipedia.org/wiki/Cross-origin_resource_sharing">Cross Origin Requests</a>.
Most browsers will allow you to enable cross domain requests via settings or command line switches,
you can start chrome with the command line switch <code>--disable-web-security</code> to allow cross origin requests.
See the <a href="http://enable-cors.org/">Enable CORS site</a> for information about CORS.
</strong>
<br>
<br>
<p>
Looking for a CORS proxy? Try <a href="https://www.npmjs.com/package/corsproxy">CORSProxy</a>
</p>
<strong>Use of this example require IE10+ or any other modern browser.</strong>
</div>
<div id="load-progress">Image Load Progress:</div>
<div class="row">
<div class="col">
<form id="form">
<div class="form-group form-row">
<label class="col-form-label col-sm-1" for="nifti-URL">URL</label>
<div class="col-sm-7">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">nifti:</div>
</div>
<input class="form-control" type="text" id="nifti-URL" placeholder="Enter URL to NIFTI file or pick from list" list="sample-nifti-files">
<datalist id="sample-nifti-files">
<option value="data/SC-N-3.nii">data/SC-N-3.nii</option>
<option value="data/avg152T1_LR_nifti.nii">data/avg152T1_LR_nifti.nii (uncompressed, 903 KB)</option>
<option value="data/avg152T1_RL_nifti.nii.gz">data/avg152T1_RL_nifti.nii.gz (compressed, 531 KB)</option>
<option value="data/avg152T1_LR_nifti2.nii.gz">data/avg152T1_LR_nifti2.nii.gz (compressed, 764 KB)</option>
<option value="data/sample_image.nii.gz">data/sample_image.nii.gz (compressed, 1.5 MB)</option>
<option value="data/fMRI_Ret_bars.nii.gz">data/fMRI_Ret_bars.nii.gz (compressed, 3.0 MB)</option>
<option value="data/dti_FA.nii.gz">data/dti_FA.nii.gz (compressed, 395.6 KB)</option>
<option value="data/dti_V1.nii.gz">data/dti_V1.nii.gz (compressed, 1.2 MB)</option>
<option value="data/mni152_2009_256.nii.gz">data/mni152_2009_256.nii.gz (compressed, 4.5 MB)</option>
<option value="data/2878_1_1_localizera.nii.gz">data/2878_1_1_localizera.nii.gz (YZX pixel order, compressed, 1.6 MB)</option>
</datalist>
</div>
</div>
<div class="col-sm-3">
<button class="btn btn-primary" type="button" id="download-and-view">Download and View</button>
</div>
</div>
</form>
</div>
</div>
<section id="image-and-data-info">
<div class="nifti-image-container"
oncontextmenu="return false"
class='disable-selection noIbar'
unselectable='on'
onselectstart='return false;'
onmousedown='return false;'>
<div id="nifti-image-z"></div>
<button data-dimension="z" class="display-metadata btn btn-sm btn-secondary">Display meta data</button>
</div>
<div class="nifti-image-container"
oncontextmenu="return false"
class='disable-selection noIbar'
unselectable='on'
onselectstart='return false;'
onmousedown='return false;'>
<div id="nifti-image-x"></div>
<button data-dimension="x" class="display-metadata btn btn-sm btn-secondary">Display meta data</button>
</div>
<div class="nifti-image-container"
oncontextmenu="return false"
class='disable-selection noIbar'
unselectable='on'
onselectstart='return false;'
onmousedown='return false;'>
<div id="nifti-image-y"></div>
<button data-dimension="y" class="display-metadata btn btn-sm btn-secondary">Display meta data</button>
</div>
<div id="nifti-image-data">
<div class="metadata">
<span class="badge badge-pill badge-light">Columns:</span>
<span class="badge badge-pill badge-primary" id="columns"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Rows:</span>
<span class="badge badge-pill badge-primary" id="rows"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Number of Frames:</span>
<span class="badge badge-pill badge-primary" id="numberOfFrames"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Pixel Spacing:</span>
<span class="badge badge-pill badge-primary" id="pixelSpacing"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Samples per Pixel:</span>
<span class="badge badge-pill badge-primary" id="samplesPerPixel"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Photometric Interpretation:</span>
<span class="badge badge-pill badge-primary" id="photometricInterpretation"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Planar Configuration:</span>
<span class="badge badge-pill badge-primary" id="planarConfiguration"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Pixel Aspect Ratio:</span>
<span class="badge badge-pill badge-primary" id="pixelAspectRatio"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Bits Allocated:</span>
<span class="badge badge-pill badge-primary" id="bitsAllocated"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Bits Stored:</span>
<span class="badge badge-pill badge-primary" id="bitsStored"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">High Bit:</span>
<span class="badge badge-pill badge-primary" id="highBit"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Pixel Representation:</span>
<span class="badge badge-pill badge-primary" id="pixelRepresentation"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">WindowCenter:</span>
<span class="badge badge-pill badge-primary" id="windowCenter"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">WindowWidth:</span>
<span class="badge badge-pill badge-primary" id="windowWidth"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">RescaleIntercept:</span>
<span class="badge badge-pill badge-primary" id="rescaleIntercept"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">RescaleSlope:</span>
<span class="badge badge-pill badge-primary" id="rescaleSlope"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Min Stored Pixel Value:</span>
<span class="badge badge-pill badge-primary" id="minStoredPixelValue"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Max Stored Pixel Value:</span>
<span class="badge badge-pill badge-primary" id="maxStoredPixelValue"></span>
</div>
<div class="metadata">
<span class="badge badge-pill badge-light">Timepoints:</span>
<span class="badge badge-pill badge-primary" id="timepoints"></span>
</div>
<hr />
<div>
<button class="btn btn-secondary btn-sm" type="button" id="prev-timepoint">Prev. Timepoint</button>
<button class="btn btn-secondary btn-sm" type="button" id="next-timepoint">Next Timepoint</button>
<div class="metadata">
<span class="badge badge-pill badge-light">Current Timepoint:</span>
<span class="badge badge-pill badge-primary" id="currentTimepoint"></span>
</div>
</div>
</div>
</section>
</div>
</body>
<!-- include the cornerstone library -->
<script src="cornerstone.js"></script>
<script src="cornerstoneMath.js"></script>
<script src="cornerstoneTools.js"></script>
<!-- include the cornerstoneNIFTIImageLoader library -->
<script src="../../dist/cornerstoneNIFTIImageLoader.js"></script>
<script>
cornerstoneNIFTIImageLoader.external.cornerstone = cornerstone;
const ImageId = cornerstoneNIFTIImageLoader.nifti.ImageId;
cornerstoneNIFTIImageLoader.nifti.streamingMode = true;
let loaded = false;
var synchronizer = new cornerstoneTools.Synchronizer("cornerstonenewimage", cornerstoneTools.updateImageSynchronizer);
function loadAndViewImage(element, imageId) {
const imageIdObject = ImageId.fromURL(imageId);
element.dataset.imageId = imageIdObject.url;
//cornerstoneNIFTIImageLoader.nifti.loadVolumeTimepoint(imageIdObject).then((data) => {
// console.info(data);
//});
//return;
try {
cornerstone.loadAndCacheImage(imageIdObject.url).then(function(image) {
const numberOfSlices = cornerstone.metaData.get('multiFrameModule', imageIdObject.url).numberOfFrames;
const stack = {
currentImageIdIndex: imageIdObject.slice.index,
imageIds: Array.from(Array(numberOfSlices), (_, i) => `nifti:${imageIdObject.filePath}#${imageIdObject.slice.dimension}-${i},t-0`)
};
const viewport = cornerstone.getDefaultViewportForImage(element, image);
cornerstone.displayImage(element, image, viewport);
cornerstone.resize(element, true);
cornerstoneTools.addStackStateManager(element, ['stack']);
cornerstoneTools.addToolState(element, 'stack', stack);
cornerstoneTools.mouseInput.enable(element);
cornerstoneTools.mouseWheelInput.enable(element);
cornerstoneTools.pan.activate(element, 2);
cornerstoneTools.zoom.activate(element, 4);
cornerstoneTools.stackScrollWheel.activate(element);
cornerstoneTools.orientationMarkers.enable(element);
synchronizer.add(element);
cornerstoneTools.stackPrefetch.enable(element);
cornerstoneTools.referenceLines.tool.enable(element, synchronizer);
cornerstoneTools.crosshairs.enable(element, 1, synchronizer);
}, function(err) {
throw err;
alert(err);
});
} catch(err) {
throw err;
alert(err);
}
}
const elementZ = document.getElementById('nifti-image-z');
const elementX = document.getElementById('nifti-image-x');
const elementY = document.getElementById('nifti-image-y');
let timepoint = 0;
function downloadAndView(elements) {
let url = document.getElementById('nifti-URL').value;
let imageIdObject = ImageId.fromURL(`nifti:${url}`);
document.getElementById('currentTimepoint').textContent = timepoint;
loadAndViewImage(elementZ, `nifti:${imageIdObject.filePath}#z,t-${timepoint}`);
loadAndViewImage(elementX, `nifti:${imageIdObject.filePath}#x,t-${timepoint}`);
loadAndViewImage(elementY, `nifti:${imageIdObject.filePath}#y,t-${timepoint}`);
}
cornerstone.events.addEventListener('cornerstoneimageloadprogress', function(event) {
const eventData = event.detail;
const loadProgress = document.getElementById('load-progress');
loadProgress.textContent = `Image Load Progress: ${eventData.percentComplete}%`;
});
cornerstone.enable(elementZ);
cornerstone.enable(elementX);
cornerstone.enable(elementY);
document.getElementById('download-and-view').addEventListener('click', function(e) {
let url = document.getElementById('nifti-URL').value;
downloadAndView([elementZ, elementX, elementY]);
});
document.getElementById('next-timepoint').addEventListener('click', function (e) {
timepoint++;
downloadAndView([elementZ, elementX, elementY]);
});
document.getElementById('prev-timepoint').addEventListener('click', function (e) {
timepoint--;
downloadAndView([elementZ, elementX, elementY]);
});
const form = document.getElementById('form');
form.addEventListener('submit', function(e) {
downloadAndView();
e.preventDefault();
});
document.getElementById('toggle-more-info').addEventListener('click', function() {
const moreInfoEl = document.querySelector('#more-info');
let justAppeared = !moreInfoEl.classList.toggle('collapse');
if (justAppeared) {
moreInfoEl.classList.add('highlighted');
setTimeout(() => moreInfoEl.classList.remove('highlighted'), 100);
}
});
document.addEventListener('DOMContentLoaded', () => {
// check if there is a query parameter pointing to a file to be preloaded
const fileToPreload = (getParameterByName('file') || '').trim();
if (fileToPreload !== '') {
document.getElementById('nifti-URL').value = fileToPreload;
downloadAndView();
}
});
document.querySelectorAll('.display-metadata').forEach(button => {
button.addEventListener('click', (e) => {
const imageId = e.currentTarget.previousElementSibling.dataset.imageId;
if (typeof imageId !== 'undefined') {
displayMetaData(imageId);
}
});
});
function displayMetaData (imageId) {
const metaData = (type) => cornerstone.metaData.get(type, imageId);
document.getElementById('columns').textContent = metaData('imagePixelModule').columns;
document.getElementById('rows').textContent = metaData('imagePixelModule').rows;
document.getElementById('numberOfFrames').textContent = metaData('multiFrameModule').numberOfFrames;
document.getElementById('pixelSpacing').textContent = metaData('imagePlaneModule').columnPixelSpacing.toFixed(2) + ' / ' + metaData('imagePlaneModule').rowPixelSpacing.toFixed(2);
document.getElementById('samplesPerPixel').textContent = metaData('imagePixelModule').samplesPerPixel;
document.getElementById('photometricInterpretation').textContent = metaData('imagePixelModule').photometricInterpretation;
document.getElementById('planarConfiguration').textContent = getPlanarConfiguration(metaData('imagePixelModule').planarConfiguration);
document.getElementById('pixelAspectRatio').textContent = metaData('imagePixelModule').pixelAspectRatio;
document.getElementById('bitsAllocated').textContent = metaData('imagePixelModule').bitsAllocated;
document.getElementById('bitsStored').textContent = metaData('imagePixelModule').bitsStored;
document.getElementById('highBit').textContent = metaData('imagePixelModule').highBit;
document.getElementById('pixelRepresentation').textContent = getPixelRepresentation(metaData('imagePixelModule').pixelRepresentation);
document.getElementById('windowCenter').textContent = metaData('voiLutModule').windowCenter;
document.getElementById('windowWidth').textContent = metaData('voiLutModule').windowWidth;
document.getElementById('rescaleIntercept').textContent = metaData('modalityLutModule').rescaleIntercept;
document.getElementById('rescaleSlope').textContent = metaData('modalityLutModule').rescaleSlope;
document.getElementById('minStoredPixelValue').textContent = metaData('imagePixelModule').smallestPixelValue;
document.getElementById('maxStoredPixelValue').textContent = metaData('imagePixelModule').largestPixelValue;
document.getElementById('timepoints').textContent = metaData('functional').timeSlices;
function getPixelRepresentation(value) {
return value === '0000H' ? '(unsigned)' : '(signed)';
}
function getPlanarConfiguration(value) {
return (typeof value === 'undefined' ? 'unspecified' : (value === 0 ? '(pixel)' : '(plane)'));
}
}
function getParameterByName (name) {
const url = window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`);
const results = regex.exec(url);
if (!results) {
return null;
}
if (!results[2]) {
return '';
}
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
</script>
</html>