245 lines
5.7 KiB
JavaScript
245 lines
5.7 KiB
JavaScript
import * as cornerstone from '@cornerstonejs/core'
|
|
const canvas = document.createElement('canvas')
|
|
let lastImageIdDrawn
|
|
|
|
// Todo: this loader should exist in a separate package in the same monorepo
|
|
|
|
/**
|
|
* creates a cornerstone Image object for the specified Image and imageId
|
|
*
|
|
* @param image - An Image
|
|
* @param imageId - the imageId for this image
|
|
* @returns Cornerstone Image Object
|
|
*/
|
|
function createImage(image, imageId) {
|
|
// extract the attributes we need
|
|
const rows = image.naturalHeight
|
|
const columns = image.naturalWidth
|
|
|
|
function getPixelData(targetBuffer) {
|
|
const imageData = getImageData()
|
|
|
|
let targetArray
|
|
|
|
// Check if targetBuffer is provided for volume viewports
|
|
if (targetBuffer) {
|
|
targetArray = new Uint8Array(
|
|
targetBuffer.arrayBuffer,
|
|
targetBuffer.offset,
|
|
targetBuffer.length
|
|
)
|
|
} else {
|
|
targetArray = new Uint8Array(imageData.width * imageData.height * 3)
|
|
}
|
|
|
|
// modify original image data and remove alpha channel (RGBA to RGB)
|
|
convertImageDataToRGB(imageData, targetArray)
|
|
|
|
return targetArray
|
|
}
|
|
|
|
function convertImageDataToRGB(imageData, targetArray) {
|
|
for (let i = 0, j = 0; i < imageData.data.length; i += 4, j += 3) {
|
|
targetArray[j] = imageData.data[i]
|
|
targetArray[j + 1] = imageData.data[i + 1]
|
|
targetArray[j + 2] = imageData.data[i + 2]
|
|
}
|
|
}
|
|
|
|
function getImageData() {
|
|
let context
|
|
|
|
if (lastImageIdDrawn === imageId) {
|
|
context = canvas.getContext('2d')
|
|
} else {
|
|
canvas.height = image.naturalHeight
|
|
canvas.width = image.naturalWidth
|
|
context = canvas.getContext('2d')
|
|
context.drawImage(image, 0, 0)
|
|
lastImageIdDrawn = imageId
|
|
}
|
|
|
|
return context.getImageData(0, 0, image.naturalWidth, image.naturalHeight)
|
|
}
|
|
|
|
function getCanvas() {
|
|
if (lastImageIdDrawn === imageId) {
|
|
return canvas
|
|
}
|
|
|
|
canvas.height = image.naturalHeight
|
|
canvas.width = image.naturalWidth
|
|
const context = canvas.getContext('2d')
|
|
|
|
context.drawImage(image, 0, 0)
|
|
lastImageIdDrawn = imageId
|
|
|
|
return canvas
|
|
}
|
|
|
|
// Extract the various attributes we need
|
|
return {
|
|
imageId,
|
|
minPixelValue: 0,
|
|
maxPixelValue: 255,
|
|
slope: 1,
|
|
intercept: 0,
|
|
windowCenter: 128,
|
|
windowWidth: 255,
|
|
getPixelData,
|
|
getCanvas,
|
|
getImage: () => image,
|
|
rows,
|
|
columns,
|
|
height: rows,
|
|
width: columns,
|
|
color: true,
|
|
// we converted the canvas rgba already to rgb above
|
|
rgba: false,
|
|
columnPixelSpacing: 1, // for web it's always 1
|
|
rowPixelSpacing: 1, // for web it's always 1
|
|
invert: false,
|
|
sizeInBytes: rows * columns * 3,
|
|
numberOfComponents: 3
|
|
}
|
|
}
|
|
|
|
function arrayBufferToImage(arrayBuffer) {
|
|
return new Promise((resolve, reject) => {
|
|
const image = new Image()
|
|
const arrayBufferView = new Uint8Array(arrayBuffer)
|
|
const blob = new Blob([arrayBufferView])
|
|
const urlCreator = window.URL || window.webkitURL
|
|
const imageUrl = urlCreator.createObjectURL(blob)
|
|
|
|
image.src = imageUrl
|
|
image.onload = () => {
|
|
resolve(image)
|
|
urlCreator.revokeObjectURL(imageUrl)
|
|
}
|
|
|
|
image.onerror = (error) => {
|
|
urlCreator.revokeObjectURL(imageUrl)
|
|
reject(error)
|
|
}
|
|
})
|
|
}
|
|
|
|
//
|
|
// This is a cornerstone image loader for web images such as PNG and JPEG
|
|
//
|
|
const options = {
|
|
// callback allowing customization of the xhr (e.g. adding custom auth headers, cors, etc)
|
|
beforeSend: (xhr) => {
|
|
// xhr
|
|
}
|
|
}
|
|
|
|
// Loads an image given a url to an image
|
|
function loadImage(uri, imageId) {
|
|
const xhr = new XMLHttpRequest()
|
|
|
|
xhr.open('GET', uri, true)
|
|
xhr.responseType = 'arraybuffer'
|
|
options.beforeSend(xhr)
|
|
|
|
xhr.onprogress = function(oProgress) {
|
|
if (oProgress.lengthComputable) {
|
|
// evt.loaded the bytes browser receive
|
|
// evt.total the total bytes set by the header
|
|
const loaded = oProgress.loaded
|
|
const total = oProgress.total
|
|
const percentComplete = Math.round((loaded / total) * 100)
|
|
|
|
const eventDetail = {
|
|
imageId,
|
|
loaded,
|
|
total,
|
|
percentComplete
|
|
}
|
|
|
|
cornerstone.triggerEvent(
|
|
cornerstone.eventTarget,
|
|
'cornerstoneimageloadprogress',
|
|
eventDetail
|
|
)
|
|
}
|
|
}
|
|
|
|
const promise = new Promise((resolve, reject) => {
|
|
xhr.onload = function() {
|
|
const imagePromise = arrayBufferToImage(this.response)
|
|
|
|
imagePromise
|
|
.then((image) => {
|
|
const imageObject = createImage(image, imageId)
|
|
|
|
resolve(imageObject)
|
|
}, reject)
|
|
.catch((error) => {
|
|
console.error(error)
|
|
})
|
|
}
|
|
xhr.onerror = function(error) {
|
|
reject(error)
|
|
}
|
|
|
|
xhr.send()
|
|
})
|
|
|
|
const cancelFn = () => {
|
|
xhr.abort()
|
|
}
|
|
|
|
return {
|
|
promise,
|
|
cancelFn
|
|
}
|
|
}
|
|
|
|
function registerWebImageLoader(imageLoader) {
|
|
imageLoader.registerImageLoader('web', _loadImageIntoBuffer)
|
|
}
|
|
|
|
/**
|
|
* Small stripped down loader from cornerstoneDICOMImageLoader
|
|
* Which doesn't create cornerstone images that we don't need
|
|
*/
|
|
function _loadImageIntoBuffer(imageId, options) {
|
|
const uri = imageId.replace('web:', '')
|
|
|
|
const promise = new Promise((resolve, reject) => {
|
|
// get the pixel data from the server
|
|
loadImage(uri, imageId)
|
|
.promise.then(
|
|
(image) => {
|
|
if (
|
|
!options?.targetBuffer?.length ||
|
|
!options?.targetBuffer?.offset
|
|
) {
|
|
resolve(image)
|
|
return
|
|
}
|
|
|
|
// @ts-ignore
|
|
image.getPixelData(options.targetBuffer)
|
|
|
|
resolve(true)
|
|
},
|
|
(error) => {
|
|
reject(error)
|
|
}
|
|
)
|
|
.catch((error) => {
|
|
reject(error)
|
|
})
|
|
})
|
|
|
|
return {
|
|
promise,
|
|
cancelFn: undefined
|
|
}
|
|
}
|
|
|
|
export default registerWebImageLoader
|