From 87e1d86f61fb5bc65742bd502ccfa2a014eeeaf0 Mon Sep 17 00:00:00 2001 From: Peyton Lee Date: Fri, 13 Dec 2024 14:20:39 -0800 Subject: [PATCH] refactor: Replaced DATARANGE_UINT8 with calculated range values --- public/index.ts | 4 ++-- src/PathTracedVolume.ts | 4 ++-- src/loaders/JsonImageInfoLoader.ts | 19 +++++++++++++------ src/loaders/OpenCellLoader.ts | 10 ++++++++-- src/loaders/RawArrayLoader.ts | 6 ++++-- src/test/volume.test.ts | 6 +++--- src/types.ts | 2 -- src/utils/num_utils.ts | 10 ++++++++++ 8 files changed, 42 insertions(+), 19 deletions(-) diff --git a/public/index.ts b/public/index.ts index 2fc9e326..3fdecae3 100644 --- a/public/index.ts +++ b/public/index.ts @@ -23,8 +23,8 @@ import { import { OpenCellLoader } from "../src/loaders/OpenCellLoader"; import { State, TestDataSpec } from "./types"; import VolumeLoaderContext from "../src/workers/VolumeLoaderContext"; -import { DATARANGE_UINT8 } from "../src/types"; import { RawArrayLoaderOptions } from "../src/loaders/RawArrayLoader"; +import { getDataRange } from "../src/utils/num_utils"; const CACHE_MAX_SIZE = 1_000_000_000; const CONCURRENCY_LIMIT = 8; @@ -877,7 +877,7 @@ function loadImageData(jsonData: ImageInfo, volumeData: Uint8Array[]) { // according to jsonData.tile_width*jsonData.tile_height*jsonData.tiles // (first row of first plane is the first data in // the layout, then second row of first plane, etc) - vol.setChannelDataFromVolume(i, volumeData[i], DATARANGE_UINT8); + vol.setChannelDataFromVolume(i, volumeData[i], getDataRange(volumeData[i])); setInitialRenderMode(); diff --git a/src/PathTracedVolume.ts b/src/PathTracedVolume.ts index 2cca0de0..6e0ce6bb 100644 --- a/src/PathTracedVolume.ts +++ b/src/PathTracedVolume.ts @@ -492,11 +492,11 @@ export default class PathTracedVolume implements VolumeRenderImpl { // TODO expand to 16-bpp raw intensities? this.pathTracingUniforms.gIntensityMax.value.setComponent( i, - this.volume.channels[channel].histogram.getMax() / 255.0 + this.volume.channels[channel].histogram.getDataMax() / 255.0 ); this.pathTracingUniforms.gIntensityMin.value.setComponent( i, - this.volume.channels[channel].histogram.getMin() / 255.0 + this.volume.channels[channel].histogram.getDataMin() / 255.0 ); } this.pathTracingUniforms.gLutTexture.value.needsUpdate = true; diff --git a/src/loaders/JsonImageInfoLoader.ts b/src/loaders/JsonImageInfoLoader.ts index 2fde3051..229516a8 100644 --- a/src/loaders/JsonImageInfoLoader.ts +++ b/src/loaders/JsonImageInfoLoader.ts @@ -10,7 +10,7 @@ import { computeAtlasSize, type ImageInfo } from "../ImageInfo.js"; import type { VolumeDims } from "../VolumeDims.js"; import VolumeCache from "../VolumeCache.js"; import type { TypedArray, NumberType } from "../types.js"; -import { DATARANGE_UINT8 } from "../types.js"; +import { getDataRange } from "../utils/num_utils.js"; interface PackedChannelsImage { name: string; @@ -262,14 +262,15 @@ class JsonImageInfoLoader extends ThreadableVolumeLoader { const cacheResult = cache?.get(`${image.name}/${chindex}`); if (cacheResult) { // all data coming from this loader is natively 8-bit + const channelData = new Uint8Array(cacheResult); if (syncChannels) { // if we are synchronizing channels, we need to keep track of the data resultChannelIndices.push(chindex); resultChannelDtype.push("uint8"); - resultChannelData.push(new Uint8Array(cacheResult)); - resultChannelRanges.push(DATARANGE_UINT8); + resultChannelData.push(channelData); + resultChannelRanges.push(getDataRange(channelData)); } else { - onData([chindex], ["uint8"], [new Uint8Array(cacheResult)], [DATARANGE_UINT8]); + onData([chindex], ["uint8"], [new Uint8Array(cacheResult)], [getDataRange(channelData)]); } } else { cacheHit = false; @@ -308,10 +309,16 @@ class JsonImageInfoLoader extends ThreadableVolumeLoader { } // extract the data + let rawRange: [number, number][] = []; for (let j = 0; j < Math.min(image.channels.length, 4); ++j) { + let rawMin = Infinity; + let rawMax = -Infinity; for (let px = 0; px < length; px++) { channelsBits[j][px] = iData.data[px * 4 + j]; + rawMin = Math.min(rawMin, channelsBits[j][px]); + rawMax = Math.max(rawMax, channelsBits[j][px]); } + rawRange[j] = [rawMin, rawMax]; } // done with `iData` and `canvas` now. @@ -325,9 +332,9 @@ class JsonImageInfoLoader extends ThreadableVolumeLoader { resultChannelIndices.push(chindex); resultChannelDtype.push("uint8"); resultChannelData.push(channelsBits[ch]); - resultChannelRanges.push(DATARANGE_UINT8); + resultChannelRanges.push(rawRange[ch]); } else { - onData([chindex], ["uint8"], [channelsBits[ch]], [DATARANGE_UINT8], [bitmap.width, bitmap.height]); + onData([chindex], ["uint8"], [channelsBits[ch]], [rawRange[ch]], [bitmap.width, bitmap.height]); } } }); diff --git a/src/loaders/OpenCellLoader.ts b/src/loaders/OpenCellLoader.ts index d5dec7b2..58cfa563 100644 --- a/src/loaders/OpenCellLoader.ts +++ b/src/loaders/OpenCellLoader.ts @@ -2,7 +2,7 @@ import { ThreadableVolumeLoader, LoadSpec, RawChannelDataCallback, LoadedVolumeI import { computeAtlasSize, type ImageInfo } from "../ImageInfo.js"; import type { VolumeDims } from "../VolumeDims.js"; import { JsonImageInfoLoader } from "./JsonImageInfoLoader.js"; -import { DATARANGE_UINT8 } from "../types.js"; +import { getDataRange } from "../utils/num_utils.js"; class OpenCellLoader extends ThreadableVolumeLoader { async loadDims(_: LoadSpec): Promise { @@ -74,7 +74,13 @@ class OpenCellLoader extends ThreadableVolumeLoader { const [w, h] = computeAtlasSize(imageInfo); // all data coming from this loader is natively 8-bit return JsonImageInfoLoader.loadVolumeAtlasData(urls, (ch, dtype, data) => - onData(ch, dtype, data, [DATARANGE_UINT8], [w, h]) + onData( + ch, + dtype, + data, + data.map((arr) => getDataRange(arr)), + [w, h] + ) ); } } diff --git a/src/loaders/RawArrayLoader.ts b/src/loaders/RawArrayLoader.ts index 952c8cb5..4b8f29c6 100644 --- a/src/loaders/RawArrayLoader.ts +++ b/src/loaders/RawArrayLoader.ts @@ -9,7 +9,8 @@ import { import { computePackedAtlasDims } from "./VolumeLoaderUtils.js"; import type { ImageInfo } from "../ImageInfo.js"; import type { VolumeDims } from "../VolumeDims.js"; -import { DATARANGE_UINT8, Uint8 } from "../types.js"; +import { Uint8 } from "../types.js"; +import { getDataRange } from "../utils/num_utils.js"; // this is the form in which a 4D numpy array arrives as converted // by jupyterlab into a js object. @@ -135,8 +136,9 @@ class RawArrayLoader extends ThreadableVolumeLoader { } const volSizeBytes = this.data.shape[3] * this.data.shape[2] * this.data.shape[1]; // x*y*z pixels * 1 byte/pixel const channelData = new Uint8Array(this.data.buffer.buffer, chindex * volSizeBytes, volSizeBytes); + const range = getDataRange(channelData); // all data coming from this loader is natively 8-bit - onData([chindex], ["uint8"], [channelData], [DATARANGE_UINT8]); + onData([chindex], ["uint8"], [channelData], [range]); } return Promise.resolve(); diff --git a/src/test/volume.test.ts b/src/test/volume.test.ts index e4368fb4..b5c80c15 100644 --- a/src/test/volume.test.ts +++ b/src/test/volume.test.ts @@ -4,8 +4,8 @@ import Volume from "../Volume"; import VolumeMaker from "../VolumeMaker"; import { LUT_ARRAY_LENGTH } from "../Lut"; import Channel from "../Channel"; -import { DATARANGE_UINT8 } from "../types"; import { CImageInfo, ImageInfo } from "../ImageInfo"; +import { getDataRange } from "../utils/num_utils"; // PREPARE SOME TEST DATA TO TRY TO DISPLAY A VOLUME. const testimgdata: ImageInfo = { @@ -87,14 +87,14 @@ describe("test volume", () => { const conedata = VolumeMaker.createCone(size.x, size.y, size.z, size.x / 8, size.z); - v.setChannelDataFromVolume(0, conedata, DATARANGE_UINT8); + v.setChannelDataFromVolume(0, conedata, getDataRange(conedata)); const c0 = v.getChannel(0); checkChannelDataConstruction(c0, 0, testimgdata); const spheredata = VolumeMaker.createSphere(size.x, size.y, size.z, size.z / 4); - v.setChannelDataFromVolume(1, spheredata, DATARANGE_UINT8); + v.setChannelDataFromVolume(1, spheredata, getDataRange(spheredata)); const c1 = v.getChannel(1); checkChannelDataConstruction(c1, 1, testimgdata); diff --git a/src/types.ts b/src/types.ts index 174b9594..1f15391d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -150,5 +150,3 @@ export const isTop = (corner: ViewportCorner): boolean => corner === ViewportCorner.TOP_LEFT || corner === ViewportCorner.TOP_RIGHT; export const isRight = (corner: ViewportCorner): boolean => corner === ViewportCorner.TOP_RIGHT || corner === ViewportCorner.BOTTOM_RIGHT; - -export const DATARANGE_UINT8: [number, number] = [0, 255]; diff --git a/src/utils/num_utils.ts b/src/utils/num_utils.ts index a5cd0292..b70b920f 100644 --- a/src/utils/num_utils.ts +++ b/src/utils/num_utils.ts @@ -230,3 +230,13 @@ export function constrainToAxis( return [...src]; } } + +export function getDataRange(data: ArrayLike): [number, number] { + let min = Infinity; + let max = -Infinity; + for (let i = 0; i < data.length; i++) { + min = Math.min(min, data[i]); + max = Math.max(max, data[i]); + } + return [min, max]; +}