neurovolume 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.3
2
+ Name: neurovolume
3
+ Version: 0.0.0
4
+ Summary: Python library for Neurovolume. Build VDBs for scientific visualizations
5
+ Author: joachimbbp
6
+ Author-email: joachimbbp <104856283+joachimbbp@users.noreply.github.com>
7
+ Requires-Dist: numpy>=2.3.5
8
+ Requires-Dist: pytest>=9.0.1
9
+ Requires-Dist: ziglang>=0.15.1
10
+ Requires-Python: >=3.14
11
+ Description-Content-Type: text/markdown
12
+
13
+
14
+
15
+ Neurovolume is a volumetric scientific visualization pipeline and custom-built, scientific data-focused, VDB writer. The VDB writer is written in Zig with no external dependencies.
16
+
17
+ While this project focuses on neuroscience, it includes `ndarray` to `VDB` to support virtually any volumetric data pipeline.
18
+
19
+ This project is very much a **work in progress**. (see "Missing Features" below). As of now, I do not recommend regarding the images created by this software as scientifically accurate.
20
+
21
+ ![Render of a non-skull stripped MNI Template](readme_media/mni_template_render.png)
22
+
23
+
24
+ # 🏗️ Setup and Build
25
+ Neurovolume requires [Zig 0.15.1](https://ziglang.org/download/#release-0.15.1). It was developed using [Blender 4.3.2](https://www.blender.org/download/releases/4-3/) and [Python 3.11.13](https://www.python.org/downloads/release/python-31113/).
26
+
27
+ To compile, run `zig build` from the project repo root.
28
+
29
+ The following files need to be modified before building and running. Presently the most robust way to run this program is to include the full system paths for all of these. Feel free to look at the example paths to get an idea of the setup.
30
+
31
+ In `./src/config.zig.zon`:
32
+ If you wish to run tests, download the [T1](https://s3.amazonaws.com/openneuro.org/ds003548/sub-01/anat/sub-01_T1w.nii.gz?versionId=5ZTXVLawdWoVNWe5XVuV6DfF2BnmxzQz) and [BOLD](https://s3.amazonaws.com/openneuro.org/ds003548/sub-01/func/sub-01_task-emotionalfaces_run-1_bold.nii.gz?versionId=tq8Y3ktm31Aa8JB0991n9K0XNmHyRS1Q) images to `./media`
33
+ Unzip both of these `.gz` files before running the tests.
34
+ - Set `.nifti_t1` and `.bold` to point to the above test files in media.
35
+ - Set `.vdb_output_dir` and `.output` to your output folder (defaults to `./output`).
36
+
37
+ In `./neurovolume/src/neurovolume/core.py` (The Python library):
38
+ - Set `lib_path` to the build file of the zig library (defaults to `./zig-out/lib/libneurovolume.dylib`)
39
+ - Set `output_dir` to your output directory (same as the `./output` path mentioned above in the `.zon` file)
40
+
41
+ In `./python/__init__.py` (the Blender plugin):
42
+ - Set `user_set_output_path` to the output path (same output as always)
43
+ - Set `user_set_default_nifti` to the `sub-01_T1w.nii` file in your media folder. This is optional, but it's sometimes nice to have a default path here when testing.
44
+
45
+ In `./tests/test_core.py` (Optional testing file):
46
+ - Set `static_testfile` and the `fmri_testfile` to the T1 and BOLD testfiles you downloaded to `./media`
47
+
48
+ These hard-coded paths are not great and very much a hack. They were needed to cover some weird edge cases early in development and will be cleaned up later.
49
+
50
+ If you wish to find all of these, they should be tagged with `USERSET:`
51
+
52
+ # 🔌 Blender Plugin
53
+ Install the Blender plugin using one of the following methods:
54
+ - With [Jacques Lucke's vsCode extension for Blender](https://github.com/JacquesLucke/blender_vscode) (recommended)
55
+ - [Via the Add-ons section](https://docs.blender.org/manual/en/latest/editors/preferences/addons.html)
56
+ - Copy-pasting the add-on into Blender's [Text-editor](https://docs.blender.org/manual/en/latest/editors/text_editor.html) and then clicking the triangular "play" button to run.
57
+
58
+ Import a NIfTI files as a VDBs via the Neurovolume panel:
59
+
60
+ ![Panel](readme_media/panel.png)
61
+
62
+ Render and enjoy!
63
+
64
+ ![overlayed bold and T1 VDBs in blender](readme_media/overlayed_bold_and_t1.jpeg)
65
+
66
+ # 🐍 Python and ndArray usage
67
+ In the library located at `./python/neurovolume_lib.py` there is `ndarray_toVDB`. This function will build a static VDB out of a 3D ndarray and a transform. This allows users to build a VDB straight from their own domain-specific data-processing pipeline.
68
+
69
+ The following following neuroscience-specific example actually runs faster than the native `NIfTI1` implementation:
70
+
71
+ ````python
72
+ import nibabel as nib
73
+ import neurovolume_lib as nv
74
+ import numpy as np
75
+ from datetime import datetime
76
+
77
+ static_testfile = "./media/sub-01_T1w.nii"
78
+
79
+ def normalize_array(arr):
80
+ return (arr - np.min(arr)) / (np.max(arr) - np.min(arr))
81
+
82
+ img = nib.load(static_testfile)
83
+ data = np.array(img.get_fdata(), order='C', dtype=np.float64)
84
+ norm = normalize_array(data).astype(np.float64)
85
+
86
+ norm = np.transpose(norm, (1, 2, 0))
87
+ norm = np.ascontiguousarray(norm)
88
+
89
+ output = "./output/from_nib.vdb"
90
+ nv.ndarray_to_VDB(norm, output, img.affine)
91
+ ````
92
+ Note that all data must be normalized from 0.0-1.0 before being written to a VDB.
93
+
94
+ # ☁️ Why VDB?
95
+ VDBs are a highly performant, art-directable, volumetric data structure that supports animations. Our volume-based approach aims to provide easy access to the original density data throughout the visualization and analysis pipeline. Unlike the [openVDB repo](https://www.openvdb.org/), our smaller version is much more readable and does not need to be run in a docker container.
96
+
97
+
98
+ # 🛠️ Missing Features
99
+ While a comprehensive road-map will be published soon, there are a few important considerations to take into account now.
100
+ - Presently the VDB writer isn't sparse nor does it support multiple grids. Tiles and multiple grids are in development.
101
+ - Neurovolume currently only natively supports `NIfTI1` files (and only some variants). Full coverage and `NIfTI2` will be supported soon. Until then, you can use an `ndarray` as an intermediary (see Python Usage).
102
+ - Frame interpolation (present in the original Go prototype) is currently under development on this branch. If you wish to access the old Go code, check out [the archive](https://github.com/joachimbbp/neurovolume_archive)
103
+
104
+
105
+ # 🧠 Dataset Citation
106
+ This software was tested using the following datasets.
107
+
108
+ Isaac David and Victor Olalde-Mathieu and Ana Y. Martínez and Lluviana Rodríguez-Vidal and Fernando A. Barrios (2021). Emotion Category and Face Perception Task Optimized for Multivariate Pattern Analysis. OpenNeuro. [Dataset] doi: 10.18112/openneuro.ds003548.v1.0.1
109
+
110
+ [OpenNeuro Study Link](https://openneuro.org/datasets/ds003548/versions/1.0.1)
111
+
112
+ [Direct Download Link for T1 Anat test file](https://s3.amazonaws.com/openneuro.org/ds003548/sub-01/anat/sub-01_T1w.nii.gz?versionId=5ZTXVLawdWoVNWe5XVuV6DfF2BnmxzQz)
113
+
114
+ [Direct Download Link for BOLD test file](https://s3.amazonaws.com/openneuro.org/ds003548/sub-01/func/sub-01_task-emotionalfaces_run-1_bold.nii.gz?versionId=tq8Y3ktm31Aa8JB0991n9K0XNmHyRS1Q)
115
+
116
+ The MNI Template can be found [Here](https://github.com/Angeluz-07/MRI-preprocessing-techniques/tree/main/assets/templates)
@@ -0,0 +1,104 @@
1
+
2
+
3
+ Neurovolume is a volumetric scientific visualization pipeline and custom-built, scientific data-focused, VDB writer. The VDB writer is written in Zig with no external dependencies.
4
+
5
+ While this project focuses on neuroscience, it includes `ndarray` to `VDB` to support virtually any volumetric data pipeline.
6
+
7
+ This project is very much a **work in progress**. (see "Missing Features" below). As of now, I do not recommend regarding the images created by this software as scientifically accurate.
8
+
9
+ ![Render of a non-skull stripped MNI Template](readme_media/mni_template_render.png)
10
+
11
+
12
+ # 🏗️ Setup and Build
13
+ Neurovolume requires [Zig 0.15.1](https://ziglang.org/download/#release-0.15.1). It was developed using [Blender 4.3.2](https://www.blender.org/download/releases/4-3/) and [Python 3.11.13](https://www.python.org/downloads/release/python-31113/).
14
+
15
+ To compile, run `zig build` from the project repo root.
16
+
17
+ The following files need to be modified before building and running. Presently the most robust way to run this program is to include the full system paths for all of these. Feel free to look at the example paths to get an idea of the setup.
18
+
19
+ In `./src/config.zig.zon`:
20
+ If you wish to run tests, download the [T1](https://s3.amazonaws.com/openneuro.org/ds003548/sub-01/anat/sub-01_T1w.nii.gz?versionId=5ZTXVLawdWoVNWe5XVuV6DfF2BnmxzQz) and [BOLD](https://s3.amazonaws.com/openneuro.org/ds003548/sub-01/func/sub-01_task-emotionalfaces_run-1_bold.nii.gz?versionId=tq8Y3ktm31Aa8JB0991n9K0XNmHyRS1Q) images to `./media`
21
+ Unzip both of these `.gz` files before running the tests.
22
+ - Set `.nifti_t1` and `.bold` to point to the above test files in media.
23
+ - Set `.vdb_output_dir` and `.output` to your output folder (defaults to `./output`).
24
+
25
+ In `./neurovolume/src/neurovolume/core.py` (The Python library):
26
+ - Set `lib_path` to the build file of the zig library (defaults to `./zig-out/lib/libneurovolume.dylib`)
27
+ - Set `output_dir` to your output directory (same as the `./output` path mentioned above in the `.zon` file)
28
+
29
+ In `./python/__init__.py` (the Blender plugin):
30
+ - Set `user_set_output_path` to the output path (same output as always)
31
+ - Set `user_set_default_nifti` to the `sub-01_T1w.nii` file in your media folder. This is optional, but it's sometimes nice to have a default path here when testing.
32
+
33
+ In `./tests/test_core.py` (Optional testing file):
34
+ - Set `static_testfile` and the `fmri_testfile` to the T1 and BOLD testfiles you downloaded to `./media`
35
+
36
+ These hard-coded paths are not great and very much a hack. They were needed to cover some weird edge cases early in development and will be cleaned up later.
37
+
38
+ If you wish to find all of these, they should be tagged with `USERSET:`
39
+
40
+ # 🔌 Blender Plugin
41
+ Install the Blender plugin using one of the following methods:
42
+ - With [Jacques Lucke's vsCode extension for Blender](https://github.com/JacquesLucke/blender_vscode) (recommended)
43
+ - [Via the Add-ons section](https://docs.blender.org/manual/en/latest/editors/preferences/addons.html)
44
+ - Copy-pasting the add-on into Blender's [Text-editor](https://docs.blender.org/manual/en/latest/editors/text_editor.html) and then clicking the triangular "play" button to run.
45
+
46
+ Import a NIfTI files as a VDBs via the Neurovolume panel:
47
+
48
+ ![Panel](readme_media/panel.png)
49
+
50
+ Render and enjoy!
51
+
52
+ ![overlayed bold and T1 VDBs in blender](readme_media/overlayed_bold_and_t1.jpeg)
53
+
54
+ # 🐍 Python and ndArray usage
55
+ In the library located at `./python/neurovolume_lib.py` there is `ndarray_toVDB`. This function will build a static VDB out of a 3D ndarray and a transform. This allows users to build a VDB straight from their own domain-specific data-processing pipeline.
56
+
57
+ The following following neuroscience-specific example actually runs faster than the native `NIfTI1` implementation:
58
+
59
+ ````python
60
+ import nibabel as nib
61
+ import neurovolume_lib as nv
62
+ import numpy as np
63
+ from datetime import datetime
64
+
65
+ static_testfile = "./media/sub-01_T1w.nii"
66
+
67
+ def normalize_array(arr):
68
+ return (arr - np.min(arr)) / (np.max(arr) - np.min(arr))
69
+
70
+ img = nib.load(static_testfile)
71
+ data = np.array(img.get_fdata(), order='C', dtype=np.float64)
72
+ norm = normalize_array(data).astype(np.float64)
73
+
74
+ norm = np.transpose(norm, (1, 2, 0))
75
+ norm = np.ascontiguousarray(norm)
76
+
77
+ output = "./output/from_nib.vdb"
78
+ nv.ndarray_to_VDB(norm, output, img.affine)
79
+ ````
80
+ Note that all data must be normalized from 0.0-1.0 before being written to a VDB.
81
+
82
+ # ☁️ Why VDB?
83
+ VDBs are a highly performant, art-directable, volumetric data structure that supports animations. Our volume-based approach aims to provide easy access to the original density data throughout the visualization and analysis pipeline. Unlike the [openVDB repo](https://www.openvdb.org/), our smaller version is much more readable and does not need to be run in a docker container.
84
+
85
+
86
+ # 🛠️ Missing Features
87
+ While a comprehensive road-map will be published soon, there are a few important considerations to take into account now.
88
+ - Presently the VDB writer isn't sparse nor does it support multiple grids. Tiles and multiple grids are in development.
89
+ - Neurovolume currently only natively supports `NIfTI1` files (and only some variants). Full coverage and `NIfTI2` will be supported soon. Until then, you can use an `ndarray` as an intermediary (see Python Usage).
90
+ - Frame interpolation (present in the original Go prototype) is currently under development on this branch. If you wish to access the old Go code, check out [the archive](https://github.com/joachimbbp/neurovolume_archive)
91
+
92
+
93
+ # 🧠 Dataset Citation
94
+ This software was tested using the following datasets.
95
+
96
+ Isaac David and Victor Olalde-Mathieu and Ana Y. Martínez and Lluviana Rodríguez-Vidal and Fernando A. Barrios (2021). Emotion Category and Face Perception Task Optimized for Multivariate Pattern Analysis. OpenNeuro. [Dataset] doi: 10.18112/openneuro.ds003548.v1.0.1
97
+
98
+ [OpenNeuro Study Link](https://openneuro.org/datasets/ds003548/versions/1.0.1)
99
+
100
+ [Direct Download Link for T1 Anat test file](https://s3.amazonaws.com/openneuro.org/ds003548/sub-01/anat/sub-01_T1w.nii.gz?versionId=5ZTXVLawdWoVNWe5XVuV6DfF2BnmxzQz)
101
+
102
+ [Direct Download Link for BOLD test file](https://s3.amazonaws.com/openneuro.org/ds003548/sub-01/func/sub-01_task-emotionalfaces_run-1_bold.nii.gz?versionId=tq8Y3ktm31Aa8JB0991n9K0XNmHyRS1Q)
103
+
104
+ The MNI Template can be found [Here](https://github.com/Angeluz-07/MRI-preprocessing-techniques/tree/main/assets/templates)
@@ -0,0 +1,18 @@
1
+ [project]
2
+ name = "neurovolume"
3
+ version = "0.0.0"
4
+ description = "Python library for Neurovolume. Build VDBs for scientific visualizations"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "joachimbbp", email = "104856283+joachimbbp@users.noreply.github.com" }
8
+ ]
9
+ requires-python = ">=3.14"
10
+ dependencies = [
11
+ "numpy>=2.3.5",
12
+ "pytest>=9.0.1",
13
+ "ziglang>=0.15.1",
14
+ ]
15
+
16
+ [build-system]
17
+ requires = ["uv_build>=0.9.8,<0.10.0"]
18
+ build-backend = "uv_build"
@@ -0,0 +1 @@
1
+ from neurovolume.core import *
@@ -0,0 +1,188 @@
1
+ import ctypes as c
2
+ import numpy as np # DEPENDENCY:
3
+ import sys
4
+ import ctypes
5
+ from pathlib import Path
6
+
7
+
8
+ # LLM:
9
+ def get_library_name():
10
+ if sys.platform == "darwin":
11
+ return "libneurovolume.dylib"
12
+ elif sys.platform == "win32":
13
+ return "libneurovolume.dll"
14
+ else: # Linux and others
15
+ return "libneurovolume.so"
16
+
17
+
18
+ lib_path = Path(__file__).parent / "_native" / get_library_name()
19
+ lib = ctypes.CDLL(str(lib_path))
20
+ # LLMEND:
21
+
22
+ # _: Main code:
23
+
24
+ nvol = c.cdll.LoadLibrary(lib_path) # Neurovolume library
25
+
26
+
27
+ def b(string):
28
+ """
29
+ Returns the utf-u8 encoded bytes literal of the string
30
+ Equivalent to 'b"inputstring"'
31
+ """
32
+ return string.encode("utf-8")
33
+
34
+
35
+ def get_basename(path):
36
+ hierarchy = path.split("/")
37
+ return hierarchy[-1].split(".")[0]
38
+
39
+
40
+ def get_folder(path):
41
+ """Returns the folder in which the path points to"""
42
+ hiearchy = path.split("/")
43
+ return "/".join(hiearchy[:-1])
44
+
45
+
46
+ def ndarray_to_VDB(arr: np.ndarray, save_path: str, transform: np.ndarray = None):
47
+ # LOTS OF LLM: here
48
+ if transform is None:
49
+ transform = np.eye(4, dtype=np.float64)
50
+ affine_flat = transform.flatten().astype(np.float64)
51
+
52
+ arr = np.ascontiguousarray(arr, dtype=np.float32)
53
+ dims = np.array(arr.shape, dtype=np.uint64)
54
+ nvol.ndArrayToVDB_c.argtypes = [
55
+ np.ctypeslib.ndpointer(dtype=np.float32, flags="C_CONTIGUOUS"),
56
+ c.POINTER(c.c_size_t),
57
+ np.ctypeslib.ndpointer(dtype=np.float64, flags="C_CONTIGUOUS"),
58
+ c.c_char_p,
59
+ ]
60
+ nvol.ndArrayToVDB_c.restype = c.c_size_t
61
+ res = nvol.ndArrayToVDB_c(
62
+ arr,
63
+ dims.ctypes.data_as(c.POINTER(c.c_size_t)),
64
+ affine_flat,
65
+ save_path.encode("utf-8"),
66
+ )
67
+ if res == 0:
68
+ print("error!")
69
+
70
+
71
+ def nifti1_to_VDB(filepath: str, normalize: bool) -> str:
72
+ BUF_SIZE = 4096 # somewhat arbitrary, should be big enough for file name
73
+ save_location = c.create_string_buffer(BUF_SIZE)
74
+ nvol.nifti1ToVDB_c.argtypes = [
75
+ c.c_char_p,
76
+ c.c_char_p,
77
+ c.c_bool,
78
+ c.POINTER(c.c_char),
79
+ c.c_size_t,
80
+ ]
81
+ nvol.nifti1ToVDB_c.restype = c.c_size_t
82
+ nvol.nifti1ToVDB_c(b(filepath), b(output_dir), normalize, save_location, BUF_SIZE)
83
+
84
+ return save_location.value.decode()
85
+
86
+
87
+ # FIX: almost all of these `case "NIfTI1"` switches are redundant,
88
+ # the same logic is following in the zig code
89
+
90
+
91
+ def num_frames(filepath: str, filetype: str) -> int:
92
+ match filetype:
93
+ case "NIfTI1":
94
+ nvol.numFrames_c.argtypes = [
95
+ c.c_char_p,
96
+ c.c_char_p,
97
+ ]
98
+ nvol.numFrames_c.restype = c.c_size_t
99
+ num_frames = nvol.numFrames_c(b(filepath), b(filetype))
100
+ return num_frames
101
+ case _:
102
+ err_msg = f"{filetype} is unsupported for num_frames access"
103
+ raise ValueError(err_msg)
104
+
105
+
106
+ def pixdim(filepath: str, filetype: str, dim: int) -> float:
107
+ match filetype:
108
+ case "NIfTI1":
109
+ nvol.pixdim_c.argtypes = [c.c_char_p, c.c_char_p, c.c_int]
110
+ nvol.pixdim_c.restype = c.c_float
111
+ pixdim = nvol.pixdim_c(b(filepath), b(filetype), dim)
112
+ return pixdim
113
+ case _:
114
+ err_msg = f"{filetype} is unsupported for pixdim access"
115
+ raise ValueError(err_msg)
116
+
117
+
118
+ # WARN: never tested or used and test file just puts this as 0 for some reason
119
+ def slice_duration(filepath: str, filetype: str) -> int:
120
+ match filetype:
121
+ case "NIfTI1":
122
+ nvol.sliceDuration_c.argtypes = [
123
+ c.c_char_p,
124
+ c.c_char_p,
125
+ ]
126
+ nvol.sliceDuration_c.restype = c.c_size_t
127
+ slice_duration = nvol.sliceDuration_c(b(filepath), b(filetype))
128
+ return slice_duration
129
+ case _:
130
+ err_msg = f"{filetype} is unsupported for slice_duration access"
131
+ raise ValueError(err_msg)
132
+
133
+
134
+ def unit(filepath: str, filetype: str, unit_kind: str) -> str:
135
+ BUF_SIZE = 64 # generously padded, tbh
136
+ unit_name = c.create_string_buffer(BUF_SIZE)
137
+ nvol.unit_c.argtypes = [
138
+ c.c_char_p,
139
+ c.c_char_p,
140
+ c.c_char_p,
141
+ c.POINTER(c.c_char),
142
+ c.c_size_t,
143
+ ]
144
+ nvol.unit_c.restype = c.c_size_t
145
+ nvol.unit_c(b(filepath), b(filetype), b(unit_kind), unit_name, BUF_SIZE)
146
+ return unit_name.value.decode()
147
+
148
+
149
+ def source_fps(filepath: str, filetype: str) -> int:
150
+ match filetype:
151
+ case "NIfTI1":
152
+ if num_frames(filepath, filetype) == 1:
153
+ # staic file, frames per second is zero
154
+ return 0
155
+
156
+ time_unit = unit(filepath, filetype, "time")
157
+ time_value = pixdim(filepath, filetype, 4)
158
+ match time_unit:
159
+ # time_in_seconds / time_value
160
+ case "Seconds":
161
+ return 1 / time_value
162
+ case "Miliseconds":
163
+ return 0.001 / time_value
164
+ case "Microseconds":
165
+ return 0.000001 / time_value
166
+
167
+ # These will probably be different
168
+ case "Hertz":
169
+ raise ValueError("hz not implemented yet")
170
+ case "Parts_per_million":
171
+ raise ValueError("ppm not implemented yet")
172
+ case "Radians_per_second":
173
+ raise ValueError("rpm not implemented yet")
174
+ case _:
175
+ raise ValueError(unit, "is an unknown unit, not implemented yet")
176
+
177
+ case _:
178
+ err_msg = f"{filetype} is unsupported for num_frames access"
179
+ raise ValueError(err_msg)
180
+
181
+
182
+ #
183
+ # def real_size():
184
+ # # TODO: will need to get measurement units and as well as the pixdim
185
+ #
186
+ # # TODO: def runtime
187
+ # # which will include a lot fo the stuff in fps as well as temporal_offset
188
+
File without changes