-
Notifications
You must be signed in to change notification settings - Fork 4
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: Loading from the landing page, viewer page routing #212
Changes from 13 commits
cc58736
12da14d
f3e48cd
9a4f105
c874c55
466d5b7
0f1f8e1
b883707
8d18a12
746f473
1a16c02
675591f
c68dff3
b167967
e76894c
9003180
5c4f77f
b72d82d
33e4990
4bf2218
5b56362
2172864
c15caf1
e2cb168
b239dd2
8cb2ad9
3224aa9
16ed045
b6c00f0
68a2c40
18e9fe3
d8deb01
a1b9774
e06bb53
d0e8862
8747501
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,262 +1,38 @@ | ||
import React from "react"; | ||
import ReactDOM from "react-dom"; | ||
import { createBrowserRouter, RouterProvider } from "react-router-dom"; | ||
|
||
import "antd/dist/antd.less"; | ||
|
||
// Components | ||
import { RenderMode, ViewerChannelSettings, ViewMode } from "../src"; | ||
import AppWrapper from "../src/website/components/AppWrapper"; | ||
import LandingPage from "../src/website/components/LandingPage"; | ||
import { AppProps, GlobalViewerSettings } from "../src/aics-image-viewer/components/App/types"; | ||
import StyleProvider from "../src/aics-image-viewer/components/StyleProvider"; | ||
import "../src/aics-image-viewer/assets/styles/typography.css"; | ||
import "./App.css"; | ||
|
||
import { ImageViewerApp, RenderMode, ViewerChannelSettings, ViewMode } from "../src"; | ||
import FirebaseRequest, { DatasetMetaData } from "./firebase"; | ||
import { AppProps, GlobalViewerSettings } from "../src/aics-image-viewer/components/App/types"; | ||
|
||
// vars filled at build time using webpack DefinePlugin | ||
console.log(`website-3d-cell-viewer ${WEBSITE3DCELLVIEWER_BUILD_ENVIRONMENT} build`); | ||
console.log(`website-3d-cell-viewer Version ${WEBSITE3DCELLVIEWER_VERSION}`); | ||
console.log(`volume-viewer Version ${VOLUMEVIEWER_VERSION}`); | ||
|
||
export const VIEWER_3D_SETTINGS: ViewerChannelSettings = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. deleted There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good to delete yes, but wondering how we can get test data like this into the viewer during dev. These settings are basically how we want the variance dataset to be presented in the viewer. |
||
groups: [ | ||
{ | ||
name: "Observed channels", | ||
channels: [ | ||
{ name: "Membrane", match: ["(CMDRP)"], color: "E2CDB3", enabled: true, lut: ["p50", "p98"] }, | ||
{ | ||
name: "Labeled structure", | ||
match: ["(EGFP)|(RFPT)"], | ||
color: "6FBA11", | ||
enabled: true, | ||
lut: ["p50", "p98"], | ||
}, | ||
{ name: "DNA", match: ["(H3342)"], color: "8DA3C0", enabled: true, lut: ["p50", "p98"] }, | ||
{ name: "Bright field", match: ["(100)|(Bright)"], color: "F5F1CB", enabled: false, lut: ["p50", "p98"] }, | ||
], | ||
}, | ||
{ | ||
name: "Segmentation channels", | ||
channels: [ | ||
{ | ||
name: "Labeled structure", | ||
match: ["(SEG_STRUCT)"], | ||
color: "E0E3D1", | ||
enabled: false, | ||
lut: ["p50", "p98"], | ||
}, | ||
{ name: "Membrane", match: ["(SEG_Memb)"], color: "DD9BF5", enabled: false, lut: ["p50", "p98"] }, | ||
{ name: "DNA", match: ["(SEG_DNA)"], color: "E3F4F5", enabled: false, lut: ["p50", "p98"] }, | ||
], | ||
}, | ||
{ | ||
name: "Contour channels", | ||
channels: [ | ||
{ name: "Membrane", match: ["(CON_Memb)"], color: "FF6200", enabled: false, lut: ["p50", "p98"] }, | ||
{ name: "DNA", match: ["(CON_DNA)"], color: "F7DB78", enabled: false, lut: ["p50", "p98"] }, | ||
], | ||
}, | ||
], | ||
// must be the true channel name in the volume data | ||
maskChannelName: "SEG_Memb", | ||
}; | ||
|
||
type ParamKeys = "mask" | "ch" | "luts" | "colors" | "url" | "file" | "dataset" | "id" | "view"; | ||
type Params = { [_ in ParamKeys]?: string }; | ||
|
||
function parseQueryString(): Params { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. deleted; using |
||
const pairs = location.search.slice(1).split("&"); | ||
const result = {}; | ||
pairs.forEach((pairString) => { | ||
const pair = pairString.split("="); | ||
result[pair[0]] = decodeURIComponent(pair[1] || ""); | ||
}); | ||
return JSON.parse(JSON.stringify(result)); | ||
} | ||
const params = parseQueryString(); | ||
|
||
const decodeURL = (url: string): string => { | ||
const decodedUrl = decodeURIComponent(url); | ||
return decodedUrl.endsWith("/") ? decodedUrl.slice(0, -1) : decodedUrl; | ||
}; | ||
|
||
/** Try to parse a `string` as a list of 2 or more URLs. Returns `undefined` if the string is not a valid URL list. */ | ||
const tryDecodeURLList = (url: string, delim = ","): string[] | undefined => { | ||
if (!url.includes(delim)) { | ||
return undefined; | ||
} | ||
|
||
const urls = url.split(delim).map((u) => decodeURL(u)); | ||
|
||
// Verify that all urls are valid | ||
for (const u of urls) { | ||
try { | ||
new URL(u); | ||
} catch (_e) { | ||
return undefined; | ||
} | ||
} | ||
|
||
return urls; | ||
}; | ||
|
||
const BASE_URL = "https://s3-us-west-2.amazonaws.com/bisque.allencell.org/v1.4.0/Cell-Viewer_Thumbnails/"; | ||
const args: Omit<AppProps, "appHeight" | "canvasMargin"> = { | ||
cellId: "2025", | ||
imageUrl: BASE_URL + "AICS-22/AICS-22_8319_2025_atlas.json", | ||
parentImageUrl: BASE_URL + "AICS-22/AICS-22_8319_atlas.json", | ||
parentImageDownloadHref: "https://files.allencell.org/api/2.0/file/download?collection=cellviewer-1-4/?id=F8319", | ||
imageDownloadHref: "https://files.allencell.org/api/2.0/file/download?collection=cellviewer-1-4/?id=C2025", | ||
viewerChannelSettings: VIEWER_3D_SETTINGS, | ||
}; | ||
const viewerSettings: Partial<GlobalViewerSettings> = { | ||
showAxes: false, | ||
showBoundingBox: false, | ||
autorotate: false, | ||
viewMode: ViewMode.threeD, | ||
renderMode: RenderMode.volumetric, | ||
maskAlpha: 50, | ||
brightness: 70, | ||
density: 50, | ||
levels: [0, 128, 255] as [number, number, number], | ||
backgroundColor: [0, 0, 0] as [number, number, number], | ||
boundingBoxColor: [255, 255, 255] as [number, number, number], | ||
}; | ||
Comment on lines
-101
to
-122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. deleted |
||
|
||
async function loadDataset(dataset: string, id: string) { | ||
const db = new FirebaseRequest(); | ||
|
||
const datasets = await db.getAvailableDatasets(); | ||
|
||
let datasetMeta: DatasetMetaData | undefined = undefined; | ||
for (const d of datasets) { | ||
const innerDatasets = d.datasets!; | ||
const names = Object.keys(innerDatasets); | ||
const matchingName = names.find((name) => name === dataset); | ||
if (matchingName) { | ||
datasetMeta = innerDatasets[matchingName]; | ||
break; | ||
} | ||
} | ||
if (datasetMeta === undefined) { | ||
console.error(`No matching dataset: ${dataset}`); | ||
return; | ||
} | ||
|
||
const datasetData = await db.selectDataset(datasetMeta.manifest!); | ||
const baseUrl = datasetData.volumeViewerDataRoot + "/"; | ||
args.imageDownloadHref = datasetData.downloadRoot + "/" + id; | ||
// args.fovDownloadHref = datasetData.downloadRoot + "/" + id; | ||
|
||
const fileInfo = await db.getFileInfoByCellId(id); | ||
args.imageUrl = baseUrl + fileInfo!.volumeviewerPath; | ||
args.parentImageUrl = baseUrl + fileInfo!.fovVolumeviewerPath; | ||
runApp(); | ||
|
||
// only now do we have all the data needed | ||
} | ||
|
||
if (params) { | ||
if (params.mask) { | ||
viewerSettings.maskAlpha = parseInt(params.mask, 10); | ||
} | ||
if (params.view) { | ||
const mapping = { | ||
"3D": ViewMode.threeD, | ||
Z: ViewMode.xy, | ||
Y: ViewMode.xz, | ||
X: ViewMode.yz, | ||
}; | ||
const allowedViews = Object.keys(mapping); | ||
if (!allowedViews.includes(params.view)) { | ||
params.view = "3D"; | ||
} | ||
viewerSettings.viewMode = mapping[params.view]; | ||
} | ||
if (params.ch) { | ||
// ?ch=1,2 | ||
// ?luts=0,255,0,255 | ||
// ?colors=ff0000,00ff00 | ||
const initialChannelSettings: ViewerChannelSettings = { | ||
groups: [{ name: "Channels", channels: [] }], | ||
}; | ||
const ch = initialChannelSettings.groups[0].channels; | ||
|
||
const channelsOn = params.ch.split(",").map((numstr) => parseInt(numstr, 10)); | ||
for (let i = 0; i < channelsOn.length; ++i) { | ||
ch.push({ match: channelsOn[i], enabled: true }); | ||
} | ||
// look for luts or color | ||
if (params.luts) { | ||
const luts = params.luts.split(","); | ||
if (luts.length !== ch.length * 2) { | ||
console.log("ILL-FORMED QUERYSTRING: luts must have a min/max for each ch"); | ||
} | ||
for (let i = 0; i < ch.length; ++i) { | ||
ch[i]["lut"] = [luts[i * 2], luts[i * 2 + 1]]; | ||
} | ||
} | ||
if (params.colors) { | ||
const colors = params.colors.split(","); | ||
if (colors.length !== ch.length) { | ||
console.log("ILL-FORMED QUERYSTRING: if colors specified, must have a color for each ch"); | ||
} | ||
for (let i = 0; i < ch.length; ++i) { | ||
ch[i]["color"] = colors[i]; | ||
} | ||
} | ||
args.viewerChannelSettings = initialChannelSettings; | ||
} | ||
if (params.url) { | ||
const imageUrls = tryDecodeURLList(params.url) ?? decodeURL(params.url); | ||
const firstUrl = Array.isArray(imageUrls) ? imageUrls[0] : imageUrls; | ||
|
||
args.cellId = "1"; | ||
args.imageUrl = imageUrls; | ||
// this is invalid for zarr? | ||
args.imageDownloadHref = firstUrl; | ||
args.parentImageUrl = ""; | ||
args.parentImageDownloadHref = ""; | ||
// if json, then use the CFE settings for now. | ||
// (See VIEWER_3D_SETTINGS) | ||
// otherwise turn the first 3 channels on and group them | ||
if (!firstUrl.endsWith("json") && !params.ch) { | ||
args.viewerChannelSettings = { | ||
groups: [ | ||
// first 3 channels on by default! | ||
{ | ||
name: "Channels", | ||
channels: [ | ||
{ match: [0, 1, 2], enabled: true }, | ||
{ match: "(.+)", enabled: false }, | ||
], | ||
}, | ||
], | ||
}; | ||
} | ||
runApp(); | ||
} else if (params.file) { | ||
// quick way to load a atlas.json from a special directory. | ||
// | ||
// ?file=relative-path-to-atlas-on-isilon | ||
args.cellId = "1"; | ||
const baseUrl = "http://dev-aics-dtp-001.corp.alleninstitute.org/dan-data/"; | ||
args.imageUrl = baseUrl + params.file; | ||
args.parentImageUrl = baseUrl + params.file; | ||
args.parentImageDownloadHref = ""; | ||
args.imageDownloadHref = ""; | ||
runApp(); | ||
} else if (params.dataset && params.id) { | ||
// ?dataset=aics_hipsc_v2020.1&id=232265 | ||
loadDataset(params.dataset, params.id); | ||
} else { | ||
runApp(); | ||
} | ||
} else { | ||
runApp(); | ||
} | ||
|
||
function runApp() { | ||
ReactDOM.render( | ||
<ImageViewerApp {...args} appHeight="100vh" canvasMargin="0 0 0 0" viewerSettings={viewerSettings} />, | ||
document.getElementById("cell-viewer") | ||
); | ||
} | ||
const router = createBrowserRouter([ | ||
{ | ||
path: "/", | ||
element: <LandingPage />, | ||
}, | ||
{ | ||
path: "viewer", | ||
element: <AppWrapper />, | ||
}, | ||
]); | ||
|
||
ReactDOM.render( | ||
// <ImageViewerApp {...args} appHeight="100vh" canvasMargin="0 0 0 0" viewerSettings={viewerSettings} />, | ||
<StyleProvider> | ||
<RouterProvider router={router} /> | ||
</StyleProvider>, | ||
document.getElementById("cell-viewer") | ||
); |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code was throwing an error due to the various refs being undefined on startup. |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of the parsing logic below was moved to
src/website/utils/url_utils.tsx
.