Camera linearity test

Scripts and programs to automate Astroart
fabdev
Posts: 285
Joined: 03 Dec 2018, 21:43

Camera linearity test

The linearity of a CCD camera is a common issue which is often addressed in literature. For example with the internet search:

ccd linearity test astronomy

you will find several papers about the matter.
So, if a given CCD receives 2X light does it really outputs 2X adu ? (after having corrected the dark frame of course). Even if you are not interested in precise photometry this is important for flat field correction: on some cameras there's only a limited range where a flat field is correct.

The usual method to verify linearity is to take a sequence of exposures, for example 1, 3, 5, 7, seconds.. up to saturation, and measuring the average intensity of a central subframe, then the linearity can be expressed in percentage with a formula like:
Abs(measured-expected) / expected , where "expected" is a linear interpolation calculated on the data. Sometimes a "1.0" is added to the result, so that the linearity can be graphed as 0.98 .. 1.02, like in the following example:
this was calculated on a cheap camera with the script below, and as you can see, a good flat field can be acquired only between 25000 and 45000 ADU.
Saturation happens at approx 58000 (not 65500) because the dark frame was corrected, and the antiblooming was active.

This was the script used:

Code: Select all

``````exposureInit = 1.0
exposureStep = 2.0
nExpo = 20
decimalSeparator = "."

dim times(nExpo)
dim values(nExpo)
Camera.Binning(1)

for i = 1 to nExpo
exposure = exposureInit + exposureStep * (i-1)

'Message("Dark frame")
Camera.Start(0.2, false)  'exposure for extra flush
Camera.Wait
Image.Close
Camera.Start(exposure, false)
Camera.Wait
valueDark = CalcAverageOfCenter

'Message("Light image")
Camera.Start(exposure)
Camera.Wait
valueLight = CalcAverageOfCenter

times(i) = exposure
values(i) = valueLight - valueDark
print Str(i) + "/" + Str(nExpo) ,  exposure , valueLight ; valueDark
if i = Round(nexpo/2)-1 then linCoeff = values(i) / times(i)
next i

cls
tab = chr(9)
print "Time" + tab + "Light" + tab + "Expected" + tab + "Linearity"
for i = 1 to nExpo              ' print results for Excel/Calc
line = Str(times(i)) + tab
line = line + Format(values(i), "00000.00") + tab
valueLin = linCoeff*times(i)
line = line + Format(valuelin, "00000.00") + tab
linearity = 1 + (values(i)-valueLin) / valueLin
line = line + Format(linearity, "0.0000")
line = CorrectDecimalSeparator(line)
Print line
next i

end

function CalcAverageOfCenter
xc = Image.GetKey("NAXIS1") / 2
yc = Image.GetKey("NAXIS2") / 2
avg = Image.Average
Pause(0.5)
Image.Close
return avg
end function

function CorrectDecimalSeparator(s)
scorr = ""
for i = 1 to Len(s)
if s[i] = "." then
scorr = scorr + DecimalSeparator
else
scorr = scorr + s[i]
end if
next i
return scorr
end function

``````
the initial exposure is 1.0 seconds, the increment 2.0 seconds, repeated 20 times, so 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39 seconds. The actual output of the script was:

Code: Select all

``````Time	Light		Expected	Linearity
1	02209,88	01942,72	1,1375
3	05611,45	05828,16	0,9628
5	09288,68	09713,60	0,9563
7	12946,97	13599,04	0,9521
9	18009,75	17484,48	1,0300
11	21478,18	21369,92	1,0051
13	25363,49	25255,36	1,0043
15	29178,42	29140,81	1,0013
17	33026,25	33026,25	1,0000
19	36872,27	36911,69	0,9989
21	40747,67	40797,13	0,9988
23	44557,65	44682,57	0,9972
25	48385,58	48568,01	0,9962
27	52142,24	52453,45	0,9941
29	55530,34	56338,89	0,9856
31	56503,16	60224,33	0,9382
33	56691,09	64109,77	0,8843
35	56886,83	67995,21	0,8366
37	57026,03	71880,65	0,7933
39	57100,90	75766,09	0,7536``````
The output of the script can be copied & pasted to Excel/Calc to make the graphs. Remember to set the correct "decimalSeparator" for your country, this is required by the spreadsheets.

If your camera has not the shutter, you may uncomment the "Message(..)" instructions to pause the script and cover the camera. If doing that 40 times is too much, you may use the following variation where the dark frames are taken at the beginning of the procedure:

Code: Select all

``````exposureInit = 1.0
exposureStep = 2.0
nExpo = 20
decimalSeparator = "."

dim times(nExpo)
dim values(nExpo)
Camera.Binning(1)

for kind = 1 to 2
openShutter = (kind = 2)
if not openShutter then
Message("Dark frames")
else
Message("Light frames")
end if
for i = 1 to nExpo
exposure = exposureInit + exposureStep * (i-1)
Camera.Start(exposure)
Camera.Wait
value = CalcAverageOfCenter
times(i) = exposure
if openShutter then
values(i) = value - values(i)
else
values(i) = value
end if
print Str(i) + "/" + Str(nExpo) ,  exposure , value
if i = Round(nexpo/2)-1 then linCoeff = values(i) / times(i)
next i
next kind

cls
tab = chr(9)
print "Time" + tab + "Light" + tab + "Expected" + tab + "Linearity"
for i = 1 to nExpo              ' print results for Excel
line = Str(times(i)) + tab
line = line + Format(values(i), "00000.00") + tab
valueLin = linCoeff*times(i)
line = line + Format(valuelin, "00000.00") + tab
linearity = 1 + (values(i)-valueLin) / valueLin
line = line + Format(linearity, "0.0000")
line = CorrectDecimalSeparator(line)
Print line
next i

end

function CalcAverageOfCenter
xc = Image.GetKey("NAXIS1") / 2
yc = Image.GetKey("NAXIS2") / 2
avg = Image.Average
Pause(0.5)
Image.Close
return avg
end function

function CorrectDecimalSeparator(s)
scorr = ""
for i = 1 to Len(s)
if s[i] = "." then
scorr = scorr + DecimalSeparator
else
scorr = scorr + s[i]
end if
next i
return scorr
end function
``````

For an extremely precise measure of the linearity, the following script calculates the Linear Regression with least squares, but it can be used only on the linear range (not for saturation).

Code: Select all

``````exposureInit = 1.0
exposureStep = 2.0
nExpo = 10
decimalSeparator = "."

dim times(nExpo)
dim values(nExpo)
Camera.Binning(1)

for i = 1 to nExpo
exposure = exposureInit + exposureStep * (i-1)

'Message("Dark frame")
Camera.Start(0.2, false)  'exposure for extra flush
Camera.Wait
Image.Close
Camera.Start(exposure, false)
Camera.Wait
valueDark = CalcAverageOfCenter

'Message("Light image")
Camera.Start(exposure)
Camera.Wait
valueLight = CalcAverageOfCenter

times(i) = exposure
values(i) = valueLight - valueDark
print Str(i) + "/" + Str(nExpo) ,  exposure , valueLight ; valueDark
next i

cls
Regression(times,values,nexpo)
tab = chr(9)
print "Time" + tab + "Light" + tab + "Expected" + tab + "Linearity"
for i = 1 to nExpo              ' print results for Excel
line = Str(times(i)) + tab
line = line + Format(values(i), "00000.00") + tab
valueLin = offset + slope*times(i)
line = line + Format(valuelin, "00000.00") + tab
linearity = 1 + (values(i)-valueLin) / valueLin
line = line + Format(linearity, "0.0000")
line = CorrectDecimalSeparator(line)
Print line
next i

end

function CalcAverageOfCenter
xc = Image.GetKey("NAXIS1") / 2
yc = Image.GetKey("NAXIS2") / 2
avg = Image.Average
Pause(0.5)
Image.Close
return avg
end function

function CorrectDecimalSeparator(s)
scorr = ""
for i = 1 to Len(s)
if s[i] = "." then
scorr = scorr + DecimalSeparator
else
scorr = scorr + s[i]
end if
next i
return scorr
end function

sub Regression(t,v,n)
SumX = 0
SumY = 0
SumXX = 0
SumXY = 0
for i = 1 to n
SumX = SumX + t(i)
SumXX = SumXX + t(i)*t(i)
SumXY = SumXY + t(i)*v(i)
SumY = SumY + v(i)
next i
slope = (SumXY*n - SumX*SumY) / (SumXX*n - SumX*SumX)
offset = (SumY - slope*SumX) / n
end sub
``````
If you want to try with your camera, you need a faint light source which could saturate the CCD in 30..60 seconds, and you must be sure that the temperature is stable.