From b89c7bbddf2f2a99038398873aee3110388160d3 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Mon, 14 Mar 2022 17:58:13 +0100 Subject: [PATCH 01/12] Fix authentication provider Co-authored-by: James Collier --- client/src/api/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/api/auth.ts b/client/src/api/auth.ts index 85721a35..c6b5d0e9 100644 --- a/client/src/api/auth.ts +++ b/client/src/api/auth.ts @@ -95,7 +95,7 @@ const decodeAuthTokenResponse = ( 'access_token' in response && 'token_type' in response && typeof (response as { access_token: unknown }).access_token === - 'string' && + 'string' && typeof (response as { token_type: unknown }).token_type === 'string' ) { if ('user' in response) { @@ -177,7 +177,7 @@ const decodeProvider = (data: unknown): Result => { 'url' in data && typeof (data as { id: unknown }).id === 'number' && typeof (data as { name: unknown }).name === 'string' && - typeof (data as { icon: unknown }).icon === 'string' && + (typeof (data as { icon: unknown }).icon === 'string' || (data as { icon: unknown }).icon === null) && typeof (data as { url: unknown }).url === 'string' ) { const provider: Provider = { From 04ca60288619fc699debd0d620fcd1637091d3ab Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Mon, 14 Mar 2022 17:58:54 +0100 Subject: [PATCH 02/12] Add backend schema for coords --- server/scopeserver/api/v1/__init__.py | 3 ++- server/scopeserver/api/v1/data.py | 37 +++++++++++++++++++++++++++ server/scopeserver/schemas.py | 12 +++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 server/scopeserver/api/v1/data.py diff --git a/server/scopeserver/api/v1/__init__.py b/server/scopeserver/api/v1/__init__.py index e4669b72..3c0d81a8 100644 --- a/server/scopeserver/api/v1/__init__.py +++ b/server/scopeserver/api/v1/__init__.py @@ -2,10 +2,11 @@ from fastapi import APIRouter -from scopeserver.api.v1 import auth, projects, users, legacy +from scopeserver.api.v1 import auth, projects, users, legacy, data api_v1_router = APIRouter() api_v1_router.include_router(projects.router, prefix="/project", tags=["projects"]) api_v1_router.include_router(users.router, prefix="/user", tags=["users"]) api_v1_router.include_router(auth.router, prefix="/auth", tags=["auth"]) api_v1_router.include_router(legacy.router, prefix="/legacy", tags=["legacy"]) +api_v1_router.include_router(data.router, prefix="/data") diff --git a/server/scopeserver/api/v1/data.py b/server/scopeserver/api/v1/data.py new file mode 100644 index 00000000..473a91c1 --- /dev/null +++ b/server/scopeserver/api/v1/data.py @@ -0,0 +1,37 @@ +" TODO: THIS IS A TEMPORARY HACK JOB " + +from typing import List +from pathlib import Path +import shutil + +from fastapi import APIRouter, Depends, File, HTTPException, Response, UploadFile, status +from sqlalchemy.orm import Session + +import loompy as lp + +from scopeserver import crud, models, schemas +from scopeserver.api import deps +from scopeserver.config import settings + +router = APIRouter() + + +@router.get("/dataset", response_model=List[schemas.Coordinate]) +def get_dataset( + *, + database: Session = Depends(deps.get_db), + dataset: int, +): + entry = crud.get_dataset(database, dataset) + + with lp.connect( + settings.DATA_PATH / "c9ef0a31-a7cd-4d04-a73f-2575babf7e30" / Path(entry.filename), validate=False + ) as ds: + print(ds.ca.Embeddings_X[0][0]) + x = [_x[0] for _x in ds.ca.Embeddings_X] + y = [_y[0] for _y in ds.ca.Embeddings_Y] + print(x[:5]) + print(y[:5]) + print([(X, Y) for X, Y in zip(x, y)][:5]) + + return [schemas.Coordinate(x=X, y=Y) for X, Y in zip(x, y)] diff --git a/server/scopeserver/schemas.py b/server/scopeserver/schemas.py index 395679cb..6f33cb97 100644 --- a/server/scopeserver/schemas.py +++ b/server/scopeserver/schemas.py @@ -23,6 +23,18 @@ class Config: orm_mode = True +class DatasetHack(Dataset): + project: "Project" + + class Config: + orm_mode = True + + +class Coordinate(BaseModel): + x: float + y: float + + # Users and Projects class UserBase(BaseModel): name: Optional[str] From 5867dc04081210693620cc9f45f7727376c265c7 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Mon, 14 Mar 2022 17:59:44 +0100 Subject: [PATCH 03/12] Add frontend functions and data store for coords --- client/src/api/project.ts | 20 ++++++++++++++++++++ client/src/redux/actionTypes.ts | 4 ++++ client/src/redux/actions.ts | 15 ++++++++++++++- client/src/redux/reducers/main.ts | 5 +++++ client/src/redux/sagas/index.ts | 1 + client/src/redux/sagas/scope/index.ts | 9 +++++++++ client/src/redux/types.ts | 23 +++++++++++++++++++++-- 7 files changed, 74 insertions(+), 3 deletions(-) diff --git a/client/src/api/project.ts b/client/src/api/project.ts index 8a2ec8e5..633cbad2 100644 --- a/client/src/api/project.ts +++ b/client/src/api/project.ts @@ -185,3 +185,23 @@ export async function makeProject( return error(`Cannot create project: ${JSON.stringify(err)}`); } } + +export type Coordinate = { + x: number; + y: number; +}; + +export async function getCoordinates( + dataset: string, +): Promise> { + const url = new URL(API_URL + 'data/dataset'); + url.search = new URLSearchParams({ dataset }).toString(); + const response = await fetch(url.toString(), { + method: 'GET', + headers: { + Accept: 'application/json', + }, + }); + + return await response.json() as Array; +} \ No newline at end of file diff --git a/client/src/redux/actionTypes.ts b/client/src/redux/actionTypes.ts index 60dd3c08..7ebd626d 100644 --- a/client/src/redux/actionTypes.ts +++ b/client/src/redux/actionTypes.ts @@ -21,3 +21,7 @@ export const UPLOAD_PROGRESS = 'UPLOAD_PROGRESS'; export const UPLOAD_SUCCESS = 'UPLOAD_SUCCESS'; export const MODIFIER_KEY_TOGGLE = 'MODIFIER_KEY_TOGGLE'; + +export const GET_COORDINATES = 'GET_COORDINATES'; + +export const RECEIVED_COORDINATES = 'RECEIVED_COORDINATES'; \ No newline at end of file diff --git a/client/src/redux/actions.ts b/client/src/redux/actions.ts index b1617fc4..ab80acae 100644 --- a/client/src/redux/actions.ts +++ b/client/src/redux/actions.ts @@ -1,4 +1,4 @@ -import { Project, DataSet } from '../api'; +import { Project, DataSet, Coordinate } from '../api'; import * as AT from './actionTypes'; import { @@ -10,6 +10,8 @@ import { AddDataSetAction, ModifierKey, ToggleModifierKey, + GetCoordinates, + ReceivedCoordinates, } from './types'; export const setAppLoading = (isAppLoading: MainState['isAppLoading']) => ({ @@ -74,3 +76,14 @@ export const toggleModifierKey = (key: ModifierKey): ToggleModifierKey => ({ type: AT.MODIFIER_KEY_TOGGLE, payload: { key }, }); + +export const getCoordinates = (dataset: string): GetCoordinates => ({ + type: AT.GET_COORDINATES, + payload: { dataset }, +}) + + +export const receivedCoordinates = (coords: Array): ReceivedCoordinates => ({ + type: AT.RECEIVED_COORDINATES, + payload: { coordinates: coords }, +}) \ No newline at end of file diff --git a/client/src/redux/reducers/main.ts b/client/src/redux/reducers/main.ts index 8bec9ed1..0eddc9e9 100644 --- a/client/src/redux/reducers/main.ts +++ b/client/src/redux/reducers/main.ts @@ -9,6 +9,7 @@ const initialState: MainState = { sessionMode: SESSION_READ, projects: [], datasets: [], + coords: [], upload: { state: 'none', progress: 0, @@ -59,6 +60,10 @@ const main = produce((draft: MainState, action: MainAction) => { ? 'None' : action.payload.key; break; + + case Action.RECEIVED_COORDINATES: + draft.coords = action.payload.coordinates; + break; } }, initialState); diff --git a/client/src/redux/sagas/index.ts b/client/src/redux/sagas/index.ts index 32cda90a..0d51e65f 100644 --- a/client/src/redux/sagas/index.ts +++ b/client/src/redux/sagas/index.ts @@ -13,5 +13,6 @@ export default function* rootSaga() { SCOPE.watchPermalinkRequests(), SCOPE.watchCreateNewProject(), SCOPE.watchUploadRequest(), + SCOPE.watchGetCoordinates(), ]); } diff --git a/client/src/redux/sagas/scope/index.ts b/client/src/redux/sagas/scope/index.ts index c8cf6e34..2b53c983 100644 --- a/client/src/redux/sagas/scope/index.ts +++ b/client/src/redux/sagas/scope/index.ts @@ -93,6 +93,15 @@ export function* watchUploadRequest() { yield takeEvery(AT.UPLOAD_REQUEST, uploadRequest); } +function* requestCoords(action: T.GetCoordinates) { + const coords = yield call(API.getCoordinates, action.payload.dataset); + yield put(A.receivedCoordinates(coords)); +} + +export function* watchGetCoordinates() { + yield takeEvery(AT.GET_COORDINATES, requestCoords); +} + export * from '../../../components/Search/effects'; export { watchGuestLogin, diff --git a/client/src/redux/types.ts b/client/src/redux/types.ts index 10ec5906..73f83dfa 100644 --- a/client/src/redux/types.ts +++ b/client/src/redux/types.ts @@ -1,4 +1,4 @@ -import { Project, DataSet } from '../api'; +import { Project, DataSet, Coordinate } from '../api'; import * as AT from './actionTypes'; @@ -17,6 +17,7 @@ export interface MainState { sessionMode: SessionMode; projects: Array; datasets: Array; + coords: Array; upload: { state: UploadState; progress: number; @@ -106,6 +107,22 @@ export interface ToggleModifierKey { }; } +export interface GetCoordinates { + type: typeof AT.GET_COORDINATES; + payload: { + dataset: string; + } +} + + +export interface ReceivedCoordinates { + type: typeof AT.RECEIVED_COORDINATES; + payload: { + coordinates: Array; + } +} + + export type MainAction = | SetLoadingAction | SetUUIDAction @@ -118,4 +135,6 @@ export type MainAction = | UploadRequest | UploadProgress | UploadSuccess - | ToggleModifierKey; + | ToggleModifierKey + | GetCoordinates + | ReceivedCoordinates; From 71d71bbdcd836b3d6200401a7f5cbf1550cc08a1 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Mon, 14 Mar 2022 18:00:24 +0100 Subject: [PATCH 04/12] Preliminary viewer implementation (broken - loop) --- client/package-lock.json | 13 +- client/package.json | 22 ++- client/src/components/Viewer/Viewer.tsx | 161 ++++++++++++++++++ .../src/components/Viewer/ViewerWrapper.tsx | 4 +- 4 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 client/src/components/Viewer/Viewer.tsx diff --git a/client/package-lock.json b/client/package-lock.json index 493be3b5..4556b1a0 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -33,7 +33,8 @@ "react-table": "^6.8.6", "redux": "^4.0.5", "redux-saga": "^1.1.3", - "semantic-ui-react": "^2.0.1" + "semantic-ui-react": "^2.0.1", + "three": "^0.138.3" }, "devDependencies": { "@babel/cli": "^7.12.8", @@ -13860,6 +13861,11 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/three": { + "version": "0.138.3", + "resolved": "https://registry.npmjs.org/three/-/three-0.138.3.tgz", + "integrity": "sha512-4t1cKC8gimNyJChJbaklg8W/qj3PpsLJUIFm5LIuAy/hVxxNm1ru2FGTSfbTSsuHmC/7ipsyuGKqrSAKLNtkzg==" + }, "node_modules/throat": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", @@ -25815,6 +25821,11 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "three": { + "version": "0.138.3", + "resolved": "https://registry.npmjs.org/three/-/three-0.138.3.tgz", + "integrity": "sha512-4t1cKC8gimNyJChJbaklg8W/qj3PpsLJUIFm5LIuAy/hVxxNm1ru2FGTSfbTSsuHmC/7ipsyuGKqrSAKLNtkzg==" + }, "throat": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", diff --git a/client/package.json b/client/package.json index 6029878c..59dfe5b9 100644 --- a/client/package.json +++ b/client/package.json @@ -52,7 +52,8 @@ "react-table": "^6.8.6", "redux": "^4.0.5", "redux-saga": "^1.1.3", - "semantic-ui-react": "^2.0.1" + "semantic-ui-react": "^2.0.1", + "three": "^0.138.3" }, "devDependencies": { "@babel/cli": "^7.12.8", @@ -152,7 +153,10 @@ "ecmaVersion": 2018, "sourceType": "module" }, - "plugins": ["react", "@typescript-eslint"], + "plugins": [ + "react", + "@typescript-eslint" + ], "rules": { "prettier/prettier": "error", "no-unused-vars": [ @@ -170,9 +174,17 @@ "no-var": "warn", "react/prop-types": "off", "react/jsx-key": "off", - "eqeqeq": ["error", "always"], - "curly": ["error", "all"], - "no-bitwise": ["error"] + "eqeqeq": [ + "error", + "always" + ], + "curly": [ + "error", + "all" + ], + "no-bitwise": [ + "error" + ] }, "settings": { "react": { diff --git a/client/src/components/Viewer/Viewer.tsx b/client/src/components/Viewer/Viewer.tsx new file mode 100644 index 00000000..2c47dd42 --- /dev/null +++ b/client/src/components/Viewer/Viewer.tsx @@ -0,0 +1,161 @@ +import React, { useEffect, useRef } from 'react'; +import * as THREE from 'three'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; +import { useDispatch, useSelector } from 'react-redux'; +import { Coordinate } from '../../api'; +import { RootState } from '../../redux/reducers'; +import { getCoordinates } from '../../redux/actions'; +import { render } from '@testing-library/react'; +import * as R from 'ramda' + +export type ViewerProps = { + dataset: number; +}; + +const initGraphics = () => { + const scene = new THREE.Scene(); + const camera = new THREE.OrthographicCamera(-250, 250, -250, 250) + camera.position.set(0, 0, 2000) + camera.lookAt(new THREE.Vector3(0, 0, 0)) + const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: true }); + renderer.setSize(500, 500); + const texture = new THREE.TextureLoader().load('src/images/dot.png'); + const geometry = new THREE.BufferGeometry(); + const material = new THREE.PointsMaterial({ + size: 5, + vertexColors: true, + map: texture, + transparent: true, + depthWrite: false, + blending: THREE.NoBlending, + }); + const controls = new OrbitControls( + camera, + renderer.domElement + ); + controls.mouseButtons = { + LEFT: THREE.MOUSE.PAN, + MIDDLE: null, + RIGHT: null, + }; + controls.enableRotate = false; + + const result = { + renderer, + scene, + camera, + geometry, + }; + + controls.addEventListener('change', () => renderScene(result)); + + return result; +}; + +const renderScene = ({ renderer, scene, camera }) => { + renderer.render(scene, camera); +} + + +const intitializeDataPoints = (scene, camera, geometry, coords) => { + const x = [] as any; + const y = [] as any; + const positions = [] as any; + const colors = [] as any; + + coords.forEach((coord: { x: number; y: number; }) => { + x.push(coord.x); + y.push(coord.y); + positions.push(coord.x, coord.y, 0); + colors.push(0, 0, 0); + + }) + + scene.clear(); + + const sorted_x = R.sort(x) + const sorted_y = R.sort(y) + + const xMax = sorted_x[sorted_x.length - 1] + const xMin = sorted_x[0] + const yMax = sorted_y[sorted_y.length - 1] + const yMin = sorted_y[0] + + const xCenter = (xMax - xMin) / 2 + const yCenter = (yMax - yMin) / 2 + + const maxDiff = + Math.max(Math.abs(xMax - xCenter), Math.abs(yMax - yCenter)) * 1.5; + const aspectRatio = 1 + camera.left = xCenter - maxDiff; + camera.right = xCenter + maxDiff; + camera.top = yCenter + maxDiff / aspectRatio; + camera.bottom = yCenter - maxDiff / aspectRatio; + camera.updateProjectionMatrix(); + + geometry.setAttribute( + 'position', + new THREE.Float32BufferAttribute(positions, 3) + ); + geometry.setAttribute( + 'color', + new THREE.Float32BufferAttribute(colors, 3) + ); + + // const points = new THREE.Points(this.geometry, this.material); + const points = new THREE.Points(geometry); + scene.add(points); +} + +export const Viewer: React.FC = (props: ViewerProps) => { + const dispatch = useDispatch(); + const coords = useSelector>((root: RootState) => { + return root.main.coords; + }); + const mount = useRef(null!) + const [count, setCount] = React.useState(0) + const [rendererState, setRenderer] = React.useState() + const [sceneState, setScene] = React.useState() + const [cameraState, setCamera] = React.useState() + const [geometryState, setGeometry] = React.useState() + + const requestRef = React.useRef() as React.MutableRefObject; + const previousTimeRef = React.useRef(); + + const animate = time => { + if (previousTimeRef.current != undefined) { + const deltaTime = time - previousTimeRef.current; + + // Pass on a function to the setter of the state + // to make sure we always have the latest state + setCount(prevCount => (prevCount + deltaTime * 0.01) % 100); + renderScene({ renderer: rendererState, scene: sceneState, camera: cameraState }); + } + previousTimeRef.current = time; + requestRef.current = requestAnimationFrame(animate); + } + + useEffect(() => { + if (coords.length === 0) { + dispatch(getCoordinates(`${props.dataset}`)); + + const { + renderer, + scene, + camera, + geometry, + } = initGraphics(); + setRenderer(renderer); + setScene(scene); + setCamera(camera); + setGeometry(geometry); + mount.current.appendChild(renderer.domElement); + } + requestRef.current = requestAnimationFrame(animate); + return () => cancelAnimationFrame(requestRef.current); + }) + + return (
) +} \ No newline at end of file diff --git a/client/src/components/Viewer/ViewerWrapper.tsx b/client/src/components/Viewer/ViewerWrapper.tsx index 748cfd6a..a1b5a3ba 100644 --- a/client/src/components/Viewer/ViewerWrapper.tsx +++ b/client/src/components/Viewer/ViewerWrapper.tsx @@ -9,6 +9,7 @@ import { RootState } from '../../redux/reducers'; import { ViewerId, ViewerInfo, ViewerMap } from './model'; import * as Select from './selectors'; import * as Action from './actions'; +import { Viewer } from './Viewer'; const Placeholder: React.FC = (props: ViewerInfo) => { return ( @@ -68,9 +69,8 @@ export const ViewerWrapper: React.FC<{}> = () => { return
Empty
; } else { return ( - ); } From 62131ba10839064e3694443201cfc5fd24961031 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 15 Mar 2022 12:48:48 +0100 Subject: [PATCH 05/12] Working viewer --- client/src/components/Viewer/Viewer.tsx | 75 +++++++++++++++---------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/client/src/components/Viewer/Viewer.tsx b/client/src/components/Viewer/Viewer.tsx index 2c47dd42..cf6854f6 100644 --- a/client/src/components/Viewer/Viewer.tsx +++ b/client/src/components/Viewer/Viewer.tsx @@ -14,11 +14,14 @@ export type ViewerProps = { const initGraphics = () => { const scene = new THREE.Scene(); - const camera = new THREE.OrthographicCamera(-250, 250, -250, 250) + // scene.background = new THREE.Color(0x00ff00); + const height = 1000 + const width = 1000 + const camera = new THREE.OrthographicCamera(width / - 2, width / 2, height / 2, height / - 2) camera.position.set(0, 0, 2000) camera.lookAt(new THREE.Vector3(0, 0, 0)) const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: true }); - renderer.setSize(500, 500); + renderer.setSize(width, height); const texture = new THREE.TextureLoader().load('src/images/dot.png'); const geometry = new THREE.BufferGeometry(); const material = new THREE.PointsMaterial({ @@ -45,6 +48,7 @@ const initGraphics = () => { scene, camera, geometry, + material }; controls.addEventListener('change', () => renderScene(result)); @@ -53,11 +57,13 @@ const initGraphics = () => { }; const renderScene = ({ renderer, scene, camera }) => { - renderer.render(scene, camera); + if (typeof renderer != 'undefined') { + renderer.render(scene, camera); + } } -const intitializeDataPoints = (scene, camera, geometry, coords) => { +const intitializeDataPoints = (scene, camera, geometry, material, coords) => { const x = [] as any; const y = [] as any; const positions = [] as any; @@ -67,22 +73,24 @@ const intitializeDataPoints = (scene, camera, geometry, coords) => { x.push(coord.x); y.push(coord.y); positions.push(coord.x, coord.y, 0); - colors.push(0, 0, 0); + colors.push(255, 0, 0); }) + console.log('Initializing data points...' + colors.length + ' ' + positions.length); scene.clear(); - const sorted_x = R.sort(x) - const sorted_y = R.sort(y) + const diff = function (a: number, b: number) { return a - b; }; + const sorted_x = R.sort(diff, x) + const sorted_y = R.sort(diff, y) const xMax = sorted_x[sorted_x.length - 1] const xMin = sorted_x[0] const yMax = sorted_y[sorted_y.length - 1] const yMin = sorted_y[0] - const xCenter = (xMax - xMin) / 2 - const yCenter = (yMax - yMin) / 2 + const xCenter = (xMax + xMin) / 2 + const yCenter = (yMax + yMin) / 2 const maxDiff = Math.max(Math.abs(xMax - xCenter), Math.abs(yMax - yCenter)) * 1.5; @@ -91,6 +99,7 @@ const intitializeDataPoints = (scene, camera, geometry, coords) => { camera.right = xCenter + maxDiff; camera.top = yCenter + maxDiff / aspectRatio; camera.bottom = yCenter - maxDiff / aspectRatio; + camera.updateProjectionMatrix(); geometry.setAttribute( @@ -102,8 +111,8 @@ const intitializeDataPoints = (scene, camera, geometry, coords) => { new THREE.Float32BufferAttribute(colors, 3) ); - // const points = new THREE.Points(this.geometry, this.material); - const points = new THREE.Points(geometry); + const points = new THREE.Points(geometry, material); + // const points = new THREE.Points(geometry); scene.add(points); } @@ -113,22 +122,18 @@ export const Viewer: React.FC = (props: ViewerProps) => { return root.main.coords; }); const mount = useRef(null!) - const [count, setCount] = React.useState(0) const [rendererState, setRenderer] = React.useState() + const [mounted, setMounted] = React.useState(false) const [sceneState, setScene] = React.useState() const [cameraState, setCamera] = React.useState() const [geometryState, setGeometry] = React.useState() + const [materialState, setMaterial] = React.useState() const requestRef = React.useRef() as React.MutableRefObject; const previousTimeRef = React.useRef(); const animate = time => { if (previousTimeRef.current != undefined) { - const deltaTime = time - previousTimeRef.current; - - // Pass on a function to the setter of the state - // to make sure we always have the latest state - setCount(prevCount => (prevCount + deltaTime * 0.01) % 100); renderScene({ renderer: rendererState, scene: sceneState, camera: cameraState }); } previousTimeRef.current = time; @@ -136,24 +141,34 @@ export const Viewer: React.FC = (props: ViewerProps) => { } useEffect(() => { + const { + renderer, + scene, + camera, + geometry, + material + } = initGraphics(); + setRenderer(renderer); + setScene(scene); + setCamera(camera); + setGeometry(geometry); + setMaterial(material); + + if (!mounted) { + mount.current.appendChild(renderer.domElement); + setMounted(true); + } + if (coords.length === 0) { dispatch(getCoordinates(`${props.dataset}`)); - - const { - renderer, - scene, - camera, - geometry, - } = initGraphics(); - setRenderer(renderer); - setScene(scene); - setCamera(camera); - setGeometry(geometry); - mount.current.appendChild(renderer.domElement); } + if (sceneState && cameraState && geometryState) { + intitializeDataPoints(sceneState, cameraState, geometryState, materialState, coords); + } + requestRef.current = requestAnimationFrame(animate); return () => cancelAnimationFrame(requestRef.current); - }) + }, [coords]); return (
Date: Tue, 15 Mar 2022 14:23:55 +0100 Subject: [PATCH 06/12] Add typing, use shaders --- client/package-lock.json | 16 +++- client/package.json | 1 + client/src/components/Viewer/Viewer.tsx | 101 +++++++++++++++++++----- 3 files changed, 98 insertions(+), 20 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 4556b1a0..0f1ad5eb 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -55,6 +55,7 @@ "@types/react": "^16.14.2", "@types/react-dom": "^16.9.10", "@types/react-redux": "^7.1.11", + "@types/three": "^0.138.0", "@typescript-eslint/eslint-plugin": "^5.5.0", "@typescript-eslint/parser": "^5.5.0", "babel-jest": "^27.0.6", @@ -3717,6 +3718,12 @@ "@types/jest": "*" } }, + "node_modules/@types/three": { + "version": "0.138.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.138.0.tgz", + "integrity": "sha512-D8AoV7h2kbCfrv/DcebHOFh1WDwyus3HdooBkAwcBikXArdqnsQ38PQ85JCunnvun160oA9jz53GszF3zch3tg==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.2", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.2.tgz", @@ -7711,10 +7718,9 @@ "dev": true }, "node_modules/grpc-bus": { - "version": "v1.0.1-dev5", + "version": "1.0.1-dev5", "resolved": "https://github.com/gabrielgrant/grpc-bus/releases/download/v1.0.1-dev5/grpc-bus-1.0.1-dev5.tgz", "integrity": "sha512-u5ZNfzrgFIPSltdbAzXE409wYGTzXEG2doyMCzmK86h8Ju2Ny7y1zxaXjtNmc9RBuIfaQsAHhOP0e4H+KL6RUg==", - "license": "MIT", "dependencies": { "lodash": "^4.0.0", "rxjs": "5.4.2" @@ -18122,6 +18128,12 @@ "@types/jest": "*" } }, + "@types/three": { + "version": "0.138.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.138.0.tgz", + "integrity": "sha512-D8AoV7h2kbCfrv/DcebHOFh1WDwyus3HdooBkAwcBikXArdqnsQ38PQ85JCunnvun160oA9jz53GszF3zch3tg==", + "dev": true + }, "@types/ws": { "version": "8.5.2", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.2.tgz", diff --git a/client/package.json b/client/package.json index 59dfe5b9..74d4e49f 100644 --- a/client/package.json +++ b/client/package.json @@ -74,6 +74,7 @@ "@types/react": "^16.14.2", "@types/react-dom": "^16.9.10", "@types/react-redux": "^7.1.11", + "@types/three": "^0.138.0", "@typescript-eslint/eslint-plugin": "^5.5.0", "@typescript-eslint/parser": "^5.5.0", "babel-jest": "^27.0.6", diff --git a/client/src/components/Viewer/Viewer.tsx b/client/src/components/Viewer/Viewer.tsx index cf6854f6..e7740b6f 100644 --- a/client/src/components/Viewer/Viewer.tsx +++ b/client/src/components/Viewer/Viewer.tsx @@ -22,24 +22,66 @@ const initGraphics = () => { camera.lookAt(new THREE.Vector3(0, 0, 0)) const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: true }); renderer.setSize(width, height); - const texture = new THREE.TextureLoader().load('src/images/dot.png'); - const geometry = new THREE.BufferGeometry(); - const material = new THREE.PointsMaterial({ - size: 5, - vertexColors: true, - map: texture, + + // --------------- Start of sprite stuff ---------------- + // const texture = new THREE.TextureLoader().load('src/images/dot.png'); + // const material = new THREE.PointsMaterial({ + // size: 5, + // vertexColors: true, + // map: texture, + // transparent: true, + // depthWrite: false, + // blending: THREE.NoBlending, + // }); + + // --------------- End of sprite stuff ---------------- + + // --------------- Start of shaders stuff ---------------- + const uniforms = { + pointTexture: { value: new THREE.TextureLoader().load('src/images/dot.png') } + }; + + const vertexShader = ` + attribute float size; + varying vec3 vColor; + void main() { + vColor = color; + vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); + gl_PointSize = size * ( 300.0 / -mvPosition.z ); + gl_Position = projectionMatrix * mvPosition; + + }` + const fragmentShader = ` + uniform sampler2D pointTexture; + varying vec3 vColor; + void main() { + gl_FragColor = vec4( vColor, 1.0 ); + gl_FragColor = gl_FragColor * texture2D( pointTexture, gl_PointCoord ); + }` + + const material = new THREE.ShaderMaterial({ + uniforms: uniforms, + vertexShader: vertexShader, + fragmentShader: fragmentShader, + blending: THREE.AdditiveBlending, + depthTest: false, transparent: true, - depthWrite: false, - blending: THREE.NoBlending, + vertexColors: true + }); + + // --------------- End of shaders stuff ---------------- + + const geometry = new THREE.BufferGeometry(); + const controls = new OrbitControls( camera, renderer.domElement ); controls.mouseButtons = { LEFT: THREE.MOUSE.PAN, - MIDDLE: null, - RIGHT: null, + MIDDLE: THREE.MOUSE.MIDDLE, + RIGHT: THREE.MOUSE.RIGHT, }; controls.enableRotate = false; @@ -68,13 +110,31 @@ const intitializeDataPoints = (scene, camera, geometry, material, coords) => { const y = [] as any; const positions = [] as any; const colors = [] as any; - + const sizes = [] as any; + + // const multiplyCells = 50; + // const nRows = 10; + // const spacing = 30 + // let col = 0; + + // for (let n = 0; n < multiplyCells; ++n) { + // if (n % nRows == 0) { + // col += 1; + // } + // coords.forEach((coord: { x: number; y: number; }) => { + // x.push(coord.x + spacing * (n % nRows)); + // y.push(coord.y + spacing * col); + // positions.push(coord.x + spacing * (n % nRows), coord.y + spacing * col, 0); + // colors.push(255, 0, 0); + // sizes.push(20); + // }) + // } coords.forEach((coord: { x: number; y: number; }) => { x.push(coord.x); y.push(coord.y); positions.push(coord.x, coord.y, 0); colors.push(255, 0, 0); - + sizes.push(1); }) console.log('Initializing data points...' + colors.length + ' ' + positions.length); @@ -110,6 +170,11 @@ const intitializeDataPoints = (scene, camera, geometry, material, coords) => { 'color', new THREE.Float32BufferAttribute(colors, 3) ); + geometry.setAttribute( + 'size', + new THREE.Float32BufferAttribute(sizes, 1) + .setUsage(THREE.DynamicDrawUsage) + ); const points = new THREE.Points(geometry, material); // const points = new THREE.Points(geometry); @@ -122,12 +187,12 @@ export const Viewer: React.FC = (props: ViewerProps) => { return root.main.coords; }); const mount = useRef(null!) - const [rendererState, setRenderer] = React.useState() - const [mounted, setMounted] = React.useState(false) - const [sceneState, setScene] = React.useState() - const [cameraState, setCamera] = React.useState() - const [geometryState, setGeometry] = React.useState() - const [materialState, setMaterial] = React.useState() + const [rendererState, setRenderer] = React.useState() + const [mounted, setMounted] = React.useState(false) + const [sceneState, setScene] = React.useState() + const [cameraState, setCamera] = React.useState() + const [geometryState, setGeometry] = React.useState() + const [materialState, setMaterial] = React.useState() const requestRef = React.useRef() as React.MutableRefObject; const previousTimeRef = React.useRef(); From 9b703415b17516c69ed807afbb3ed2f64b678601 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 15 Mar 2022 14:38:29 +0100 Subject: [PATCH 07/12] Remove dynamic size and adjust default --- client/src/components/Viewer/Viewer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/Viewer/Viewer.tsx b/client/src/components/Viewer/Viewer.tsx index e7740b6f..fe696354 100644 --- a/client/src/components/Viewer/Viewer.tsx +++ b/client/src/components/Viewer/Viewer.tsx @@ -47,7 +47,7 @@ const initGraphics = () => { void main() { vColor = color; vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); - gl_PointSize = size * ( 300.0 / -mvPosition.z ); + gl_PointSize = size; gl_Position = projectionMatrix * mvPosition; }` @@ -134,7 +134,7 @@ const intitializeDataPoints = (scene, camera, geometry, material, coords) => { y.push(coord.y); positions.push(coord.x, coord.y, 0); colors.push(255, 0, 0); - sizes.push(1); + sizes.push(5); }) console.log('Initializing data points...' + colors.length + ' ' + positions.length); From 49e075f85e792395cc740cc95c395a9abc86dfa9 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 15 Mar 2022 14:48:13 +0100 Subject: [PATCH 08/12] Client linting --- client/src/api/auth.ts | 5 +- client/src/api/project.ts | 6 +- client/src/components/Viewer/Viewer.tsx | 145 ++++++++++-------- .../src/components/Viewer/ViewerWrapper.tsx | 16 +- client/src/redux/actionTypes.ts | 2 +- client/src/redux/actions.ts | 9 +- client/src/redux/types.ts | 6 +- 7 files changed, 94 insertions(+), 95 deletions(-) diff --git a/client/src/api/auth.ts b/client/src/api/auth.ts index c6b5d0e9..25b2c297 100644 --- a/client/src/api/auth.ts +++ b/client/src/api/auth.ts @@ -95,7 +95,7 @@ const decodeAuthTokenResponse = ( 'access_token' in response && 'token_type' in response && typeof (response as { access_token: unknown }).access_token === - 'string' && + 'string' && typeof (response as { token_type: unknown }).token_type === 'string' ) { if ('user' in response) { @@ -177,7 +177,8 @@ const decodeProvider = (data: unknown): Result => { 'url' in data && typeof (data as { id: unknown }).id === 'number' && typeof (data as { name: unknown }).name === 'string' && - (typeof (data as { icon: unknown }).icon === 'string' || (data as { icon: unknown }).icon === null) && + (typeof (data as { icon: unknown }).icon === 'string' || + (data as { icon: unknown }).icon === null) && typeof (data as { url: unknown }).url === 'string' ) { const provider: Provider = { diff --git a/client/src/api/project.ts b/client/src/api/project.ts index 633cbad2..55d11a2d 100644 --- a/client/src/api/project.ts +++ b/client/src/api/project.ts @@ -192,7 +192,7 @@ export type Coordinate = { }; export async function getCoordinates( - dataset: string, + dataset: string ): Promise> { const url = new URL(API_URL + 'data/dataset'); url.search = new URLSearchParams({ dataset }).toString(); @@ -203,5 +203,5 @@ export async function getCoordinates( }, }); - return await response.json() as Array; -} \ No newline at end of file + return (await response.json()) as Array; +} diff --git a/client/src/components/Viewer/Viewer.tsx b/client/src/components/Viewer/Viewer.tsx index fe696354..9c95c041 100644 --- a/client/src/components/Viewer/Viewer.tsx +++ b/client/src/components/Viewer/Viewer.tsx @@ -5,8 +5,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { Coordinate } from '../../api'; import { RootState } from '../../redux/reducers'; import { getCoordinates } from '../../redux/actions'; -import { render } from '@testing-library/react'; -import * as R from 'ramda' +import * as R from 'ramda'; export type ViewerProps = { dataset: number; @@ -15,12 +14,21 @@ export type ViewerProps = { const initGraphics = () => { const scene = new THREE.Scene(); // scene.background = new THREE.Color(0x00ff00); - const height = 1000 - const width = 1000 - const camera = new THREE.OrthographicCamera(width / - 2, width / 2, height / 2, height / - 2) - camera.position.set(0, 0, 2000) - camera.lookAt(new THREE.Vector3(0, 0, 0)) - const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: true }); + const height = 1000; + const width = 1000; + const camera = new THREE.OrthographicCamera( + width / -2, + width / 2, + height / 2, + height / -2 + ); + camera.position.set(0, 0, 2000); + camera.lookAt(new THREE.Vector3(0, 0, 0)); + const renderer = new THREE.WebGLRenderer({ + alpha: true, + antialias: true, + preserveDrawingBuffer: true, + }); renderer.setSize(width, height); // --------------- Start of sprite stuff ---------------- @@ -38,7 +46,9 @@ const initGraphics = () => { // --------------- Start of shaders stuff ---------------- const uniforms = { - pointTexture: { value: new THREE.TextureLoader().load('src/images/dot.png') } + pointTexture: { + value: new THREE.TextureLoader().load('src/images/dot.png'), + }, }; const vertexShader = ` @@ -50,14 +60,14 @@ const initGraphics = () => { gl_PointSize = size; gl_Position = projectionMatrix * mvPosition; - }` + }`; const fragmentShader = ` uniform sampler2D pointTexture; varying vec3 vColor; void main() { gl_FragColor = vec4( vColor, 1.0 ); gl_FragColor = gl_FragColor * texture2D( pointTexture, gl_PointCoord ); - }` + }`; const material = new THREE.ShaderMaterial({ uniforms: uniforms, @@ -66,18 +76,14 @@ const initGraphics = () => { blending: THREE.AdditiveBlending, depthTest: false, transparent: true, - vertexColors: true - + vertexColors: true, }); // --------------- End of shaders stuff ---------------- const geometry = new THREE.BufferGeometry(); - const controls = new OrbitControls( - camera, - renderer.domElement - ); + const controls = new OrbitControls(camera, renderer.domElement); controls.mouseButtons = { LEFT: THREE.MOUSE.PAN, MIDDLE: THREE.MOUSE.MIDDLE, @@ -90,7 +96,7 @@ const initGraphics = () => { scene, camera, geometry, - material + material, }; controls.addEventListener('change', () => renderScene(result)); @@ -99,11 +105,10 @@ const initGraphics = () => { }; const renderScene = ({ renderer, scene, camera }) => { - if (typeof renderer != 'undefined') { + if (typeof renderer !== 'undefined') { renderer.render(scene, camera); } -} - +}; const intitializeDataPoints = (scene, camera, geometry, material, coords) => { const x = [] as any; @@ -129,32 +134,36 @@ const intitializeDataPoints = (scene, camera, geometry, material, coords) => { // sizes.push(20); // }) // } - coords.forEach((coord: { x: number; y: number; }) => { + coords.forEach((coord: { x: number; y: number }) => { x.push(coord.x); y.push(coord.y); positions.push(coord.x, coord.y, 0); colors.push(255, 0, 0); sizes.push(5); - }) + }); - console.log('Initializing data points...' + colors.length + ' ' + positions.length); + console.log( + 'Initializing data points...' + colors.length + ' ' + positions.length + ); scene.clear(); - const diff = function (a: number, b: number) { return a - b; }; - const sorted_x = R.sort(diff, x) - const sorted_y = R.sort(diff, y) + const diff = function (a: number, b: number) { + return a - b; + }; + const sorted_x = R.sort(diff, x); + const sorted_y = R.sort(diff, y); - const xMax = sorted_x[sorted_x.length - 1] - const xMin = sorted_x[0] - const yMax = sorted_y[sorted_y.length - 1] - const yMin = sorted_y[0] + const xMax = sorted_x[sorted_x.length - 1]; + const xMin = sorted_x[0]; + const yMax = sorted_y[sorted_y.length - 1]; + const yMin = sorted_y[0]; - const xCenter = (xMax + xMin) / 2 - const yCenter = (yMax + yMin) / 2 + const xCenter = (xMax + xMin) / 2; + const yCenter = (yMax + yMin) / 2; const maxDiff = Math.max(Math.abs(xMax - xCenter), Math.abs(yMax - yCenter)) * 1.5; - const aspectRatio = 1 + const aspectRatio = 1; camera.left = xCenter - maxDiff; camera.right = xCenter + maxDiff; camera.top = yCenter + maxDiff / aspectRatio; @@ -166,53 +175,51 @@ const intitializeDataPoints = (scene, camera, geometry, material, coords) => { 'position', new THREE.Float32BufferAttribute(positions, 3) ); - geometry.setAttribute( - 'color', - new THREE.Float32BufferAttribute(colors, 3) - ); + geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); geometry.setAttribute( 'size', - new THREE.Float32BufferAttribute(sizes, 1) - .setUsage(THREE.DynamicDrawUsage) + new THREE.Float32BufferAttribute(sizes, 1).setUsage( + THREE.DynamicDrawUsage + ) ); const points = new THREE.Points(geometry, material); // const points = new THREE.Points(geometry); scene.add(points); -} +}; export const Viewer: React.FC = (props: ViewerProps) => { const dispatch = useDispatch(); - const coords = useSelector>((root: RootState) => { - return root.main.coords; - }); - const mount = useRef(null!) - const [rendererState, setRenderer] = React.useState() - const [mounted, setMounted] = React.useState(false) - const [sceneState, setScene] = React.useState() - const [cameraState, setCamera] = React.useState() - const [geometryState, setGeometry] = React.useState() - const [materialState, setMaterial] = React.useState() + const coords = useSelector>( + (root: RootState) => { + return root.main.coords; + } + ); + const mount = useRef(null!); + const [rendererState, setRenderer] = React.useState(); + const [mounted, setMounted] = React.useState(false); + const [sceneState, setScene] = React.useState(); + const [cameraState, setCamera] = React.useState(); + const [geometryState, setGeometry] = React.useState(); + const [materialState, setMaterial] = React.useState(); const requestRef = React.useRef() as React.MutableRefObject; const previousTimeRef = React.useRef(); - const animate = time => { - if (previousTimeRef.current != undefined) { - renderScene({ renderer: rendererState, scene: sceneState, camera: cameraState }); + const animate = (time) => { + if (previousTimeRef.current !== undefined) { + renderScene({ + renderer: rendererState, + scene: sceneState, + camera: cameraState, + }); } previousTimeRef.current = time; requestRef.current = requestAnimationFrame(animate); - } + }; useEffect(() => { - const { - renderer, - scene, - camera, - geometry, - material - } = initGraphics(); + const { renderer, scene, camera, geometry, material } = initGraphics(); setRenderer(renderer); setScene(scene); setCamera(camera); @@ -228,14 +235,18 @@ export const Viewer: React.FC = (props: ViewerProps) => { dispatch(getCoordinates(`${props.dataset}`)); } if (sceneState && cameraState && geometryState) { - intitializeDataPoints(sceneState, cameraState, geometryState, materialState, coords); + intitializeDataPoints( + sceneState, + cameraState, + geometryState, + materialState, + coords + ); } requestRef.current = requestAnimationFrame(animate); return () => cancelAnimationFrame(requestRef.current); }, [coords]); - return (
) -} \ No newline at end of file + return
; +}; diff --git a/client/src/components/Viewer/ViewerWrapper.tsx b/client/src/components/Viewer/ViewerWrapper.tsx index a1b5a3ba..0b018f48 100644 --- a/client/src/components/Viewer/ViewerWrapper.tsx +++ b/client/src/components/Viewer/ViewerWrapper.tsx @@ -6,19 +6,11 @@ import { Button, Icon } from 'semantic-ui-react'; import * as MainSelect from '../../redux/selectors'; import { RootState } from '../../redux/reducers'; -import { ViewerId, ViewerInfo, ViewerMap } from './model'; +import { ViewerId, ViewerMap } from './model'; import * as Select from './selectors'; import * as Action from './actions'; import { Viewer } from './Viewer'; -const Placeholder: React.FC = (props: ViewerInfo) => { - return ( -
- {props.project}, {props.dataset} -
- ); -}; - type WrapperState = { viewers: ViewerMap; grid: Array>; @@ -68,11 +60,7 @@ export const ViewerWrapper: React.FC<{}> = () => { if (viewer === undefined) { return
Empty
; } else { - return ( - - ); + return ; } } })} diff --git a/client/src/redux/actionTypes.ts b/client/src/redux/actionTypes.ts index 7ebd626d..973d7306 100644 --- a/client/src/redux/actionTypes.ts +++ b/client/src/redux/actionTypes.ts @@ -24,4 +24,4 @@ export const MODIFIER_KEY_TOGGLE = 'MODIFIER_KEY_TOGGLE'; export const GET_COORDINATES = 'GET_COORDINATES'; -export const RECEIVED_COORDINATES = 'RECEIVED_COORDINATES'; \ No newline at end of file +export const RECEIVED_COORDINATES = 'RECEIVED_COORDINATES'; diff --git a/client/src/redux/actions.ts b/client/src/redux/actions.ts index ab80acae..69a6ad48 100644 --- a/client/src/redux/actions.ts +++ b/client/src/redux/actions.ts @@ -80,10 +80,11 @@ export const toggleModifierKey = (key: ModifierKey): ToggleModifierKey => ({ export const getCoordinates = (dataset: string): GetCoordinates => ({ type: AT.GET_COORDINATES, payload: { dataset }, -}) - +}); -export const receivedCoordinates = (coords: Array): ReceivedCoordinates => ({ +export const receivedCoordinates = ( + coords: Array +): ReceivedCoordinates => ({ type: AT.RECEIVED_COORDINATES, payload: { coordinates: coords }, -}) \ No newline at end of file +}); diff --git a/client/src/redux/types.ts b/client/src/redux/types.ts index 73f83dfa..81809953 100644 --- a/client/src/redux/types.ts +++ b/client/src/redux/types.ts @@ -111,18 +111,16 @@ export interface GetCoordinates { type: typeof AT.GET_COORDINATES; payload: { dataset: string; - } + }; } - export interface ReceivedCoordinates { type: typeof AT.RECEIVED_COORDINATES; payload: { coordinates: Array; - } + }; } - export type MainAction = | SetLoadingAction | SetUUIDAction From 43ca1e8a543334ba01962897b9c0d46562725f75 Mon Sep 17 00:00:00 2001 From: James Collier Date: Tue, 15 Mar 2022 16:23:30 +0100 Subject: [PATCH 09/12] Clean up Hack Coordinate HTTP endpoint --- server/scopeserver/api/v1/data.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/server/scopeserver/api/v1/data.py b/server/scopeserver/api/v1/data.py index 473a91c1..c5ea4aea 100644 --- a/server/scopeserver/api/v1/data.py +++ b/server/scopeserver/api/v1/data.py @@ -2,14 +2,13 @@ from typing import List from pathlib import Path -import shutil -from fastapi import APIRouter, Depends, File, HTTPException, Response, UploadFile, status +from fastapi import APIRouter, Depends from sqlalchemy.orm import Session import loompy as lp -from scopeserver import crud, models, schemas +from scopeserver import crud, schemas from scopeserver.api import deps from scopeserver.config import settings @@ -27,11 +26,7 @@ def get_dataset( with lp.connect( settings.DATA_PATH / "c9ef0a31-a7cd-4d04-a73f-2575babf7e30" / Path(entry.filename), validate=False ) as ds: - print(ds.ca.Embeddings_X[0][0]) - x = [_x[0] for _x in ds.ca.Embeddings_X] - y = [_y[0] for _y in ds.ca.Embeddings_Y] - print(x[:5]) - print(y[:5]) - print([(X, Y) for X, Y in zip(x, y)][:5]) - - return [schemas.Coordinate(x=X, y=Y) for X, Y in zip(x, y)] + xs = [_x[0] for _x in ds.ca.Embeddings_X] + ys = [_y[0] for _y in ds.ca.Embeddings_Y] + + return [schemas.Coordinate(x=X, y=Y) for X, Y in zip(xs, ys)] From a15ed69d43abf6bd7781f187be42994c95c07fb9 Mon Sep 17 00:00:00 2001 From: James Collier Date: Tue, 15 Mar 2022 15:36:40 +0100 Subject: [PATCH 10/12] Respect the types man --- server/scopeserver/api/v1/data.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/server/scopeserver/api/v1/data.py b/server/scopeserver/api/v1/data.py index c5ea4aea..0053ea6b 100644 --- a/server/scopeserver/api/v1/data.py +++ b/server/scopeserver/api/v1/data.py @@ -23,10 +23,13 @@ def get_dataset( ): entry = crud.get_dataset(database, dataset) - with lp.connect( - settings.DATA_PATH / "c9ef0a31-a7cd-4d04-a73f-2575babf7e30" / Path(entry.filename), validate=False - ) as ds: - xs = [_x[0] for _x in ds.ca.Embeddings_X] - ys = [_y[0] for _y in ds.ca.Embeddings_Y] + if entry is not None: + with lp.connect( + settings.DATA_PATH / "c9ef0a31-a7cd-4d04-a73f-2575babf7e30" / Path(entry.filename), validate=False + ) as ds: + xs = [_x[0] for _x in ds.ca.Embeddings_X] + ys = [_y[0] for _y in ds.ca.Embeddings_Y] - return [schemas.Coordinate(x=X, y=Y) for X, Y in zip(xs, ys)] + return [schemas.Coordinate(x=X, y=Y) for X, Y in zip(xs, ys)] + + return [] From 04bfc8c63a0788122b933f83a9ecf7a356462f6d Mon Sep 17 00:00:00 2001 From: James Collier Date: Tue, 15 Mar 2022 15:57:59 +0100 Subject: [PATCH 11/12] Include the project in the coordinates request --- client/src/api/project.ts | 8 ++++++-- client/src/components/Viewer/Viewer.tsx | 3 ++- client/src/components/Viewer/ViewerWrapper.tsx | 7 ++++++- client/src/redux/actions.ts | 7 +++++-- client/src/redux/sagas/scope/index.ts | 6 +++++- client/src/redux/types.ts | 3 ++- server/scopeserver/api/v1/data.py | 5 ++--- 7 files changed, 28 insertions(+), 11 deletions(-) diff --git a/client/src/api/project.ts b/client/src/api/project.ts index 55d11a2d..27d2a10f 100644 --- a/client/src/api/project.ts +++ b/client/src/api/project.ts @@ -192,10 +192,14 @@ export type Coordinate = { }; export async function getCoordinates( - dataset: string + project: string, + dataset: number ): Promise> { const url = new URL(API_URL + 'data/dataset'); - url.search = new URLSearchParams({ dataset }).toString(); + url.search = new URLSearchParams({ + project, + dataset: `${dataset}`, + }).toString(); const response = await fetch(url.toString(), { method: 'GET', headers: { diff --git a/client/src/components/Viewer/Viewer.tsx b/client/src/components/Viewer/Viewer.tsx index 9c95c041..b09b1fec 100644 --- a/client/src/components/Viewer/Viewer.tsx +++ b/client/src/components/Viewer/Viewer.tsx @@ -8,6 +8,7 @@ import { getCoordinates } from '../../redux/actions'; import * as R from 'ramda'; export type ViewerProps = { + project: string; dataset: number; }; @@ -232,7 +233,7 @@ export const Viewer: React.FC = (props: ViewerProps) => { } if (coords.length === 0) { - dispatch(getCoordinates(`${props.dataset}`)); + dispatch(getCoordinates(props.project, props.dataset)); } if (sceneState && cameraState && geometryState) { intitializeDataPoints( diff --git a/client/src/components/Viewer/ViewerWrapper.tsx b/client/src/components/Viewer/ViewerWrapper.tsx index 0b018f48..a4c453b6 100644 --- a/client/src/components/Viewer/ViewerWrapper.tsx +++ b/client/src/components/Viewer/ViewerWrapper.tsx @@ -60,7 +60,12 @@ export const ViewerWrapper: React.FC<{}> = () => { if (viewer === undefined) { return
Empty
; } else { - return ; + return ( + + ); } } })} diff --git a/client/src/redux/actions.ts b/client/src/redux/actions.ts index 69a6ad48..a09bd91f 100644 --- a/client/src/redux/actions.ts +++ b/client/src/redux/actions.ts @@ -77,9 +77,12 @@ export const toggleModifierKey = (key: ModifierKey): ToggleModifierKey => ({ payload: { key }, }); -export const getCoordinates = (dataset: string): GetCoordinates => ({ +export const getCoordinates = ( + project: string, + dataset: number +): GetCoordinates => ({ type: AT.GET_COORDINATES, - payload: { dataset }, + payload: { project, dataset }, }); export const receivedCoordinates = ( diff --git a/client/src/redux/sagas/scope/index.ts b/client/src/redux/sagas/scope/index.ts index 2b53c983..1a140878 100644 --- a/client/src/redux/sagas/scope/index.ts +++ b/client/src/redux/sagas/scope/index.ts @@ -94,7 +94,11 @@ export function* watchUploadRequest() { } function* requestCoords(action: T.GetCoordinates) { - const coords = yield call(API.getCoordinates, action.payload.dataset); + const coords = yield call( + API.getCoordinates, + action.payload.project, + action.payload.dataset + ); yield put(A.receivedCoordinates(coords)); } diff --git a/client/src/redux/types.ts b/client/src/redux/types.ts index 81809953..43e184b5 100644 --- a/client/src/redux/types.ts +++ b/client/src/redux/types.ts @@ -110,7 +110,8 @@ export interface ToggleModifierKey { export interface GetCoordinates { type: typeof AT.GET_COORDINATES; payload: { - dataset: string; + project: string; + dataset: number; }; } diff --git a/server/scopeserver/api/v1/data.py b/server/scopeserver/api/v1/data.py index 0053ea6b..f79ff98a 100644 --- a/server/scopeserver/api/v1/data.py +++ b/server/scopeserver/api/v1/data.py @@ -19,14 +19,13 @@ def get_dataset( *, database: Session = Depends(deps.get_db), + project: str, dataset: int, ): entry = crud.get_dataset(database, dataset) if entry is not None: - with lp.connect( - settings.DATA_PATH / "c9ef0a31-a7cd-4d04-a73f-2575babf7e30" / Path(entry.filename), validate=False - ) as ds: + with lp.connect(settings.DATA_PATH / Path(project) / Path(entry.filename), validate=False) as ds: xs = [_x[0] for _x in ds.ca.Embeddings_X] ys = [_y[0] for _y in ds.ca.Embeddings_Y] From 223e7298ce8e21fccd2c32c6983bd785fca93602 Mon Sep 17 00:00:00 2001 From: James Collier Date: Tue, 15 Mar 2022 16:15:37 +0100 Subject: [PATCH 12/12] Try to make semgrep happy --- client/src/components/Viewer/Viewer.tsx | 29 +++++++++++++------------ 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/client/src/components/Viewer/Viewer.tsx b/client/src/components/Viewer/Viewer.tsx index b09b1fec..90b9d4d6 100644 --- a/client/src/components/Viewer/Viewer.tsx +++ b/client/src/components/Viewer/Viewer.tsx @@ -148,16 +148,17 @@ const intitializeDataPoints = (scene, camera, geometry, material, coords) => { ); scene.clear(); - const diff = function (a: number, b: number) { - return a - b; - }; - const sorted_x = R.sort(diff, x); - const sorted_y = R.sort(diff, y); - - const xMax = sorted_x[sorted_x.length - 1]; - const xMin = sorted_x[0]; - const yMax = sorted_y[sorted_y.length - 1]; - const yMin = sorted_y[0]; + const sorted_x = R.sort(R.subtract, x); + const sorted_y = R.sort(R.subtract, y); + + const [xMin, xMax] = [ + R.head(sorted_x) || 0, + R.nth(sorted_x.length - 1, sorted_x) || 0, + ]; + const [yMin, yMax] = [ + R.head(sorted_y) || 0, + R.nth(sorted_y.length - 1, sorted_y) || 0, + ]; const xCenter = (xMax + xMin) / 2; const yCenter = (yMax + yMin) / 2; @@ -196,7 +197,7 @@ export const Viewer: React.FC = (props: ViewerProps) => { return root.main.coords; } ); - const mount = useRef(null!); + const mount = useRef(null!); // nosemgrep: typescript.react.security.audit.react-no-refs.react-no-refs const [rendererState, setRenderer] = React.useState(); const [mounted, setMounted] = React.useState(false); const [sceneState, setScene] = React.useState(); @@ -204,8 +205,8 @@ export const Viewer: React.FC = (props: ViewerProps) => { const [geometryState, setGeometry] = React.useState(); const [materialState, setMaterial] = React.useState(); - const requestRef = React.useRef() as React.MutableRefObject; - const previousTimeRef = React.useRef(); + const requestRef = useRef() as React.MutableRefObject; // nosemgrep: typescript.react.security.audit.react-no-refs.react-no-refs + const previousTimeRef = useRef(); // nosemgrep: typescript.react.security.audit.react-no-refs.react-no-refs const animate = (time) => { if (previousTimeRef.current !== undefined) { @@ -249,5 +250,5 @@ export const Viewer: React.FC = (props: ViewerProps) => { return () => cancelAnimationFrame(requestRef.current); }, [coords]); - return
; + return
; // nosemgrep: typescript.react.security.audit.react-no-refs.react-no-refs };