1313from jdaviz .core .custom_traitlets import IntHandleEmpty , FloatHandleEmpty
1414from jdaviz .core .marks import PluginLine
1515
16- from astropy .nddata import NDData , StdDevUncertainty , VarianceUncertainty , UnknownUncertainty
17- from specutils import Spectrum1D
16+ from astropy .modeling import models
17+ from astropy .nddata import StdDevUncertainty , VarianceUncertainty , UnknownUncertainty
18+ from astropy import units
1819from specreduce import tracing
1920from specreduce import background
2021from specreduce import extract
2122
2223__all__ = ['SpectralExtraction' ]
2324
25+ _model_cls = {'Spline' : models .Spline1D ,
26+ 'Polynomial' : models .Polynomial1D ,
27+ 'Legendre' : models .Legendre1D ,
28+ 'Chebyshev' : models .Chebyshev1D }
29+
2430
2531@tray_registry ('spectral-extraction' , label = "Spectral Extraction" ,
2632 viewer_requirements = ['spectrum' , 'spectrum-2d' ])
@@ -42,12 +48,15 @@ class SpectralExtraction(PluginTemplateMixin):
4248 * ``trace_type`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`):
4349 controls the type of trace to be generated.
4450 * ``trace_peak_method`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`):
45- only applicable if ``trace_type`` is set to ``Auto ``.
51+ only applicable if ``trace_type`` is not ``Flat ``.
4652 * :attr:`trace_pixel` :
47- pixel of the trace. If ``trace_type`` is set to ``Auto ``, then this
53+ pixel of the trace. If ``trace_type`` is not ``Flat ``, then this
4854 is the "guess" for the automated trace.
55+ * :attr:`trace_do_binning` :
56+ only applicable if ``trace_type`` is not ``Flat``. Bin the input data when fitting the
57+ trace.
4958 * :attr:`trace_bins` :
50- only applicable if ``trace_type`` is set to ``Auto ``.
59+ only applicable if ``trace_type`` is not ``Flat`` and ``trace_do_binning ``.
5160 * :attr:`trace_window` :
5261 full width of the trace.
5362 * :meth:`import_trace`
@@ -101,10 +110,12 @@ class SpectralExtraction(PluginTemplateMixin):
101110 trace_type_selected = Unicode ().tag (sync = True )
102111
103112 trace_pixel = FloatHandleEmpty (0 ).tag (sync = True )
113+ trace_order = IntHandleEmpty (3 ).tag (sync = True )
104114
105115 trace_peak_method_items = List ().tag (sync = True )
106116 trace_peak_method_selected = Unicode ().tag (sync = True )
107117
118+ trace_do_binning = Bool (True ).tag (sync = True )
108119 trace_bins = IntHandleEmpty (20 ).tag (sync = True )
109120 trace_window = IntHandleEmpty (0 ).tag (sync = True )
110121
@@ -205,7 +216,9 @@ def __init__(self, *args, **kwargs):
205216 self .trace_type = SelectPluginComponent (self ,
206217 items = 'trace_type_items' ,
207218 selected = 'trace_type_selected' ,
208- manual_options = ['Flat' , 'Auto' ])
219+ manual_options = ['Flat' , 'Polynomial' ,
220+ 'Legendre' , 'Chebyshev' ,
221+ 'Spline' ])
209222
210223 self .trace_peak_method = SelectPluginComponent (self ,
211224 items = 'trace_peak_method_items' ,
@@ -302,8 +315,10 @@ def __init__(self, *args, **kwargs):
302315 @property
303316 def user_api (self ):
304317 return PluginUserApi (self , expose = ('interactive_extract' ,
305- 'trace_dataset' , 'trace_type' , 'trace_peak_method' ,
306- 'trace_pixel' , 'trace_bins' , 'trace_window' ,
318+ 'trace_dataset' , 'trace_type' ,
319+ 'trace_order' , 'trace_peak_method' ,
320+ 'trace_pixel' ,
321+ 'trace_do_binning' , 'trace_bins' , 'trace_window' ,
307322 'import_trace' ,
308323 'export_trace' ,
309324 'bg_dataset' , 'bg_type' ,
@@ -407,7 +422,7 @@ def _update_plugin_marks(self, *args):
407422 sp1d = self .export_extract_spectrum (add_data = False )
408423 except Exception as e :
409424 # NOTE: ignore error, but will be raised when clicking ANY of the export buttons
410- # NOTE: KosmosTrace or manual background are often giving a
425+ # NOTE: FitTrace or manual background are often giving a
411426 # "background regions overlapped" error from specreduce
412427 self .ext_specreduce_err = repr (e )
413428 self .marks ['extract' ].clear ()
@@ -473,9 +488,9 @@ def marks(self):
473488 return self ._marks
474489
475490 @observe ('trace_dataset_selected' , 'trace_type_selected' ,
476- 'trace_trace_selected' , 'trace_offset' ,
491+ 'trace_trace_selected' , 'trace_offset' , 'trace_order' ,
477492 'trace_pixel' , 'trace_peak_method_selected' ,
478- 'trace_bins' , 'trace_window' , 'active_step' )
493+ 'trace_do_binning' , ' trace_bins' , 'trace_window' , 'active_step' )
479494 def _interaction_in_trace_step (self , event = {}):
480495 if not self .plugin_opened or not self ._do_marks :
481496 return
@@ -613,11 +628,14 @@ def import_trace(self, trace):
613628 if isinstance (trace , tracing .FlatTrace ):
614629 self .trace_type_selected = 'Flat'
615630 self .trace_pixel = trace .trace_pos
616- elif isinstance (trace , tracing .KosmosTrace ):
617- self .trace_type_selected = 'Auto'
631+ elif isinstance (trace , tracing .FitTrace ):
632+ self .trace_type_selected = trace . trace_model . __class__ . __name__ . strip ( '1D' )
618633 self .trace_pixel = trace .guess
619634 self .trace_window = trace .window
620635 self .trace_bins = trace .bins
636+ self .trace_do_binning = True
637+ if hasattr (trace .trace_model , 'degree' ):
638+ self .trace_order = trace .trace_model .degree
621639 elif isinstance (trace , tracing .ArrayTrace ): # pragma: no cover
622640 raise NotImplementedError (f"cannot import ArrayTrace into plugin. Use viz.load_trace instead" ) # noqa
623641 else : # pragma: no cover
@@ -644,22 +662,24 @@ def export_trace(self, add_data=False, **kwargs):
644662 # being able to load back into the plugin)
645663 orig_trace = self .trace_trace .selected_obj
646664 if isinstance (orig_trace , tracing .FlatTrace ):
647- trace = tracing .FlatTrace (self .trace_dataset .selected_obj . data ,
665+ trace = tracing .FlatTrace (self .trace_dataset .selected_obj ,
648666 orig_trace .trace_pos + self .trace_offset )
649667 else :
650- trace = tracing .ArrayTrace (self .trace_dataset .selected_obj . data ,
668+ trace = tracing .ArrayTrace (self .trace_dataset .selected_obj ,
651669 self .trace_trace .selected_obj .trace + self .trace_offset )
652670
653671 elif self .trace_type_selected == 'Flat' :
654- trace = tracing .FlatTrace (self .trace_dataset .selected_obj . data ,
672+ trace = tracing .FlatTrace (self .trace_dataset .selected_obj ,
655673 self .trace_pixel )
656674
657- elif self .trace_type_selected == 'Auto' :
658- trace = tracing .KosmosTrace (self .trace_dataset .selected_obj .data ,
659- guess = self .trace_pixel ,
660- bins = int (self .trace_bins ),
661- window = self .trace_window ,
662- peak_method = self .trace_peak_method_selected .lower ())
675+ elif self .trace_type_selected in _model_cls :
676+ trace_model = _model_cls [self .trace_type_selected ](degree = self .trace_order )
677+ trace = tracing .FitTrace (self .trace_dataset .selected_obj ,
678+ guess = self .trace_pixel ,
679+ bins = int (self .trace_bins ) if self .trace_do_binning else None ,
680+ window = self .trace_window ,
681+ peak_method = self .trace_peak_method_selected .lower (),
682+ trace_model = trace_model )
663683
664684 else :
665685 raise NotImplementedError (f"trace_type={ self .trace_type_selected } not implemented" )
@@ -674,7 +694,7 @@ def vue_create_trace(self, *args):
674694
675695 def _get_bg_trace (self ):
676696 if self .bg_type_selected == 'Manual' :
677- trace = tracing .FlatTrace (self .trace_dataset .selected_obj . data ,
697+ trace = tracing .FlatTrace (self .trace_dataset .selected_obj ,
678698 self .bg_trace_pixel )
679699 elif self .bg_trace_selected == 'From Plugin' :
680700 trace = self .export_trace (add_data = False )
@@ -736,15 +756,15 @@ def export_bg(self, **kwargs):
736756 trace = self ._get_bg_trace ()
737757
738758 if self .bg_type_selected == 'Manual' :
739- bg = background .Background (self .bg_dataset .selected_obj . data ,
759+ bg = background .Background (self .bg_dataset .selected_obj ,
740760 [trace ], width = self .bg_width )
741761 elif self .bg_type_selected == 'OneSided' :
742- bg = background .Background .one_sided (self .bg_dataset .selected_obj . data ,
762+ bg = background .Background .one_sided (self .bg_dataset .selected_obj ,
743763 trace ,
744764 self .bg_separation ,
745765 width = self .bg_width )
746766 elif self .bg_type_selected == 'TwoSided' :
747- bg = background .Background .two_sided (self .bg_dataset .selected_obj . data ,
767+ bg = background .Background .two_sided (self .bg_dataset .selected_obj ,
748768 trace ,
749769 self .bg_separation ,
750770 width = self .bg_width )
@@ -763,10 +783,7 @@ def export_bg_img(self, add_data=False, **kwargs):
763783 Whether to add the resulting image to the application, according to the options
764784 defined in the plugin.
765785 """
766- bg = self .export_bg (** kwargs )
767-
768- bg_spec = Spectrum1D (spectral_axis = self .bg_dataset .selected_obj .spectral_axis ,
769- flux = bg .bkg_image ()* self .bg_dataset .selected_obj .flux .unit )
786+ bg_spec = self .export_bg (** kwargs ).bkg_image ()
770787
771788 if add_data :
772789 self .bg_add_results .add_results_from_plugin (bg_spec , replace = True )
@@ -792,12 +809,15 @@ def export_bg_spectrum(self, add_data=False, **kwargs):
792809 Whether to add the resulting spectrum to the application, according to the options
793810 defined in the plugin.
794811 """
795- bg = self .export_bg (** kwargs )
796- spec = bg .bkg_spectrum ()
812+ spec = self .export_bg (** kwargs ).bkg_spectrum ()
797813
798814 if add_data :
799815 self .bg_spec_add_results .add_results_from_plugin (spec , replace = False )
800816
817+ # TEMPORARY: override spectral axis to be in pixels until properly supporting plotting
818+ # in wavelength/frequency
819+ spec ._spectral_axis = np .arange (len (spec .spectral_axis )) * units .pix
820+
801821 return spec
802822
803823 def vue_create_bg_spec (self , * args ):
@@ -813,10 +833,7 @@ def export_bg_sub(self, add_data=False, **kwargs):
813833 Whether to add the resulting image to the application, according to the options
814834 defined in the plugin.
815835 """
816- bg = self .export_bg (** kwargs )
817-
818- bg_sub_spec = Spectrum1D (spectral_axis = self .bg_dataset .selected_obj .spectral_axis ,
819- flux = bg .sub_image ()* self .bg_dataset .selected_obj .flux .unit )
836+ bg_sub_spec = self .export_bg (** kwargs ).sub_image ()
820837
821838 if add_data :
822839 self .bg_sub_add_results .add_results_from_plugin (bg_sub_spec , replace = True )
@@ -867,13 +884,13 @@ def export_extract(self, **kwargs):
867884 inp_sp2d = self ._get_ext_input_spectrum ()
868885
869886 if self .ext_type_selected == 'Boxcar' :
870- ext = extract .BoxcarExtract (inp_sp2d . data , trace , width = self .ext_width )
887+ ext = extract .BoxcarExtract (inp_sp2d , trace , width = self .ext_width )
871888 elif self .ext_type_selected == 'Horne' :
872- uncert = inp_sp2d . uncertainty if inp_sp2d .uncertainty is not None else VarianceUncertainty ( np . ones_like ( inp_sp2d . data )) # noqa
873- if not hasattr ( uncert , 'uncertainty_type' ):
874- uncert = StdDevUncertainty ( uncert )
875- image = NDData ( inp_sp2d .data , uncertainty = uncert )
876- ext = extract .HorneExtract (image , trace )
889+ if inp_sp2d .uncertainty is None :
890+ inp_sp2d . uncertainty = VarianceUncertainty ( np . ones_like ( inp_sp2d . data ))
891+ if not hasattr ( inp_sp2d . uncertainty , 'uncertainty_type' ):
892+ inp_sp2d .uncertainty = StdDevUncertainty ( inp_sp2d . uncert )
893+ ext = extract .HorneExtract (inp_sp2d , trace )
877894 else :
878895 raise NotImplementedError (f"extraction type '{ self .ext_type_selected } ' not supported" ) # noqa
879896
@@ -892,12 +909,9 @@ def export_extract_spectrum(self, add_data=False, **kwargs):
892909 extract = self .export_extract (** kwargs )
893910 spectrum = extract .spectrum
894911
895- # Specreduce returns a spectral axis in pixels, so we'll replace with input spectral_axis
896- # NOTE: this is currently disabled until proper handling of axes-limit linking between
897- # the 2D spectrum image (plotted in pixels) and a 1D spectrum (plotted in freq or
898- # wavelength) is implemented.
899-
900- # spectrum = Spectrum1D(spectral_axis=inp_sp2d.spectral_axis, flux=spectrum.flux)
912+ # TEMPORARY: override spectral axis to be in pixels until properly supporting plotting
913+ # in wavelength/frequency
914+ spectrum ._spectral_axis = np .arange (len (spectrum .spectral_axis )) * units .pix
901915
902916 if add_data :
903917 self .ext_add_results .add_results_from_plugin (spectrum , replace = False )
0 commit comments