sage-viewer 0.3.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.
- sage_viewer-0.3.0/LICENSE +21 -0
- sage_viewer-0.3.0/PKG-INFO +288 -0
- sage_viewer-0.3.0/README.md +224 -0
- sage_viewer-0.3.0/pyproject.toml +80 -0
- sage_viewer-0.3.0/sage_viewer/__init__.py +3 -0
- sage_viewer-0.3.0/sage_viewer/__main__.py +3 -0
- sage_viewer-0.3.0/sage_viewer/_version.py +1 -0
- sage_viewer-0.3.0/sage_viewer/app.py +1300 -0
- sage_viewer-0.3.0/sage_viewer/cli.py +147 -0
- sage_viewer-0.3.0/sage_viewer/config.py +48 -0
- sage_viewer-0.3.0/sage_viewer/io/__init__.py +13 -0
- sage_viewer-0.3.0/sage_viewer/io/galaxy_reader.py +380 -0
- sage_viewer-0.3.0/sage_viewer/io/halo_reader.py +233 -0
- sage_viewer-0.3.0/sage_viewer/io/par_reader.py +81 -0
- sage_viewer-0.3.0/sage_viewer/io/sage_header.py +77 -0
- sage_viewer-0.3.0/sage_viewer/io/snapshot_table.py +65 -0
- sage_viewer-0.3.0/sage_viewer/parallel/__init__.py +3 -0
- sage_viewer-0.3.0/sage_viewer/parallel/loader.py +146 -0
- sage_viewer-0.3.0/sage_viewer/scene/__init__.py +6 -0
- sage_viewer-0.3.0/sage_viewer/scene/box_profile.py +172 -0
- sage_viewer-0.3.0/sage_viewer/scene/camera.py +492 -0
- sage_viewer-0.3.0/sage_viewer/scene/fof_layer.py +136 -0
- sage_viewer-0.3.0/sage_viewer/scene/galaxy_layer.py +524 -0
- sage_viewer-0.3.0/sage_viewer/scene/halo_layer.py +213 -0
- sage_viewer-0.3.0/sage_viewer/scene/model.py +181 -0
- sage_viewer-0.3.0/sage_viewer/scene/scene.py +634 -0
- sage_viewer-0.3.0/sage_viewer/ui/__init__.py +9 -0
- sage_viewer-0.3.0/sage_viewer/ui/info_panel.py +164 -0
- sage_viewer-0.3.0/sage_viewer/ui/navigation_panel.py +4403 -0
- sage_viewer-0.3.0/sage_viewer/ui/toolbar.py +728 -0
- sage_viewer-0.3.0/sage_viewer/utils/__init__.py +15 -0
- sage_viewer-0.3.0/sage_viewer/utils/catalogue.py +278 -0
- sage_viewer-0.3.0/sage_viewer/utils/colormap.py +66 -0
- sage_viewer-0.3.0/sage_viewer/utils/command_parser.py +466 -0
- sage_viewer-0.3.0/sage_viewer/utils/discover.py +50 -0
- sage_viewer-0.3.0/sage_viewer/utils/galaxy_info.py +193 -0
- sage_viewer-0.3.0/sage_viewer/utils/group_info.py +92 -0
- sage_viewer-0.3.0/sage_viewer/utils/kdtree.py +43 -0
- sage_viewer-0.3.0/sage_viewer/utils/sizing.py +87 -0
- sage_viewer-0.3.0/sage_viewer/wizard/__init__.py +0 -0
- sage_viewer-0.3.0/sage_viewer/wizard/controller.py +848 -0
- sage_viewer-0.3.0/sage_viewer/wizard/launch.py +78 -0
- sage_viewer-0.3.0/sage_viewer/wizard/ui.py +248 -0
- sage_viewer-0.3.0/sage_viewer.egg-info/PKG-INFO +288 -0
- sage_viewer-0.3.0/sage_viewer.egg-info/SOURCES.txt +54 -0
- sage_viewer-0.3.0/sage_viewer.egg-info/dependency_links.txt +1 -0
- sage_viewer-0.3.0/sage_viewer.egg-info/entry_points.txt +2 -0
- sage_viewer-0.3.0/sage_viewer.egg-info/requires.txt +20 -0
- sage_viewer-0.3.0/sage_viewer.egg-info/top_level.txt +1 -0
- sage_viewer-0.3.0/setup.cfg +4 -0
- sage_viewer-0.3.0/tests/test_colormap.py +36 -0
- sage_viewer-0.3.0/tests/test_galaxy_reader.py +31 -0
- sage_viewer-0.3.0/tests/test_halo_reader.py +68 -0
- sage_viewer-0.3.0/tests/test_par_reader.py +21 -0
- sage_viewer-0.3.0/tests/test_sizing.py +39 -0
- sage_viewer-0.3.0/tests/test_snapshot_table.py +28 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Bradley
|
|
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,288 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sage-viewer
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Interactive 3D viewer for SAGE semi-analytic galaxy formation outputs
|
|
5
|
+
Author-email: Michael Bradley <mbrads85@live.com.au>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Michael Bradley
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/MBradley1985/SAGE-Viewer
|
|
29
|
+
Project-URL: Documentation, https://sage-viewer.readthedocs.io/en/latest/
|
|
30
|
+
Project-URL: Repository, https://github.com/MBradley1985/SAGE-Viewer
|
|
31
|
+
Project-URL: Issues, https://github.com/MBradley1985/SAGE-Viewer/issues
|
|
32
|
+
Keywords: astronomy,galaxy-formation,dark-matter,visualization,SAGE
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Science/Research
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
41
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
42
|
+
Requires-Python: >=3.10
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
License-File: LICENSE
|
|
45
|
+
Requires-Dist: pyvista>=0.44
|
|
46
|
+
Requires-Dist: trame>=3.6
|
|
47
|
+
Requires-Dist: trame-vtk>=2.8
|
|
48
|
+
Requires-Dist: trame-vuetify>=2.7
|
|
49
|
+
Requires-Dist: h5py>=3.9
|
|
50
|
+
Requires-Dist: numpy>=1.24
|
|
51
|
+
Requires-Dist: joblib>=1.3
|
|
52
|
+
Provides-Extra: docs
|
|
53
|
+
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
54
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
|
|
55
|
+
Requires-Dist: mkdocs-macros-plugin>=1.0; extra == "docs"
|
|
56
|
+
Provides-Extra: dev
|
|
57
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
58
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
59
|
+
Requires-Dist: ruff>=0.3; extra == "dev"
|
|
60
|
+
Requires-Dist: black>=24.0; extra == "dev"
|
|
61
|
+
Requires-Dist: pre-commit>=3.6; extra == "dev"
|
|
62
|
+
Requires-Dist: sage-viewer[docs]; extra == "dev"
|
|
63
|
+
Dynamic: license-file
|
|
64
|
+
|
|
65
|
+
<p align="center">
|
|
66
|
+
<img src="docs/images/SAGElogo.jpg" alt="SAGE-Viewer logo" width="220"/>
|
|
67
|
+
</p>
|
|
68
|
+
|
|
69
|
+
# SAGE-Viewer
|
|
70
|
+
|
|
71
|
+
An interactive 3D visualization package for [SAGE26](https://github.com/MBradley1985/SAGE26) semi-analytic galaxy formation outputs.
|
|
72
|
+
|
|
73
|
+
Renders dark matter haloes and SAGE galaxies together in a browser-based interactive viewer powered by [PyVista](https://pyvista.org) and [Trame](https://kitware.github.io/trame/).
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
## Features
|
|
78
|
+
|
|
79
|
+
### Rendering
|
|
80
|
+
- World-space gaussian splat rendering of haloes and galaxies — splats scale with camera distance and stay physically meaningful at any zoom
|
|
81
|
+
- **Structure** render mode: each galaxy drawn as a layered composition — cold-gas envelope (blue, sized by ColdGas) + outer envelope (green for CGM-regime sized by CGMgas, red for Hot-regime sized by HotGas)
|
|
82
|
+
- 27 selectable matplotlib colormaps, identical lists for halo and galaxy layers
|
|
83
|
+
- Live colormap, colour-by mode, opacity and visibility controls per layer
|
|
84
|
+
- Colour-by dropdowns are model-aware — only modes whose underlying field is present in the loaded model appear in the list; they update automatically on model switch
|
|
85
|
+
- Full still-quality rendering at all times — no resolution drop during camera drag or playback
|
|
86
|
+
|
|
87
|
+

|
|
88
|
+
|
|
89
|
+
### Playback & camera
|
|
90
|
+
- Play / Pause / Stop / Reverse / Repeat transport at 0.1× – 5× speeds
|
|
91
|
+
- Continuous camera rotation (CW / CCW at 15° / 30° / 60° per second)
|
|
92
|
+
- Reset / Centre / Focus buttons
|
|
93
|
+
- Fly to halo, galaxy, coordinates, or sub-box (with focus mode that masks everything outside)
|
|
94
|
+
- **Draw Sphere** (Coords tab): place a live two-handle sphere in the viewport — drag the centre ball to translate, drag the edge ball to resize; **Lock Sphere** commits it as the active focus region
|
|
95
|
+
- **Draw Box** (Box tab): place a live resizable box widget — drag any face or corner handle; **Lock Box** commits it; **Clear** on both tabs cancels the widget without navigating
|
|
96
|
+
- Switching models always lands at z=0 of the new model; slider and snap chip update immediately
|
|
97
|
+
- Camera bookmarks (save, restore, delete)
|
|
98
|
+
|
|
99
|
+
### Selection & inspection
|
|
100
|
+
- **Galaxy Info** panel (Target tab) — GalaxyID, type, halo Mvir, stellar mass, sSFR, cold gas, B/T, BH mass, H2 mass, gas regime, FFB regime, environment classification, mass-weighted stellar age
|
|
101
|
+
- **Group Info** panel (Environment tab) — FOF-aggregate stats: classification, member breakdown (centrals vs satellites), host Mvir, total stellar / cold gas / SFR, mean B/T, spatial extent, target role, BCG stellar mass
|
|
102
|
+
- **Highlight Galaxy** / **Highlight Members** buttons add regime-coloured splat overlays — CGM-regime members in dodgerblue, Hot-regime in tomato; the selected galaxy is marked with a white border ring
|
|
103
|
+
- **Double-click any point** in the viewport (any tab) to populate the Target tab's halo + galaxy IDs and draw a red marker on the selection. Only currently visible galaxies (passing all filters and focus) are selectable. If Focus is active, the camera carries to the new selection at the last-used radius.
|
|
104
|
+
- **Enter to run** in every input field — Halo idx, Galaxy idx, Coords X/Y/Z, Box bounds, Console command, script path, screenshot/movie label all submit on Enter, equivalent to clicking the paired Go / Zoom / Run / Take Screenshot button
|
|
105
|
+
|
|
106
|
+

|
|
107
|
+
|
|
108
|
+
### Filtering
|
|
109
|
+
- Halo filters: Mvir (log10), Rvir (Mpc/h), Vvir (km/s)
|
|
110
|
+
- Galaxy filters: stellar mass, sSFR, B/T, age, BH mass, ICS mass, type (centrals / satellites), FFB regime, CGM / Hot regime, environment class (Field / Isolated / Group / Cluster, via checkboxes in the Environment tab)
|
|
111
|
+
- Filters are **active-only** — a slider sitting at its full-range endpoints has no effect; move it inward to filter. Every galaxy with detectable mass is visible at startup.
|
|
112
|
+
- Filters auto-disable when the loaded model doesn't contain the underlying field
|
|
113
|
+
- Reset Filters button restores defaults
|
|
114
|
+
- **FoF links are filter-aware** — satellite→central gold lines are only drawn for halos that pass the active filter mask, focus sphere/box, and the halos-visible toggle; they stay correct during playback and recording
|
|
115
|
+
- **Playback respects all scene state** — the pre-render frame cache is keyed on filter values, focus region, layer visibility/opacity/color-mode, and FoF state; changing any of these and pressing Play again always produces fresh frames
|
|
116
|
+
|
|
117
|
+
### Side-by-side multi-box comparison
|
|
118
|
+
- Load two or more SAGE models side-by-side in a single viewport with `+SBS` in the Models section of the hamburger menu
|
|
119
|
+
- Each box is fully independent: its own snapshot, filters, colormaps, opacity, and visibility settings
|
|
120
|
+
- A box strip at the bottom of the viewport shows all loaded boxes; click any box label to make it active — the entire right panel (Structure, Filters, Target, Console, …) then controls that box
|
|
121
|
+
- Active box label is **green**; idle boxes are **white**
|
|
122
|
+
- Play, step, and the snapshot slider advance only the active box's snapshot
|
|
123
|
+
- Rotation is disabled in multi-box mode (all boxes share one camera; independent rotation is not supported)
|
|
124
|
+
- Halo Mvir colour mode is always locked to Viridis; the colormap selector is greyed out when Mvir is selected
|
|
125
|
+
- CLR button in the box strip resets that box to its defaults without affecting others
|
|
126
|
+
|
|
127
|
+
### Multi-model (overlays)
|
|
128
|
+
- Auto-scans `<sage_root>/output/` for SAGE model subfolders
|
|
129
|
+
- Switch the primary model from the hamburger menu (any box size)
|
|
130
|
+
- Overlay a second compatible model on top (same box size + snap count)
|
|
131
|
+
- Loading spinner during model swaps; warning snackbar for incompatible overlays
|
|
132
|
+
|
|
133
|
+
### Output
|
|
134
|
+
- Screenshots in PNG / JPG / TIFF
|
|
135
|
+
- Movie recording in GIF / MOV (H.264, via ffmpeg) / PNG sequence
|
|
136
|
+
- Configurable FPS (1 – 60) and resolution (Native / 2× / 4× supersampled)
|
|
137
|
+
- Optional user-typed label per capture; everything goes into a single session folder per app launch
|
|
138
|
+
|
|
139
|
+
### Launch Mode wizard
|
|
140
|
+
- Guided setup flow for configuring and launching SAGE26, accessible from the top-left Launch Mode menu or the Explore Mode hamburger
|
|
141
|
+
- Step chips in the header track progress (cyan = current step, green = done, white = pending)
|
|
142
|
+
- **Rescan** button re-runs the environment scan from scratch at any point
|
|
143
|
+
- **Create config file** option generates a new `.par` from the built-in millennium.par template; choose a custom filename before writing
|
|
144
|
+
- Par file editor opens side-by-side with the terminal when a `.par` file needs editing — both panels visible simultaneously
|
|
145
|
+
- Wizard always resets cleanly when reopened from Explore Mode
|
|
146
|
+
|
|
147
|
+
<!-- Console screenshot: drop docs/images/console.png here once captured -->
|
|
148
|
+
|
|
149
|
+
### Embedded console (Console tab)
|
|
150
|
+
- **Terminal mode** — a live xterm.js terminal backed by a real PTY (`$SHELL -l`); full ANSI colour, cursor control, and interactive programs (`vim`, `top`, `htop`, `less`) all work
|
|
151
|
+
- **SAGE command mode** — natural-language SAGE commands (`show only clusters`, `go to halo 42`, `snap 30`, `screenshot`, …); switch via the **SAGE Cmds** button, `terminal` returns to the shell
|
|
152
|
+
- **Multiple sessions** with a `+` button — each console has its own PTY process and command history
|
|
153
|
+
- **Pop-out** floats a movable / resizable console card over the viewport so you can keep typing while watching the render
|
|
154
|
+
|
|
155
|
+
### Self-contained metadata
|
|
156
|
+
- Cosmology (h, Ω_m, Ω_Λ), box size, and snapshot redshifts are read directly from `model_0.hdf5`'s `Header/Simulation`
|
|
157
|
+
- The `.par` file is now only needed for tree-file paths
|
|
158
|
+
|
|
159
|
+
## Supported simulations
|
|
160
|
+
|
|
161
|
+
| Simulation | Box size | Snapshots | Tree format |
|
|
162
|
+
|---|---|---|---|
|
|
163
|
+
| miniMillennium | 62.5 Mpc/h | 64 | lhalo_binary |
|
|
164
|
+
| microUchuu | 96 Mpc/h | 50 | lhalo_binary |
|
|
165
|
+
|
|
166
|
+
Both supported automatically — point at the `.par` file and SAGE-Viewer figures out the rest from the HDF5.
|
|
167
|
+
|
|
168
|
+
## Quick start
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
git clone https://github.com/MBradley1985/SAGE-Viewer
|
|
172
|
+
cd SAGE-Viewer
|
|
173
|
+
pip install -e .
|
|
174
|
+
sage-viewer --par /path/to/millennium.par
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
> **Note:** PyPI publishing is coming with v1.0. Until then, install from source as above.
|
|
178
|
+
|
|
179
|
+
Open the printed URL in any browser. To launch on a remote cluster and view locally, use SSH port-forwarding:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# On the cluster
|
|
183
|
+
sage-viewer --par millennium.par --port 8080
|
|
184
|
+
|
|
185
|
+
# In a local terminal
|
|
186
|
+
ssh -L 8080:localhost:8080 user@cluster
|
|
187
|
+
# Then open http://localhost:8080 in your browser
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Command-line options
|
|
191
|
+
|
|
192
|
+
```text
|
|
193
|
+
--par FILE Path to a SAGE .par file (required)
|
|
194
|
+
--par-dir DIR Directory to scan for additional .par files
|
|
195
|
+
(defaults to the parent of --par; used for the
|
|
196
|
+
multi-model dropdown)
|
|
197
|
+
--snap N Initial snapshot number (default: last = z=0)
|
|
198
|
+
--port N Trame server port (default: 8080)
|
|
199
|
+
--n-jobs N Worker threads for parallel halo file reads
|
|
200
|
+
--max-halos N Downsample ceiling per snapshot
|
|
201
|
+
--max-galaxies N Downsample ceiling per snapshot
|
|
202
|
+
--min-halo-mass MSUN Minimum halo mass to load
|
|
203
|
+
--min-stellar-mass MSUN Minimum stellar mass to load
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Multi-model workflow
|
|
207
|
+
|
|
208
|
+
If your SAGE root looks like:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
SAGE26/
|
|
212
|
+
├── input/
|
|
213
|
+
│ ├── millennium.par
|
|
214
|
+
│ ├── millennium_vanilla.par
|
|
215
|
+
│ └── microuchuu.par
|
|
216
|
+
└── output/
|
|
217
|
+
├── millennium/model_0.hdf5
|
|
218
|
+
├── millennium_vanilla/model_0.hdf5
|
|
219
|
+
└── microuchuu/model_0.hdf5
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
then `sage-viewer --par input/millennium.par` discovers all three models automatically. Click the hamburger icon (top-left) → Models section to switch, or click "+ overlay" next to a compatible model to render both at once.
|
|
223
|
+
|
|
224
|
+
## Installation
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# From source (PyPI release coming with v1.0)
|
|
228
|
+
git clone https://github.com/MBradley1985/SAGE-Viewer
|
|
229
|
+
cd SAGE-Viewer
|
|
230
|
+
pip install -e ".[dev]"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Requires Python ≥ 3.10. Movie recording in MOV format requires `ffmpeg` in your `PATH`.
|
|
234
|
+
|
|
235
|
+
### HPC / supercomputer
|
|
236
|
+
|
|
237
|
+
A helper script is included for module-system clusters (Slurm, PBS, etc.):
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# Load a Python module first (name varies by cluster)
|
|
241
|
+
module load python/3.12.0
|
|
242
|
+
|
|
243
|
+
# Create a venv and install SAGE-Viewer in one step
|
|
244
|
+
./install_hpc.sh
|
|
245
|
+
|
|
246
|
+
# Optional: place the venv on scratch for faster I/O
|
|
247
|
+
./install_hpc.sh /scratch/$USER/sage-viewer-env
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The install is editable (`pip install -e .`) so a `git pull` updates the code immediately with no reinstall. `ffmpeg` is checked separately — load it via your module system if you need MOV recording.
|
|
251
|
+
|
|
252
|
+
Then in every session:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
source .venv/bin/activate
|
|
256
|
+
sage-viewer --par /path/to/millennium.par --port 8080
|
|
257
|
+
# SSH-tunnel the port to your local browser
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Documentation
|
|
261
|
+
|
|
262
|
+
Full documentation at [sage-viewer.readthedocs.io](https://sage-viewer.readthedocs.io/en/latest/).
|
|
263
|
+
|
|
264
|
+
## Tabs at a glance
|
|
265
|
+
|
|
266
|
+
When multiple boxes are loaded a **box strip** appears at the bottom of the viewport. Click any box to make it active (green label). All tab controls then target that box.
|
|
267
|
+
|
|
268
|
+
| Tab | Purpose |
|
|
269
|
+
|---|---|
|
|
270
|
+
| Structure | Layer visibility, opacity, colour-by mode, colormap (with inline colorbar) |
|
|
271
|
+
| Filters | Range sliders for halo and galaxy properties |
|
|
272
|
+
| Record | Screenshots and movie recording |
|
|
273
|
+
| Target | Halo / galaxy navigation, focus zoom, Galaxy Info, Highlight Galaxy |
|
|
274
|
+
| Environment| Halo selector, environment-class checkboxes, Group Info, Highlight Members |
|
|
275
|
+
| Coords | Fly to arbitrary (x, y, z) — "Use Current Position" populates from camera; **Draw Sphere** places an interactive two-handle sphere (drag centre ball to translate, drag edge ball to resize); **Lock Sphere** commits it as the focus region |
|
|
276
|
+
| Box | Zoom to axis-aligned sub-box — "Use Current View" populates from camera; **Draw Box** places a resizable interactive box; **Lock Box** commits it as the focus region |
|
|
277
|
+
| Console | Live xterm.js shell terminal (PTY-backed) + SAGE natural-language command mode. Multiple sessions, pop-out window |
|
|
278
|
+
| Library | Browse stored screenshots / movies; double-click a row to open as a movable, resizable floating card over the viewport (multiple items open simultaneously); per-row delete button removes the file from disk immediately |
|
|
279
|
+
|
|
280
|
+

|
|
281
|
+
|
|
282
|
+
<!-- Library pop-out screenshot: drop docs/images/library_popup.png here once captured -->
|
|
283
|
+
|
|
284
|
+
The **Focus button** (top of the right panel) is tab-aware: it focuses on whatever's active in the current tab (target galaxy, environment halo, coords point, or box region).
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/images/SAGElogo.jpg" alt="SAGE-Viewer logo" width="220"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# SAGE-Viewer
|
|
6
|
+
|
|
7
|
+
An interactive 3D visualization package for [SAGE26](https://github.com/MBradley1985/SAGE26) semi-analytic galaxy formation outputs.
|
|
8
|
+
|
|
9
|
+
Renders dark matter haloes and SAGE galaxies together in a browser-based interactive viewer powered by [PyVista](https://pyvista.org) and [Trame](https://kitware.github.io/trame/).
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
### Rendering
|
|
16
|
+
- World-space gaussian splat rendering of haloes and galaxies — splats scale with camera distance and stay physically meaningful at any zoom
|
|
17
|
+
- **Structure** render mode: each galaxy drawn as a layered composition — cold-gas envelope (blue, sized by ColdGas) + outer envelope (green for CGM-regime sized by CGMgas, red for Hot-regime sized by HotGas)
|
|
18
|
+
- 27 selectable matplotlib colormaps, identical lists for halo and galaxy layers
|
|
19
|
+
- Live colormap, colour-by mode, opacity and visibility controls per layer
|
|
20
|
+
- Colour-by dropdowns are model-aware — only modes whose underlying field is present in the loaded model appear in the list; they update automatically on model switch
|
|
21
|
+
- Full still-quality rendering at all times — no resolution drop during camera drag or playback
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
### Playback & camera
|
|
26
|
+
- Play / Pause / Stop / Reverse / Repeat transport at 0.1× – 5× speeds
|
|
27
|
+
- Continuous camera rotation (CW / CCW at 15° / 30° / 60° per second)
|
|
28
|
+
- Reset / Centre / Focus buttons
|
|
29
|
+
- Fly to halo, galaxy, coordinates, or sub-box (with focus mode that masks everything outside)
|
|
30
|
+
- **Draw Sphere** (Coords tab): place a live two-handle sphere in the viewport — drag the centre ball to translate, drag the edge ball to resize; **Lock Sphere** commits it as the active focus region
|
|
31
|
+
- **Draw Box** (Box tab): place a live resizable box widget — drag any face or corner handle; **Lock Box** commits it; **Clear** on both tabs cancels the widget without navigating
|
|
32
|
+
- Switching models always lands at z=0 of the new model; slider and snap chip update immediately
|
|
33
|
+
- Camera bookmarks (save, restore, delete)
|
|
34
|
+
|
|
35
|
+
### Selection & inspection
|
|
36
|
+
- **Galaxy Info** panel (Target tab) — GalaxyID, type, halo Mvir, stellar mass, sSFR, cold gas, B/T, BH mass, H2 mass, gas regime, FFB regime, environment classification, mass-weighted stellar age
|
|
37
|
+
- **Group Info** panel (Environment tab) — FOF-aggregate stats: classification, member breakdown (centrals vs satellites), host Mvir, total stellar / cold gas / SFR, mean B/T, spatial extent, target role, BCG stellar mass
|
|
38
|
+
- **Highlight Galaxy** / **Highlight Members** buttons add regime-coloured splat overlays — CGM-regime members in dodgerblue, Hot-regime in tomato; the selected galaxy is marked with a white border ring
|
|
39
|
+
- **Double-click any point** in the viewport (any tab) to populate the Target tab's halo + galaxy IDs and draw a red marker on the selection. Only currently visible galaxies (passing all filters and focus) are selectable. If Focus is active, the camera carries to the new selection at the last-used radius.
|
|
40
|
+
- **Enter to run** in every input field — Halo idx, Galaxy idx, Coords X/Y/Z, Box bounds, Console command, script path, screenshot/movie label all submit on Enter, equivalent to clicking the paired Go / Zoom / Run / Take Screenshot button
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
### Filtering
|
|
45
|
+
- Halo filters: Mvir (log10), Rvir (Mpc/h), Vvir (km/s)
|
|
46
|
+
- Galaxy filters: stellar mass, sSFR, B/T, age, BH mass, ICS mass, type (centrals / satellites), FFB regime, CGM / Hot regime, environment class (Field / Isolated / Group / Cluster, via checkboxes in the Environment tab)
|
|
47
|
+
- Filters are **active-only** — a slider sitting at its full-range endpoints has no effect; move it inward to filter. Every galaxy with detectable mass is visible at startup.
|
|
48
|
+
- Filters auto-disable when the loaded model doesn't contain the underlying field
|
|
49
|
+
- Reset Filters button restores defaults
|
|
50
|
+
- **FoF links are filter-aware** — satellite→central gold lines are only drawn for halos that pass the active filter mask, focus sphere/box, and the halos-visible toggle; they stay correct during playback and recording
|
|
51
|
+
- **Playback respects all scene state** — the pre-render frame cache is keyed on filter values, focus region, layer visibility/opacity/color-mode, and FoF state; changing any of these and pressing Play again always produces fresh frames
|
|
52
|
+
|
|
53
|
+
### Side-by-side multi-box comparison
|
|
54
|
+
- Load two or more SAGE models side-by-side in a single viewport with `+SBS` in the Models section of the hamburger menu
|
|
55
|
+
- Each box is fully independent: its own snapshot, filters, colormaps, opacity, and visibility settings
|
|
56
|
+
- A box strip at the bottom of the viewport shows all loaded boxes; click any box label to make it active — the entire right panel (Structure, Filters, Target, Console, …) then controls that box
|
|
57
|
+
- Active box label is **green**; idle boxes are **white**
|
|
58
|
+
- Play, step, and the snapshot slider advance only the active box's snapshot
|
|
59
|
+
- Rotation is disabled in multi-box mode (all boxes share one camera; independent rotation is not supported)
|
|
60
|
+
- Halo Mvir colour mode is always locked to Viridis; the colormap selector is greyed out when Mvir is selected
|
|
61
|
+
- CLR button in the box strip resets that box to its defaults without affecting others
|
|
62
|
+
|
|
63
|
+
### Multi-model (overlays)
|
|
64
|
+
- Auto-scans `<sage_root>/output/` for SAGE model subfolders
|
|
65
|
+
- Switch the primary model from the hamburger menu (any box size)
|
|
66
|
+
- Overlay a second compatible model on top (same box size + snap count)
|
|
67
|
+
- Loading spinner during model swaps; warning snackbar for incompatible overlays
|
|
68
|
+
|
|
69
|
+
### Output
|
|
70
|
+
- Screenshots in PNG / JPG / TIFF
|
|
71
|
+
- Movie recording in GIF / MOV (H.264, via ffmpeg) / PNG sequence
|
|
72
|
+
- Configurable FPS (1 – 60) and resolution (Native / 2× / 4× supersampled)
|
|
73
|
+
- Optional user-typed label per capture; everything goes into a single session folder per app launch
|
|
74
|
+
|
|
75
|
+
### Launch Mode wizard
|
|
76
|
+
- Guided setup flow for configuring and launching SAGE26, accessible from the top-left Launch Mode menu or the Explore Mode hamburger
|
|
77
|
+
- Step chips in the header track progress (cyan = current step, green = done, white = pending)
|
|
78
|
+
- **Rescan** button re-runs the environment scan from scratch at any point
|
|
79
|
+
- **Create config file** option generates a new `.par` from the built-in millennium.par template; choose a custom filename before writing
|
|
80
|
+
- Par file editor opens side-by-side with the terminal when a `.par` file needs editing — both panels visible simultaneously
|
|
81
|
+
- Wizard always resets cleanly when reopened from Explore Mode
|
|
82
|
+
|
|
83
|
+
<!-- Console screenshot: drop docs/images/console.png here once captured -->
|
|
84
|
+
|
|
85
|
+
### Embedded console (Console tab)
|
|
86
|
+
- **Terminal mode** — a live xterm.js terminal backed by a real PTY (`$SHELL -l`); full ANSI colour, cursor control, and interactive programs (`vim`, `top`, `htop`, `less`) all work
|
|
87
|
+
- **SAGE command mode** — natural-language SAGE commands (`show only clusters`, `go to halo 42`, `snap 30`, `screenshot`, …); switch via the **SAGE Cmds** button, `terminal` returns to the shell
|
|
88
|
+
- **Multiple sessions** with a `+` button — each console has its own PTY process and command history
|
|
89
|
+
- **Pop-out** floats a movable / resizable console card over the viewport so you can keep typing while watching the render
|
|
90
|
+
|
|
91
|
+
### Self-contained metadata
|
|
92
|
+
- Cosmology (h, Ω_m, Ω_Λ), box size, and snapshot redshifts are read directly from `model_0.hdf5`'s `Header/Simulation`
|
|
93
|
+
- The `.par` file is now only needed for tree-file paths
|
|
94
|
+
|
|
95
|
+
## Supported simulations
|
|
96
|
+
|
|
97
|
+
| Simulation | Box size | Snapshots | Tree format |
|
|
98
|
+
|---|---|---|---|
|
|
99
|
+
| miniMillennium | 62.5 Mpc/h | 64 | lhalo_binary |
|
|
100
|
+
| microUchuu | 96 Mpc/h | 50 | lhalo_binary |
|
|
101
|
+
|
|
102
|
+
Both supported automatically — point at the `.par` file and SAGE-Viewer figures out the rest from the HDF5.
|
|
103
|
+
|
|
104
|
+
## Quick start
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git clone https://github.com/MBradley1985/SAGE-Viewer
|
|
108
|
+
cd SAGE-Viewer
|
|
109
|
+
pip install -e .
|
|
110
|
+
sage-viewer --par /path/to/millennium.par
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
> **Note:** PyPI publishing is coming with v1.0. Until then, install from source as above.
|
|
114
|
+
|
|
115
|
+
Open the printed URL in any browser. To launch on a remote cluster and view locally, use SSH port-forwarding:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# On the cluster
|
|
119
|
+
sage-viewer --par millennium.par --port 8080
|
|
120
|
+
|
|
121
|
+
# In a local terminal
|
|
122
|
+
ssh -L 8080:localhost:8080 user@cluster
|
|
123
|
+
# Then open http://localhost:8080 in your browser
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Command-line options
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
--par FILE Path to a SAGE .par file (required)
|
|
130
|
+
--par-dir DIR Directory to scan for additional .par files
|
|
131
|
+
(defaults to the parent of --par; used for the
|
|
132
|
+
multi-model dropdown)
|
|
133
|
+
--snap N Initial snapshot number (default: last = z=0)
|
|
134
|
+
--port N Trame server port (default: 8080)
|
|
135
|
+
--n-jobs N Worker threads for parallel halo file reads
|
|
136
|
+
--max-halos N Downsample ceiling per snapshot
|
|
137
|
+
--max-galaxies N Downsample ceiling per snapshot
|
|
138
|
+
--min-halo-mass MSUN Minimum halo mass to load
|
|
139
|
+
--min-stellar-mass MSUN Minimum stellar mass to load
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Multi-model workflow
|
|
143
|
+
|
|
144
|
+
If your SAGE root looks like:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
SAGE26/
|
|
148
|
+
├── input/
|
|
149
|
+
│ ├── millennium.par
|
|
150
|
+
│ ├── millennium_vanilla.par
|
|
151
|
+
│ └── microuchuu.par
|
|
152
|
+
└── output/
|
|
153
|
+
├── millennium/model_0.hdf5
|
|
154
|
+
├── millennium_vanilla/model_0.hdf5
|
|
155
|
+
└── microuchuu/model_0.hdf5
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
then `sage-viewer --par input/millennium.par` discovers all three models automatically. Click the hamburger icon (top-left) → Models section to switch, or click "+ overlay" next to a compatible model to render both at once.
|
|
159
|
+
|
|
160
|
+
## Installation
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# From source (PyPI release coming with v1.0)
|
|
164
|
+
git clone https://github.com/MBradley1985/SAGE-Viewer
|
|
165
|
+
cd SAGE-Viewer
|
|
166
|
+
pip install -e ".[dev]"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Requires Python ≥ 3.10. Movie recording in MOV format requires `ffmpeg` in your `PATH`.
|
|
170
|
+
|
|
171
|
+
### HPC / supercomputer
|
|
172
|
+
|
|
173
|
+
A helper script is included for module-system clusters (Slurm, PBS, etc.):
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Load a Python module first (name varies by cluster)
|
|
177
|
+
module load python/3.12.0
|
|
178
|
+
|
|
179
|
+
# Create a venv and install SAGE-Viewer in one step
|
|
180
|
+
./install_hpc.sh
|
|
181
|
+
|
|
182
|
+
# Optional: place the venv on scratch for faster I/O
|
|
183
|
+
./install_hpc.sh /scratch/$USER/sage-viewer-env
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The install is editable (`pip install -e .`) so a `git pull` updates the code immediately with no reinstall. `ffmpeg` is checked separately — load it via your module system if you need MOV recording.
|
|
187
|
+
|
|
188
|
+
Then in every session:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
source .venv/bin/activate
|
|
192
|
+
sage-viewer --par /path/to/millennium.par --port 8080
|
|
193
|
+
# SSH-tunnel the port to your local browser
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Documentation
|
|
197
|
+
|
|
198
|
+
Full documentation at [sage-viewer.readthedocs.io](https://sage-viewer.readthedocs.io/en/latest/).
|
|
199
|
+
|
|
200
|
+
## Tabs at a glance
|
|
201
|
+
|
|
202
|
+
When multiple boxes are loaded a **box strip** appears at the bottom of the viewport. Click any box to make it active (green label). All tab controls then target that box.
|
|
203
|
+
|
|
204
|
+
| Tab | Purpose |
|
|
205
|
+
|---|---|
|
|
206
|
+
| Structure | Layer visibility, opacity, colour-by mode, colormap (with inline colorbar) |
|
|
207
|
+
| Filters | Range sliders for halo and galaxy properties |
|
|
208
|
+
| Record | Screenshots and movie recording |
|
|
209
|
+
| Target | Halo / galaxy navigation, focus zoom, Galaxy Info, Highlight Galaxy |
|
|
210
|
+
| Environment| Halo selector, environment-class checkboxes, Group Info, Highlight Members |
|
|
211
|
+
| Coords | Fly to arbitrary (x, y, z) — "Use Current Position" populates from camera; **Draw Sphere** places an interactive two-handle sphere (drag centre ball to translate, drag edge ball to resize); **Lock Sphere** commits it as the focus region |
|
|
212
|
+
| Box | Zoom to axis-aligned sub-box — "Use Current View" populates from camera; **Draw Box** places a resizable interactive box; **Lock Box** commits it as the focus region |
|
|
213
|
+
| Console | Live xterm.js shell terminal (PTY-backed) + SAGE natural-language command mode. Multiple sessions, pop-out window |
|
|
214
|
+
| Library | Browse stored screenshots / movies; double-click a row to open as a movable, resizable floating card over the viewport (multiple items open simultaneously); per-row delete button removes the file from disk immediately |
|
|
215
|
+
|
|
216
|
+

|
|
217
|
+
|
|
218
|
+
<!-- Library pop-out screenshot: drop docs/images/library_popup.png here once captured -->
|
|
219
|
+
|
|
220
|
+
The **Focus button** (top of the right panel) is tab-aware: it focuses on whatever's active in the current tab (target galaxy, environment halo, coords point, or box region).
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sage-viewer"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Interactive 3D viewer for SAGE semi-analytic galaxy formation outputs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
authors = [{ name = "Michael Bradley", email = "mbrads85@live.com.au" }]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
keywords = ["astronomy", "galaxy-formation", "dark-matter", "visualization", "SAGE"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Astronomy",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"pyvista>=0.44",
|
|
27
|
+
"trame>=3.6",
|
|
28
|
+
"trame-vtk>=2.8",
|
|
29
|
+
"trame-vuetify>=2.7",
|
|
30
|
+
"h5py>=3.9",
|
|
31
|
+
"numpy>=1.24",
|
|
32
|
+
"joblib>=1.3",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
docs = [
|
|
37
|
+
"mkdocs-material>=9.5",
|
|
38
|
+
"mkdocstrings[python]>=0.24",
|
|
39
|
+
"mkdocs-macros-plugin>=1.0",
|
|
40
|
+
]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=7.4",
|
|
43
|
+
"pytest-cov>=4.1",
|
|
44
|
+
"ruff>=0.3",
|
|
45
|
+
"black>=24.0",
|
|
46
|
+
"pre-commit>=3.6",
|
|
47
|
+
"sage-viewer[docs]",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.scripts]
|
|
51
|
+
sage-viewer = "sage_viewer.cli:main"
|
|
52
|
+
|
|
53
|
+
[project.urls]
|
|
54
|
+
Homepage = "https://github.com/MBradley1985/SAGE-Viewer"
|
|
55
|
+
Documentation = "https://sage-viewer.readthedocs.io/en/latest/"
|
|
56
|
+
Repository = "https://github.com/MBradley1985/SAGE-Viewer"
|
|
57
|
+
Issues = "https://github.com/MBradley1985/SAGE-Viewer/issues"
|
|
58
|
+
|
|
59
|
+
[tool.setuptools.dynamic]
|
|
60
|
+
version = { attr = "sage_viewer._version.__version__" }
|
|
61
|
+
|
|
62
|
+
[tool.setuptools.packages.find]
|
|
63
|
+
where = ["."]
|
|
64
|
+
include = ["sage_viewer*"]
|
|
65
|
+
|
|
66
|
+
[tool.ruff]
|
|
67
|
+
line-length = 79
|
|
68
|
+
target-version = "py310"
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint]
|
|
71
|
+
select = ["E", "F", "W", "B", "C4", "UP"]
|
|
72
|
+
ignore = ["E203", "E266", "E501", "W503", "F403", "F401"]
|
|
73
|
+
|
|
74
|
+
[tool.black]
|
|
75
|
+
line-length = 79
|
|
76
|
+
target-version = ["py310", "py311", "py312"]
|
|
77
|
+
|
|
78
|
+
[tool.pytest.ini_options]
|
|
79
|
+
testpaths = ["tests"]
|
|
80
|
+
addopts = "-v --tb=short"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|