4
* PSF fitting functions for spydr. Computes Strehl ratio and FWHM.
6
* This file is part of spydr, an image viewer/data analysis tool
8
* $Id: spydr_psffit.i,v 1.10 2008/02/12 13:58:43 frigaut Exp $
10
* Copyright (c) 2007, Francois Rigaut
12
* This program is free software; you can redistribute it and/or modify it
13
* under the terms of the GNU General Public License as published by the
14
* Free Software Foundation; either version 3 of the License, or (at your
15
* option) any later version.
17
* This program is distributed in the hope that it will be useful, but
18
* WITHOUT ANY WARRANTY; without even the implied warranty of
19
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20
* General Public License for more details.
22
* You should have received a copy of the GNU General Public License
23
* along with this program. If not, see <http://www.gnu.org/licenses/>.
25
* $Log: spydr_psffit.i,v $
26
* Revision 1.10 2008/02/12 13:58:43 frigaut
27
* changelog to version 0.7.7:
29
* - fixed a bug when spydr_lut is not 0 and one creates a new
31
* - other minor bug fixes.
32
* - updated spydr man page
33
* - written and published web doc on maumae.
35
* Revision 1.9 2008/02/10 15:08:07 frigaut
37
* - can now change the dpi on the fly. ctrl++ and ctrl+- will enlarge
38
* or shrink the graphical areas. long time missing in yorick.
39
* I have tried to make the window resizable, but it's a mess. Not
40
* only in the management of events, but also in the policy: really,
41
* only enlarging proportionally makes sense.
42
* - changed a bit the zoom behavior: now zoom is started once (the first
43
* time the mouse enter drawingarea1), and does not stop from that point.
44
* This is not ideal/economical (although disp_zoom returns immediately
45
* if the mouse is not in the image window), but it has the advantage
46
* of being sure the disp_zoom process does not spawn multiple instances
47
* (recurrent issue with "after").
48
* - The menu items in the left menu bar are hidden/shown according to the
50
* - gotten rid of a few (unused) functions in spydr.i (the progressbar
51
* and message functions) that were conflicting with other pyk instances.
52
* - there's now focus in and out functions that will reset the current
53
* window to what it was before the focus was given to spydr. This is
54
* convenient when one just want to popup a spydr window to look at an
55
* image, and then come back to whatever one was doing without having to
56
* execute a window,n command.
57
* - fixed a bug in disp_cpc. Now, when a "e"/"E" command is executed
58
* while a subimage is displayed, the "e"/"E" applies to the displayed
59
* subimage, not the whole image.
60
* - changed a bit the behavior of the lower graphical area: not the y
61
* range is the same as the image zcuts (cmin/cmax).
62
* - fixed a small bug in get_subim (using floor/ceil instead of round
63
* for the indices determination).
64
* - added "compact" keyword to the spydr function (when called from
66
* - clipping dpi values to [30,400].
67
* - spydr.py: went for a self autodic instead of an explicit
68
* declaration of all functions.
69
* - implemented smoothing by x2
70
* - implemented 1d linear fitting
72
* Revision 1.8 2008/02/02 05:12:05 frigaut
73
* fixed bug when picking star for fitting while being in "graphical axis
76
* Revision 1.7 2008/01/30 05:28:19 frigaut
77
* - added spydr_pyk to avoid conflicts with other calls of pyk, and modify
78
* spydr_pyk for our purpose. I know this means we will not benefit from
79
* future pyk code improvements, but I can deal with that.
80
* - added check of yorick main version to avoid use with V<2.1.05 (in which
81
* current_mouse does not exist)
83
* Revision 1.6 2008/01/25 03:03:49 frigaut
84
* - updated license or license text to GPLv3 in all files
86
* Revision 1.5 2008/01/24 15:05:17 frigaut
87
* - added "delete from stack" feature
88
* - some bugfix in psffit
90
* Revision 1.4 2008/01/23 21:11:22 frigaut
91
* - load of new things:
94
* - added a number of command line flags (see man page or spydr -h)
95
* - can now handle series of image of different sizes
96
* - can mix single image and cube
97
* - cmin and cmax are now set per image (sticky setting)
98
* - image titles are better handled
100
* - new image can be opened from the GUI menu (filechooser, multiple
102
* - migrated to a spydrs structure, replaced many different variables, cleaner.
103
* - now opens the GUI even with no image argument (can use "open" from menu)
104
* - all errors are now also displayed as popups (critical quits yorick
105
* when called from shell)
106
* - because some (of the more critical) errors can happen before python is
107
* started, I had to use zenity for the popup window. New dependency.
108
* - added an "append" keyword to spydr. If set, the new image is appended
109
* to the list of displayed image. The old ones are kept, and the total
110
* number of image is ++
111
* - append is also available from the GUI menu
112
* - any action on displayed image can be null by using "help->refresh
113
* display" (in particular, sigmafilter)
114
* - created "about" dialog.
115
* - added an "image" menu (with names of all images in stack). user can
116
* select image form there.
117
* - added an "ops" (operation) menu. Can compute median, average, sum and
119
* - small gui (without lower panel) form is called with --compact (-c)
122
* - fixed path to find python and glade files
123
* - fixed path for configuration file
124
* - main routine re-written and much more robust and clean
125
* - (kind of) solved a issue where image got displayed several times
126
* because of echo from setting cmin and cmax
127
* - fixed thibaut bug when closing window.
128
* - fixed "called_from_shell" when no image argument.
129
* - waiting for a doc for the user buttons, set to insivible.
130
* - waiting for a proper implementation of find, pane set to invisible.
133
* - bug: sometimes the next/previous image does not register
135
* Revision 1.3 2008/01/17 13:15:17 frigaut
136
* - modified the name of lmfit (-> spydr_lmfit) in spydr_psffit to avoid
137
* conflicts with the lmfit of yutils.
138
* - modified all calls of lmfit -> spydr_lmfit (spydr_psffit and spydr_various)
139
* - also default box size is now 51 (was 181)
141
* Revision 1.2 2007/12/13 13:43:27 frigaut
142
* - added license headers in all files
144
* - slightly modified Makefile
154
modifDate = "June 17, 2007";
158
require,"random_et.i";
160
require,"spydr_various.i";
161
require,"astro_util1.i"; // for sky()
162
require,"aoutil.i"; // for fwhmStrehl()
164
struct s_yfwhmres { double xpos, xposerr, ypos, yposerr, pstrehl, pfwhm, xfwhm, xfwhmerr, yfwhm, yfwhmerr, flux, fluxerr, el, elerr, angle, maxim, background;};
166
func parseDate(strdate)
169
sread,strdate,format="%4d-%2d-%2d",y,m,d;
172
func parseTime(strtime)
175
sread,strtime,format="%2d:%2d:%f",h,m,s;
176
return h+m/60.+s/3600.;
179
func getam(hdr,verbose=)
180
/* DOCUMENT func getam(hdr,verbose=)
181
Determine airmass from the information available in the AC header
182
for use in yfwhm.i procedure.
186
// Find the observing location (GN or GS?):
187
loc = strtrim(sxpar(hdr,"OBSERVAT"));
188
if (loc == "Gemini-North") {
189
longitude= -1*[155,28.3]; // At MK
191
if (is_set(verbose)) {print,"Found Location : Gemini-North";}
192
} else if (loc == "Gemini-South") {
193
longitude= -1*[70,43.4]; // At Pachon
194
latitude= -1*[30,13.7];
195
if (is_set(verbose)) {print,"Found Location : Gemini-South";}
197
exit,"Problem in determining location, see getam";
200
// Convert long and lat in decimal:
201
longitude= longitude(1)+longitude(2)/60.;
202
latitude= latitude(1)+latitude(2)/60.;
204
// parse date and time and convert in float:
205
date= parseDate(strtrim(sxpar(hdr,"DATE-OBS")));
206
time= parseTime(strtrim(sxpar(hdr,"TIME-OBS")));
209
jdStart= jdcnv(date(1),date(2),date(3),time);
212
lst= ct2lst(longitude,0.,jdStart);
213
if (is_set(verbose)) {
214
write,format="LST = %f ; RA = %f ; dec = %f\n",
215
lst,sxpar(hdr,"RA"),sxpar(hdr,"DEC");
218
ha = min(abs([lst-sxpar(hdr,"RA"),(24+lst)-sxpar(hdr,"RA")]));
219
dec = sxpar(hdr,"DEC");
222
print,"DATE-OBS (UT) = ",date;
223
print,"TIME-OBS (UT) = ",time;
225
print,"RA = ",sxpar(hdr,"RA");
226
exit,"Problem, HA is > 6 !";
229
// compute the elevation from the HA and dec:
230
altaz,ha/12*180.,dec,latitude,alt,az;
235
if (is_set(verbose)) {
236
write,format="Zenith angle = %f ; Airmass = %f\n",zenang,airmass(zenang);
239
return airmass(zenang);
245
// a = [sky,flux,Xcent,Ycent,Xfwhm,Yfwhm]
246
y = a(1)+a(2)*exp(-( (abs(x(,,1)-a(3))/a(5))^2. + (abs(x(,,2)-a(4))/a(6))^2. )^a(7) );
250
func gaussianRound(x,a)
252
// FIXME: protect against zeros in next 3 functions (as in moffat)
253
// a = [sky,Totalflux,Xcent,Ycent,~fwhm]
254
a(3) = clip(a(3),-10,dimsof(x)(2)+10);
255
a(4) = clip(a(4),-10,dimsof(x)(3)+10);
256
a(5:6) = abs(a(5:6));
258
a(5:6) = (atan(a(5:6))+pi/2.)*8;
262
if (a(5)==0.) return a(1)+z*0.;
263
z = exp(-((xp/a(5))^2.+(yp/a(5))^2.));
264
if (sum(z)==0) return a(1)+z;
265
z = a(1)+a(2)*z/sum(z);
269
func lmfit_lim(a,dir)
270
/* DOCUMENT to be called within F(x,a) as
272
to set limits to the range of possible a values
273
and then to be called as
275
after spydr_lmfit has returned.
276
also, add the extern statement to calling function.
277
also, set the default values for the affected a to 0 (average value between
282
extern lmfit_amin,lmfit_amax;
283
w = where(lmfit_amin!=lmfit_amax);
284
if (numberof(w)!=0) {
286
a(w) = (atan(a(w))/pi+0.5)*(lmfit_amax(w)-lmfit_amin(w))+lmfit_amin(w);
287
} else if (dir==-1) {
288
a(w) = tan(((a(w)-lmfit_amin(w))/(lmfit_amax(w)-lmfit_amin(w))-0.5)*pi);
289
} else error,"dir has to be 1 (forward) or -1 (backward) ";
298
// a = [sky,Totalflux,Xcent,Ycent,~Xfwhm,~Yfwhm,angle]
300
alpha = a(7)/180.*pi;
301
xp = (x(,,1)-a(3))*cos(alpha)+(x(,,2)-a(4))*sin(alpha);
302
yp = -(x(,,1)-a(3))*sin(alpha)+(x(,,2)-a(4))*cos(alpha);
303
r = sqrt(xp^2.+yp^2.);
305
z = exp(-(yp/a(6))^2.);
306
} else if (a(6)==0) {
307
z = exp(-(xp/a(5))^2.);
308
} else z = exp(-((xp/a(5))^2.+(yp/a(6))^2.));
309
if (sum(z)==0) return a(1)+z;
310
z = a(1)+a(2)*z/sum(z);
315
func moffatRound(x,a)
317
// a=[sky,total,xc,yc,a,coefpow]
318
// a6 = atan(a(6))/(pi/2.)+1.2;
320
a(6)=clip(a(6),-30,30);
323
// z = (1. + ((xp/a1)^2.+(yp/a1)^2.))^(-a(6));
328
z = (1. + ((yp/a1)^2.))^(-a(6));
330
z = (1. + ((xp/a1)^2.+(yp/a1)^2.))^(-a(6));
333
if (sum(z)==0) return a(1)+z;
334
z = a(1)+a(2)*z/sum(z);
341
// a=[sky,total,xc,yc,a,b,angle,coefpow]
342
// alpha angle of longest axis clockwise
343
// a(8)=clip(a(8),-50,50);
345
alpha = a(7)/180.*pi;
348
xp = (x(,,1)-a(3))*cos(alpha)+(x(,,2)-a(4))*sin(alpha);
349
yp = -(x(,,1)-a(3))*sin(alpha)+(x(,,2)-a(4))*cos(alpha);
354
z = (1. + ((yp/a2)^2.))^(-a(8));
356
z = (1. + ((xp/a1)^2.))^(-a(8));
358
z = (1. + ((xp/a1)^2.+(yp/a2)^2.))^(-a(8));
361
if (sum(z)==0) return a(1)+z;
362
z = a(1)+a(2)*z/sum(z);
368
write,"Yorick function to interactively measure FWHM on an image";
370
"Syntax: yfwhm [-help] [-a -p pixelsize -b boxsize -f functype -e extnum -s saturation -mag -v] image";
372
func printlonghelp(void)
374
write,"Yorick function to interactively measure FWHM on an image";
375
write,"Version "+version+" / Last modified "+modifDate;
376
write,"Syntax: yfwhm [-help] [-p pixelsize -b boxsize -mag] image";
377
write,"-help Prints this message";
378
// write,"-1 Uses only one window for graphic display";
379
write,"-a Outputs airmass corrected FWHM values";
380
write,"-p pixelsize Specify the image pixel size";
381
write,"-b boxsize Specify the size of the box of sub-images,";
382
write," usually 4-10 times the fwhm";
383
write,"-f functype function to use for fit (gaussian,special,moffat)";
384
write,"-e extnum fits extension number (main = 1)";
385
write,"-s saturation Saturation value (prevent picking saturated stars)";
386
write,"-mag Output flux in magnitude (zp=spydr_zero_point is used)";
387
write,"-v Verbose mode (more chatty)";
390
func yfwhm(bim,onepass,xstar,ystar,fluxstar,boxsize=,saturation=,pixsize=,funtype=, \
391
magswitch=,verbose=,airmass=)
392
/* DOCUMENT func yfwhm(image,boxsize=,saturation=,pixsize=,funtype=,
393
magswitch=,nwindow=,verbose=,airmass=)
395
airmass = airmass. Outputs airmass corrected FWHM values
396
pixsize = Specify the image pixel size
397
boxsize = Specify the size of the box of sub-images
398
(usually 4-10 times the fwhm)
399
funtype = function to use for fit (gaussian,special,moffat
400
saturation = Saturation value (prevents picking saturated stars)
401
magswitch = Output flux in magnitude (zp=spydr_zero_point is used)
402
nwindow = Number of window for UI (default 2)
403
verbose = Verbose mode (0/1=more chatty)
404
x and y: added 2007jun15 for compatibilty with spydr find mode.
405
if x and y are set, then the interactive mode is turned off,
406
and the (x,y) coordinates are looped on to produce the
407
final yfwhmres (this function loops on the (x,y) and fit
411
extern spydr_fit_fwhm_estimate,imnum;
412
extern spydr_fit_background_estimate;
414
if (!is_set(boxsize)) boxsize = spydr_boxsize;
415
if (!is_set(saturation)) saturation = spydr_saturation;
416
if (!is_set(pixsize)) {pixsize = spydrs(imnum).pixsize;}
417
if (pixsize!=1.0) pixset=1;
418
if (!is_set(funtype)) {funtype = spydr_funtype;} else {funcset=1;};
419
if (!is_set(magswitch)) magswitch= output_magnitudes;
420
if (!is_set(airmass)) airmass = spydr_airmass;
422
if (!is_set(verbose)) verbose=0;
424
if (!compute_strehl) {
425
if (funtype == "gaussian") {write,"Using Gaussian fit";}
426
if (funtype == "special") {write,"Using Special fit";}
427
if (funtype == "moffat") {write,"Using Moffat fit";}
432
batch_mode = (xstar!=[]);
435
if (numberof(xstar)!=numberof(ystar)) \
436
error,"X and Y are set but do not have the same dim";
437
n_to_do = numberof(xstar);
438
write,"Entering non interactive mode";
442
window,spydr_wins(1);
444
maskarg = array(1,narg);
446
yfwhmres = s_yfwhmres();
449
local b,pow,zp,f,ferr,el,eler,an,airmass,dims,sky1,bim;
452
// update zoom to match boxsize:
455
zp = spydr_zero_point;
456
f = array(float,2,1);
457
ferr = array(float,2,1);
461
airmass = double(airmass);
463
dims = (dimsof(bim))(2:3);
465
sky1 = sky(bim,dev1);
467
if (saturation != 0.) {saturation -= sky1;}
469
/* this is called from spydr, so the windows pre-exist. */
472
// write,"Click on star";
474
write,"Left click on star for FWHM. Right click to exit.";
475
write,"Middle click to remove last entry.";
478
if (onepass) spydr_pyk_status_push,"Click on star";
479
else spydr_pyk_status_push,"BUTTONS: Left:Select Star / Middle:Remove last entry / Right:Exit.";
481
if (!compute_strehl) {
484
write,"X[pix] Y[pix] X FWHM[\"] Y FWHM[\"] FLUX[ADU] ELLIP ANGLE MAX";
486
write,"X[pix] Y[pix] X FWHM[\"] Y FWHM[\"] MAGNITUDE ELLIP ANGLE MAX";
490
write,"X[pix] Y[pix] X FWHM[pix] Y FWHM[pix] FLUX[ADU] ELLIP ANGLE MAX";
492
write,"X[pix] Y[pix] X FWHM[pix] Y FWHM[pix] MAGNITUDE ELLIP ANGLE MAX";
502
c = long(_(xstar(nloop),ystar(nloop)));
503
if (nloop==n_to_do) but=3; else but=1; // but=3 will exit main loop
504
} else { // interactive mode
506
if (spydr_plot_in_arcsec) res(1:4) = spydr_arcsec_to_pixels(res(1:4));
507
spydr_pyk_status_push,"Processing...";
512
if (numberof(el) == 1) {
513
write,"You can only unbuffer after having buffered at least one star!";
514
spydr_pyk_warning,"You can only unbuffer after having buffered at least one star!";
522
write,"Last measurement taken out of star list";
523
spydr_pyk_warning,"Last measurement taken out of star list";
528
i1 = clip(c(1)-b,1,);
529
i2 = clip(c(1)+b,,dims(1));
530
j1 = clip(c(2)-b,1,);
531
j2 = clip(c(2)+b,,dims(2));
533
im = smooth(bim(i1:i2,j1:j2),2);
534
wm = where2(im == max(im))(*)(1:2)-b-1;
536
im = bim(i1:i2,j1:j2);
539
// im = sigmaFilter(im,5,iter=2,silent=1);
540
if ((saturation > 0) && (max(im) > saturation)) {
542
write,"Some pixels > specified saturation level. Aborting !";
543
spydr_pyk_status_push,"Some pixels > specified saturation level. Aborting !";
546
write,"Some pixels > specified saturation level. Choose another star";
547
spydr_pyk_status_push,"Some pixels > specified saturation level. Choose another star";
555
w = 1.+0.*clip(im,dev2,)^2;
561
write,"Error detected, exiting.";
562
spydr_pyk_status_push,"Error detected, exiting.";
565
write,"Error detected, skipping source";
566
spydr_pyk_status_push,"Error detected, skipping source";
571
extern lmfit_amin,lmfit_amax;
573
if (compute_strehl==0) {
574
if (funtype == "gaussian") {
575
// a = [sky,Totalflux,Xcent,Ycent,fwhm_parameter]
576
ai = [0,sum(im-median(im(*))),d(2)/2.,d(3)/2.,4.];
577
if (batch_mode) { // we've been given (guessed) coordinates, use them
578
ai = [0,fluxstar(nloop)*10.,xstar(nloop)-i1+1,ystar(nloop)-j1+1,spydr_fit_fwhm_estimate];
580
//r=spydr_lmfit(gaussianRound,x,ai,im,w,tol=1e-6,itmax=50,silent=1);
582
//a=[sky,Totalflux,Xcent,Ycent,a,b,angle]
583
a = [ai(1),ai(2),ai(3),ai(4),ai(5),ai(5),0.];
585
lmfit_amin = [0,0,a(3)-3,a(4)-3,a(5)-2,a(6)-2,0];
586
lmfit_amax = [0,0,a(3)+3,a(4)+3,a(5)+2,a(6)+2,0];
588
} else { // interative, no constraints.
589
lmfit_amin = [0,0,0,0,0,0,0];
590
lmfit_amax = [0,0,0,0,0,0,0];
592
r = spydr_lmfit(gaussian,x,a,im,w,stdev=1,tol=1e-8,itmax=50,silent=1);
595
a(5:6) = abs(a(5:6));
597
pos = pos + a(3:4)-0.5;
598
angle = (a(7) % 180.) ;
599
angle -= 90.; // w.r.t. vertical axis instead of horizontal axis.
601
tmp2 = a(5); a(5) = a(6); a(6) = tmp2;
604
while (angle > 90) angle -= 180.;
605
while (angle < -90) angle += 180.;
606
//if (angle < 0) {angle = angle+180.;}
607
//if (a(5) < a(6)) {angle = angle+90;}
608
//angle = (angle % 180.) ;
609
fwhm = a(5:6)*2*(-log(0.5))^(1./2.)*pixsize; //gaussian
610
fwhmerr = err(5:6)*2*(-log(0.5))^(1./2.)*pixsize;
611
fwhm = fwhm/airmass^0.6; fwhmerr = fwhmerr/airmass^0.6;
612
ellip = abs(fwhm(2)-fwhm(1))/avg(fwhm);
613
ellerr= 2*(fwhmerr(1)+fwhmerr(2))*(2*fwhm(2))/(fwhm(1)+fwhm(2))^2.;
615
} else if (funtype == "moffat") {
617
// a = [sky,total,xc,yc,fwhm_parameter,beta]
618
ai = [0,sum(im-median(im(*))),d(2)/2.,d(3)/2.,5.,1.7];
619
if (batch_mode) { // we've been given (guessed) coordinates, use them
620
if (spydr_fit_fwhm_estimate==[]) spydr_fit_fwhm_estimate=3.;
621
ai = [0,fluxstar(nloop)*10.,xstar(nloop)-i1+1,ystar(nloop)-j1+1,
622
clip(spydr_fit_fwhm_estimate,2.1,),1.7];
624
// r = spydr_lmfit(moffatRound,x,ai,im,w,tol=1e-6,itmax=50,silent=1);
625
// a = [sky, total, xc, yc, a, b, angle,beta]
626
a = [ai(1),ai(2),ai(3),ai(4),ai(5),ai(5),0.,ai(6)];
627
// a = [ai(1),ai(2),ai(3),ai(4),ai(5),ai(5),0.];
629
lmfit_amin = [0,0,a(3)-3,a(4)-3,a(5)-2,a(6)-2,0,1.];
630
lmfit_amax = [0,0,a(3)+3,a(4)+3,a(5)+2,a(6)+2,0,3.];
632
} else { // interative, no constraints.
633
lmfit_amin = [0,0,0,0,0,0,0,0];
634
lmfit_amax = [0,0,0,0,0,0,0,0];
636
fit = [1,2,3,4,5,6,7,8];
637
r = spydr_lmfit(moffat,x,a,im,w,stdev=1,tol=2e-8,itmax=50,silent=1,fit=fit);
640
a(5:6) = abs(a(5:6));
642
pos = pos + a(3:4)-0.5;
643
angle = (a(7) % 180.) ; //write,format="%f %f %f\n",a(5),a(6),angle;
644
angle -= 90.; // w.r.t. vertical axis instead of horizontal axis.
646
tmp2 = a(5); a(5) = a(6); a(6) = tmp2;
649
while (angle > 90) angle -= 180.;
650
while (angle < -90) angle += 180.;
652
fwhm = 2*a(5:6)*sqrt(0.5^(-1./a(8))-1.)*pixsize; // moffat
653
fwhmerr = fwhm*(err(5:6)/a(5:6)+
654
0.5*abs(log(0.5))*err(8)/a(8)^2.*0.5^(1./a(8))/(0.5^(1./a(8))-1.));
655
fwhm = fwhm/airmass^0.6; fwhmerr = fwhmerr/airmass^0.6;
656
fwhmerr = fwhmerr(sort(fwhm)(::-1));
657
fwhm = fwhm(sort(fwhm)(::-1));
658
ellip = (fwhm(1)-fwhm(2))/avg(fwhm);
659
ellerr = 2*(fwhmerr(1)+fwhmerr(2))*(2*fwhm(2))/(fwhm(1)+fwhm(2))^2.;
663
window,spydr_wins(3);
664
tv,transpose(grow(transpose(im),transpose(tmp),
665
transpose(im-tmp+a(1)))),square=1;
666
plt,"image",0.201,0.89,tosys=0;
667
plt,"fit",0.357,0.89,tosys=0;
668
plt,"image-fit",0.51,0.89,tosys=0;
669
spydr_xytitles,"pixels","pixels";
670
window,spydr_wins(1);
678
if (magswitch) {flux = zp-2.5*log10(clip(a(2),1e-10,));} else {flux = a(2);}
680
yfwhmres.xpos = pos(1);
681
yfwhmres.ypos = pos(2);
682
yfwhmres.xposerr = (*r.stdev)(3);
683
yfwhmres.yposerr = (*r.stdev)(4);
684
yfwhmres.xfwhm = fwhm(1);
685
yfwhmres.yfwhm = fwhm(2);
686
yfwhmres.xfwhmerr = fwhmerr(1);
687
yfwhmres.yfwhmerr = fwhmerr(2);
688
yfwhmres.flux = flux;
689
yfwhmres.fluxerr = (*r.stdev)(2);
691
yfwhmres.elerr = ellerr;
692
yfwhmres.angle = angle;
693
yfwhmres.maxim = maxim;
694
yfwhmres.background = a(1)+sky1+sky2;
696
grow,allres,yfwhmres;
698
msg=swrite(format="%7.2f %7.2f %6.3f+/-%5.3f %6.3f+/-%5.3f %9.1f %4.2f %6.2f %6.1f",
699
pos(1),pos(2),fwhm(1),fwhmerr(1),fwhm(2),fwhmerr(2),flux,ellip,angle,maxim);
700
write,format="%s\n",msg;
702
msg=swrite(format=" x=%.2f | y=%.2f | xfwhm=%.3f | yfwhm=%.3f | flux=%.1f | ell=%.2f | ang=%.2f | max=%.1f | bckgrd=%.1f",
703
pos(1),pos(2),fwhm(1),fwhm(2),flux,ellip,angle,maxim,a(1)+sky1+sky2);
704
msg = msg+" ("+funtype+")";
706
} else if (compute_strehl) {
709
spydr_pyk_error,"Need pixsize set to compute Strehl!";
712
if (spydrs(imnum).wavelength==0) {
713
spydr_pyk_warning,"Need wavelength to compute Strehl!";
717
// determination of background
718
sdim = dimsof(im)(2);
719
rmask = spydr_strehlaper/2.;
720
// write,format="rmask=%f, boxsize=%f\n",rmask*1.,boxsize*1.;
721
if ((2*rmask)>boxsize) {
722
spydr_pyk_error,"boxsize smaller than aperture for Strehl. Increase boxsize or decrease aperture.";
725
smask = (dist(sdim)>rmask);
726
psky = im(where(smask)); // outside of disk
729
w = where( (psky>(median(psky)-psky(rms)*nsig)) & (psky<(median(psky)+psky(rms)*nsig)) );
730
skyavg = psky(w)(avg);
731
skyrms = psky(w)(rms);
733
w = where( (psky>(skyavg-skyrms*nsig)) & (psky<(skyavg+skyrms*nsig)) );
734
hy = histo2(psky(w),hx);
735
ag = [max(hy),hx(wheremax(hy)(1)),3.];
736
clmfit,float(hy),hx,ag,"a(1)*exp(-0.5*((x-a(2))/a(3))^2.)",yfit;
738
skyrms = ag(3)*2.355;
740
curw = current_window();
741
window,spydr_wins(3);
742
fma; limits,square=0; limits;
744
plh,yfit,hx,color="red";
745
spydr_pltitle,swrite(format="Background and fit (avg=%f, rms=%f)",skyavg,skyrms);
746
spydr_xytitles,"value","number in bin";
748
//write,format="a(1) = %f ; sky = %f +/- %f\n",a(1),skyavg,skyrms;
750
fwhmStrehl,im-skyavg,pixsize,spydrs(imnum).wavelength,spydr_teldiam, \
751
spydr_cobs,pstrehl,pfwhm,rmask=rmask,silent=1,source=spydr_sourcediam;
754
pstrehl *= spydr_strehlfudge;
758
if (spydr_plot_in_arcsec) {
759
fact = spydrs(imnum).pixsize;
766
plg,([j2+1,j2+1,j1,j1,j2+1])*fact,([i1,i2+1,i2+1,i1,i1])*fact,color="red";
767
plt,"Sky",(i1)*fact,(j2+1)*fact,justify="LT",tosys=1,color="red",opaque=0;
768
tmp = span(0.,2*pi,100);
769
plg,((j2+j1)/2.+0.5+rmask*sin(tmp))*fact,((i2+i1)/2.+0.5+rmask*cos(tmp))*fact,color="red";
771
nptvalid = pi*rmask^2.;
772
nptsky = numberof(w);
773
strehl_err = skyrms/sqrt(nptsky)*nptvalid/max(im)*pstrehl;
774
// note: FIXME. This is just the strehl error due to the estimation
775
// of the average sky. There is an additional component
776
// due to the noise within the aperture.
777
msg = swrite(format="FWHM = %.3f | strehl = %.2f",pfwhm,pstrehl);
778
write,format="%s wvl=%.3f FWHM=%.3f Strehl=%.2f +/- %.2f (fudge=%.3f)\n",\
779
spydrs(imnum).name,spydrs(imnum).wavelength,pfwhm,pstrehl, \
780
strehl_err,spydr_strehlfudge;
781
//yfwhmres.pstrehl = pstrehl;
782
//yfwhmres.pfwhm = pfwhm;
785
spydr_pyk_status_push,msg;
793
if (!compute_strehl) {
798
avgfwhm = sum((f*1./ferr)(*))/sum(1./ferr(*));
799
// stdfwhm = f(*)(rms);
800
stdfwhm = avg([f(1,)(rms),f(2,)(rms)]); // avg X and Y rms
802
stdel = el(rms)+sqrt(sum(eler^2.))/numberof(eler);
805
msg=swrite(format="Median FWHM : X = %5.3f / Y = %5.3f / <XY> = %6.3f [arcsec]",
806
median(f(1,)),median(f(2,)),avg([median(f(1,)),median(f(2,))]));
808
msg=swrite(format="Median FWHM : X = %6.3f / Y = %6.3f / <XY> = %6.3f [pixel]",
809
median(f(1,)),median(f(2,)),avg([median(f(1,)),median(f(2,))]));
811
write,format="\n%s\n",msg;
812
if (!onepass) spydr_pyk_status_push,msg;
814
spydr_fit_fwhm_estimate = avg([median(f(1,)),median(f(2,))])/pixsize;
815
spydr_fit_background_estimate = avg(allres.background);
816
} else { // compute_strehl
817
spydr_fit_fwhm_estimate = pfwhm/pixsize;
818
spydr_fit_background_estimate = skyavg;
821
spydr_pyk,swrite(format="y_text_parm_update('find_fwhm','%.3f')",spydr_fit_fwhm_estimate);
823
if (compute_strehl) return _(pfwhm,pstrehl);
830
struct lmfit_result {
831
/* DOCUMENT lmfit_result -- structure returned by lmfit
844
pointer stdev_monte_carlo;
848
func spydr_lmfit(f, x, &a, y, w, fit=, correl=, stdev=, gain=, tol=, deriv=, itmax=,
849
lambda=, eps=, monte_carlo=,silent=)
850
/* DOCUMENT spydr_lmfit
851
Non-linear least-squares fit by Levenberg-Marquardt method.
852
This is a local copy, slightly modified, of lmfit.i (in yutils).
853
For help, please refer to the lmfit document section.
854
In general, use lmfit instead of spydr_lmfit.
859
/* Maybe subset of parameters to fit. */
860
if (structof(a)!=double) {
862
if (structof(a)!=double)
863
error, "bad data type for parameters (complex unsupported)";
868
else if (dimsof(fit)(1) == 0)
872
error, "no parameters to fit.";
875
if (is_void(w)) w= 1.0;
876
else if (anyof(w < 0.0))
877
error, "bad weights.";
878
if (numberof(w) != numberof(y))
879
w += array(0.0, dimsof(y));
880
nfree= sum(w != 0.0) - nfit; // Degrees of freedom
882
error, "not enough data points.";
884
/* Other settings. */
885
diag= indgen(1:nfit^2:nfit+1); // Subscripts of diagonal elements
886
if (is_void(lambda)) lambda= 1e-3;
887
if (is_void(gain)) gain= 10.0;
888
if (is_void(itmax)) itmax= 100;
889
if (is_void(eps)) eps= 1e-6; // sqrt(machine_precision)/100
891
error, "bad value for EPS.";
892
if (is_void(tol)) tol= 1e-7;
893
monte_carlo= is_void(monte_carlo) ? 0 : long(monte_carlo);
895
warn= "*** Warning: spydr_lmfit ";
902
m= f(x, a, grad, deriv=1);
904
grad= nfit == na ? grad(*,) : grad(*,fit);
910
inc= eps * abs(a(fit));
911
if (numberof((i= where(inc <= 0.0)))) inc(i)= eps;
912
grad= array(double, numberof(y), nfit);
913
for (i=1; i<=nfit; i++) {
914
anew= a; // Copy current parameters
915
anew(fit(i)) += inc(i);
916
grad(,i)= (f(x,anew)-m)(*)/inc(i);
920
beta= w * (chi2= y-m);
921
if (niter) chi2= chi2new;
922
else chi2= chi2_first= sum(beta * chi2);
923
beta= grad(+,) * beta(*)(+);
924
alpha= ((w(*)(,-) * grad)(+,) * grad(+,));
925
gamma= sqrt(alpha(diag));
926
if (anyof(gamma <= 0.0)) {
927
/* Some derivatives are null (certainly because of rounding
930
if (!silent) write, warn+"founds zero derivatives.";
933
gamma(where(gamma <= 0.0))= eps * max(gamma);
934
if (allof(gamma<=0.0)) goto done; // case where all gamma=0
938
alpha *= gamma(,-) * gamma(-,);
941
alpha(diag)= 1.0 + lambda;
943
anew(fit) += gamma * LUsolve(alpha, beta);
951
if (allof(anew == a)) {
952
/* No change in parameters. */
953
if (!silent) write, warn+"makes no progress.";
960
conv= 2.0*(chi2-chi2new)/(chi2+chi2new);
963
if (niter >= itmax) {
965
write, format=warn+"reached maximum number of iterations (%d).\n",
973
sigma= sqrt(nfree/chi2);
974
result= lmfit_result(neval=neval, niter=niter, nfree=nfree, nfit=nfit,
975
lambda=lambda, chi2_first=chi2_first, chi2_last=chi2, conv=conv,
977
if (correl || stdev) {
978
/* Compute correlation matrice and/or standard deviation vector. */
980
alpha= LUsolve(alpha);
981
if (anyof((tmp1= alpha(diag)) < 0.0))
982
write, format=warn+"%s\n", "found negative variance(s)";
983
tmp1= sqrt(abs(tmp1));
985
/* Standard deviation is rescaled assuming that statistically
986
* chi2 = nfree +/- sqrt(2*nfree). */
987
(tmp2= array(double,na))(fit)= gamma * tmp1 / sigma;
992
alpha *= gamma(-,) * gamma(,-);
994
result.correl= α
996
(tmp2= array(double, na, na))(fit,fit)= alpha;
997
result.correl= &tmp2;
1001
alpha= beta= gamma= []; // Free some memory.
1002
if (monte_carlo >= 1) {
1004
sig= (w > 0.0) /(sqrt(max(nfree/chi2*w, 0.0)) + (w == 0.0));
1005
for (i=1; i<=monte_carlo; i++) {
1007
// ynew= y + sig * random_normal(dimsof(y));
1008
ynew= y + sig * random_n(dimsof(y));
1009
spydr_lmfit, f, x, anew, ynew, w, fit=fit, gain=gain, tol=tol,
1010
deriv=deriv, itmax=itmax, lambda=lambda, eps=eps;
1014
result.monte_carlo= monte_carlo;
1015
result.stdev_monte_carlo= &sqrt(saa / monte_carlo);