13
13
from jdaviz .core .custom_traitlets import IntHandleEmpty , FloatHandleEmpty
14
14
from jdaviz .core .marks import PluginLine
15
15
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
18
19
from specreduce import tracing
19
20
from specreduce import background
20
21
from specreduce import extract
21
22
22
23
__all__ = ['SpectralExtraction' ]
23
24
25
+ _model_cls = {'Spline' : models .Spline1D ,
26
+ 'Polynomial' : models .Polynomial1D ,
27
+ 'Legendre' : models .Legendre1D ,
28
+ 'Chebyshev' : models .Chebyshev1D }
29
+
24
30
25
31
@tray_registry ('spectral-extraction' , label = "Spectral Extraction" ,
26
32
viewer_requirements = ['spectrum' , 'spectrum-2d' ])
@@ -42,12 +48,15 @@ class SpectralExtraction(PluginTemplateMixin):
42
48
* ``trace_type`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`):
43
49
controls the type of trace to be generated.
44
50
* ``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 ``.
46
52
* :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
48
54
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.
49
58
* :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 ``.
51
60
* :attr:`trace_window` :
52
61
full width of the trace.
53
62
* :meth:`import_trace`
@@ -101,10 +110,12 @@ class SpectralExtraction(PluginTemplateMixin):
101
110
trace_type_selected = Unicode ().tag (sync = True )
102
111
103
112
trace_pixel = FloatHandleEmpty (0 ).tag (sync = True )
113
+ trace_order = IntHandleEmpty (3 ).tag (sync = True )
104
114
105
115
trace_peak_method_items = List ().tag (sync = True )
106
116
trace_peak_method_selected = Unicode ().tag (sync = True )
107
117
118
+ trace_do_binning = Bool (True ).tag (sync = True )
108
119
trace_bins = IntHandleEmpty (20 ).tag (sync = True )
109
120
trace_window = IntHandleEmpty (0 ).tag (sync = True )
110
121
@@ -205,7 +216,9 @@ def __init__(self, *args, **kwargs):
205
216
self .trace_type = SelectPluginComponent (self ,
206
217
items = 'trace_type_items' ,
207
218
selected = 'trace_type_selected' ,
208
- manual_options = ['Flat' , 'Auto' ])
219
+ manual_options = ['Flat' , 'Polynomial' ,
220
+ 'Legendre' , 'Chebyshev' ,
221
+ 'Spline' ])
209
222
210
223
self .trace_peak_method = SelectPluginComponent (self ,
211
224
items = 'trace_peak_method_items' ,
@@ -302,8 +315,10 @@ def __init__(self, *args, **kwargs):
302
315
@property
303
316
def user_api (self ):
304
317
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' ,
307
322
'import_trace' ,
308
323
'export_trace' ,
309
324
'bg_dataset' , 'bg_type' ,
@@ -407,7 +422,7 @@ def _update_plugin_marks(self, *args):
407
422
sp1d = self .export_extract_spectrum (add_data = False )
408
423
except Exception as e :
409
424
# 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
411
426
# "background regions overlapped" error from specreduce
412
427
self .ext_specreduce_err = repr (e )
413
428
self .marks ['extract' ].clear ()
@@ -473,9 +488,9 @@ def marks(self):
473
488
return self ._marks
474
489
475
490
@observe ('trace_dataset_selected' , 'trace_type_selected' ,
476
- 'trace_trace_selected' , 'trace_offset' ,
491
+ 'trace_trace_selected' , 'trace_offset' , 'trace_order' ,
477
492
'trace_pixel' , 'trace_peak_method_selected' ,
478
- 'trace_bins' , 'trace_window' , 'active_step' )
493
+ 'trace_do_binning' , ' trace_bins' , 'trace_window' , 'active_step' )
479
494
def _interaction_in_trace_step (self , event = {}):
480
495
if not self .plugin_opened or not self ._do_marks :
481
496
return
@@ -613,11 +628,14 @@ def import_trace(self, trace):
613
628
if isinstance (trace , tracing .FlatTrace ):
614
629
self .trace_type_selected = 'Flat'
615
630
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' )
618
633
self .trace_pixel = trace .guess
619
634
self .trace_window = trace .window
620
635
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
621
639
elif isinstance (trace , tracing .ArrayTrace ): # pragma: no cover
622
640
raise NotImplementedError (f"cannot import ArrayTrace into plugin. Use viz.load_trace instead" ) # noqa
623
641
else : # pragma: no cover
@@ -644,22 +662,24 @@ def export_trace(self, add_data=False, **kwargs):
644
662
# being able to load back into the plugin)
645
663
orig_trace = self .trace_trace .selected_obj
646
664
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 ,
648
666
orig_trace .trace_pos + self .trace_offset )
649
667
else :
650
- trace = tracing .ArrayTrace (self .trace_dataset .selected_obj . data ,
668
+ trace = tracing .ArrayTrace (self .trace_dataset .selected_obj ,
651
669
self .trace_trace .selected_obj .trace + self .trace_offset )
652
670
653
671
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 ,
655
673
self .trace_pixel )
656
674
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 )
663
683
664
684
else :
665
685
raise NotImplementedError (f"trace_type={ self .trace_type_selected } not implemented" )
@@ -674,7 +694,7 @@ def vue_create_trace(self, *args):
674
694
675
695
def _get_bg_trace (self ):
676
696
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 ,
678
698
self .bg_trace_pixel )
679
699
elif self .bg_trace_selected == 'From Plugin' :
680
700
trace = self .export_trace (add_data = False )
@@ -736,15 +756,15 @@ def export_bg(self, **kwargs):
736
756
trace = self ._get_bg_trace ()
737
757
738
758
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 ,
740
760
[trace ], width = self .bg_width )
741
761
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 ,
743
763
trace ,
744
764
self .bg_separation ,
745
765
width = self .bg_width )
746
766
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 ,
748
768
trace ,
749
769
self .bg_separation ,
750
770
width = self .bg_width )
@@ -763,10 +783,7 @@ def export_bg_img(self, add_data=False, **kwargs):
763
783
Whether to add the resulting image to the application, according to the options
764
784
defined in the plugin.
765
785
"""
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 ()
770
787
771
788
if add_data :
772
789
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):
792
809
Whether to add the resulting spectrum to the application, according to the options
793
810
defined in the plugin.
794
811
"""
795
- bg = self .export_bg (** kwargs )
796
- spec = bg .bkg_spectrum ()
812
+ spec = self .export_bg (** kwargs ).bkg_spectrum ()
797
813
798
814
if add_data :
799
815
self .bg_spec_add_results .add_results_from_plugin (spec , replace = False )
800
816
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
+
801
821
return spec
802
822
803
823
def vue_create_bg_spec (self , * args ):
@@ -813,10 +833,7 @@ def export_bg_sub(self, add_data=False, **kwargs):
813
833
Whether to add the resulting image to the application, according to the options
814
834
defined in the plugin.
815
835
"""
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 ()
820
837
821
838
if add_data :
822
839
self .bg_sub_add_results .add_results_from_plugin (bg_sub_spec , replace = True )
@@ -867,13 +884,13 @@ def export_extract(self, **kwargs):
867
884
inp_sp2d = self ._get_ext_input_spectrum ()
868
885
869
886
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 )
871
888
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 )
877
894
else :
878
895
raise NotImplementedError (f"extraction type '{ self .ext_type_selected } ' not supported" ) # noqa
879
896
@@ -892,12 +909,9 @@ def export_extract_spectrum(self, add_data=False, **kwargs):
892
909
extract = self .export_extract (** kwargs )
893
910
spectrum = extract .spectrum
894
911
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
901
915
902
916
if add_data :
903
917
self .ext_add_results .add_results_from_plugin (spectrum , replace = False )
0 commit comments