Camera linearity test

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

Camera linearity test

Post by fabdev » 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:

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

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
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
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.

Post Reply