-
Notifications
You must be signed in to change notification settings - Fork 128
Expand file tree
/
Copy pathflat.py
More file actions
1126 lines (1007 loc) · 51.4 KB
/
flat.py
File metadata and controls
1126 lines (1007 loc) · 51.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
""" Core module for methods related to flat fielding
"""
import inspect
import numpy as np
import os
from scipy import interpolate
from pypeit import msgs
from pypeit.core import parse
from pypeit.core import pixels
from pypeit.core import tracewave
from scipy.interpolate import interp1d
from pypeit import debugger
from pypeit import utils
from pypeit.core import pydl
from matplotlib import pyplot as plt
import copy
from IPython import embed
import scipy
# NOTE: This is replaced by pypeit.core.flat.tweak_slit_edges
def tweak_slit_edges(slit_left_in, slit_righ_in, ximg_fit, normimg, tweak_slits_thresh,
tweak_slits_maxfrac):
"""
DOC THIS!
"""
tweak_left = False
tweak_righ = False
slit_left_out = np.copy(slit_left_in)
slit_righ_out = np.copy(slit_righ_in)
# How many pixels wide is the slit at each Y?
slitwidth = np.median(slit_righ_in - slit_left_in)
# Determine the maximum at the left and right end of the slit
ileft = (ximg_fit > 0.1) & (ximg_fit < 0.4)
irigh = (ximg_fit > 0.6) & (ximg_fit < 0.9)
# if (not np.any(ileft)) or (not np.any(irigh)):
# msgs.error('Cannot tweak slits because much of the slit is masked. You probably have a bad slit')
# tweak_dict = {'xleft': 0.0, 'xrigh': 0.0,
# 'norm_max_left': 0.0, 'norm_max_righ': 0.0,
# 'tweak_left': tweak_left, 'tweak_righ': tweak_righ}
# return slit_left_out, slit_righ_out, tweak_dict
#xleft = ximg_fit[ileft]
#xrigh = ximg_fit[irigh]
norm_max_left = normimg[ileft].max()
norm_max_righ = normimg[irigh].max()
msgs.info('Tweaking slit boundaries using slit illumination function')
step = 0.001
# march out from middle to find left edge
msgs.info('Left threshold = {:5.3f}'.format(tweak_slits_thresh * norm_max_left) +
' -- or {:5.3f}'.format(
100.0 * tweak_slits_thresh) + ' % of left side max of illumination function = {:5.3f}'.format(norm_max_left))
for xleft in np.arange(0.5, ximg_fit.min(), -step):
norm_now = np.interp(xleft, ximg_fit, normimg)
if (norm_now < tweak_slits_thresh * norm_max_left) & (xleft < tweak_slits_maxfrac):
slit_left_out += xleft * slitwidth
tweak_left = True
msgs.info('Tweaking left slit boundary by {:5.3f}'.format(100 * xleft) +
' %, or {:7.3f}'.format(xleft * slitwidth) + ' pixels')
if np.abs(xleft - tweak_slits_maxfrac) < 0.01:
msgs.warn(
'Left slit boundary tweak limited by maximum changed allowed by tweak_slits_maxfracn={:5.3f}'.format(
100.0 * tweak_slits_maxfrac) + ' %')
break
msgs.info('Right threshold = {:5.3f}'.format(tweak_slits_thresh * norm_max_righ) +
' -- or {:5.3f}'.format(
100.0 * tweak_slits_thresh) + ' % of right side max of illumination function = {:5.3f}'.format(norm_max_righ))
# march out from middle to find right edge
for xrigh in np.arange(0.5, ximg_fit.max(), step):
norm_now = np.interp(xrigh, ximg_fit, normimg)
if (norm_now < tweak_slits_thresh * norm_max_righ) & ((1.0 - xrigh) < tweak_slits_maxfrac):
slit_righ_out -= (1.0 - xrigh) * slitwidth
tweak_righ = True
msgs.info('Tweaking right slit boundary by {:5.3f}'.format(100 * (1.0 - xrigh)) +
' %, or {:7.3f}'.format((1.0 - xrigh) * slitwidth) + ' pixels')
if np.abs((1.0 - xrigh) - tweak_slits_maxfrac) < 0.01:
msgs.warn(
'Right slit boundary tweak limited by maximum changed allowed by tweak_slits_maxfracn={:5.3f}'.format(
100.0 * tweak_slits_maxfrac) + ' %')
break
tweak_dict = {'xleft': xleft, 'xrigh': xrigh,
'norm_max_left': norm_max_left, 'norm_max_righ': norm_max_righ,
'tweak_left': tweak_left, 'tweak_righ': tweak_righ}
return slit_left_out, slit_righ_out, tweak_dict
# NOTE: This was moved to pypeit.flatfield.FlatField.fit and refactored.
def fit_flat(flat, tilts_dict, tslits_dict_in, slit, inmask = None,
spec_samp_fine = 1.2, spec_samp_coarse = 50.0, spat_samp = 5.0, npoly = None, trim_edg = (3.0,3.0), pad =5.0,
tweak_slits = True, tweak_slits_thresh = 0.93, tweak_slits_maxfrac = 0.10, nonlinear_counts =1e10, debug=False):
""" Compute pixelflat and illumination flat from a flat field image.
Parameters
----------
flat : float ndarray, shape (nspec, nspat)
Flat field image in units of electrons.
tilts_dict: dict
Dictionary containing wavelength tilts image and other information indicating how wavelengths move across the slit
tslits_dict: dict
Dictionary with information on the slit boundaries
slit: int
Slit currently being considered
inmask: boolean ndarray, shape (nspec, nspat), default inmask = None, optional
Input mask for pixels not to be included in sky subtraction fits. True = Good (not masked), False = Bad (masked)
spec_samp_fine: float, default = 1.2, optional
bspline break point spacing in units of pixels for spectral fit to flat field blaze function.
spec_samp_coarse: float, default = 50.0, optional
bspline break point spacing in units of pixels for 2-d bspline-polynomial fit to flat field image residuals.
This should be a large number unless you are trying to fit a sky flat with lots of features.
spat_samp: float, default = 5.0, optional
Spatial sampling for spatial slit illumination function. This is the width of the median filter in pixels used to
determine the slit illumination function, and thus sets the minimum scale on which the illumination function will
have features.
trim_edg: tuple of floats (left_edge, right_edge), default (3,3), optional
indicates how many pixels to trim from left and right slit edges for creating the edgemask, which is used to mask
the edges from the initial (fine) spectroscopic fit to the blaze function.
pad: int, default = 5, optional
Padding window used to create expanded slitmask images used for re-determining slit boundaries. Tilts are also
computed using this expanded slitmask in cases the slit boundaries need to be moved outward.
npoly: int, default = None, optional
Order of polynomial for 2-d bspline-polynomial fit to flat field image residuals. The code determines the order of
these polynomials to each slit automatically depending on the slit width, which is why the default is None.
Do not attempt to set this paramter unless you know what you are doing.
tweak_slits: bool, default = True, optional
Slit edges will be tweaked such the left and right bounadaries intersect the location where the illumination
function falls below tweak_slits_thresh (see below) of its maximum value near the center (moving out from the center)
tweak_slits_thresh: float, default = 0.93, optional
If tweak_slits is True, this sets the illumination function threshold used to tweak the slits
tweak_slits_maxfrac: float, default = 0.10, optional
Maximum fractinoal amount (of slit width) allowed for each trimming the left and right slit boundaries, i.e. the
default is 10% which means slits would shrink by at most 20% (10% on each side)
debug: bool, default = False, optional
Show plots useful for debugging. This will block further execution of the code until the plot windows are closed.
Returns
-------
pixeflat: ndarray with same shape as flat
Pixelflat gives pixel-to-pixel variations of detector response. Values are centered about unity.
illumflat: ndarray with same shape as flat
Illumination flat gives variations of the slit illumination function across the spatial direction of the detect.
Values are centered about unity. The slit illumination function is computed by dividing out the spectral response and
collapsing out the spectral direction.
flat_model: ndarray with same shape as flat
Full 2-d model image of the input flat image in units of electrons. The pixelflat is defined to be flat/flat_model.
tilts: ndarray with same shape as flat
Tilts image fit for this slit evaluated using the new slit boundaries
thismask_out: ndarray with same shape as flat, bool
Boolean mask indicating which pixels are on the slit now with the new slit boundaries
slit_left_out: ndarray with shape (nspec,)
Tweaked left slit bounadries
slit_righ_out: ndarray with shape (nspec,)
Tweaked right slit bounadries
Notes
-----
Revision History
- 11-Mar-2005 First version written by Scott Burles.
- 2005-2018 Improved by J. F. Hennawi and J. X. Prochaska
- 3-Sep-2018 Ported to python by J. F. Hennawi and significantly improved
"""
shape = flat.shape
nspec = shape[0]
nspat = shape[1]
# Get the thismask_in and input slit bounadries from the tslits_dict
slit_left_in = tslits_dict_in['slit_left'][:,slit]
slit_righ_in = tslits_dict_in['slit_righ'][:,slit]
thismask_in = pixels.tslits2mask(tslits_dict_in) == slit
# Check for saturation of the flat. If there are not enough pixels do not attempt a fit
good_frac = np.sum(thismask_in & (flat < nonlinear_counts))/np.sum(thismask_in)
if good_frac < 0.5:
msgs.warn(msgs.newline() + 'Only {:4.2f}'.format(100*good_frac) + '% of the pixels on this slit are not saturated.' +
msgs.newline() + 'Consider raising nonlinear_counts={:5.3f}'.format(nonlinear_counts) +
msgs.newline() + 'Not attempting to flat field slit# {:d}'.format(slit))
return np.ones_like(flat), np.ones_like(flat), np.zeros_like(flat), tilts_dict['tilts'], thismask_in, slit_left_in, slit_righ_in
# Approximate number of pixels sampling each spatial pixel for this (original) slit.
npercol = np.fmax(np.floor(np.sum(thismask_in)/nspec),1.0)
# Demand at least 10 pixels per row (on average) per degree of the polynomial
if npoly is None:
npoly_in = 7
npoly = np.clip(npoly_in, 1, np.ceil(npercol/10.).astype(int))
#npoly = np.fmax(np.fmin(npoly_in, (np.ceil(npercol/10.)).astype(int)),1)
ximg_in, edgmask_in = pixels.ximg_and_edgemask(slit_left_in, slit_righ_in, thismask_in, trim_edg=trim_edg)
# Create a fractional position image ximg that encompasses the whole image, rather than just the thismask_in slit pixels
spat_img = np.outer(np.ones(nspec), np.arange(nspat)) # spatial position everywhere along image
slit_left_img = np.outer(slit_left_in, np.ones(nspat)) # left slit boundary replicated spatially
slitwidth_img = np.outer(slit_righ_in - slit_left_in, np.ones(nspat)) # slit width replicated spatially
ximg = (spat_img - slit_left_img)/slitwidth_img
# Create a wider slitmask image with shift pixels padded on each side
slitmask_pad = pixels.tslits2mask(tslits_dict_in, pad = pad)
thismask = (slitmask_pad == slit) # mask enclosing the wider slit bounadries
# Create a tilts image using this padded thismask, rather than using the original thismask_in slit pixels
tilts = tracewave.fit2tilts(tilts_dict['coeffs'], tilts_dict['func2d'], shape=shape)
piximg = tilts * (nspec-1)
pixvec = np.arange(nspec)
if inmask is None:
inmask = np.copy(thismask)
# Fit the spectral direction of the blaze. We do this in the log
log_flat = np.log(np.fmax(flat, 1.0))
inmask_log = ((flat > 1.0) & inmask)
log_ivar = inmask_log.astype(float)/0.5**2 # set errors to just be 0.5 in the log
# Flat field pixels for fitting spectral direction. Restrict to original slit pixels
fit_spec = thismask_in & inmask & np.invert(edgmask_in) #& (flat < nonlinear_counts)
nfit_spec = np.sum(fit_spec)
spec_frac = nfit_spec/np.sum(thismask_in)
msgs.info('Spectral fit of flatfield for {:}'.format(nfit_spec) + ' pixels')
if spec_frac < 0.5:
msgs.warn('Spectral flatfield fit is to only {:4.2f}'.format(100*spec_frac) + '% of the pixels on this slit.' +
msgs.newline() + ' Something appears to be wrong here')
isrt_spec = np.argsort(piximg[fit_spec])
pix_fit = piximg[fit_spec][isrt_spec]
log_flat_fit = log_flat[fit_spec][isrt_spec]
log_ivar_fit = log_ivar[fit_spec][isrt_spec]
inmask_log_fit = inmask_log[fit_spec][isrt_spec]
logrej = 0.5 # rejectino threshold for spectral fit in log(image)
# ToDo Figure out how to deal with the fits going crazy at the edges of the chip in spec direction
spec_set_fine, outmask_spec, specfit, _, exit_status = \
utils.bspline_profile(pix_fit, log_flat_fit, log_ivar_fit,np.ones_like(pix_fit), inmask = inmask_log_fit,
nord = 4, upper=logrej, lower=logrej,
kwargs_bspline = {'bkspace':spec_samp_fine},kwargs_reject={'groupbadpix':True, 'maxrej': 5})
# Debugging/checking spectral fit
if debug:
goodbk = spec_set_fine.mask
specfit_bkpt, _ = spec_set_fine.value(spec_set_fine.breakpoints[goodbk])
was_fit_and_masked = (outmask_spec == False)
plt.clf()
ax = plt.gca()
ax.plot(pix_fit,log_flat_fit, color='k', marker='o', markersize=0.4, mfc='k', fillstyle='full',
linestyle='None', label = 'all pixels')
ax.plot(pix_fit[was_fit_and_masked],log_flat_fit[was_fit_and_masked], color='red', marker='+',
markersize=1.5, mfc='red', fillstyle='full', linestyle='None', label='masked')
ax.plot(pix_fit, specfit, color='cornflowerblue', label = 'fit to blaze')
ax.plot(spec_set_fine.breakpoints[goodbk], specfit_bkpt, color='lawngreen', marker='o', markersize=2.0,
mfc='lawngreen', fillstyle='full', linestyle='None', label='bspline breakpoints')
ax.set_ylim((0.99*specfit.min(),1.01*specfit.max()))
plt.legend()
plt.xlabel('Spectral Pixel')
plt.ylabel('log(flat counts)')
plt.title('Spectral Fit for slit={:d}'.format(slit))
plt.show()
# # JXP
# plt.clf()
# ax = plt.gca()
# ax.scatter(pix_fit, log_flat_fit)
# embed(header='265 of flat.py')
# Evaluate and save
spec_model = np.ones_like(flat)
spec_model[thismask], _ = np.exp(spec_set_fine.value(piximg[thismask]))
norm_spec = np.ones_like(flat)
norm_spec[thismask] = flat[thismask]/np.fmax(spec_model[thismask], 1.0)
# Flat field pixels for fitting spatial direction
# Determine maximum counts in median filtered flat spectrum. Only fit pixels > 0.1 of this maximum
specfit_interp = interp1d(pix_fit, specfit, kind='linear', bounds_error=False, fill_value=-np.inf)
log_specfit = specfit_interp(pixvec)
specvec = np.exp(log_specfit)
spec_sm = utils.fast_running_median(specvec,np.fmax(np.ceil(0.10*nspec).astype(int),10))
spec_sm_max = spec_sm.max()
fit_spat = thismask & inmask & (spec_model > 1.0) & (spec_model > 0.1*spec_sm_max) & \
(norm_spec > 0.0) & (norm_spec < 1.7) #& (flat < nonlinear_counts)
nfit_spat = np.sum(fit_spat)
spat_frac = nfit_spat/np.sum(thismask)
msgs.info('Spatial fit to flatfield for {:}'.format(nfit_spec) + ' pixels')
if spat_frac < 0.5:
msgs.warn('Spatial flatfield fit is to only {:4.2f}'.format(100*spat_frac) + '% of the pixels on this slit.' +
msgs.newline() + ' Something apperas to be wrong here')
isrt_spat = np.argsort(ximg[fit_spat])
ximg_fit = ximg[fit_spat][isrt_spat]
norm_spec_fit = norm_spec[fit_spat][isrt_spat]
#norm_spec_ivar = np.ones_like(norm_spec_fit)/(spat_illum_thresh**2)
nfit_spat = np.sum(fit_spat)
slitwidth = np.median(slit_righ_in - slit_left_in) # How many pixels wide is the slit at each Y?
ximg_resln = spat_samp/slitwidth
med_width = (np.ceil(nfit_spat*ximg_resln)).astype(int)
normimg_raw = utils.fast_running_median(norm_spec_fit,med_width)
sig_res = np.fmax(med_width/20.0,0.5)
normimg = scipy.ndimage.filters.gaussian_filter1d(normimg_raw,sig_res, mode='nearest')
# mask regions where illumination function takes on extreme values
if np.any(np.invert(np.isfinite(normimg))):
msgs.error('Inifinities in slit illumination function computation normimg')
# Determine the breakpoint spacing from the sampling of the ximg
ximg_samp = np.median(ximg_fit - np.roll(ximg_fit,1))
ximg_1pix = 1.0/slitwidth
# Use breakpoints at a spacing of a 1/10th of a pixel, but do not allow a bsp smaller than the typical sampling
ximg_bsp = np.fmax(ximg_1pix/10.0, ximg_samp*1.2)
bsp_set = pydl.bspline(ximg_fit,nord=4, bkspace=ximg_bsp)
fullbkpt = bsp_set.breakpoints
spat_set, outmask_spat, spatfit, _, exit_status = \
utils.bspline_profile(ximg_fit, normimg, np.ones_like(normimg),np.ones_like(normimg),
nord=4,upper=5.0, lower=5.0,fullbkpt = fullbkpt)
# Evaluate and save
illumflat = np.ones_like(flat)
illumflat[thismask], _ = spat_set.value(ximg[thismask])
norm_spec_spat = np.ones_like(flat)
norm_spec_spat[thismask] = flat[thismask]/np.fmax(spec_model[thismask], 1.0)/np.fmax(illumflat[thismask],0.01)
if tweak_slits:
slit_left_out, slit_righ_out, tweak_dict = tweak_slit_edges(
slit_left_in, slit_righ_in, ximg_fit, normimg, tweak_slits_thresh, tweak_slits_maxfrac)
# Recreate all the quantities we need based on the tweaked slits
tslits_dict_out = copy.deepcopy(tslits_dict_in)
tslits_dict_out['slit_left'][:,slit] = slit_left_out
tslits_dict_out['slit_righ'][:,slit] = slit_righ_out
slitmask_out = pixels.tslits2mask(tslits_dict_out)
thismask_out = (slitmask_out == slit)
ximg_out, edgmask_out = pixels.ximg_and_edgemask(slit_left_out, slit_righ_out, thismask_out, trim_edg=trim_edg)
# Note that nothing changes with the tilts, since these were already extrapolated across the whole image.
else:
# Generate the edgemask using the original slit boundaries and thismask_in
slit_left_out = np.copy(slit_left_in)
slit_righ_out = np.copy(slit_righ_in)
thismask_out = thismask_in
ximg_out = ximg_in
# Add an approximate pixel axis at the top
if debug:
plt.clf()
ax = plt.gca()
ax.plot(ximg_fit, norm_spec_fit, color='k', marker='o', markersize=0.4, mfc='k', fillstyle='full',linestyle='None',
label = 'all pixels')
#ax.plot(ximg_fit[~imed], norm_spec_fit[~imed], color='darkred', marker='+',markersize=4.0, mfc='red',
# fillstyle='full', linestyle='None', label = 'masked')
#ax.plot(ximg_fit[imed], normfit[imed], color='orange', label = 'median spatial profile')
ax.plot(ximg_fit, spatfit, color='cornflowerblue', label = 'final slit illumination function')
ymin = np.fmax(0.8 * spatfit.min(), 0.5)
ymax = 1.2*spatfit.max()
ax.set_ylim((np.fmax(0.8 * spatfit.min(), 0.5), 1.2 * spatfit.max()))
ax.set_xlim(ximg_fit.min(), ximg_fit.max())
plt.vlines(0.0, ymin, ymax, color='lightgreen', linestyle=':', linewidth=2.0, label='original left edge',zorder=8)
plt.vlines(1.0,ymin,ymax, color='red',linestyle=':', linewidth = 2.0, label='original right edge',zorder=9)
if tweak_slits:
if tweak_dict['tweak_left']:
label = 'threshold = {:5.2f}'.format(tweak_slits_thresh) + ' % of max of left illumprofile'
plt.hlines(tweak_slits_thresh*tweak_dict['norm_max_left'], ximg_fit.min(), 0.5, color='lightgreen',
linewidth=3.0,label=label, zorder=10)
plt.vlines(tweak_dict['xleft'],ymin,ymax, color='lightgreen',linestyle='--', linewidth = 3.0, label='tweaked left edge',zorder=11)
if tweak_dict['tweak_righ']:
label = 'threshold = {:5.2f}'.format(tweak_slits_thresh) + ' % of max of right illumprofile'
plt.hlines(tweak_slits_thresh * tweak_dict['norm_max_righ'], 0.5, ximg_fit.max(), color='red', linewidth=3.0,
label=label, zorder=10)
plt.vlines(tweak_dict['xrigh'],ymin,ymax, color='red',linestyle='--', linewidth = 3.0, label='tweaked right edge',zorder=20)
plt.legend()
plt.xlabel('Normalized Slit Position')
plt.ylabel('Normflat Spatial Profile')
plt.title('Illumination Function Fit for slit={:d}'.format(slit))
plt.show()
msgs.info('Performing illumination + scattembedered light flat field fit')
# Flat field pixels for fitting spectral direction
isrt_spec = np.argsort(piximg[thismask_out])
pix_twod = piximg[thismask_out][isrt_spec]
ximg_twod = ximg_out[thismask_out][isrt_spec]
norm_twod = norm_spec_spat[thismask_out][isrt_spec]
fitmask = inmask[thismask_out][isrt_spec] & (np.abs(norm_twod - 1.0) < 0.30)
# Here we ignore the formal photon counting errors and simply assume that a typical error per pixel.
# This guess is somewhat aribtrary. We then set the rejection threshold with sigrej_illum
var_value = 0.01
norm_twod_ivar = fitmask.astype(float)/(var_value**2)
sigrej_illum = 4.0
poly_basis = pydl.fpoly(2.0*ximg_twod - 1.0, npoly).T
# Perform the full 2d fit now
twod_set, outmask_twod, twodfit, _ , exit_status = \
utils.bspline_profile(pix_twod, norm_twod, norm_twod_ivar,poly_basis,inmask = fitmask, nord = 4,
upper=sigrej_illum, lower=sigrej_illum,
kwargs_bspline = {'bkspace':spec_samp_coarse},kwargs_reject={'groupbadpix':True, 'maxrej': 10})
if debug:
resid = (norm_twod - twodfit)
badpix = np.invert(outmask_twod) & fitmask
goodpix = outmask_twod & fitmask
plt.clf()
ax = plt.gca()
ax.plot(pix_twod[goodpix], resid[goodpix], color='k', marker='o', markersize=0.2, mfc='k', fillstyle='full',linestyle='None',
label = 'good points')
ax.plot(pix_twod[badpix],resid[badpix], color='red', marker='+',markersize=0.5, mfc='red', fillstyle='full', linestyle='None', label='masked')
plt.hlines(sigrej_illum*var_value,pix_twod.min(),pix_twod.max(), color='lawngreen',linestyle='--',
label='rejection thresholds',zorder=10,linewidth=2.0)
plt.hlines(-sigrej_illum*var_value,pix_twod.min(),pix_twod.max(), color='lawngreen',linestyle='--',
zorder=10,linewidth=2.0)
ax.set_ylim((-0.05,0.05))
ax.set_xlim((pix_twod.min(), pix_twod.max()))
plt.legend()
plt.xlabel('Spectral Pixel')
plt.ylabel('Residuals from pixelflat 2-d fit')
plt.title('Spectral Residuals for slit={:d}'.format(slit))
plt.show()
plt.clf()
ax = plt.gca()
ax.plot(ximg_twod[goodpix], resid[goodpix], color='k', marker='o', markersize=0.2, mfc='k', fillstyle='full',
linestyle='None',
label='good points')
ax.plot(ximg_twod[badpix], resid[badpix], color='red', marker='+', markersize=0.5, mfc='red', fillstyle='full',
linestyle='None', label='masked')
plt.hlines(sigrej_illum * var_value, ximg_twod.min(), ximg_twod.max(), color='lawngreen', linestyle='--',
label='rejection thresholds', zorder=10,linewidth=2.0)
plt.hlines(-sigrej_illum * var_value, ximg_twod.min(), ximg_twod.max(), color='lawngreen', linestyle='--',
zorder=10,linewidth=2.0)
ax.set_ylim((-0.05, 0.05))
ax.set_xlim(-0.02, 1.02)
plt.legend()
plt.xlabel('Normalized Slit Position')
plt.ylabel('Residuals from pixelflat 2-d fit')
plt.title('Spatial Residuals for slit={:d}'.format(slit))
plt.show()
# Evaluate and save
twod_model = np.ones_like(flat)
twod_this = np.zeros_like(twodfit)
twod_this[isrt_spec] = twodfit
twod_model[thismask_out] = twod_this
# Compute all the final output images output
pixelflat = np.ones_like(flat)
flat_model = np.ones_like(flat)
flat_model[thismask_out] = twod_model[thismask_out]*np.fmax(illumflat[thismask_out],0.05)*np.fmax(spec_model[thismask_out],1.0)
pixelflat[thismask_out] = flat[thismask_out]/flat_model[thismask_out]
# ToDo Add some code here to treat the edges and places where fits go bad?
# Set the pixelflat to 1.0 wherever the flat was nonlinear
pixelflat[flat >= nonlinear_counts] = 1.0
# Do not apply pixelflat field corrections that are greater than 100% to avoid creating edge effects, etc.
# TODO Should we do the same for the illumflat??
#pixelflat = np.fmax(np.fmin(pixelflat, 2.0), 0.5)
pixelflat = np.clip(pixelflat, 0.5, 2.0)
return pixelflat, illumflat, flat_model, tilts, thismask_out, slit_left_out, slit_righ_out
# TODO: How much of the rest of this is used?
# JFH These routines below are all deprecated
def get_ampscale(datasec_img, msflat, namp):
""" Normalize the flat-field frame
Parameters
----------
datasec_img : ndarray
msflat : ndarray
Flat-field image
namp : int
Returns
-------
sclframe : ndarray
A frame to scale all amplifiers to the same counts at the amplifier borders
"""
sclframe = np.ones_like(msflat)
ampdone = np.zeros(namp, dtype=int) # 1 = amplifiers have been assigned a scale
ampdone[0]=1
while np.sum(ampdone) != namp:
abst, bbst, nbst, n0bst, n1bst = -1, -1, -1, -1, -1 # Reset the values for the most overlapping amplifier
for a in range(0, namp): # amplifier 'a' is always the reference amplifier
if ampdone[a] == 0: continue
for b in range(0, namp):
if ampdone[b] == 1 or a == b: continue
tstframe = np.zeros_like(msflat)
tstframe[np.where(datasec_img == a+1)] = 1
tstframe[np.where(datasec_img == b+1)] = 2
# Determine the total number of adjacent edges between amplifiers a and b
n0 = np.sum(tstframe[1:,:]-tstframe[:-1,:])
n1 = np.sum(tstframe[:,1:]-tstframe[:,:-1])
if (abs(n0)+abs(n1)) > nbst:
n0bst = n0
n1bst = n1
nbst = abs(n0)+abs(n1)
abst = a
bbst = b
# Determine the scaling factor for these two amplifiers
tstframe = np.zeros_like(msflat)
tstframe[np.where(datasec_img == abst+1)] = 1
tstframe[np.where(datasec_img == bbst+1)] = 2
if abs(n0bst) > abs(n1bst):
# The amplifiers overlap on the zeroth index
w = np.where(tstframe[1:,:]-tstframe[:-1,:] != 0)
sclval = np.median(msflat[w[0][0]+1, w[1]])/np.median(msflat[w[0][0], w[1]])
# msflat[w[0][0], w[1][0:50]] = 1.0E10
# msflat[w[0][0]-1, w[1][0:50]] = -1.0E10
# arutils.ds9plot(msflat)
if n0bst > 0:
# Then pixel w[0][0] falls on amplifier a
sclval = sclframe[w[0][0], w[1]] * sclval
else:
# pixel w[0][0] falls on amplifier b
sclval = sclframe[w[0][0]+1, w[1]] / sclval
else:
# The amplifiers overlap on the first index
w = np.where(tstframe[:,1:]-tstframe[:,:-1] != 0)
sclval = np.median(msflat[w[0], w[1][0]+1]/msflat[w[0], w[1][0]])
if n1bst > 0:
# Then pixel w[1][0] falls on amplifier a
sclval = sclframe[w[0], w[1][0]] * sclval
else:
# pixel w[1][0] falls on amplifier b
sclval = sclframe[w[0], w[1][0]+1] / sclval
# Finally, apply the scale factor thwe amplifier b
w = np.where(datasec_img == bbst+1)
sclframe[w] = np.median(sclval)
ampdone[bbst] = 1
return sclframe
def slit_profile(slit, mstrace, tilts, slordloc, srordloc, slitpix, pixwid,
ntckx=3, ntcky=20):
"""
Parameters
----------
slit : int
Slit (indexed from 0)
mstrace : ndarray
Flat field image
tilts : ndarray
Tilts image
slordloc : ndarray (nwave)
Left edge of the slit
srordloc : ndarray (nwave)
Right edge of the slit
slitpix : ndarray
Slit pixel image
pixwid : ndarray
Slit width array
ntckx : int, optional
Spacking of knots in spatial dimensions
ntcky : int, optional
Spacking of knots in spectral dimensions
Returns
-------
modvals : ndarray
Pixels in the slit
nrmvals : ndarray
Pixels in the slit
msblaze_slit : ndarray (nwave)
blazeext_slit : ndarray (nwave)
iextrap_slit : float
0 = Do not extrapolate
1 = Do extrapolate
"""
# TODO -- Refactor to use new bspline when it is ready
iextrap_slit = 0.
word = np.where(slitpix == slit+1)
if word[0].size <= (ntcky+1)*(2*pixwid[slit]+1):
msgs.warn("There are not enough pixels in slit {0:d}".format(slit+1))
return None, None, None, None, 1.
spatval = (word[1] - slordloc[word[0]])/(srordloc[word[0]] - slordloc[word[0]])
specval = tilts[word]
fluxval = mstrace[word]
# Only use pixels where at least half the slit is on the chip
cordloc = 0.5 * (slordloc[word[0]] + srordloc[word[0]])
wcchip = ((cordloc > 0.0) & (cordloc < mstrace.shape[1]-1.0))
# Derive the blaze function
wsp = np.where((spatval > 0.25) & (spatval < 0.75) & wcchip)
if wsp[0].size <= (ntcky+1)*(2*pixwid[slit]+1):
msgs.warn("There are not enough pixels in slit {0:d}".format(slit+1))
return None, None, None, None, 1.
if (np.min(word[0]) > 0) or (np.max(word[0]) < mstrace.shape[0]-1):
iextrap_slit = 1.0
tcky = np.linspace(min(0.0, np.min(specval[wsp])), max(1.0, np.max(specval[wsp])), ntcky)
tcky = tcky[np.where((tcky > np.min(specval[wsp])) & (tcky < np.max(specval[wsp])))]
srt = np.argsort(specval[wsp])
# Only perform a bspline if there are enough pixels for the specified knots
if tcky.size >= 2:
yb, ye = min(np.min(specval), tcky[0]), max(np.max(specval), tcky[-1])
bspline_par = dict(xmin=yb, xmax=ye, everyn=specval[wsp].size//tcky.size) # knots=tcky)
mask, blzspl = utils.robust_polyfit(specval[wsp][srt], fluxval[wsp][srt], 3, function='bspline',
sigma=5., maxone=False, bspline_par=bspline_par)
#xmin=yb, xmax=ye, everyn=specval[wsp].size//tcky.size) # knots=tcky)
blz_flat = utils.func_val(blzspl, specval, 'bspline')
msblaze_slit = utils.func_val(blzspl, np.linspace(0.0, 1.0, slordloc.shape[0]), 'bspline')
else:
mask, blzspl = utils.robust_polyfit(specval[wsp][srt], fluxval[wsp][srt], 2, function='polynomial',
sigma=5., maxone=False)
blz_flat = utils.func_val(blzspl, specval, 'polynomial')
msblaze_slit = utils.func_val(blzspl, np.linspace(0.0, 1.0, slordloc.shape[0]), 'polynomial')
iextrap_slit = 1.0
# Extract a spectrum of the trace frame
xext = np.arange(mstrace.shape[0])
yext = np.round(0.5 * (slordloc + srordloc)).astype(np.int)
wcc = np.where((yext > 0) & (yext < mstrace.shape[1] - 1.0))
blazeext_slit = np.zeros(slordloc.shape[0])
blazeext_slit[wcc[0]] = mstrace[(xext[wcc], yext[wcc],)]
if wcc[0].size != mstrace.shape[0]:
iextrap_slit = 1.0
# Calculate the slit profile
sprof_fit = fluxval / (blz_flat + (blz_flat == 0.0))
wch = np.where(wcchip)
tckx = np.linspace(min(0.0, np.min(spatval[wch])), max(1.0, np.max(spatval[wch])), ntckx)
tckx = tckx[np.where((tckx > np.min(spatval[wch])) & (tckx < np.max(spatval[wch])))]
srt = np.argsort(spatval[wch])
# Only perform a bspline if there are enough pixels for the specified knots
if tckx.size >= 1:
xb, xe = min(np.min(spatval), tckx[0]), max(np.max(spatval), tckx[-1])
bspline_par = dict(xmin=xb, xmax=xe, everyn=specval[wch].size//tckx.size) # knots=tcky)
mask, sltspl = utils.robust_polyfit(spatval[wch][srt], sprof_fit[wch][srt], 3, function='bspline',
sigma=5., maxone=False, bspline_par=bspline_par)
#xmin=xb, xmax=xe, everyn=spatval[wch].size//tckx.size) #, knots=tckx)
slt_flat = utils.func_val(sltspl, spatval, 'bspline')
sltnrmval = utils.func_val(sltspl, 0.5, 'bspline')
else:
mask, sltspl = utils.robust_polyfit(spatval[srt], sprof_fit[srt], 2, function='polynomial',
sigma=5., maxone=False)
slt_flat = utils.func_val(sltspl, spatval, 'polynomial')
sltnrmval = utils.func_val(sltspl, 0.5, 'polynomial')
iextrap_slit = 1.0
modvals = blz_flat * slt_flat
# Normalize to the value at the centre of the slit
nrmvals = blz_flat * sltnrmval
return modvals, nrmvals, msblaze_slit, blazeext_slit, iextrap_slit
def prep_ntck(pixwid, method='bspline', params=[20], get_slitprofile=True, ntcky=None):
"""
Prepare the number of knots for the bspline fitting
Parameters
----------
pixwid : int
Width of slit in pixels
settings : dict
Could probably replace this with the few parameters needed
ntcky : int, optional
Number of knots in spectral
Returns
-------
ntckx : int
ntcky : int
"""
# Set the number of knots in the spectral direction
if ntcky is None:
if method == 'bspline':
ntcky = params[0]
if params[0] < 1.0:
ntcky = int(1.0/ntcky)+0.5
else:
ntcky = 20
else:
if ntcky < 1.0:
ntcky = int(1.0 / ntcky) + 0.5
ntcky = int(ntcky)
# Set the number of knots in the spatial direction
# TODO -- Should this be set per slit/order?
ntckx = 2 * np.max(pixwid)
if not get_slitprofile:
# The slit profile is not needed, so just do the quickest possible fit
ntckx = 3
# Return
return ntckx, ntcky
def norm_slits(mstrace, datasec_img, lordloc, rordloc, pixwid,
slitpix, det, tilts, settings_argflag, settings_spect, ntcky=None):
""" Generate an image of the spatial slit profile.
DEPRECATED?
Parameters
----------
mstrace : ndarray
Master trace frame that is used to trace the slit edges.
datasec_img : ndarra
Image of amp positions
lordloc : ndarray
rordloc : ndarray
pixwid : int
slitpix : ndarray
Image of slit positions
det : int
tilts : ndarray
settings_argflag : dict
settings_spect : dict
ntcky : int, optional
Number of bspline knots in the spectral direction.
Returns
-------
slit_profile : ndarray
An image containing the slit profile
mstracenrm : ndarray
The input trace frame, normalized by the blaze function (but still contains the slit profile)
msblaze : ndarray
A model of the blaze function of each slit
blazeext : ndarray
The blaze function extracted down the centre of the slit
extrap_slit : ndarray
Mask indicating if a slit is well-determined (0) or poor (1). If the latter, the slit profile
and blaze function for those slits should be extrapolated or determined from another means
"""
dnum = parse.get_dnum(det)
nslits = lordloc.shape[1]
# First, determine the relative scale of each amplifier (assume amplifier 1 has a scale of 1.0)
if (settings_spect[dnum]['numamplifiers'] > 1) & (nslits > 1):
sclframe = get_ampscale(datasec_img, mstrace, settings_spect[dnum]['numamplifiers'])
# Divide the master flat by the relative scale frame
mstrace /= sclframe
mstracenrm = mstrace.copy()
msblaze = np.ones_like(lordloc)
blazeext = np.ones_like(lordloc)
slit_profiles = np.ones_like(mstrace)
extrap_slit = np.zeros(nslits, dtype=np.int)
# Tck
ntckx, ntcky = prep_ntck(pixwid, settings_argflag['reduce'], ntcky=ntcky)
# Calculate the slit and blaze profiles
msgs.work("Multiprocess this step")
for slit in range(nslits):
word = np.where(slitpix == slit+1)
modvals, nrmvals, msblaze_slit, blazeext_slit, iextrap_slit = slit_profile(
slit, mstrace, tilts, lordloc[:,slit], rordloc[:,slit], slitpix,
pixwid, ntckx=ntckx, ntcky=ntcky)
if modvals is None:
extrap_slit[slit] = 1.0
continue
else:
extrap_slit[slit] = iextrap_slit
#
if settings_argflag["reduce"]["slitprofile"]["perform"]:
# Leave slit_profiles as ones if the slitprofile is not being determined, otherwise, set the model.
slit_profiles[word] = modvals/nrmvals
mstracenrm[word] /= nrmvals
# Fill
msblaze[:,slit] = msblaze_slit
blazeext[:,slit] = blazeext_slit
# Return
return slit_profiles, mstracenrm, msblaze, blazeext, extrap_slit
#def slit_profile_pca(mstrace, tilts, msblaze, extrap_slit, slit_profiles,
# lordloc, rordloc, pixwid, slitpix, setup, debug=False):
# """ Perform a PCA analysis on the spatial slit profile and blaze function.
#
# Parameters
# ----------
# mstrace : ndarray
# tilts : ndarray
# msblaze : ndarray
# A model of the blaze function of each slit
# extrap_slit : ndarray
# Mask indicating if a slit is well-determined (0) or poor (1). If the latter, the slit profile
# and blaze function for those slits should be extrapolated or determined from another means
# slit_profiles : ndarray
# An image containing the slit profile
# lordloc : ndarray
# rordloc : ndarray
# pixwid : ndarray
# slitpix : ndarray
# setup : str
#
# Returns
# -------
# slit_profiles : ndarray
# An image containing the slit profile
# mstracenrm : ndarray
# The input trace frame, normalized by the blaze function (but still contains the slit profile)
# extrap_blz : ndarray
# A model of the blaze function of each slit
# """
# #################
# # Parameters to include in settings file
# fitfunc = "legendre"
# ordfit = 4
# ofit = [2, 3, 3, 2, 2]
# sordfit = 2
# sofit = [1, 3, 1]
# #################
#
# nslits = extrap_slit.size
# gds = np.where(extrap_slit == 0)
# maskord = np.where(extrap_slit == 1)[0]
# specfit = np.arange(mstrace.shape[0])
# nspec = np.max(pixwid)*10
# spatbins = np.linspace(-0.25, 1.25, nspec + 1)
# # Perform a PCA on the spectral (i.e. blaze) function
# blzmxval = np.ones((1, nslits))
# lorr = 0
# for o in range(0, nslits):
# # if extrap_slit[o] == 1:
# # continue
# # Find which pixels are on the slit
# wch = np.where((lordloc[:, o] > 0.0) &
# (rordloc[:, o] < mstrace.shape[1]-1.0))
# cordloc = np.round(0.5 * (lordloc[:, o] + rordloc[:, o])).astype(np.int)
# if wch[0].size < mstrace.shape[0]:
# # The entire order is not on the chip
# if cordloc[int(0.5*mstrace.shape[0])] < mstrace.shape[1]/2:
# lorr = -1 # Once a full order is found, go left
# continue
# else:
# lorr = +1 # Go right
# else:
# blzmxval[0, o] = np.median(mstrace[wch[0], cordloc[wch]])
# if lorr == -1:
# # A full order has been found, go back and fill in the gaps
# for i in range(1, o+1):
# wch = np.where((lordloc[:, o-i] > 0.0) &
# (rordloc[:, o-i] < mstrace.shape[1] - 1.0))
# # Calculate the previous order flux
# cordloc = np.round(0.5 * (lordloc[:, o-i+1] + rordloc[:, o-i+1])).astype(np.int)
# prval = mstrace[wch[0], cordloc[wch]]
# # Calculate the current order flux
# cordloc = np.round(0.5 * (lordloc[:, o-i] + rordloc[:, o-i])).astype(np.int)
# mnval = mstrace[wch[0], cordloc[wch]]
# wnz = np.where(prval != 0.0)
# blzmxval[0, o-i] = blzmxval[0, o-i+1] * np.median(mnval[wnz] / prval[wnz])
# lorr = 0
# elif lorr == +1:
# # Calibrate the current order with the previous one
# mnval = mstrace[wch[0], cordloc[wch]]
# cordloc = np.round(0.5 * (lordloc[:, o-1] + rordloc[:, o-1])).astype(np.int)
# prval = mstrace[wch[0], cordloc[wch]]
# wnz = np.where(prval != 0.0)
# blzmxval[0, o] = blzmxval[0, o-1] * np.median(mnval[wnz] / prval[wnz])
# lorr = 0
#
# # Check for nan values (i.e. when median is given a zero element array)
# blznan = np.isnan(blzmxval[0, :])
# if np.any(blznan):
# # Find the acceptable values and linearly interpolate
# blzx = np.arange(nslits)
# wnnan = np.where(~blznan)
# fblz = interpolate.interp1d(blzx[wnnan], blzmxval[0, wnnan],
# kind="linear", bounds_error=False, fill_value="extrapolate")
# blzmxval = fblz(blzx).reshape(blzmxval.shape)
# elif np.all(blznan):
# msgs.bug("All of the blaze values are NaN... time to debug")
# debugger.set_trace()
#
# # Calculate the mean blaze function of all good orders
# blzmean = np.mean(msblaze[:, gds[0]], axis=1)
# blzmean /= np.max(blzmean)
# blzmean = blzmean.reshape((blzmean.size, 1))
# msblaze /= blzmean
# msblaze /= blzmxval
# # Fit the blaze functions
# fitcoeff = np.ones((ordfit+1, nslits))
# for o in range(nslits):
# if extrap_slit[o] == 1:
# continue
# wmask = np.where(msblaze[:, o] != 0.0)[0]
# null, bcoeff = utils.robust_polyfit(specfit[wmask], msblaze[wmask, o],
# ordfit, function=fitfunc, sigma=2.0,
# minv=0.0, maxv=mstrace.shape[0])
# fitcoeff[:, o] = bcoeff
#
# lnpc = len(ofit) - 1
# xv = np.arange(mstrace.shape[0])
# blzval = utils.func_val(fitcoeff, xv, fitfunc,
# minv=0.0, maxv=mstrace.shape[0] - 1).T
# # Only do a PCA if there are enough good orders
# if np.sum(1.0 - extrap_slit) > ofit[0] + 1:
# # Perform a PCA on the tilts
# msgs.info("Performing a PCA on the spectral blaze function")
# ordsnd = np.arange(nslits) + 1.0
# xcen = xv[:, np.newaxis].repeat(nslits, axis=1)
# fitted, outpar = pca.basis(xcen, blzval, fitcoeff, lnpc, ofit, x0in=ordsnd, mask=maskord, skipx0=False,
# function=fitfunc)
# if not debug:
## arqa.pca_plot(slf, outpar, ofit, "Blaze_Profile", pcadesc="PCA of blaze function fits")
# pca.pca_plot(slf.setup, outpar, ofit, "Blaze_Profile",
# pcadesc="PCA of blaze function fits")
# # Extrapolate the remaining orders requested
# orders = 1.0 + np.arange(nslits)
# extrap_blz, outpar = pca.extrapolate(outpar, orders, function=fitfunc)
# extrap_blz *= blzmean
# extrap_blz *= blzmxval
# else:
# msgs.warn("Could not perform a PCA on the order blaze function" + msgs.newline() +
# "Not enough well-traced orders")
# msgs.info("Using direct determination of the blaze function instead")
# extrap_blz = msblaze*blzmean
#
# # Normalize the trace frame, but don't remove the slit profile
# mstracenrm = mstrace.copy()
# for o in range(nslits):
# word = np.where(slitpix == o+1)
# specval = tilts[word]
# blzspl = interpolate.interp1d(np.linspace(0.0, 1.0, mstrace.shape[0]), extrap_blz[:, o],
# kind="linear", fill_value="extrapolate")
# mstracenrm[word] /= blzspl(specval)
#
# # Now perform a PCA on the spatial (i.e. slit) profile
# # First generate the original model of the spatial slit profiles
# msslits = np.zeros((nspec, nslits))
# mskslit = np.ones((nspec, nslits))
# for o in range(nslits):
# if extrap_slit[o] == 1:
# continue
# word = np.where(slitpix == o+1)
# spatval = (word[1] + 0.5 - lordloc[:, o][word[0]]) /\
# (rordloc[:, o][word[0]] - lordloc[:, o][word[0]])
# groups = np.digitize(spatval, spatbins)
# modelw = slit_profiles[word]
# for mm in range(1, spatbins.size):
# tmp = modelw[groups == mm]
# if tmp.size != 0.0:
# msslits[mm - 1, o] = tmp.mean()
# else:
# mskslit[mm - 1, o] = 0.0
#
# # Calculate the spatial profile of all good orders
# sltmean = np.mean(msslits[:, gds[0]], axis=1)
# sltmean = sltmean.reshape((sltmean.size, 1))
# msslits /= (sltmean + (sltmean == 0))
#
# # Fit the spatial profiles
# spatfit = 0.5*(spatbins[1:]+spatbins[:-1])
# fitcoeff = np.ones((sordfit+1, nslits))
# for o in range(nslits):
# if extrap_slit[o] == 1:
# continue
# wmask = np.where(mskslit[:, o] == 1.0)[0]
# null, bcoeff = utils.robust_polyfit(spatfit[wmask], msslits[wmask, o],
# sordfit, function=fitfunc, sigma=2.0,
# minv=spatfit[0], maxv=spatfit[-1])
# fitcoeff[:, o] = bcoeff
#
# lnpc = len(sofit) - 1
# sltval = utils.func_val(fitcoeff, spatfit, fitfunc,
# minv=spatfit[0], maxv=spatfit[-1]).T
# # Only do a PCA if there are enough good orders
# if np.sum(1.0 - extrap_slit) > sofit[0] + 1:
# # Perform a PCA on the tilts
# msgs.info("Performing a PCA on the spatial slit profiles")
# ordsnd = np.arange(nslits) + 1.0
# xcen = spatfit[:, np.newaxis].repeat(nslits, axis=1)
# fitted, outpar = pca.basis(xcen, sltval, fitcoeff, lnpc, sofit, x0in=ordsnd, mask=maskord, skipx0=False,
# function=fitfunc)
# if not debug: