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.
Files changed (30) hide show
  1. imaging_metadata_converter-2.0.1/LICENSE +21 -0
  2. imaging_metadata_converter-2.0.1/PKG-INFO +260 -0
  3. imaging_metadata_converter-2.0.1/README.md +220 -0
  4. imaging_metadata_converter-2.0.1/pyproject.toml +39 -0
  5. imaging_metadata_converter-2.0.1/setup.cfg +4 -0
  6. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/AcquisitionMetadataMapper.py +1393 -0
  7. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/ModelPaths.py +165 -0
  8. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/__init__.py +45 -0
  9. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/mappings/combinations.json +378 -0
  10. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/mappings/mappings.json +561 -0
  11. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/models/imaging.yaml +13528 -0
  12. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/models/imaging_extension.yaml +598 -0
  13. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/models/imaging_provenance.yaml +76 -0
  14. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter/models/imaging_units.yaml +1783 -0
  15. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter.egg-info/PKG-INFO +260 -0
  16. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter.egg-info/SOURCES.txt +28 -0
  17. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter.egg-info/dependency_links.txt +1 -0
  18. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter.egg-info/requires.txt +6 -0
  19. imaging_metadata_converter-2.0.1/src/imaging_metadata_converter.egg-info/top_level.txt +1 -0
  20. imaging_metadata_converter-2.0.1/tests/test_acquisition_metadata_mapper.py +1047 -0
  21. imaging_metadata_converter-2.0.1/tests/test_dataset_exporter.py +281 -0
  22. imaging_metadata_converter-2.0.1/tests/test_docs_data.py +83 -0
  23. imaging_metadata_converter-2.0.1/tests/test_examples.py +54 -0
  24. imaging_metadata_converter-2.0.1/tests/test_linkml_converter.py +332 -0
  25. imaging_metadata_converter-2.0.1/tests/test_metaseed_generator.py +201 -0
  26. imaging_metadata_converter-2.0.1/tests/test_metaseed_validation.py +78 -0
  27. imaging_metadata_converter-2.0.1/tests/test_model_fit.py +155 -0
  28. imaging_metadata_converter-2.0.1/tests/test_model_paths.py +66 -0
  29. imaging_metadata_converter-2.0.1/tests/test_no_data_loss.py +265 -0
  30. 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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+