Skip to content

Commit eb4fe02

Browse files
authored
Merge pull request #28 from berenslab/dev-plot
add: plotting options,including rasterize
2 parents bdacc1b + f173dfd commit eb4fe02

File tree

1 file changed

+61
-33
lines changed

1 file changed

+61
-33
lines changed

skeliner/plot/vis2d.py

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,10 @@ def projection(
291291
xlim: tuple[float, float] | None = None,
292292
ylim: tuple[float, float] | None = None,
293293
draw_skel: bool = True,
294+
draw_mesh: bool = True,
294295
draw_edges: bool = True,
295296
draw_cylinders: bool = False,
297+
rasterized: bool = True,
296298
ax: Axes | None = None,
297299
mesh_cmap: str
298300
| mcolors.Colormap
@@ -311,6 +313,7 @@ def projection(
311313
unit: str | None = None,
312314
# soma --------------------------------------------------------------- #
313315
draw_soma_mask: bool = True,
316+
soma_style: str = "dashed", # "dashed" | "filled"
314317
# colors
315318
color_by: str = "fixed", # "ntype" or "fixed"
316319
) -> tuple[Figure, Axes]:
@@ -335,8 +338,14 @@ def projection(
335338
xlim, ylim : (min, max) or *None*
336339
Spatial extent **before** plotting. If not given, limits are inferred
337340
from the histogram (when *mesh* is available) or the skeleton.
338-
draw_skel, draw_edges, draw_cylinders : bool
341+
draw_skel, draw_mesh, draw_edges, draw_cylinders: bool
339342
Toggles for skeleton glyphs.
343+
soma_style : str
344+
How to plot the soma, currently supported styles are:
345+
- "dashed" : dashed ellipse outline (default)
346+
- "filled" : filled circle with soma colour
347+
rasterized : bool | int
348+
Rasterize skeleton. If int and > 1, not only skeleton will be rasterized.
340349
ax : matplotlib.axes.Axes | None
341350
Existing *Axes* to draw into. When *None*, a new figure is created.
342351
mesh_cmap, vmax_fraction : appearance of the histogram – see original docs.
@@ -385,7 +394,7 @@ def projection(
385394
xy_skel = _project(skel.nodes, ix, iy) * scl_skel
386395
rr = skel.radii[radius_metric] * scl_skel
387396

388-
if mesh is not None:
397+
if mesh is not None and draw_mesh:
389398
xy_mesh = _project(mesh.vertices, ix, iy) * scl_mesh
390399
else: # empty placeholder for unified code‑path
391400
xy_mesh = np.empty((0, 2), dtype=float)
@@ -412,12 +421,12 @@ def _crop_window(xy: np.ndarray) -> np.ndarray:
412421
else:
413422
col_nodes = "red"
414423

415-
if mesh is not None and xy_mesh.size:
424+
if mesh is not None and xy_mesh.size and draw_mesh:
416425
keep_mesh = _crop_window(xy_mesh)
417426
xy_mesh = xy_mesh[keep_mesh]
418427

419428
# ─────────────────────────────── histogram (mesh may be None) ──────────
420-
if mesh is not None and xy_mesh.size:
429+
if mesh is not None and xy_mesh.size and draw_mesh:
421430
# ensure bins argument correct
422431
if isinstance(bins, int):
423432
bins_arg: int | tuple[int, int] = bins
@@ -448,14 +457,15 @@ def _crop_window(xy: np.ndarray) -> np.ndarray:
448457
fig = ax.figure
449458

450459
# background image – only when we do have a histogram
451-
if hist is not None:
460+
if hist is not None and draw_mesh:
452461
ax.imshow(
453462
hist,
454463
extent=(xedges[0], xedges[-1], yedges[0], yedges[-1]),
455464
origin="lower",
456465
cmap=_as_cmap(mesh_cmap),
457466
vmax=hist.max() * vmax_fraction,
458467
alpha=1.0,
468+
rasterized=rasterized > 1,
459469
)
460470

461471
# ──────────────────────── draw skeleton circles (always) ───────────────
@@ -483,6 +493,7 @@ def _crop_window(xy: np.ndarray) -> np.ndarray:
483493
linewidths=1.0,
484494
alpha=circle_alpha,
485495
zorder=2,
496+
rasterized=rasterized > 0,
486497
)
487498

488499
# highlighted nodes – filled circles
@@ -499,50 +510,61 @@ def _crop_window(xy: np.ndarray) -> np.ndarray:
499510
linewidths=0.9,
500511
alpha=highlight_face_alpha,
501512
zorder=3.5,
513+
rasterized=rasterized > 1,
502514
)
503515

504516
# ───────────────────────── soma shell & center (if possible) ───────────
505517
c_xy = _project(skel.nodes[[0]] * scl_skel, ix, iy).ravel()
506-
centre_col = swc_colors[1] if color_by == "ntype" else "k"
507-
ax.scatter(*c_xy, color="black", s=15, zorder=3)
518+
col_soma = swc_colors[1] if color_by == "ntype" else "pink"
519+
520+
if soma_style == 'filled':
521+
soma_fc = col_soma
522+
soma_ec = 'k'
523+
soma_ls = '-'
524+
soma_mc = 'none'
525+
else:
526+
ax.scatter(*c_xy, color="black", s=15, zorder=3)
527+
soma_fc = 'none'
528+
soma_ec = 'k'
529+
soma_ls = '--'
530+
soma_mc = col_soma
508531

509532
if (
510-
draw_soma_mask
511-
and mesh is not None
533+
mesh is not None
512534
and skel.soma is not None
513535
and skel.soma.verts is not None
514536
):
515-
xy_soma = _project(mesh.vertices[np.asarray(skel.soma.verts, int)], ix, iy)
516-
xy_soma = xy_soma * scl_mesh
517-
xy_soma = xy_soma[_crop_window(xy_soma)] # respect crop
518-
519-
col_soma = swc_colors[1] if color_by == "ntype" else "pink"
520-
521-
ax.scatter(
522-
xy_soma[:, 0],
523-
xy_soma[:, 1],
524-
s=1,
525-
c=[col_soma],
526-
alpha=0.5,
527-
linewidths=0,
528-
label="soma surface",
529-
)
537+
if draw_soma_mask:
538+
xy_soma = _project(mesh.vertices[np.asarray(skel.soma.verts, int)], ix, iy)
539+
xy_soma = xy_soma * scl_mesh
540+
xy_soma = xy_soma[_crop_window(xy_soma)] # respect crop
541+
542+
ax.scatter(
543+
xy_soma[:, 0],
544+
xy_soma[:, 1],
545+
s=1,
546+
c=[soma_mc],
547+
alpha=0.5,
548+
linewidths=0,
549+
label="soma surface",
550+
rasterized=rasterized > 0,
551+
)
530552
# dashed ellipse outline
531553
ell = _soma_ellipse2d(skel.soma, plane, scale=scl_skel)
532-
ell.set_edgecolor("k")
533-
ell.set_facecolor("none")
534-
ell.set_linestyle("--")
554+
ell.set_edgecolor(soma_ec)
555+
ell.set_facecolor(soma_fc)
556+
ell.set_linestyle(soma_ls)
535557
ell.set_linewidth(0.8)
536558
ell.set_alpha(0.9)
537559
ax.add_patch(ell)
538560
else:
539561
soma_circle = Circle(
540562
c_xy,
541563
skel.soma.equiv_radius * scl_skel,
542-
facecolor="none",
543-
edgecolor=centre_col,
564+
facecolor=soma_fc,
565+
edgecolor=soma_ec,
544566
linewidth=0.8,
545-
linestyle="--",
567+
linestyle=soma_ls,
546568
alpha=0.9,
547569
)
548570
ax.add_patch(soma_circle)
@@ -567,6 +589,7 @@ def _crop_window(xy: np.ndarray) -> np.ndarray:
567589
colors="black",
568590
linewidths=edge_lw,
569591
alpha=cylinder_alpha,
592+
rasterized=rasterized > 0,
570593
)
571594
ax.add_collection(lc)
572595

@@ -603,11 +626,13 @@ def _crop_window(xy: np.ndarray) -> np.ndarray:
603626
edgecolors="red",
604627
alpha=cylinder_alpha,
605628
zorder=10,
629+
rasterized=rasterized > 0,
606630
)
607631
ax.add_collection(pc)
608632

609633
# ────────────────────────────── final cosmetics ────────────────────────
610-
# ax.set_aspect("equal")
634+
if plane in ['xy', 'yx']:
635+
ax.set_aspect('equal', adjustable='box')
611636

612637
if unit is None:
613638
unit_str = "" if scl_skel == 1.0 else f"(×{scl_skel:g})"
@@ -961,14 +986,17 @@ def _mask_window(xy: np.ndarray) -> np.ndarray:
961986
ax.add_patch(ell)
962987

963988
# ────────────── cosmetics & labels ────────────────────────────────────
964-
ax.set_aspect("equal")
989+
if plane in ['xy', 'yx']:
990+
ax.set_aspect("equal", adjustable="box")
991+
965992
if unit is None:
966993
ax.set_xlabel(f"{plane[0]}")
967994
ax.set_ylabel(f"{plane[1]}")
968995
else:
969996
ax.set_xlabel(f"{plane[0]} ({unit})")
970997
ax.set_ylabel(f"{plane[1]} ({unit})")
971998

999+
9721000
if xlim is not None:
9731001
ax.set_xlim(xlim)
9741002
if ylim is not None:
@@ -1205,4 +1233,4 @@ def node_details(
12051233
highlight_face_alpha=highlight_alpha,
12061234
**kwargs,
12071235
)
1208-
return fig, ax
1236+
return fig, ax

0 commit comments

Comments
 (0)