imaging-metadata-converter 2.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- imaging_metadata_converter-2.0.1/LICENSE +21 -0
- imaging_metadata_converter-2.0.1/PKG-INFO +260 -0
- imaging_metadata_converter-2.0.1/README.md +220 -0
- imaging_metadata_converter-2.0.1/pyproject.toml +39 -0
- imaging_metadata_converter-2.0.1/setup.cfg +4 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/AcquisitionMetadataMapper.py +1393 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/ModelPaths.py +165 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/__init__.py +45 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/mappings/combinations.json +378 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/mappings/mappings.json +561 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/models/imaging.yaml +13528 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/models/imaging_extension.yaml +598 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/models/imaging_provenance.yaml +76 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/models/imaging_units.yaml +1783 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter.egg-info/PKG-INFO +260 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter.egg-info/SOURCES.txt +28 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter.egg-info/dependency_links.txt +1 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter.egg-info/requires.txt +6 -0
- imaging_metadata_converter-2.0.1/src/imaging_metadata_converter.egg-info/top_level.txt +1 -0
- imaging_metadata_converter-2.0.1/tests/test_acquisition_metadata_mapper.py +1047 -0
- imaging_metadata_converter-2.0.1/tests/test_dataset_exporter.py +281 -0
- imaging_metadata_converter-2.0.1/tests/test_docs_data.py +83 -0
- imaging_metadata_converter-2.0.1/tests/test_examples.py +54 -0
- imaging_metadata_converter-2.0.1/tests/test_linkml_converter.py +332 -0
- imaging_metadata_converter-2.0.1/tests/test_metaseed_generator.py +201 -0
- imaging_metadata_converter-2.0.1/tests/test_metaseed_validation.py +78 -0
- imaging_metadata_converter-2.0.1/tests/test_model_fit.py +155 -0
- imaging_metadata_converter-2.0.1/tests/test_model_paths.py +66 -0
- imaging_metadata_converter-2.0.1/tests/test_no_data_loss.py +265 -0
- imaging_metadata_converter-2.0.1/tests/test_redundancy.py +194 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NL-BioImaging AM
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: imaging-metadata-converter
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
Summary: Convert custom imaging acquisition metadata to common metadata
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 NL-BioImaging AM
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Project-URL: Homepage, https://github.com/NL-BioImaging/imaging-metadata-converter
|
|
28
|
+
Keywords: imaging,metadata,microscopy,ome,acquisition
|
|
29
|
+
Classifier: Programming Language :: Python :: 3
|
|
30
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
31
|
+
Requires-Python: >=3.9
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Requires-Dist: linkml-runtime>=1.9
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
37
|
+
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
38
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# imaging-metadata-converter
|
|
42
|
+
|
|
43
|
+
Convert custom (vendor-specific) imaging acquisition metadata to common metadata.
|
|
44
|
+
|
|
45
|
+
Dict in, dict out - no file I/O, no CLI. The target is the imaging model, a
|
|
46
|
+
LinkML model built from LiMi and extended with electron-microscopy and other
|
|
47
|
+
imaging metadata; the one dependency is `linkml-runtime`, for reading it.
|
|
48
|
+
|
|
49
|
+
The model, and the metaseed profile made from it, are maintained here (see
|
|
50
|
+
Maintaining the model); they were developed in
|
|
51
|
+
[imaging-metadata-consolidator](https://github.com/NL-BioImaging/imaging-metadata-consolidator),
|
|
52
|
+
which this repository replaces.
|
|
53
|
+
|
|
54
|
+
Documentation, with a browser for the whole model and a map of what the
|
|
55
|
+
converter produces: <https://nl-bioimaging.github.io/imaging-metadata-converter/>
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install .
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or, for development (editable install plus the tests):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install -e .
|
|
67
|
+
python -m pytest tests
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
### 1. Convert a metadata dict
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from imaging_metadata_converter import convert_metadata
|
|
76
|
+
|
|
77
|
+
custom = {
|
|
78
|
+
'Make': 'Acme',
|
|
79
|
+
'Model': 'Widget-1000',
|
|
80
|
+
'Scan': {'ResolutionX': 1024, 'ResolutionY': 768},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
common = convert_metadata(custom)
|
|
84
|
+
# {'Instrument': {'Manufacturer': 'Acme', 'Model': 'Widget-1000'},
|
|
85
|
+
# 'Pixels': {'SizeX': 1024, 'SizeY': 768},
|
|
86
|
+
# 'SourceMap': {'Instrument.Manufacturer': 'Make', 'Instrument.Model': 'Model',
|
|
87
|
+
# 'Pixels.SizeX': 'Scan.ResolutionX', 'Pixels.SizeY': 'Scan.ResolutionY'}}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`SourceMap` records the source path of every output field, so renamed and
|
|
91
|
+
collapsed keys stay recoverable from the output alone.
|
|
92
|
+
|
|
93
|
+
A value the model spells otherwise, listed as an alias of one of its
|
|
94
|
+
enumeration values, is written in the model's spelling: Leica's immersion
|
|
95
|
+
`"OIL"` as `Oil`, BigDataViewer's unit `"micron"` as `µm`. Its `SourceMap`
|
|
96
|
+
entry then records the source's spelling too:
|
|
97
|
+
`{"Source": "...Immersion", "SourceValue": "OIL"}`.
|
|
98
|
+
|
|
99
|
+
The input is whatever metadata dict you already extracted from your file or
|
|
100
|
+
acquisition software; the output is the same information placed on the common
|
|
101
|
+
model. Nested dicts and lists of dicts are walked recursively.
|
|
102
|
+
|
|
103
|
+
### 2. Reuse the mapper for many files
|
|
104
|
+
|
|
105
|
+
The model and mappings are parsed once per mapper instance, so create one
|
|
106
|
+
mapper and convert repeatedly rather than calling `convert_metadata` in a
|
|
107
|
+
tight loop:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import json
|
|
111
|
+
from pathlib import Path
|
|
112
|
+
|
|
113
|
+
from imaging_metadata_converter import AcquisitionMetadataMapper
|
|
114
|
+
|
|
115
|
+
mapper = AcquisitionMetadataMapper()
|
|
116
|
+
|
|
117
|
+
for path in Path('examples').glob('*.json'):
|
|
118
|
+
custom = json.loads(path.read_text(encoding='utf-8'))
|
|
119
|
+
common = mapper.convert_metadata(custom)
|
|
120
|
+
print(path.name, sorted(common))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. Use your own model or mappings
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
mapper = AcquisitionMetadataMapper(
|
|
127
|
+
schema_file='my_model.yaml', mappings_file='my_mappings.json',
|
|
128
|
+
combinations_file='my_combinations.json')
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Each argument accepts a file path; omitting one uses the packaged file. The
|
|
132
|
+
model is a LinkML model like `models/imaging.yaml`.
|
|
133
|
+
|
|
134
|
+
## Examples
|
|
135
|
+
|
|
136
|
+
`examples/` holds real acquisition metadata from a range of instruments, used
|
|
137
|
+
by `tests/test_examples.py` and handy as input while extending the mappings:
|
|
138
|
+
|
|
139
|
+
| Example | Source |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `Cikteq SEM4000x Automap.json` | Cikteq SEM4000x (Automap) |
|
|
142
|
+
| `Cikteq SEM4000x Normal.json` | Cikteq SEM4000x (Normal) |
|
|
143
|
+
| `Delmic FAST-EM.json` | Delmic FAST-EM |
|
|
144
|
+
| `EMSIS Xarosa.json` | EMSIS Xarosa |
|
|
145
|
+
| `TFS Phenom Pharos.json` | Thermo Fisher Phenom Pharos |
|
|
146
|
+
| `TFS TALOSF.json`, `TFS TALOSF 2.json` | Thermo Fisher Talos F |
|
|
147
|
+
| `Zeiss Supra55 Fibics ATLAS.json` | Zeiss Supra55 (Fibics ATLAS) |
|
|
148
|
+
| `ome-tiff.json` | OME-TIFF derived metadata |
|
|
149
|
+
| `dicom.json` | DICOM (dummy patient data) |
|
|
150
|
+
| `lif_metadata.json`, `lif_tilescan_metadata.json` | Leica LIF (LAS X) |
|
|
151
|
+
| `lif_sp5_metadata.json` | Leica LIF (LAS AF, a TCS SP5) |
|
|
152
|
+
| `lif_lmd7_metadata.json` | Leica LIF (LMD7 laser microdissection) |
|
|
153
|
+
| `platy_tomography.json` | BigDataViewer (SpimData) tomography |
|
|
154
|
+
| `svs_metadata.json` | Aperio SVS |
|
|
155
|
+
|
|
156
|
+
`output/` holds what the converter makes of each, as YAML, written by
|
|
157
|
+
`scripts/convert_examples.py`.
|
|
158
|
+
|
|
159
|
+
## How mapping works
|
|
160
|
+
|
|
161
|
+
Each source value is placed by a rule in `mappings/mappings.json`, else where
|
|
162
|
+
its path's end names a model path (an OME-derived source's `Pixels.SizeX`);
|
|
163
|
+
`mappings/combinations.json` builds values from several (a date from its
|
|
164
|
+
date, time and zone), and a few vendor steps join what a source spreads over
|
|
165
|
+
its structure (Leica's sequential channels, a detector's settings for the
|
|
166
|
+
image). A value is never written over another: where its target is taken, it
|
|
167
|
+
stays at its source path, as does every value no rule or model path names,
|
|
168
|
+
and the output's `SourceMap` names the source of every value, so nothing is
|
|
169
|
+
lost and every value can be traced.
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
"Beam.WD": "Image.ElectronBeamSettings.WorkingDistance.Value",
|
|
173
|
+
"PixelSpacing[0]": {"target": "Pixels.PhysicalSizeY", "unit": "mm"},
|
|
174
|
+
"Vacuum.*": "Instrument.Vacuum"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
[How the mapping works](https://nl-bioimaging.github.io/imaging-metadata-converter/mapping/)
|
|
178
|
+
(`docs/mapping.md`) describes it in full, with examples: model paths, every
|
|
179
|
+
form of rule, matching by name, vendor wrappers, combinations, the vendor
|
|
180
|
+
steps and the SourceMap.
|
|
181
|
+
|
|
182
|
+
## The model
|
|
183
|
+
|
|
184
|
+
`models/imaging.yaml` is LiMi as a LinkML model, importing
|
|
185
|
+
`imaging_extension.yaml` (metadata beyond LiMi, mostly electron microscopy),
|
|
186
|
+
`imaging_provenance.yaml` (`Property`, `SourceFile`, `SourceMapping`) and
|
|
187
|
+
`imaging_units.yaml` (the unit enumerations). `ModelPaths` reads it into the
|
|
188
|
+
dotted paths the mapper uses:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from imaging_metadata_converter import ModelPaths
|
|
192
|
+
|
|
193
|
+
tree = ModelPaths().tree()
|
|
194
|
+
tree['Pixels']['PhysicalSizeX']
|
|
195
|
+
# 'PositiveFloat'
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Every leaf's value is its range: a type (`string`, `float`, `datetime`, ...),
|
|
199
|
+
an enumeration (`UnitsLength`, ...) or, for a reference, the class it refers
|
|
200
|
+
to. The ranges are **descriptive, not enforced**: the mapper matches on paths
|
|
201
|
+
only, never validates a value against its range and never coerces one, so
|
|
202
|
+
whatever the source held is written through unchanged (combinations aside).
|
|
203
|
+
|
|
204
|
+
The [model page](https://nl-bioimaging.github.io/imaging-metadata-converter/model/)
|
|
205
|
+
of the documentation browses every path, flags the ones the extensions add
|
|
206
|
+
and the ones a mapping rule targets, and describes where the model comes from;
|
|
207
|
+
[how the mapping works](https://nl-bioimaging.github.io/imaging-metadata-converter/mapping/)
|
|
208
|
+
describes, with examples, how a source's values reach them.
|
|
209
|
+
|
|
210
|
+
## Data files
|
|
211
|
+
|
|
212
|
+
- `src/imaging_metadata_converter/models/imaging.yaml` and its imports - the
|
|
213
|
+
imaging model (see The model).
|
|
214
|
+
- `src/imaging_metadata_converter/mappings/mappings.json` - source field to
|
|
215
|
+
model field mappings.
|
|
216
|
+
- `src/imaging_metadata_converter/mappings/combinations.json` - values built
|
|
217
|
+
from others (see Combinations).
|
|
218
|
+
|
|
219
|
+
## Maintaining the model
|
|
220
|
+
|
|
221
|
+
Outside the package, `scripts/` holds the tools the model is maintained with;
|
|
222
|
+
[Maintaining the model](https://nl-bioimaging.github.io/imaging-metadata-converter/maintaining/)
|
|
223
|
+
in the documentation describes them in full.
|
|
224
|
+
|
|
225
|
+
- `scripts/linkml_converter.py` - converted the LiMi XSD (`reference/`) into
|
|
226
|
+
the model, once; the model is edited by hand since, so it refuses to
|
|
227
|
+
overwrite it without `--force`. Kept for comparing a future LiMi XSD.
|
|
228
|
+
- `scripts/metaseed_generator.py` - generates the metaseed profile
|
|
229
|
+
`profile/imaging.metaseed.yaml` (`imaging` 2.0; 1.1 is published on the
|
|
230
|
+
metaseed Hub) from the model; rerun it after a change to the model.
|
|
231
|
+
- `scripts/dataset_exporter.py` - exports each example as a metaseed dataset
|
|
232
|
+
of the profile into `export/`, with every value the model has no field for
|
|
233
|
+
kept as a `Property` record.
|
|
234
|
+
- `scripts/model_fit.py` - how well each example can be expressed in the
|
|
235
|
+
model, from its dataset in `export/`: whether the output keeps and traces
|
|
236
|
+
every input value (100% each), the source keys covered by a rule or
|
|
237
|
+
automatically, and the ones not covered, with why. `analyse()` returns
|
|
238
|
+
these statistics for any conversion, `analyse_metadata()` for a source dict
|
|
239
|
+
(also the docs' Model fit page).
|
|
240
|
+
|
|
241
|
+
The tests check that `profile/` and `export/` are up to date and lose no
|
|
242
|
+
data; the tests of the XSD conversion need `linkml` and those validating
|
|
243
|
+
`export/` with metaseed need `metaseed`, and are skipped without them.
|
|
244
|
+
|
|
245
|
+
## Documentation
|
|
246
|
+
|
|
247
|
+
The documentation site is built with MkDocs from `docs/` and published to
|
|
248
|
+
GitHub Pages on every push to `main`. To build it locally:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
pip install -e ".[docs]"
|
|
252
|
+
mkdocs serve
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Nothing on the model pages is stored in the repository. The MkDocs hook
|
|
256
|
+
`scripts/docs_data.py` reads the packaged model and mappings as the site is
|
|
257
|
+
built: it writes the JSON the model browser fetches (`data/model.json`,
|
|
258
|
+
`added.json`, `mappings.json`, `details.json`) into the site, fills in the model's counts on
|
|
259
|
+
`model.md`, and the examples' fit on `model-fit.md`. A new version of the model files or an edited
|
|
260
|
+
`mappings.json` shows up on the next build, with nothing to regenerate.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# imaging-metadata-converter
|
|
2
|
+
|
|
3
|
+
Convert custom (vendor-specific) imaging acquisition metadata to common metadata.
|
|
4
|
+
|
|
5
|
+
Dict in, dict out - no file I/O, no CLI. The target is the imaging model, a
|
|
6
|
+
LinkML model built from LiMi and extended with electron-microscopy and other
|
|
7
|
+
imaging metadata; the one dependency is `linkml-runtime`, for reading it.
|
|
8
|
+
|
|
9
|
+
The model, and the metaseed profile made from it, are maintained here (see
|
|
10
|
+
Maintaining the model); they were developed in
|
|
11
|
+
[imaging-metadata-consolidator](https://github.com/NL-BioImaging/imaging-metadata-consolidator),
|
|
12
|
+
which this repository replaces.
|
|
13
|
+
|
|
14
|
+
Documentation, with a browser for the whole model and a map of what the
|
|
15
|
+
converter produces: <https://nl-bioimaging.github.io/imaging-metadata-converter/>
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or, for development (editable install plus the tests):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install -e .
|
|
27
|
+
python -m pytest tests
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
### 1. Convert a metadata dict
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from imaging_metadata_converter import convert_metadata
|
|
36
|
+
|
|
37
|
+
custom = {
|
|
38
|
+
'Make': 'Acme',
|
|
39
|
+
'Model': 'Widget-1000',
|
|
40
|
+
'Scan': {'ResolutionX': 1024, 'ResolutionY': 768},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
common = convert_metadata(custom)
|
|
44
|
+
# {'Instrument': {'Manufacturer': 'Acme', 'Model': 'Widget-1000'},
|
|
45
|
+
# 'Pixels': {'SizeX': 1024, 'SizeY': 768},
|
|
46
|
+
# 'SourceMap': {'Instrument.Manufacturer': 'Make', 'Instrument.Model': 'Model',
|
|
47
|
+
# 'Pixels.SizeX': 'Scan.ResolutionX', 'Pixels.SizeY': 'Scan.ResolutionY'}}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`SourceMap` records the source path of every output field, so renamed and
|
|
51
|
+
collapsed keys stay recoverable from the output alone.
|
|
52
|
+
|
|
53
|
+
A value the model spells otherwise, listed as an alias of one of its
|
|
54
|
+
enumeration values, is written in the model's spelling: Leica's immersion
|
|
55
|
+
`"OIL"` as `Oil`, BigDataViewer's unit `"micron"` as `µm`. Its `SourceMap`
|
|
56
|
+
entry then records the source's spelling too:
|
|
57
|
+
`{"Source": "...Immersion", "SourceValue": "OIL"}`.
|
|
58
|
+
|
|
59
|
+
The input is whatever metadata dict you already extracted from your file or
|
|
60
|
+
acquisition software; the output is the same information placed on the common
|
|
61
|
+
model. Nested dicts and lists of dicts are walked recursively.
|
|
62
|
+
|
|
63
|
+
### 2. Reuse the mapper for many files
|
|
64
|
+
|
|
65
|
+
The model and mappings are parsed once per mapper instance, so create one
|
|
66
|
+
mapper and convert repeatedly rather than calling `convert_metadata` in a
|
|
67
|
+
tight loop:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import json
|
|
71
|
+
from pathlib import Path
|
|
72
|
+
|
|
73
|
+
from imaging_metadata_converter import AcquisitionMetadataMapper
|
|
74
|
+
|
|
75
|
+
mapper = AcquisitionMetadataMapper()
|
|
76
|
+
|
|
77
|
+
for path in Path('examples').glob('*.json'):
|
|
78
|
+
custom = json.loads(path.read_text(encoding='utf-8'))
|
|
79
|
+
common = mapper.convert_metadata(custom)
|
|
80
|
+
print(path.name, sorted(common))
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3. Use your own model or mappings
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
mapper = AcquisitionMetadataMapper(
|
|
87
|
+
schema_file='my_model.yaml', mappings_file='my_mappings.json',
|
|
88
|
+
combinations_file='my_combinations.json')
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Each argument accepts a file path; omitting one uses the packaged file. The
|
|
92
|
+
model is a LinkML model like `models/imaging.yaml`.
|
|
93
|
+
|
|
94
|
+
## Examples
|
|
95
|
+
|
|
96
|
+
`examples/` holds real acquisition metadata from a range of instruments, used
|
|
97
|
+
by `tests/test_examples.py` and handy as input while extending the mappings:
|
|
98
|
+
|
|
99
|
+
| Example | Source |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| `Cikteq SEM4000x Automap.json` | Cikteq SEM4000x (Automap) |
|
|
102
|
+
| `Cikteq SEM4000x Normal.json` | Cikteq SEM4000x (Normal) |
|
|
103
|
+
| `Delmic FAST-EM.json` | Delmic FAST-EM |
|
|
104
|
+
| `EMSIS Xarosa.json` | EMSIS Xarosa |
|
|
105
|
+
| `TFS Phenom Pharos.json` | Thermo Fisher Phenom Pharos |
|
|
106
|
+
| `TFS TALOSF.json`, `TFS TALOSF 2.json` | Thermo Fisher Talos F |
|
|
107
|
+
| `Zeiss Supra55 Fibics ATLAS.json` | Zeiss Supra55 (Fibics ATLAS) |
|
|
108
|
+
| `ome-tiff.json` | OME-TIFF derived metadata |
|
|
109
|
+
| `dicom.json` | DICOM (dummy patient data) |
|
|
110
|
+
| `lif_metadata.json`, `lif_tilescan_metadata.json` | Leica LIF (LAS X) |
|
|
111
|
+
| `lif_sp5_metadata.json` | Leica LIF (LAS AF, a TCS SP5) |
|
|
112
|
+
| `lif_lmd7_metadata.json` | Leica LIF (LMD7 laser microdissection) |
|
|
113
|
+
| `platy_tomography.json` | BigDataViewer (SpimData) tomography |
|
|
114
|
+
| `svs_metadata.json` | Aperio SVS |
|
|
115
|
+
|
|
116
|
+
`output/` holds what the converter makes of each, as YAML, written by
|
|
117
|
+
`scripts/convert_examples.py`.
|
|
118
|
+
|
|
119
|
+
## How mapping works
|
|
120
|
+
|
|
121
|
+
Each source value is placed by a rule in `mappings/mappings.json`, else where
|
|
122
|
+
its path's end names a model path (an OME-derived source's `Pixels.SizeX`);
|
|
123
|
+
`mappings/combinations.json` builds values from several (a date from its
|
|
124
|
+
date, time and zone), and a few vendor steps join what a source spreads over
|
|
125
|
+
its structure (Leica's sequential channels, a detector's settings for the
|
|
126
|
+
image). A value is never written over another: where its target is taken, it
|
|
127
|
+
stays at its source path, as does every value no rule or model path names,
|
|
128
|
+
and the output's `SourceMap` names the source of every value, so nothing is
|
|
129
|
+
lost and every value can be traced.
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
"Beam.WD": "Image.ElectronBeamSettings.WorkingDistance.Value",
|
|
133
|
+
"PixelSpacing[0]": {"target": "Pixels.PhysicalSizeY", "unit": "mm"},
|
|
134
|
+
"Vacuum.*": "Instrument.Vacuum"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
[How the mapping works](https://nl-bioimaging.github.io/imaging-metadata-converter/mapping/)
|
|
138
|
+
(`docs/mapping.md`) describes it in full, with examples: model paths, every
|
|
139
|
+
form of rule, matching by name, vendor wrappers, combinations, the vendor
|
|
140
|
+
steps and the SourceMap.
|
|
141
|
+
|
|
142
|
+
## The model
|
|
143
|
+
|
|
144
|
+
`models/imaging.yaml` is LiMi as a LinkML model, importing
|
|
145
|
+
`imaging_extension.yaml` (metadata beyond LiMi, mostly electron microscopy),
|
|
146
|
+
`imaging_provenance.yaml` (`Property`, `SourceFile`, `SourceMapping`) and
|
|
147
|
+
`imaging_units.yaml` (the unit enumerations). `ModelPaths` reads it into the
|
|
148
|
+
dotted paths the mapper uses:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from imaging_metadata_converter import ModelPaths
|
|
152
|
+
|
|
153
|
+
tree = ModelPaths().tree()
|
|
154
|
+
tree['Pixels']['PhysicalSizeX']
|
|
155
|
+
# 'PositiveFloat'
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Every leaf's value is its range: a type (`string`, `float`, `datetime`, ...),
|
|
159
|
+
an enumeration (`UnitsLength`, ...) or, for a reference, the class it refers
|
|
160
|
+
to. The ranges are **descriptive, not enforced**: the mapper matches on paths
|
|
161
|
+
only, never validates a value against its range and never coerces one, so
|
|
162
|
+
whatever the source held is written through unchanged (combinations aside).
|
|
163
|
+
|
|
164
|
+
The [model page](https://nl-bioimaging.github.io/imaging-metadata-converter/model/)
|
|
165
|
+
of the documentation browses every path, flags the ones the extensions add
|
|
166
|
+
and the ones a mapping rule targets, and describes where the model comes from;
|
|
167
|
+
[how the mapping works](https://nl-bioimaging.github.io/imaging-metadata-converter/mapping/)
|
|
168
|
+
describes, with examples, how a source's values reach them.
|
|
169
|
+
|
|
170
|
+
## Data files
|
|
171
|
+
|
|
172
|
+
- `src/imaging_metadata_converter/models/imaging.yaml` and its imports - the
|
|
173
|
+
imaging model (see The model).
|
|
174
|
+
- `src/imaging_metadata_converter/mappings/mappings.json` - source field to
|
|
175
|
+
model field mappings.
|
|
176
|
+
- `src/imaging_metadata_converter/mappings/combinations.json` - values built
|
|
177
|
+
from others (see Combinations).
|
|
178
|
+
|
|
179
|
+
## Maintaining the model
|
|
180
|
+
|
|
181
|
+
Outside the package, `scripts/` holds the tools the model is maintained with;
|
|
182
|
+
[Maintaining the model](https://nl-bioimaging.github.io/imaging-metadata-converter/maintaining/)
|
|
183
|
+
in the documentation describes them in full.
|
|
184
|
+
|
|
185
|
+
- `scripts/linkml_converter.py` - converted the LiMi XSD (`reference/`) into
|
|
186
|
+
the model, once; the model is edited by hand since, so it refuses to
|
|
187
|
+
overwrite it without `--force`. Kept for comparing a future LiMi XSD.
|
|
188
|
+
- `scripts/metaseed_generator.py` - generates the metaseed profile
|
|
189
|
+
`profile/imaging.metaseed.yaml` (`imaging` 2.0; 1.1 is published on the
|
|
190
|
+
metaseed Hub) from the model; rerun it after a change to the model.
|
|
191
|
+
- `scripts/dataset_exporter.py` - exports each example as a metaseed dataset
|
|
192
|
+
of the profile into `export/`, with every value the model has no field for
|
|
193
|
+
kept as a `Property` record.
|
|
194
|
+
- `scripts/model_fit.py` - how well each example can be expressed in the
|
|
195
|
+
model, from its dataset in `export/`: whether the output keeps and traces
|
|
196
|
+
every input value (100% each), the source keys covered by a rule or
|
|
197
|
+
automatically, and the ones not covered, with why. `analyse()` returns
|
|
198
|
+
these statistics for any conversion, `analyse_metadata()` for a source dict
|
|
199
|
+
(also the docs' Model fit page).
|
|
200
|
+
|
|
201
|
+
The tests check that `profile/` and `export/` are up to date and lose no
|
|
202
|
+
data; the tests of the XSD conversion need `linkml` and those validating
|
|
203
|
+
`export/` with metaseed need `metaseed`, and are skipped without them.
|
|
204
|
+
|
|
205
|
+
## Documentation
|
|
206
|
+
|
|
207
|
+
The documentation site is built with MkDocs from `docs/` and published to
|
|
208
|
+
GitHub Pages on every push to `main`. To build it locally:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
pip install -e ".[docs]"
|
|
212
|
+
mkdocs serve
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Nothing on the model pages is stored in the repository. The MkDocs hook
|
|
216
|
+
`scripts/docs_data.py` reads the packaged model and mappings as the site is
|
|
217
|
+
built: it writes the JSON the model browser fetches (`data/model.json`,
|
|
218
|
+
`added.json`, `mappings.json`, `details.json`) into the site, fills in the model's counts on
|
|
219
|
+
`model.md`, and the examples' fit on `model-fit.md`. A new version of the model files or an edited
|
|
220
|
+
`mappings.json` shows up on the next build, with nothing to regenerate.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "imaging-metadata-converter"
|
|
7
|
+
version = "2.0.1"
|
|
8
|
+
description = "Convert custom imaging acquisition metadata to common metadata"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
keywords = ["imaging", "metadata", "microscopy", "ome", "acquisition"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"linkml-runtime>=1.9",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
docs = [
|
|
23
|
+
"mkdocs>=1.6",
|
|
24
|
+
"mkdocs-material>=9.5",
|
|
25
|
+
"mkdocstrings[python]>=0.24",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/NL-BioImaging/imaging-metadata-converter"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
imaging_metadata_converter = ["models/*.yaml", "mappings/*.json"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
pythonpath = ["src"]
|
|
39
|
+
testpaths = ["tests"]
|