Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 20 additions & 1 deletion docs/docusaurus.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type * as Preset from '@docusaurus/preset-classic';
import type { Config } from '@docusaurus/types';
import type { Config, Plugin } from '@docusaurus/types';
import { themes as prismThemes } from 'prism-react-renderer';

// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
Expand All @@ -20,6 +20,8 @@ const config: Config = {
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/SpatialData.js/',

plugins: [disableDevSplitChunks],

// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'Taylor-CCB-Group', // Usually your GitHub org/user name.
Expand Down Expand Up @@ -130,3 +132,20 @@ const config: Config = {
};

export default config;

function disableDevSplitChunks(): Plugin {
return {
name: 'disable-dev-split-chunks',
configureWebpack(_config, isServer) {
if (process.env.NODE_ENV === 'production' || isServer) {
return {};
}

return {
optimization: {
splitChunks: false,
},
};
},
};
}
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"dev": "docusaurus start --port 3001",
"dev": "docusaurus start --host 127.0.0.1 --port 3001",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
Expand Down
11 changes: 8 additions & 3 deletions packages/avivatorish/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
getMultiSelectionStats,
getBoundingCube,
isInterleaved,
resolveRasterSource,
} from "./utils";
import { COLOR_PALLETE, FILL_PIXEL_VALUE } from "./constants";
import { createLoader } from "./utils";
Expand Down Expand Up @@ -53,7 +54,7 @@ export const useImage = (
async function changeLoader() {
// Placeholder
viewerStore.setState({ isChannelLoading: [true] });
viewerStore.setState({ isViewerLoading: true });
viewerStore.setState({ isViewerLoading: true, metadata: null });
if (use3d) toggleUse3d();
if (!source) throw "this should never happen - this is a type-guard";
const { urlOrFile } = source;
Expand Down Expand Up @@ -109,11 +110,15 @@ export const useImage = (
useEffect(() => {
if (!metadata) return;
const changeSettings = async () => {
const rasterSource = resolveRasterSource(loader);
if (!rasterSource) {
return;
}
// Placeholder
viewerStore.setState({ isChannelLoading: [true] });
viewerStore.setState({ isViewerLoading: true });
if (use3d) toggleUse3d();
const newSelections = buildDefaultSelection(loader[0]);
const newSelections = buildDefaultSelection(rasterSource);
const { Channels } = metadata.Pixels;
const channelOptions = Channels.map(
(c, i) => c.Name ?? `Channel ${i}`,
Expand All @@ -126,7 +131,7 @@ export const useImage = (
let newColors: Colors = [];
const isRgb = guessRgb(metadata);
if (isRgb) {
if (isInterleaved(loader[0].shape)) {
if (isInterleaved(rasterSource.shape)) {
// These don't matter because the data is interleaved.
newContrastLimits = [[0, 255]];
newDomains = [[0, 255]];
Expand Down
55 changes: 51 additions & 4 deletions packages/avivatorish/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,55 @@ function narrowLimits(limits: number[]): limits is [number, number] {
function narrowStats(stats: { domain: number[]; contrastLimits: number[] }): stats is { domain: [number, number]; contrastLimits: [number, number] } {
return narrowLimits(stats.domain) && narrowLimits(stats.contrastLimits);
}
type RasterSourceLike = {
getRaster: (args: { selection: VivSelection }) => Promise<{ data: any }>;
labels: string[];
shape: number[];
};
export function isRasterSourceLike(value: unknown): value is RasterSourceLike {
if (!value || typeof value !== "object") return false;
return "getRaster" in value && typeof value.getRaster === "function" && "labels" in value && "shape" in value;
}
export function resolveRasterSource(loader: LOADER): RasterSourceLike | undefined {
const seen = new Set<unknown>();
function visit(current: unknown): RasterSourceLike | undefined {
if (!current || typeof current !== "object" || seen.has(current)) {
return undefined;
}
seen.add(current);
if (isRasterSourceLike(current)) {
return current;
}
if (Array.isArray(current)) {
for (let i = current.length - 1; i >= 0; i -= 1) {
const found = visit(current[i]);
if (found) return found;
}
return undefined;
}
if ("data" in current) {
const found = visit(current.data);
if (found) return found;
}
if ("source" in current) {
const found = visit(current.source);
if (found) return found;
}
if ("loader" in current) {
const found = visit(current.loader);
if (found) return found;
}
return undefined;
}
return visit(loader);
}
export function getRasterSource(loader: LOADER): RasterSourceLike {
const source = resolveRasterSource(loader);
if (source) return source;
throw new Error("Expected Viv loader to resolve to a raster source with getRaster().");
}
export async function getSingleSelectionStats2D({ loader, selection }: { loader: LOADER, selection: VivSelection}) {
const data = Array.isArray(loader) ? loader[loader.length - 1] : loader;
const data = getRasterSource(loader);
const raster = await data.getRaster({ selection });
const selectionStats = getChannelStats(raster.data);
if (!narrowStats(selectionStats)) {
Expand All @@ -422,7 +469,7 @@ export async function getSingleSelectionStats2D({ loader, selection }: { loader:
}

export async function getSingleSelectionStats3D({ loader, selection }: { loader: LOADER, selection: VivSelection }) {
const lowResSource = loader[loader.length - 1];
const lowResSource = getRasterSource(loader);
const { shape, labels } = lowResSource;
const sizeZ = shape[labels.indexOf("z")];
const raster0 = await lowResSource.getRaster({
Expand Down Expand Up @@ -531,9 +578,9 @@ export function getPhysicalSizeScalingMatrix(loader: PixelSource | any) {
}

export function getBoundingCube(loader: PixelSource) {
const source = Array.isArray(loader) ? loader[0] : loader;
const source = getRasterSource(loader);
const { shape, labels } = source;
Comment thread
xinaesthete marked this conversation as resolved.
const physicalSizeScalingMatrix = getPhysicalSizeScalingMatrix(source);
const physicalSizeScalingMatrix = getPhysicalSizeScalingMatrix(loader);
const xSlice: [number, number] = [
0,
physicalSizeScalingMatrix[0] * shape[labels.indexOf("x")],
Expand Down
2 changes: 1 addition & 1 deletion packages/avivatorish/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const pkgRoot = fileURLToPath(new URL('.', import.meta.url));
const baseConfig = defineViteConfig({
pkgRoot,
libName: 'SpatialDataAvivatorish',
external: ['@hms-dbmi/viv', '@math.gl/core', 'geotiff', 'zustand'],
external: ['@hms-dbmi/viv', '@math.gl/core', 'geotiff', /^zustand(?:\/.*)?$/],
});

export default mergeConfig(baseConfig, {
Expand Down
5 changes: 3 additions & 2 deletions packages/vis/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
],
"scripts": {
"build": "vite build && tsc --noEmit",
"dev": "vite --config vite.config.demo.ts",
"dev": "node scripts/dev.mjs",
"dev:demo": "vite --config vite.config.demo.ts --host 127.0.0.1 --port 5173 --strictPort",
"watch": "vite build --watch",
"test": "vitest run",
"test:watch": "vitest",
Expand Down Expand Up @@ -61,4 +62,4 @@
"url": "https://github.com/Taylor-CCB-Group/SpatialData.js.git",
"directory": "packages/vis"
}
}
}
61 changes: 61 additions & 0 deletions packages/vis/scripts/dev.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { spawn } from 'node:child_process';

const pnpmCommand = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
const children = [];
let shuttingDown = false;

function start(name, args) {
const child = spawn(pnpmCommand, ['exec', ...args], {
stdio: 'inherit',
env: process.env,
});

child.on('error', (error) => {
if (shuttingDown) {
return;
}

console.error(`[${name}] failed to start:`, error);
shutdown(1);
});

child.on('exit', (code, signal) => {
if (shuttingDown) {
return;
}

if (code === 0 && !signal) {
shutdown(0);
return;
}

console.error(`[${name}] exited with ${signal ?? `code ${code ?? 1}`}`);
shutdown(code ?? 1);
});

children.push(child);
}

function shutdown(code) {
if (shuttingDown) {
return;
}

shuttingDown = true;

for (const child of children) {
child.kill();
}

const timer = setTimeout(() => {
process.exit(code);
}, 500);
timer.unref();
}

process.on('SIGINT', () => shutdown(130));
process.on('SIGTERM', () => shutdown(143));

console.log('Starting vis build watch and demo server...');
start('watch', ['vite', 'build', '--watch']);
start('demo', ['vite', '--config', 'vite.config.demo.ts', '--host', '127.0.0.1', '--port', '5173', '--strictPort']);
8 changes: 4 additions & 4 deletions packages/vis/src/ImageView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
VivProvider,
useChannelsStoreApi,
DEFAULT_CHANNEL_STATE,
resolveRasterSource,
} from '@spatialdata/avivatorish';
import { DetailView, VivViewer, getDefaultInitialViewState } from '@hms-dbmi/viv';
import { useImage } from '@spatialdata/avivatorish';
Expand All @@ -19,9 +20,8 @@ import { useImage } from '@spatialdata/avivatorish';
* the conditions under which this function returns false are conditions where internally it would have pixelWidth undefined, etc.
*/
function _isValidImage(image: ReturnType<typeof useLoader>) {
if (!image) return false;
const source = Array.isArray(image) ? image[0] : image;
return source.shape.length > 0;
const source = resolveRasterSource(image);
return !!source && source.shape.length > 0;
}

function VivImage({ url, width, height }: { url?: string | URL; width: number; height: number }) {
Expand Down Expand Up @@ -66,7 +66,7 @@ function VivImage({ url, width, height }: { url?: string | URL; width: number; h
useEffect(() => {
if (!url) return;
const source = { urlOrFile: url.toString(), description: 'image' };
viewerStore.setState({ source, viewState: null });
viewerStore.setState({ source, viewState: null, metadata: null });
channelsStore.setState({ loader: DEFAULT_CHANNEL_STATE.loader });
}, [url, viewerStore, channelsStore]);

Expand Down
5 changes: 4 additions & 1 deletion packages/vis/vite.config.demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export default defineConfig({
dedupe: ['react', 'react-dom'],
},
server: {
open: true,
host: '127.0.0.1',
port: 5173,
strictPort: true,
open: false,
},
});
2 changes: 1 addition & 1 deletion packages/vis/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const coreSrcIndex = path.resolve(pkgRoot, '../core/src/index.ts');
const baseConfig = defineViteConfig({
pkgRoot,
libName: 'SpatialDataVis',
external: ['@spatialdata/core', '@spatialdata/react'],
external: [/^@spatialdata\/[^/]+$/, /^zustand(?:\/.*)?$/],
});

const testResolve =
Expand Down
8 changes: 7 additions & 1 deletion vite.config.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ export function defineViteConfig(options: DefineConfigOptions) {
formats: ['es'],
},
rollupOptions: {
external: ['react', 'react-dom', ...external],
external: [
'react',
'react-dom',
'react/jsx-runtime',
'react/jsx-dev-runtime',
...external,
],
},
},
});
Expand Down
Loading