Camera linearity test
Posted: 23 Dec 2019, 13:41
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.
The graph about ADUs versus Time was:
Saturation happens at approx 58000 (not 65500) because the dark frame was corrected, and the antiblooming was active.
This was the script used:
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:
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:
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).
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.
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.
The graph about ADUs versus Time was:
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
radius = 150
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
Image.Crop(xc-radius, yc-radius, xc+radius, yc+radius)
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
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
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
radius = 150
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
Image.Crop(xc-radius, yc-radius, xc+radius, yc+radius)
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
radius = 150
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
Image.Crop(xc-radius, yc-radius, xc+radius, yc+radius)
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