Skip to content

Commit 7f8279e

Browse files
committed
fix crop
1 parent 3fd96bf commit 7f8279e

2 files changed

Lines changed: 66 additions & 29 deletions

File tree

src/cardiotensor/scripts/generate_streamlines.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ def script():
3333
parser.add_argument("--end-x", type=int, default=None, help="End slice index in X")
3434
parser.add_argument("--bin", type=int, default=1, help="Downsampling factor")
3535
parser.add_argument("--seeds", type=int, default=20000, help="Number of seeds")
36-
parser.add_argument("--fa-seed-min", type=float, default=0.4, help="Min FA for seeding")
37-
parser.add_argument("--fa-threshold", type=float, default=0.1, help="FA threshold")
36+
parser.add_argument("--fa-seed-min", type=float, default=0.2, help="Min FA for seeding")
37+
parser.add_argument("--fa-threshold", type=float, default=0.07, help="FA threshold")
3838
parser.add_argument("--step", type=float, default=0.5, help="Step length in voxels")
3939
parser.add_argument(
4040
"--max-steps", type=int, default=None, help="Max steps per streamline"

src/cardiotensor/visualization/fury_plotting_streamlines.py

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,41 @@ def matplotlib_cmap_to_fury_lut(cmap, value_range=(-1, 1), n_colors=256):
4646
return lut
4747

4848

49+
def _split_streamline_by_bounds(sl: np.ndarray,
50+
cl: np.ndarray,
51+
x_min, x_max, y_min, y_max, z_min, z_max):
52+
"""
53+
Returns lists of segments (points) and colors, one per contiguous in-bounds run.
54+
No straight 'bridging' lines will be drawn between disjoint segments.
55+
"""
56+
# in-bounds mask
57+
within = (
58+
(sl[:, 0] >= x_min) & (sl[:, 0] <= x_max) &
59+
(sl[:, 1] >= y_min) & (sl[:, 1] <= y_max) &
60+
(sl[:, 2] >= z_min) & (sl[:, 2] <= z_max)
61+
)
62+
63+
if not np.any(within):
64+
return [], []
65+
66+
# find starts/ends of True runs
67+
w = within.astype(np.int8)
68+
# transitions: +1 = False->True (start), -1 = True->False (end+1)
69+
trans = np.diff(np.pad(w, (1, 1), constant_values=0))
70+
starts = np.where(trans == +1)[0]
71+
ends = np.where(trans == -1)[0] # each end is exclusive index
72+
73+
segs, cols = [], []
74+
for s, e in zip(starts, ends):
75+
seg = sl[s:e] # e is exclusive
76+
col = cl[s:e]
77+
if len(seg) > 0:
78+
segs.append(seg)
79+
cols.append(col)
80+
81+
return segs, cols
82+
83+
4984
def show_streamlines(
5085
streamlines_xyz: list[np.ndarray],
5186
color_values: list[np.ndarray],
@@ -107,48 +142,49 @@ def show_streamlines(
107142

108143
# --- Cropping
109144
print(
110-
f"Cropping streamlines within bounds: {crop_bounds}"
111-
if crop_bounds
112-
else "No cropping applied."
145+
f"Cropping streamlines within bounds: {crop_bounds}"
146+
if crop_bounds
147+
else "No cropping applied."
113148
)
149+
114150
if crop_bounds is not None:
115151
z_min, z_max = crop_bounds[2]
116152
y_min, y_max = crop_bounds[1]
117153
x_min, x_max = crop_bounds[0]
118154

119155
new_streamlines = []
120156
new_color_values = []
121-
color_idx = 0
122157

123-
for sl in streamlines_xyz:
124-
n_pts = len(sl)
125-
cl = color_values[color_idx : color_idx + n_pts]
158+
# If color_values was provided flattened, keep track with a cursor.
159+
# But later code expects lists, so we’ll convert to lists here.
160+
# Detect if flat (ndarray) or already list-like:
161+
flat_input = not isinstance(color_values, (list, tuple))
126162

163+
color_cursor = 0
164+
for sl in streamlines_xyz:
127165
sl = np.asarray(sl)
128-
cl = np.asarray(cl)
129-
130-
within = (
131-
(sl[:, 0] >= x_min)
132-
& (sl[:, 0] <= x_max)
133-
& (sl[:, 1] >= y_min)
134-
& (sl[:, 1] <= y_max)
135-
& (sl[:, 2] >= z_min)
136-
& (sl[:, 2] <= z_max)
166+
if flat_input:
167+
n_pts = len(sl)
168+
cl = np.asarray(color_values[color_cursor:color_cursor + n_pts])
169+
color_cursor += n_pts
170+
else:
171+
# color_values is parallel to streamlines_xyz
172+
# pull next color array by index; we’ll iterate with zip below if you prefer
173+
raise ValueError("color_values should be a flat 1D array before cropping.")
174+
175+
segs, cols = _split_streamline_by_bounds(
176+
sl, cl, x_min, x_max, y_min, y_max, z_min, z_max
137177
)
178+
if segs:
179+
new_streamlines.extend(segs)
180+
new_color_values.extend(cols)
138181

139-
if np.any(within): # Keep only remaining points
140-
new_sl = sl[within]
141-
new_cl = cl[within]
142-
if len(new_sl) > 0:
143-
new_streamlines.append(new_sl)
144-
new_color_values.append(new_cl)
182+
streamlines_xyz = new_streamlines
183+
color_values = new_color_values # now a list aligned with streamlines
145184

146-
color_idx += n_pts
185+
if not streamlines_xyz:
186+
raise ValueError("❌ No streamlines intersect the crop box.")
147187

148-
streamlines_xyz = new_streamlines
149-
color_values = (
150-
np.concatenate(new_color_values) if new_color_values else np.array([])
151-
)
152188

153189
# --- Downsample and filter
154190
downsampled_streamlines = []
@@ -218,6 +254,7 @@ def show_streamlines(
218254
scene = fury.window.Scene()
219255
colors = flat_colors # per-vertex scalars
220256

257+
221258
# --- Render according to mode
222259
if mode == "tube":
223260
actor = fury.actor.streamtube(

0 commit comments

Comments
 (0)