@@ -977,6 +977,22 @@ def check_error_file(filename):
977977 parser .add_argument ('-reconMethod' , type = str , required = False , default = 'fbp' , choices = ['fbp' , 'mlem' , 'osem' ], help = 'Sinogram reconstruction method: fbp=filtered back-projection via gridrec (default), mlem=Maximum Likelihood EM (handles sparse/missing angles natively), osem=Ordered Subsets EM (accelerated MLEM).' )
978978 parser .add_argument ('-mlemIter' , type = int , required = False , default = 50 , help = 'Number of MLEM/OSEM iterations (only used when -reconMethod is mlem or osem). Default: 50.' )
979979 parser .add_argument ('-osemSubsets' , type = int , required = False , default = 4 , help = 'Number of ordered subsets for OSEM (only used when -reconMethod is osem). Default: 4.' )
980+ parser .add_argument ('-useEM' , type = int , required = False , default = 0 ,
981+ help = 'Use EM spot-ownership for soft sinogram generation (requires doTomo=1). Default: 0 (off).' )
982+ parser .add_argument ('-emIter' , type = int , required = False , default = 10 ,
983+ help = 'Number of EM iterations. Default: 10.' )
984+ parser .add_argument ('-emSigmaInit' , type = float , required = False , default = 0.02 ,
985+ help = 'Initial sigma for EM Gaussian kernel (radians, ~1 degree). Default: 0.02.' )
986+ parser .add_argument ('-emSigmaMin' , type = float , required = False , default = 0.005 ,
987+ help = 'Minimum sigma for EM annealing floor (radians). Default: 0.005.' )
988+ parser .add_argument ('-emSigmaDecay' , type = float , required = False , default = 0.9 ,
989+ help = 'Sigma decay factor per EM iteration. Default: 0.9.' )
990+ parser .add_argument ('-emRefineOrientations' , type = int , required = False , default = 1 ,
991+ help = 'Whether EM M-step refines grain orientations. 0=E-step only, 1=full EM (default).' )
992+ parser .add_argument ('-emOptSteps' , type = int , required = False , default = 5 ,
993+ help = 'Gradient steps per EM M-step per grain. Default: 5.' )
994+ parser .add_argument ('-emLR' , type = float , required = False , default = 0.005 ,
995+ help = 'Learning rate for EM M-step Adam optimizer. Default: 0.005.' )
980996 parser .add_argument ('-resume' , type = str , required = False , default = '' ,
981997 help = 'Path to a pipeline H5 file to resume from. Auto-detects the first incomplete stage.' )
982998 parser .add_argument ('-restartFrom' , type = str , required = False , default = '' ,
@@ -1008,6 +1024,7 @@ def check_error_file(filename):
10081024 sinoType = args .sinoType
10091025 sinoSource = args .sinoSource
10101026 sinoMode = 1 if sinoSource == 'indexing' else 0
1027+ useEM = args .useEM
10111028 # Ensure command line file arguments are absolute paths before a potential directory change
10121029 param_path = os .path .abspath (args .paramFile )
10131030 param_dir = os .path .dirname (param_path )
@@ -1575,6 +1592,59 @@ def _should_run(stage_name):
15751592 if idData [voxNr , 1 ] != 0 :
15761593 f .write (f"{ idData [voxNr , 0 ]} { idData [voxNr , 1 ]} { idData [voxNr , 2 ]} { idData [voxNr , 3 ]} { idData [voxNr , 4 ]} \n " )
15771594
1595+ # EM spot-ownership: pre-tomo refinement + soft sinograms
1596+ if useEM == 1 and doTomo == 1 :
1597+ logger .info ("=== EM Spot-Ownership Pipeline ===" )
1598+
1599+ # Step 1: Pre-tomo refinement to get better initial orientations
1600+ logger .info ("Running pre-tomo refinement for EM initialization" )
1601+
1602+ # Save first-pass Output/Results before refinement overwrites
1603+ for dirn in ['Results' ]:
1604+ if os .path .isdir (dirn ):
1605+ shutil .rmtree (dirn )
1606+ os .makedirs (dirn , exist_ok = True )
1607+
1608+ resRefine0 = []
1609+ for nodeNr in range (nNodes ):
1610+ resRefine0 .append (refinescanning (topdir , numProcs , midas_path ,
1611+ blockNr = nodeNr , numBlocks = nNodes ,
1612+ useGPU = useGPU ))
1613+ outputRefine0 = [i .result () for i in resRefine0 ]
1614+ for i , output in enumerate (outputRefine0 ):
1615+ if output and "Failed" in output :
1616+ logger .warning (f"Pre-tomo refinement warning for node { i } : { output } " )
1617+
1618+ logger .info ("Pre-tomo refinement complete. Updating grain orientations." )
1619+
1620+ # Step 2: Re-derive unique grain orientations from refined results
1621+ from em_pf_integration import update_unique_orientations_from_refinement
1622+ update_unique_orientations_from_refinement (topdir , nScans )
1623+
1624+ # Step 3: Run EM spot-ownership
1625+ logger .info ("Running EM spot-ownership for weighted sinograms" )
1626+ from em_pf_integration import run_em_spot_ownership
1627+
1628+ # Remove old sinogram files before EM writes new ones
1629+ for pattern in ["sinos_*.bin" , "omegas_*.bin" , "nrHKLs_*.bin" ]:
1630+ for old_f in glob .glob (pattern ):
1631+ os .remove (old_f )
1632+
1633+ run_em_spot_ownership (
1634+ topdir = topdir ,
1635+ n_scans = nScans ,
1636+ n_iter = getattr (args , 'emIter' , 10 ),
1637+ sigma_init = getattr (args , 'emSigmaInit' , 0.02 ),
1638+ sigma_min = getattr (args , 'emSigmaMin' , 0.005 ),
1639+ sigma_decay = getattr (args , 'emSigmaDecay' , 0.9 ),
1640+ n_opt_steps = getattr (args , 'emOptSteps' , 5 ),
1641+ lr = getattr (args , 'emLR' , 0.005 ),
1642+ refine_orientations = bool (getattr (args , 'emRefineOrientations' , 1 )),
1643+ use_refined_orientations = True ,
1644+ )
1645+
1646+ logger .info ("EM complete. Proceeding with tomo reconstruction on EM sinograms." )
1647+
15781648 # Run tomography if requested
15791649 if doTomo == 1 :
15801650 # Find sino file
0 commit comments