First load the libraries we need:
library(knitr)
library(ProFit)
library(ProFound)
library(FITSio)
library(RColorBrewer)
So, you want to profile a galaxy, and you have next to no information about it. Well with ProFit no segmentation map, no sigma map, no PSF, no gain and no sky subtraction is (almost) no problem. Thanks to a number of utility functions we can achieve a reasonable fit bootstrapping directly from the data.
As an example, our mystery data will also be in a fairly crowded region, which will either require good (and conservative) image segmentation, or multiple object fitting.
First load the data:
image=readFITS(system.file("extdata", 'VIKING/mystery_VIKING_Z.fits', package="ProFound"))$imDat
And take a look at what we have got:
magimage(image, hicut=1)
It is Z-band data from the VIKING survey, and in the second image it is possible to see that the bright source close to our spiral galaxy of interest in the centre is in fact two very bright stars.
The first thing to do is to create an object segmentation of the full image. Here we input the known magnitude zero-point for this frame (30), and the pixel scale for VISTA VIRCAM (0.34 asec/pix). If these are not provided then outputs would be in terms on ADUs and pixels rather than AB mag and asec.
segim=profoundMakeSegim(image, magzero=30, pixscale=0.34, plot=TRUE)
This is a pretty decent start, and it has identified all the main sources of interest. We can look at the object information in the attached table:
head(segim$segstats)
## segID uniqueID xcen ycen xmax ymax RAcen Deccen RAmax Decmax
## 1 1 222149 220.8006 148.816985 221.5 148.5 NA NA NA NA
## 2 2 227142 226.6080 140.860108 226.5 141.5 NA NA NA NA
## 3 3 194148 194.6212 147.320906 193.5 147.5 NA NA NA NA
## 4 4 122001 120.8390 1.762258 121.5 0.5 NA NA NA NA
## 5 5 175133 175.0705 132.687395 174.5 132.5 NA NA NA NA
## 6 6 178179 177.3220 179.112582 177.5 178.5 NA NA NA NA
## sep flux mag cenfrac N50 N90 N100
## 1 0.2610818 2793220.921 13.88474 0.033672294 25.50136 125.05904 1369
## 2 0.2206382 1179596.316 14.82067 0.028486958 31.47948 160.36872 1637
## 3 0.3860557 71540.759 17.86362 0.024208192 54.92371 533.79528 1004
## 4 0.4844438 4470.405 20.87413 0.074356865 12.58724 52.52055 90
## 5 0.2041593 29987.584 18.80765 0.008405805 116.48915 429.49826 702
## 6 0.2168907 41397.227 18.45757 0.005463447 159.26901 524.66905 837
## R50 R90 R100 SB_N50 SB_N90 SB_N100 xsd ysd
## 1 1.0473543 2.319367 7.673857 15.81111 16.89931 19.38314 3.297982 3.327806
## 2 1.1940494 2.695059 8.610589 16.97570 18.10525 20.51318 4.147727 3.844814
## 3 1.8620163 5.804849 7.961051 20.62299 22.45384 23.02535 4.669932 7.863550
## 4 0.9688225 1.978990 2.590600 22.03393 22.94674 23.41713 2.908660 1.435360
## 5 2.7956724 5.368145 6.862970 22.38333 23.16184 23.58088 6.211350 5.698805
## 6 4.2104311 7.641940 9.652141 22.37287 23.02907 23.42178 8.568237 8.189845
## covxy corxy con asymm flux_reflect mag_reflect
## 1 1.69717653 0.154639642 0.4515690 NA NA NA
## 2 3.04309939 0.190822970 0.4430512 NA NA NA
## 3 -4.91777329 -0.133918109 0.3207691 NA NA NA
## 4 0.02959194 0.007087932 0.4895539 NA NA NA
## 5 -18.85275159 -0.532604602 0.5207893 NA NA NA
## 6 -56.31392793 -0.802506650 0.5509636 NA NA NA
## semimaj semimin axrat ang signif FPlim flux_err mag_err
## 1 3.560272 3.045560 0.8554291 136.665909 -Inf 2.297260 NA NA
## 2 4.389550 3.566243 0.8124393 124.153850 -Inf 2.228710 NA NA
## 3 7.901314 4.605748 0.5829091 6.902655 -Inf 2.412490 NA NA
## 4 2.908684 1.435312 0.4934576 90.264911 -Inf 3.190499 NA NA
## 5 7.390998 4.053442 0.5484296 49.598169 -Inf 2.540224 NA NA
## 6 11.253766 3.720352 0.3305873 46.611219 -Inf 2.478096 NA NA
## flux_err_sky flux_err_skyRMS flux_err_shot sky_mean sky_sum skyRMS_mean
## 1 NA NA 0 NA NA NA
## 2 NA NA 0 NA NA NA
## 3 NA NA 0 NA NA NA
## 4 NA NA 0 NA NA NA
## 5 NA NA 0 NA NA NA
## 6 NA NA 0 NA NA NA
## Nedge Nsky Nobject Nborder Nmask edge_frac edge_excess flag_border
## 1 NA NA NA NA NA NA NA NA
## 2 NA NA NA NA NA NA NA NA
## 3 NA NA NA NA NA NA NA NA
## 4 NA NA NA NA NA NA NA NA
## 5 NA NA NA NA NA NA NA NA
## 6 NA NA NA NA NA NA NA NA
The output is approximately in descending flux order. This data happened to have a mag-zero point of 30, so we can extract approximate magnitudes already. Our object of interest is the galaxy near the centre of the frame (178,178), so we already have approximate parameter properties for fitting purposes. E.g. the magnitude to start with is going to be close to 18.55 (notice since we provided a magzero argument before the flux is given in magnitudes already). This number will be useful later.
The next step is to expand out the segmantation for our target galaxy. this is a good idea because we want our model fit to descend into sky dominated pixels and not to be tightly defined by bright central pixels:
segim_expand=profoundMakeSegimExpand(image, segim$segim, expand=6, expandsigma=3, skycut=-1, magzero=30, pixscale=0.34, rotstats=TRUE, plot=TRUE)
Notice because we set expand=6 only our object of interest (with segID=6) has been expanded. The other sources are left as they were.
We can use this output to calculate an approximate sky surface brightness limit (taken as being the RMS of the sky):
SBlim=profitFlux2SB(segim_expand$skyRMS, magzero=30, pixscale=0.34)
print(SBlim)
## [1] 25.32381
Given this segmentation we can look at some statistics on our apparent sky pixels.
object_frac=length(which(segim_expand$objects==1))/length(image)
qnorm(1-object_frac)
## [1] 1.392283
The above tells us that at a skycut level of ~1.4 is the point where we would expect 50% of pixels to be False-Positive (i.e. positive sky fluctuations) rather than True-Positives (associated with real sources). If we cut higher than this then most of our pixels at the surface brightness threshold will be real.
It is also instructive to make the qqplot of sky pixels:
temp=qqnorm(sort((image[segim_expand$objects==0]-segim_expand$sky)/segim_expand$skyRMS), plot.it = FALSE)
magplot(0,0,xlim=c(-5,5),ylim=c(-5,5), grid=TRUE, type='n', xlab='Normal Quantile', ylab='Sky Pixel Quantile')
lines(temp)
abline(0,1,col='red')
As we should hope, the qqplot crosses at very close to [0,0], but we can see evidence of a sytematic excess for positive flux in our sky pixels. This is to be expected- the Universe contains only positive sources (resolved or not, faint or bright) so if we have Normal statistics to the negative side, we should always see an excess on the positive side. Given this nature of the astrophysical sky, we look to have recovered the absolute sky and RMS of the sky to within a few percent (try perturbing the sky and skyRMS values to see how this affects the qqplot).
Now we have a decent segmentation we can estimate the image gain and the sigma map:
gain=profoundGainEst(image,objects=segim_expand$objects, sky=segim_expand$sky, skyRMS=segim_expand$skyRMS)
gain
## [1] 0.5374133
The number found is ~0.5. Since the gain estimation routine is typically accurate to a factor of a few, it is quite likely that the image is already in photo-electron counts (i.e. gain=1), but we will continue with our lower estimate regardless.
Now we have the segmentation, sky estimates and the gain, we can make a sgima map:
sigma=profoundMakeSigma(image, objects=segim_expand$objects, gain=gain, sky=segim_expand$sky, skyRMS =segim_expand$skyRMS, plot=TRUE)
The sigma map will look much liek our original image, but with sky pixels set to a fixed value of uncertainty, and object pixels having an additional component of shot-noise.
Next we want to find the PSF for fitting, so we should extract a a bright but non-saturated star from the list above:
magplot(segim_expand$segstats$R50, segim_expand$segstats$con,log='x', ylim=c(0,1), col=hsv(magmap(segim_expand$segstats$axrat, flip=TRUE)$map), xlab='Major Axis / pix', ylab='Concentration')
magbar('topleft', title='Axrat', titleshift=1)
abline(h=c(0.4,0.65), lty=2)
abline(v=c(0.7,1.2), lty=2)
magplot(segim_expand$segstats$SB_N50, segim_expand$segstats$con, ylim=c(0,1), col=hsv(magmap(segim_expand$segstats$axrat, flip=TRUE)$map), xlab='SB[50] / mag/asec^2', ylab='Concentration', grid=TRUE)
magbar('topleft', title='Axrat', titleshift=1)
abline(v=c(23.5,2), lty=2)
magplot( segim_expand$segstats$asymm, segim_expand$segstats$con, ylim=c(0,1), col=hsv(magmap(segim_expand$segstats$axrat, flip=TRUE)$map), xlab='Asymmetry', ylab='Concentration', grid=TRUE)
magbar('topleft', title='Axrat', titleshift=1)
abline(v=c(0.4), lty=2)
We want to extract very red (axial ratio ~ 1) stars from the above region contained by the horizontal and vertical dashed lines:
starlist=segim_expand$segstats[segim_expand$segstats$axrat>0.9 & segim_expand$segstats$con>0.4 & segim_expand$segstats$con<0.65 & segim_expand$segstats$R50>0.7 & segim_expand$segstats$R50<1.2 & segim_expand$segstats$SB_N50<23.5 & segim_expand$segstats$asymm<0.2,]
print(starlist)
## segID uniqueID xcen ycen xmax ymax RAcen Deccen RAmax
## 8 8 155320 154.16633 319.79331 154.5 319.5 NA NA NA
## 9 9 149074 148.57386 73.66517 148.5 73.5 NA NA NA
## 11 11 11270 10.07722 269.39516 10.5 269.5 NA NA NA
## 15 15 23196 22.24594 196.18791 22.5 195.5 NA NA NA
## 16 16 247350 247.57572 350.29745 246.5 349.5 NA NA NA
## Decmax sep flux mag cenfrac N50 N90 N100
## 8 NA 0.15104880 5510.949 20.64693 0.02919578 30.60758 107.14359 162
## 9 NA 0.06151681 4207.171 20.94002 0.04094768 22.56507 78.48507 125
## 11 NA 0.14809991 4302.862 20.91561 0.02689790 32.56247 89.04415 132
## 15 NA 0.24933092 2994.224 21.30929 0.03332703 25.30008 75.92407 106
## 16 NA 0.45528234 2324.345 21.58425 0.03267639 25.16386 67.42119 89
## R50 R90 R100 SB_N50 SB_N90 SB_N100 xsd ysd
## 8 1.096784 2.052059 2.523272 22.77148 23.49364 23.82812 2.787881 2.825894
## 9 0.928727 1.732061 2.185872 22.73359 23.44878 23.83969 2.467070 2.407673
## 11 1.131449 1.871023 2.278051 23.10737 23.56141 23.87444 2.531516 2.669244
## 15 1.014427 1.757316 2.076409 23.22706 23.78203 24.02995 2.480545 2.367198
## 16 0.997193 1.632258 1.875364 23.49616 23.92803 24.11512 2.237498 2.402598
## covxy corxy con asymm flux_reflect mag_reflect
## 8 -0.50710550 -0.064367735 0.5344799 0.1537517 6011.061 20.55262
## 9 0.17362276 0.029229927 0.5361975 0.1626332 4184.797 20.94581
## 11 -0.26784025 -0.039637561 0.6047223 0.1384214 4268.729 20.92425
## 15 0.51872770 0.088340068 0.5772597 0.1850167 3334.711 21.19235
## 16 0.02276788 0.004235247 0.6109286 0.1557238 2496.639 21.50661
## semimaj semimin axrat ang signif FPlim flux_err mag_err
## 8 2.897790 2.713074 0.9362561 39.05886 -Inf 3.016573 NA NA
## 9 2.483490 2.390732 0.9626500 115.08864 -Inf 3.094326 NA NA
## 11 2.685878 2.513861 0.9359552 18.39557 -Inf 3.078126 NA NA
## 15 2.542707 2.300298 0.9046652 121.04624 -Inf 3.142911 NA NA
## 16 2.402739 2.237347 0.9311653 178.29917 -Inf 3.193726 NA NA
## flux_err_sky flux_err_skyRMS flux_err_shot sky_mean sky_sum skyRMS_mean
## 8 NA NA 0 NA NA NA
## 9 NA NA 0 NA NA NA
## 11 NA NA 0 NA NA NA
## 15 NA NA 0 NA NA NA
## 16 NA NA 0 NA NA NA
## Nedge Nsky Nobject Nborder Nmask edge_frac edge_excess flag_border
## 8 NA NA NA NA NA NA NA NA
## 9 NA NA NA NA NA NA NA NA
## 11 NA NA NA NA NA NA NA NA
## 15 NA NA NA NA NA NA NA NA
## 16 NA NA NA NA NA NA NA NA
There are 5 sensible looking options it seems. We can extract the brightest of these likely stars to do our rough PSF estimate. When doing this in anger you might want to consider fitting all 5 stars (or however many make it through the selection cuts above). This gives you a way to marginalise over the uncertainty in the PSF (I discuss PSF uncertainty in some more detail at the end of this vignette).
psf_image=magcutout(image, loc=starlist[1,c('xcen','ycen')], box=c(31,31))
psf_sigma=magcutout(sigma, loc=starlist[1,c('xcen','ycen')], box=c(31,31))
psf_segim=magcutout(segim_expand$segim, loc=starlist[1,c('xcen','ycen')], box=c(31,31))
Look at our target star to check we are happy:
magimage(psf_image$image)
magimage(psf_sigma$image)
magimage(psf_segim$image)
Next we make our initial model, where we take our PSF estimates from the segmentation stats:
psf_x=psf_image$loc[1]
psf_y=psf_image$loc[2]
psf_mag=starlist[1,'mag']
psf_fwhm=starlist[1,'R50']*2/0.339
psf_con=1/starlist[1,'con']
psf_modellist=list(
moffat=list(
xcen=psf_x,
ycen=psf_y,
mag=psf_mag,
fwhm=psf_fwhm,
con=psf_con,
axrat=1,
box=0
)
)
We also need to set up the tofit, tolog and intervals structure
psf_tofit=list(
moffat=list(
xcen=TRUE,
ycen=TRUE,
mag=TRUE,
fwhm=TRUE,
con=TRUE,
axrat=FALSE,
box=FALSE
)
)
psf_tolog=list(
moffat=list(
xcen=FALSE,
ycen=FALSE,
mag=FALSE,
fwhm=TRUE,
con=TRUE,
axrat=FALSE,
box=FALSE
)
)
psf_intervals=list(
moffat=list(
xcen=list(psf_x+c(-5,5)),
ycen=list(psf_y+c(-5,5)),
mag=list(psf_mag+c(-2,2)),
fwhm=list(c(1,10)),
con=list(c(1,10)),
axrat=list(c(0.1,1)),
box=list(c(-1,1))
)
)
Since there are no other sources in the frame we can use the full psf_image to attempt our profile fit (so no need to provide psf_segim):
psf_Data=profitSetupData(psf_image$image, sigma=psf_sigma$image, modellist=psf_modellist, tofit=psf_tofit, tolog=psf_tolog, magzero=30, algo.func='optim', intervals=psf_intervals)
We can check our initial guess easily:
profitLikeModel(parm=psf_Data$init, Data=psf_Data, makeplots=TRUE, plotchisq=TRUE)
Not bad, but not perfect. We can do an optim fit to improve things:
psf_fit=optim(psf_Data$init, profitLikeModel, method='BFGS', Data=psf_Data, control=list(fnscale=-1))
Now we plot the output:
profitLikeModel(parm=psf_fit$par, Data=psf_Data, makeplots=TRUE, plotchisq=TRUE)
## Warning in dt(x, tdof): NaNs produced
This looks like a very good fit. We will now contruct our model PSF:
psf_modellist_fit=profitRemakeModellist(parm=psf_fit$par, Data=psf_Data)$modellist
psf_modellist_fit$moffat$xcen=25/2
psf_modellist_fit$moffat$ycen=25/2
psf_model=profitMakeModel(modellist=psf_modellist_fit, dim=c(25,25))$z
And now we can look at our final PSF model:
magimage(psf_model)
Now we have all the key bits we need in order to fit our target galaxy!
A more complicated alternative is to fit all the good stars (5 in this case) with the same profile simultaneously. Basically we have 5 PSFs distributed around our image, but we want to fit them all with the same PSF, modulo different xcen, ycen and mag:
psf_x=starlist$xcen
psf_y=starlist$ycen
psf_mag=starlist$mag
psf_fwhm=starlist[1,'R50']*2/0.339
psf_con=1/starlist[1,'con']
psf_modellist2=list(
pointsource = list(
xcen = psf_x,
ycen = psf_y,
mag = psf_mag
),
psf=list(
moffat=list(
mag=0,
fwhm=psf_fwhm,
con=psf_con,
axrat=1,
box=0
)
)
)
We need to make our own region map since our object to fit will not be centrally located. First we should expand our segments of interest (not always vital- it depends whether you think there is much PSF flux hidden in broad wings or not):
segim_expand_psf=profoundMakeSegimExpand(image, segim$segim, expand=starlist$segID, expandsigma=3, skycut=-1, magzero=30, pixscale=0.34, plot=TRUE)
psf_region=matrix(0, dim(image)[1], dim(image)[2])
psf_region[segim_expand$segim %in% starlist$segID]=1
We now need to prepare a more complicated fitting structure. We want to fit the 5 stars with the same PSF, modulo different xcen, ycen and mag:
psf_tofit2=list(
pointsource = list(
xcen = rep(TRUE,5),
ycen = rep(TRUE,5),
mag = rep(TRUE,5)
),
psf=list(
moffat=list(
mag=FALSE,
fwhm=TRUE,
con=TRUE,
axrat=FALSE,
box=FALSE
)
)
)
psf_tolog2=list(
pointsource = list(
xcen = rep(FALSE,5),
ycen = rep(FALSE,5),
mag = rep(FALSE,5)
),
psf=list(
moffat=list(
mag=FALSE,
fwhm=TRUE,
con=TRUE,
axrat=TRUE,
box=TRUE
)
)
)
psf_intervals2=list(
pointsource = list(
xcen=list(starlist$xcen[1]+c(-5,5), starlist$xcen[2]+c(-5,5), starlist$xcen[3]+c(-5,5), starlist$xcen[4]+c(-5,5), starlist$xcen[5]+c(-5,5)),
ycen=list(starlist$ycen[1]+c(-5,5), starlist$ycen[2]+c(-5,5), starlist$ycen[3]+c(-5,5), starlist$ycen[4]+c(-5,5), starlist$ycen[5]+c(-5,5)),
mag=list(starlist$mag[1]+c(-2,2), starlist$mag[2]+c(-2,2), starlist$mag[3]+c(-2,2), starlist$mag[4]+c(-2,2), starlist$mag[5]+c(-2,2))
),
psf=list(
moffat=list(
mag=list(c(-1, 1)),
fwhm=list(c(1,10)),
con=list(c(1,10)),
axrat=list(c(0.1,1)),
box=list(c(-1,1))
)
)
)
From here everything is much simpler again, since we just feed our prepared objects into our setup function as per usual:
psf_Data2=profitSetupData(image, sigma=sigma, modellist=psf_modellist2, tofit=psf_tofit2, tolog=psf_tolog2, magzero=30, algo.func='optim', intervals=psf_intervals2, region=psf_region)
We check the initial guess. Notice the three stars are visible as the three circled regions. Given the image scale it is a bit tricky to see how good our initial guesses actually were, but probably not too bad.
profitLikeModel(parm=psf_Data2$init, Data=psf_Data2, makeplots=TRUE, plotchisq=TRUE)
So like before, not bad, but not perfect. We can do an optim fit to improve things:
psf_fit2=optim(psf_Data2$init, profitLikeModel, method='BFGS', Data=psf_Data2, control=list(fnscale=-1))
Now we plot the output:
profitLikeModel(parm=psf_fit2$par, Data=psf_Data2, makeplots=TRUE, plotchisq=TRUE)
## Warning in dt(x, tdof): NaNs produced
Maybe useful to zoom in a bit:
psf_modellist_fit2=profitRemakeModellist(parm=psf_fit2$par, Data=psf_Data2)$modellist
psf_model_full2=profitMakeModel(psf_modellist_fit2,dim=dim(image),magzero=30)$z
for(i in 1:5){
magimage(magcutout(image, loc=starlist[i,c('xcen','ycen')], box=c(51,51))$image, col=rev(colorRampPalette(brewer.pal(9, "RdYlBu"))(100)), magmap=FALSE, zlim=c(-2e2,2e2))
magimage(magcutout(image-psf_model_full2, loc=starlist[i,c('xcen','ycen')], box=c(51,51))$image, col=rev(colorRampPalette(brewer.pal(9, "RdYlBu"))(100)), magmap=FALSE, zlim=c(-2e2,2e2))
}
This looks like a very good fit. We will now contruct our model PSF:
psf_modellist_fit2$psf$moffat$xcen=25/2
psf_modellist_fit2$psf$moffat$ycen=25/2
psf_model2=profitMakeModel(modellist=psf_modellist_fit2$psf, magzero=30, dim=c(25,25))$z
And now we can look at our final PSF model:
magimage(psf_model2)
Much like we did with the PSF fit, we have to extract the region of interest and setup our fitting structures:
gal_image=magcutout(image, loc=segim_expand$segstats[6,c('xcen','ycen')], box=c(101,101))
gal_sigma=magcutout(sigma, loc=segim_expand$segstats[6,c('xcen','ycen')], box=c(101,101))
gal_segim=magcutout(segim_expand$segim, loc=segim_expand$segstats[6,c('xcen','ycen')], box=c(101,101))
Check the cutout regions:
magimage(gal_image$image)
magimage(gal_sigma$image)
magimage(gal_segim$image)
As before, we have to setup some reasonable guesses for our galaxy fit. We give it a very faint initial bulge since we can tell it is a very disky system and we are only using a simple optimiser so we do not want it to get stuck in an odd local minima with a massive bulge. To do this properly and with psychic inputs you would use a better optimiser (e.g. full MCMC or genetic algorithm).
gal_x=gal_image$loc[1]
gal_y=gal_image$loc[2]
gal_mag=segim_expand$segstats[6,'mag']
gal_re=segim_expand$segstats[6,'R50']/0.339
gal_ang=segim_expand$segstats[6,'ang']
gal_axrat=segim_expand$segstats[6,'axrat']
gal_modellist=list(
sersic=list(
xcen=rep(gal_x,2),
ycen=rep(gal_y,2),
mag=rep(gal_mag,2)+c(2,0.7),
re=c(gal_re/4,gal_re),
nser=c(4,1),
ang=c(0,gal_ang),
axrat=c(1,gal_axrat),
box=rep(0,2)
)
)
And now we make the other inputs we need for our fitting Data structure:
gal_tofit=list(
sersic=list(
xcen= c(TRUE,NA), #We fit for xcen and tie the two togther
ycen= c(TRUE,NA), #We fit for ycen and tie the two togther
mag= c(TRUE,TRUE), #Fit for both
re= c(TRUE,TRUE), #Fit for both
nser= c(FALSE,FALSE), #Fit for neither
ang= c(FALSE,TRUE), #Fit for disk
axrat= c(FALSE,TRUE), #Fit for disk
box= c(FALSE,FALSE) #Fit for neither
)
)
gal_tolog=list(
sersic=list(
xcen= c(FALSE,FALSE),
ycen= c(FALSE,FALSE),
mag= c(FALSE,FALSE),
re= c(TRUE,TRUE), #re is best fit in log space
nser= c(TRUE,TRUE), #nser is best fit in log space
ang= c(FALSE,FALSE),
axrat= c(TRUE,TRUE), #axrat is best fit in log space
box= c(FALSE,FALSE)
)
)
gal_intervals=list(
sersic=list(
xcen=list(lim=gal_x+c(-5,5),lim=gal_x+c(-5,5)),
ycen=list(lim=gal_y+c(-5,5),gal_y+c(-5,5)),
mag=list(lim=c(10,30),lim=c(10,30)),
re=list(lim=c(1,5),lim=c(1,100)),
nser=list(lim=c(0.5,20),lim=c(0.5,20)),
ang=list(lim=c(-180,360),lim=c(-180,360)),
axrat=list(lim=c(0.001,1),lim=c(0.001,1)),
box=list(lim=c(-1,1),lim=c(-1,1))
)
)
Now we can set up our full fitting structure with averything we have made (notice we use our simpler PSF model here):
gal_Data=profitSetupData(gal_image$image, sigma=gal_sigma$image, modellist=gal_modellist, tofit=gal_tofit, tolog=gal_tolog, magzero=30, algo.func='optim', intervals=gal_intervals, psf=psf_model, segim=gal_segim$image)
profitLikeModel(parm=gal_Data$init, Data=gal_Data, makeplots=TRUE, plotchisq=TRUE)
Not a bad start, but certainly not great. Let’s do an optim fit:
gal_fit=optim(gal_Data$init, profitLikeModel, method='BFGS', Data=gal_Data, control=list(fnscale=-1))
We can now recreate our optimised modellist:
gal_fit_modellist=profitRemakeModellist(gal_fit$par, Data=gal_Data)$modellist
print(gal_fit_modellist)
## $sersic
## $sersic$xcen
## x x
## 51.50431 51.50431
##
## $sersic$ycen
## y y
## 49.62235 49.62235
##
## $sersic$mag
## [1] 23.43033 18.36817
##
## $sersic$re
## [1] 5.00000 14.27345
##
## $sersic$nser
## [1] 4 1
##
## $sersic$ang
## [1] 0.0000 44.3247
##
## $sersic$axrat
## [1] 1.00000000 0.09618156
##
## $sersic$box
## [1] 0 0
And we can then plot the output:
profitLikeModel(parm=gal_fit$par, Data=gal_Data, makeplots=TRUE, plotchisq=TRUE)
## Warning in dt(x, tdof): NaNs produced
profitEllipsePlot(Data=gal_Data, modellist=gal_fit_modellist, pixscale=0.34, SBlim=SBlim)
This looks pretty good!
So there we have it- using image pixel data and no header information except for the magnitude zero-point (which we can always calibrate later by matching to known sources) we have managed to achieve a very reasonable looking galaxy fit in a complex region with bright nearby contaminating sources. Our full chain of work was:
Nearly all of the above can be automated for user data of interest, and should act as a good outline of how you can use ProFit to fit a range of data where not all of the meta information is to hand.
For various reasons users might get better results from using, e.g., SExtractor as part of their processing pipeline rather than the internal utility functions, but there should be enough to get you going on your virtuous path of galaxy fitting ;-)
In all of this, the biggest gotcha is no doubt achieving a decent estimate of the PSF. Here we fit to a single star using a Moffat function, but with HST data you will almost certainly need to use Tiny-Tim, and many people advocate using shapelet fitting programs like PSF-Ex and their ilk.
For space derived data you probably do want to use tools provided by the relevant facility, but for most ground imaging better results can be achieved through fitting analytic models to stars near your target galaxy (ideally multiple stars, so you can reject bad fits etc.) and constructing an idealised PSF from the analytic model. If you are feeling really adventurous you could even marginalise over your uncertainty in the PSF by re-doing your fit with samples from the PSF model posterior.
The other parts of the process mentioned above are much more straight-forward, and there are lots of routes (including the above) that will give you decent results. In my experience when things go badly wrong (a few percent of the time when trying to fully automate all of this) the main culprit is bad segmentation due to nearby very bright stars. In these situations you probably should not be trying to achieve a good fit given the data quality. Issues with the sigma maps going wrong are easily spotted when looking at the error residuals for a good fit- the main culprit here will usually be a mistake in the gain (e.g. using inverse gain as gain). Remember: image ADUs x gain = image photo-electrons! If really unsure use the provided profitGainEst function to sanity check your value.