Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c51d430
Changing TrackManager and PointCanvas to accomodate for having a diff…
TeunHuijben Aug 20, 2024
508f9af
changed default datURL to point to a public dataset with size informa…
TeunHuijben Aug 20, 2024
e89db62
resolved bug when assigning this.points.material.size
TeunHuijben Aug 21, 2024
0067de8
changed default dataset upon loading
TeunHuijben Aug 21, 2024
6393e39
minor changes to pass tests
TeunHuijben Aug 21, 2024
3eeb120
changed default dataset
TeunHuijben Aug 21, 2024
d71afc4
fixed typo
TeunHuijben Aug 21, 2024
744cd5a
removed clutter
TeunHuijben Aug 21, 2024
c361d6b
changed rendering to have depth-ordering correct
TeunHuijben Aug 22, 2024
b87d8cc
removedunused imports
TeunHuijben Aug 22, 2024
bdaca6f
fixed bug, now each point actually has different size
TeunHuijben Aug 22, 2024
a6b4484
formatting changes
TeunHuijben Aug 22, 2024
dd93a07
changes according to lint syntax suggestions
TeunHuijben Aug 22, 2024
dd9a193
centered points around origin of fov
TeunHuijben Aug 22, 2024
98ca77e
added alphaTest to FragmentShader
TeunHuijben Aug 24, 2024
1818403
finetuned bloomPass for esthetics
TeunHuijben Aug 24, 2024
c0f3b62
highlight selected cells over time, based on branch 'fix-selection-st…
TeunHuijben Aug 27, 2024
cba39eb
changes size of points to reflect reality
TeunHuijben Sep 4, 2024
66c95db
steps towards loading datasets with/without point size information
TeunHuijben Sep 5, 2024
02102d6
make dataloading compatible with each point having 3 or 4 values, dep…
TeunHuijben Sep 5, 2024
d3e644f
fixed bug where selected cells lost pink color when brightness was ch…
TeunHuijben Sep 5, 2024
d1ed4b6
lint fixes
TeunHuijben Sep 5, 2024
1a836aa
PointCanvas/updateSelectedPointIndices is memoized, in a way that sel…
TeunHuijben Sep 6, 2024
3c0f334
highlight all selected cells, not only the ones that are selected in …
TeunHuijben Sep 14, 2024
52e6c72
Merge branch 'main' into radius-from-data
TeunHuijben Sep 14, 2024
48f1591
merged this PR with main to get config functionality
TeunHuijben Sep 14, 2024
2a04cf5
fixed cacheKey going to infinity
TeunHuijben Sep 16, 2024
b0d41fc
lint fixes
TeunHuijben Sep 16, 2024
3f6fd6a
added .zattrs to points field that indicates whether 3/4 values (w/wo…
TeunHuijben Sep 16, 2024
8af485b
renamed conversion script
TeunHuijben Sep 16, 2024
485b0d7
resolved issue where disabled button gave error when using tooltip
TeunHuijben Sep 16, 2024
25132a8
added point size as param in config, if radius not provided in zarr
TeunHuijben Sep 16, 2024
d9c84f3
finetuning
TeunHuijben Sep 16, 2024
8a7fa57
removes saopass
TeunHuijben Sep 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/hooks/usePointCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,12 @@ function reducer(canvas: PointCanvas, action: PointCanvasAction): PointCanvas {
case ActionType.POINT_BRIGHTNESS:
newCanvas.pointBrightness = action.brightness;
newCanvas.resetPointColors();
newCanvas.updateSelectedPointIndices();
break;
case ActionType.POINTS_POSITIONS:
newCanvas.setPointsPositions(action.positions);
newCanvas.resetPointColors();
newCanvas.updateSelectedPointIndices();
break;
case ActionType.REMOVE_ALL_TRACKS:
newCanvas.removeAllTracks();
Expand Down
94 changes: 81 additions & 13 deletions src/lib/PointCanvas.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {
AdditiveBlending,
AxesHelper,
BufferGeometry,
Color,
Float32BufferAttribute,
FogExp2,
NormalBlending,
PerspectiveCamera,
Points,
PointsMaterial,
Scene,
ShaderMaterial,
SRGBColorSpace,
TextureLoader,
Vector2,
Expand All @@ -19,10 +19,12 @@ import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
// import { SAOPass } from 'three/addons/postprocessing/SAOPass.js';

import { Track } from "@/lib/three/Track";
import { PointSelector, PointSelectionMode } from "@/lib/PointSelector";
import { ViewerState } from "./ViewerState";
import { numberOfValuesPerPoint } from "./TrackManager";

// TrackType is a place to store the visual information about a track and any track-specific attributes
type TrackType = {
Expand Down Expand Up @@ -51,6 +53,7 @@ export class PointCanvas {
readonly fetchedRootTrackIds = new Set<number>();
// Needed to skip fetches for point IDs that been selected.
readonly fetchedPointIds = new Set<number>();
selectedPointIndices: number[] = [];

// All the point IDs that have been selected.
// PointCanvas.selector.selection is the transient array of selected
Expand Down Expand Up @@ -80,16 +83,54 @@ export class PointCanvas {
);

const pointsGeometry = new BufferGeometry();
const pointsMaterial = new PointsMaterial({
size: 16.0,
map: new TextureLoader().load("/spark1.png"),
vertexColors: true,
blending: AdditiveBlending,
// const pointsMaterial = new PointsMaterial({
// size: 100.0,
// map: new TextureLoader().load("/spark1.png"),
// vertexColors: true,
// blending: NormalBlending,
// depthTest: false,
// alphaTest: 0.1,
// depthWrite: true,
// transparent: true,
// });
const pointVertexShader = `
attribute float size;
attribute vec3 color; //Declare the color attribute
varying vec3 vColor;

void main() {
vColor = color;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = size * (300.0 / -mvPosition.z); // Adjust scaling factor
gl_Position = projectionMatrix * mvPosition;
}
`;
const pointFragmentShader = `
varying vec3 vColor;
uniform sampler2D pointTexture;

void main() {
gl_FragColor = vec4(vColor, 1.0);
gl_FragColor = gl_FragColor * texture2D(pointTexture, gl_PointCoord);
if (gl_FragColor.a < .5) discard;
}
`;

const shaderMaterial = new ShaderMaterial({
uniforms: {
color: { value: new Color(0xffffff) },
pointTexture: { value: new TextureLoader().load("/spark1.png") },
},
vertexShader: pointVertexShader,
fragmentShader: pointFragmentShader,

blending: NormalBlending,
depthTest: true,
alphaTest: 0.1,
transparent: true,
// alphaTest: 0.1, //no effect
depthWrite: true, // true by default
transparent: false,
});
this.points = new Points(pointsGeometry, pointsMaterial);
this.points = new Points(pointsGeometry, shaderMaterial);

this.scene.add(new AxesHelper(128));
this.scene.add(this.points);
Expand All @@ -101,13 +142,15 @@ export class PointCanvas {
new Vector2(width, height), // resolution
0.4, // strength
0, // radius
0, // threshold
0.2, // threshold
);
const outputPass = new OutputPass();
this.composer = new EffectComposer(this.renderer);
this.composer.addPass(renderModel);
this.composer.addPass(this.bloomPass);
this.composer.addPass(outputPass);
// const saoPass = new SAOPass( this.scene, this.camera, false, true );
// this.composer.addPass( saoPass );

// Set up controls
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
Expand Down Expand Up @@ -165,6 +208,18 @@ export class PointCanvas {
this.controls.update();
};

updateSelectedPointIndices() {
const idOffset = this.curTime * this.maxPointsPerTimepoint;
this.selectedPointIndices = [];
for (const track of this.tracks.values()) {
if (this.curTime < track.threeTrack.startTime || this.curTime > track.threeTrack.endTime) continue;
const timeIndex = this.curTime - track.threeTrack.startTime;
const pointId = track.threeTrack.pointIds[timeIndex];
this.selectedPointIndices.push(pointId - idOffset);
}
this.highlightPoints(this.selectedPointIndices);
}
Comment on lines +211 to +224

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could get pretty slow with a lot tracks selected. My idea in #96 (comment) was to basically memoize this function. I think the cache can also be computed/updated when adding tracks instead of when the timepoint changes.

The tradeoff is more memory, of course. I guess it should be tested but I don't think it's that much.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Ahsley, that is good point. I made a bit of a mess by including elements from #96 (@andy-sweet) in this branch without fully merging with that PR. The reason was that #96 diverged from main a lot earlier than this branch, which made merging difficult.

I have now updated updateSelectedPointIndices in the next commit by using memoization. I create a map that relates a CacheKeys to the selectedPointIndices vector. The body of this function is only evaluated the first time this combination of curTime and tracks is requested. Any future request, the vector is taken from the cache. The full cache is removed as soon as the user selects more/other cells, to prevent the cache from becoming too big.

Your suggestion of precomputing the cache every time tracks are added might be cumbersome in the case of many timepoints, but we could consider this.

The dataset here is too small to experience a speed-up, but if should be faster. Was this the idea you had in mind? Let me know if anything can be improved.


highlightPoints(points: number[]) {
const colorAttribute = this.points.geometry.getAttribute("color");
const color = new Color();
Expand Down Expand Up @@ -211,18 +266,31 @@ export class PointCanvas {
if (!geometry.hasAttribute("color") || geometry.getAttribute("color").count !== maxPointsPerTimepoint) {
geometry.setAttribute("color", new Float32BufferAttribute(new Float32Array(3 * maxPointsPerTimepoint), 3));
}
if (!geometry.hasAttribute("size") || geometry.getAttribute("size").count !== maxPointsPerTimepoint) {
geometry.setAttribute("size", new Float32BufferAttribute(new Float32Array(maxPointsPerTimepoint), 1));
}
// Initialize all the colors immediately.
this.resetPointColors();
}

setPointsPositions(data: Float32Array) {
const numPoints = data.length / 3;
const numPoints = data.length / numberOfValuesPerPoint;
const geometry = this.points.geometry;
const positions = geometry.getAttribute("position");
const sizes = geometry.getAttribute("size");
const num = numberOfValuesPerPoint;
for (let i = 0; i < numPoints; i++) {
positions.setXYZ(i, data[3 * i], data[3 * i + 1], data[3 * i + 2]);
positions.setXYZ(i, data[num * i], data[num * i + 1], data[num * i + 2]);
if (num == 4) {
sizes.setX(i, 28 * data[4 * i + 3]);
} else {
sizes.setX(i, 30);
}
// factor of 21 used to match the desired size of the points
// console.log("plotted point %d on (%d,%d,%d) with size %d (=21 * %d)", i,data[4 * i], data[4 * i + 1], data[4 * i + 2],11*data[4 * i + 3],data[4 * i + 3]);
}
positions.needsUpdate = true;
sizes.needsUpdate = true;
geometry.setDrawRange(0, numPoints);
this.points.geometry.computeBoundingSphere();
}
Expand Down
41 changes: 36 additions & 5 deletions src/lib/TrackManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-expect-error - types for zarr are not working right now, but a PR is open https://github.com/gzuidhof/zarr.js/pull/149
import { ZarrArray, slice, Slice, openArray, NestedArray } from "zarr";
export let numberOfValuesPerPoint = 3; // 3 if points=[x,y,z], 4 if points=[x,y,z,size]

class SparseZarrArray {
store: string;
Expand Down Expand Up @@ -83,7 +84,7 @@ export class TrackManager {
this.tracksToPoints = tracksToPoints;
this.tracksToTracks = tracksToTracks;
this.numTimes = points.shape[0];
this.maxPointsPerTimepoint = points.shape[1] / 3;
this.maxPointsPerTimepoint = points.shape[1] / numberOfValuesPerPoint; // default is /3
}

async fetchPointsAtTime(timeIndex: number): Promise<Float32Array> {
Expand All @@ -97,9 +98,9 @@ export class TrackManager {
let endIndex = points.findIndex((value) => value <= -127);
if (endIndex === -1) {
endIndex = points.length;
} else if (endIndex % 3 !== 0) {
console.error("invalid points - %d not divisible by 3", endIndex);
endIndex -= endIndex % 3;
} else if (endIndex % numberOfValuesPerPoint !== 0) {
console.error("invalid points - %d not divisible by %d", endIndex, numberOfValuesPerPoint);
endIndex -= endIndex % numberOfValuesPerPoint;
}
return points.subarray(0, endIndex);
}
Expand Down Expand Up @@ -144,15 +145,45 @@ export class TrackManager {
export async function loadTrackManager(url: string) {
let trackManager;
try {
// initialize variables
let pathName = "...";
let numValues = 0;

// very suboptimal way of checking whether the zarr store has a path "points" or "points_with_radius":
try {
await openArray({
store: url,
path: "points",
mode: "r",
});
pathName = "points";
numValues = 3;
console.log("succeeded - points loaded");
} catch (error) {
pathName = "points_with_radius";
numValues = 4;
console.log("not succeeded - point_with_radius loaded");
}

// set the global variable 'numberOfValuesPerPoint' to either 3 or 4, dependent on the data
numberOfValuesPerPoint = numValues;

// load the actual points, dependent on "pathName"
const points = await openArray({
store: url,
path: "points",
path: pathName,
mode: "r",
});

const pointsToTracks = await openSparseZarrArray(url, "points_to_tracks", false);
const tracksToPoints = await openSparseZarrArray(url, "tracks_to_points", true);
const tracksToTracks = await openSparseZarrArray(url, "tracks_to_tracks", true);

// make trackManager, and reset "maxPointsPerTimepoint", because tm constructor does points/3
trackManager = new TrackManager(url, points, pointsToTracks, tracksToPoints, tracksToTracks);
if (numberOfValuesPerPoint == 4) {
trackManager.maxPointsPerTimepoint = trackManager.points.shape[1] / numberOfValuesPerPoint;
}
} catch (err) {
console.error("Error opening TrackManager: %s", err);
trackManager = null;
Expand Down
10 changes: 7 additions & 3 deletions src/lib/ViewerState.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export const DEFAULT_ZARR_URL =
"https://sci-imaging-vis-public-demo-data.s3.us-west-2.amazonaws.com" +
"/points-web-viewer/sparse-zarr-v2/ZSNS001_tracks_bundle.zarr";
// export const DEFAULT_ZARR_URL =
// "https://sci-imaging-vis-public-demo-data.s3.us-west-2.amazonaws.com" +
// "/points-web-viewer/sparse-zarr-v2/ZSNS001_tracks_bundle.zarr";

// public Ascidian dataset that contains radius:
export const DEFAULT_ZARR_URL = "https://public.czbiohub.org/royerlab/zoo/Ascidian/tracks_withSize_bundle.zarr/";
// export const DEFAULT_ZARR_URL = "https://public.czbiohub.org/royerlab/zoo/misc/tracks_withSize_3dots_bundle.zarr/";

const HASH_KEY = "viewerState";

Expand Down
9 changes: 9 additions & 0 deletions src/lib/three/Track.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export class Track extends Mesh {
type = "Track";
declare geometry: TrackGeometry;
declare material: TrackMaterial;
pointIds: Int32Array = new Int32Array(0);
startTime: number = -1;
endTime: number = -1;

static new(positions: Float32Array, pointIDs: Int32Array, maxPointsPerTimepoint: number) {
const geometry = new TrackGeometry();
Expand Down Expand Up @@ -53,6 +56,12 @@ export class Track extends Mesh {
track.geometry.setColors(colors);
track.geometry.setTime(time);
track.geometry.computeBoundingSphere();

track.pointIds = pointIDs;
if (time.length > 0) {
track.startTime = time[0];
track.endTime = time[time.length - 1];
}
return track;
}

Expand Down
Loading