Skip to content

Improve visualization of "raw" datasets with array shapes#1768

Merged
axelboc merged 2 commits into
mainfrom
array-vis
Mar 20, 2026
Merged

Improve visualization of "raw" datasets with array shapes#1768
axelboc merged 2 commits into
mainfrom
array-vis

Conversation

@axelboc

@axelboc axelboc commented Mar 14, 2025

Copy link
Copy Markdown
Contributor

This PR improves handling of array datasets by the Raw visualization. The idea is to visualize only one scalar slice at a time instead of the entire array, notably to support stacks of raw JPEG/PNG images.

It's the first of three steps. I'm planning to then:

  • remove Scalar vis and let Raw vis handle all scalars;
  • rename Raw back to Scalar (and everything else called "raw").

This way we'll end up with a single Scalar vis fallback to visualize all scalar values (whether from a scalar dataset or from a scalar slice of an array dataset).


The main challenge was dealing with "scalar selections". I'm talking about selection strings of the form [0-9](,[0-9])* (e.g. "2", "0,1", "2,0,4", etc.) This was previously discussed here.

The first problem came from the providers. h5grove handles these scalar selections as expected — i.e. it returns a scalar value — but h5wasm and HSDS have quirks:

  • h5wasm inconsistently unwraps the scalar depending on the dtype of the dataset — I'll endeavour to contribute a fix to the h5wasm repo at some point.
  • HSDS never unwraps the scalar — a version 1.0.0 should be coming soon that seems to be a major overhaul, so I'll make sure to adapt the provider and test it thoroughly once it's out.

The second problem came from our Value<D extends HasShape & HasType> type, which is not aware of "scalar selections" — i.e. if you give it a dataset with an array shape as parameter, it returns an ArrayValue type, full stop. So the ValueFetcher component, useValue hook and assertValue guard would either lie or fail when encountering scalar slices taken from array datasets.

For a while, I considered making Value<D>, ValueFetcher, useValue and assertValue aware of selections but this made everything way more complicated and messy. Instead, I ended up creating a ScalarFetcher component that handles this case specifically in RawVisContainer without affecting any of the types, hooks and guards.


I've updated sample.h5 (demos: h5grove, h5wasm) by merging compound_nested_scalar and compound_array_vlen_1D into a more comprehensive compound_mixed_1D dataset that demonstrates everything at once (vlen/array dtypes + nested compound with int64 and complex fields).

PROVIDER BEFORE AFTER
h5grove image image
h5wasm image image

Note that the following datasets in sample.h5 also show the dimension mapper now: byte_string_1D, vlen_*_1D.

I've updated the mock file with a similar oneD_compound_mixed dataset, as well as with multiple opaque datasets: oneD_opaque, oneD_opaque_png, and twoD_opaque.

image

The mock oneD_opaque_png dataset demonstrates how the new Array vis handles stacks of PNG images, but you can also try it with frogs.h5:

image

ZIP with h5py script, images and frogs.h5: frogs.zip

@axelboc
axelboc requested a review from loichuder March 14, 2025 14:58
@axelboc axelboc changed the title Add fallback Array vis for array datasets Add fallback Array vis to support stacks of JPEG/PNG images Mar 14, 2025
@axelboc
axelboc marked this pull request as draft March 14, 2025 16:01
@axelboc
axelboc removed the request for review from loichuder March 14, 2025 16:01
@axelboc

This comment was marked as outdated.

@loichuder

This comment was marked as outdated.

@axelboc

This comment was marked as outdated.

@loichuder

This comment was marked as outdated.

Comment thread packages/shared/src/guards.ts Outdated
}

export function isScalarSelection(selection: string | undefined): boolean {
return selection !== undefined && /^\d+(?:,\d+)*$/u.test(selection);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I struggle to understand what you mean by "scalar selection".

Is it a selection that will produce a scalar value ? If so, the test should check also the dataset shape ?

Is it a selection that only has scalar values inside ? In that case, perhaps "selected indices" would be a better name ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a selection that produces a scalar value, like 2,0,1 or 3, as opposed to 2,0,: or :.

The dataset shape doesn't matter here: when we have a selection string, the dataset is expected to have an array shape (cf. assertDatasetValue). The selection string is also expected to have the same number of elements as the number of dimensions in the dataset.


The problem I'm facing (which is why I've switched the PR to draft) is that the Value type has to be aware of the selection so it can conditionally return ScalarValue for "scalar selections".

I've managed to make it work with:

type ValueSelection = (number | null)[];
type ScalarSelection = number[];

export type Value<
  D extends Dataset,
  V extends ValueSelection | undefined = undefined,
> =
  D extends Dataset<infer S, infer T>
    ? S extends ScalarShape
      ? ScalarValue<T>
      : S extends ArrayShape
        ? V extends ScalarSelection
          ? ScalarValue<T>
          : ArrayValue<T>
        : never
    : never;

It requires keeping the selection as an array for longer, which changes quite a few things.

I'm now trying to use the dimension mapping array directly instead of introducing a new structure. I'll keep you updated.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a selection that produces a scalar value, like 2,0,1 or 3, as opposed to 2,0,: or :.

I see. The issue I had is that I am used to Python where you can do stuff like threeD[3] and get a 2D array. In essence, the 3 is a short hand for 3, :, :.

But since we enforce the same number of elements as the number of dimensions in the dataset, we should never have this.

@axelboc
axelboc force-pushed the array-vis branch 2 times, most recently from f224ad4 to 193b553 Compare March 20, 2026 08:34
@axelboc axelboc changed the title Add fallback Array vis to support stacks of JPEG/PNG images Add fallback Array vis Mar 20, 2026
@silx-kit silx-kit deleted a comment from loichuder Mar 20, 2026
@axelboc
axelboc force-pushed the array-vis branch 5 times, most recently from d150840 to 769a09a Compare March 20, 2026 12:01
@axelboc

axelboc commented Mar 20, 2026

Copy link
Copy Markdown
Contributor Author

/approve

@axelboc
axelboc marked this pull request as ready for review March 20, 2026 12:17
@axelboc

axelboc commented Mar 20, 2026

Copy link
Copy Markdown
Contributor Author

I'm finally bringing this PR back to life, as I found a solution I like to the scalar selection problem. Sorry for the spam, it took a while to finalise... I've updated the PR description and deleted or hid all the outdated comments. Here's the deploy preview https://deploy-preview-1768--h5web.netlify.app/

@axelboc
axelboc requested a review from loichuder March 20, 2026 12:18
@axelboc axelboc changed the title Add fallback Array vis Improve _Raw_ vis for datasets with array shapes Mar 20, 2026
@axelboc axelboc changed the title Improve _Raw_ vis for datasets with array shapes Improve Raw vis for datasets with array shapes Mar 20, 2026
@axelboc axelboc changed the title Improve Raw vis for datasets with array shapes Improve visualization of "raw" datasets with array shapes Mar 20, 2026

@loichuder loichuder left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think adding a new fetcher is the way to go.

We know ahead of time whether we want to fetch a scalar since we already have the selection and the number of dims of the dataset.

Comment thread packages/h5wasm/src/worker-utils.ts Outdated
h5wDataset: H5WasmDataset,
selection: string | undefined,
): OutputData | null {
): unknown {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the return type gets in the way a bit and DataProvider#getValue has return type unknown, so it "swallows" whichever return type we specify here. We assert the value after it's returned by the provider, so we don't need to assert it more than strictly necessary inside this function.

const { dims } = entity.shape;
const [dimMapping, setDimMapping] = useDimMappingState({
dims,
axesCount: 0, // slicing only

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remind me what is this for?

@axelboc axelboc Mar 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It says that the visualization doesn't have any "axes" and therefore that every dimension needs to be "sliced". The returned dimMapping is either an empty array if dims is empty (i.e. scalar dataset), or an array with as many zeros as there are dimensions in the array dataset.

This is why we are guaranteed to end up with either an undefined selection for scalar datasets, or a "scalar selection" for array datasets.

As a reminder, when axesCount is 1, dimMapping includes one 'x', and when axesCount is 2, dimMapping includes one 'x' and one 'y'. You can check out the unit tests of initDimMapping for some examples.

@axelboc
axelboc merged commit 0655b47 into main Mar 20, 2026
13 checks passed
@axelboc
axelboc deleted the array-vis branch March 20, 2026 14:57
@axelboc axelboc mentioned this pull request Mar 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants