2424 load_pipeline_config ,
2525 save_pipeline_config ,
2626)
27+ from vesskel ._batch import _save_skeleton , _save_radius , _write_csv
2728from vesskel .pipeline import analyze_binary_image
2829
2930
@@ -35,6 +36,7 @@ class VesselAnalysisWidget(Container):
3536 def __init__ (self , napari_viewer ):
3637 super ().__init__ ()
3738 self .viewer = napari_viewer
39+ self ._output_dir : Path | None = None
3840 self ._setup_ui ()
3941
4042 def _setup_ui (self ):
@@ -205,6 +207,17 @@ def _output_params(
205207 output_group .append (self .write_node_csv_widget )
206208 output_group .append (self .write_radius_widget )
207209
210+ # ============================================================
211+ # Output Directory
212+ # ============================================================
213+ outdir_group = Container ()
214+ outdir_group .label = "Output Directory"
215+
216+ self .select_outdir_btn = PushButton (text = "Select Output Directory..." )
217+ self .select_outdir_btn .clicked .connect (self ._on_select_output_dir )
218+
219+ outdir_group .append (self .select_outdir_btn )
220+
208221 # ============================================================
209222 # Configuration Management
210223 # ============================================================
@@ -233,6 +246,7 @@ def _output_params(
233246 self .append (extraction_group )
234247 self .append (advanced_group )
235248 self .append (output_group )
249+ self .append (outdir_group )
236250 self .append (config_group )
237251 self .append (self .analyze_btn )
238252
@@ -330,6 +344,12 @@ def _on_save_config(self) -> None:
330344 except (ValueError , OSError ) as e :
331345 show_error (f"Failed to save config: { e } " )
332346
347+ def _on_select_output_dir (self ) -> None :
348+ dir_path = QFileDialog .getExistingDirectory (None , "Select Output Directory" )
349+ if dir_path :
350+ self ._output_dir = Path (dir_path )
351+ self .select_outdir_btn .text = str (self ._output_dir )
352+
333353 def _on_analyze (self ) -> None :
334354 """Execute analysis with current settings."""
335355 img = self .image_widget .value
@@ -379,5 +399,37 @@ def _on_analyze(self) -> None:
379399 show_info (
380400 f"Failed to add layer { meta .get ('name' , '<unnamed>' )} : { e } "
381401 )
402+
403+ # -- save results to disk if output directory is set ----------
404+ if self ._output_dir is not None :
405+ out = self ._output_dir / img .name
406+ out .mkdir (parents = True , exist_ok = True )
407+
408+ o = pipeline_config .output
409+ if o .write_skeleton_npy or o .write_skeleton_png :
410+ _save_skeleton (
411+ out / f"{ img .name } _skeleton" ,
412+ result .skeleton ,
413+ npy = o .write_skeleton_npy ,
414+ png = o .write_skeleton_png ,
415+ )
416+
417+ if o .write_radius and result .radius_matrix is not None :
418+ _save_radius (out / f"{ img .name } _radius" , result .radius_matrix )
419+
420+ if o .write_branch_csv and result .branch_records :
421+ _write_csv (out / f"{ img .name } _branches.csv" , result .branch_records )
422+
423+ if o .write_node_csv and result .node_records :
424+ _write_csv (out / f"{ img .name } _nodes.csv" , result .node_records )
425+
426+ if o .write_summary_csv and result .summary_features :
427+ _write_csv (
428+ out / f"{ img .name } _summary.csv" ,
429+ [{"image" : img .name , ** result .summary_features }],
430+ )
431+
432+ show_info (f"Results saved to { self ._output_dir / img .name } " )
433+
382434 except (ValueError , RuntimeError , OSError ) as e :
383435 show_error (f"Analysis failed: { e } " )
0 commit comments