cardio 2026.1.3__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.
- cardio-2026.1.3/LICENSE +15 -0
- cardio-2026.1.3/PKG-INFO +103 -0
- cardio-2026.1.3/README.md +47 -0
- cardio-2026.1.3/pyproject.toml +82 -0
- cardio-2026.1.3/src/cardio/__init__.py +29 -0
- cardio-2026.1.3/src/cardio/app.py +54 -0
- cardio-2026.1.3/src/cardio/assets/bone.toml +42 -0
- cardio-2026.1.3/src/cardio/assets/vascular_closed.toml +78 -0
- cardio-2026.1.3/src/cardio/assets/vascular_open.toml +54 -0
- cardio-2026.1.3/src/cardio/assets/xray.toml +42 -0
- cardio-2026.1.3/src/cardio/blend_transfer_functions.py +81 -0
- cardio-2026.1.3/src/cardio/color_transfer_function.py +27 -0
- cardio-2026.1.3/src/cardio/logic.py +1090 -0
- cardio-2026.1.3/src/cardio/mesh.py +255 -0
- cardio-2026.1.3/src/cardio/object.py +179 -0
- cardio-2026.1.3/src/cardio/orientation.py +218 -0
- cardio-2026.1.3/src/cardio/piecewise_function.py +27 -0
- cardio-2026.1.3/src/cardio/property_config.py +53 -0
- cardio-2026.1.3/src/cardio/rotation.py +152 -0
- cardio-2026.1.3/src/cardio/scene.py +414 -0
- cardio-2026.1.3/src/cardio/screenshot.py +18 -0
- cardio-2026.1.3/src/cardio/segmentation.py +177 -0
- cardio-2026.1.3/src/cardio/transfer_function_pair.py +23 -0
- cardio-2026.1.3/src/cardio/types.py +16 -0
- cardio-2026.1.3/src/cardio/ui.py +1089 -0
- cardio-2026.1.3/src/cardio/utils.py +51 -0
- cardio-2026.1.3/src/cardio/volume.py +413 -0
- cardio-2026.1.3/src/cardio/volume_property.py +44 -0
- cardio-2026.1.3/src/cardio/volume_property_presets.py +50 -0
- cardio-2026.1.3/src/cardio/window_level.py +33 -0
cardio-2026.1.3/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Apache Software License 2.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, Davis Marc Vigneault
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
cardio-2026.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: cardio
|
|
3
|
+
Version: 2026.1.3
|
|
4
|
+
Summary: A simple web-based viewer for 3D and 4D ('cine') medical imaging data.
|
|
5
|
+
Keywords: Medical,Imaging,3D,4D,Visualization
|
|
6
|
+
Author: Davis Marc Vigneault
|
|
7
|
+
Author-email: Davis Marc Vigneault <davis.vigneault@gmail.com>
|
|
8
|
+
License: Apache Software License 2.0
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2023, Davis Marc Vigneault
|
|
11
|
+
|
|
12
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
you may not use this file except in compliance with the License.
|
|
14
|
+
You may obtain a copy of the License at
|
|
15
|
+
|
|
16
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
|
|
18
|
+
Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
See the License for the specific language governing permissions and
|
|
22
|
+
limitations under the License.
|
|
23
|
+
Classifier: Development Status :: 3 - Alpha
|
|
24
|
+
Classifier: Environment :: Web Environment
|
|
25
|
+
Classifier: Intended Audience :: Healthcare Industry
|
|
26
|
+
Classifier: Intended Audience :: Science/Research
|
|
27
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
28
|
+
Classifier: Natural Language :: English
|
|
29
|
+
Classifier: Operating System :: OS Independent
|
|
30
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
31
|
+
Classifier: Programming Language :: JavaScript
|
|
32
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
33
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
34
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
35
|
+
Requires-Dist: trame-vuetify>=3.0.2
|
|
36
|
+
Requires-Dist: trame-vtk>=2.9.1
|
|
37
|
+
Requires-Dist: itk>=5.4.4.post1
|
|
38
|
+
Requires-Dist: vtk>=9.5.0
|
|
39
|
+
Requires-Dist: tomlkit>=0.13.3
|
|
40
|
+
Requires-Dist: numpy>=2.2.6
|
|
41
|
+
Requires-Dist: pydantic>=2.11.7
|
|
42
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
43
|
+
Requires-Dist: trame>=3.12.0
|
|
44
|
+
Requires-Dist: trame-client>=3.10.1
|
|
45
|
+
Requires-Dist: ruff>=0.12.10 ; extra == 'dev'
|
|
46
|
+
Requires-Dist: isort>=6.0.1 ; extra == 'dev'
|
|
47
|
+
Requires-Dist: bumpver>=2025.1131 ; extra == 'dev'
|
|
48
|
+
Requires-Dist: pytest>=8.4.1 ; extra == 'dev'
|
|
49
|
+
Requires-Dist: coverage>=7.10.4 ; extra == 'dev'
|
|
50
|
+
Maintainer: Davis Marc Vigneault
|
|
51
|
+
Maintainer-email: Davis Marc Vigneault <davis.vigneault@gmail.com>
|
|
52
|
+
Requires-Python: >=3.10
|
|
53
|
+
Project-URL: repository, https://github.com/sudomakeinstall/cardio
|
|
54
|
+
Provides-Extra: dev
|
|
55
|
+
Description-Content-Type: text/markdown
|
|
56
|
+
|
|
57
|
+
# cardio
|
|
58
|
+
|
|
59
|
+
`cardio` is a simple web-based viewer for 3D and 4D ('cine') medical imaging data,
|
|
60
|
+
built primarily on [trame](https://github.com/kitware/trame),
|
|
61
|
+
[vtk](https://github.com/kitware/vtk), and
|
|
62
|
+
[itk](https://github.com/insightsoftwareconsortium/itk). `cardio` can render sequences
|
|
63
|
+
of mesh files (e.g., `*.obj` files), segmentation files (e.g., `*.nii.gz` files with
|
|
64
|
+
discrete labels) and volume renderings of grayscale images (e.g., `*.nii.gz` files with
|
|
65
|
+
continuous values). `cardio` is launched from the commandline and may be configured via
|
|
66
|
+
commandline arguments, a static TOML configuration file, or a combination of the two.
|
|
67
|
+
|
|
68
|
+
## Quickstart
|
|
69
|
+
|
|
70
|
+
### Installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
$ cd /path/to/your/project
|
|
74
|
+
$ uv init
|
|
75
|
+
$ uv add cardio
|
|
76
|
+
$ . ./.venv/bin/activate
|
|
77
|
+
(project) cardio --version
|
|
78
|
+
cardio 2026.1.3
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Developing
|
|
82
|
+
|
|
83
|
+
Ensuring you have all required dependencies:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
$ uv sync --all-extras
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Pre-commit checklist:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
$ isort .
|
|
93
|
+
$ ruff format
|
|
94
|
+
$ pytest -v
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Uploading:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
$ bumpver update
|
|
101
|
+
$ uv build --no-sources
|
|
102
|
+
$ uv publish --token <pypi_api_key>
|
|
103
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# cardio
|
|
2
|
+
|
|
3
|
+
`cardio` is a simple web-based viewer for 3D and 4D ('cine') medical imaging data,
|
|
4
|
+
built primarily on [trame](https://github.com/kitware/trame),
|
|
5
|
+
[vtk](https://github.com/kitware/vtk), and
|
|
6
|
+
[itk](https://github.com/insightsoftwareconsortium/itk). `cardio` can render sequences
|
|
7
|
+
of mesh files (e.g., `*.obj` files), segmentation files (e.g., `*.nii.gz` files with
|
|
8
|
+
discrete labels) and volume renderings of grayscale images (e.g., `*.nii.gz` files with
|
|
9
|
+
continuous values). `cardio` is launched from the commandline and may be configured via
|
|
10
|
+
commandline arguments, a static TOML configuration file, or a combination of the two.
|
|
11
|
+
|
|
12
|
+
## Quickstart
|
|
13
|
+
|
|
14
|
+
### Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
$ cd /path/to/your/project
|
|
18
|
+
$ uv init
|
|
19
|
+
$ uv add cardio
|
|
20
|
+
$ . ./.venv/bin/activate
|
|
21
|
+
(project) cardio --version
|
|
22
|
+
cardio 2026.1.3
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Developing
|
|
26
|
+
|
|
27
|
+
Ensuring you have all required dependencies:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
$ uv sync --all-extras
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Pre-commit checklist:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
$ isort .
|
|
37
|
+
$ ruff format
|
|
38
|
+
$ pytest -v
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Uploading:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
$ bumpver update
|
|
45
|
+
$ uv build --no-sources
|
|
46
|
+
$ uv publish --token <pypi_api_key>
|
|
47
|
+
```
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.8.13,<0.9.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cardio"
|
|
7
|
+
version = "2026.1.3"
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Davis Marc Vigneault", email = "davis.vigneault@gmail.com"}
|
|
10
|
+
]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Environment :: Web Environment",
|
|
14
|
+
"Intended Audience :: Healthcare Industry",
|
|
15
|
+
"Intended Audience :: Science/Research",
|
|
16
|
+
"License :: OSI Approved :: Apache Software License",
|
|
17
|
+
"Natural Language :: English",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: JavaScript",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Image Processing",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Medical Science Apps.",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"trame-vuetify>=3.0.2",
|
|
27
|
+
"trame-vtk>=2.9.1",
|
|
28
|
+
"itk>=5.4.4.post1",
|
|
29
|
+
"vtk>=9.5.0",
|
|
30
|
+
"tomlkit>=0.13.3",
|
|
31
|
+
"numpy>=2.2.6",
|
|
32
|
+
"pydantic>=2.11.7",
|
|
33
|
+
"pydantic-settings>=2.0.0",
|
|
34
|
+
"trame>=3.12.0",
|
|
35
|
+
"trame-client>=3.10.1",
|
|
36
|
+
]
|
|
37
|
+
description = "A simple web-based viewer for 3D and 4D ('cine') medical imaging data."
|
|
38
|
+
keywords = ["Medical", "Imaging", "3D", "4D", "Visualization"]
|
|
39
|
+
license = {file = "LICENSE"}
|
|
40
|
+
maintainers = [
|
|
41
|
+
{name = "Davis Marc Vigneault", email = "davis.vigneault@gmail.com"}
|
|
42
|
+
]
|
|
43
|
+
readme = "README.md"
|
|
44
|
+
requires-python = ">=3.10"
|
|
45
|
+
|
|
46
|
+
[project.optional-dependencies]
|
|
47
|
+
dev = [
|
|
48
|
+
"ruff>=0.12.10",
|
|
49
|
+
"isort>=6.0.1",
|
|
50
|
+
"bumpver>=2025.1131",
|
|
51
|
+
"pytest>=8.4.1",
|
|
52
|
+
"coverage>=7.10.4",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[project.gui-scripts]
|
|
56
|
+
cardio = "cardio.app:main"
|
|
57
|
+
|
|
58
|
+
[project.urls]
|
|
59
|
+
repository = "https://github.com/sudomakeinstall/cardio"
|
|
60
|
+
|
|
61
|
+
[tool.isort]
|
|
62
|
+
profile = "black"
|
|
63
|
+
|
|
64
|
+
[tool.bumpver]
|
|
65
|
+
current_version = "2026.1.3"
|
|
66
|
+
version_pattern = "YYYY.MM.INC0"
|
|
67
|
+
commit_message = "ENH: Bump version from {old_version} => {new_version}"
|
|
68
|
+
commit = true
|
|
69
|
+
tag = true
|
|
70
|
+
push = false
|
|
71
|
+
|
|
72
|
+
[tool.bumpver.file_patterns]
|
|
73
|
+
"pyproject.toml" = [
|
|
74
|
+
'current_version = "{version}"',
|
|
75
|
+
'version = "{version}"',
|
|
76
|
+
]
|
|
77
|
+
"src/cardio/__init__.py" = [
|
|
78
|
+
'__version__ = "{version}"',
|
|
79
|
+
]
|
|
80
|
+
"README.md" = [
|
|
81
|
+
'cardio {version}',
|
|
82
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from . import window_level
|
|
2
|
+
from .logic import Logic
|
|
3
|
+
from .mesh import Mesh
|
|
4
|
+
from .object import Object
|
|
5
|
+
from .scene import Scene
|
|
6
|
+
from .screenshot import Screenshot
|
|
7
|
+
from .segmentation import Segmentation
|
|
8
|
+
from .ui import UI
|
|
9
|
+
from .volume import Volume
|
|
10
|
+
from .volume_property_presets import (
|
|
11
|
+
list_volume_property_presets,
|
|
12
|
+
load_volume_property_preset,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Object",
|
|
17
|
+
"Mesh",
|
|
18
|
+
"Volume",
|
|
19
|
+
"Segmentation",
|
|
20
|
+
"Scene",
|
|
21
|
+
"Screenshot",
|
|
22
|
+
"UI",
|
|
23
|
+
"Logic",
|
|
24
|
+
"load_volume_property_preset",
|
|
25
|
+
"list_volume_property_presets",
|
|
26
|
+
"window_level",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
__version__ = "2026.1.3"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import pydantic_settings as ps
|
|
4
|
+
import trame as tm
|
|
5
|
+
import trame.decorators
|
|
6
|
+
|
|
7
|
+
from . import __version__
|
|
8
|
+
from .logic import Logic
|
|
9
|
+
from .scene import Scene
|
|
10
|
+
from .ui import UI
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@tm.decorators.TrameApp()
|
|
14
|
+
class CardioApp:
|
|
15
|
+
def __init__(self, server=None):
|
|
16
|
+
self.server = tm.app.get_server(server, client_type="vue3")
|
|
17
|
+
|
|
18
|
+
self.server.cli.add_argument(
|
|
19
|
+
"--config", help="TOML configuration file.", dest="cfg_file", required=False
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
self.server.cli.add_argument(
|
|
23
|
+
"--version", action="version", version=f"cardio {__version__}"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
cli_settings = ps.CliSettingsSource(
|
|
27
|
+
Scene, root_parser=self.server.cli, cli_parse_args=True
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
args, unknown = self.server.cli.parse_known_args()
|
|
31
|
+
config_file = getattr(args, "cfg_file", None)
|
|
32
|
+
|
|
33
|
+
Scene._cli_source = cli_settings
|
|
34
|
+
Scene._config_file = config_file
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
scene = Scene()
|
|
38
|
+
finally:
|
|
39
|
+
if hasattr(Scene, "_cli_source"):
|
|
40
|
+
delattr(Scene, "_cli_source")
|
|
41
|
+
if hasattr(Scene, "_config_file"):
|
|
42
|
+
delattr(Scene, "_config_file")
|
|
43
|
+
|
|
44
|
+
Logic(self.server, scene)
|
|
45
|
+
UI(self.server, scene)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def main():
|
|
49
|
+
app = CardioApp()
|
|
50
|
+
app.server.start(open_browser=False)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
main()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name = "Bone"
|
|
2
|
+
description = "Bone"
|
|
3
|
+
|
|
4
|
+
ambient = 1.0
|
|
5
|
+
diffuse = 1.0
|
|
6
|
+
specular = 0.0
|
|
7
|
+
|
|
8
|
+
[[transfer_functions]]
|
|
9
|
+
|
|
10
|
+
[transfer_functions.opacity]
|
|
11
|
+
[[transfer_functions.opacity.points]]
|
|
12
|
+
x = -1024.0
|
|
13
|
+
y = 0.0
|
|
14
|
+
|
|
15
|
+
[[transfer_functions.opacity.points]]
|
|
16
|
+
x = 54.0
|
|
17
|
+
y = 0.0
|
|
18
|
+
|
|
19
|
+
[[transfer_functions.opacity.points]]
|
|
20
|
+
x = 536.0
|
|
21
|
+
y = 0.1
|
|
22
|
+
|
|
23
|
+
[[transfer_functions.opacity.points]]
|
|
24
|
+
x = 3071.0
|
|
25
|
+
y = 0.1
|
|
26
|
+
|
|
27
|
+
[transfer_functions.color]
|
|
28
|
+
[[transfer_functions.color.points]]
|
|
29
|
+
x = -1024.0
|
|
30
|
+
color = [0.0, 0.0, 0.0]
|
|
31
|
+
|
|
32
|
+
[[transfer_functions.color.points]]
|
|
33
|
+
x = 54.0
|
|
34
|
+
color = [0.0, 0.0, 0.0]
|
|
35
|
+
|
|
36
|
+
[[transfer_functions.color.points]]
|
|
37
|
+
x = 536.0
|
|
38
|
+
color = [1.0, 1.0, 1.0]
|
|
39
|
+
|
|
40
|
+
[[transfer_functions.color.points]]
|
|
41
|
+
x = 3071.0
|
|
42
|
+
color = [1.0, 1.0, 1.0]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
name = "Vascular_Closed"
|
|
2
|
+
description = "Vascular (Closed)"
|
|
3
|
+
|
|
4
|
+
ambient = 0.7
|
|
5
|
+
diffuse = 0.4
|
|
6
|
+
specular = 1.0
|
|
7
|
+
|
|
8
|
+
[[transfer_functions]]
|
|
9
|
+
|
|
10
|
+
[transfer_functions.opacity]
|
|
11
|
+
[[transfer_functions.opacity.points]]
|
|
12
|
+
x = 150
|
|
13
|
+
y = 0.0
|
|
14
|
+
|
|
15
|
+
[[transfer_functions.opacity.points]]
|
|
16
|
+
x = 650
|
|
17
|
+
y = 1.0
|
|
18
|
+
|
|
19
|
+
[[transfer_functions.opacity.points]]
|
|
20
|
+
x = 3071
|
|
21
|
+
y = 1.0
|
|
22
|
+
|
|
23
|
+
[transfer_functions.color]
|
|
24
|
+
[[transfer_functions.color.points]]
|
|
25
|
+
x = 150
|
|
26
|
+
color = [1.0, 1.0, 1.0]
|
|
27
|
+
|
|
28
|
+
[[transfer_functions.color.points]]
|
|
29
|
+
x = 3071
|
|
30
|
+
color = [1.0, 1.0, 1.0]
|
|
31
|
+
|
|
32
|
+
[[transfer_functions]]
|
|
33
|
+
|
|
34
|
+
[transfer_functions.opacity]
|
|
35
|
+
[[transfer_functions.opacity.points]]
|
|
36
|
+
x = -96.5
|
|
37
|
+
y = 0.0
|
|
38
|
+
|
|
39
|
+
[[transfer_functions.opacity.points]]
|
|
40
|
+
x = 269.0
|
|
41
|
+
y = 0.05
|
|
42
|
+
|
|
43
|
+
[[transfer_functions.opacity.points]]
|
|
44
|
+
x = 474.5
|
|
45
|
+
y = 0.0
|
|
46
|
+
|
|
47
|
+
[transfer_functions.color]
|
|
48
|
+
[[transfer_functions.color.points]]
|
|
49
|
+
x = -96.5
|
|
50
|
+
color = [1.0, 1.0, 1.0]
|
|
51
|
+
|
|
52
|
+
[[transfer_functions.color.points]]
|
|
53
|
+
x = 474.5
|
|
54
|
+
color = [1.0, 1.0, 1.0]
|
|
55
|
+
|
|
56
|
+
[[transfer_functions]]
|
|
57
|
+
|
|
58
|
+
[transfer_functions.opacity]
|
|
59
|
+
[[transfer_functions.opacity.points]]
|
|
60
|
+
x = 120
|
|
61
|
+
y = 0.0
|
|
62
|
+
|
|
63
|
+
[[transfer_functions.opacity.points]]
|
|
64
|
+
x = 258.0
|
|
65
|
+
y = 0.8
|
|
66
|
+
|
|
67
|
+
[[transfer_functions.opacity.points]]
|
|
68
|
+
x = 320
|
|
69
|
+
y = 0.0
|
|
70
|
+
|
|
71
|
+
[transfer_functions.color]
|
|
72
|
+
[[transfer_functions.color.points]]
|
|
73
|
+
x = 120
|
|
74
|
+
color = [1.0, 0.0, 0.0]
|
|
75
|
+
|
|
76
|
+
[[transfer_functions.color.points]]
|
|
77
|
+
x = 320
|
|
78
|
+
color = [1.0, 1.0, 0.0]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name = "Vascular_Open"
|
|
2
|
+
description = "Vascular (Open)"
|
|
3
|
+
|
|
4
|
+
ambient = 0.7
|
|
5
|
+
diffuse = 0.4
|
|
6
|
+
specular = 1.0
|
|
7
|
+
|
|
8
|
+
[[transfer_functions]]
|
|
9
|
+
|
|
10
|
+
[transfer_functions.opacity]
|
|
11
|
+
[[transfer_functions.opacity.points]]
|
|
12
|
+
x = -96.5
|
|
13
|
+
y = 0.0
|
|
14
|
+
|
|
15
|
+
[[transfer_functions.opacity.points]]
|
|
16
|
+
x = 269.0
|
|
17
|
+
y = 0.05
|
|
18
|
+
|
|
19
|
+
[[transfer_functions.opacity.points]]
|
|
20
|
+
x = 474.5
|
|
21
|
+
y = 0.0
|
|
22
|
+
|
|
23
|
+
[transfer_functions.color]
|
|
24
|
+
[[transfer_functions.color.points]]
|
|
25
|
+
x = -96.5
|
|
26
|
+
color = [1.0, 1.0, 1.0]
|
|
27
|
+
|
|
28
|
+
[[transfer_functions.color.points]]
|
|
29
|
+
x = 474.5
|
|
30
|
+
color = [1.0, 1.0, 1.0]
|
|
31
|
+
|
|
32
|
+
[[transfer_functions]]
|
|
33
|
+
|
|
34
|
+
[transfer_functions.opacity]
|
|
35
|
+
[[transfer_functions.opacity.points]]
|
|
36
|
+
x = 117
|
|
37
|
+
y = 0.0
|
|
38
|
+
|
|
39
|
+
[[transfer_functions.opacity.points]]
|
|
40
|
+
x = 246.0
|
|
41
|
+
y = 0.87
|
|
42
|
+
|
|
43
|
+
[[transfer_functions.opacity.points]]
|
|
44
|
+
x = 319
|
|
45
|
+
y = 0.0
|
|
46
|
+
|
|
47
|
+
[transfer_functions.color]
|
|
48
|
+
[[transfer_functions.color.points]]
|
|
49
|
+
x = 117
|
|
50
|
+
color = [1.0, 0.0, 0.0]
|
|
51
|
+
|
|
52
|
+
[[transfer_functions.color.points]]
|
|
53
|
+
x = 319
|
|
54
|
+
color = [1.0, 1.0, 0.0]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name = "Radiograph"
|
|
2
|
+
description = "Radiograph"
|
|
3
|
+
|
|
4
|
+
ambient = 1.0
|
|
5
|
+
diffuse = 1.0
|
|
6
|
+
specular = 0.0
|
|
7
|
+
|
|
8
|
+
[[transfer_functions]]
|
|
9
|
+
|
|
10
|
+
[transfer_functions.opacity]
|
|
11
|
+
[[transfer_functions.opacity.points]]
|
|
12
|
+
x = -1024.0
|
|
13
|
+
y = 0.0
|
|
14
|
+
|
|
15
|
+
[[transfer_functions.opacity.points]]
|
|
16
|
+
x = -200.0
|
|
17
|
+
y = 0.0
|
|
18
|
+
|
|
19
|
+
[[transfer_functions.opacity.points]]
|
|
20
|
+
x = 300.0
|
|
21
|
+
y = 0.01
|
|
22
|
+
|
|
23
|
+
[[transfer_functions.opacity.points]]
|
|
24
|
+
x = 3071.0
|
|
25
|
+
y = 0.01
|
|
26
|
+
|
|
27
|
+
[transfer_functions.color]
|
|
28
|
+
[[transfer_functions.color.points]]
|
|
29
|
+
x = -1024.0
|
|
30
|
+
color = [0.0, 0.0, 0.0]
|
|
31
|
+
|
|
32
|
+
[[transfer_functions.color.points]]
|
|
33
|
+
x = -200.0
|
|
34
|
+
color = [0.0, 0.0, 0.0]
|
|
35
|
+
|
|
36
|
+
[[transfer_functions.color.points]]
|
|
37
|
+
x = 300.0
|
|
38
|
+
color = [1.0, 1.0, 1.0]
|
|
39
|
+
|
|
40
|
+
[[transfer_functions.color.points]]
|
|
41
|
+
x = 3071.0
|
|
42
|
+
color = [1.0, 1.0, 1.0]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import vtk
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def blend_transfer_functions(tfs, scalar_range=(-2000, 2000), num_samples=512):
|
|
6
|
+
"""
|
|
7
|
+
Blend multiple transfer functions using volume rendering emission-absorption model.
|
|
8
|
+
|
|
9
|
+
Based on the volume rendering equation from:
|
|
10
|
+
- Levoy, M. "Display of Surfaces from Volume Data" IEEE Computer Graphics and Applications, 1988
|
|
11
|
+
- Kajiya, J.T. & Von Herzen, B.P. "Ray tracing volume densities" ACM SIGGRAPH Computer Graphics, 1984
|
|
12
|
+
- Engel, K. et al. "Real-time Volume Graphics" A K Peters, 2006, Chapter 2
|
|
13
|
+
|
|
14
|
+
The volume rendering integral: I = ∫ C(s) * μ(s) * T(s) ds
|
|
15
|
+
where C(s) = emission color, μ(s) = opacity, T(s) = transmission
|
|
16
|
+
|
|
17
|
+
For discrete transfer functions, this becomes:
|
|
18
|
+
- Total emission = Σ(color_i * opacity_i)
|
|
19
|
+
- Total absorption = Σ(opacity_i)
|
|
20
|
+
- Final color = total_emission / total_absorption
|
|
21
|
+
"""
|
|
22
|
+
if len(tfs) == 1:
|
|
23
|
+
return tfs[0]
|
|
24
|
+
|
|
25
|
+
sample_points = np.linspace(
|
|
26
|
+
start=scalar_range[0],
|
|
27
|
+
stop=scalar_range[1],
|
|
28
|
+
num=num_samples,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Initialize arrays to store blended values
|
|
32
|
+
blended_opacity = []
|
|
33
|
+
blended_color = []
|
|
34
|
+
|
|
35
|
+
for scalar_val in sample_points:
|
|
36
|
+
# Accumulate emission and absorption for volume rendering
|
|
37
|
+
total_emission = [0.0, 0.0, 0.0]
|
|
38
|
+
total_absorption = 0.0
|
|
39
|
+
|
|
40
|
+
for otf, ctf in tfs:
|
|
41
|
+
# Get opacity and color for this scalar value
|
|
42
|
+
layer_opacity = otf.GetValue(scalar_val)
|
|
43
|
+
layer_color = [0.0, 0.0, 0.0]
|
|
44
|
+
ctf.GetColor(scalar_val, layer_color)
|
|
45
|
+
|
|
46
|
+
# Volume rendering accumulation:
|
|
47
|
+
# Emission = color * opacity (additive)
|
|
48
|
+
# Absorption = opacity (multiplicative through transmission)
|
|
49
|
+
for i in range(3):
|
|
50
|
+
total_emission[i] += layer_color[i] * layer_opacity
|
|
51
|
+
|
|
52
|
+
total_absorption += layer_opacity
|
|
53
|
+
|
|
54
|
+
# Clamp values to reasonable ranges
|
|
55
|
+
total_absorption = min(total_absorption, 1.0)
|
|
56
|
+
for i in range(3):
|
|
57
|
+
total_emission[i] = min(total_emission[i], 1.0)
|
|
58
|
+
|
|
59
|
+
# For the final color, normalize emission by absorption if absorption > 0
|
|
60
|
+
if total_absorption > 0.001: # Avoid division by zero
|
|
61
|
+
final_color = [total_emission[i] / total_absorption for i in range(3)]
|
|
62
|
+
else:
|
|
63
|
+
final_color = [0.0, 0.0, 0.0]
|
|
64
|
+
|
|
65
|
+
# Clamp final colors
|
|
66
|
+
final_color = [min(c, 1.0) for c in final_color]
|
|
67
|
+
|
|
68
|
+
blended_opacity.append(total_absorption)
|
|
69
|
+
blended_color.append(final_color)
|
|
70
|
+
|
|
71
|
+
# Create new VTK transfer functions with blended values
|
|
72
|
+
blended_otf = vtk.vtkPiecewiseFunction()
|
|
73
|
+
blended_ctf = vtk.vtkColorTransferFunction()
|
|
74
|
+
|
|
75
|
+
for i, scalar_val in enumerate(sample_points):
|
|
76
|
+
blended_otf.AddPoint(scalar_val, blended_opacity[i])
|
|
77
|
+
blended_ctf.AddRGBPoint(
|
|
78
|
+
scalar_val, blended_color[i][0], blended_color[i][1], blended_color[i][2]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return blended_otf, blended_ctf
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import pydantic as pc
|
|
2
|
+
import vtk
|
|
3
|
+
|
|
4
|
+
from .types import RGBColor
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ColorTransferFunctionPoint(pc.BaseModel):
|
|
8
|
+
"""A single point in a color transfer function."""
|
|
9
|
+
|
|
10
|
+
x: float = pc.Field(description="Scalar value")
|
|
11
|
+
color: RGBColor
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ColorTransferFunctionConfig(pc.BaseModel):
|
|
15
|
+
"""Configuration for a VTK color transfer function."""
|
|
16
|
+
|
|
17
|
+
points: list[ColorTransferFunctionPoint] = pc.Field(
|
|
18
|
+
min_length=1, description="Points defining the color transfer function"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def vtk_function(self) -> vtk.vtkColorTransferFunction:
|
|
23
|
+
"""Create VTK color transfer function from this configuration."""
|
|
24
|
+
ctf = vtk.vtkColorTransferFunction()
|
|
25
|
+
for point in self.points:
|
|
26
|
+
ctf.AddRGBPoint(point.x, *point.color)
|
|
27
|
+
return ctf
|