Skip to content

Commit 239be8c

Browse files
committed
pre-commit
1 parent b971cdb commit 239be8c

16 files changed

Lines changed: 142 additions & 109 deletions

.pre-commit-config.yaml

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,41 @@ repos:
44
hooks:
55
- id: ruff
66
args: [--fix]
7-
exclude: ^examples/
7+
exclude: ^(examples/|paper/)
88
- id: ruff-format
9-
exclude: ^examples/
9+
exclude: ^(examples/|paper/)
1010
- repo: https://github.com/Lucas-C/pre-commit-hooks
1111
rev: v1.5.5
1212
hooks:
1313
- id: forbid-tabs
14-
exclude: ^examples/
14+
exclude: ^(examples/|paper/)
1515
- repo: https://github.com/pappasam/toml-sort
1616
rev: v0.24.2
1717
hooks:
1818
- id: toml-sort-fix
19-
exclude: ^examples/
19+
exclude: ^(examples/|paper/)
2020
# - repo: https://github.com/pre-commit/mirrors-prettier
2121
# rev: v4.0.0-alpha.8
2222
# hooks:
2323
# - id: prettier
2424
# files: ^src/cardiotensor/
25-
# exclude: ^examples/
25+
# exclude: ^(examples/|paper/)
2626
- repo: https://github.com/pre-commit/pre-commit-hooks
2727
rev: v5.0.0
2828
hooks:
2929
- id: check-case-conflict
30-
exclude: ^examples/
30+
exclude: ^(examples/|paper/)
3131
- id: check-docstring-first
32-
exclude: ^examples/
32+
exclude: ^(examples/|paper/)
3333
- id: check-merge-conflict
34-
exclude: ^examples/
34+
exclude: ^(examples/|paper/)
3535
- id: check-toml
36-
exclude: ^examples/
36+
exclude: ^(examples/|paper/)
3737
- id: mixed-line-ending
3838
args: [--fix=lf]
39-
exclude: ^examples/
39+
exclude: ^(examples/|paper/)
4040
- id: trailing-whitespace
41-
exclude: ^examples/
41+
exclude: ^(examples/|paper/)
4242
- repo: https://github.com/pre-commit/mirrors-mypy
4343
rev: v1.14.1
4444
hooks:
@@ -50,7 +50,7 @@ repos:
5050
]
5151
args: [--config-file, pyproject.toml, --ignore-missing-imports]
5252
files: ^src/
53-
exclude: ^examples/
53+
exclude: ^(examples/|paper/)
5454
# - repo: https://github.com/psf/black
5555
# rev: 24.10.0
5656
# hooks:
@@ -60,4 +60,5 @@ repos:
6060
hooks:
6161
- id: codespell
6262
args: [--write-changes, --skip, "*.log"]
63-
exclude: ^examples/
63+
exclude: ^(examples/|paper/)
64+

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@ cardiotensor's documentation is available at [josephbrunet.fr/cardiotensor/](htt
4747
Have a look at our [simple example](https://www.josephbrunet.fr/cardiotensor/getting-started/examples/) that runs you through all the commands of the package
4848

4949
<p align="center">
50-
<img src="https://github.com/JosephBrunet/cardiotensor/raw/main/assets/images/pipeline.png"
51-
alt="Cardiotensor pipeline for 3D cardiac orientation analysis"
50+
<img src="https://github.com/JosephBrunet/cardiotensor/raw/main/assets/images/pipeline.png"
51+
alt="Cardiotensor pipeline for 3D cardiac orientation analysis"
5252
style="max-width: 100%; border-radius: 6px;">
5353
<br>
5454
<em>
5555
<strong>Overview of the <code>cardiotensor</code> pipeline for 3D cardiac orientation analysis and tractography.</strong>
5656
<strong>(a)</strong> Input data consist of a whole‑ or partial‑heart volume and, optionally, a binary mask to restrict analysis to myocardial tissue.
57-
<strong>(b)</strong> Local cardiomyocyte orientation is derived by 3D structure tensor computation and eigenvector decomposition.
57+
<strong>(b)</strong> Local cardiomyocyte orientation is derived by 3D structure tensor computation and eigenvector decomposition.
5858
The third eigenvector (smallest eigenvalue) is visualized as arrows, color‑coded by helix angle (HA); inset shows a zoom of the ventricular septum highlighting transmural fiber rotation.
5959
<strong>(c)</strong> After transforming to a cylindrical coordinate system aligned with the left ventricle, voxel‑wise HA, transverse angle (TA), and fractional anisotropy (FA) maps are computed for quantitative analysis.
6060
<strong>(d)</strong> Streamline tractography generated from the eigenvector field reveals continuous cardiomyocyte bundles throughout the heart, color‑coded by HA.

docs/getting-technical/angles.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ This page explains how helix and intrusion angles are calculated from the 3D eig
66

77
A transformation to a cylindrical coordinate system is defined for each voxel based on an approximation of the left ventricle (LV) centerline.
88

9-
- **Radial (r)**: outward from the LV center
10-
- **Circumferential (θ)**: tangential around the ventricle
11-
- **Longitudinal (z)**: base to apex direction
9+
- **Radial (r)**: outward from the LV center
10+
- **Circumferential (θ)**: tangential around the ventricle
11+
- **Longitudinal (z)**: base to apex direction
1212

1313
To compute local fiber angles consistently, all eigenvectors are first rotated into this cylindrical coordinate frame. This alignment is performed using Rodrigues' rotation formula, which computes the minimal-angle rotation that maps the global reference axis (here the z-axis) onto the local longitudinal axis at each point. This allows a robust comparison of orientations across the myocardium.
1414

docs/getting-technical/conventions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Sign conventions
44

5-
Cardiotensor processes full-field orientation data in 3D volumes. Consistent with NumPy matrix indexing, the position (0, 0, 0) refers to the corner at the top-left of the first slice.
5+
Cardiotensor processes full-field orientation data in 3D volumes. Consistent with NumPy matrix indexing, the position (0, 0, 0) refers to the corner at the top-left of the first slice.
66

77
This convention is maintained across all processing pipelines, including structure tensor calculation and streamline generation.
88

docs/getting-technical/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ This section provides a deeper understanding of how `cardiotensor` works, includ
44

55
## Topics Covered
66

7-
- [Conventions](./conventions.md)
7+
- [Conventions](./conventions.md)
88

99
- [Structure Tensor](./structure_tensor.md): Mathematical background on the structure tensor and eigen decomposition
1010

1111
- [Angle Definitions](./angles.md): Detailed explanation of helix and intrusion angles
1212

13-
- [Fractional Anisotropy](./fractional_anisotropy.md)
13+
- [Fractional Anisotropy](./fractional_anisotropy.md)
1414

15-
- [Tractography](./tractography.md)
15+
- [Tractography](./tractography.md)

src/cardiotensor/analysis/gui_analysis_tool.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import argparse
21
import sys
32
from pathlib import Path
43

@@ -18,7 +17,6 @@
1817
)
1918
from PyQt5.QtWidgets import (
2019
QAction,
21-
QApplication,
2220
QButtonGroup,
2321
QFileDialog,
2422
QGraphicsEllipseItem,
@@ -49,7 +47,6 @@
4947
from cardiotensor.utils.DataReader import DataReader
5048

5149

52-
5350
def np2pixmap(np_img: np.ndarray) -> QPixmap:
5451
height, width, channel = np_img.shape
5552
bytes_per_line = 3 * width
@@ -684,4 +681,3 @@ def set_img_mode(
684681

685682
# Close the dialog
686683
self.dialog.close()
687-

src/cardiotensor/orientation/orientation_computation_pipeline.py

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,14 @@
2525
from cardiotensor.utils.utils import remove_corrupted_files
2626

2727

28-
2928
# --- small helpers ---
3029
def check_already_processed(
3130
output_dir: str,
3231
start_index: int,
3332
end_index: int,
3433
write_vectors: bool,
3534
write_angles: bool,
36-
output_format: str
35+
output_format: str,
3736
) -> bool:
3837
"""
3938
Check whether all required output files already exist.
@@ -53,11 +52,13 @@ def check_already_processed(
5352
expected_files = []
5453

5554
if write_angles:
56-
expected_files.extend([
57-
f"{output_dir}/HA/HA_{idx:06d}.{output_format}",
58-
f"{output_dir}/IA/IA_{idx:06d}.{output_format}",
59-
f"{output_dir}/FA/FA_{idx:06d}.{output_format}",
60-
])
55+
expected_files.extend(
56+
[
57+
f"{output_dir}/HA/HA_{idx:06d}.{output_format}",
58+
f"{output_dir}/IA/IA_{idx:06d}.{output_format}",
59+
f"{output_dir}/FA/FA_{idx:06d}.{output_format}",
60+
]
61+
)
6162

6263
if write_vectors:
6364
expected_files.append(f"{output_dir}/eigen_vec/eigen_vec_{idx:06d}.npy")
@@ -73,7 +74,6 @@ def check_already_processed(
7374
return True
7475

7576

76-
7777
# --- main API ---
7878
def compute_orientation(
7979
volume_path: str,
@@ -136,7 +136,6 @@ def compute_orientation(
136136
- Test mode: {is_test}
137137
""")
138138

139-
140139
print("\n" + "-" * 40)
141140
print("READING VOLUME INFORMATION")
142141
print("-" * 40 + "\n")
@@ -149,22 +148,23 @@ def compute_orientation(
149148
end_index = data_reader.shape[0]
150149

151150
print(f"Number of slices: {data_reader.shape[0]}")
152-
153-
151+
154152
# --- Check if already processed ---
155153
print("Check if file is already processed...")
156-
if check_already_processed(
157-
output_dir,
158-
start_index,
159-
end_index,
160-
write_vectors,
161-
write_angles,
162-
output_format,
163-
) and not is_test:
154+
if (
155+
check_already_processed(
156+
output_dir,
157+
start_index,
158+
end_index,
159+
write_vectors,
160+
write_angles,
161+
output_format,
162+
)
163+
and not is_test
164+
):
164165
print("\nAll images are already processed. Skipping computation.\n")
165166
return
166-
167-
167+
168168
print("\n---------------------------------")
169169
print("CALCULATE CENTER LINE\n")
170170
center_line = interpolate_points(axis_points, data_reader.shape[0])
@@ -334,8 +334,6 @@ def update_bar(_):
334334
)
335335
bar() # Update the progress bar for each slice
336336

337-
338-
339337
print(f"\n🤖 - Finished processing slices {start_index} - {end_index}")
340338
print("---------------------------------\n\n")
341339
return

src/cardiotensor/scripts/compute_orientation.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
from tkinter import Tk
66
from tkinter.filedialog import askopenfilename
77

8-
from cardiotensor.orientation.orientation_computation_pipeline import compute_orientation
8+
from cardiotensor.orientation.orientation_computation_pipeline import (
9+
compute_orientation,
10+
)
911
from cardiotensor.utils.DataReader import DataReader
1012
from cardiotensor.utils.utils import read_conf_file
1113

@@ -54,7 +56,10 @@ def script() -> None:
5456
conf_file_path: str = askopenfilename(
5557
initialdir=f"{os.getcwd()}/param_files",
5658
title="Select a configuration file",
57-
filetypes=[("Config files", "*.conf *.json *.yaml *.yml"), ("All files", "*.*")]
59+
filetypes=[
60+
("Config files", "*.conf *.json *.yaml *.yml"),
61+
("All files", "*.*"),
62+
],
5863
)
5964
if not conf_file_path:
6065
sys.exit("❌ No file selected! Exiting.")

src/cardiotensor/scripts/generate_streamlines.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,20 @@
99
import argparse
1010
import sys
1111
from pathlib import Path
12+
13+
from cardiotensor.tractography.generate_streamlines import (
14+
generate_streamlines_from_params,
15+
)
1216
from cardiotensor.utils.utils import read_conf_file
13-
from cardiotensor.tractography.generate_streamlines import generate_streamlines_from_params
1417

1518

1619
def script():
1720
parser = argparse.ArgumentParser(
1821
description="Generate streamlines from a 3D vector field and save to .npz"
1922
)
20-
parser.add_argument("conf_file", type=Path, help="Path to configuration .conf file.")
23+
parser.add_argument(
24+
"conf_file", type=Path, help="Path to configuration .conf file."
25+
)
2126
parser.add_argument("--start-z", type=int, default=0, help="Start slice index in Z")
2227
parser.add_argument("--end-z", type=int, default=None, help="End slice index in Z")
2328
parser.add_argument("--start-y", type=int, default=0, help="Start slice index in Y")
@@ -28,9 +33,15 @@ def script():
2833
parser.add_argument("--seeds", type=int, default=20000, help="Number of seeds")
2934
parser.add_argument("--fa-threshold", type=float, default=0.1, help="FA threshold")
3035
parser.add_argument("--step", type=float, default=0.5, help="Step length in voxels")
31-
parser.add_argument("--max-steps", type=int, default=None, help="Max steps per streamline")
32-
parser.add_argument("--angle", type=float, default=60.0, help="Max turning angle (deg)")
33-
parser.add_argument("--min-len", type=int, default=10, help="Minimum streamline length (points)")
36+
parser.add_argument(
37+
"--max-steps", type=int, default=None, help="Max steps per streamline"
38+
)
39+
parser.add_argument(
40+
"--angle", type=float, default=60.0, help="Max turning angle (deg)"
41+
)
42+
parser.add_argument(
43+
"--min-len", type=int, default=10, help="Minimum streamline length (points)"
44+
)
3445

3546
args = parser.parse_args()
3647

src/cardiotensor/scripts/gui_analysis_tool.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import argparse
22
import sys
3-
from pathlib import Path
3+
44
from PyQt5.QtWidgets import QApplication
55

66
from cardiotensor.analysis.gui_analysis_tool import Window
@@ -19,11 +19,19 @@ def parse_arguments() -> argparse.Namespace:
1919
parser = argparse.ArgumentParser(
2020
description="Open a GUI to interactively plot transmural profiles of the angles."
2121
)
22-
parser.add_argument("conf_file_path", type=str, help="Path to the configuration file")
23-
parser.add_argument("--N_slice", type=int, default=None, help="Slice number (optional)")
22+
parser.add_argument(
23+
"conf_file_path", type=str, help="Path to the configuration file"
24+
)
25+
parser.add_argument(
26+
"--N_slice", type=int, default=None, help="Slice number (optional)"
27+
)
2428
parser.add_argument("--N_line", type=int, default=5, help="Number of lines")
25-
parser.add_argument("--angle_range", type=float, default=20, help="Angle range in degrees")
26-
parser.add_argument("--image_mode", type=str, default="HA", help="Output mode (HA, IA, or FA)")
29+
parser.add_argument(
30+
"--angle_range", type=float, default=20, help="Angle range in degrees"
31+
)
32+
parser.add_argument(
33+
"--image_mode", type=str, default="HA", help="Output mode (HA, IA, or FA)"
34+
)
2735
return parser.parse_args()
2836

2937

@@ -45,7 +53,9 @@ def script() -> None:
4553
output_dir = params.get("OUTPUT_PATH", "./output")
4654

4755
# Determine slice number: CLI overrides config
48-
N_slice = args.N_slice if args.N_slice is not None else params.get("N_SLICE_TEST", 0)
56+
N_slice = (
57+
args.N_slice if args.N_slice is not None else params.get("N_SLICE_TEST", 0)
58+
)
4959

5060
# Initialize the PyQt5 application
5161
app = QApplication(sys.argv)

0 commit comments

Comments
 (0)