@@ -814,7 +814,7 @@ def find_solution(init_models, config, peaks=None, peak_weights=None,
814814
815815 # We allow this continue if we only matched 1 line but only 1 line is
816816 # expected
817- if len (matched ) > 1 : # or (len(matched) * nlines_expected == 1 and len(peaks) <= 2):
817+ if len (matched ) > 1 or (len (matched ) * nlines_expected == 1 and len (peaks ) <= 2 ):
818818 m_init = models .Chebyshev1D (
819819 degree = min (config ["order" ], (len (matched ) + 1 ) // 2 ),
820820 domain = domain )
@@ -829,59 +829,69 @@ def find_solution(init_models, config, peaks=None, peak_weights=None,
829829 matched_peaks = peaks [matched ]
830830 matched_arc_lines = arc_lines [matches [matched ]]
831831 m_final = fit_it (m_init , matched_peaks , matched_arc_lines )
832- print ( repr (m_final ) )
832+ dw = abs ( np . diff (m_final ( m_final . domain ))[ 0 ] / np . diff ( m_final . domain )[ 0 ] )
833833 #for p, l in zip(matched_peaks, matched_arc_lines):
834834 # print(f"{p:.2f} => {l:.2f}")
835835
836- # We're close to the correct solution, perform a KDFit
837- m_init = models .Chebyshev1D (m_final .degree , domain = domain )
838- for p , v in zip (m_final .param_names , m_final .parameters ):
839- setattr (m_init , p , v )
840- dw = abs (np .diff (m_final (m_final .domain ))[0 ] / np .diff (m_final .domain )[0 ])
841- fit_it = matching .KDTreeFitter (sigma = 2 * abs (dw ), maxsig = 5 ,
842- k = k , method = 'Nelder-Mead' )
843- m_final = fit_it (m_init , peaks , arc_lines , in_weights = peak_weights ,
844- ref_weights = arc_weights )
845- logit (f'{ repr (m_final )} { fit_it .statistic } ' )
846-
847- # And then recalculate the matches
848- match_radius = 4 * fwidth * abs (m_final .c1 ) / len_data # 2*fwidth pixels
849- try :
850- matched = matching .match_sources (m_final (peaks ), arc_lines ,
851- radius = match_radius )
852- incoords , outcoords = zip (* [(peaks [i ], arc_lines [m ])
853- for i , m in enumerate (matched ) if m > - 1 ])
854- # Probably incoords and outcoords as defined here should go to
855- # the interactive fitter, but cull to derive the "best" model
856- fit1d = fit_1D (outcoords , points = incoords , function = "chebyshev" ,
857- order = min (m_final .degree , (len (incoords ) + 1 ) // 2 ),
858- domain = m_final .domain ,
859- niter = config ["niter" ], sigma_lower = config ["lsigma" ],
860- sigma_upper = config ["hsigma" ])
861- fit1d .image = np .asarray (outcoords )
862- except ValueError :
863- log .warning ("Line-matching failed" )
864- continue
865- nmatched = np .sum (~ fit1d .mask )
866- logit (f"{ filename } { repr (fit1d .model )} { nmatched } { fit1d .rms } " )
867-
868- # A posteriori check that the fit is within the bounds
869- # (LinearLSQFitter does not allow bounds so we can't force
870- # the model to be bounded).
871- try :
872- for p , v in zip (fit1d .model .param_names , fit1d .model .parameters ):
873- bounds = model_bounds .get (p , (- np .inf , np .inf ))
874- new_bounds = (np .asarray (bounds ) +
875- np .array ([- 0.5 , 0.5 ]) * np .diff (bounds )[0 ])
876- assert new_bounds [0 ] <= v <= new_bounds [1 ]
877- except AssertionError :
878- for p , v in zip (fit1d .model .param_names , fit1d .model .parameters ):
879- bounds = model_bounds .get (p , (- np .inf , np .inf ))
880- new_bounds = (np .asarray (bounds ) +
881- np .array ([- 0.5 , 0.5 ]) * np .diff (bounds )[0 ])
882- print (p , v , new_bounds )
883- print ("*** REJECTED ***" )
884- continue
836+ if len (matched ) > 1 :
837+ # We're close to the correct solution, perform a KDFit
838+ m_init = models .Chebyshev1D (m_final .degree , domain = domain )
839+ for p , v in zip (m_final .param_names , m_final .parameters ):
840+ setattr (m_init , p , v )
841+ fit_it = matching .KDTreeFitter (sigma = 2 * abs (dw ), maxsig = 5 ,
842+ k = k , method = 'Nelder-Mead' )
843+ m_final = fit_it (m_init , peaks , arc_lines , in_weights = peak_weights ,
844+ ref_weights = arc_weights )
845+ logit (f'{ repr (m_final )} { fit_it .statistic } ' )
846+
847+ # And then recalculate the matches
848+ match_radius = 4 * fwidth * abs (m_final .c1 ) / len_data # 2*fwidth pixels
849+ try :
850+ matched = matching .match_sources (m_final (peaks ), arc_lines ,
851+ radius = match_radius )
852+ incoords , outcoords = zip (* [(peaks [i ], arc_lines [m ])
853+ for i , m in enumerate (matched ) if m > - 1 ])
854+ # Probably incoords and outcoords as defined here should go to
855+ # the interactive fitter, but cull to derive the "best" model
856+ fit1d = fit_1D (outcoords , points = incoords , function = "chebyshev" ,
857+ order = min (m_final .degree , (len (incoords ) + 1 ) // 2 ),
858+ domain = m_final .domain ,
859+ niter = config ["niter" ], sigma_lower = config ["lsigma" ],
860+ sigma_upper = config ["hsigma" ])
861+ fit1d .image = np .asarray (outcoords )
862+ except ValueError :
863+ log .warning ("Line-matching failed" )
864+ continue
865+ nmatched = np .sum (~ fit1d .mask )
866+ logit (f"{ filename } { repr (fit1d .model )} { nmatched } { fit1d .rms } " )
867+
868+ # A posteriori check that the fit is within the bounds
869+ # (LinearLSQFitter does not allow bounds so we can't force
870+ # the model to be bounded). We allow the tolerances to be twice
871+ # as large as they were during the piecewise fit, as this seems
872+ # to work well empirically.
873+ try :
874+ for p , v in zip (fit1d .model .param_names , fit1d .model .parameters ):
875+ bounds = model_bounds .get (p , (- np .inf , np .inf ))
876+ new_bounds = (np .asarray (bounds ) +
877+ np .array ([- 0.5 , 0.5 ]) * np .diff (bounds )[0 ])
878+ assert new_bounds [0 ] <= v <= new_bounds [1 ]
879+ except AssertionError :
880+ for p , v in zip (fit1d .model .param_names , fit1d .model .parameters ):
881+ bounds = model_bounds .get (p , (- np .inf , np .inf ))
882+ new_bounds = (np .asarray (bounds ) +
883+ np .array ([- 0.5 , 0.5 ]) * np .diff (bounds )[0 ])
884+ print (p , v , new_bounds )
885+ continue
886+ else :
887+ # Hack a fit1D object
888+ fit1d = fit_1D (np .arange (5 ), function = "chebyshev" , order = 1 ,
889+ niter = 0 )
890+ fit1d ._models = m_final
891+ fit1d .image = np .array (matched_arc_lines )
892+ fit1d .points = np .array (matched_peaks )
893+ fit1d .mask = np .array ([False ], dtype = bool )
894+ nmatched = 1
885895
886896 # Wavelength solution models need to be monotonic. Make that check.
887897 waves = fit1d .evaluate (np .arange (len_data ))
@@ -1023,7 +1033,7 @@ def perform_piecewise_fit(model, peaks, arc_lines, pixel_start, kdsigma,
10231033 # automatically removes old (bad) match
10241034 matches [i ] = m
10251035 found_new_matches = True
1026- print (f"Pixel { p } => was { m_init (p ):.4f} now { m_this (p ):.4f} { arc_lines [m ]} " )
1036+ # print(f"Pixel {p} => was {m_init(p):.4f} now {m_this(p):.4f} {arc_lines[m]}")
10271037 try :
10281038 p_lo = peaks [matches > - 1 ].min ()
10291039 except ValueError :
@@ -1304,14 +1314,15 @@ def create_pdf_plot(input_data, peaks, arc_lines, title="",
13041314 data = input_data ["spectrum" ]
13051315 spacing = 0.01
13061316 vert_align = "bottom"
1307- xmin , xmax = input_data ["init_models" ][0 ].domain
1317+ init_model = input_data ["init_models" ][0 ]
1318+ xmin , xmax = init_model .domain
13081319 pixels = np .arange (xmin , xmax + 1 )
13091320 data_max = data .max ()
13101321 spacing *= data_max
13111322 fig , ax = plt .subplots ()
13121323 ax .plot (pixels , data , 'b-' )
13131324 ax .set_ylim (0 , data_max * 1.1 )
1314- if len (arc_lines ) and np . diff ( arc_lines )[ 0 ] / np . diff ( peaks )[ 0 ] < 0 :
1325+ if len (arc_lines ) and init_model ( xmin ) > init_model ( xmax ) :
13151326 ax .set_xlim (xmax + 1 , xmin - 1 )
13161327 else :
13171328 ax .set_xlim (xmin - 1 , xmax + 1 )
0 commit comments