CRAMM 1.0.0__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.
@@ -0,0 +1,421 @@
1
+ Metadata-Version: 2.4
2
+ Name: CRAMM
3
+ Version: 1.0.0
4
+ Summary: CRAMM — general-purpose hyperspectral mineral identification toolkit (USGS MICA, extended: depth-ratio rules + quantitative muscovite mapping; sensor-agnostic, EMIT L2A built in)
5
+ Author: Shijie Li
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/leecugb/cramm
8
+ Project-URL: Repository, https://github.com/leecugb/cramm
9
+ Project-URL: Issues, https://github.com/leecugb/cramm/issues
10
+ Keywords: hyperspectral,EMIT,mineral,MICA,USGS,remote-sensing
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: GIS
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: numpy
25
+ Requires-Dist: pandas
26
+ Requires-Dist: netCDF4
27
+ Requires-Dist: pyproj
28
+ Requires-Dist: pyresample
29
+ Requires-Dist: threadpoolctl
30
+ Provides-Extra: tiff
31
+ Requires-Dist: gdal; extra == "tiff"
32
+ Provides-Extra: pdf
33
+ Requires-Dist: matplotlib; extra == "pdf"
34
+ Provides-Extra: all
35
+ Requires-Dist: gdal; extra == "all"
36
+ Requires-Dist: matplotlib; extra == "all"
37
+ Provides-Extra: test
38
+ Requires-Dist: pytest; extra == "test"
39
+ Requires-Dist: matplotlib; extra == "test"
40
+ Dynamic: license-file
41
+
42
+ # CRAMM
43
+
44
+ [![PyPI](https://img.shields.io/pypi/v/cramm)](https://pypi.org/project/cramm/)
45
+ [![Python](https://img.shields.io/pypi/pyversions/cramm)](https://pypi.org/project/cramm/)
46
+ [![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.22024483-blue.svg)](https://doi.org/10.5281/zenodo.22024483)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/leecugb/cramm/blob/main/LICENSE)
48
+ ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey)
49
+
50
+ **General-purpose hyperspectral mineral identification toolkit** — built on
51
+ the USGS MICA (Material Identification and Characterization Algorithm)
52
+ decision-rule system. The classification core is **sensor-agnostic**: it works
53
+ on any VNIR–SWIR reflectance cube given its band configuration (center
54
+ wavelengths, FWHM, valid-band mask), because the bundled splib06b reference
55
+ spectra are resampled to the sensor's bands at runtime. EMIT L2A is simply the
56
+ built-in data reader — one supported input type, not the defining one.
57
+
58
+ CRAMM extends MICA in three ways:
59
+
60
+ 1. **An enhanced rule schema.** CRAMM adds an optional secondary-feature
61
+ depth-ratio constraint (`max_depth_ratio_feat1_over_feat0`) that rejects
62
+ pixels whose secondary absorption is too deep relative to the primary
63
+ 2.2 µm feature — suppressing white-mica false positives that pass the
64
+ original five-layer MICA filtering. Nine bundled rules (muscovite, illite,
65
+ kaolinite–muscovite mixtures) carry the new constraint; any custom rule
66
+ can opt in. See *Enhancements over USGS MICA*.
67
+ 2. **Wavelength-arbitrated muscovite subtyping.** MICA labels a pixel
68
+ "muscovite_lowAl / medAl / medhighAl / Fe-rich" by best fit alone; CRAMM
69
+ then re-arbitrates that attribution with the pixel's fitted 2.2 µm
70
+ absorption center against per-rule calibrated wavelength windows
71
+ (`absorption_center_range`) — the spectroscopically meaningful axis along
72
+ which these four subtypes are actually defined. See *Enhancements over
73
+ USGS MICA → Wavelength-based muscovite attribution*.
74
+ 3. **From mineral detection to mineral composition.** Beyond labeling
75
+ muscovite pixels, CRAMM fits the per-pixel 2.2 µm absorption-center
76
+ wavelength (`mus_center`) — a quantitative composition proxy whose
77
+ thermodynamic basis (Tschermak substitution vs. wv2200 on a
78
+ GEMS/MINES23.1 reaction-path phase diagram) lets each fitted pixel be
79
+ read as muscovite chemistry, formation temperature and fluid K⁺/H⁺
80
+ conditions. See *Application: reading muscovite composition from
81
+ mus_center*.
82
+
83
+ Everything is pure Python and GUI-free. Cross-platform:
84
+ **Windows / Linux / macOS** · Python **3.9 – 3.13**
85
+
86
+ ---
87
+
88
+ ## Highlights
89
+
90
+ - **Enhanced MICA pipeline** — continuum removal → closed-form 2×2 least
91
+ squares → fit (r²) & absorption depth → five-layer constraint filtering
92
+ **+ the CRAMM depth-ratio constraint**, driven by a JSON rule library
93
+ (77 rules covering clay, sulfate, carbonate, mica, chlorite, amphibole,
94
+ iron oxide, snow/ice and their mixtures).
95
+ - **Quantitative muscovite mapping** — per-pixel 2.2 µm absorption-center
96
+ wavelength as a dedicated thematic map and float array; the same center
97
+ also re-arbitrates the lowAl / medAl / medhighAl / Fe-rich attribution
98
+ against calibrated wavelength windows, with a phase-diagram
99
+ interpretation framework.
100
+ - **Whole-scene and single-spectrum modes** — batch-classify an entire scene
101
+ to GeoTIFF, or identify one spectrum (GUI point-click, field
102
+ spectrometer) with Top-N ranking and a PDF diagnostic report.
103
+ - **Sensor-agnostic core** — everything downstream of data loading consumes a
104
+ generic `(spectrum, wavelengths, FWHM, valid bands)` contract. The bundled
105
+ reader covers EMIT L2A NetCDF; any other sensor (airborne or spaceborne)
106
+ plugs in through the same seven-tuple — no rule or code changes needed.
107
+ - **Fast** — reference-side constants are precompiled once per band
108
+ configuration (two-level cache; ~16× speedup on repeated single-spectrum
109
+ calls), and scene classification parallelizes across rules with worker
110
+ processes.
111
+ - **Bit-exact discipline** — serial and parallel paths produce identical
112
+ bytes; every change is guarded by a dual-path golden regression suite.
113
+ - **Self-contained** — the rule library (`rf.json`), the USGS `splib06b`
114
+ spectral library, and the mineral color table are bundled inside the wheel.
115
+
116
+ ## How it works
117
+
118
+ ```
119
+ Hyperspectral reflectance cube (any VNIR–SWIR sensor)
120
+ │ built-in: load_emit (EMIT L2A NetCDF, bad-band removal)
121
+ │ or your own loader → (spectrum, wl, w, bp, chanels)
122
+
123
+ Reference resampling ── splib06b records ──► sensor wavelengths/FWHM
124
+ │ (Gaussian kernel, cached)
125
+
126
+ Per-rule evaluation (77 rules, parallel across rules)
127
+ │ diagnostic features: continuum removal → 2×2 LSQ → r² / depth
128
+ │ not-absorption / not-related features: exclusion filters
129
+ │ continuum & depth-ratio constraints
130
+
131
+ Best-match selection (argmax fit×depth) + muscovite 2.2 µm center fit
132
+
133
+
134
+ Wavelength-arbitrated muscovite subtyping
135
+ │ mus_center vs. per-rule absorption_center_range windows
136
+ │ (lowAl / medAl / medhighAl / Fe-rich re-attribution)
137
+
138
+ Mineral map · color-enhanced map · muscovite map (+ raw float arrays)
139
+ ```
140
+
141
+ ## Installation
142
+
143
+ ```bash
144
+ pip install cramm # core features (PyPI wheels on all three platforms)
145
+ pip install cramm[tiff] # + GeoTIFF output (GDAL; PyPI wheels are Windows-only)
146
+ pip install cramm[pdf] # + single-spectrum feature PDF diagnostics
147
+ pip install cramm[all] # everything
148
+ ```
149
+
150
+ **GDAL on Linux/macOS**: PyPI ships GDAL wheels for Windows only. Install a
151
+ system libgdal first (conda-forge recommended), then install without deps:
152
+
153
+ ```bash
154
+ conda install -c conda-forge gdal
155
+ pip install cramm --no-deps # or: pip install cramm[pdf]
156
+ ```
157
+
158
+ Without GDAL, only `write_tiff` (GeoTIFF output) is unavailable — all
159
+ classification and analysis functions work (lazy import).
160
+
161
+ From source (sdist / checkout):
162
+
163
+ ```bash
164
+ pip install . # add [all] for the optional extras
165
+ ```
166
+
167
+ ## Quick start
168
+
169
+ ### Command line
170
+
171
+ The CLI uses the built-in EMIT L2A reader; for other sensors, use the Python
172
+ API (below) with your own loader.
173
+
174
+ ```bash
175
+ cramm -i EMIT_L2A_RFL_001_xxx.nc -o output [-n scene] [-w 4] [--raw]
176
+ ```
177
+
178
+ | Flag | Default | Meaning |
179
+ |---|---|---|
180
+ | `-i`, `--input` | *(required)* | Path to the EMIT L2A NetCDF file |
181
+ | `-o`, `--output` | `.` | Output directory |
182
+ | `-n`, `--name` | input filename | Output filename prefix |
183
+ | `-w`, `--workers` | `min(cpu, 8)` | Parallel worker processes (`1` = serial) |
184
+ | `--raw` | off | Also save `mus_center` + `fd` float arrays as `.npz` |
185
+
186
+ **Output files** (written to `<output>/<name>*`):
187
+
188
+ | File | Content |
189
+ |---|---|
190
+ | `<name>_mapping_orth.tiff` | Mineral map (orthorectified, rule-library colors) |
191
+ | `<name>_color_enhanced_orth.tiff` | Color-enhanced mineral map |
192
+ | `<name>_mus_orth.tiff` | Muscovite 2.2 µm absorption-center thematic map |
193
+ | `<name>_raw.npz` | *(only with `--raw`)* `mus_center` [μm] + `fd` (fit×depth), float `[r, c]` |
194
+
195
+ ### Python API
196
+
197
+ ```python
198
+ from cramm import MicaEngine
199
+
200
+ engine = MicaEngine() # all resources bundled
201
+ spectrum, lon, lat, w, bp, wl, chanels = engine.load_emit("EMIT_xxx.nc")
202
+
203
+ # --- whole scene → GeoTIFF -------------------------------------------------
204
+ orth, color, mus = engine.spectrum_analysis(spectrum, wl, w, bp, chanels,
205
+ n_workers=4)
206
+ engine.write_tiff("output/scene", lon, lat, orth, color, mus)
207
+
208
+ # --- single spectrum (one pixel, field spectrometer, ...) ------------------
209
+ pixel = spectrum[100, 200, :] # full-band [285] or selected [len(chanels)]
210
+ results = engine.classify_spectrum(pixel, wl, w, bp, chanels,
211
+ top_n=5, pdf_path="diag.pdf")
212
+ for r in results:
213
+ print(f"{r['name']:50s} fit={r['fit']:.4f} fd={r['fd']:.4f}")
214
+ ```
215
+
216
+ ### Other sensors (non-EMIT data)
217
+
218
+ `load_emit` is only a convenience reader. For any other sensor, load the cube
219
+ yourself and pass the same band-configuration contract — references are
220
+ resampled to your wavelengths/FWHM automatically:
221
+
222
+ ```python
223
+ from cramm import MicaEngine
224
+ import numpy as np
225
+
226
+ engine = MicaEngine()
227
+ spectrum = my_loader("scene.dat") # [rows, cols, bands] reflectance
228
+ w = np.array([...]) # band center wavelengths [µm]
229
+ bp = np.array([...]) # band FWHM [µm]
230
+ chanels = np.arange(len(w)) # valid bands (drop bad-band indices)
231
+ wl = w[chanels]
232
+
233
+ orth, color, mus = engine.spectrum_analysis(spectrum, wl, w, bp, chanels,
234
+ n_workers=4)
235
+ ```
236
+
237
+ Only the map rendering (`write_tiff`) needs geolocation (`lon`/`lat` grids);
238
+ classification itself is purely spectral and location-free.
239
+
240
+ `classify_spectrum` returns a list of `{"name", "fit", "fd"}` dicts sorted by
241
+ descending fit (empty list when nothing passes the filters). With
242
+ `pdf_path=` it also writes a multi-page PDF: one page per Top-N mineral with
243
+ continuum-removed feature overlays and constraint annotations (requires the
244
+ `[pdf]` extra).
245
+
246
+ Each PDF page dissects one candidate rule — every diagnostic / not-absorption
247
+ / not-relative feature with its continuum endpoints, the reference
248
+ continuum-removed profile (squares) against the input (circles), and the
249
+ full constraint audit (k0/k1, r², raw depth, weights, thresholds):
250
+
251
+ ![Single-spectrum diagnostic PDF: per-rule feature dissection](docs/single_spectrum_diagnostic.png)
252
+
253
+ More scenarios — float (`raw=True`) output, custom rule libraries, the
254
+ `invalidate_caches()` contract, component-level calls — in
255
+ [example_usage.py](https://github.com/leecugb/cramm/blob/main/example_usage.py): `python example_usage.py pixel`.
256
+
257
+ ## API overview
258
+
259
+ | `MicaEngine` method | Purpose |
260
+ |---|---|
261
+ | `load_emit(path)` | *(EMIT-specific convenience reader)* Read EMIT L2A NetCDF → `(spectrum, lon, lat, w, bp, wl, chanels)`; float32 cube, bad bands removed, fill values zeroed. Not needed for other sensors — supply the same tuple yourself |
262
+ | `spectrum_analysis(spectrum, wl, w, bp, chanels, ...)` | Classify a whole scene → 3 uint8 RGB images; `raw=True` adds `mus_center` + `fd` float arrays. Supports `progress_callback`, `log_callback`, `cancel_flag`, `n_workers` |
263
+ | `classify_spectrum(spectrum, wl, w, bp, chanels, top_n=10, pdf_path=None)` | Identify one spectrum → Top-N `[{"name", "fit", "fd"}]` |
264
+ | `write_tiff(prefix, lon, lat, orth, color, mus)` | Orthorectify (pyresample) and write the 3 GeoTIFFs; requires GDAL |
265
+ | `get_resample(w, bp)` | All reference spectra resampled to the sensor bands `{record_id: spectrum}` (cached) |
266
+ | `invalidate_caches()` | **Required** after mutating `engine.rf` in place — see below |
267
+
268
+ ### Custom rule libraries
269
+
270
+ ```python
271
+ engine = MicaEngine(rf_path="my_rules.json") # at construction
272
+ # — or mutate in place —
273
+ engine.rf["my_mineral"] = {...}
274
+ engine.invalidate_caches() # mandatory!
275
+ ```
276
+
277
+ The compiled-rule cache is keyed on band configuration only, **not** on rule
278
+ content. If you modify `engine.rf` after any classification call, you must call
279
+ `invalidate_caches()` (or build a new engine) — otherwise results silently use
280
+ the old reference-side constants.
281
+
282
+ ## Enhancements over USGS MICA
283
+
284
+ CRAMM extends the original USGS MICA decision rules with an optional per-rule
285
+ **secondary-feature depth-ratio constraint**,
286
+ `max_depth_ratio_feat1_over_feat0`:
287
+
288
+ > After the standard MICA filtering, a rule carrying this key rejects any pixel
289
+ > where `raw_depth(feat1) / raw_depth(feat0) ≥ threshold`, using the
290
+ > *unweighted* feature depths `(1 − min(continuum-removed)) × k0`. Pixels with
291
+ > an invalid primary feature (NaN depth) are conservatively kept.
292
+
293
+ For white micas the primary 2.2 µm Al-OH absorption (feat0) must dominate the
294
+ secondary ~2.35 µm feature (feat1); a secondary absorption that is too deep
295
+ relative to the primary indicates look-alike minerals rather than muscovite /
296
+ illite. Nine bundled rules use this constraint:
297
+
298
+ | Threshold | Rules |
299
+ |---|---|
300
+ | `0.6` | muscovite_lowAl, muscovite_medAl, muscovite_medhighAl, muscovite_Fe-rich, illite_imt1, illite_gds4 |
301
+ | `0.4` | kaolinite.5+muscoviteMedAl.5, kaolinite.5+muscoviteMedhighAl.5, kaolinite+muscovite_mix_intimate |
302
+
303
+ The constraint is part of the rule schema — custom rule libraries can set
304
+ `"max_depth_ratio_feat1_over_feat0": <float>` on any rule with ≥2 diagnostic
305
+ features; omitting the key disables it (original MICA behavior).
306
+
307
+ ### Wavelength-based muscovite attribution
308
+
309
+ CRAMM also adds an optional per-feature **absorption-center window**,
310
+ `absorption_center_range` on a rule's first diagnostic feature. After the
311
+ best-match selection, pixels attributed to a rule carrying this field are
312
+ re-arbitrated by their fitted 2.2 µm absorption center (`mus_center`):
313
+
314
+ > If the center falls inside exactly one rule's `[lo, hi)` window, differs
315
+ > from the current match, and that rule itself accepted the pixel, the pixel
316
+ > is reassigned to the matching rule (fit/depth/index follow, and the center
317
+ > is refitted once with the new rule's endpoints). An invalid center, a
318
+ > center outside every window, or a center inside several overlapping
319
+ > windows keeps the original match (conservative).
320
+
321
+ The four bundled pure-muscovite rules carry calibrated windows (anchored on
322
+ each reference spectrum's measured wv2200): medhighAl `[2.195, 2.200)`,
323
+ medAl `[2.200, 2.206)`, lowAl / Fe-rich `[2.206, 2.220)` — the latter two
324
+ share a window, so wavelength never overrides their mutual attribution.
325
+ This is a scene-classification feature; single-spectrum Top-N ranking is
326
+ unaffected. Custom rule libraries opt in by adding the field; rules without
327
+ it are never reassigned.
328
+
329
+ ## Application: reading muscovite composition from mus_center
330
+
331
+ The muscovite thematic map's per-pixel `mus_center` (2.2 µm Al-OH absorption
332
+ position) is a quantitative proxy for muscovite chemistry. The phase diagram
333
+ below — a GEMS/MINES23.1 titration reaction-path model of the
334
+ K₂O–Al₂O₃–SiO₂–H₂O–HCl–FeO–MgO system — overlays the Tschermak substitution
335
+ degree X_Ts = X(Fe-Celadonite)+X(Celadonite) in the muscovite stability field
336
+ with the corresponding wv2200 position (USGS conversion chain:
337
+ X_Ts → Al₂O₃ wt% → λ = −3.1·Al₂O₃ + 2308):
338
+
339
+ ![Tschermak substitution degree vs. wv2200 in the muscovite field](docs/muscovite_wv2200_phase_diagram.png)
340
+
341
+ X_Ts rises from ~0 on the high-T / low-K⁺ side to 0.35+ on the low-T / high-K⁺
342
+ side, and the wv2200 contours (magenta, 2190→2215 nm) run nearly parallel to
343
+ the X_Ts contours (dark blue). Each `mus_center` value fitted from an image
344
+ pixel therefore maps directly onto this diagram, inverting muscovite
345
+ composition — and with it formation temperature and fluid K⁺/H⁺ conditions —
346
+ from orbit.
347
+
348
+ ## Performance notes
349
+
350
+ - **Precompiled rules**: continuum endpoints, band indices, the reference-side
351
+ normal-equation constant `B` and depth factors are computed once per
352
+ `(wavelengths, FWHM, valid-band)` configuration and reused across all pixels
353
+ and calls.
354
+ - **Parallelism**: scene classification fans out across the 77 rules with
355
+ `multiprocessing` (spawn context); BLAS is pinned to a single thread so the
356
+ parallel path stays bit-identical to the serial one.
357
+ - **Typical runtime**: a full scene (e.g. an EMIT granule, ≈1280×1242 pixels)
358
+ classifies in about a minute with a few workers on a desktop; a warm
359
+ single-spectrum call is ≈10 ms.
360
+
361
+ ## Testing
362
+
363
+ ```bash
364
+ python tests/test_core.py # 18 API contract / behavior tests
365
+ # (integration section auto-skips without the test scene)
366
+ python tests/test_custom_rules.py # custom rule-library verification (7 scenarios:
367
+ # rf_path / constraint & window edits / new rules / cache contract)
368
+ python tests/test_parallel_isolation.py # shared-state isolation (6 checks: worker/thread
369
+ # isolation, env restore, temp-file cleanup)
370
+
371
+ # The suites below need the EMIT test scene in the working directory
372
+ # (file name defined in each script's NC constant):
373
+ python tests/check_rows_logic.py # rows alive-pixel semantics (16 checks)
374
+ python tests/test_single_spectrum.py # single-spectrum identification (7 tests:
375
+ # self-ID / noise robustness / determinism / ...)
376
+ python tests/check_compiled_path.py # compiled vs direct path, 302 pixels × 77 rules, bit-level diff
377
+ python tests/test_parallel.py 4 # full-scene golden regression (parallel)
378
+ python tests/test_parallel.py 1 # full-scene golden regression (serial)
379
+ ```
380
+
381
+ **The golden baseline is platform-bound.** `tests/golden_arrays.npz` encodes
382
+ this machine's BLAS results; ulp-level differences across BLAS builds are
383
+ expected. On a new platform — or after an intentional classification-semantics
384
+ change — regenerate the baseline locally with `python regen_golden.py` (runs
385
+ both paths, asserts serial ≡ parallel, then rewrites the golden) before
386
+ relying on `test_parallel.py`.
387
+
388
+ ## Troubleshooting
389
+
390
+ - **`netCDF4` fails to open a path containing non-ASCII characters on
391
+ Windows** — a limitation of the netCDF C library, not of CRAMM. `cd` into the
392
+ data directory and use a relative path instead.
393
+ - **`ImportError: gdal`** — you called `write_tiff` without GDAL installed; see
394
+ *Installation*. Classification itself never imports GDAL.
395
+
396
+ ## Package layout
397
+
398
+ ```
399
+ cramm/
400
+ __init__.py # exports MicaEngine / ProcessResult
401
+ mica_engine.py # facade: resource loading + component wiring + CLI main()
402
+ emit_reader.py # EMIT L2A NetCDF reader + bad-band removal (float32 contract)
403
+ classifier.py # MICA core: resampling / compiled rules / serial & parallel classification
404
+ renderer.py # rendering: three maps / GeoTIFF / single-spectrum PDF diagnostics
405
+ data/ # rf.json + splib06b + color_table.json
406
+ tests/ # bit-exact verification suite + API contract tests
407
+ example_usage.py # five usage-scenario examples
408
+ ```
409
+
410
+ ## Requirements
411
+
412
+ - Python 3.9 – 3.13
413
+ - Runtime: `numpy`, `pandas`, `netCDF4`, `pyproj`, `pyresample`, `threadpoolctl`
414
+ - Optional: `gdal` (GeoTIFF), `matplotlib` (PDF diagnostics)
415
+
416
+ ## Acknowledgments
417
+
418
+ The decision rules implement the USGS MICA system
419
+ (Kokaly et al., `russet`-era rule set); reference spectra come from the USGS
420
+ splib06b spectral library (Clark et al., 2007). The bundled test scene uses
421
+ EMIT L2A products, courtesy of NASA/JPL.
@@ -0,0 +1,31 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ benchmark.py
5
+ example_usage.py
6
+ pyproject.toml
7
+ regen_golden.py
8
+ CRAMM.egg-info/PKG-INFO
9
+ CRAMM.egg-info/SOURCES.txt
10
+ CRAMM.egg-info/dependency_links.txt
11
+ CRAMM.egg-info/entry_points.txt
12
+ CRAMM.egg-info/requires.txt
13
+ CRAMM.egg-info/top_level.txt
14
+ cramm/__init__.py
15
+ cramm/classifier.py
16
+ cramm/emit_reader.py
17
+ cramm/mica_engine.py
18
+ cramm/renderer.py
19
+ cramm/data/color_table.json
20
+ cramm/data/rf.json
21
+ cramm/data/splib06b
22
+ docs/muscovite_wv2200_phase_diagram.png
23
+ docs/single_spectrum_diagnostic.png
24
+ tests/check_compiled_path.py
25
+ tests/check_rows_logic.py
26
+ tests/golden_arrays.npz
27
+ tests/test_core.py
28
+ tests/test_custom_rules.py
29
+ tests/test_parallel.py
30
+ tests/test_parallel_isolation.py
31
+ tests/test_single_spectrum.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cramm = cramm.mica_engine:main
@@ -0,0 +1,20 @@
1
+ numpy
2
+ pandas
3
+ netCDF4
4
+ pyproj
5
+ pyresample
6
+ threadpoolctl
7
+
8
+ [all]
9
+ gdal
10
+ matplotlib
11
+
12
+ [pdf]
13
+ matplotlib
14
+
15
+ [test]
16
+ pytest
17
+ matplotlib
18
+
19
+ [tiff]
20
+ gdal
@@ -0,0 +1 @@
1
+ cramm
cramm-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shijie Li
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,7 @@
1
+ # sdist also ships tests and examples (the wheel contains only the cramm package)
2
+ include README.md
3
+ include example_usage.py
4
+ include regen_golden.py
5
+ include benchmark.py
6
+ recursive-include tests *.py *.npz
7
+ recursive-include docs *.png