-
Notifications
You must be signed in to change notification settings - Fork 0
SpatialCanvas displays image along with other layers #18
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
base: main
Are you sure you want to change the base?
Changes from all commits
68c2cb2
2ca9c02
6437972
cd9bd7a
7af367b
9508614
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,91 @@ | ||
| import { Matrix4 } from '@math.gl/core'; | ||
| import type { CoordinateTransformation } from '../schemas'; | ||
| import type { CoordinateTransformation, Axis } from '../schemas'; | ||
|
|
||
| /** | ||
| * Coordinate system reference from NGFF transformations. | ||
| * @see https://github.com/ome/ngff/blob/main/rfc/5/versions/1/index.md | ||
| * | ||
| * Note: `axes` is optional for backward compatibility with older NGFF data, but should be provided | ||
| * for proper transformation handling when coordinate systems include non-spatial axes (e.g., "cyx"). | ||
| * Without axes, transformations fall back to direct mapping which assumes all values are spatial. | ||
| * ^^ Do we really need to be supporting this? ^^ | ||
| */ | ||
| export interface CoordinateSystemRef { | ||
| name: string; | ||
| axes?: Array<{ name: string; type?: string; unit?: string }>; | ||
| axes?: Axis[]; | ||
| } | ||
|
|
||
| /** | ||
| * Map spatial axis values to XYZ coordinates based on axis names. | ||
| * | ||
| * Axes should be validated to have proper `type` fields by the time they reach this function. | ||
| * We check `type === 'space'` to identify spatial axes and map them by name to x, y, z coordinates. | ||
| * | ||
| * Note: Future work could handle mixed units (e.g., different spatial units like 'micrometer' vs 'meter') | ||
| * by converting to a common unit for order-of-magnitude consistency in transformations. | ||
| * | ||
| * @param values - Full transformation value array (ordered according to input axes) | ||
| * @param axes - Array of axis definitions (should be validated Axis types) | ||
| * @param defaultValue - Default value to use when padding (1 for scale, 0 for translation) | ||
| * @returns Array of [x, y, z] values mapped based on axis names | ||
| */ | ||
| function mapSpatialValuesToXYZ( | ||
| values: number[], | ||
| axes?: Axis[], | ||
| defaultValue = 1 | ||
| ): [number, number, number] { | ||
| if (!axes || axes.length === 0) { | ||
| // No axes specified - use direct mapping (backward compatibility) | ||
| console.warn("legacy data with no input axis specification - not really expecting to get here?") | ||
| const [x = defaultValue, y = defaultValue, z = defaultValue] = values; | ||
| return [x, y, z]; | ||
| } | ||
|
|
||
| // Map values to Matrix4 dimensions based on axis name | ||
| // Matrix4 uses standard x, y, z ordering | ||
| let xValue = defaultValue; | ||
| let yValue = defaultValue; | ||
| let zValue = defaultValue; | ||
|
|
||
| // Track spatial axes in order for fallback mapping | ||
| const spatialAxesInOrder: Array<{ name: string; value: number }> = []; | ||
|
|
||
| for (let i = 0; i < axes.length && i < values.length; i++) { | ||
| const axis = axes[i]; | ||
| if (axis.type === 'space') { | ||
| const axisName = axis.name.toLowerCase(); | ||
| const value = values[i] ?? defaultValue; | ||
|
|
||
| // Map by exact axis name match (most common case: "x", "y", "z") | ||
| if (axisName === 'x' && xValue === defaultValue) { | ||
| xValue = value; | ||
| } else if (axisName === 'y' && yValue === defaultValue) { | ||
| yValue = value; | ||
| } else if (axisName === 'z' && zValue === defaultValue) { | ||
| zValue = value; | ||
| } else { | ||
| // Store for fallback mapping if name doesn't match exactly | ||
| spatialAxesInOrder.push({ name: axisName, value }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Fallback: if we have unmapped spatial axes, map them in order | ||
| // This handles cases where axis names don't match x/y/z exactly | ||
| // but preserves the spatial ordering (first → x, second → y, third → z) | ||
| let fallbackIndex = 0; | ||
| for (const { value } of spatialAxesInOrder) { | ||
| if (fallbackIndex === 0 && xValue === defaultValue) { | ||
| xValue = value; | ||
| } else if (fallbackIndex === 1 && yValue === defaultValue) { | ||
| yValue = value; | ||
| } else if (fallbackIndex === 2 && zValue === defaultValue) { | ||
| zValue = value; | ||
| } | ||
| fallbackIndex++; | ||
| } | ||
|
|
||
| return [xValue, yValue, zValue]; | ||
| } | ||
|
Comment on lines
+32
to
89
Contributor
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. Bug: Axis values equal to The conditions on lines 60-68 check Consider tracking whether each dimension has been explicitly set instead of comparing against the default value: 🐛 Proposed fix using explicit assignment tracking // Map values to Matrix4 dimensions based on axis name
// Matrix4 uses standard x, y, z ordering
let xValue = defaultValue;
let yValue = defaultValue;
let zValue = defaultValue;
+ let xSet = false;
+ let ySet = false;
+ let zSet = false;
// Track spatial axes in order for fallback mapping
const spatialAxesInOrder: Array<{ name: string; value: number }> = [];
for (let i = 0; i < axes.length && i < values.length; i++) {
const axis = axes[i];
if (axis.type === 'space') {
const axisName = axis.name.toLowerCase();
const value = values[i] ?? defaultValue;
// Map by exact axis name match (most common case: "x", "y", "z")
- if (axisName === 'x' && xValue === defaultValue) {
+ if (axisName === 'x' && !xSet) {
xValue = value;
- } else if (axisName === 'y' && yValue === defaultValue) {
+ xSet = true;
+ } else if (axisName === 'y' && !ySet) {
yValue = value;
- } else if (axisName === 'z' && zValue === defaultValue) {
+ ySet = true;
+ } else if (axisName === 'z' && !zSet) {
zValue = value;
+ zSet = true;
} else {
// Store for fallback mapping if name doesn't match exactly
spatialAxesInOrder.push({ name: axisName, value });
}
}
}
// Fallback: if we have unmapped spatial axes, map them in order
- let fallbackIndex = 0;
for (const { value } of spatialAxesInOrder) {
- if (fallbackIndex === 0 && xValue === defaultValue) {
+ if (!xSet) {
xValue = value;
- } else if (fallbackIndex === 1 && yValue === defaultValue) {
+ xSet = true;
+ } else if (!ySet) {
yValue = value;
- } else if (fallbackIndex === 2 && zValue === defaultValue) {
+ ySet = true;
+ } else if (!zSet) {
zValue = value;
+ zSet = true;
}
- fallbackIndex++;
}🤖 Prompt for AI Agents |
||
|
|
||
| /** | ||
|
|
@@ -55,7 +134,12 @@ export class Translation extends BaseTransformation { | |
| get type() { return 'translation' as const; } | ||
|
|
||
| toArray(): number[] { | ||
| const [tx, ty, tz = 0] = this.translation; | ||
| // Transformation values are ordered according to the input coordinate system axes. | ||
| // For example, if input axes are ["c", "y", "x"], then translation[0] corresponds to "c", | ||
| // translation[1] to "y", and translation[2] to "x". We map spatial values to Matrix4 | ||
| // dimensions based on axis names (x→x, y→y, z→z) to preserve correct orientation. | ||
| const [tx, ty, tz] = mapSpatialValuesToXYZ(this.translation, this.input?.axes, 0); | ||
|
|
||
| return [ | ||
| 1, 0, 0, 0, | ||
| 0, 1, 0, 0, | ||
|
|
@@ -76,7 +160,12 @@ export class Scale extends BaseTransformation { | |
| get type() { return 'scale' as const; } | ||
|
|
||
| toArray(): number[] { | ||
| const [sx, sy, sz = 1] = this.scale; | ||
| // Transformation values are ordered according to the input coordinate system axes. | ||
| // For example, if input axes are ["c", "y", "x"], then scale[0] corresponds to "c", | ||
| // scale[1] to "y", and scale[2] to "x". We map spatial values to Matrix4 | ||
| // dimensions based on axis names (x→x, y→y, z→z) to preserve correct orientation. | ||
| const [sx, sy, sz] = mapSpatialValuesToXYZ(this.scale, this.input?.axes); | ||
|
|
||
| return [ | ||
| sx, 0, 0, 0, | ||
| 0, sy, 0, 0, | ||
|
|
@@ -99,6 +188,26 @@ export class Affine extends BaseTransformation { | |
| toArray(): number[] { | ||
| const { affine } = this; | ||
|
|
||
| // Validate that affine matrix dimensions match expected spatial dimensions | ||
| // when axes are specified. We assume affine matrices already represent spatial dimensions only. | ||
| if (this.input?.axes) { | ||
| const expectedSpatialDims = this.input.axes.filter(axis => axis.type === 'space').length; | ||
| const actualDims = affine.length; | ||
|
|
||
| // For 2D affine: 2x3 or 3x3 matrices | ||
| // For 3D affine: 3x4 or 4x4 matrices | ||
| // Warn if there's a mismatch | ||
| if (expectedSpatialDims === 2 && actualDims !== 2 && actualDims !== 3) { | ||
| console.warn( | ||
| `Affine matrix dimensions (${actualDims}x${affine[0]?.length}) don't match expected 2D spatial dimensions. Input axes indicate ${expectedSpatialDims} spatial dimensions. Assuming affine matrix represents spatial dimensions only.` | ||
| ); | ||
| } else if (expectedSpatialDims === 3 && actualDims !== 3 && actualDims !== 4) { | ||
| console.warn( | ||
| `Affine matrix dimensions (${actualDims}x${affine[0]?.length}) don't match expected 3D spatial dimensions. Input axes indicate ${expectedSpatialDims} spatial dimensions. Assuming affine matrix represents spatial dimensions only.` | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| if (affine.length === 2 && affine[0].length === 3) { | ||
| // 2x3 affine (2D) - common spatialdata format: [[a, b, tx], [c, d, ty]] | ||
| const [[a, b, tx], [c, d, ty]] = affine; | ||
|
|
||
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.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🌐 Web query:
Zod enum options property public API documentation💡 Result:
z.enum(...)(aZodEnum) exposes an.optionsproperty as part of the documented API, which returns the allowed values as a tuple (e.g.["Salmon", "Tuna", "Trout"]). See the enums documentation showingFishEnum.options. [1], [2]🏁 Script executed:
Repository: Taylor-CCB-Group/SpatialData.js
Length of output: 177
🏁 Script executed:
Repository: Taylor-CCB-Group/SpatialData.js
Length of output: 138
🏁 Script executed:
Repository: Taylor-CCB-Group/SpatialData.js
Length of output: 395
🏁 Script executed:
Repository: Taylor-CCB-Group/SpatialData.js
Length of output: 396
🏁 Script executed:
Repository: Taylor-CCB-Group/SpatialData.js
Length of output: 909
🏁 Script executed:
Repository: Taylor-CCB-Group/SpatialData.js
Length of output: 497
🏁 Script executed:
Repository: Taylor-CCB-Group/SpatialData.js
Length of output: 1197
🏁 Script executed:
Repository: Taylor-CCB-Group/SpatialData.js
Length of output: 313
🏁 Script executed:
Repository: Taylor-CCB-Group/SpatialData.js
Length of output: 311
Replace
_def.valueswith the public.optionsproperty.The function accesses
_def.values, which is an internal implementation detail of Zod that may break on version upgrades. Additionally, the type signature is incorrect—z.ZodEnumuses a tuple type, notRecord<string, string>.Use the public
.optionsproperty instead:♻️ Suggested refactor
🤖 Prompt for AI Agents