@@ -179,29 +179,37 @@ def build_skeleton(
179179 seg_id : int ,
180180 soma_threshold : float ,
181181 soma_distance_factor : float ,
182+ soma_init_guess_axis : str ,
182183 soma_init_guess_mode : str ,
183184 verbose : bool ,
184185 overwrite : bool ,
185186) -> Path :
186- """Skeletonise mesh, save SWC & preview PNG; return SWC path."""
187- skel_path = outdir / "skeleton.swc"
187+ """Skeletonise mesh, save SWC, NPZ & preview PNG; return NPZ path."""
188+ swc_path = outdir / "skeleton.swc"
188189 npz_path = outdir / "skeleton.npz"
189190 preview = outdir / "skeleton.png"
190191
191- if not overwrite and skel_path .exists () and preview .exists ():
192+ if not overwrite and npz_path .exists () and preview .exists ():
192193 if verbose :
193194 print (f"Skeleton for segment { seg_id } already exists at:" )
194- print (f" { skel_path } \n " )
195- return skel_path
195+ print (f" { swc_path } \n " )
196+ print (f" { npz_path } \n " )
197+
198+ return npz_path
196199
197200 if verbose :
198201 print ("Skeletonising …" )
199- print (soma_threshold , soma_distance_factor , soma_init_guess_mode )
202+ print (
203+ soma_threshold ,
204+ soma_distance_factor ,
205+ f"init_guess=({ soma_init_guess_axis } , { soma_init_guess_mode } )" ,
206+ )
200207 mesh = sk .io .load_mesh (mesh_path ) # nm
201208 skel = sk .skeletonize (
202209 mesh ,
203210 soma_radius_percentile_threshold = soma_threshold ,
204211 soma_radius_distance_factor = soma_distance_factor ,
212+ soma_init_guess_axis = soma_init_guess_axis ,
205213 soma_init_guess_mode = soma_init_guess_mode ,
206214 verbose = verbose ,
207215 id = seg_id ,
@@ -220,15 +228,15 @@ def build_skeleton(
220228 plt .close (fig )
221229
222230 skel .convert_unit (target_unit = "μm" )
223- skel .to_swc (skel_path )
231+ skel .to_swc (swc_path )
224232 skel .to_npz (npz_path )
225233 if verbose :
226234 print ("Saved skeleton and plot to: " )
227- print (f" { skel_path } " )
235+ print (f" { swc_path } " )
228236 print (f" { npz_path } " )
229237 print (f" { preview } " )
230238 print ("\n " )
231- return skel_path
239+ return npz_path
232240
233241
234242def warp_skeleton (
@@ -263,21 +271,35 @@ def warp_skeleton(
263271
264272 # w.on_sac_surface, w.off_sac_surface = _load_sac_surfaces()
265273
266- w .skeleton = sk .io .load_swc (skel_path ) # μm
274+ w .skeleton = sk .io .load_npz (skel_path ) # μm
267275 w .mapping = mapping
268276 w .warp_skeleton (z_profile_extent = z_profile_extent )
269277
270278 w .warped_skeleton .to_swc (warped_swc ) # μm
271279 w .warped_skeleton .to_npz (outdir / "skeleton_warped.npz" )
272280 # 3-D warped view -------------------------------------------------------
273- fig , ax = sk .plot3v (
274- w .warped_skeleton ,
275- scale = 1 ,
276- unit = "μm" ,
277- color_by = "ntype" ,
278- skel_cmap = "Set2" ,
279- title = f"{ seg_id } (warped)" ,
280- )
281+ # If a warped mesh exists, plot it together with the warped skeleton
282+ warped_mesh_path = outdir / "mesh_warped.obj"
283+ if warped_mesh_path .exists ():
284+ warped_mesh = sk .io .load_mesh (warped_mesh_path )
285+ fig , ax = sk .plot3v (
286+ w .warped_skeleton ,
287+ warped_mesh ,
288+ scale = (1 , 1e-3 ),
289+ unit = "μm" ,
290+ color_by = "ntype" ,
291+ skel_cmap = "Set2" ,
292+ title = f"{ seg_id } (warped)" ,
293+ )
294+ else :
295+ fig , ax = sk .plot3v (
296+ w .warped_skeleton ,
297+ scale = 1 ,
298+ unit = "μm" ,
299+ color_by = "ntype" ,
300+ skel_cmap = "Set2" ,
301+ title = f"{ seg_id } (warped)" ,
302+ )
281303
282304 fig .savefig (warped_png , dpi = 300 , bbox_inches = "tight" )
283305 plt .close (fig )
@@ -409,25 +431,77 @@ def warp_mesh_and_save(
409431 mesh_path : Path ,
410432 outdir : Path ,
411433 mapping : dict ,
434+ seg_id : str ,
412435 verbose : bool ,
413436 overwrite : bool ,
414437) -> None :
415- """Warp the *raw mesh* and save as OBJ."""
438+ """
439+ Warp the *raw mesh* and save as OBJ.
440+
441+ If mesh_warped.obj already exists and overwrite=False, skip re-warping
442+ and just (re)generate the plot using the existing warped mesh.
443+ """
416444 warped_path = outdir / "mesh_warped.obj"
417- if warped_path .exists () and not overwrite :
418- if verbose :
419- print ("Warped mesh already exists at:" )
420- print (f" { warped_path } \n " )
421- return
445+ out_png = outdir / "skeleton_warped.png"
446+
447+ to_warp = overwrite or not warped_path .exists ()
422448
423449 if verbose :
424- print ("Warping raw mesh (may be slow) …" )
425- mesh = sk .io .load_mesh (mesh_path ) # nm
426- warped_mesh = warp_mesh_fn (mesh , mapping , mesh_vertices_scale = 1e-3 , verbose = verbose )
427- warped_mesh .export (warped_path )
450+ if to_warp :
451+ print ("Warping raw mesh (may be slow) …" )
452+ else :
453+ print ("Using existing warped mesh …" )
454+
455+ if to_warp :
456+ mesh = sk .io .load_mesh (mesh_path ) # nm
457+ warped_mesh = warp_mesh_fn (
458+ mesh , mapping , mesh_vertices_scale = 1e-3 , verbose = verbose
459+ )
460+ warped_mesh .export (warped_path )
461+ else :
462+ # Load the already-warped mesh from disk
463+ warped_mesh = sk .io .load_mesh (warped_path )
464+
465+ # Create the combined plot only if a warped skeleton exists.
466+ skel_npz_path = outdir / "skeleton_warped.npz"
467+ if skel_npz_path .exists ():
468+ warped_skel = sk .io .load_npz (skel_npz_path )
469+ fig , ax = sk .plot3v (
470+ warped_skel ,
471+ warped_mesh ,
472+ scale = (1 , 1e-3 ),
473+ unit = "μm" ,
474+ title = f"{ seg_id } (warped)" ,
475+ color_by = "ntype" ,
476+ skel_cmap = "Set2" ,
477+ )
478+ fig .savefig (out_png , dpi = 300 , bbox_inches = "tight" )
479+ plt .close (fig )
480+ else :
481+ if verbose :
482+ print (
483+ "Warped skeleton not found; skipping combined plot. "
484+ "(Will be included in warp_skeleton’s plot if available.)"
485+ )
486+
428487 if verbose :
429- print ("Saved warped mesh to:" )
430- print (f" { warped_path } \n " )
488+ if to_warp and skel_npz_path .exists ():
489+ print ("Saved warped mesh and combined plot to:" )
490+ print (f" { warped_path } " )
491+ print (f" { out_png } \n " )
492+ elif to_warp and not skel_npz_path .exists ():
493+ print ("Saved warped mesh to:" )
494+ print (f" { warped_path } " )
495+ print ("No warped skeleton found; skipped combined plot.\n " )
496+ elif (not to_warp ) and skel_npz_path .exists ():
497+ print ("Reused warped mesh at:" )
498+ print (f" { warped_path } " )
499+ print ("Saved combined plot to:" )
500+ print (f" { out_png } \n " )
501+ else :
502+ print ("Reused warped mesh at:" )
503+ print (f" { warped_path } " )
504+ print ("No warped skeleton found; skipped combined plot.\n " )
431505
432506
433507def _gather_all_segids (root_out : Path ) -> list [int ]:
@@ -574,19 +648,31 @@ def _build_pipeline_parser() -> argparse.ArgumentParser:
574648 p .add_argument (
575649 "--soma-threshold" ,
576650 type = float ,
577- help = "(0-90)" ,
651+ help = (
652+ "Percentile (0-100) of the node radius distribution used to detect the "
653+ "soma; default: 99.9."
654+ ),
578655 default = 99.9 ,
579656 )
580657 p .add_argument (
581658 "--soma-distance-factor" ,
582659 type = int ,
660+ help = (
661+ "Distance factor for soma detection: keeps candidate nodes within "
662+ "factor × R_max of the largest-radius node; higher values admit more "
663+ "distant candidates (default: 4)."
664+ ),
583665 default = 4 ,
584666 )
585667 p .add_argument (
586- "--soma-init-guess-mode" ,
587- type = str ,
588- help = "choose the min or max node in Z axis as initial guess" ,
589- default = "min" ,
668+ "--soma-init-guess" ,
669+ nargs = 2 ,
670+ metavar = ("AXIS" , "EXTREME" ),
671+ default = ("z" , "min" ),
672+ help = (
673+ "Initial guess for soma seeding: provide axis and extreme, e.g. "
674+ "'z min'. AXIS ∈ {x,y,z}; EXTREME ∈ {min,max}."
675+ ),
590676 )
591677
592678 return p
@@ -681,12 +767,19 @@ def main() -> None:
681767 args .seg_id ,
682768 args .soma_threshold ,
683769 args .soma_distance_factor ,
684- args .soma_init_guess_mode ,
770+ args .soma_init_guess [0 ],
771+ args .soma_init_guess [1 ],
685772 args .verbose ,
686773 ow_skel ,
687774 )
688775 mapping = _load_global_mapping (args .mapping )
689776
777+ # Optionally warp the mesh first so that warp_skeleton can overlay it
778+ if args .warp_mesh :
779+ warp_mesh_and_save (
780+ mesh_path , outdir , mapping , args .seg_id , args .verbose , ow_meshwarp
781+ )
782+
690783 warp_skeleton (
691784 skel_path ,
692785 outdir ,
@@ -697,9 +790,6 @@ def main() -> None:
697790 overwrite = ow_prof ,
698791 )
699792
700- if args .warp_mesh :
701- warp_mesh_and_save (mesh_path , outdir , mapping , args .verbose , ow_meshwarp )
702-
703793
704794if __name__ == "__main__" :
705795 main ()
0 commit comments