Spaces:
Runtime error
Runtime error
Deploy MindVisualizer live runtime
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +8 -0
- .gitattributes +3 -32
- .gitignore +4 -0
- Dockerfile +39 -0
- README.md +7 -6
- app.py +676 -0
- data/brain_alignment.json +28 -0
- data/extra_parcellation/README.md +53 -0
- data/extra_parcellation/combined_atlas.nii.gz +3 -0
- data/extra_parcellation/combined_atlas_labels.json +129 -0
- data/label_grid_cache.keys.json +1 -0
- data/label_grid_cache.npz +3 -0
- data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_entropy.bin +3 -0
- data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mean_xyz3.bin +3 -0
- data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_meta.json +1 -0
- data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu1_xyz3.bin +3 -0
- data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu2_xyz3.bin +3 -0
- data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu3_xyz3.bin +3 -0
- data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu4_xyz3.bin +3 -0
- data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu5_xyz3.bin +3 -0
- data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu6_xyz3.bin +3 -0
- data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_pi_xyz6.bin +3 -0
- data/mdn/mdn_particles_rdcim_teacher_edge_hq_training_points.npy +3 -0
- data/meshes/.keep +1 -0
- data/meshes_obj/10153.obj +3 -0
- data/meshes_obj/10154.obj +3 -0
- data/meshes_obj/10155.obj +3 -0
- data/meshes_obj/10156.obj +3 -0
- data/meshes_obj/10157.obj +3 -0
- data/meshes_obj/10158.obj +3 -0
- data/meshes_obj/10159.obj +3 -0
- data/meshes_obj/10292.obj +3 -0
- data/meshes_obj/10306.obj +3 -0
- data/meshes_obj/10307.obj +3 -0
- data/meshes_obj/10308.obj +3 -0
- data/meshes_obj/10311.obj +3 -0
- data/meshes_obj/10313.obj +3 -0
- data/meshes_obj/10327.obj +3 -0
- data/meshes_obj/10329.obj +3 -0
- data/meshes_obj/10330.obj +3 -0
- data/meshes_obj/10331.obj +3 -0
- data/meshes_obj/10332.obj +3 -0
- data/meshes_obj/10333.obj +3 -0
- data/meshes_obj/10334.obj +3 -0
- data/meshes_obj/10335.obj +3 -0
- data/meshes_obj/10336.obj +3 -0
- data/meshes_obj/10337.obj +3 -0
- data/meshes_obj/10338.obj +3 -0
- data/meshes_obj/10339.obj +3 -0
- data/meshes_obj/10342.obj +3 -0
.dockerignore
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.idea
|
| 3 |
+
.venv
|
| 4 |
+
__pycache__
|
| 5 |
+
*.pyc
|
| 6 |
+
*.pyo
|
| 7 |
+
.env
|
| 8 |
+
data/meshes
|
.gitattributes
CHANGED
|
@@ -1,35 +1,6 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.
|
| 17 |
-
*.
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
*.obj filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 2 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 4 |
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ply filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
*.pyo
|
| 4 |
+
.env
|
Dockerfile
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
ENV DEBIAN_FRONTEND=noninteractive \
|
| 4 |
+
DISPLAY=:99 \
|
| 5 |
+
LIBGL_ALWAYS_SOFTWARE=1 \
|
| 6 |
+
MESA_GL_VERSION_OVERRIDE=3.3 \
|
| 7 |
+
PIP_NO_CACHE_DIR=1 \
|
| 8 |
+
PYTHONUNBUFFERED=1
|
| 9 |
+
|
| 10 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 11 |
+
ca-certificates \
|
| 12 |
+
libegl1 \
|
| 13 |
+
libgl1 \
|
| 14 |
+
libgl1-mesa-dri \
|
| 15 |
+
libglib2.0-0 \
|
| 16 |
+
libglu1-mesa \
|
| 17 |
+
libsm6 \
|
| 18 |
+
libx11-6 \
|
| 19 |
+
libxext6 \
|
| 20 |
+
libxrender1 \
|
| 21 |
+
libxt6 \
|
| 22 |
+
xvfb \
|
| 23 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 24 |
+
|
| 25 |
+
WORKDIR /app/mindVisualizer
|
| 26 |
+
COPY . /app/mindVisualizer
|
| 27 |
+
|
| 28 |
+
RUN python -m pip install --upgrade pip setuptools wheel && \
|
| 29 |
+
python -m pip install \
|
| 30 |
+
numpy scipy vtk \
|
| 31 |
+
langchain langchain-openai langchain-community \
|
| 32 |
+
faiss-cpu python-dotenv certifi tiktoken openai \
|
| 33 |
+
brainglobe-atlasapi nibabel nilearn scikit-image \
|
| 34 |
+
trame trame-vtk trame-vuetify && \
|
| 35 |
+
chmod +x /app/mindVisualizer/start.sh
|
| 36 |
+
|
| 37 |
+
EXPOSE 7860
|
| 38 |
+
|
| 39 |
+
CMD ["/app/mindVisualizer/start.sh"]
|
README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
| 1 |
-
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
colorTo: green
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: MindVisualizer Live
|
| 3 |
+
colorFrom: green
|
| 4 |
+
colorTo: blue
|
|
|
|
| 5 |
sdk: docker
|
| 6 |
pinned: false
|
| 7 |
---
|
| 8 |
|
| 9 |
+
# MindVisualizer Live
|
| 10 |
+
|
| 11 |
+
Runs the Python/VTK MindVisualizer runtime through a Trame web transport.
|
app.py
ADDED
|
@@ -0,0 +1,676 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""HF-safe live MindVisualizer wrapper.
|
| 3 |
+
|
| 4 |
+
This is a web transport for the Python/VTK runtime. It imports the original
|
| 5 |
+
project modules for field loading, particle helpers, mesh overlay, probes, and
|
| 6 |
+
GPT analysis instead of reimplementing the algorithms in TypeScript.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import argparse
|
| 12 |
+
import asyncio
|
| 13 |
+
import os
|
| 14 |
+
import sys
|
| 15 |
+
import threading
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
import vtk
|
| 20 |
+
from scipy.spatial import cKDTree
|
| 21 |
+
from vtkmodules.util.numpy_support import numpy_to_vtk
|
| 22 |
+
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera
|
| 23 |
+
|
| 24 |
+
PROJECT_ROOT = Path(__file__).resolve().parent
|
| 25 |
+
DATA_DIR = PROJECT_ROOT / "data"
|
| 26 |
+
MDN_DIR = DATA_DIR / "mdn"
|
| 27 |
+
MESH_DIR = DATA_DIR / "meshes"
|
| 28 |
+
ALIGNMENT_FILE = DATA_DIR / "brain_alignment.json"
|
| 29 |
+
DEFAULT_META = MDN_DIR / "mdn_particles_rdcim_teacher_edge_hq_grid125_meta.json"
|
| 30 |
+
DEFAULT_OOS = MDN_DIR / "mdn_particles_rdcim_teacher_edge_hq_training_points.npy"
|
| 31 |
+
|
| 32 |
+
if str(PROJECT_ROOT) not in sys.path:
|
| 33 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 34 |
+
|
| 35 |
+
from src.colormaps import bicolor_white_center, rainbow_rgb01, turbo_rgb01
|
| 36 |
+
from src.field_loader import TriLinearSampler, load_field, load_points_any
|
| 37 |
+
from src.main import (
|
| 38 |
+
VtkTextOverlay,
|
| 39 |
+
add_window_legend,
|
| 40 |
+
build_cloud,
|
| 41 |
+
compute_fraction_mask,
|
| 42 |
+
densify_oos_surface,
|
| 43 |
+
lattice_positions,
|
| 44 |
+
wrap_inside,
|
| 45 |
+
)
|
| 46 |
+
from src.mesh_overlay import FlowMeshOverlay
|
| 47 |
+
from src.probe import ProbeSystem
|
| 48 |
+
from src.region_analyzer import analyze_probe_path, analyze_with_gpt
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def ensure_ssl() -> None:
|
| 52 |
+
try:
|
| 53 |
+
import certifi
|
| 54 |
+
|
| 55 |
+
cert_file = certifi.where()
|
| 56 |
+
for name in ("SSL_CERT_FILE", "REQUESTS_CA_BUNDLE"):
|
| 57 |
+
current = os.environ.get(name, "")
|
| 58 |
+
if not current or not os.path.isfile(current):
|
| 59 |
+
os.environ[name] = cert_file
|
| 60 |
+
except Exception:
|
| 61 |
+
pass
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class LiveMindVisualizer:
|
| 65 |
+
def __init__(self, args: argparse.Namespace):
|
| 66 |
+
self.args = args
|
| 67 |
+
self.rng = np.random.default_rng(2025)
|
| 68 |
+
self.paused = False
|
| 69 |
+
self.field_mode = "mean"
|
| 70 |
+
self.colour_mode = "SPEED"
|
| 71 |
+
self.cmap_mode = "TURBO"
|
| 72 |
+
self.filter_mode = "OFF"
|
| 73 |
+
self.filter_top = True
|
| 74 |
+
self.filter_frac = 1.0
|
| 75 |
+
self.clip_on = False
|
| 76 |
+
self.clip_frac = 1.0
|
| 77 |
+
self.mdn_alpha = 1.0
|
| 78 |
+
self.oos_visible = False
|
| 79 |
+
self.probe_busy = False
|
| 80 |
+
self.gpt_pending: str | None = None
|
| 81 |
+
self._load_data()
|
| 82 |
+
self._setup_vtk()
|
| 83 |
+
|
| 84 |
+
def _load_data(self) -> None:
|
| 85 |
+
self.field = load_field(self.args.meta)
|
| 86 |
+
self.G = self.field["G"]
|
| 87 |
+
self.amin = self.field["amin"]
|
| 88 |
+
self.amax = self.field["amax"]
|
| 89 |
+
self.V_mean = self.field["mean"]
|
| 90 |
+
self.MUS = self.field["mus"]
|
| 91 |
+
self.PI = self.field["pi"]
|
| 92 |
+
self.ENT = self.field["ENT"]
|
| 93 |
+
self.K = len(self.MUS)
|
| 94 |
+
|
| 95 |
+
self.sampler_mean = TriLinearSampler(self.V_mean, self.amin, self.amax)
|
| 96 |
+
self.diag = float(np.linalg.norm(self.amax - self.amin))
|
| 97 |
+
self.target_step = self.args.max_step_frac * max(self.diag, 1e-6)
|
| 98 |
+
self.vmax_mean = self._interior_percentile(self.V_mean)
|
| 99 |
+
|
| 100 |
+
self.samplers_comp = [TriLinearSampler(mu, self.amin, self.amax) for mu in self.MUS]
|
| 101 |
+
self.sampler_gated = None
|
| 102 |
+
self.V_gated = None
|
| 103 |
+
if self.PI is not None and self.PI.ndim == 4 and self.PI.shape[-1] == self.K and self.K > 0:
|
| 104 |
+
idx = np.argmax(self.PI, axis=-1)
|
| 105 |
+
self.V_gated = np.zeros_like(self.MUS[0], dtype=np.float32)
|
| 106 |
+
for k_i in range(self.K):
|
| 107 |
+
self.V_gated += self.MUS[k_i] * (idx == k_i)[..., None].astype(np.float32)
|
| 108 |
+
self.sampler_gated = TriLinearSampler(self.V_gated, self.amin, self.amax)
|
| 109 |
+
|
| 110 |
+
self.field_modes = ["mean"]
|
| 111 |
+
if self.sampler_gated is not None:
|
| 112 |
+
self.field_modes.append("gated")
|
| 113 |
+
self.field_modes += [f"comp{i + 1}" for i in range(self.K)]
|
| 114 |
+
|
| 115 |
+
self.sampler_by_name = {"mean": self.sampler_mean}
|
| 116 |
+
if self.sampler_gated is not None:
|
| 117 |
+
self.sampler_by_name["gated"] = self.sampler_gated
|
| 118 |
+
for i, sampler in enumerate(self.samplers_comp):
|
| 119 |
+
self.sampler_by_name[f"comp{i + 1}"] = sampler
|
| 120 |
+
|
| 121 |
+
self.vmax_by_name = {"mean": self.vmax_mean}
|
| 122 |
+
if self.V_gated is not None:
|
| 123 |
+
self.vmax_by_name["gated"] = self._interior_percentile(self.V_gated)
|
| 124 |
+
for i, mu in enumerate(self.MUS):
|
| 125 |
+
self.vmax_by_name[f"comp{i + 1}"] = self._interior_percentile(mu)
|
| 126 |
+
|
| 127 |
+
self.ent_min = self.ent_max = 0.0
|
| 128 |
+
if self.ENT is not None:
|
| 129 |
+
self.ent_min = float(np.min(self.ENT))
|
| 130 |
+
self.ent_max = float(np.max(self.ENT))
|
| 131 |
+
|
| 132 |
+
self.oos_pts = None
|
| 133 |
+
if self.args.oos is not None and Path(self.args.oos).exists():
|
| 134 |
+
self.oos_pts = load_points_any(Path(self.args.oos)).astype(np.float32)
|
| 135 |
+
|
| 136 |
+
self._seed_particles()
|
| 137 |
+
self._prepare_oos_distance()
|
| 138 |
+
|
| 139 |
+
def _interior_percentile(self, vec: np.ndarray, q: float = 100.0) -> float:
|
| 140 |
+
mag = np.linalg.norm(vec.reshape(-1, 3), axis=1)
|
| 141 |
+
nz = mag[mag > 0]
|
| 142 |
+
return float(np.percentile(nz, q)) if nz.size else 1.0
|
| 143 |
+
|
| 144 |
+
def _seed_particles(self) -> None:
|
| 145 |
+
overlap_radius = float(self.args.overlap_frac) * max(self.diag, 1e-9)
|
| 146 |
+
overlap_sigma = float(self.args.overlap_jitter) * max(self.diag, 1e-9)
|
| 147 |
+
self.seed_mode = "grid"
|
| 148 |
+
self.seed_points = None
|
| 149 |
+
self.n_axis = None
|
| 150 |
+
|
| 151 |
+
train = self.field["TRAIN"]
|
| 152 |
+
if train is not None and self.oos_pts is not None and len(train) > 0 and len(self.oos_pts) > 0:
|
| 153 |
+
try:
|
| 154 |
+
tree = cKDTree(train.astype(np.float32))
|
| 155 |
+
dists, _ = tree.query(self.oos_pts.astype(np.float32), k=1)
|
| 156 |
+
candidates = self.oos_pts[dists <= overlap_radius]
|
| 157 |
+
if candidates.size > 0:
|
| 158 |
+
self.seed_mode = "overlap"
|
| 159 |
+
if self.args.overlap_limit and len(candidates) > self.args.overlap_limit:
|
| 160 |
+
idx = self.rng.choice(len(candidates), size=int(self.args.overlap_limit), replace=False)
|
| 161 |
+
candidates = candidates[idx]
|
| 162 |
+
self.seed_points = candidates.astype(np.float32)
|
| 163 |
+
except Exception as exc:
|
| 164 |
+
print("[seed] overlap failed:", exc)
|
| 165 |
+
|
| 166 |
+
if self.seed_mode == "overlap" and self.seed_points is not None:
|
| 167 |
+
if self.args.oos_seed_limit > 0 and len(self.seed_points) > self.args.oos_seed_limit:
|
| 168 |
+
idx = self.rng.choice(len(self.seed_points), size=self.args.oos_seed_limit, replace=False)
|
| 169 |
+
self.seed_points = self.seed_points[idx]
|
| 170 |
+
if self.args.oos_fill_count > 0 and self.oos_pts is not None:
|
| 171 |
+
extras = densify_oos_surface(
|
| 172 |
+
base_pts=self.seed_points,
|
| 173 |
+
full_oos_pts=self.oos_pts,
|
| 174 |
+
amin=self.amin,
|
| 175 |
+
amax=self.amax,
|
| 176 |
+
extra_count=self.args.oos_fill_count,
|
| 177 |
+
)
|
| 178 |
+
if extras is not None and len(extras) > 0:
|
| 179 |
+
self.seed_points = np.concatenate([self.seed_points, extras], axis=0).astype(np.float32)
|
| 180 |
+
|
| 181 |
+
if self.seed_mode == "overlap" and self.seed_points is not None:
|
| 182 |
+
if overlap_sigma > 0:
|
| 183 |
+
jitter = self.rng.standard_normal(self.seed_points.shape).astype(np.float32) * overlap_sigma
|
| 184 |
+
self.P0 = np.clip(self.seed_points + jitter, self.amin, self.amax)
|
| 185 |
+
else:
|
| 186 |
+
self.P0 = np.clip(self.seed_points, self.amin, self.amax)
|
| 187 |
+
else:
|
| 188 |
+
self.n_axis = max(2, self.G // max(1, self.args.stride))
|
| 189 |
+
self.P0 = lattice_positions(
|
| 190 |
+
self.n_axis,
|
| 191 |
+
self.amin,
|
| 192 |
+
self.amax,
|
| 193 |
+
margin=0.02,
|
| 194 |
+
jitter=self.args.respawn_jitter,
|
| 195 |
+
seed=2025,
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
self.P = self.P0.copy()
|
| 199 |
+
self.Np = len(self.P)
|
| 200 |
+
self.base_ttl_lo = max(8, 60 // 2)
|
| 201 |
+
self.base_ttl_hi = 60
|
| 202 |
+
self.ttl_scale = 1.0
|
| 203 |
+
self.ttl = self._sample_ttl(self.Np)
|
| 204 |
+
self.ages = self.rng.integers(0, np.maximum(1, self.ttl), size=self.Np, dtype=np.int32)
|
| 205 |
+
self.ent_prev = self.sampler_mean.sample_scalar(self.ENT, self.P).astype(np.float32) if self.ENT is not None else None
|
| 206 |
+
self.ent_d_ema = np.zeros(self.Np, np.float32) if self.ENT is not None else None
|
| 207 |
+
|
| 208 |
+
def _sample_ttl(self, n: int) -> np.ndarray:
|
| 209 |
+
base = self.rng.integers(self.base_ttl_lo, self.base_ttl_hi + 1, size=int(n))
|
| 210 |
+
return np.maximum(1, np.round(base * self.ttl_scale)).astype(np.int32)
|
| 211 |
+
|
| 212 |
+
def _prepare_oos_distance(self) -> None:
|
| 213 |
+
self.OOS_DIST = None
|
| 214 |
+
self.sampler_oosdist = None
|
| 215 |
+
self.far_oos_params = None
|
| 216 |
+
if self.args.no_far_from_oos or self.oos_pts is None or len(self.oos_pts) == 0:
|
| 217 |
+
return
|
| 218 |
+
Ng = int(max(8, self.args.oos_dist_grid))
|
| 219 |
+
tree_oos = cKDTree(self.oos_pts.astype(np.float32))
|
| 220 |
+
gridP = lattice_positions(Ng, self.amin, self.amax, margin=0.0, jitter=0.0, seed=0)
|
| 221 |
+
dists, _ = tree_oos.query(gridP, k=1)
|
| 222 |
+
self.OOS_DIST = dists.reshape(Ng, Ng, Ng).astype(np.float32)
|
| 223 |
+
self.sampler_oosdist = TriLinearSampler(np.zeros((Ng, Ng, Ng, 3), np.float32), self.amin, self.amax)
|
| 224 |
+
self.far_oos_params = {
|
| 225 |
+
"d0": float(self.args.oos_dist_thresh_frac) * self.diag,
|
| 226 |
+
"gamma": float(self.args.oos_dist_gamma),
|
| 227 |
+
"boost": float(self.args.oos_death_boost),
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
def _setup_vtk(self) -> None:
|
| 231 |
+
init_rgba = np.tile(np.array([[255, 255, 255, 48]], np.uint8), (self.Np, 1))
|
| 232 |
+
self.points, self.color_arr, self.poly, self.p_actor = build_cloud(self.P, point_size=2.0, rgba=init_rgba)
|
| 233 |
+
|
| 234 |
+
self.ren = vtk.vtkRenderer()
|
| 235 |
+
self.ren.SetBackground(0, 0, 0)
|
| 236 |
+
self.ren.AddActor(self.p_actor)
|
| 237 |
+
|
| 238 |
+
self.win = vtk.vtkRenderWindow()
|
| 239 |
+
self.win.SetOffScreenRendering(1)
|
| 240 |
+
self.win.AddRenderer(self.ren)
|
| 241 |
+
self.win.SetSize(self.args.window_size[0], self.args.window_size[1])
|
| 242 |
+
self.win.SetWindowName("mindVisualizer Live")
|
| 243 |
+
|
| 244 |
+
self.iren = vtk.vtkRenderWindowInteractor()
|
| 245 |
+
self.iren.SetRenderWindow(self.win)
|
| 246 |
+
self.iren.SetInteractorStyle(vtkInteractorStyleTrackballCamera())
|
| 247 |
+
self.iren.Initialize()
|
| 248 |
+
|
| 249 |
+
self.o_actor = None
|
| 250 |
+
if self.oos_pts is not None and len(self.oos_pts) > 0:
|
| 251 |
+
rgb_oos = np.tile(np.array([[255, 255, 255]], np.uint8), (len(self.oos_pts), 1))
|
| 252 |
+
oos_rgba = np.concatenate([rgb_oos, np.full((len(self.oos_pts), 1), 72, np.uint8)], axis=1)
|
| 253 |
+
_, _, _, self.o_actor = build_cloud(self.oos_pts, point_size=1.5, rgba=oos_rgba, opacity=72 / 255.0)
|
| 254 |
+
|
| 255 |
+
self.flow_mesh = None
|
| 256 |
+
try:
|
| 257 |
+
self.flow_mesh = FlowMeshOverlay(ren=self.ren, win=self.win, mesh_dir=MESH_DIR, alignment_file=ALIGNMENT_FILE)
|
| 258 |
+
grid_cache = DATA_DIR / "label_grid_cache.npz"
|
| 259 |
+
if not self.flow_mesh.load_label_grid(grid_cache):
|
| 260 |
+
self.flow_mesh.build_label_grid()
|
| 261 |
+
self.flow_mesh.save_label_grid(grid_cache)
|
| 262 |
+
if self.flow_mesh.get_all_region_keys():
|
| 263 |
+
self.flow_mesh.cycle(+1)
|
| 264 |
+
except Exception as exc:
|
| 265 |
+
print("[flow mesh] disabled:", exc)
|
| 266 |
+
|
| 267 |
+
self.text_overlay = VtkTextOverlay(self.ren)
|
| 268 |
+
self.probe_sys = ProbeSystem(self.ren, self.win, self.amin, self.amax)
|
| 269 |
+
if self.flow_mesh is not None:
|
| 270 |
+
self.probe_sys.set_mesh_overlay(self.flow_mesh)
|
| 271 |
+
self.probe_sys.set_multi_count(self.args.multi_probe)
|
| 272 |
+
if self.args.branching and self.PI is not None:
|
| 273 |
+
self.probe_sys.set_branching(True, pi_field=self.PI, mus_samplers=self.samplers_comp)
|
| 274 |
+
|
| 275 |
+
add_window_legend(
|
| 276 |
+
self.ren,
|
| 277 |
+
[
|
| 278 |
+
"mindVisualizer live Python/VTK",
|
| 279 |
+
"space pause | f field | v colour | m cmap | q/w mesh | hide",
|
| 280 |
+
"[ ] dt | +/- speed | 1/2 lifetime | 7/8 alpha | y/u/z/x filter",
|
| 281 |
+
"o OOS | j clip | a/d frac | probe | analyze | clear",
|
| 282 |
+
],
|
| 283 |
+
font_px=12,
|
| 284 |
+
)
|
| 285 |
+
self.ren.ResetCamera()
|
| 286 |
+
for _ in range(12):
|
| 287 |
+
V = self.sampler_mean.sample_vec(self.P)
|
| 288 |
+
self.P = wrap_inside(
|
| 289 |
+
self.P + V * (self.args.dt * self.args.speed_scale) * (self.target_step / max(self.vmax_mean, 1e-9)),
|
| 290 |
+
self.amin,
|
| 291 |
+
self.amax,
|
| 292 |
+
margin_frac=self.args.margin,
|
| 293 |
+
)
|
| 294 |
+
if self.ENT is not None:
|
| 295 |
+
self.ent_prev = self.sampler_mean.sample_scalar(self.ENT, self.P).astype(np.float32)
|
| 296 |
+
self.ent_d_ema[:] = 0.0
|
| 297 |
+
self.win.Render()
|
| 298 |
+
|
| 299 |
+
def current_sampler(self) -> TriLinearSampler:
|
| 300 |
+
return self.sampler_by_name.get(self.field_mode, self.sampler_mean)
|
| 301 |
+
|
| 302 |
+
def vmax_for_mode(self) -> float:
|
| 303 |
+
return self.vmax_by_name.get(self.field_mode, self.vmax_mean)
|
| 304 |
+
|
| 305 |
+
def map_rgb_from_t(self, t01: np.ndarray) -> np.ndarray:
|
| 306 |
+
return rainbow_rgb01(t01) if self.cmap_mode == "RAINBOW" else turbo_rgb01(t01)
|
| 307 |
+
|
| 308 |
+
def apply_colors(self, speed_vals: np.ndarray, P_world: np.ndarray, dent_vals=None, vis_mask=None) -> None:
|
| 309 |
+
eps = 1e-12
|
| 310 |
+
if self.colour_mode == "ENTROPY" and self.ENT is not None:
|
| 311 |
+
scal = self.sampler_mean.sample_scalar(self.ENT, P_world)
|
| 312 |
+
t = (scal - self.ent_min) / max(self.ent_max - self.ent_min, eps)
|
| 313 |
+
base_rgb = self.map_rgb_from_t(np.clip(t, 0, 1))
|
| 314 |
+
elif self.colour_mode == "DELTA_ENTROPY" and dent_vals is not None:
|
| 315 |
+
a = np.abs(dent_vals)
|
| 316 |
+
scale = float(np.percentile(a, 97)) if a.size else 1.0
|
| 317 |
+
if not np.isfinite(scale) or scale <= eps:
|
| 318 |
+
scale = 1.0
|
| 319 |
+
base_rgb = self.map_rgb_from_t(np.clip(a / scale, 0, 1))
|
| 320 |
+
elif self.colour_mode == "DIR_DELTA_ENTROPY" and dent_vals is not None:
|
| 321 |
+
base_rgb = bicolor_white_center(dent_vals)
|
| 322 |
+
else:
|
| 323 |
+
smin = float(np.min(speed_vals)) if speed_vals.size else 0.0
|
| 324 |
+
smax = float(np.max(speed_vals)) if speed_vals.size else 1.0
|
| 325 |
+
t = (speed_vals - smin) / max(smax - smin, eps)
|
| 326 |
+
base_rgb = self.map_rgb_from_t(np.clip(t, 0, 1))
|
| 327 |
+
|
| 328 |
+
alpha = int(np.clip(255.0 * self.mdn_alpha, 5, 255))
|
| 329 |
+
a = np.full(len(base_rgb), alpha, np.uint8)
|
| 330 |
+
if vis_mask is not None:
|
| 331 |
+
a = a.copy()
|
| 332 |
+
a[~vis_mask] = 0
|
| 333 |
+
rgba = np.concatenate([base_rgb, a[:, None]], axis=1).astype(np.uint8)
|
| 334 |
+
self.color_arr.DeepCopy(numpy_to_vtk(rgba, deep=True))
|
| 335 |
+
self.color_arr.Modified()
|
| 336 |
+
self.poly.Modified()
|
| 337 |
+
|
| 338 |
+
def step(self) -> None:
|
| 339 |
+
if self.gpt_pending is not None:
|
| 340 |
+
result = self.gpt_pending
|
| 341 |
+
self.gpt_pending = None
|
| 342 |
+
self.probe_busy = False
|
| 343 |
+
self.text_overlay.show_gpt(result)
|
| 344 |
+
self.text_overlay.add_log("GPT analysis complete")
|
| 345 |
+
|
| 346 |
+
if self.paused:
|
| 347 |
+
self.win.Render()
|
| 348 |
+
return
|
| 349 |
+
|
| 350 |
+
sampler = self.current_sampler()
|
| 351 |
+
Vraw = sampler.sample_vec(self.P)
|
| 352 |
+
vmx = float(self.vmax_for_mode())
|
| 353 |
+
if self.clip_on:
|
| 354 |
+
thr = float(max(1e-9, self.clip_frac * vmx))
|
| 355 |
+
speed = np.linalg.norm(Vraw, axis=1)
|
| 356 |
+
k = np.minimum(1.0, thr / (speed + 1e-9)).astype(np.float32)
|
| 357 |
+
Vstep = Vraw * k[:, None]
|
| 358 |
+
eff_vmax = min(vmx, thr)
|
| 359 |
+
else:
|
| 360 |
+
Vstep = Vraw
|
| 361 |
+
eff_vmax = vmx
|
| 362 |
+
|
| 363 |
+
step_size = self.args.dt * self.args.speed_scale * (self.target_step / max(eff_vmax, 1e-9))
|
| 364 |
+
self.P = wrap_inside(self.P + Vstep * step_size, self.amin, self.amax, margin_frac=self.args.margin)
|
| 365 |
+
|
| 366 |
+
dent = None
|
| 367 |
+
if self.ENT is not None:
|
| 368 |
+
ent_now = self.sampler_mean.sample_scalar(self.ENT, self.P).astype(np.float32)
|
| 369 |
+
d = ent_now - self.ent_prev
|
| 370 |
+
self.ent_d_ema = (1.0 - 0.2) * self.ent_d_ema + 0.2 * d
|
| 371 |
+
self.ent_prev = ent_now
|
| 372 |
+
dent = self.ent_d_ema
|
| 373 |
+
|
| 374 |
+
age_inc = np.ones(len(self.P), np.int32)
|
| 375 |
+
if self.sampler_oosdist is not None and self.OOS_DIST is not None and self.far_oos_params is not None:
|
| 376 |
+
d_oos = self.sampler_oosdist.sample_scalar(self.OOS_DIST, self.P).astype(np.float32)
|
| 377 |
+
d0 = max(1e-9, self.far_oos_params["d0"])
|
| 378 |
+
t = np.clip((d_oos - d0) / d0, 0.0, 1.0)
|
| 379 |
+
gate = np.power(t, float(max(0.1, self.far_oos_params["gamma"]))).astype(np.float32)
|
| 380 |
+
age_inc += np.floor(gate * float(max(0.0, self.far_oos_params["boost"])) + 1e-9).astype(np.int32)
|
| 381 |
+
self.ages += age_inc
|
| 382 |
+
|
| 383 |
+
dead = self.ages >= self.ttl
|
| 384 |
+
if np.any(dead):
|
| 385 |
+
if self.seed_mode == "overlap" and self.seed_points is not None and len(self.seed_points) > 0:
|
| 386 |
+
sel = self.rng.integers(0, len(self.seed_points), size=dead.sum())
|
| 387 |
+
base = self.seed_points[sel]
|
| 388 |
+
if self.args.overlap_jitter > 0:
|
| 389 |
+
sigma = float(self.args.overlap_jitter) * max(self.diag, 1e-9)
|
| 390 |
+
jitter = self.rng.standard_normal((dead.sum(), 3)).astype(np.float32) * sigma
|
| 391 |
+
self.P[dead] = np.clip(base + jitter, self.amin, self.amax)
|
| 392 |
+
else:
|
| 393 |
+
self.P[dead] = np.clip(base, self.amin, self.amax)
|
| 394 |
+
else:
|
| 395 |
+
jitter_world = (self.amax - self.amin) * (self.args.respawn_jitter / max((self.n_axis or 2) - 1, 1))
|
| 396 |
+
jitter = (self.rng.random((dead.sum(), 3)).astype(np.float32) - 0.5) * 2.0 * jitter_world
|
| 397 |
+
self.P[dead] = np.clip(self.P0[dead] + jitter, self.amin, self.amax)
|
| 398 |
+
self.ages[dead] = 0
|
| 399 |
+
self.ttl[dead] = self._sample_ttl(dead.sum())
|
| 400 |
+
if self.ENT is not None:
|
| 401 |
+
self.ent_prev[dead] = self.sampler_mean.sample_scalar(self.ENT, self.P[dead]).astype(np.float32)
|
| 402 |
+
self.ent_d_ema[dead] = 0.0
|
| 403 |
+
|
| 404 |
+
if self.probe_sys.active:
|
| 405 |
+
self.probe_sys.step(sampler, step_size)
|
| 406 |
+
|
| 407 |
+
speed = np.linalg.norm(Vstep, axis=1)
|
| 408 |
+
if self.filter_mode == "OFF":
|
| 409 |
+
vis_mask = np.ones(len(self.P), dtype=bool)
|
| 410 |
+
else:
|
| 411 |
+
vis_mask = compute_fraction_mask(speed, self.filter_frac, top=self.filter_top)
|
| 412 |
+
|
| 413 |
+
self.points.SetData(numpy_to_vtk(self.P, deep=True))
|
| 414 |
+
self.points.Modified()
|
| 415 |
+
self.apply_colors(speed, self.P, dent_vals=dent, vis_mask=vis_mask)
|
| 416 |
+
self.win.Render()
|
| 417 |
+
|
| 418 |
+
def cycle_field(self) -> str:
|
| 419 |
+
idx = self.field_modes.index(self.field_mode)
|
| 420 |
+
self.field_mode = self.field_modes[(idx + 1) % len(self.field_modes)]
|
| 421 |
+
return self.field_mode
|
| 422 |
+
|
| 423 |
+
def cycle_colour(self) -> str:
|
| 424 |
+
modes = ["SPEED"]
|
| 425 |
+
if self.ENT is not None:
|
| 426 |
+
modes += ["ENTROPY", "DELTA_ENTROPY", "DIR_DELTA_ENTROPY"]
|
| 427 |
+
idx = modes.index(self.colour_mode) if self.colour_mode in modes else 0
|
| 428 |
+
self.colour_mode = modes[(idx + 1) % len(modes)]
|
| 429 |
+
return self.colour_mode
|
| 430 |
+
|
| 431 |
+
def place_probe_center(self) -> None:
|
| 432 |
+
pos = ((self.amin + self.amax) * 0.5).astype(np.float32)
|
| 433 |
+
self.probe_sys.place(pos)
|
| 434 |
+
self.text_overlay.add_log("Probe placed at brain center")
|
| 435 |
+
|
| 436 |
+
def analyze_probe(self, use_rag: bool) -> None:
|
| 437 |
+
if not self.probe_sys.active or self.flow_mesh is None or self.probe_busy:
|
| 438 |
+
self.text_overlay.add_log("No probe ready to analyze")
|
| 439 |
+
return
|
| 440 |
+
self.probe_sys.freeze()
|
| 441 |
+
snapshots = []
|
| 442 |
+
for probe in self.probe_sys.get_all_probes():
|
| 443 |
+
if not probe.path or len(probe.path) < 3:
|
| 444 |
+
continue
|
| 445 |
+
snapshots.append(
|
| 446 |
+
{
|
| 447 |
+
"path": probe.get_path_array().copy(),
|
| 448 |
+
"field_mags": probe.get_field_mags_array().copy(),
|
| 449 |
+
"ghost": probe.ghost,
|
| 450 |
+
"label": probe.label,
|
| 451 |
+
}
|
| 452 |
+
)
|
| 453 |
+
if not snapshots:
|
| 454 |
+
self.text_overlay.add_log("No probe data")
|
| 455 |
+
return
|
| 456 |
+
self.probe_busy = True
|
| 457 |
+
self.text_overlay.add_log("Analyzing probe path...")
|
| 458 |
+
threading.Thread(target=self._analyze_async, args=(snapshots, use_rag), daemon=True).start()
|
| 459 |
+
|
| 460 |
+
def _analyze_async(self, snapshots, use_rag: bool) -> None:
|
| 461 |
+
try:
|
| 462 |
+
transitions = []
|
| 463 |
+
for snap in snapshots:
|
| 464 |
+
transitions.extend(
|
| 465 |
+
analyze_probe_path(
|
| 466 |
+
snap["path"],
|
| 467 |
+
self.flow_mesh,
|
| 468 |
+
sample_every=5,
|
| 469 |
+
field_mags=snap["field_mags"] if len(snap["field_mags"]) == len(snap["path"]) else None,
|
| 470 |
+
entropy_sampler=self.sampler_mean if self.ENT is not None else None,
|
| 471 |
+
entropy_field=self.ENT,
|
| 472 |
+
)
|
| 473 |
+
)
|
| 474 |
+
if not transitions:
|
| 475 |
+
self.gpt_pending = "No brain regions detected along probe path."
|
| 476 |
+
return
|
| 477 |
+
self.gpt_pending = analyze_with_gpt(transitions, use_rag=use_rag, model=self.args.model, debug=False)
|
| 478 |
+
except Exception as exc:
|
| 479 |
+
self.gpt_pending = f"[GPT ERROR] {exc}"
|
| 480 |
+
|
| 481 |
+
@property
|
| 482 |
+
def status(self) -> str:
|
| 483 |
+
mesh_count = len(self.flow_mesh.get_all_region_keys()) if self.flow_mesh else 0
|
| 484 |
+
return (
|
| 485 |
+
f"{self.field_mode} | {self.colour_mode} | "
|
| 486 |
+
f"{self.Np:,} particles | {mesh_count} meshes | "
|
| 487 |
+
f"{'paused' if self.paused else 'running'}"
|
| 488 |
+
)
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
def build_arg_parser() -> argparse.ArgumentParser:
|
| 492 |
+
parser = argparse.ArgumentParser(description="MindVisualizer Trame live server")
|
| 493 |
+
parser.add_argument("--host", default="0.0.0.0")
|
| 494 |
+
parser.add_argument("--port", type=int, default=int(os.environ.get("PORT", "7860")))
|
| 495 |
+
parser.add_argument("--meta", type=Path, default=DEFAULT_META)
|
| 496 |
+
parser.add_argument("--oos", type=Path, default=DEFAULT_OOS)
|
| 497 |
+
parser.add_argument("--fps", type=int, default=int(os.environ.get("MINDVIS_FPS", "18")))
|
| 498 |
+
parser.add_argument("--stride", type=int, default=3)
|
| 499 |
+
parser.add_argument("--dt", type=float, default=1.0)
|
| 500 |
+
parser.add_argument("--max-step-frac", type=float, default=0.01)
|
| 501 |
+
parser.add_argument("--speed-scale", type=float, default=1.0)
|
| 502 |
+
parser.add_argument("--margin", type=float, default=0.0)
|
| 503 |
+
parser.add_argument("--respawn-jitter", type=float, default=0.015)
|
| 504 |
+
parser.add_argument("--oos-seed-limit", type=int, default=50000)
|
| 505 |
+
parser.add_argument("--oos-fill-count", type=int, default=10000)
|
| 506 |
+
parser.add_argument("--overlap-frac", type=float, default=0.01)
|
| 507 |
+
parser.add_argument("--overlap-jitter", type=float, default=0.002)
|
| 508 |
+
parser.add_argument("--overlap-limit", type=int, default=200000)
|
| 509 |
+
parser.add_argument("--no-far-from-oos", action="store_true")
|
| 510 |
+
parser.add_argument("--oos-dist-grid", type=int, default=96)
|
| 511 |
+
parser.add_argument("--oos-dist-thresh-frac", type=float, default=0.009)
|
| 512 |
+
parser.add_argument("--oos-dist-gamma", type=float, default=3.0)
|
| 513 |
+
parser.add_argument("--oos-death-boost", type=float, default=8.0)
|
| 514 |
+
parser.add_argument("--multi-probe", type=int, default=1)
|
| 515 |
+
parser.add_argument("--branching", action="store_true")
|
| 516 |
+
parser.add_argument("--window-size", type=int, nargs=2, default=[1320, 820])
|
| 517 |
+
parser.add_argument("--model", default=os.environ.get("OPENAI_MODEL", "gpt-5.4-mini"))
|
| 518 |
+
parser.add_argument("--rag", action="store_true")
|
| 519 |
+
return parser
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
def main() -> None:
|
| 523 |
+
ensure_ssl()
|
| 524 |
+
args = build_arg_parser().parse_args()
|
| 525 |
+
|
| 526 |
+
from trame.app import get_server
|
| 527 |
+
from trame.ui.vuetify3 import SinglePageLayout
|
| 528 |
+
from trame.widgets import html, vtk as vtk_widgets, vuetify3
|
| 529 |
+
|
| 530 |
+
app = LiveMindVisualizer(args)
|
| 531 |
+
server = get_server(client_type="vue3")
|
| 532 |
+
state, ctrl = server.state, server.controller
|
| 533 |
+
state.trame__title = "MindVisualizer Live"
|
| 534 |
+
state.status = app.status
|
| 535 |
+
|
| 536 |
+
def refresh_status() -> None:
|
| 537 |
+
state.status = app.status
|
| 538 |
+
state.flush()
|
| 539 |
+
|
| 540 |
+
def update_view() -> None:
|
| 541 |
+
if hasattr(ctrl, "view_update"):
|
| 542 |
+
ctrl.view_update()
|
| 543 |
+
refresh_status()
|
| 544 |
+
|
| 545 |
+
@ctrl.add("toggle_pause")
|
| 546 |
+
def toggle_pause(**_):
|
| 547 |
+
app.paused = not app.paused
|
| 548 |
+
update_view()
|
| 549 |
+
|
| 550 |
+
@ctrl.add("cycle_field")
|
| 551 |
+
def cycle_field(**_):
|
| 552 |
+
app.cycle_field()
|
| 553 |
+
update_view()
|
| 554 |
+
|
| 555 |
+
@ctrl.add("cycle_colour")
|
| 556 |
+
def cycle_colour(**_):
|
| 557 |
+
app.cycle_colour()
|
| 558 |
+
update_view()
|
| 559 |
+
|
| 560 |
+
@ctrl.add("cycle_cmap")
|
| 561 |
+
def cycle_cmap(**_):
|
| 562 |
+
app.cmap_mode = "RAINBOW" if app.cmap_mode == "TURBO" else "TURBO"
|
| 563 |
+
update_view()
|
| 564 |
+
|
| 565 |
+
@ctrl.add("mesh_prev")
|
| 566 |
+
def mesh_prev(**_):
|
| 567 |
+
if app.flow_mesh:
|
| 568 |
+
app.flow_mesh.cycle(-1)
|
| 569 |
+
update_view()
|
| 570 |
+
|
| 571 |
+
@ctrl.add("mesh_next")
|
| 572 |
+
def mesh_next(**_):
|
| 573 |
+
if app.flow_mesh:
|
| 574 |
+
app.flow_mesh.cycle(+1)
|
| 575 |
+
update_view()
|
| 576 |
+
|
| 577 |
+
@ctrl.add("mesh_hide")
|
| 578 |
+
def mesh_hide(**_):
|
| 579 |
+
if app.flow_mesh:
|
| 580 |
+
app.flow_mesh.hide()
|
| 581 |
+
update_view()
|
| 582 |
+
|
| 583 |
+
@ctrl.add("mesh_alpha_down")
|
| 584 |
+
def mesh_alpha_down(**_):
|
| 585 |
+
if app.flow_mesh:
|
| 586 |
+
app.flow_mesh.set_opacity(1.0 / 1.25)
|
| 587 |
+
update_view()
|
| 588 |
+
|
| 589 |
+
@ctrl.add("mesh_alpha_up")
|
| 590 |
+
def mesh_alpha_up(**_):
|
| 591 |
+
if app.flow_mesh:
|
| 592 |
+
app.flow_mesh.set_opacity(1.25)
|
| 593 |
+
update_view()
|
| 594 |
+
|
| 595 |
+
@ctrl.add("toggle_oos")
|
| 596 |
+
def toggle_oos(**_):
|
| 597 |
+
if app.o_actor is not None:
|
| 598 |
+
if app.oos_visible:
|
| 599 |
+
app.ren.RemoveActor(app.o_actor)
|
| 600 |
+
else:
|
| 601 |
+
app.ren.AddActor(app.o_actor)
|
| 602 |
+
app.oos_visible = not app.oos_visible
|
| 603 |
+
update_view()
|
| 604 |
+
|
| 605 |
+
@ctrl.add("place_probe")
|
| 606 |
+
def place_probe(**_):
|
| 607 |
+
app.place_probe_center()
|
| 608 |
+
update_view()
|
| 609 |
+
|
| 610 |
+
@ctrl.add("analyze_probe")
|
| 611 |
+
def analyze_probe(**_):
|
| 612 |
+
app.analyze_probe(use_rag=args.rag)
|
| 613 |
+
update_view()
|
| 614 |
+
|
| 615 |
+
@ctrl.add("clear_probe")
|
| 616 |
+
def clear_probe(**_):
|
| 617 |
+
app.probe_sys.clear()
|
| 618 |
+
app.text_overlay.clear_log()
|
| 619 |
+
app.text_overlay.hide_gpt()
|
| 620 |
+
update_view()
|
| 621 |
+
|
| 622 |
+
@ctrl.add("reset_camera")
|
| 623 |
+
def reset_camera(**_):
|
| 624 |
+
if hasattr(ctrl, "view_reset_camera"):
|
| 625 |
+
ctrl.view_reset_camera()
|
| 626 |
+
update_view()
|
| 627 |
+
|
| 628 |
+
async def animation_loop():
|
| 629 |
+
delay = 1.0 / max(1, int(args.fps))
|
| 630 |
+
while True:
|
| 631 |
+
app.step()
|
| 632 |
+
if hasattr(ctrl, "view_update"):
|
| 633 |
+
ctrl.view_update()
|
| 634 |
+
state.status = app.status
|
| 635 |
+
state.flush()
|
| 636 |
+
await asyncio.sleep(delay)
|
| 637 |
+
|
| 638 |
+
ctrl.on_server_ready.add(lambda **_: asyncio.create_task(animation_loop()))
|
| 639 |
+
|
| 640 |
+
with SinglePageLayout(server) as layout:
|
| 641 |
+
layout.title.set_text("MindVisualizer Live")
|
| 642 |
+
with layout.toolbar:
|
| 643 |
+
vuetify3.VBtn("Pause", click=ctrl.toggle_pause, density="compact", variant="tonal")
|
| 644 |
+
vuetify3.VBtn("Field", click=ctrl.cycle_field, density="compact", variant="tonal")
|
| 645 |
+
vuetify3.VBtn("Colour", click=ctrl.cycle_colour, density="compact", variant="tonal")
|
| 646 |
+
vuetify3.VBtn("Cmap", click=ctrl.cycle_cmap, density="compact", variant="tonal")
|
| 647 |
+
vuetify3.VDivider(vertical=True, classes="mx-2")
|
| 648 |
+
vuetify3.VBtn("Mesh -", click=ctrl.mesh_prev, density="compact", variant="tonal")
|
| 649 |
+
vuetify3.VBtn("Mesh +", click=ctrl.mesh_next, density="compact", variant="tonal")
|
| 650 |
+
vuetify3.VBtn("Hide", click=ctrl.mesh_hide, density="compact", variant="tonal")
|
| 651 |
+
vuetify3.VBtn("Alpha -", click=ctrl.mesh_alpha_down, density="compact", variant="tonal")
|
| 652 |
+
vuetify3.VBtn("Alpha +", click=ctrl.mesh_alpha_up, density="compact", variant="tonal")
|
| 653 |
+
vuetify3.VDivider(vertical=True, classes="mx-2")
|
| 654 |
+
vuetify3.VBtn("OOS", click=ctrl.toggle_oos, density="compact", variant="tonal")
|
| 655 |
+
vuetify3.VBtn("Probe", click=ctrl.place_probe, density="compact", variant="tonal")
|
| 656 |
+
vuetify3.VBtn("Analyze", click=ctrl.analyze_probe, density="compact", variant="tonal")
|
| 657 |
+
vuetify3.VBtn("Clear", click=ctrl.clear_probe, density="compact", variant="tonal")
|
| 658 |
+
vuetify3.VSpacer()
|
| 659 |
+
html.Div("{{ status }}", classes="text-caption text-medium-emphasis")
|
| 660 |
+
|
| 661 |
+
with layout.content:
|
| 662 |
+
with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height", style="background: #000;"):
|
| 663 |
+
view = vtk_widgets.VtkRemoteView(
|
| 664 |
+
app.win,
|
| 665 |
+
ref="view",
|
| 666 |
+
interactive_ratio=0.65,
|
| 667 |
+
interactive_quality=55,
|
| 668 |
+
)
|
| 669 |
+
ctrl.view_update = view.update
|
| 670 |
+
ctrl.view_reset_camera = view.reset_camera
|
| 671 |
+
|
| 672 |
+
server.start(host=args.host, port=args.port)
|
| 673 |
+
|
| 674 |
+
|
| 675 |
+
if __name__ == "__main__":
|
| 676 |
+
main()
|
data/brain_alignment.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"matrix": [
|
| 3 |
+
[
|
| 4 |
+
-0.03483789662196803,
|
| 5 |
+
-0.1654855793300339,
|
| 6 |
+
0.9305766368497694,
|
| 7 |
+
-68.4377205759064
|
| 8 |
+
],
|
| 9 |
+
[
|
| 10 |
+
-0.9422190580860553,
|
| 11 |
+
-0.06752555868994387,
|
| 12 |
+
-0.047281905216520856,
|
| 13 |
+
106.73199000287599
|
| 14 |
+
],
|
| 15 |
+
[
|
| 16 |
+
0.0747101104018069,
|
| 17 |
+
-0.9287770749837014,
|
| 18 |
+
-0.16236864674663903,
|
| 19 |
+
115.80117022013123
|
| 20 |
+
],
|
| 21 |
+
[
|
| 22 |
+
0.0,
|
| 23 |
+
0.0,
|
| 24 |
+
0.0,
|
| 25 |
+
1.0
|
| 26 |
+
]
|
| 27 |
+
]
|
| 28 |
+
}
|
data/extra_parcellation/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Extra Parcellation Data
|
| 2 |
+
|
| 3 |
+
Provides fine-grained subregion labels alongside the primary Allen atlas meshes.
|
| 4 |
+
|
| 5 |
+
## Setup (recommended)
|
| 6 |
+
|
| 7 |
+
Run the setup script to build the combined atlas automatically:
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
python scripts/setup_extra_parcellation.py
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
This downloads and merges three complementary MNI-space atlases via nilearn:
|
| 14 |
+
|
| 15 |
+
| Layer | Source | Regions | Role |
|
| 16 |
+
|-------|--------|---------|------|
|
| 17 |
+
| 1 | Harvard-Oxford Cortical | 48 | Broad cortical coverage |
|
| 18 |
+
| 2 | Harvard-Oxford Subcortical | 17 | Thalamus, putamen, caudate, hippocampus, amygdala, etc. |
|
| 19 |
+
| 3 | **Julich-Brain** (highest priority) | 62 | Cytoarchitectonic: motor BA4a/4p, somatosensory BA1-3, visual V1-V5, auditory, Broca's, hippocampal subfields |
|
| 20 |
+
|
| 21 |
+
**Total: 127 labeled regions**, Julich-Brain labels override coarser Harvard-Oxford
|
| 22 |
+
where available.
|
| 23 |
+
|
| 24 |
+
Output files (auto-detected by flow mode):
|
| 25 |
+
- `combined_atlas.nii.gz` — NIfTI volume (~700KB)
|
| 26 |
+
- `combined_atlas_labels.json` — label ID to name map
|
| 27 |
+
|
| 28 |
+
## How it works
|
| 29 |
+
|
| 30 |
+
The combined atlas is in MNI152 1mm space. The Allen atlas scene space is
|
| 31 |
+
approximately MNI-aligned, so probe coordinates can be used directly for lookups.
|
| 32 |
+
When the exact voxel is unlabeled, a nearby-search fallback finds the closest
|
| 33 |
+
labeled region within a few mm.
|
| 34 |
+
|
| 35 |
+
When both the primary Allen mesh and the extra parcellation cover a probe position,
|
| 36 |
+
both are highlighted: the Allen mesh in red, the extra parcellation subregion in
|
| 37 |
+
orange. The subregion name is included in the LLM context as additional detail.
|
| 38 |
+
|
| 39 |
+
## Custom atlas
|
| 40 |
+
|
| 41 |
+
You can also provide any NIfTI atlas in MNI space:
|
| 42 |
+
|
| 43 |
+
```bash
|
| 44 |
+
python examples/flow_probe_example.py \
|
| 45 |
+
--extra-parcellation my_atlas.nii.gz \
|
| 46 |
+
--extra-parcellation-labels my_atlas_labels.json
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
## Requirements
|
| 50 |
+
|
| 51 |
+
```bash
|
| 52 |
+
pip install nilearn nibabel scikit-image
|
| 53 |
+
```
|
data/extra_parcellation/combined_atlas.nii.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:68b7dbb0b9949e0e3021d60db9d6f1499615560cba955e0a2f6ab2e41962d25a
|
| 3 |
+
size 693799
|
data/extra_parcellation/combined_atlas_labels.json
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"1": "Frontal Pole",
|
| 3 |
+
"2": "Insular Cortex",
|
| 4 |
+
"3": "Superior Frontal Gyrus",
|
| 5 |
+
"4": "Middle Frontal Gyrus",
|
| 6 |
+
"5": "Inferior Frontal Gyrus, pars triangularis",
|
| 7 |
+
"6": "Inferior Frontal Gyrus, pars opercularis",
|
| 8 |
+
"7": "Precentral Gyrus",
|
| 9 |
+
"8": "Temporal Pole",
|
| 10 |
+
"9": "Superior Temporal Gyrus, anterior division",
|
| 11 |
+
"10": "Superior Temporal Gyrus, posterior division",
|
| 12 |
+
"11": "Middle Temporal Gyrus, anterior division",
|
| 13 |
+
"12": "Middle Temporal Gyrus, posterior division",
|
| 14 |
+
"13": "Middle Temporal Gyrus, temporooccipital part",
|
| 15 |
+
"14": "Inferior Temporal Gyrus, anterior division",
|
| 16 |
+
"15": "Inferior Temporal Gyrus, posterior division",
|
| 17 |
+
"16": "Inferior Temporal Gyrus, temporooccipital part",
|
| 18 |
+
"17": "Postcentral Gyrus",
|
| 19 |
+
"18": "Superior Parietal Lobule",
|
| 20 |
+
"19": "Supramarginal Gyrus, anterior division",
|
| 21 |
+
"20": "Supramarginal Gyrus, posterior division",
|
| 22 |
+
"21": "Angular Gyrus",
|
| 23 |
+
"22": "Lateral Occipital Cortex, superior division",
|
| 24 |
+
"23": "Lateral Occipital Cortex, inferior division",
|
| 25 |
+
"24": "Intracalcarine Cortex",
|
| 26 |
+
"25": "Frontal Medial Cortex",
|
| 27 |
+
"26": "Juxtapositional Lobule Cortex (formerly Supplementary Motor Cortex)",
|
| 28 |
+
"27": "Subcallosal Cortex",
|
| 29 |
+
"28": "Paracingulate Gyrus",
|
| 30 |
+
"29": "Cingulate Gyrus, anterior division",
|
| 31 |
+
"30": "Cingulate Gyrus, posterior division",
|
| 32 |
+
"31": "Precuneous Cortex",
|
| 33 |
+
"32": "Cuneal Cortex",
|
| 34 |
+
"33": "Frontal Orbital Cortex",
|
| 35 |
+
"34": "Parahippocampal Gyrus, anterior division",
|
| 36 |
+
"35": "Parahippocampal Gyrus, posterior division",
|
| 37 |
+
"36": "Lingual Gyrus",
|
| 38 |
+
"37": "Temporal Fusiform Cortex, anterior division",
|
| 39 |
+
"38": "Temporal Fusiform Cortex, posterior division",
|
| 40 |
+
"39": "Temporal Occipital Fusiform Cortex",
|
| 41 |
+
"40": "Occipital Fusiform Gyrus",
|
| 42 |
+
"41": "Frontal Opercular Cortex",
|
| 43 |
+
"42": "Central Opercular Cortex",
|
| 44 |
+
"43": "Parietal Opercular Cortex",
|
| 45 |
+
"44": "Planum Polare",
|
| 46 |
+
"45": "Heschl's Gyrus (includes H1 and H2)",
|
| 47 |
+
"46": "Planum Temporale",
|
| 48 |
+
"47": "Supracalcarine Cortex",
|
| 49 |
+
"48": "Occipital Pole",
|
| 50 |
+
"103": "Left Lateral Ventricle",
|
| 51 |
+
"104": "Left Thalamus",
|
| 52 |
+
"105": "Left Caudate",
|
| 53 |
+
"106": "Left Putamen",
|
| 54 |
+
"107": "Left Pallidum",
|
| 55 |
+
"108": "Brain-Stem",
|
| 56 |
+
"109": "Left Hippocampus",
|
| 57 |
+
"110": "Left Amygdala",
|
| 58 |
+
"111": "Left Accumbens",
|
| 59 |
+
"114": "Right Lateral Ventricle",
|
| 60 |
+
"115": "Right Thalamus",
|
| 61 |
+
"116": "Right Caudate",
|
| 62 |
+
"117": "Right Putamen",
|
| 63 |
+
"118": "Right Pallidum",
|
| 64 |
+
"119": "Right Hippocampus",
|
| 65 |
+
"120": "Right Amygdala",
|
| 66 |
+
"121": "Right Accumbens",
|
| 67 |
+
"201": "Amygdala_centromedial group",
|
| 68 |
+
"202": "Amygdala_laterobasal group",
|
| 69 |
+
"203": "Amygdala_superficial group",
|
| 70 |
+
"204": "Anterior intra-parietal sulcus hIP1",
|
| 71 |
+
"205": "Anterior intra-parietal sulcus hIP2",
|
| 72 |
+
"206": "Anterior intra-parietal sulcus hIP3",
|
| 73 |
+
"207": "Broca's area BA44",
|
| 74 |
+
"208": "Broca's area BA45",
|
| 75 |
+
"209": "Hippocampus cornu ammonis",
|
| 76 |
+
"210": "Hippocampus dentate gyrus",
|
| 77 |
+
"211": "Hippocampus entorhinal cortex",
|
| 78 |
+
"212": "Hippocampus hippocampal-amygdaloid transition area",
|
| 79 |
+
"213": "Hippocampus subiculum",
|
| 80 |
+
"214": "Inferior parietal lobule PF",
|
| 81 |
+
"215": "Inferior parietal lobule PFcm",
|
| 82 |
+
"216": "Inferior parietal lobule PFm",
|
| 83 |
+
"217": "Inferior parietal lobule PFop",
|
| 84 |
+
"218": "Inferior parietal lobule PFt",
|
| 85 |
+
"219": "Inferior parietal lobule PGp",
|
| 86 |
+
"220": "Inferior parietal lobule Pga",
|
| 87 |
+
"221": "Insula Id1",
|
| 88 |
+
"222": "Insula Ig1",
|
| 89 |
+
"223": "Insula Ig2",
|
| 90 |
+
"224": "Lateral geniculate body",
|
| 91 |
+
"225": "Mamillary body",
|
| 92 |
+
"226": "Medial geniculate body",
|
| 93 |
+
"227": "Premotor cortex BA6",
|
| 94 |
+
"228": "Primary auditory cortex TE1.0",
|
| 95 |
+
"229": "Primary auditory cortex TE1.1",
|
| 96 |
+
"230": "Primary auditory cortex TE1.2",
|
| 97 |
+
"231": "Primary motor cortex BA4a",
|
| 98 |
+
"232": "Primary motor cortex BA4p",
|
| 99 |
+
"233": "Primary somatosensory cortex BA1",
|
| 100 |
+
"234": "Primary somatosensory cortex BA2",
|
| 101 |
+
"235": "Primary somatosensory cortex BA3a",
|
| 102 |
+
"236": "Primary somatosensory cortex BA3b",
|
| 103 |
+
"237": "Secondary somatosensory cortex / Parietal operculum OP1",
|
| 104 |
+
"238": "Secondary somatosensory cortex / Parietal operculum OP2",
|
| 105 |
+
"239": "Secondary somatosensory cortex / Parietal operculum OP3",
|
| 106 |
+
"240": "Secondary somatosensory cortex / Parietal operculum OP4",
|
| 107 |
+
"241": "Superior parietal lobule 5Ci",
|
| 108 |
+
"242": "Superior parietal lobule 5L",
|
| 109 |
+
"243": "Superior parietal lobule 5M",
|
| 110 |
+
"244": "Superior parietal lobule 7A",
|
| 111 |
+
"245": "Superior parietal lobule 7M",
|
| 112 |
+
"246": "Superior parietal lobule 7P",
|
| 113 |
+
"247": "Superior parietal lobule 7PC",
|
| 114 |
+
"248": "Visual cortex V1 BA17",
|
| 115 |
+
"249": "Visual cortex V2 BA18",
|
| 116 |
+
"250": "Visual cortex V3V",
|
| 117 |
+
"251": "Visual cortex V4",
|
| 118 |
+
"252": "Visual cortex V5",
|
| 119 |
+
"253": "WM: Acoustic radiation",
|
| 120 |
+
"254": "WM: Callosal body",
|
| 121 |
+
"255": "WM: Cingulum",
|
| 122 |
+
"256": "WM: Corticospinal tract",
|
| 123 |
+
"257": "WM: Fornix",
|
| 124 |
+
"258": "WM: Inferior occipito-frontal fascicle",
|
| 125 |
+
"259": "WM: Optic radiation",
|
| 126 |
+
"260": "WM: Superior longitudinal fascicle",
|
| 127 |
+
"261": "WM: Superior occipito-frontal fascicle",
|
| 128 |
+
"262": "WM: Uncinate fascicle"
|
| 129 |
+
}
|
data/label_grid_cache.keys.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
["", "10159", "10292", "10306", "10307", "10308", "10311", "10313", "10327", "10329", "10330", "10331", "10332", "10333", "10334", "10335", "10336", "10337", "10338", "10339", "10342", "10343", "10344", "10346", "10349", "10361", "10383", "10384", "10389", "10390", "10465", "10466", "10467", "10595", "10596", "10597", "10598", "10599", "10600", "10602", "10648", "10649", "10650", "10651", "10653", "10654", "10655", "10656", "10657", "10658", "10659", "10660", "10661", "10662", "10669", "12114", "12115", "12116", "12117", "12118", "12119", "12121", "12122", "12123", "12124", "12125", "12126", "12127", "12132", "12133", "12136", "12137", "12138", "12140", "12142", "12143", "12144", "12145", "12146", "12147", "12148", "12149", "12150", "12151", "12152", "12153", "12154", "12155", "12156", "12157", "12158", "12159", "12160", "12162", "12163", "12164", "12165", "12166", "12170", "12171", "12173", "12174", "12176", "12177", "12178", "12179", "12181", "12195", "12291", "12292", "12305", "12330", "12354", "12369", "12384", "12390", "12405", "12416", "12535", "12538", "12805", "12826", "13229", "13230", "146034872", "146034876", "146034884", "146034888", "266441657"]
|
data/label_grid_cache.npz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ab71af20799b4cefb9986791337fcb609105b4653141cde9285d486cbfaa4791
|
| 3 |
+
size 101223
|
data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_entropy.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2dc61d9b05f915dad3e8ade5e5d9e5f1df27983cbeaf34c0232010ab7e0008d7
|
| 3 |
+
size 7812500
|
data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mean_xyz3.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:34525f6bf4a68ac06f87801aa180b57cfea10c8c8559ea67d23d66ea54865109
|
| 3 |
+
size 23437500
|
data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_meta.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"grid":125,"axis_min":[-69.5,-104.5625,-54.25],"axis_max":[70.0625,76.09375,81.8125],"files":{"mean_xyz3":"mdn_particles_rdcim_teacher_edge_hq_grid125_mean_xyz3.bin","pi":"mdn_particles_rdcim_teacher_edge_hq_grid125_pi_xyz6.bin","entropy":"mdn_particles_rdcim_teacher_edge_hq_grid125_entropy.bin","mu1_xyz3":"mdn_particles_rdcim_teacher_edge_hq_grid125_mu1_xyz3.bin","mu2_xyz3":"mdn_particles_rdcim_teacher_edge_hq_grid125_mu2_xyz3.bin","mu3_xyz3":"mdn_particles_rdcim_teacher_edge_hq_grid125_mu3_xyz3.bin","mu4_xyz3":"mdn_particles_rdcim_teacher_edge_hq_grid125_mu4_xyz3.bin","mu5_xyz3":"mdn_particles_rdcim_teacher_edge_hq_grid125_mu5_xyz3.bin","mu6_xyz3":"mdn_particles_rdcim_teacher_edge_hq_grid125_mu6_xyz3.bin"},"dtype":"float32","layout":"C-order (x,y,z,3) for *_xyz3; (x,y,z,K) for pi_xyzK; (x,y,z) for entropy","endianness":"little","mixture_mean_field":true,"model":{"quality":"hq","components":6},"mdn_source":"particles/rdcim_teacher_edge","bounds_mode":"full_brain","mode":"field","notes":"Trained on teacher segments (XYZ→ΔS) with optional rDCM conditioning and ROI embeddings","training_points_npy":"mdn_particles_rdcim_teacher_edge_hq_training_points.npy"}
|
data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu1_xyz3.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:44c45d1ccd4c2afb936d7a4622493b1d363df9664f6ce53cb26cd5bd892b79e8
|
| 3 |
+
size 23437500
|
data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu2_xyz3.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2f1b9505a5ae611528425d57b09387e35dd921c20377a3b232a4267133b19baf
|
| 3 |
+
size 23437500
|
data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu3_xyz3.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:53b0999649dfccf31f8726695c4d5a738009f408389a16954144be89d2a58c87
|
| 3 |
+
size 23437500
|
data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu4_xyz3.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:afd81e6a49823e2ddb42f8e4256f2bf58f268f5fbf83d95fbf0dc0f0eedef559
|
| 3 |
+
size 23437500
|
data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu5_xyz3.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:68c1f5eceb934435f58ea77c3b623abb114256cfc23affaa2dba1a202139a4aa
|
| 3 |
+
size 23437500
|
data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_mu6_xyz3.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9827f9f7a923994c84e1a44773fc2fe88dd91fd7905e718083dc752d24a31f4d
|
| 3 |
+
size 23437500
|
data/mdn/mdn_particles_rdcim_teacher_edge_hq_grid125_pi_xyz6.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ffb8a88b783e3a9aefeb8b7c14cd925769fcd3b4ef7d9ca029e4a8f71cd25265
|
| 3 |
+
size 46875000
|
data/mdn/mdn_particles_rdcim_teacher_edge_hq_training_points.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0f4ac819327e772ab01ed85b81150d2a28111df1ba4ebd9b2b3af200a011849b
|
| 3 |
+
size 14400128
|
data/meshes/.keep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
placeholder so code paths that check data/meshes exist keep working; actual OBJ meshes are in data/meshes_obj.
|
data/meshes_obj/10153.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:03245194c13e6081c41f81bfafb018f9aa819af57642f1729d6830db236912cb
|
| 3 |
+
size 9316827
|
data/meshes_obj/10154.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0026eafd072ba11e02f3f4eab7f768ebcf89f32f5a0144fa38d8774576a1baff
|
| 3 |
+
size 13523828
|
data/meshes_obj/10155.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:89e07d4dc266bd9c69404da5b1ce8a1ea23dd2a41560e30f350b3c49a23e4c65
|
| 3 |
+
size 13523828
|
data/meshes_obj/10156.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8dd907bcc3ed5f654395b9b2bec702f2f00355d6e7a9d208e18e01766f2782eb
|
| 3 |
+
size 12090622
|
data/meshes_obj/10157.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c1432ccc092ed2e7b1495e506a11179420fcfb0919519824bb35329ee8171002
|
| 3 |
+
size 3291242
|
data/meshes_obj/10158.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:be09e3ec39b5cacb24af0e9f7d8e69086fd35853899daea7729bf7f048bb5c6d
|
| 3 |
+
size 1992516
|
data/meshes_obj/10159.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aa7c48344b0af6f41f069450f319e9c360ef8b45fe8f36dded24cb45e7ba0aef
|
| 3 |
+
size 149484
|
data/meshes_obj/10292.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2c4b4771c184c4512cfadef75b152e0bcbe9d816a62102d7b93349c967d0d955
|
| 3 |
+
size 112083
|
data/meshes_obj/10306.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aef8de5471788784415bdd77b44e5c532cabc79f5fc3f3c4be37e9dd6c5222e8
|
| 3 |
+
size 112083
|
data/meshes_obj/10307.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:25a071fd3a955681fcd64f6d28fd917c921e9558b792f622649f93ebace11aa6
|
| 3 |
+
size 19205
|
data/meshes_obj/10308.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:83c6c01f3d02a6980403f340692930dbe47339c03784b4c6a41d6cc8bcf45dd2
|
| 3 |
+
size 28542
|
data/meshes_obj/10311.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6e94088b6a00b31e0077ecb75f28d21621f8b8f4ae1a033b76756713b66737fe
|
| 3 |
+
size 57711
|
data/meshes_obj/10313.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:22a69c74d7c982a8372ac63c49df436bf8bc0fc1edb401710c4939c85fd9866f
|
| 3 |
+
size 48263
|
data/meshes_obj/10327.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:61602d7e56749544c6773467d7b8af22aa7788de7640de8c0b18e6fc9630239a
|
| 3 |
+
size 48263
|
data/meshes_obj/10329.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f145efd0531e791a5f2059f89a4c649a83b12ffee4bd06648569cc36396bbd3c
|
| 3 |
+
size 17345
|
data/meshes_obj/10330.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8d96c21416298bac87134b1f41f762ac122854cd66a93f53bd97a8d833f7b99c
|
| 3 |
+
size 36168
|
data/meshes_obj/10331.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1e092fe926797259b4568d792ea0439daf36297ab5c9946a689892abe4c3b9aa
|
| 3 |
+
size 1853173
|
data/meshes_obj/10332.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c5030b54f80cad2316a5cc57db2649ac44e38d31d61677bda1710ebb29e1418f
|
| 3 |
+
size 1027691
|
data/meshes_obj/10333.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:df7e6a6ae97116b644f5090984b56ddcf5da574b5387c22bb8c554f9fff089ce
|
| 3 |
+
size 949235
|
data/meshes_obj/10334.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a16d8d4d1fcb2cc0ac4be610e5f200c6efd910b3de982492432f563c39f6c2b9
|
| 3 |
+
size 498360
|
data/meshes_obj/10335.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6f4c64f991bb22248463cf2415ab33c6cf89235bb85fa26425672958c6362708
|
| 3 |
+
size 225016
|
data/meshes_obj/10336.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:88b46eb320fe5c91aa1da562277d436caaabace45b0547a1b0d71e13cfa86d81
|
| 3 |
+
size 157144
|
data/meshes_obj/10337.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f60b0f68b013cfca7c50fc2afd63db26e64098d8e6476990d7cbc339613d68eb
|
| 3 |
+
size 162134
|
data/meshes_obj/10338.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0a913a6bd57ddc20209c2a7d0c873705c234eb0f116ae0dfbb7fa88a7074d017
|
| 3 |
+
size 374785
|
data/meshes_obj/10339.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0521f9f55fd1c10d2d9560fff80cb2385a6a9fab003bea1c524e4db1a154bcbb
|
| 3 |
+
size 74905
|
data/meshes_obj/10342.obj
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0864087632dbee936a66dd7d0ab48f208db7fc57118bdef6a1b37e0dbe38b162
|
| 3 |
+
size 142593
|