Astroimaging filter offset determination for best focus

G. Shaughnessy 9/18/2017

This is an attempt to measure both the focus offsets and drift with temperature for the LRGB filters I use for astro-imaging. I'm using Astrodon LRGB E-series Gen2 filters with a TMB130SS, which is f/7 native and a focal reducer/flattener from Astronomics with a 0.8x magnification factor.

The data was gathered on vdb142, the Elephant's Trunk Nebula, over one night. Images were taken with Sequence Generator Pro, and autofocus was set to occur before each filter change. The filter sequence was RGBRGBRGB... and managed to get 15-16 subs per filter. I had forgotten to include Luminance for some reason, so halfway through the run, I added six Luminance exposures to help determine the filter offset from Luminance.

Preliminaries

In [29]:
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
from astropy.io import fits
from os import listdir
from os.path import isfile, join
matplotlib.rcParams['figure.figsize'] = (12.0, 8.0)
matplotlib.rcParams['font.size']=14
import pandas as pd
import numpy as np
import seaborn as sns
In [39]:
def getFocusParms(file,x):
    hdulist=fits.open(file)
    header=hdulist[0].header
    x.append([file,header['DATE-LOC'],header['FILTER'],header['FOCTEMP'],header['FOCPOS'],header['TEMPERAT'],header['AIRMASS'],header['wgt']])
    return x
In [40]:
paths=['2017-09-17/accepted/']
files=[path+f for path in paths for f in listdir(path) if isfile(join(path,f)) ]
print 'Number of total fits files: ',len(files)
Number of total fits files:  58

Import and list details of fits files.

In [42]:
x=[]
for f in files:
    x=getFocusParms(f,x)
df=pd.DataFrame(x,columns=('file','date','Filter','Focuser Temperature','Focuser Position','Air Temperature','Airmass','FWHM'))    

df.sort_values(by='date')
Out[42]:
file date Filter Focuser Temperature Focuser Position Air Temperature Airmass FWHM
17 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_000... 2017-09-18T01:27:13 R 16.266667 39843.0 16.7 1.104917 3.1300
14 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_000... 2017-09-18T01:35:19 G 15.813333 39768.0 16.7 1.095766 3.2130
47 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_000... 2017-09-18T01:43:36 B 15.193333 39750.0 16.6 1.087482 3.1210
51 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_000... 2017-09-18T01:52:00 R 14.533333 39679.0 16.6 1.079641 3.4070
54 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_000... 2017-09-18T02:00:06 G 14.086667 39711.0 16.6 1.072542 3.4580
0 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_000... 2017-09-18T02:08:27 B 13.606667 39609.0 16.6 1.066100 3.7070
52 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_000... 2017-09-18T02:16:48 R 13.113333 39508.0 15.6 1.060144 3.9340
25 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_000... 2017-09-18T02:24:54 G 12.960000 39506.0 15.6 1.054888 3.4340
12 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_000... 2017-09-18T02:33:25 B 12.580000 39486.0 15.6 1.050172 3.0950
48 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_000... 2017-09-18T02:41:44 R 12.386667 39492.0 15.5 1.045986 3.1710
57 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_000... 2017-09-18T02:49:53 G 12.213333 39442.0 15.0 1.042461 2.9590
21 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_000... 2017-09-18T02:57:59 B 12.346667 39391.0 15.0 1.039527 3.0840
5 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_000... 2017-09-18T03:06:08 R 12.473333 39417.0 14.1 1.037134 2.8530
26 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_000... 2017-09-18T03:14:12 G 12.600000 39414.0 14.0 1.035286 2.5730
34 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_000... 2017-09-18T03:22:17 B 12.573333 39386.0 14.0 1.033967 2.6450
29 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_000... 2017-09-18T03:34:12 R 12.400000 39376.0 14.0 1.032909 2.4720
36 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_000... 2017-09-18T03:42:24 G 12.606667 39366.0 13.5 1.032948 2.3080
46 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_000... 2017-09-18T03:53:51 B 12.693333 39291.0 13.5 1.033720 2.8210
13 2017-09-17/accepted/vdb142_L_4x4_-20C_0005_000... 2017-09-18T04:01:53 L 12.606667 39301.0 13.5 1.034630 0.9733
28 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_000... 2017-09-18T04:04:54 R 12.440000 39174.0 12.9 1.035687 3.7660
56 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_000... 2017-09-18T04:13:24 G 12.033333 39250.0 12.9 1.037770 3.0460
50 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_000... 2017-09-18T04:21:30 B 11.893333 39192.0 12.8 1.040357 2.4730
35 2017-09-17/accepted/vdb142_L_4x4_-20C_0005_000... 2017-09-18T04:29:17 L 11.846667 39270.0 12.8 1.042284 1.0510
22 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_000... 2017-09-18T04:31:56 R 11.640000 39141.0 12.8 1.044407 3.6140
31 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_000... 2017-09-18T04:40:13 G 11.520000 39044.0 12.5 1.048239 3.8080
42 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_000... 2017-09-18T04:48:25 B 11.646667 39134.0 12.5 1.052655 2.8830
39 2017-09-17/accepted/vdb142_L_4x4_-20C_0005_000... 2017-09-18T04:56:19 L 11.680000 39134.0 12.5 1.055865 0.9448
49 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_000... 2017-09-18T04:59:02 R 11.740000 39117.0 12.5 1.059180 3.0560
8 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_000... 2017-09-18T05:07:19 G 11.493333 39103.0 12.5 1.064926 3.0500
6 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_000... 2017-09-18T05:15:46 B 11.233333 39132.0 11.7 1.071398 2.6120
44 2017-09-17/accepted/vdb142_L_4x4_-20C_0005_000... 2017-09-18T05:23:45 L 11.233333 39119.0 11.7 1.076089 0.9442
30 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_001... 2017-09-18T05:26:19 R 11.420000 39071.0 11.7 1.080557 2.7550
2 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_001... 2017-09-18T05:34:35 G 11.506667 39061.0 11.7 1.088363 2.7150
40 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_001... 2017-09-18T05:42:51 B 11.680000 39064.0 11.7 1.096826 2.6520
33 2017-09-17/accepted/vdb142_L_4x4_-20C_0005_000... 2017-09-18T05:50:46 L 11.700000 39069.0 11.5 1.102789 0.9803
37 2017-09-17/accepted/vdb142_L_4x4_-20C_0005_000... 2017-09-18T05:50:46 L 11.700000 39069.0 11.5 1.102789 0.9803
4 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_001... 2017-09-18T05:53:38 R 11.800000 39041.0 11.5 1.108804 2.7350
7 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_001... 2017-09-18T06:01:51 G 11.800000 39053.0 11.5 1.118852 2.6090
18 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_001... 2017-09-18T06:09:56 B 11.753333 39103.0 10.8 1.129462 2.1960
9 2017-09-17/accepted/vdb142_L_4x4_-20C_0005_000... 2017-09-18T06:17:43 L 11.706667 39038.0 10.8 1.136737 0.9623
55 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_001... 2017-09-18T06:20:31 R 11.746667 38908.0 10.8 1.144111 3.5210
1 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_001... 2017-09-18T06:28:42 G 11.680000 38979.0 10.8 1.156494 2.6000
45 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_001... 2017-09-18T06:36:46 B 11.633333 38968.0 10.4 1.169457 2.5090
38 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_001... 2017-09-18T06:45:01 R 11.600000 39062.0 10.4 1.183288 2.5960
23 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_001... 2017-09-18T06:53:15 G 11.520000 38920.0 10.4 1.198103 3.0340
24 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_001... 2017-09-18T07:01:21 B 11.473333 38926.0 10.4 1.213561 2.8300
20 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_001... 2017-09-18T07:09:36 R 11.426667 38968.0 10.3 1.229993 2.9060
11 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_001... 2017-09-18T07:17:53 G 11.340000 38889.0 10.3 1.247555 3.6120
53 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_001... 2017-09-18T07:25:55 B 11.313333 38867.0 10.3 1.265766 3.5980
19 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_001... 2017-09-18T07:34:04 R 11.300000 38868.0 10.3 1.284881 3.8760
10 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_001... 2017-09-18T07:42:28 G 11.300000 38863.0 11.1 1.305450 3.6670
16 2017-09-17/accepted/vdb142_B_1x1_-20C_0300_001... 2017-09-18T07:50:56 B 11.320000 38834.0 11.1 1.327610 3.9270
43 2017-09-17/accepted/vdb142_R_1x1_-20C_0300_001... 2017-09-18T07:59:17 R 11.453333 38758.0 11.1 1.350825 4.6110
32 2017-09-17/accepted/vdb142_G_1x1_-20C_0300_001... 2017-09-18T08:07:44 G 11.566667 38805.0 11.1 1.375233 3.9640
3 2017-09-17/accepted/m1_OIII_1x1_-20C_0900_0009... 2017-09-18T08:53:14 OIII 11.300000 38812.0 10.8 1.310604 3.7760
41 2017-09-17/accepted/m1_Ha_1x1_-20C_0900_0010_a... 2017-09-18T09:16:50 Ha 10.846667 38787.0 10.5 1.232154 3.6720
27 2017-09-17/accepted/m1_OIII_1x1_-20C_0900_0010... 2017-09-18T09:40:22 OIII 10.580000 38803.0 10.5 1.178706 3.5030
15 2017-09-17/accepted/m1_Ha_1x1_-20C_0900_0011_a... 2017-09-18T10:03:50 Ha 9.886667 38667.0 9.6 1.137965 4.1980

Determine the critical focus zone for this setup

CFZ is determined by the relation $$CFZ = 4.88 \cdot {\cal F}^2 \cdot \lambda$$ where ${\cal F}$ is the focal ratio, 5.6 in this case, and $\lambda$ is the wavelength of light, taken to be green or $\lambda=500nm$

The focus motor I'm using is the HSM30 from Starlight Instruments, and has a resolution of 24300 steps per inch.

In [21]:
stepsPerInch=24300
micronsPerInch=25.4e3
micronsPerStep = micronsPerInch / stepsPerInch
print 'Microns per step = ', micronsPerStep

focalRatio=7*0.8
lam=500
CFZ=4.88*focalRatio**2*lam*1e-3
print 'Critical Focus zone (microns) = ',CFZ
print 'Critical Focus zone (steps) = ',CFZ*micronsPerStep
Microns per step =  1.04526748971
Critical Focus zone (microns) =  76.5184
Critical Focus zone (steps) =  79.9821958848

Drop first two samples of RGB filters as scope was still cooling down from taking outside.

In [22]:
R=df[df.Filter=='R'].sort_values(by='date').iloc[2:]
G=df[df.Filter=='G'].sort_values(by='date').iloc[2:]
B=df[df.Filter=='B'].sort_values(by='date').iloc[2:]
L=df[df.Filter=='L'].sort_values(by='date')

Check scatter of focuser temperature vs. FWHM of stars. The luminance values are much lower since they're binned at 4x4 at 10s/sub instead of 1x1 at 300s/sub for the RGB set. The addition of L in this sequence was an afterthought and I wanted to get a good RGB imaging set. There does not appear to be any bias in the FWHM with temperature after tossing the first two images of the RGB set.


In [23]:
plt.scatter(R['Focuser Temperature'],R['FWHM'],color='r')
plt.scatter(G['Focuser Temperature'],G['FWHM'],color='g')
plt.scatter(B['Focuser Temperature'],B['FWHM'],color='b')
plt.scatter(L['Focuser Temperature'],L['FWHM'],color='k')
plt.xlabel('Temperature')
plt.ylabel('FWHM (arc-seconds)')
Out[23]:
<matplotlib.text.Text at 0x7f633e57a250>
In [24]:
plt.figure()
plt.scatter(R['Focuser Temperature'],R['Focuser Position'],color='r')
plt.scatter(G['Focuser Temperature'],G['Focuser Position'],color='g')
plt.scatter(B['Focuser Temperature'],B['Focuser Position'],color='b')
plt.scatter(L['Focuser Temperature'],L['Focuser Position'],color='k')

plt.scatter(R['Air Temperature'],R['Focuser Position'],color='r',marker='x')
plt.scatter(G['Air Temperature'],G['Focuser Position'],color='g',marker='x')
plt.scatter(B['Air Temperature'],B['Focuser Position'],color='b',marker='x')
plt.scatter(L['Air Temperature'],L['Focuser Position'],color='k',marker='x')

plt.xlabel('Temperature')
plt.ylabel('Focuser position')
Out[24]:
<matplotlib.text.Text at 0x7f633ffab8d0>

As final validation, check that the FWHM from each image as measured in the subframe selector tool in Pixinsight does not have a dependence on the focuser temperature. This is to be expected since a focusing run is done on each filter before each subframe exposure.

In [25]:
plt.figure()
plt.scatter(R['Focuser Temperature'],R['FWHM'],color='r')
plt.scatter(G['Focuser Temperature'],G['FWHM'],color='g')
plt.scatter(B['Focuser Temperature'],B['FWHM'],color='b')
plt.xlabel('Focuser Temperature')
plt.ylabel('FWHM')
Out[25]:
<matplotlib.text.Text at 0x7f633e5fa350>

Fit with linear model

Cycle through LRGB image set and perform fit. Display the pearson's $r^2$ and the fit parameters. The slope relates to the focus change with temperature and the intercept could viewed as a form of focus offset. However, the focus offsets we should use should be based on a central temperature to avoid extrapolation. Here, I use T=11.8 C

Offsets with respect to the Luminance filter are quite small, with the largest departure of 57 steps being within the range of the CFZ of my setup. It appears these filters are indeed parfocal as advertised by Astrodon. The slope is also similar among the RGB set, which is something to expect since the only thing changing is the filter in each of these data. Thermal expansion and displacement of the focus spot should not be dependent on the filter used, and that's verified here.

The Luminance set has a slope that is quite a bit off, probably due to the lower number of samples being used.

In [26]:
from sklearn import linear_model
In [27]:
for X in [L,R,G,B]:

    x=X['Focuser Temperature'].values.reshape(len(X),1)
    y=X['Focuser Position'].values.reshape(len(X),1)
    lm=linear_model.LinearRegression()
    model=lm.fit(x,y)

    r2=lm.score(x,y)
    m=lm.coef_[0][0]
    b=lm.intercept_[0]
    
    print ''
    filterName = X.Filter.iloc[0]
    print 'Filter = ',filterName
    print 'r^2 = ', r2
    print 'Slope = ', m
    print 'Intercept = ', b
    T=11.8
    filtPosition=m*T+b
    print 'Predicted = ',filtPosition
    
    if filterName == 'L':
        refPosition=m*T+b
    else:
        print 'Offset = ',filtPosition-refPosition
        
    
Filter =  L
r^2 =  0.460873064937
Slope =  170.120875408
Intercept =  37138.5091908
Predicted =  39145.9355206

Filter =  R
r^2 =  0.755359562881
Slope =  377.477623045
Intercept =  34634.6346863
Predicted =  39088.8706383
Offset =  -57.0648823239

Filter =  G
r^2 =  0.829978404787
Slope =  400.674311724
Intercept =  34366.2121322
Predicted =  39094.1690105
Offset =  -51.7665101041

Filter =  B
r^2 =  0.766312373334
Slope =  350.87002041
Intercept =  34976.2226965
Predicted =  39116.4889373
Offset =  -29.4465833029

Display fits

The scatter in the RGB frames on either side of the regression line appears to be consistent with the CFZ of 80 steps. The luminance will likely have to be revisited.

In [28]:
plt.figure()
plt.subplot(2,2,1)
sns.regplot(x='Focuser Temperature',y='Focuser Position',data=L,color='k')
plt.title('Luminance')
plt.subplot(2,2,2)
sns.regplot(x='Focuser Temperature',y='Focuser Position',data=R,color='r')
plt.title('Red')
plt.subplot(2,2,3)
sns.regplot(x='Focuser Temperature',y='Focuser Position',data=G,color='g')
plt.title('Green')
plt.subplot(2,2,4)
sns.regplot(x='Focuser Temperature',y='Focuser Position',data=B,color='b')
plt.title('Blue')
plt.tight_layout()
In [ ]: