diff --git a/app/scripts/menu.js b/app/scripts/menu.js index eefef5f5..e21c62b4 100644 --- a/app/scripts/menu.js +++ b/app/scripts/menu.js @@ -255,6 +255,9 @@ export default class Menu { fillColorBarColorsFromRGB(colors) { const HEX_BASE = 16; let r, g, b; + if (colors === null) { + return; + } for (let i = 0; i < this.colorBarColors.length; ++i) { r = colors[4 * i].toString(HEX_BASE).padStart(2, '0'); // eslint-disable-line g = colors[4 * i + 1].toString(HEX_BASE).padStart(2, '0'); // eslint-disable-line @@ -453,7 +456,9 @@ export default class Menu { tf2dValues[i].x = tf2dSlider.x[i + 1]; tf2dValues[i].y = tf2dValues[i].value = tf2dSlider.y[i + 1]; } - this.engine3d.setTransferFuncColors(tf2dSlider.handleColor); + if (tf2dSlider.handleColor !== null) { + this.engine3d.setTransferFuncColors(tf2dSlider.handleColor); + } const colors = this.engine3d.updateTransferFuncTexture(tf2dValues.map(z => z.x), tf2dValues.map(z => z.value)); this.fillColorBarColorsFromRGB(colors); this.transFunc2dSlider.flush(); @@ -956,7 +961,8 @@ export default class Menu { const volSize = { x: this.engine2d.m_volumeHeader.m_pixelWidth, y: this.engine2d.m_volumeHeader.m_pixelHeight, - z: volData.length / this.engine2d.m_volumeHeader.m_pixelWidth / this.engine2d.m_volumeHeader.m_pixelHeight, + z: Math.floor(volData.length / this.engine2d.m_volumeHeader.m_pixelWidth / + this.engine2d.m_volumeHeader.m_pixelHeight), pixdim1: this.engine2d.m_volumeBox.x / this.engine2d.m_volumeHeader.m_pixelWidth, pixdim2: this.engine2d.m_volumeBox.y / this.engine2d.m_volumeHeader.m_pixelHeight, pixdim3: this.engine2d.m_volumeBox.z / this.engine2d.m_volumeHeader.m_pixelDepth, diff --git a/lib/scripts/graphics2d/graphics2d.js b/lib/scripts/graphics2d/graphics2d.js index bd2dc93f..b62b1d92 100644 --- a/lib/scripts/graphics2d/graphics2d.js +++ b/lib/scripts/graphics2d/graphics2d.js @@ -76,6 +76,7 @@ export default class Graphics2d { this.m_width = width; this.m_height = height; this.m_material = null; + this.m_mesh = null; console.log(`Graphics2d create size = ${width} * ${height}`); //! Volume data @@ -138,7 +139,7 @@ export default class Graphics2d { new THREE.Vector2(0.0, 0.0), new THREE.Vector2(1.0, 1.0), ]); - let normal; + const normal = new THREE.Vector3(); THREE.Triangle.getNormal(v0, v1, v2, normal); @@ -263,12 +264,13 @@ export default class Graphics2d { //this.m_gradTool.clear(); //this.m_materialsTex2d.m_uniforms.currentGradient.value = this.m_gradTool.m_gradient; this.m_contrastBrightTool.clear(); - this.m_materialsTex2d.m_uniforms.contrast.value = this.m_contrastBrightTool.m_contrast; - this.m_materialsTex2d.m_uniforms.brightness.value = this.m_contrastBrightTool.m_brightness; - this.m_materialsTex2d.m_uniforms.flag.value = false; - this.m_filterTool.clear(); - this.m_materialsTex2d.m_uniforms.sigma.value = this.m_filterTool.m_sigma; - //this.m_materialsTex2d.m_uniforms.sigmaB.value = this.m_filterTool.m_sigmaB; + if (this.m_materialsTex2d !== null) { + this.m_materialsTex2d.m_uniforms.contrast.value = this.m_contrastBrightTool.m_contrast; + this.m_materialsTex2d.m_uniforms.brightness.value = this.m_contrastBrightTool.m_brightness; + this.m_materialsTex2d.m_uniforms.flag.value = false; + this.m_filterTool.clear(); + this.m_materialsTex2d.m_uniforms.sigma.value = this.m_filterTool.m_sigma; + } } clear2DTools() { @@ -1427,6 +1429,11 @@ export default class Graphics2d { const yDim = this.m_volumeHeader.m_pixelHeight; const zDim = this.m_volumeHeader.m_pixelDepth; + if (volTexture === null) { + console.log('no vol texture data'); + return; + } + // const off = xDim/2 + (yDim/2) * xDim + (zDim/2)*xDim*yDim; // const valLog = this.m_volumeData[off]; // console.log(`createTileMaps with ${valLog}`); @@ -1651,12 +1658,13 @@ export default class Graphics2d { //this.m_gradTool.clear(); //this.m_materialsTex2d.m_uniforms.currentGradient.value = this.m_gradTool.m_gradient; this.m_contrastBrightTool.clear(); - this.m_materialsTex2d.m_uniforms.contrast.value = this.m_contrastBrightTool.m_contrast; - this.m_materialsTex2d.m_uniforms.brightness.value = this.m_contrastBrightTool.m_brightness; - this.m_materialsTex2d.m_uniforms.flag.value = false; - this.m_filterTool.clear(); - this.m_materialsTex2d.m_uniforms.sigma.value = this.m_filterTool.m_sigma; - //this.m_materialsTex2d.m_uniforms.sigmaB.value = this.m_filterTool.m_sigmaB; + if (this.m_materialsTex2d !== null) { + this.m_materialsTex2d.m_uniforms.contrast.value = this.m_contrastBrightTool.m_contrast; + this.m_materialsTex2d.m_uniforms.brightness.value = this.m_contrastBrightTool.m_brightness; + this.m_materialsTex2d.m_uniforms.flag.value = false; + this.m_filterTool.clear(); + this.m_materialsTex2d.m_uniforms.sigma.value = this.m_filterTool.m_sigma; + } } getSliceAxis() { @@ -1694,12 +1702,14 @@ export default class Graphics2d { * Render something on screen */ render() { - this.m_renderer.render(this.m_scene, this.m_camera); - // update text - this.updateText(); - // need not update vertices no more - this.m_geometry.verticesNeedUpdate = false; - this.m_geometry.uvsNeedUpdate = false; + if (this.m_mesh !== null) { + this.m_renderer.render(this.m_scene, this.m_camera); + // update text + this.updateText(); + // need not update vertices no more + this.m_geometry.verticesNeedUpdate = false; + this.m_geometry.uvsNeedUpdate = false; + } // if mesh exists } // render } // class Graphics2d diff --git a/lib/scripts/graphics2d/meshtext2d.js b/lib/scripts/graphics2d/meshtext2d.js index 9539261f..f1209a96 100644 --- a/lib/scripts/graphics2d/meshtext2d.js +++ b/lib/scripts/graphics2d/meshtext2d.js @@ -169,7 +169,7 @@ export default class MeshText2D extends Text2D { new THREE.Vector2(0.0, 1.0), new THREE.Vector2(X_TEXT_COORD_MAX, 1.0 - Y_TEXT_COORD_MAX), ]); - let normal; + const normal = new THREE.Vector3(); THREE.Triangle.getNormal(v0, v1, v2, normal); // eslint-disable-next-line diff --git a/lib/scripts/graphics2d/mprrenderer.js b/lib/scripts/graphics2d/mprrenderer.js index d749cff0..3bb31bdc 100644 --- a/lib/scripts/graphics2d/mprrenderer.js +++ b/lib/scripts/graphics2d/mprrenderer.js @@ -249,7 +249,7 @@ export default class MprRenderer { new THREE.Vector2(0.0, 0.0), new THREE.Vector2(1.0, 1.0), ]; - let normal; + const normal = new THREE.Vector3(); THREE.Triangle.getNormal(v0, v1, v2, normal); diff --git a/lib/scripts/graphics3d/graphics3d.js b/lib/scripts/graphics3d/graphics3d.js index 4ec253e7..1b331ff2 100644 --- a/lib/scripts/graphics3d/graphics3d.js +++ b/lib/scripts/graphics3d/graphics3d.js @@ -631,44 +631,47 @@ export default class Graphics3d { const VAL_3 = 3; const HALF = 0.5; + const OFF_0 = 0; + const OFF_1 = 1; + const OFF_2 = 2; const max = this.geometry.boundingBox.max; const min = this.geometry.boundingBox.min; + if ((nonEmptyBoxMin.x < 0.0) || (nonEmptyBoxMin.x < 0.0) || (nonEmptyBoxMin.z < 0.0)) { + const v = nonEmptyBoxMin; + console.log(`invalid non empty box min = ${v.x}, ${v.y}, ${v.z}`); + } + if ((nonEmptyBoxMax.x > 1.0) || (nonEmptyBoxMax.x > 1.0) || (nonEmptyBoxMax.z > 1.0)) { + const v = nonEmptyBoxMax; + console.log(`invalid non empty box max = ${v.x}, ${v.y}, ${v.z}`); + } const offset = new THREE.Vector3(0 - min.x, 0 - min.y, 0 - min.z); const range = new THREE.Vector3(max.x - min.x, max.y - min.y, max.z - min.z); const uvw = new Float32Array(this.geometry.getAttribute('position').count * VAL_3); this.geo_offset1 = new THREE.Vector3(0, 0, 0); this.geo_offset1 = offset; this.geo_offset2 = new THREE.Vector3(0, 0, 0); - this.geo_offset2.x = nonEmptyBoxMin.x + HALF; + this.geo_offset2.x = nonEmptyBoxMin.x - HALF; this.geo_offset2.y = nonEmptyBoxMin.y - HALF; this.geo_offset2.z = nonEmptyBoxMin.z - HALF; this.geo_scale = new THREE.Vector3(0, 0, 0); this.geo_scale.x = (nonEmptyBoxMax.x - nonEmptyBoxMin.x) / range.x; this.geo_scale.y = (nonEmptyBoxMax.y - nonEmptyBoxMin.y) / range.y; this.geo_scale.z = (nonEmptyBoxMax.z - nonEmptyBoxMin.z) / range.z; - for (let i = 0; i < this.geometry.getAttribute('position').count; i++) { const vx = this.geometry.getAttribute('position').getX(i); const vy = this.geometry.getAttribute('position').getY(i); const vz = this.geometry.getAttribute('position').getZ(i); - // eslint-disable-next-line - uvw[i * VAL_3 + 0] = -(vx + this.geo_offset1.x) * this.geo_scale.x + this.geo_offset2.x; - // eslint-disable-next-line - uvw[i * VAL_3 + 1] = (vy + this.geo_offset1.y) * this.geo_scale.y + this.geo_offset2.y; - // eslint-disable-next-line - uvw[i * VAL_3 + 2] = (vz + this.geo_offset1.z) * this.geo_scale.z + this.geo_offset2.z; + uvw[i * VAL_3 + OFF_0] = -(vx + this.geo_offset1.x) * this.geo_scale.x - this.geo_offset2.x; + uvw[i * VAL_3 + OFF_1] = +(vy + this.geo_offset1.y) * this.geo_scale.y + this.geo_offset2.y; + uvw[i * VAL_3 + OFF_2] = +(vz + this.geo_offset1.z) * this.geo_scale.z + this.geo_offset2.z; /* uvw[i * VAL_3 + 0] = -vx; - // eslint-disable-next-line uvw[i * VAL_3 + 1] = vy; - // eslint-disable-next-line uvw[i * VAL_3 + 2] = vz; uvw[i * VAL_3 + 0] = -(vx + offset.x) * (nonEmptyBoxMax.x - nonEmptyBoxMin.x) / range.x + nonEmptyBoxMin.x + HALF; - // eslint-disable-next-line uvw[i * VAL_3 + 1] = (vy + offset.y) * (nonEmptyBoxMax.y - nonEmptyBoxMin.y) / range.y + nonEmptyBoxMin.y - HALF; - // eslint-disable-next-line uvw[i * VAL_3 + 2] = (vz + offset.z) * (nonEmptyBoxMax.z - nonEmptyBoxMin.z) / range.z + nonEmptyBoxMin.z - HALF; */ } @@ -681,6 +684,9 @@ export default class Graphics3d { const VAL_3 = 3; const HALF = 0.5; + const OFF_0 = 0; + const OFF_1 = 1; + const OFF_2 = 2; const max = this.geometrySphere.boundingBox.max; const min = this.geometrySphere.boundingBox.min; @@ -703,11 +709,11 @@ export default class Graphics3d { const vy = this.geometrySphere.getAttribute('position').getY(i); const vz = this.geometrySphere.getAttribute('position').getZ(i); // eslint-disable-next-line - uvw[i * VAL_3 + 0] = vx;//-(vx + this.geo_offset1.x) * this.geo_scale.x + this.geo_offset2.x; + uvw[i * VAL_3 + OFF_0] = vx;//-(vx + this.geo_offset1.x) * this.geo_scale.x + this.geo_offset2.x; // eslint-disable-next-line - uvw[i * VAL_3 + 1] = vy;//(vy + this.geo_offset1.y) * this.geo_scale.y + this.geo_offset2.y; + uvw[i * VAL_3 + OFF_1] = vy;//(vy + this.geo_offset1.y) * this.geo_scale.y + this.geo_offset2.y; // eslint-disable-next-line - uvw[i * VAL_3 + 2] = vz;//(vz + this.geo_offset1.z) * this.geo_scale.z + this.geo_offset2.z; + uvw[i * VAL_3 + OFF_2] = vz;//(vz + this.geo_offset1.z) * this.geo_scale.z + this.geo_offset2.z; /* uvw[i * VAL_3 + 0] = -vx; // eslint-disable-next-line @@ -726,7 +732,6 @@ export default class Graphics3d { this.geometrySphere.getAttribute('uvw').needsUpdate = true; } - /* createTetraGeometry() { const genTetra = new TetrahedronGenerator(); @@ -776,6 +781,9 @@ export default class Graphics3d { } updateClipPlaneGeometry() { const VAL_3 = 3; + const OFF_0 = 0; + const OFF_1 = 1; + const OFF_2 = 2; const uvw = new Float32Array(this.planeGeometry.getAttribute('position').count * VAL_3); const l2w = new THREE.Matrix4(); l2w.getInverse(this.mesh.matrix); @@ -792,11 +800,9 @@ export default class Graphics3d { v.applyMatrix4(invPerspective); v.applyMatrix4(invView); v.applyMatrix4(l2w); - uvw[i * VAL_3 + 0] = -(v.x + this.geo_offset1.x) * this.geo_scale.x + this.geo_offset2.x; - // eslint-disable-next-line - uvw[i * VAL_3 + 1] = (v.y + this.geo_offset1.y) * this.geo_scale.y + this.geo_offset2.y; - // eslint-disable-next-line - uvw[i * VAL_3 + 2] = (v.z + this.geo_offset1.z) * this.geo_scale.z + this.geo_offset2.z; + uvw[i * VAL_3 + OFF_0] = -(v.x + this.geo_offset1.x) * this.geo_scale.x + this.geo_offset2.x; + uvw[i * VAL_3 + OFF_1] = (v.y + this.geo_offset1.y) * this.geo_scale.y + this.geo_offset2.y; + uvw[i * VAL_3 + OFF_2] = (v.z + this.geo_offset1.z) * this.geo_scale.z + this.geo_offset2.z; /* // eslint-disable-next-line uvw[i * VAL_3 + 0] = -v.x; @@ -898,6 +904,9 @@ export default class Graphics3d { } this.volumeUpdater = new VolumeFilter3D(); this.engine2d.volumeUpdater = this.volumeUpdater; + // !!!!!!!!!!!!!! TEST !!!!!!!!!!! + // Do not create texture + // return; this.volTexture = this.volumeUpdater.createUpdatableVolumeTex(this.engine2d, isRoiVolume, this.roiPalette); if (this.origVolumeTex) { @@ -910,7 +919,6 @@ export default class Graphics3d { this.texVolumeAO.dispose(); } this.texVolumeAO = this.volumeUpdater.gettexVolumeAO(); - if (this.renderer.getContext().getExtension('OES_texture_float')) { if (this.bfTexture) { this.bfTexture.dispose(); @@ -990,7 +998,6 @@ export default class Graphics3d { console.log(`startRot = ${this.curFileDataType.startRotX} ${this.curFileDataType.startRotY}`); this.orbitControl.setMesh(this.mesh); - // Create material for volume render to texture const offsets = []; const nOffs = 64; @@ -1101,7 +1108,9 @@ export default class Graphics3d { * @param ctrlPts Array of control points of type HEX = color value */ setTransferFuncColors(ctrlPtsColorsHex) { - this.volumeUpdater.setTransferFuncColors(ctrlPtsColorsHex); + if ((this.volumeUpdater !== null) && (ctrlPtsColorsHex !== null)) { + this.volumeUpdater.setTransferFuncColors(ctrlPtsColorsHex); + } } /** @@ -1110,7 +1119,10 @@ export default class Graphics3d { * //intensity [0,255] opacity [0,1] */ updateTransferFuncTexture(intensities, opacities) { - return this.volumeUpdater.updateTransferFuncTexture(intensities, opacities); + if (this.volumeUpdater !== null) { + return this.volumeUpdater.updateTransferFuncTexture(intensities, opacities); + } + return null; } /** diff --git a/lib/scripts/graphics3d/volumeFilter3d.js b/lib/scripts/graphics3d/volumeFilter3d.js index b49048ce..54542769 100644 --- a/lib/scripts/graphics3d/volumeFilter3d.js +++ b/lib/scripts/graphics3d/volumeFilter3d.js @@ -28,6 +28,28 @@ import MaterialAO from '../gfx/matAO'; /** Class Graphics3d is used for 3d render */ export default class VolumeFilter3d { + constructor() { + this.sceneBlur = null; + this.material = null; + this.cameraOrtho = null; + this.rendererBlur = null; + this.selectedROIs = null; + this.numTfPixels = 0; + this.transferFuncRgba = null; + this.texRoiColor = null; + this.texRoiId = null; + this.lastSize = []; + this.lastDepth = []; + this.lastRotationVector = []; + this.lastTarget = []; + this.lastBackDistance = []; + this.resetflag = false; + this.sceneAO = null; + this.rendererAO = null; + this.vectors = null; + this.vectorsTex = null; + this.texVolumeAO = null; + } /** * Filtering the source data and building the normals on the GPU @@ -103,6 +125,12 @@ export default class VolumeFilter3d { this.vectorsTex.magFilter = THREE.NearestFilter; this.vectorsTex.minFilter = THREE.NearestFilter; this.vectorsTex.needsUpdate = true; + if (!Number.isInteger(this.xTex)) { + console.log(`!!! Non-integer array size = ${this.xTex}`); + } + if (!Number.isInteger(this.yTex)) { + console.log(`!!! Non-integer array size = ${this.yTex}`); + } this.bufferTextureAO = new Uint8Array(this.xTex * this.yTex); for (let y = 0; y < this.yTex; y++) { @@ -224,6 +252,9 @@ export default class VolumeFilter3d { * //intensity [0,255] opacity [0,1] */ updateTransferFuncTexture(intensities, opacities) { + if (this.transferFuncRgba === null) { + return null; + } for (let curPt = 0; curPt < intensities.length - 1; curPt++) { const pixStart = Math.floor(intensities[curPt]); const pixEnd = Math.floor(intensities[curPt + 1]); @@ -778,6 +809,9 @@ export default class VolumeFilter3d { const TWO = 2; const ONE = 1; const zDimSqrt = TWO ** (ONE + Math.floor(Math.log(Math.sqrt(zDim)) / Math.log(TWO))); + if (!Number.isInteger(zDimSqrt)) { + console.log(`!!! zDimSqrt should be integer, but = ${zDimSqrt}`); + } const xTex = xDim * zDimSqrt; const yTex = yDim * zDimSqrt; const numPixelsBuffer = xTex * yTex; @@ -863,8 +897,9 @@ export default class VolumeFilter3d { const numPixelsBuffer = xTex * yTex; this.bufferMask = new Uint8Array(numPixelsBuffer); for (let y = 0; y < yTex; y++) { + const yOff = y * xTex; for (let x = 0; x < xTex; x++) { - this.bufferMask[x + y * xTex] = 255.0; + this.bufferMask[x + yOff] = 255; } } if (this.updatableTextureMask) { diff --git a/lib/scripts/loaders/dicomloader.js b/lib/scripts/loaders/dicomloader.js index b7f1d948..16c5c82c 100644 --- a/lib/scripts/loaders/dicomloader.js +++ b/lib/scripts/loaders/dicomloader.js @@ -41,9 +41,9 @@ const DEBUG_PRINT_TAGS_INFO = false; const DEBUG_PRINT_INDI_SLICE_INFO = false; /** deep artificially fix volume texture size to even numbers */ -const NEED_EVEN_TEXTURE_SIZE = true; +const NEED_EVEN_TEXTURE_SIZE = false; -const NEED_SCAN_EMPTY_BORDER = true; // false; +const NEED_SCAN_EMPTY_BORDER = false; const NEED_APPLY_GAUSS_SMOOTHING = false; /* eslint-disable */ const MAGIC_DICM = [68, 73, 67, 77]; @@ -317,6 +317,10 @@ export default class DicomFolderLoader { return this.m_dicomInfo; } + /** + * Create volume data array from individual slices, loaded from different files + * @return {LoadResult} LoadResult.SUCCESS if success + */ createVolumeFromSlices() { let i; let dataSize = 0; @@ -325,6 +329,10 @@ export default class DicomFolderLoader { // normal copy volume with transform 16 -> 8 bit dataSize = this.m_xDim * this.m_yDim * this.m_zDim; dataArray = new Uint8Array(dataSize); + if (dataArray === null) { + console.log('no memory'); + return LoadResult.ERROR_NO_MEMORY; + } for (i = 0; i < dataSize; i++) { dataArray[i] = 0; } @@ -333,6 +341,7 @@ export default class DicomFolderLoader { if (this.m_slicesVolume.m_numSlices !== this.m_zDim) { console.log('Error logic zDim'); + return LoadResult.ERROR_WRONG_NUM_SLICES; } const numSlices = this.m_slicesVolume.m_numSlices; @@ -344,6 +353,8 @@ export default class DicomFolderLoader { maxVal = (val16 > maxVal) ? val16 : maxVal; } // for (i) all slice pixels } // for (s) all slices + // allocate one more entry for values + maxVal++; console.log(`Build Volume. max value=${maxVal}. Volume dim = ${this.m_xDim}*${this.m_yDim}*${this.m_zDim}`); console.log(`Min slice number = ${this.m_slicesVolume.m_minSlice}`); @@ -358,7 +369,7 @@ export default class DicomFolderLoader { const sliceData16 = slice.m_image; for (i = 0; i < xyDim; i++) { const val16 = sliceData16[i]; - histogram[val16] += 1; + histogram[val16]++; } } const histSmooothed = new Int32Array(maxVal); @@ -366,7 +377,7 @@ export default class DicomFolderLoader { const okBuild = VolumeTools.buildSmoothedHistogram(histogram, histSmooothed, maxVal, HIST_SMOOTH_SIGMA); if (okBuild !== 1) { console.log('Error build histogram'); - return; + return LoadResult.ERROR_PROCESS_HISTOGRAM; } // Find val max in smoothed histogram @@ -404,7 +415,7 @@ export default class DicomFolderLoader { const deltaIndex = idxSomeAfterMax - idxLastLocalMax; if (deltaIndex === 0) { console.log('Critical error build 8bit volume'); - return; + return LoadResult.ERROR_HISTOGRAM_DETECT_RIDGES; } const koefA = (yAftMax - yLocMax) / deltaIndex; const koefB = yAftMax - koefA * idxSomeAfterMax; @@ -418,11 +429,11 @@ export default class DicomFolderLoader { const BITS_ACCUR = 11; const BITS_IN_BYTE = 8; - const scale = (1 << (BITS_IN_BYTE + BITS_ACCUR)) / maxVal; + const scale = Math.floor((1 << (BITS_IN_BYTE + BITS_ACCUR)) / maxVal); const TOO_MIN_SCALE = 4; if (scale <= TOO_MIN_SCALE) { console.log('Bad scaling: image will be 0'); - return; + return LoadResult.ERROR_SCALING; } // remove unused objects in slices (make length equal to actual number of slices) @@ -451,7 +462,7 @@ export default class DicomFolderLoader { const z = slice.m_sliceNumber - this.m_slicesVolume.m_minSlice; if ((z < 0) || (z >= this.m_zDim)) { console.log('Invalid z slice reference'); - return; + return LoadResult.ERROR_INVALID_SLICE_INDEX; } if (sliceData16 !== null) { const offZ = z * xyDim; @@ -682,6 +693,7 @@ export default class DicomFolderLoader { if (callbackRead) { callbackRead(LoadResult.SUCCESS, header, dataSize, dataArray); } // if callback ready + return LoadResult.SUCCESS; } // createVolumeFromSlices /** @@ -774,7 +786,14 @@ export default class DicomFolderLoader { this.m_boxSize.x = this.m_xDim * this.m_pixelSpacing.x; this.m_boxSize.y = this.m_yDim * this.m_pixelSpacing.y; console.log(`Volume local phys dim: ${this.m_boxSize.x} * ${this.m_boxSize.y} * ${this.m_boxSize.z}`); - this.createVolumeFromSlices(); + const errStatus = this.createVolumeFromSlices(); + if (errStatus !== LoadResult.SUCCESS) { + if (this.m_callbackRead !== null) { + this.m_callbackRead(errStatus, null, 0, null, fileName); + return false; + } + return false; + } } return true; }, (errMsg) => { @@ -939,6 +958,10 @@ export default class DicomFolderLoader { parseDicomFileBuffer(indexFile, arrayBuf) { // build 8 byte buffer from abstract arrau buffer const dataView = new DataView(arrayBuf); + if (dataView === null) { + console.log('No memory'); + return LoadResult.ERROR_NO_MEMORY; + } const fileSize = dataView.byteLength; // check dicom header const SIZE_HEAD = 144; @@ -1270,7 +1293,7 @@ export default class DicomFolderLoader { // check correct data from tags const BITS_IN_BYTE = 8; - const imageSizeBytes = this.m_xDim * this.m_yDim * (this.m_bitsPerPixel / BITS_IN_BYTE); + const imageSizeBytes = Math.floor(this.m_xDim * this.m_yDim * (this.m_bitsPerPixel / BITS_IN_BYTE)); if ((imageSizeBytes !== tag.m_value.byteLength) || (pixelBitMask === 0)) { console.log(`Wrong image pixels size. Readed ${tag.m_value.byteLength}, but expected ${imageSizeBytes}`); return LoadResult.WRONG_HEADER_DATA_SIZE; @@ -1279,11 +1302,16 @@ export default class DicomFolderLoader { const numPixels = this.m_xDim * this.m_yDim; const volSlice = this.m_slicesVolume.getNewSlice(); if (volSlice === null) { + console.log('No memory'); return LoadResult.ERROR_NO_MEMORY; } // this.m_slices[this.m_imageNumber] = new Uint16Array(numPixels); volSlice.m_image = new Uint16Array(numPixels); + if (volSlice.m_image === null) { + console.log('No memory'); + return LoadResult.ERROR_NO_MEMORY; + } volSlice.m_sliceNumber = this.m_imageNumber; volSlice.m_sliceLocation = this.m_sliceLocation; @@ -1293,6 +1321,10 @@ export default class DicomFolderLoader { // const imageDst = this.m_slices[this.m_imageNumber]; const imageDst = volSlice.m_image; const imageSrc = new DataView(tag.m_value); + if (imageSrc === null) { + console.log('No memory'); + return LoadResult.ERROR_NO_MEMORY; + } const BITS_16 = 16; let i; @@ -1313,7 +1345,7 @@ export default class DicomFolderLoader { } else { // if no max value const SCALE_BIT_ACC = 12; const MAX_SCALED_VAL = 4095; - const valScale = (MAX_SCALED_VAL << SCALE_BIT_ACC) / (pixelMaxValue - pixelMinValue); + const valScale = Math.floor((MAX_SCALED_VAL << SCALE_BIT_ACC) / (pixelMaxValue - pixelMinValue)); for (i = 0; i < numPixels; i++) { let val = imageSrc.getInt16(i2, this.m_littleEndian); i2 += SIZE_SHORT; diff --git a/lib/scripts/loaders/hdrvolume.js b/lib/scripts/loaders/hdrvolume.js index ccf0f6e9..988745e6 100644 --- a/lib/scripts/loaders/hdrvolume.js +++ b/lib/scripts/loaders/hdrvolume.js @@ -35,7 +35,7 @@ import VolumeTools from './voltools'; // ****************************************************************** /** Need to make texture z dimension evenly aligned */ -const NEED_EVEN_TEXTURE_SIZE = true; +const NEED_EVEN_TEXTURE_SIZE = false; const HDR_DT_NONE = 0; // const HDR_DT_BINARY = 1; diff --git a/lib/scripts/loaders/ktxheader.js b/lib/scripts/loaders/ktxheader.js index 91ba3c92..4e0988bb 100644 --- a/lib/scripts/loaders/ktxheader.js +++ b/lib/scripts/loaders/ktxheader.js @@ -60,6 +60,22 @@ export default class KtxHeader { /** @property {number} m_bytesOfKeyValueData - extra key values, usually 0 */ this.m_bytesOfKeyValueData = 0; } + copyFrom(headerSrc) { + this.m_id = headerSrc.m_id; + this.m_endianness = headerSrc.m_endianness; + this.m_glType = headerSrc.m_glType; + this.m_glTypeSize = headerSrc.m_glTypeSize; + this.m_glFormat = headerSrc.m_glFormat; + this.m_glInternalFormat = headerSrc.m_glInternalFormat; + this.m_glBaseInternalFormat = headerSrc.m_glBaseInternalFormat; + this.m_pixelWidth = headerSrc.m_pixelWidth; + this.m_pixelHeight = headerSrc.m_pixelHeight; + this.m_pixelDepth = headerSrc.m_pixelDepth; + this.m_numberOfArrayElements = headerSrc.m_numberOfArrayElements; + this.m_numberOfFaces = headerSrc.m_numberOfFaces; + this.m_numberOfMipmapLevels = headerSrc.m_numberOfMipmapLevels; + this.m_bytesOfKeyValueData = headerSrc.m_bytesOfKeyValueData; + } } /** @const {number} KTX_GL_RED - 1 byte per voxel */ diff --git a/lib/scripts/loaders/ktxloader.js b/lib/scripts/loaders/ktxloader.js index 61bbdd30..1d014b3b 100644 --- a/lib/scripts/loaders/ktxloader.js +++ b/lib/scripts/loaders/ktxloader.js @@ -36,7 +36,7 @@ import VolumeTools from './voltools'; const NEED_CONTRAST_DATA = false; /** deep artificially fix volume texture size to even numbers */ -const NEED_EVEN_TEXTURE_SIZE = true; +const NEED_EVEN_TEXTURE_SIZE = false; /** Class KtxLoader Load KTX volum files */ export default class KtxLoader { @@ -95,14 +95,12 @@ export default class KtxLoader { const BYTES_IN_FLOAT = 4; const arBuf = new ArrayBuffer(BYTES_IN_FLOAT); const dataArray = new DataView(arBuf); - // eslint-disable-next-line - dataArray.setUint8(0, buf[off + 0]); - // eslint-disable-next-line - dataArray.setUint8(1, buf[off + 1]); - // eslint-disable-next-line - dataArray.setUint8(2, buf[off + 2]); - // eslint-disable-next-line - dataArray.setUint8(3, buf[off + 3]); + const OFF_0 = 0; const OFF_1 = 1; + const OFF_2 = 2; const OFF_3 = 3; + dataArray.setUint8(OFF_0, buf[off + OFF_0]); + dataArray.setUint8(OFF_1, buf[off + OFF_1]); + dataArray.setUint8(OFF_2, buf[off + OFF_2]); + dataArray.setUint8(OFF_3, buf[off + OFF_3]); const IS_LITTLE_ENDIAN = true; const res = dataArray.getFloat32(0, IS_LITTLE_ENDIAN); return res; diff --git a/lib/scripts/loaders/loadresult.js b/lib/scripts/loaders/loadresult.js index b7a9d71b..10cb0867 100644 --- a/lib/scripts/loaders/loadresult.js +++ b/lib/scripts/loaders/loadresult.js @@ -60,6 +60,14 @@ export default class LoadResult { return 'No memory during loading'; case LoadResult.ERROR_CANT_OPEN_URL: return 'Cant open file via url'; + case LoadResult.ERROR_WRONG_NUM_SLICES: + return 'Wrong number of slices'; + case LoadResult.ERROR_HISTOGRAM_DETECT_RIDGES: + return 'Error detect histogram ridges'; + case LoadResult.ERROR_SCALING: + return 'Error scaling 16 bit data into 8 bit'; + case LoadResult.ERROR_INVALID_SLICE_INDEX: + return 'Invalid slice index. Possible reason: incomplete dicom folder'; default: return 'Unknown error code'; } // switch @@ -81,4 +89,7 @@ LoadResult.WRONG_IMAGE_DIM_Y = 12; LoadResult.ERROR_PIXELS_TAG_NOT_FOUND = 13; LoadResult.ERROR_NO_MEMORY = 14; LoadResult.ERROR_CANT_OPEN_URL = 15; - +LoadResult.ERROR_WRONG_NUM_SLICES = 16; +LoadResult.ERROR_HISTOGRAM_DETECT_RIDGES = 17; +LoadResult.ERROR_SCALING = 18; +LoadResult.ERROR_INVALID_SLICE_INDEX = 19; diff --git a/lib/scripts/loaders/niiloader.js b/lib/scripts/loaders/niiloader.js index d1ffe624..6eb078a7 100644 --- a/lib/scripts/loaders/niiloader.js +++ b/lib/scripts/loaders/niiloader.js @@ -38,7 +38,7 @@ import DicomInfo from './dicominfo'; // ****************************************************************** /** deep artificially fix volume texture size to even numbers */ -const NEED_EVEN_TEXTURE_SIZE = true; +const NEED_EVEN_TEXTURE_SIZE = false; // ****************************************************************** // Class diff --git a/lib/scripts/loaders/voltools.js b/lib/scripts/loaders/voltools.js index efcacba0..6355221b 100644 --- a/lib/scripts/loaders/voltools.js +++ b/lib/scripts/loaders/voltools.js @@ -68,7 +68,7 @@ export default class VolumeTools { gaussRadius, gaussSigma) { // eslint-disable-next-line - const side = gaussRadius * 2 + 1; + const side = Math.floor(gaussRadius * 2 + 1); // check valid arguments const ERR_SIDE = -1; const ERR_BAD_SIGMA = -2; @@ -317,9 +317,9 @@ export default class VolumeTools { } const xyDimSrc = xDimSrc * yDimSrc; - const xStep = (xDimSrc << ACC_BITS) / xDimDst; - const yStep = (yDimSrc << ACC_BITS) / yDimDst; - const zStep = (zDimSrc << ACC_BITS) / zDimDst; + const xStep = Math.floor((xDimSrc << ACC_BITS) / xDimDst); + const yStep = Math.floor((yDimSrc << ACC_BITS) / yDimDst); + const zStep = Math.floor((zDimSrc << ACC_BITS) / zDimDst); let indDst = 0; let zSrcAccL = ACC_HALF; let zSrcAccH = zSrcAccL + zStep; @@ -837,6 +837,201 @@ export default class VolumeTools { return 1; } // showTexture2d + /** + * Test int value: is this 2^N + * @param {number} val - tested value + * @return {boolean} Is power of 2 or not + */ + static isPowerOfTwo(val) { + const MAX_PWR = 31; + for (let i = 1; i < MAX_PWR; i++) { + const vpwr = 1 << i; + if (val === vpwr) { + return true; + } + } + return false; + } + + /** + * Get closest power of 2: greater or equal then imput argument + * @param {number} val - tested value + * @return {number} Closest (greater or equal) power of two + */ + static getGreatOrEqualPowerOfTwo(val) { + const MAX_PWR = 31; + for (let i = 1; i < MAX_PWR; i++) { + const vpwr = 1 << i; + if (val === vpwr) { + return val; + } + const vpwrNext = 1 << (i + 1); + if ((val > vpwr) && (val < vpwrNext)) { + return vpwrNext; + } + } + return -1; + } + + /** + * Make texture size equal to power of 2 for x, y dimensions + * @param {array} pixelsSrc - source volume pixels + * @param {number} xDimSrc - volume size on x + * @param {number} yDimSrc - volume size on y + * @param {number} zDimSrc - volume size on z + * @param {number} xDimDst - new x dimension + * @param {number} yDimDst - new y dimension + * @return {array} new array + */ + static makeTextureSizePowerOfTwoXY(pixelsSrc, xDimSrc, yDimSrc, zDimSrc, xDimDst, yDimDst) { + const zDimDst = zDimSrc; + const HALF = 2; + if (xDimDst < xDimSrc) { + console.log(`Error: ${xDimDst} < ${xDimSrc}`); + } + if (yDimDst < yDimSrc) { + console.log(`Error: ${yDimDst} < ${yDimSrc}`); + } + const xShift = Math.floor((xDimDst - xDimSrc) / HALF); + const yShift = Math.floor((yDimDst - yDimSrc) / HALF); + const numPixelsOld = xDimSrc * yDimSrc * zDimSrc; + if (pixelsSrc.length !== numPixelsOld) { + console.log(`Error source volume bpp: = ${xDimSrc} * ${yDimSrc} * ${zDimSrc}`); + } + const numPixelsNew = xDimDst * yDimDst * zDimDst; + const pixelsDst = new Uint8Array(numPixelsNew); + let i; + for (i = 0; i < numPixelsNew; i++) { + pixelsDst[i] = 0; + } + let offSrc = 0; + for (let z = 0; z < zDimSrc; z++) { + // const MASK_CONVERT_PROGRESS = 31; + // if ((z & MASK_CONVERT_PROGRESS) === 0) { + // console.log(`progress convert z = ${z}`); + // } + const zOffDst = z * xDimDst * yDimDst; + for (let y = 0; y < yDimSrc; y++) { + const yOffDst = (y + yShift) * xDimDst; + let offDst = xShift + yOffDst + zOffDst; + for (let x = 0; x < xDimSrc; x++, offDst++) { + const val = pixelsSrc[offSrc++]; + pixelsDst[offDst] = val; + } // for (x) source + } // for (y) source + } // for (z) source + return pixelsDst; + } // end of makeTextureSizePowerOfTwoXY + /** + * Scan volume for non-empty box (with non-zero voxels) + * fill resul in [0..1] range. Use 8 as "black" color barrier + * @param {array} pixelsSrc - source volume pixels + * @param {number} xDim - volume size on x + * @param {number} yDim - volume size on y + * @param {number} zDim - volume size on z + * @param {number} boxMin - Object (x,y,z) with minumim non-empty box coords + * @param {number} boxMax - Object (x,y,z) with maximum non-empty box coords + */ + static detectNonEmptyBox(pixelsSrc, xDim, yDim, zDim, boxMin, boxMax) { + const MIN_VAL_BARRIER = 8; + const TWICE = 2; + const xyDim = xDim * yDim; + const xDimHalf = Math.floor(xDim / TWICE); + const yDimHalf = Math.floor(yDim / TWICE); + const zDimHalf = Math.floor(zDim / TWICE); + let x, y, z; + let isEmpty; + isEmpty = true; + for (x = 0; (x < xDimHalf) && isEmpty; x++) { + // check is empty plane + for (y = 0; (y < yDim) && (isEmpty); y++) { + for (z = 0; (z < zDim) && (isEmpty); z++) { + const off = (z * xyDim) + (y * xDim) + x; + if (pixelsSrc[off] > MIN_VAL_BARRIER) { + isEmpty = false; + } + } // for (z) + } // for (y() + } // for (x) + const xBorderMin = x; + + isEmpty = true; + for (x = xDim - 1; (x > xDimHalf) && (isEmpty); x--) { + // check is empty plane + for (y = 0; (y < yDim) && (isEmpty); y++) { + for (z = 0; (z < zDim) && (isEmpty); z++) { + const off = (z * xyDim) + (y * xDim) + x; + if (pixelsSrc[off] > MIN_VAL_BARRIER) { + isEmpty = false; + } + } // for (z) + } // for (y() + } // for (x) + const xBorderMax = x; + + isEmpty = true; + for (y = 0; (y < yDimHalf) && (isEmpty); y++) { + // check is empty plane + for (x = 0; (x < xDim) && (isEmpty); x++) { + for (z = 0; (z < zDim) && (isEmpty); z++) { + const off = (z * xyDim) + (y * xDim) + x; + if (pixelsSrc[off] > MIN_VAL_BARRIER) { + isEmpty = false; + } + } // for (z) + } // for (y() + } // for (x) + const yBorderMin = y; + + isEmpty = true; + for (y = yDim - 1; (y > yDimHalf) && (isEmpty); y--) { + // check is empty plane + for (x = 0; (x < xDim) && (isEmpty); x++) { + for (z = 0; (z < zDim) && (isEmpty); z++) { + const off = (z * xyDim) + (y * xDim) + x; + if (pixelsSrc[off] > MIN_VAL_BARRIER) { + isEmpty = false; + } + } // for (z) + } // for (y() + } // for (x) + const yBorderMax = y; + + isEmpty = true; + for (z = 0; (z < zDimHalf) && (isEmpty); z++) { + // check is empty plane + for (x = 0; (x < xDim) && (isEmpty); x++) { + for (y = 0; (y < yDim) && (isEmpty); y++) { + const off = (z * xyDim) + (y * xDim) + x; + if (pixelsSrc[off] > MIN_VAL_BARRIER) { + isEmpty = false; + } + } // for (z) + } // for (y() + } // for (x) + const zBorderMin = z; + + isEmpty = true; + for (z = zDim - 1; (z > zDimHalf) && (isEmpty); z--) { + // check is empty plane + for (x = 0; (x < xDim) && (isEmpty); x++) { + for (y = 0; (y < yDim) && (isEmpty); y++) { + const off = (z * xyDim) + (y * xDim) + x; + if (pixelsSrc[off] > MIN_VAL_BARRIER) { + isEmpty = false; + } + } // for (z) + } // for (y() + } // for (x) + const zBorderMax = z; + boxMin.x = xBorderMin / xDim; + boxMin.y = yBorderMin / yDim; + boxMin.z = zBorderMin / zDim; + boxMax.x = xBorderMax / xDim; + boxMax.y = yBorderMax / yDim; + boxMax.z = zBorderMax / zDim; + } + } // class VolumeTools VolumeTools.VOLTOOLS_ERROR_OK = 1; diff --git a/lib/scripts/med3web.js b/lib/scripts/med3web.js index 0e0a2446..ad55df0f 100644 --- a/lib/scripts/med3web.js +++ b/lib/scripts/med3web.js @@ -34,9 +34,11 @@ import GlCheck from './graphics3d/glcheck'; import BrowserDetector from './utils/browserdetector'; import KtxLoader from './loaders/ktxloader'; +import KtxHeader from './loaders/ktxheader'; import DicomFolderLoader from './loaders/dicomloader'; import NiftiLoader from './loaders/niiloader'; import HdrLoader from './loaders/hdrloader'; +import VolumeTools from './loaders/voltools'; import LoadResult from './loaders/loadresult'; @@ -593,6 +595,65 @@ export default class Med3Web { return okLoad; } // end loadScene + // **************************************************************** + // Finalize loading on success + // **************************************************************** + finalizeLoadedData(header, dataArrayBytes, volumeInfo, box, nonEmptyBoxMin, nonEmptyBoxMax, isRoiVolume) { + const xDim = header.m_pixelWidth; + const yDim = header.m_pixelHeight; + const zDim = header.m_pixelDepth; + console.log(`finalizeLoadedData. Loaded dimensions = dim=${xDim} * ${yDim} * ${zDim}`); + + const isPowerOfTwoX = VolumeTools.isPowerOfTwo(xDim); + const isPowerOfTwoY = VolumeTools.isPowerOfTwo(yDim); + if (!isPowerOfTwoX || !isPowerOfTwoY) { + // artificially create power of 2 texture + const xNewDim = VolumeTools.getGreatOrEqualPowerOfTwo(xDim); + const yNewDim = VolumeTools.getGreatOrEqualPowerOfTwo(yDim); + + const dataArrayNew = VolumeTools.makeTextureSizePowerOfTwoXY(dataArrayBytes, xDim, yDim, zDim, xNewDim, yNewDim); + const zNewDim = zDim; + console.log(`Converted dimensions into power of 2: ${xNewDim} * ${yNewDim} * ${zNewDim} `); + const headerNew = new KtxHeader(); + headerNew.copyFrom(header); + headerNew.m_pixelWidth = xNewDim; + headerNew.m_pixelHeight = yNewDim; + headerNew.m_pixelDepth = zNewDim; + + this.m_engine2d.m_volumeHeader = headerNew; + this.m_engine2d.m_volumeData = dataArrayNew; + } else { + this.m_engine2d.m_volumeHeader = header; + this.m_engine2d.m_volumeData = dataArrayBytes; + } + this.m_engine2d.m_volumeBox = box; + this.m_engine2d.m_volumeInfo = volumeInfo; + if (dataArrayBytes === null) { + console.log('BAD null volume data array'); + } else { + if (this.m_menu) { + this.m_menu.stopProgressBar(); + } + // scan for non-empty border + + VolumeTools.detectNonEmptyBox(this.m_engine2d.m_volumeData, + this.m_engine2d.m_volumeHeader.m_pixelWidth, + this.m_engine2d.m_volumeHeader.m_pixelHeight, + this.m_engine2d.m_volumeHeader.m_pixelDepth, + nonEmptyBoxMin, nonEmptyBoxMax); + const bMin = nonEmptyBoxMin; const bMax = nonEmptyBoxMax; + console.log(`Non empty box: ${bMin.x}, ${bMin.y}, ${bMin.z} => ${bMax.x}, ${bMax.y}, ${bMax.z}`); + console.log(`Volume box: ${box.x}, ${box.y}, ${box.z}`); + // create 3d data + this.m_engine3d.callbackCreateCubeVolumeBF(window, box, nonEmptyBoxMin, nonEmptyBoxMax, isRoiVolume); + // create 2d data + this.m_engine2d.createTileMapsWithTexture(this.m_engine3d.volTexture, isRoiVolume); + + this._callbackFileLoaded(); + } + } + + // **************************************************************** // Loaders // Dicom loader @@ -626,27 +687,12 @@ export default class Med3Web { } /** if read dicom folder success */ _dicomOnSucces(dataName, loader, header, dataSize, dataArray) { - const xd = header.m_pixelWidth; - const yd = header.m_pixelHeight; - const zd = header.m_pixelDepth; - console.log(`Success loading Dicom: ${dataName} dim=${xd}*${yd}*${zd}`); - const box = loader.getBoxSize(); - const nonEmptyBoxMin = loader.getNonEmptyBoxMin(); - const nonEmptyBoxMax = loader.getNonEmptyBoxMax(); - this.m_engine2d.m_volumeHeader = header; - this.m_engine2d.m_volumeData = dataArray; - this.m_engine2d.m_volumeBox = box; - this.m_engine2d.m_volumeInfo = loader.getDicomInfo(); - if (this.m_engine2d.m_volumeData === null) { - console.log('BAD null volume data array'); - } else { - if (this.m_menu) { - this.m_menu.stopProgressBar(); - } - this.m_engine3d.callbackCreateCubeVolumeBF(window, box, nonEmptyBoxMin, nonEmptyBoxMax); - this.m_engine2d.createTileMapsWithTexture(this.m_engine3d.origVolumeTex); - this._callbackFileLoaded(); - } + + console.log(`Success loading Dicom: ${dataName} `); + + const NOT_ROI = false; + this.finalizeLoadedData(header, dataArray, loader.getDicomInfo(), loader.getBoxSize(), + loader.getNonEmptyBoxMin(), loader.getNonEmptyBoxMax(), NOT_ROI); } /** if read dicom folder failed */ _dicomOnFail(dataName, status) { @@ -770,10 +816,7 @@ export default class Med3Web { /** If loading KTX succeded */ _ktxOnSuccess(dataName, loader, header, dataSize, dataArray) { - const hd = loader.m_header; - console.log(`Success loading KTX: ${dataName} dim=${hd.m_pixelWidth}*${hd.m_pixelHeight}*${hd.m_pixelDepth}`); - const box = loader.getBoxSize(); - /** @property {object} m_boxSize - vertex3f with physic volume dimension */ + console.log(`Success loading KTX: ${dataName}`); const nonEmptyBoxMin = { x: 0.0, y: 0.0, @@ -784,21 +827,9 @@ export default class Med3Web { y: 1.0, z: 1.0, }; - this.m_engine2d.m_volumeHeader = header; - this.m_engine2d.m_volumeData = dataArray; - this.m_engine2d.m_volumeBox = box; - this.m_engine2d.m_volumeInfo = null; - - if (this.m_engine2d.m_volumeData === null) { - console.log('BAD null volume data array'); - } else { - if (this.m_menu) { - this.m_menu.stopProgressBar(); - } - this.m_engine3d.callbackCreateCubeVolumeBF(window, box, nonEmptyBoxMin, nonEmptyBoxMax); - this.m_engine2d.createTileMapsWithTexture(this.m_engine3d.origVolumeTex); - this._callbackFileLoaded(); - } + const NOT_ROI = false; + this.finalizeLoadedData(header, dataArray, null, loader.getBoxSize(), + nonEmptyBoxMin, nonEmptyBoxMax, NOT_ROI); } /** If loading KTX failed */ _ktxOnFail(dataName, status) { @@ -894,11 +925,7 @@ export default class Med3Web { // Load Nifti file callback // ****************************************************************** _niftiOnSuccess(dataName, loader, header, dataSize, dataArray) { - const hx = header.m_pixelWidth; - const hy = header.m_pixelHeight; - const hz = header.m_pixelDepth; - console.log(`Success loading Nifti: ${dataName} dim = ${hx} * ${hy} * ${hz}`); - const box = loader.getBoxSize(); + console.log(`Success loading Nifti: ${dataName}`); const nonEmptyBoxMin = { x: 0.0, y: 0.0, @@ -909,21 +936,9 @@ export default class Med3Web { y: 1.0, z: 1.0, }; - this.m_engine2d.m_volumeHeader = header; - this.m_engine2d.m_volumeData = dataArray; - this.m_engine2d.m_volumeBox = box; - this.m_engine2d.m_volumeInfo = loader.getDicomInfo(); - - if (this.m_engine2d.m_volumeData === null) { - console.log('BAD null volume data array'); - } else { - if (this.m_menu) { - this.m_menu.stopProgressBar(); - } - this.m_engine3d.callbackCreateCubeVolumeBF(window, box, nonEmptyBoxMin, nonEmptyBoxMax); - this.m_engine2d.createTileMapsWithTexture(this.m_engine3d.origVolumeTex); - this._callbackFileLoaded(); - } + const NOT_ROI = false; + this.finalizeLoadedData(header, dataArray, loader.getDicomInfo(), loader.getBoxSize(), + nonEmptyBoxMin, nonEmptyBoxMax, NOT_ROI); } /** Failed load nifti file callback */ @@ -1012,11 +1027,7 @@ export default class Med3Web { // Hdr format loading // ****************************************************************** _hdrOnSuccess(dataName, loader, header, dataSize, dataArray, isRoiVolume) { - const hx = header.m_pixelWidth; - const hy = header.m_pixelHeight; - const hz = header.m_pixelDepth; - console.log(`Success loading Hdr: ${dataName} dim = ${hx} * ${hy} * ${hz}. isRoi:${isRoiVolume}`); - const box = loader.getBoxSize(); + console.log(`Success loading Hdr: ${dataName}`); const nonEmptyBoxMin = { x: 0.0, y: 0.0, @@ -1027,21 +1038,8 @@ export default class Med3Web { y: 1.0, z: 1.0, }; - this.m_engine2d.m_volumeHeader = header; - this.m_engine2d.m_volumeData = dataArray; - this.m_engine2d.m_volumeBox = box; - this.m_engine2d.m_volumeInfo = loader.getDicomInfo(); - - if (this.m_engine2d.m_volumeData === null) { - console.log('BAD null volume data array'); - } else { - if (this.m_menu) { - this.m_menu.stopProgressBar(); - } - this.m_engine3d.callbackCreateCubeVolumeBF(window, box, nonEmptyBoxMin, nonEmptyBoxMax, isRoiVolume); - this.m_engine2d.createTileMapsWithTexture(this.m_engine3d.volTexture, isRoiVolume); - this._callbackFileLoaded(); - } + this.finalizeLoadedData(header, dataArray, loader.getDicomInfo(), loader.getBoxSize(), + nonEmptyBoxMin, nonEmptyBoxMax, isRoiVolume); } _hdrOnFail(dataName, status) { const strTitle = `Error loading: ${dataName}`; diff --git a/test/loaders/voltools.test.js b/test/loaders/voltools.test.js index e427388f..10785332 100644 --- a/test/loaders/voltools.test.js +++ b/test/loaders/voltools.test.js @@ -402,6 +402,44 @@ describe('Test: VolumeTools', () => { const valCenterNex = pixelsDst[offCentral + 1]; assert.equal(valCenterNex, 0); }); // end of it + it('check is power of two', () => { + const VAL_A = 0; + const VAL_B = 1; + const VAL_C = 8; + const VAL_D = 15; + const VAL_E = 4096; + const IS_A = VolumeTools.isPowerOfTwo(VAL_A); + assert.equal(IS_A, false); + const IS_B = VolumeTools.isPowerOfTwo(VAL_B); + assert.equal(IS_B, false); + const IS_C = VolumeTools.isPowerOfTwo(VAL_C); + assert.equal(IS_C, true); + const IS_D = VolumeTools.isPowerOfTwo(VAL_D); + assert.equal(IS_D, false); + const IS_E = VolumeTools.isPowerOfTwo(VAL_E); + assert.equal(IS_E, true); + }); // end of it + it('check find GE power of two', () => { + const VAL_A = 4; + const VAL_B = 9; + const VAL_B_PWR = 16; + const VAL_C = 31; + const VAL_C_PWR = 32; + const VAL_D = 500; + const VAL_D_PWR = 512; + const VAL_E = 4096; + const PWR_A = VolumeTools.getGreatOrEqualPowerOfTwo(VAL_A); + assert.equal(PWR_A, VAL_A); + const PWR_B = VolumeTools.getGreatOrEqualPowerOfTwo(VAL_B); + assert.equal(PWR_B, VAL_B_PWR); + const PWR_C = VolumeTools.getGreatOrEqualPowerOfTwo(VAL_C); + assert.equal(PWR_C, VAL_C_PWR); + const PWR_D = VolumeTools.getGreatOrEqualPowerOfTwo(VAL_D); + assert.equal(PWR_D, VAL_D_PWR); + const PWR_E = VolumeTools.getGreatOrEqualPowerOfTwo(VAL_E); + assert.equal(PWR_E, VAL_E); + }); // end of it + }); // contrastEnchanceUnsharpMask tests }); // end of tests volume tools