Skip to content

Commit 4ac2016

Browse files
authored
Merge pull request galaxyproject#22420 from davelopez/feature/add-iiif-filesource
Add IIIF File Source Support
2 parents 99af84d + b570b22 commit 4ac2016

9 files changed

Lines changed: 152 additions & 0 deletions

File tree

client/src/api/fileSources.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ export const templateTypes: FileSourceTypesDetail = {
7878
icon: faHubspot,
7979
message: "This is a file repository plugin that connects with the Hugging Face Hub.",
8080
},
81+
iiif: {
82+
icon: faNetworkWired,
83+
message:
84+
"This is a read-only file repository plugin that connects to IIIF (International Image Interoperability Framework) sources. IIIF is a framework widely used by museums, libraries, and archives for delivering high-resolution image-based cultural heritage materials.",
85+
},
8186
omero: {
8287
icon: faNetworkWired,
8388
message: "This is a file repository plugin that connects with an OMERO server.",

client/src/api/schema/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12846,6 +12846,7 @@ export interface components {
1284612846
| "rspace"
1284712847
| "dataverse"
1284812848
| "huggingface"
12849+
| "iiif"
1284912850
| "omero";
1285012851
/** Variables */
1285112852
variables?:
@@ -24485,6 +24486,7 @@ export interface components {
2448524486
| "rspace"
2448624487
| "dataverse"
2448724488
| "huggingface"
24489+
| "iiif"
2448824490
| "omero";
2448924491
/** Uri Root */
2449024492
uri_root: string;

client/src/utils/url.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const URI_PREFIXES = [
3131
"elabftw://",
3232
"zip://",
3333
"ascp://",
34+
"iiif://",
3435
];
3536

3637
export function isUrl(content: string): boolean {

lib/galaxy/config/sample/file_sources_conf.yml.sample

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,12 @@
313313
api_key: ${user.user_vault.read_secret('preferences/elabftw/api_key')}
314314
writable: true
315315
endpoint: ${user.preferences['elabftw|endpoint']}
316+
317+
# IIIF (International Image Interoperability Framework) file source
318+
# For examples and to find public IIIF resources, visit: [https://iiif.io/guides/finding_resources/](https://iiif.io/guides/finding_resources/)
319+
- type: iiif
320+
id: iiif-cambridge-scientific-instrument
321+
label: Cambridge Scientific Instrument Company
322+
doc: Browse and import canvases from the Cambridge Digital Library
323+
manifest_url: "https://cudl.lib.cam.ac.uk/iiif/collection/csic"
324+
writable: false

lib/galaxy/dependencies/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,9 @@ def check_huggingface_hub(self):
356356
def check_omero_py(self):
357357
return "omero" in self.file_sources
358358

359+
def check_iiif_fsspec(self):
360+
return "iiif" in self.file_sources
361+
359362

360363
def strip_comment(line):
361364
# lifted from https://github.com/tox-dev/tox/commit/3c6b4f204e89852c4b7536b246a66d20be6d39ec

lib/galaxy/dependencies/conditional-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ rspace-client>=2.6.1,<3 # type: rspace
3636
adlfs
3737
huggingface_hub
3838
omero-py #type: omero
39+
iiif-fsspec # type: iiif
3940

4041
# Vault backend
4142
hvac

lib/galaxy/files/sources/iiif.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import os
2+
from typing import Union
3+
4+
from fsspec import AbstractFileSystem
5+
6+
from galaxy.exceptions import MessageException
7+
from galaxy.files.models import (
8+
AnyRemoteEntry,
9+
FilesSourceRuntimeContext,
10+
RemoteDirectory,
11+
RemoteFile,
12+
)
13+
from galaxy.files.sources._defaults import DEFAULT_SCHEME
14+
from galaxy.files.sources._fsspec import (
15+
CacheOptionsDictType,
16+
FsspecBaseFileSourceConfiguration,
17+
FsspecBaseFileSourceTemplateConfiguration,
18+
FsspecFilesSource,
19+
)
20+
from galaxy.util.config_templates import TemplateExpansion
21+
22+
try:
23+
from iiif_fsspec import IIIFFileSystem
24+
except ImportError:
25+
IIIFFileSystem = None
26+
27+
28+
class IIIFFileSourceTemplateConfiguration(FsspecBaseFileSourceTemplateConfiguration):
29+
manifest_url: Union[str, TemplateExpansion]
30+
31+
32+
class IIIFFileSourceConfiguration(FsspecBaseFileSourceConfiguration):
33+
manifest_url: str
34+
35+
36+
class IIIFFilesSource(FsspecFilesSource[IIIFFileSourceTemplateConfiguration, IIIFFileSourceConfiguration]):
37+
plugin_type = "iiif"
38+
required_module = IIIFFileSystem
39+
required_package = "iiif-fsspec"
40+
41+
template_config_class = IIIFFileSourceTemplateConfiguration
42+
resolved_config_class = IIIFFileSourceConfiguration
43+
44+
def _open_fs(
45+
self,
46+
_context: FilesSourceRuntimeContext[IIIFFileSourceConfiguration],
47+
cache_options: CacheOptionsDictType,
48+
) -> AbstractFileSystem:
49+
if IIIFFileSystem is None:
50+
raise self.required_package_exception
51+
52+
return IIIFFileSystem(**cache_options)
53+
54+
def _to_filesystem_path(self, path: str, config: IIIFFileSourceConfiguration) -> str:
55+
if path in ("", "/"):
56+
return self._normalize_manifest_url(config.manifest_url)
57+
return path.lstrip("/")
58+
59+
def _info_to_entry(self, info: dict, config: IIIFFileSourceConfiguration) -> AnyRemoteEntry:
60+
filesystem_path = info["name"]
61+
entry_path = filesystem_path
62+
entry_name = self._entry_name_from_info(info, filesystem_path)
63+
uri = self.uri_from_path(entry_path)
64+
65+
if info.get("type") == "directory":
66+
return RemoteDirectory(name=entry_name, uri=uri, path=entry_path)
67+
68+
size = int(info.get("size", 0))
69+
ctime = self._get_formatted_timestamp(info)
70+
hashes = self._get_file_hashes(info)
71+
return RemoteFile(name=entry_name, size=size, ctime=ctime, uri=uri, path=entry_path, hashes=hashes)
72+
73+
def _entry_name_from_info(self, info: dict, filesystem_path: str) -> str:
74+
iiif_label = info.get("iiif_label")
75+
if isinstance(iiif_label, str) and iiif_label:
76+
return iiif_label
77+
return os.path.basename(filesystem_path.rstrip("/"))
78+
79+
@staticmethod
80+
def _normalize_manifest_url(manifest_url: str) -> str:
81+
return manifest_url.rstrip("/")
82+
83+
def _write_from(
84+
self,
85+
_target_path: str,
86+
_native_path: str,
87+
_context: FilesSourceRuntimeContext[IIIFFileSourceConfiguration],
88+
):
89+
raise MessageException("IIIF file sources are read-only and do not support exporting files.")
90+
91+
def get_scheme(self) -> str:
92+
return self.scheme if self.scheme and self.scheme != DEFAULT_SCHEME else "iiif"
93+
94+
95+
__all__ = ("IIIFFilesSource",)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
- id: iiif
2+
version: 0
3+
name: IIIF Manifest
4+
description: |
5+
Browse and import image canvases from an [IIIF (International Image Interoperability Framework)](https://iiif.io/) Presentation API manifest. IIIF is a framework used by museums, libraries, and archives to deliver high-quality digital images of cultural heritage materials through a standardized API. You can find public IIIF resources at [https://iiif.io/guides/finding_resources/](https://iiif.io/guides/finding_resources/).
6+
7+
The images imported from the IIIF manifest will be added to your Galaxy history as new datasets, allowing you to analyze and work with them using Galaxy's tools and workflows.
8+
variables:
9+
manifest_url:
10+
label: IIIF Manifest URL
11+
type: string
12+
help: |
13+
Full URL to a IIIF Presentation manifest JSON document. IIIF manifests are structured descriptions of digitized collections that can include metadata, image sequences, and navigation information.
14+
15+
For examples and to find public IIIF resources, visit: [https://iiif.io/guides/finding_resources/](https://iiif.io/guides/finding_resources/)
16+
17+
Example: https://iiif.io/api/cookbook/recipe/0001-mvm-image/manifest.json
18+
configuration:
19+
type: iiif
20+
manifest_url: "{{ variables.manifest_url }}"

lib/galaxy/files/templates/models.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"rspace",
4949
"dataverse",
5050
"huggingface",
51+
"iiif",
5152
"omero",
5253
]
5354

@@ -353,6 +354,18 @@ class HuggingFaceFileSourceConfiguration(StrictModel):
353354
endpoint: Optional[str] = None
354355

355356

357+
class IIIFFileSourceTemplateConfiguration(StrictModel):
358+
type: Literal["iiif"]
359+
manifest_url: Union[str, TemplateExpansion]
360+
template_start: Optional[str] = None
361+
template_end: Optional[str] = None
362+
363+
364+
class IIIFFileSourceConfiguration(StrictModel):
365+
type: Literal["iiif"]
366+
manifest_url: str
367+
368+
356369
class OmeroFileSourceTemplateConfiguration(StrictModel):
357370
type: Literal["omero"]
358371
username: Union[str, TemplateExpansion]
@@ -391,6 +404,7 @@ class OmeroFileSourceConfiguration(StrictModel):
391404
RSpaceFileSourceTemplateConfiguration,
392405
DataverseFileSourceTemplateConfiguration,
393406
HuggingFaceFileSourceTemplateConfiguration,
407+
IIIFFileSourceTemplateConfiguration,
394408
OmeroFileSourceTemplateConfiguration,
395409
],
396410
Field(discriminator="type"),
@@ -414,6 +428,7 @@ class OmeroFileSourceConfiguration(StrictModel):
414428
RSpaceFileSourceConfiguration,
415429
DataverseFileSourceConfiguration,
416430
HuggingFaceFileSourceConfiguration,
431+
IIIFFileSourceConfiguration,
417432
OmeroFileSourceConfiguration,
418433
],
419434
Field(discriminator="type"),
@@ -495,6 +510,7 @@ def template_to_configuration(
495510
"rspace": RSpaceFileSourceConfiguration,
496511
"dataverse": DataverseFileSourceConfiguration,
497512
"huggingface": HuggingFaceFileSourceConfiguration,
513+
"iiif": IIIFFileSourceConfiguration,
498514
"omero": OmeroFileSourceConfiguration,
499515
}
500516

0 commit comments

Comments
 (0)