Skip to content

feat: generate viewer links dynamically for Zarr datasets#307

Open
allison-truhlar wants to merge 31 commits intomainfrom
viewers-config
Open

feat: generate viewer links dynamically for Zarr datasets#307
allison-truhlar wants to merge 31 commits intomainfrom
viewers-config

Conversation

@allison-truhlar
Copy link
Collaborator

Clickup id: 86advt10e

This PR implements dynamic viewer configuration for Zarr datasets via a build-time viewers.config.yaml file and integration of the @bioimagetools/capability-manifest library for automatic dataset-viewer compatibility detection.

Changes

  • Created viewers.config.yaml with documentation for customizing viewer configuration. This file specifies available viewers for a Fileglancer deployment. The default configuration is our current viewers: Neuroglancer, Avivator, Vol-E, and OME-Zarr Validator. Configuration is bundled at build time.

  • Added ViewersContext for managing viewer configuration. This context:

    • Loads and validates viewer configuration from YAML at initialization
    • Fetches capability manifests using @bioimagetools/capability-manifest library
    • Provides getCompatibleViewers() function to determine dataset-viewer compatibility
      • For viewers with manifests: uses capability-manifest library for compatibility detection
      • For custom viewers: uses user-supplied OME-Zarr versions and requires multiscales metadata
  • Refactored useZarrMetadata to dynamically generate openWithToolUrls using ViewersContext. URLs are generated only for compatible viewers based on the getCompatibleViewers function.

  • Updated DataToolLinks to render viewer buttons dynamically by mapping over valid viewers from ViewersContext. The "Copy URL" tool is always available when a data link is available.

  • Added logo management system with convention-based naming ({name}.png), custom logo override support, and fallback logo for missing assets.

  • Added unit tests for config parsing and component tests for DataToolLinks.

  • Updated documentation with a ViewersConfiguration.md guide and updated CLAUDE.md with viewer configuration instructions.

Added dependencies

  • @bioimagetools/capability-manifest (^0.2.0) for viewer capability detection
  • js-yaml (^4.1.1) and @types/js-yaml (^4.0.9) for YAML parsing

- Custom viewers (validator, vol-e) now check for multiscales
- Ensures viewers only display for OME-Zarr datasets, not plain Zarr arrays
- Update DataToolLinks alt text to use displayName for E2E test compatibility
Add useCallback to memoize the getCompatibleViewers function in
ViewersContext to prevent unnecessary recalculations and improve
performance when filtering compatible viewers based on metadata.
Add more detailed error logging and ensure graceful degradation when:
- Capability manifests fail to load
- Viewer configuration parsing fails
- No valid viewers are configured

The application will continue with an empty viewer list and clear
console messages to help users troubleshoot configuration issues.
Copy link
Member

@neomorphic neomorphic left a comment

Choose a reason for hiding this comment

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

Looks good to me. I was able to enable and disable a viewer using the viewers.config.yaml. I added a few comments about some the code I wasn't sure on.

export type N5OpenWithToolUrls = {
copy: string;
neuroglancer: string;
validator: null;
Copy link
Member

Choose a reason for hiding this comment

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

I don't know what this data structure is doing? Why did three of the viewers get removed, but not neuroglancer?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is only tangentially related to the purpose of this PR - when the support for N5 metadata was added, this type was added and exactly matched the the OpenWithToolUrls type used for the Zarr metadata URL tools display. However, of these tools, N5s can only be opened in Neuroglancer (or copied), so I removed the unneeded tool keys that will never be populated for N5s.

validator: null,
vole: null,
avivator: null
neuroglancer: ''
Copy link
Member

Choose a reason for hiding this comment

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

Again, I am not sure what this dict is doing, but why does neuroglacer remain and not the others?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See this reply: #307 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

There is a lot of custom code validation going on here. Have you looked at zod as a way to do the validation? I understand if we don't want to introduce another module, but if there are other places where we do validation, this might be worth it.

Copy link
Collaborator Author

@allison-truhlar allison-truhlar Feb 3, 2026

Choose a reason for hiding this comment

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

I had asked Claude about using zod at one point, but due to the conditional validation for certain fields based on whether a viewer has a capability manifest or not, Claude said it wasn't worth it. Checking with Claude again today, it said it was so I added a commit of what the validation looks like with zod to test it out. It's still a little complex due to needing a second pass of custom validation for the capability manifests, and also because I wanted to keep more helpful error messages with the viewer name where the validation failed, where possible: 7ec34ec.

I think it's worth keeping with the advantage being that if we want to add another field that has straightforward validation (ie., doesn't need to be validated differently if the parent viewer has a capability manifest or not), it's very easy to add this validation by just adding the field to the zod schema. And, to your point, if we end up needing validation elsewhere in the app, zod will already be installed.

- previously, if a logo path wasn't found, a 404 URL was still created, so it was never falling through to the fallback_logo.
- includes checking that a fallback_logo is used when no logo is found for a viewer
if (viewer.key === 'neuroglancer') {
// Extract base URL from template (everything before #!)
const neuroglancerBaseUrl = viewer.urlTemplate.split('#!')[0] + '#!';
if (disableNeuroglancerStateGeneration) {
Copy link
Collaborator Author

@allison-truhlar allison-truhlar Feb 3, 2026

Choose a reason for hiding this comment

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

We are generating state for Neuroglancer links in Fileglancer, but the Neuroglancer template url currently used in the bioimagetools/capability-manifests assumes you're not https://github.com/BioImageTools/capability-manifest/blob/9e65138b0608b31c886e7a4ec7f54782794b6256/public/viewers/neuroglancer.yaml#L5C3-L5C123. This is just a temporary fix for now - we should think about whether it makes sense to rely on those template urls, how they need to be formatted, and, in the case of Neuroglancer, if it will expect generated state or not

if (url) {
// Extract base URL from template (everything before #!)
const neuroglancerBaseUrl =
viewer.urlTemplate.split('#!')[0] + '#!';
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

see comment above - this is a temporary fix

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