Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Draw motion vectors #469

Merged
merged 41 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
444b052
feat: Helper method for calculating motion vectors, smoothed over time
ShrimpCryptid Nov 1, 2024
9eb1a31
feat: Added vector config type
ShrimpCryptid Nov 1, 2024
877574f
Merge branch 'main' into feature/motion-vectors
ShrimpCryptid Nov 1, 2024
2593e19
feat: Atempted to draw motion vectors as an overlay element
ShrimpCryptid Nov 2, 2024
256d0e8
feat: Fixed motion deltas matching pan and scale
ShrimpCryptid Nov 4, 2024
0b9f5de
refactor: Added caching for motion delta calculation
ShrimpCryptid Nov 4, 2024
20618c7
feat: Draw arrowheads
ShrimpCryptid Nov 4, 2024
7a68782
feat: Added settings controls for smoothing, min timesteps, and length
ShrimpCryptid Nov 5, 2024
27a3f04
fix: Vector position when header is present
ShrimpCryptid Nov 5, 2024
db36f0d
feat: Added worker task for calculating vector fields
ShrimpCryptid Nov 5, 2024
3c77486
feat: Added optional tooltip for settings
ShrimpCryptid Nov 5, 2024
40bab34
refactor: Moved motion delta calculation to worker thread
ShrimpCryptid Nov 5, 2024
397ece4
fix: Cached ID at time lookup for faster performance
ShrimpCryptid Nov 5, 2024
6dcde8c
feature: Added checkbox for showing vectors
ShrimpCryptid Nov 5, 2024
9f9e6fe
fix: Linting
ShrimpCryptid Nov 5, 2024
98f1474
refactor: Moved motion delta calculation out of `data_utils` due to b…
ShrimpCryptid Nov 5, 2024
e87ed83
refactor: Moved vector settings to a separate component
ShrimpCryptid Nov 5, 2024
df8d36d
refactor: Nest settings under motion vector
ShrimpCryptid Nov 6, 2024
6c77ef1
refactor: Add padding in settings area
ShrimpCryptid Nov 6, 2024
09612a1
feat: Render lines using Three.js
ShrimpCryptid Nov 7, 2024
aaaea3a
feat: Add arrow heads
ShrimpCryptid Nov 8, 2024
2a86ef2
feat: WebGL arrow heads are now constant with zoom
ShrimpCryptid Nov 8, 2024
ef7120a
feat: Vector arrows now have constant onscreen size, documentation
ShrimpCryptid Nov 8, 2024
7fb50e3
refactor: Code cleanup, documentation
ShrimpCryptid Nov 8, 2024
cca9dba
refactor: Deleted canvas vector renderer, renamed VectorField
ShrimpCryptid Nov 8, 2024
5babcff
refactor: Code cleanup cont.
ShrimpCryptid Nov 8, 2024
de51274
refactor: Reduced repeated delta calculations in `math_utils.ts`
ShrimpCryptid Nov 9, 2024
7dcbe15
feat: Formatting in vector view settings
ShrimpCryptid Nov 9, 2024
afaaec8
refactor: Simplified vector settings area, renamed some settings for …
ShrimpCryptid Nov 11, 2024
6056d74
Merge branch 'main' into feature/motion-vectors
ShrimpCryptid Nov 11, 2024
a648962
refactor: Code cleanup
ShrimpCryptid Nov 11, 2024
371f191
fix: Linting
ShrimpCryptid Nov 11, 2024
5b5e1c9
refactor: Code cleanup
ShrimpCryptid Nov 11, 2024
4083579
refactor: Removed thresholding for time intervals
ShrimpCryptid Nov 11, 2024
532d657
fix: Fixed bug when switching datasets where arrows would be in the i…
ShrimpCryptid Nov 11, 2024
64f7bc5
refactor: Code cleanup, comments
ShrimpCryptid Nov 12, 2024
73ad395
refactor: Worker exits early when `numTimeIntervals` is too small
ShrimpCryptid Nov 18, 2024
0778014
refactor: Moved vector rendering logic into helper methods
ShrimpCryptid Nov 18, 2024
09986b7
refactor: Moved settings checkboxes to left
ShrimpCryptid Nov 18, 2024
1b53fef
refactor: Renamed arrow heads to refer to left/right
ShrimpCryptid Nov 20, 2024
77d9c2c
refactor: Created helper methods for scaling, adding array vectors
ShrimpCryptid Nov 20, 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
5 changes: 4 additions & 1 deletion src/Viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
import { AnalyticsEvent, triggerAnalyticsEvent } from "./colorizer/utils/analytics";
import { getColorMap, getInRangeLUT, thresholdMatchFinder, validateThresholds } from "./colorizer/utils/data_utils";
import { numberToStringDecimal } from "./colorizer/utils/math_utils";
import { useConstructor, useDebounce, useRecentCollections } from "./colorizer/utils/react_utils";
import { useConstructor, useDebounce, useMotionDeltas, useRecentCollections } from "./colorizer/utils/react_utils";
import * as urlUtils from "./colorizer/utils/url_utils";
import { SCATTERPLOT_TIME_FEATURE } from "./components/Tabs/scatter_plot_data_utils";
import { DEFAULT_PLAYBACK_FPS } from "./constants";
Expand Down Expand Up @@ -107,6 +107,8 @@ function Viewer(): ReactElement {
getDefaultScatterPlotConfig()
);

const motionDeltas = useMotionDeltas(dataset, workerPool, config.vectorConfig);

const [isInitialDatasetLoaded, setIsInitialDatasetLoaded] = useState(false);
const [isDatasetLoading, setIsDatasetLoading] = useState(false);
const [datasetLoadProgress, setDatasetLoadProgress] = useState<number | null>(null);
Expand Down Expand Up @@ -1017,6 +1019,7 @@ function Viewer(): ReactElement {
loadingProgress={datasetLoadProgress}
canv={canv}
collection={collection || null}
vectorData={motionDeltas}
dataset={dataset}
datasetKey={datasetKey}
featureKey={featureKey}
Expand Down
28 changes: 24 additions & 4 deletions src/colorizer/ColorizeCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ import {
OUT_OF_RANGE_COLOR_DEFAULT,
OUTLIER_COLOR_DEFAULT,
OUTLINE_COLOR_DEFAULT,
VectorConfig,
} from "./types";
import { packDataTexture } from "./utils/texture_utils";

import ColorRamp from "./ColorRamp";
import Dataset from "./Dataset";
import Track from "./Track";
import VectorField from "./VectorField";

import pickFragmentShader from "./shaders/cellId_RGBA8U.frag";
import vertexShader from "./shaders/colorize.vert";
Expand Down Expand Up @@ -122,12 +124,14 @@ export default class ColorizeCanvas {
private mesh: Mesh;
private pickMesh: Mesh;

private vectorField: VectorField;
// Rendered track line that shows the trajectory of a cell.
private line: Line;
private points: Float32Array;
private showTrackPath: boolean;

protected frameSizeInCanvasCoordinates: Vector2;
private frameToCanvasCoordinates: Vector2;
protected frameToCanvasCoordinates: Vector2;

/**
* The zoom level of the frame in the canvas. At default zoom level 1, the frame will be
Expand All @@ -150,7 +154,6 @@ export default class ColorizeCanvas {

protected dataset: Dataset | null;
protected track: Track | null;
private points: Float32Array;
protected canvasResolution: Vector2 | null;

protected featureKey: string | null;
Expand Down Expand Up @@ -187,10 +190,14 @@ export default class ColorizeCanvas {
this.camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
this.scene = new Scene();
this.scene.add(this.mesh);

this.vectorField = new VectorField();
this.scene.add(this.vectorField.sceneObject);

this.pickScene = new Scene();
this.pickScene.add(this.pickMesh);

// Configure lines
// Configure track lines
this.points = new Float32Array([0, 0, 0]);

const lineGeometry = new BufferGeometry();
Expand All @@ -210,7 +217,7 @@ export default class ColorizeCanvas {
this.pickRenderTarget = new WebGLRenderTarget(1, 1, {
depthBuffer: false,
});
this.renderer = new WebGLRenderer();
this.renderer = new WebGLRenderer({ antialias: true });
this.checkPixelRatio();

this.dataset = null;
Expand Down Expand Up @@ -286,6 +293,7 @@ export default class ColorizeCanvas {
2 * this.panOffset.y * this.frameToCanvasCoordinates.y,
0
);
this.vectorField.setPosition(this.panOffset, this.frameToCanvasCoordinates);
this.render();
}

Expand Down Expand Up @@ -329,6 +337,8 @@ export default class ColorizeCanvas {
2 * this.panOffset.y * this.frameToCanvasCoordinates.y,
0
);
this.vectorField.setPosition(this.panOffset, this.frameToCanvasCoordinates);
this.vectorField.setScale(this.frameToCanvasCoordinates, this.canvasResolution || new Vector2(1, 1));
}

public async setDataset(dataset: Dataset): Promise<void> {
Expand All @@ -346,6 +356,7 @@ export default class ColorizeCanvas {
this.currentFrame = -1;
await this.setFrame(frame);
this.updateScaling(this.dataset.frameResolution, this.canvasResolution);
this.vectorField.setDataset(dataset);
this.render();
}

Expand Down Expand Up @@ -404,6 +415,14 @@ export default class ColorizeCanvas {
}
}

setVectorData(vectorData: Float32Array | null): void {
this.vectorField.setVectorData(vectorData);
}

setVectorFieldConfig(config: VectorConfig): void {
this.vectorField.setConfig(config);
}

setSelectedTrack(track: Track | null): void {
if (this.track && this.track?.trackId === track?.trackId) {
return;
Expand Down Expand Up @@ -627,6 +646,7 @@ export default class ColorizeCanvas {
this.onFrameChangeCallback(isMissingFile);
// Force rescale in case frame dimensions changed
this.updateScaling(this.dataset?.frameResolution || null, this.canvasResolution);
this.vectorField.setFrame(this.currentFrame);
}

/** Switches the coloring between the categorical and color ramps depending on the currently
Expand Down
13 changes: 13 additions & 0 deletions src/colorizer/Dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,19 @@ export default class Dataset {
return this.trackIds?.[index] || 0;
}

/**
* Returns the 2D centroid of a given object id.
*/
public getCentroid(objectId: number): [number, number] | undefined {
const index = objectId * 2;
const x = this.centroids?.[index];
const y = this.centroids?.[index + 1];
if (x && y) {
return [x, y];
}
return undefined;
}

private getIdsOfTrack(trackId: number): number[] {
return this.trackIds?.reduce((arr: number[], elem: number, ind: number) => {
if (elem === trackId) arr.push(ind);
Expand Down
16 changes: 10 additions & 6 deletions src/colorizer/Track.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
export default class Track {
public trackId: number;
/** Times, in ascending order. */
public times: number[];
/** Object IDs in the track, in ascending order of time. */
public ids: number[];
/** Centroids in the track, in ascending order of time. */
public centroids: number[];
/** Bounds in the track, in ascending order of time. */
public bounds: number[];

constructor(trackId: number, times: number[], ids: number[], centroids: number[], bounds: number[]) {
Expand All @@ -19,17 +23,17 @@ export default class Track {
indices.sort((a, b) => (times[a] < times[b] ? -1 : times[a] === times[b] ? 0 : 1));
this.times = indices.map((i) => times[i]);
this.ids = indices.map((i) => ids[i]);

this.centroids = indices.reduce((result, i) => {
result.push(centroids[i * 2], centroids[i * 2 + 1]);
return result;
}, [] as number[]);

this.bounds = indices.reduce((result, i) => {
result.push(bounds[i * 4], bounds[i * 4 + 1], bounds[i * 4 + 2], bounds[i * 4 + 3]);
return result;
}, [] as number[]);
}
console.log(
`Track ${trackId} has ${this.times.length} objects over ${this.duration()} timepoints starting from ${
this.times[0]
} to ${this.times[this.times.length - 1]}`
);
console.log(this.ids);
}

getIdAtTime(t: number): number {
Expand Down
Loading
Loading