-
Notifications
You must be signed in to change notification settings - Fork 0
/
cleanir.py.2017-Mar-11
executable file
·1357 lines (1162 loc) · 53.3 KB
/
cleanir.py.2017-Mar-11
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
#!/usr/bin/env python
#
# 2007-Jul-08 - Andrew W Stephens - alpha version
# 2007-Jul-09 - AWS - beta version
# 2007-Jul-10 - AWS - move most operations to cleanquad function
# 2007-Jul-11 - AWS - use stddev to decide whether to keep orig quad
# 2007-Jul-14 - AWS - generalized code to allow different pattern sizes
# 2007-Jul-18 - AWS - fix bug generating index arrays
# 2007-Jul-20 - AWS - add quadrant bias-level normalization
# 2007-Jul-23 - AWS - add force option
# 2007-Aug-06 - AWS - f/6 spectroscopy mode: use top & bottom for pattern
# 2007-Aug-22 - AWS - add -a flag to use all pixels
# 2008-Jan-11 - AWS - check for available image extensions
# 2008-Feb-05 - AWS - don't close input file until the end (req'd if next>2)
# 2008-Oct-02 - AWS - don't write pattern unless given the -p flag
# 2009-May-03 - AWS - use conformant default output file name
# 2009-May-13 - AWS - verify FITS header (old images have unquoted release date)
# 2009-May-22 - AWS - output full-frame pattern
# 2009-May-22 - AWS - improve quadrant bias normalization
# 2009-May-23 - AWS - add optional sky frame
# 2009-May-26 - AWS - add user-supplied bias offset correction
# 2009-Jul-04 - AWS - do not try to bias correct spectral flats
# 2009-Oct-24 - AWS - add basic row filtering
# 2009-Nov-06 - AWS - ignore bad pixels flagged in DQ extension
# 2009-Nov-08 - AWS - use mode for quadrant bias level normalization
# 2009-Nov-12 - AWS - use sigma-clipped stddev to judge quality of bias normalization
# 2009-Nov-17 - AWS - fit a Gaussian to the sky pixel distribution for bias norm.
# 2010-Feb-02 - AWS - sky subtract before quadrant normalization
# 2010-Feb-18 - AWS - add sigma-clipping to row filtering
# 2010-Apr-09 - AWS - only check for gcal status if OBSTYPE = FLAT
# 2010-Apr-10 - AWS - accept list input
# 2010-Apr-13 - AWS - minor tweak of the spectroscopic regions
# 2010-Jul-11 - AWS - allow images sizes which are multiples of the pattern size
# 2010-Oct-08 - AWS - option to read in bad pixel mask (e.g. object mask from nisky)
# 2010-Oct-10 - AWS - change the -g flag to take arguments
# 2010-Oct-11 - AWS - pad GNIRS images (2 row at the top)
# 2010-Oct-12 - AWS - GNIRS row filtering using an 8-pixel wide kernel
# 2010-Dec-21 - AWS - add grid filter
# 2010-Dec-28 - AWS - select GNIRS pattern region based on camera & slit
# 2011-Feb-03 - AWS - use extension 2 for nsprepared GNIRS data
# 2011-Feb-05 - AWS - add input glob expansion
# 2011-May-05 - AWS - output 32-bit files
# 2011-Jun-17 - AWS - catch modified GNIRS XD decker name
# 2012-Mar-29 - AWS - use the mode instead of median for pattern determination
# 2012-May-17 - AWS - do not modify input flag values
# 2013-Jun-16 - AWS - allow processing of non-standard FITS (with a warning)
# 2013-Jun-22 - AWS - add option to ignore DQ plane
# 2013-Jun-22 - AWS - use whole array if SKYIMAGE header keyword is present
# 2016-Jul-21 - AWS - propagate bad pixels through to the row filtering
# 2016-Oct-28 - AWS - switch pyfits -> astropy.io.fits
# 2017-Feb-01 - AWS - add option to reject pixels above a threshold before calculating pattern
# 2017-Mar-11 - AWS - fix calculation of stddev after quadrant adjustment
# To Do:
# Add option to fit an N order surface before calculating the pattern
# Perform FFT on each row to decide if it has pattern noise
# -> use this to set the region(s) for pattern noise removal
# -> see for example N20140402S0001-90.fits
# GNIRS: Mask out padding when a DQ or pixel mask is available
# Detect and mask out objects before doing any calculations
# Check if run before
# Properly handle images < 1024 x 1024
# Specification of image section to use to calculate pattern
# Specification of image section affected by pattern
# Look at stddev of each row to identify which have pattern noise
# --------------------------------------------------------------------------------------------------
from astropy.io import fits
import datetime
import getopt
import glob
import matplotlib.pyplot as pyplot
import numpy
import os
from scipy.optimize import leastsq
import string
import sys
version = '2017-Mar-11'
# ---------------------------------------------------------------------------------------------------
def usage():
print ''
print 'NAME'
print ' cleanir.py - filter pattern noise out of NIRI and GNIRS frames\n'
print 'SYNOPSIS'
print ' cleanir.py [options] infile/list\n'
print 'DESCRIPTION'
print ' This script assumes that the NIRI/GNIRS pattern noise in a quadrant'
print ' can be represented by a fixed pattern which is repeated over the'
print ' entire quadrant. The default size for this pattern is 16 pixels'
print ' wide and 4 pixels high (which may be changed via the -x and -y'
print ' flags). The pattern is determined for each quadrant by taking the'
print ' mode of the pixel value distribution at each position in the'
print ' pattern. Once the pattern has been determined for a quadrant'
print ' it is replicated to cover the entire quadrant and subtracted,'
print ' and the mean of the pattern is added back to preserve flux.'
print ' The standard deviation of all the pixels in the quadrant is'
print ' compared to that before the pattern subtraction, and if no'
print ' reduction was achieved the subtraction is undone. The pattern'
print ' subtraction may be forced via the -f flag. This process is'
print ' repeated for all four quadrants and the cleaned frame is written'
print ' to c<infile> (or the file specified with the -o flag). The'
print ' pattern derived for each quadrant may be saved with the -p flag.'
print ''
print ' Pattern noise is often accompanied by an offset in the bias'
print ' values between the four quadrants. One may want to use the'
print ' -q flag to try to remove this offset. This attempts to match'
print ' the iteratively determined median value of each quadrant.'
print ' This method works best with sky subtraction (i.e. with the -s'
print ' flag), and does not work well if there are large extended objects'
print ' in the frame. By default the median is determined from the'
print ' entire frame, although the -c flag will only use a central'
print ' portion of the image. Note that the derived quadrant offsets'
print ' will be applied to the output pattern file.'
print ''
print ' Removing the pattern from spectroscopy is more difficult because'
print ' of many vertical sky lines. By default NIRI f/6 spectroscopy with'
print ' the 2-pixel or blue slits (which do not fill the detector), uses the'
print ' empty regions at the bottom (1-272) and top (720-1024) of the'
print ' array for measuring the pattern. This is not possible for other'
print ' modes of spectroscopy where the spectrum fills the detector.'
print ' For these modes it is best to do sky subtraction before pattern'
print ' removal. The quickest method is to pass a sky frame (or an offset'
print ' frame) via the -s flag. The manual method is to generate and'
print ' subtract the sky, determine and save the pattern via the -p flag,'
print ' then subtract the pattern from the original image. One may use'
print ' the -a flag to force using all of the pixels for the pattern'
print ' determination. If the SKYIMAGE FITS header keyword is present'
print ' it is assumed that the sky has already been subtracted and all'
print ' pixels will be used for the pattern determination.'
print ''
print ' Note that you may use glob expansion in infile, however, the'
print ' entire string must then be quoted or any pattern matching'
print ' characters (*,?) must be escaped with a backslash.'
print ''
print 'OPTIONS'
print ' -a : use all pixels for pattern determination'
print ' -b <badpixelmask> : specify a bad pixel mask (overrides DQ plane)'
print ' -c <frac> : use central <fraction> of image for bias adjustment [1]'
print ' -d <dir> : specify an input data directory'
print ' -f : force cleaning of all quads even if stddev does not decrease'
print ' -g # : graph results (0=none, 1=pattern, 2=offsets, 3=both)'
print ' -h <val> : ignore values above <val> in pattern determination'
print ' -m : use median for quadrant normalization (instead of fitting a Gaussian)'
print ' --nodq : ignore the DQ plane'
print ' -o <file> : write output to <file> (instead of c<infile>)'
print ' -p <file> : write full-frame pattern to <file>'
print ' -q : adjust quadrant offsets'
print ' -r : row filtering (useful for GNIRS XD spectra)'
print ' -s <sky> : sky frame to help in pattern recognition'
print ' -t : apply test grid filter before normalizing quadrants'
print ' -v : verbose debugging output'
print ' -x <size> : set pattern x size in pix [16]'
print ' -y <size> : set pattern y size in pix [4]\n'
print 'VERSION'
print ' ', version
print ''
raise SystemExit
#-----------------------------------------------------------------------
def main():
global allpixels, applygridfilter
global bad, badmask, bias1, bias2, bias3, bias4, biasadjust
global cfrac, datadir, force, graph, high
global output, patternfile, pxsize, pysize, quadmedian, rowfilter
global savepattern, skyfile, subtractsky, subtractrowmedian
global usedq, verbose
try:
opts, args = getopt.getopt(sys.argv[1:], 'ab:c:d:fg:h:mo:p:qrs:tx:y:v',
['nodq', 'q1=','q2=','q3=','q4='])
except getopt.GetoptError, err:
print str(err)
usage()
sys.exit(2)
if (len(args)) != 1:
usage()
nargs = len(sys.argv[1:])
nopts = len(opts)
allpixels = False
applygridfilter = False
bad = -9.e6 # value assigned to bad pixels
badmask = 'DQ'
bias1 = 0.0
bias2 = 0.0
bias3 = 0.0
bias4 = 0.0
biasadjust = False
cfrac = 1.0 # use whole image
datadir = ''
force = False
graph = 0
high = None
output = 'default'
patternfile = ''
pxsize = 16
pysize = 4
quadmedian = False
rowfilter = False
savepattern = False
skyfile = ''
subtractsky = False
subtractrowmedian = False
usedq = True
verbose = False
for o, a in opts:
if o in ('-a'): # force using all pixels for pattern determination
allpixels = True
elif o in ('-b'):
badmask = a
elif o in ('-c'): # use central fraction for bias normalization
cfrac = float(a)
elif o in ('-d'): # input data directory
datadir = a
elif o in ('-f'): # force pattern subtraction in every quadrant
force = True
elif o in ('-g'): # graph results
graph = int(a)
elif o in ('-h'): # high value
high = int(a)
elif o in ('-o'): # specify cleaned output file
output = a
elif o in ('-m'):
quadmedian = True
elif o in ('--nodq'):
usedq = False
elif o in ('-p'): # write pattern file
patternfile = a
savepattern = True
elif o in ('-q'): # try to adjust quadrant bias values
biasadjust = True
elif o in ('--q1'): # bias offset for quadrant 1
bias1 = float(a)
elif o in ('--q2'):
bias2 = float(a)
elif o in ('--q3'):
bias3 = float(a)
elif o in ('--q4'):
bias4 = float(a)
elif o in ('-r'): # row filtering
rowfilter = True
elif o in ('-s'): # sky frame
skyfile = a
subtractsky = True
elif o in ('-t'): # test grid filter
applygridfilter = True
elif o in ('-x'): # specify pattern x-dimension
pxsize = int(a)
elif o in ('-y'): # specify pattern y-dimension
pysize = int(a)
elif o in ('-v'): # verbose debugging output
verbose = True
else:
assert False, "unhandled option"
inputfile = args[0]
files = glob.glob(inputfile)
if verbose:
print '...input = ', inputfile
print '...files = ', files
print ''
for f in files:
if IsFits(f):
cleanir(f)
else: # file list
print 'Expanding ' + f + '...\n'
inlist = open(f,'r')
for line in inlist:
cleanir(line.strip())
inlist.close()
#-----------------------------------------------------------------------
def IsFits(infile):
global datadir
# If the file exists and has a .fits extension assume that it is FITS:
if os.path.exists(datadir + infile):
if infile.endswith('.fits'):
return True
else:
return False
elif os.path.exists(infile): # Check for lists in the CWD
if infile.endswith('.fits'):
return True
else:
return False
else: # assume it is a FITS image missing the .fits extension
return True
#-----------------------------------------------------------------------
def IterStat(vector, lowsigma=3, highsigma=3):
global verbose
median = numpy.median(vector)
stddev = numpy.std(vector)
minval = median - lowsigma * stddev
maxval = median + highsigma * stddev
num = numpy.size(vector)
dn = 1000
i = 0
if verbose:
print ' ...',i,' median=',median,' stddev=',stddev,' min=',minval,' max=',maxval,' N=',num
while (num > 0 and dn > 0 and stddev > 0 ):
i += 1
tmpvec = vector[(vector>minval) & (vector<maxval)]
median = numpy.median(tmpvec)
stddev = numpy.std(tmpvec)
dn = num - numpy.size(tmpvec)
num = numpy.size(tmpvec)
if verbose:
print ' ...',i,' median=',median,' stddev=',stddev,' min=',minval,' max=',maxval,' N=',num,' dN=',dn
minval = median - lowsigma * stddev
maxval = median + highsigma * stddev
return (median, stddev)
#-----------------------------------------------------------------------
# Try to use binning to approximate the mode
# The graphs look better, but not the image... :-(
def HistMode(x):
if x.size < 1:
print 'ERROR: cannot determine the mode of an empty array'
sys.exit(2)
median = numpy.median(x)
stddev = numpy.std(x)
bins = numpy.arange(median-stddev, median+stddev, stddev/100.)
h, edges = numpy.histogram(x, bins)
#print 'bins =', bins
#print 'histogram =',h
peak = numpy.max(h)
#print 'peak =', peak
mode = bins[numpy.argmax(h)] # Need to handle if there are two max values
#print 'mode =', mode
if type(mode) is numpy.ndarray:
print 'WARNING: mode is multi-valued:', mode
mode = numpy.mean(mode)
print 'using average: ', mode
minpts = 20
if peak >= minpts:
return mode
else:
print 'WARNING: low mode frequency =', peak, ' using median'
return median
#-----------------------------------------------------------------------
# This seems like a roundabout way of calculating the mode?
# Since we are dealing with floats, the mode is often undefined.
def Mode(x):
if x.size < 1:
print 'ERROR: cannot determine the mode of an empty array'
sys.exit(2)
y = numpy.sort(x)
starts = numpy.concatenate(([1], numpy.diff(y).astype(bool).astype(int)))
starts_sum = starts.cumsum()
counts = numpy.bincount(starts_sum)
arg_mode_freq = counts.argmax()
counts_sum = counts.cumsum()
mode = y[counts_sum[arg_mode_freq-1]]
minpts = 20
minpts = 10
if counts[arg_mode_freq] >= minpts:
return mode
else:
print 'WARNING: low mode frequency =', counts[arg_mode_freq], ' switching to median'
return numpy.median(x)
#-----------------------------------------------------------------------
def CleanQuad(quad,patternin):
# quad = quadrant to be pattern-subtracted
# patternin = region to use for pattern determination
global qxsize, qysize # quadrant size
global pxsize, pysize # pattern size
global inputmedian, inputstddev
if verbose:
print ' ...mean of input quadrant =', numpy.mean(quad)
print ' ...median of input quadrant =', numpy.median(quad)
# create arrays of indices which correspond to the pattern tiled over
# the region of the input quadrant to be used for pattern determination
inpx = len(patternin[0])
inpy = len(patternin)
if verbose:
print ' ...size of pattern determination region =',inpx,'x',inpy
indx = numpy.tile(numpy.arange(0,inpx,pxsize), inpy/pysize)
indy = numpy.arange(0,inpy,pysize).repeat(inpx/pxsize)
if verbose:
print ' ...indx:', indx
print ' ...indy:', indy
# create blank pattern array:
pattern = numpy.zeros(pysize*pxsize).reshape(pysize,pxsize)
origstddev = numpy.std(quad)
print ' ...standard deviation of input quadrant =%9.3f' % origstddev
# find median pattern across quadrant:
if (graph > 0):
binwidth = 1
binmin = inputmedian - 5. * inputstddev
binmax = inputmedian + 5. * inputstddev
#binmax = numpy.max(quad) # show all the data
bins = numpy.arange( binmin, binmax, binwidth )
bincenters = bins[1:bins.size] - binwidth/2.
iplot = 0
for iy in range(0, pysize):
for ix in range(0, pxsize):
tmpdata = patternin[indy+iy,indx+ix]
#pattern[iy,ix] = numpy.median(tmpdata[tmpdata!=bad]) # filter out bad pix
pattern[iy,ix] = Mode(tmpdata[tmpdata!=bad])
if (graph==1 or graph==3):
iplot += 1
hist,bins = numpy.histogram(tmpdata, bins=bins)
plot = pyplot.subplot(pysize,pxsize,iplot)
pyplot.plot(bincenters, hist, linestyle='', marker='.')
pyplot.axvline(x=pattern[iy,ix], ls='--', color='green')
if ix != 0:
plot.set_yticklabels([])
if verbose:
print '...pattern:', pattern
if (graph==1 or graph==3):
print ('...graphing results...')
pyplot.subplots_adjust(left=0.05, bottom=0.05, right=0.95, top=0.95, wspace=0., hspace=0.2)
pyplot.show()
# tile pattern over quadrant:
quadpattern = numpy.tile(pattern, (qysize/pysize, qxsize/pxsize))
quadpattern -= numpy.mean(pattern) # set the mean value to zero
#print ' ...mean of pattern = ', numpy.mean(quadpattern)
cleanedquad = quad - quadpattern # subtract pattern
cleanstddev = numpy.std(cleanedquad) # calculate standard deviation
print ' ...standard deviation of cleaned quadrant = %.3f' % cleanstddev
if (force):
print ' ...forcing pattern subtraction'
else:
# has subtracting the pattern reduced the standard deviation?
if ( origstddev - cleanstddev > 0.01 ):
print ' ...improvement!'
else:
print ' ...no significant improvement so using the original quadrant'
cleanedquad = quad # reset quadrant pixels to original values
quadpattern = quadpattern * 0 # set pattern to zeros
return cleanedquad, quadpattern
#-----------------------------------------------------------------------
def CleanRow(row, sample, value):
# row = row to be pattern-subtracted
# sample = sample used to measure pattern
# value = desired final median value of row
indx = numpy.arange(0,len(sample),8)
pattern = numpy.zeros(8)
for ix in range(0, 8):
tmpdata = sample[indx+ix]
tmpdata = tmpdata[tmpdata!=bad] # filter out bad pix
# pattern[ix] = numpy.median(tmpdata)
median,stddev = IterStat(tmpdata)
pattern[ix] = median
if verbose:
print '...pattern:', pattern
# repeat the pattern over the row and subtract:
rowpattern = numpy.tile(pattern, len(row)/8)
cleanedrow = row - rowpattern + value
#median,stddev = IterStat(cleanedrow, lowsigma=3, highsigma=1)
#cleanedrow = cleanedrow + (value - median)
return cleanedrow
#-----------------------------------------------------------------------
def ApplyRowFilter(quad, patternin):
# quad = quadrant to be pattern-subtracted
# patternin = region to use for pattern determination
global qxsize, qysize # quadrant size
median,stddev = IterStat(patternin[patternin!=bad]) # iterative median
print '...median of input sample quadrant =', median, '+/-', stddev
for iy in range(0,qysize): # this is not correct, but will work for GNIRS
if verbose:
print '...row =', iy
quad[iy] = CleanRow(quad[iy], patternin[iy], median)
#quad[iy] = CleanRow(quad[iy], patternin[iy], inputmedian)
return quad
#-----------------------------------------------------------------------
def gaussian(t,p): # p[0] = mu p[1] = sigma p[2] = peak
return(p[2] * numpy.exp( -(t - p[0])**2 / (2 * p[1]**2) ))
def residuals(p,data,t):
err = data - gaussian(t,p)
return err
def NormQuadMedian(quad):
global bins, bincenters, inputmean, inputmedian, inputstddev
global lsigma, hsigma, bad
hist,bins = numpy.histogram(quad, bins=bins)
if verbose:
print '...calculating median using low-sigma =',lsigma,' and high-sigma =',hsigma
mincts = inputmedian - lsigma * inputstddev
maxcts = inputmedian + hsigma * inputstddev
if verbose:
print '...input median=',inputmedian,' min=',mincts,' max=',maxcts
flatquad = quad[quad != bad] # flatten array and filter out bad pix
npix = numpy.size(flatquad)
dn = 100
while (npix > 10000 and dn > 10):
tmpquad = flatquad[(flatquad>mincts) & (flatquad<maxcts)]
median = numpy.median(tmpquad)
stddev = numpy.std(tmpquad)
mincts = median - lsigma * stddev
maxcts = median + hsigma * stddev
dn = npix - numpy.size(tmpquad)
npix = numpy.size(tmpquad)
if verbose:
print '...median=',median,' stddev=',stddev,' min=',mincts,' max=',maxcts,' npix=',npix,' dn=',dn
offset = inputmedian - median
print ' ...offset = %.3f' % offset
return hist, median, offset
#-----------------------------------------------------------------------
def NormQuadGauss(quad):
global bins, bincenters, inputmean, inputmedian, inputstddev
hist,bins = numpy.histogram(quad, bins=bins)
mode = bins[ hist.argmax() ]
peak = hist.max()
fitsigma = 1.0 # this should probably be a command-line parameter
mincts = mode - fitsigma * inputstddev
maxcts = mode + fitsigma * inputstddev
t = bincenters[ (bincenters>mincts) & (bincenters<maxcts) ]
data = hist[ (bincenters>mincts) & (bincenters<maxcts) ]
p0 = [mode, inputstddev, peak]
print ' ...initial parameter guesses = %.3f %.3f %.0f' % (p0[0],p0[1],p0[2])
pbest = leastsq(residuals, p0, args=(data,t), full_output=1)
p = pbest[0]
print ' ...best fit parameters = %.3f %.3f %.0f' % (p[0],p[1],p[2])
offset = inputmean - p[0]
print ' ...offset = %.3f' % offset
xfit = numpy.linspace(mincts, maxcts, 100)
yfit = gaussian(xfit, p)
return hist, p[0], offset, xfit, yfit
#-----------------------------------------------------------------------
def GridFilter(img):
global qxsize, qysize # quadrant size
gsize = 64
indx = numpy.tile(numpy.arange(0,qxsize-gsize+1,gsize), qysize/gsize)
indy = numpy.arange(0,qysize-gsize+1,gsize).repeat(qxsize/gsize)
tmpimg = numpy.zeros((gsize, gsize))
for iy in range(0, gsize):
for ix in range(0, gsize):
tmpdata = img[indy+iy,indx+ix]
tmpimg[iy,ix] = numpy.median(tmpdata)
return tmpimg
#-----------------------------------------------------------------------
def cleanir(inputfile):
global allpixels
global badmask, biasadjust, bias1, bias2, bias3, bias4, bins, bincenters
global cfrac, datadir, force
global inputmean, inputmedian, inputstddev, lsigma, hsigma
global output, pattern, patternfile, pxsize, pysize, qxsize, qysize, quadmedian, rowfilter
global skyfile, subtractsky
global verbose
print 'CLEANIR v.', version
havedq = False # we have DQ information
lampsonflat = False # this is a lamps-on flat
if verbose:
print '...inputfile =', inputfile
print '...allpixels =', allpixels
print '...badmask =', badmask
print '...biasadjust =', biasadjust
print '...bias1 =', bias1
print '...bias2 =', bias2
print '...bias3 =', bias3
print '...bias4 =', bias4
print '...cfrac =', cfrac
print '...datadir =', datadir
print '...force =', force
print '...high =', high
print '...quadmedian =', quadmedian
print '...output =', output
print '...patternfile =', patternfile
print '...row filter =', rowfilter
print '...skyfile =', skyfile
print '...subtractsky =', subtractsky
print '...pxsize =', pxsize
print '...pysize =', pysize
if not inputfile.endswith('.fits'):
inputfile = inputfile + '.fits'
if (output == 'default'):
outputfile = 'c' + os.path.basename(inputfile)
else:
outputfile = output
if ( not outputfile.endswith('.fits') ):
outputfile = outputfile + '.fits'
if (datadir != ''):
inputfile = datadir + '/' + inputfile
print 'Removing pattern noise from', inputfile
if (savepattern):
if ( not patternfile.endswith('.fits') ):
patternfile = patternfile + '.fits'
if subtractsky:
if ( not skyfile.endswith('.fits') ):
skyfile = skyfile + '.fits'
if not os.path.exists(inputfile): # check whether input file exists
print 'ERROR: ', inputfile, 'does not exist'
sys.exit(2)
if os.path.exists(outputfile): # check whether output file exists
print '...removing old', outputfile
os.remove(outputfile)
if savepattern:
if os.path.exists(patternfile): # check whether pattern file exists
print '...removing old', patternfile
os.remove(patternfile)
if subtractsky:
if not os.path.exists(skyfile): # check whether sky file exists
print skyfile, 'does not exist'
sys.exit(2)
if cfrac < 0.1:
print 'ERROR: central fraction must be >= 0.1'
sys.exit(2)
if cfrac > 1:
print 'ERROR: central fraction must be <= 1'
sys.exit(2)
if verbose:
print '...reading', inputfile
hdulist = fits.open(inputfile)
if verbose:
print '...hdulist:', hdulist.info()
# This isn't always reliable, so just ignore non-standard FITS for now...
#if verbose:
# print '...verifying FITS header...'
# hdulist.verify('fix')
#else:
# hdulist.verify('silentfix')
next = len(hdulist)
if verbose:
print '...number of extensions = ', next
if ( next == 1 ):
sci = 0
elif ( next < 5 ):
sci = 1
else:
sci = 2
print '...assuming the science data are in extension', sci
image = numpy.array(hdulist[sci].data)
if verbose:
print '...SCI: ', image
print image.dtype.name
try:
naxis1,naxis2 = hdulist[sci].header['naxis1'], hdulist[sci].header['naxis2']
except:
print 'ERROR: cannot get the dimensions of extension ', sci
fits.info(inputfile)
sys.exit(2)
print '...image dimensions = ', naxis1, 'x', naxis2
try:
instrument = hdulist[0].header['INSTRUME']
if verbose:
print '...instrument =', instrument
except:
print 'WARNING: cannot determine instrument'
instrument = 'INDEF'
allpixels = True
try:
nscut = hdulist[0].header['NSCUT']
nscut = True
except:
nscut = False
if verbose:
print '...nscut =', nscut
if instrument == 'GNIRS':
print '...padding the top of GNIRS image...'
pad = numpy.zeros((2,naxis1), dtype=numpy.float32) # create 2D array of padding
image = numpy.append(image,pad,axis=0) # append the padding array to the end
if verbose:
print '...new image:', image
naxis2 = naxis2 + 2
print '...image dimensions = ', naxis1, 'x', naxis2
print '...pattern size =', pxsize, 'x', pysize
qxsize = naxis1 / 2 # quadrant x size
qysize = naxis2 / 2 # quadrant y size
if qxsize%pxsize != 0 or qysize%pysize != 0:
print 'ERROR: quadrant size is not a multiple of the pattern size'
sys.exit(2)
if pxsize > qxsize or pysize > qysize:
print 'ERROR: pattern size is larger than the quadrant size!'
sys.exit(2)
try:
skyimage = hdulist[0].header['SKYIMAGE']
skysubtracted = True
except:
skysubtracted = False
print '...sky subtracted =', skysubtracted
#-----------------------------------------------------------------------
if usedq and badmask == 'DQ':
if verbose:
print '...reading data quality extension...'
try:
dq = numpy.array(hdulist['DQ'].data)
havedq = True
if verbose:
print '...DQ: ', dq
if ( numpy.size(dq[dq>0]) > numpy.size(dq)/2 ):
print 'WARNING:', numpy.size(dq[dq>0]), 'pixels are flagged as bad in the DQ plane!'
except:
print '...no DQ data found'
# dq = numpy.zeros(naxis2*naxis1,int)
# dq.resize(naxis2,naxis1)
elif os.path.exists(badmask): # bad pixel mask specified on the command line
if verbose:
print '...reading bad pixel mask', badmask
if badmask.endswith('.pl'):
if verbose:
print '...converting pl to fits'
# fitsfile = inputfile.replace('.pl', '.fits')
tmpbadmask = 'cleanir-badpixelmask.fits'
if os.path.exists(tmpbadmask):
os.remove(tmpbadmask)
from pyraf import iraf
iraf.imcopy(badmask, tmpbadmask)
badmask = tmpbadmask
badmaskhdu = fits.open(badmask)
if verbose:
badmaskhdu.info()
dq = numpy.array(badmaskhdu[0].data)
havedq = True
if badmask.endswith('.pl'):
os.remove(tmpbadmask)
if verbose:
print '...DQ: ', dq
badmaskhdu.close()
elif badmask == 'DQ' and not usedq:
print '! WARNING: ignoring DQ plane!'
havedq = False
else:
print '! WARNING: ', badmask, 'does not exist!'
havedq = False
#-----------------------------------------------------------------------
if biasadjust:
try:
obstype = hdulist[0].header['OBSTYPE']
except:
print 'WARNING: cannot determine obstype'
obstype = 'INDEF'
if verbose:
print '...obstype =', obstype
if (obstype == 'FLAT'):
try:
gcalshutter = hdulist[0].header['GCALSHUT']
except:
print 'WARNING: cannot determine GCAL shutter status'
if verbose:
print '...gcal shutter =', gcalshutter
if (gcalshutter == 'OPEN'):
print '! WARNING: This is a lamps-on flat, so turning off quadrant normalization.'
lampsonflat = True
# Bias level adjustment should probably only be done on flat-fielded data.
#-----------------------------------------------------------------------
if subtractsky:
print '...reading sky', skyfile
sky = fits.open(skyfile)
print '...verifying sky...'
if verbose:
sky.verify('fix')
else:
sky.verify('silentfix')
skyimage = numpy.array(sky[sci].data)
if instrument == 'GNIRS':
print '...padding the top of the GNIRS sky...'
skyimage = numpy.append(skyimage,pad,axis=0) # append the padding array to the end
skysubtracted = True # This variable used when determining the area to use for pattern determination.
# NEED ERROR CHECKING HERE! (extensions, image size, filter, instrument, etc.)
#-----------------------------------------------------------------------
if subtractrowmedian:
print '...subtracting the median of each rows...'
imagemean = numpy.mean(image)
for iy in range(0, naxis2):
tmprow = image[iy,:]
if verbose:
print '...row ', iy
median,stddev = IterStat(tmprow) # iterative median
image[iy,:] -= median
image += ( imagemean - image.mean() ) # reset image to the original mean value
# image[iy,:] -= numpy.median(image[iy,:]) # simple row-filtering over the whole image
# Median filter each quadrant:
# image[iy,0:naxis1/2] -= numpy.median(image[iy,0:naxis1/2])
# image[iy,naxis1/2:naxis1] -= numpy.median(image[iy,naxis1/2:naxis1])
#-----------------------------------------------------------------------
# Set regions to be used for pattern determination:
# +-------+
# | 1 | 2 |
# +---+---+
# | 3 | 4 |
# +---+---+
if instrument == 'NIRI':
camera = 'INDEF'
decker = 'INDEF'
try:
fpmask = hdulist[0].header['FPMASK']
except:
print 'WARNING: cannot find FPMASK header keyword'
print ' Assuming that this is imaging...'
fpmask = 'f6-cam_G5208'
elif instrument == 'GNIRS':
fpmask = 'INDEF'
try:
camera = hdulist[0].header['CAMERA']
except:
print 'WARNING: cannot find CAMERA header keyword'
camera = 'INDEF'
try:
decker = hdulist[0].header['DECKER']
except:
print 'WARNING: cannot find DECKER header keyword'
decker = 'INDEF'
try:
lnrs = hdulist[0].header['LNRS']
except:
print 'WARNING: cannot find LNRS header keyword'
lnrs = 'INDEF'
else:
fpmask = 'INDEF'
camera = 'INDEF'
decker = 'INDEF'
allpixels = True
if verbose:
print '...fpmask = ', fpmask
print '...camera = ', camera
print '...decker = ', decker
if allpixels:
print '...using whole image for pattern determination'
q1x1,q1x2, q1y1,q1y2 = 0,qxsize, qysize,naxis2 # quad 1
q2x1,q2x2, q2y1,q2y2 = qxsize,naxis1, qysize,naxis2 # quad 2
q3x1,q3x2, q3y1,q3y2 = 0,qxsize, 0,qysize # quad 3
q4x1,q4x2, q4y1,q4y2 = qxsize,naxis1, 0,qysize # quad 4
lsigma = 3.0
hsigma = 1.0 # set a very small upper threshold to reject stars
elif instrument == 'NIRI' and not skysubtracted and \
(fpmask == 'f6-2pixBl_G5214' or \
fpmask == 'f6-4pixBl_G5215' or \
fpmask == 'f6-6pixBl_G5216' or \
fpmask == 'f6-2pix_G5211'):
print '...using region above and below slit (y<=272 and y>=728) for pattern determination'
q1x1,q1x2, q1y1,q1y2 = 0,qxsize, 728,naxis2
q2x1,q2x2, q2y1,q2y2 = qxsize,naxis1, 728,naxis2
q3x1,q3x2, q3y1,q3y2 = 0,qxsize, 0,272
q4x1,q4x2, q4y1,q4y2 = qxsize,naxis1, 0,272
lsigma = 3.0
hsigma = 3.0
elif instrument == 'NIRI' and not skysubtracted and \
(fpmask == 'f6-4pix_G5212' or \
fpmask == 'f6-6pix_G5213' or \
fpmask == 'f32-6pix_G5229' or \
fpmask == 'f32-9pix_G5230'):
print '...using whole image for pattern determination'
print 'WARNING: Sky lines may be altered by pattern removal!'
q1x1,q1x2, q1y1,q1y2 = 0,qxsize, qysize,naxis2
q2x1,q2x2, q2y1,q2y2 = qxsize,naxis1, qysize,naxis2
q3x1,q3x2, q3y1,q3y2 = 0,qxsize, 0,qysize
q4x1,q4x2, q4y1,q4y2 = qxsize,naxis1, 0,qysize
lsigma = 3.0
hsigma = 3.0
elif instrument == 'GNIRS' and not skysubtracted and \
'Short' in camera and not 'XD' in decker: # GNIRS short-camera long-slit
print '...using x<=160 and x>=864 for pattern determination'
q1x1,q1x2, q1y1,q1y2 = 0,160, qysize,naxis2
q2x1,q2x2, q2y1,q2y2 = 864,naxis1, qysize,naxis2
q3x1,q3x2, q3y1,q3y2 = 0,160, 0,qysize
q4x1,q4x2, q4y1,q4y2 = 864,naxis1, 0,qysize
lsigma = 3.0
hsigma = 3.0
else:
print '...using whole image for pattern determination'
q1x1,q1x2, q1y1,q1y2 = 0,qxsize, qysize,naxis2 # quad 1
q2x1,q2x2, q2y1,q2y2 = qxsize,naxis1, qysize,naxis2 # quad 2
q3x1,q3x2, q3y1,q3y2 = 0,qxsize, 0,qysize # quad 3
q4x1,q4x2, q4y1,q4y2 = qxsize,naxis1, 0,qysize # quad 4
lsigma = 3.0
hsigma = 1.0 # set a very small upper threshold to reject stars
patternin = image.copy()
patternin1 = patternin[q1y1:q1y2, q1x1:q1x2]
patternin2 = patternin[q2y1:q2y2, q2x1:q2x2]
patternin3 = patternin[q3y1:q3y2, q3x1:q3x2]
patternin4 = patternin[q4y1:q4y2, q4x1:q4x2]
#-------------------------------------------------------------------
# Subtract sky frame
if subtractsky:
print '...subtracting sky...'
patternin1 -= skyimage[q1y1:q1y2, q1x1:q1x2]
patternin2 -= skyimage[q2y1:q2y2, q2x1:q2x2]