Skip to content

Commit

Permalink
feat: Spatial Mode User Story 1 (#805)
Browse files Browse the repository at this point in the history
Co-authored-by: Timmy Huang <[email protected]>
  • Loading branch information
kaloster and tihuan authored Mar 8, 2024
1 parent d2e1ea2 commit df7d42d
Show file tree
Hide file tree
Showing 19 changed files with 620 additions and 157 deletions.
4 changes: 2 additions & 2 deletions client/__tests__/e2e/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ test("pan zoom mode resets lasso selection", async ({ page }) => {
page,
true
);
expect(page.getByTestId("lasso-element")).toBeVisible();
await expect(page.getByTestId("lasso-element")).toBeVisible();

const initialCount = await getCellSetCount(1, page);

Expand Down Expand Up @@ -525,7 +525,7 @@ test("lasso moves after pan", async ({ page }) => {
true
);

expect(page.getByTestId("lasso-element")).toBeVisible();
await expect(page.getByTestId("lasso-element")).toBeVisible();

const initialCount = await getCellSetCount(1, page);

Expand Down
17 changes: 8 additions & 9 deletions client/__tests__/e2e/playwright.global.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,14 @@ const setup = async ({ page }: { page: Page }) => {
},
});
});

// page.on("console", (message) => {
// if (message.type() === "error") {
// throw new Error(`CLIENT SIDE ERROR: ${ message.text()}`);
// }
// });
// page.on("pageerror", (error) => {
// throw new Error(`UNCAUGHT CLIENT ERROR: ${ error}`);
// });
page.on("console", (message) => {
if (message.type() === "error") {
throw new Error(`CLIENT SIDE ERROR: ${JSON.stringify(message)}`);
}
});
page.on("pageerror", (error) => {
throw new Error(`UNCAUGHT CLIENT ERROR: ${error}`);
});
};

export default setup;
44 changes: 41 additions & 3 deletions client/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import * as genesetActions from "./geneset";
import { AppDispatch, GetState } from "../reducers";
import { EmbeddingSchema, Field, Schema } from "../common/types/schema";
import { ConvertedUserColors } from "../reducers/colors";
import type { DatasetMetadata, Dataset, S3URI } from "../common/types/entities";
import type {
DatasetMetadata,
Dataset,
S3URI,
DatasetSpatialMetadata,
} from "../common/types/entities";
import { postExplainNewTab } from "../components/framework/toasters";
import { KEYS } from "../components/util/localStorage";
import {
Expand All @@ -29,7 +34,11 @@ import { packDiffExPdu, DiffExMode, DiffExArguments } from "../util/diffexpdu";
import { track } from "../analytics";
import { EVENTS } from "../analytics/events";
import AnnoMatrix from "../annoMatrix/annoMatrix";
import { checkFeatureFlags } from "../util/featureFlags/featureFlags";
import {
checkFeatureFlags,
getFeatureFlag,
} from "../util/featureFlags/featureFlags";
import { FEATURES } from "../util/featureFlags/features";
import { DATASET_METADATA_RESPONSE } from "../../__tests__/__mocks__/apiMock";

function setGlobalConfig(config: Config) {
Expand Down Expand Up @@ -126,6 +135,31 @@ async function datasetMetadataFetchAndLoad(
});
}

/**
* Fetches and loads dataset spatial metadata.
* @param dispatch - Function facilitating update of store.
* @param oldPrefix - API prefix with dataset path that dataset metadata lives on. (Not S3 URI)
*/
async function datasetSpatialMetadataFetchAndLoad(
dispatch: AppDispatch,
oldPrefix: string
): Promise<void> {
try {
const datasetSpatialMetadataResponse = await fetchJson<{
metadata: DatasetSpatialMetadata;
}>("spatial/meta", oldPrefix);
dispatch({
type: "request spatial metadata success",
data: datasetSpatialMetadataResponse,
});
} catch (error) {
dispatch({
type: "request spatial metadata error",
error,
});
}
}

interface GeneInfoAPI {
ncbi_url: string;
name: string;
Expand Down Expand Up @@ -178,6 +212,7 @@ const doInitialDataLoad = (): ((

// check URL for feature flags
checkFeatureFlags();
const isSpatial = getFeatureFlag(FEATURES.SPATIAL);

try {
const s3URI = await s3URIFetch();
Expand All @@ -190,7 +225,10 @@ const doInitialDataLoad = (): ((
]);

datasetMetadataFetchAndLoad(dispatch, oldPrefix, config);

// TODO: add logic to ensure this is working for spatial datasets when flag removed
if (isSpatial) {
datasetSpatialMetadataFetchAndLoad(dispatch, oldPrefix);
}
const baseDataUrl = `${globals.API.prefix}${globals.API.version}`;
const annoMatrix = new AnnoMatrixLoader(baseDataUrl, schema.schema);
const obsCrossfilter = new AnnoMatrixObsCrossfilter(annoMatrix);
Expand Down
3 changes: 3 additions & 0 deletions client/src/common/types/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export interface DatasetMetadata {
s3_URI: S3URI;
}

// TODO: proper typing after migration and final CXG schema
export type DatasetSpatialMetadata = any;

export type S3URI = string;

/**
Expand Down
25 changes: 15 additions & 10 deletions client/src/components/embedding/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@ import { EVENTS } from "../../analytics/events";
type EmbeddingState = any;

// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
@connect((state) => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
layoutChoice: (state as any).layoutChoice,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
schema: (state as any).annoMatrix?.schema,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
crossfilter: (state as any).obsCrossfilter,
@connect((state: RootState) => ({
layoutChoice: state.layoutChoice,
schema: state.annoMatrix?.schema,
crossfilter: state.obsCrossfilter,
imageUnderlay: state.imageUnderlay,
}))
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
class Embedding extends React.PureComponent<{}, EmbeddingState> {
Expand All @@ -41,14 +39,21 @@ class Embedding extends React.PureComponent<{}, EmbeddingState> {
track(EVENTS.EXPLORER_LAYOUT_CHOICE_BUTTON_CLICKED);
};

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
handleLayoutChoiceChange = (e: any) => {
handleLayoutChoiceChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
const { dispatch } = this.props;
const { dispatch, imageUnderlay } = this.props;

track(EVENTS.EXPLORER_LAYOUT_CHOICE_CHANGE_ITEM_CLICKED);

dispatch(actions.layoutChoiceAction(e.currentTarget.value));
if (
imageUnderlay.isActive &&
e.target.value !== globals.spatialEmbeddingKeyword
) {
dispatch({
type: "toggle image underlay",
});
}
};

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
Expand Down
66 changes: 66 additions & 0 deletions client/src/components/graph/drawSpatialImageRegl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Regl, Texture2D } from "regl";

interface ReglProps {
rectCoords: Float32Array;
spatialImageAsTexture: Texture2D;
projView: number[];
imageWidth: number;
imageHeight: number;
count: number;
}

export default function drawSpatialImageRegl(regl: Regl) {
return regl({
frag: `
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_image, v_texCoord);
}`,

vert: `
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform vec2 u_resolution;
uniform mat3 projView;
varying vec2 v_texCoord;
void main() {
// convert the rectangle from pixels to 0.0 to 1.0
vec3 pos = vec3(a_position, 1.);
vec2 zeroToOne = pos.xy / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
vec3 pos2 = projView * vec3(clipSpace, 1.);
gl_Position = vec4(pos2.xy , 0, 1);
// pass the texCoord to the fragment shader
// The GPU will interpolate this value between points.
v_texCoord = a_texCoord;
}`,

attributes: {
a_texCoord: [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0],
a_position: regl.prop<ReglProps, "rectCoords">("rectCoords"),
},

uniforms: {
projView: regl.prop<ReglProps, "projView">("projView"),
u_image: regl.prop<ReglProps, "spatialImageAsTexture">(
"spatialImageAsTexture"
),
color: [1, 0, 0, 1],
u_resolution: [
regl.prop<ReglProps, "imageWidth">("imageWidth"),
regl.prop<ReglProps, "imageHeight">("imageHeight"),
],
image_width: regl.prop<ReglProps, "imageWidth">("imageWidth"),
},
// This tells regl the number of vertices to draw in this command
// https://github.com/regl-project/regl
count: 6,
});
}
Loading

0 comments on commit df7d42d

Please sign in to comment.