CCD astronomical imaging: color image processing​
​by Tom Sherlock​
First part (image acquisition): https://community.wolfram.com/groups/-/m/t/2565873
I captured these images of M57 and M27 from my backyard in Urbana. Both were captured using a monochrome CCD camera through a series of red, green and blue filters.
The imaging frames plus the calibration frames (dark and bias) were all captured using Mathematica to control the CCD camera and the filter wheel using the ASCOM API and NETLink.
All the alignment, stacking and calibration plus the initial image processing were also done in the same notebook using WL plus some Python routines called from WL code. The only thing not done in Mathematica was the control of the guiding camera which kept the telescope locked onto these objects and some minor touchups in Gimp.
Both images consist of 10, 30-second subframes captured through each of the 3 filters, plus 10 bias frames and 10 dark frames. The camera was cooled to 0 degrees C and this was also set using WL.

Python Prerequisites


Implementation

Hybrid Python and WL Code for Stacking and Aligning Images


Code to Combine Color Channels


Code to Stretch Image Data


Process Individual Exposures

In[]:=
outputDir="c:\\users\\tom\\pictures\\6_28_2 - Copy";

Compute Master Bias Calibration Frame

In[]:=
ComputeMasterBias[FileNames[FileNameJoin[{outputDir,"Bias","bias*.fit"}]],FileNameJoin[{outputDir,"MasterBias.fit"}]]
Averaging 10 bias frames
c:\users\tom\pictures\6_28_2 - Copy\MasterBias.fit
Out[]=
c:\users\tom\pictures\6_28_2 - Copy\MasterBias.fit

Compute Master Dark Calibration Frame

In[]:=
ComputeMasterDark[FileNames[FileNameJoin[{outputDir,"Darks","dark*.fit"}]],FileNameJoin[{outputDir,"MasterBias.fit"}],​​FileNameJoin[{outputDir,"MasterDark.fit"}]]
Averaging 10 dark frames
Writing master dark: c:\users\tom\pictures\6_28_2 - Copy\MasterDark.fit
Out[]=
c:\users\tom\pictures\6_28_2 - Copy\MasterDark.fit

Aligning and Stacking Light Frames

In[]:=
targetDir="M27";

Frames taken through red filter

In[]:=
redFile=AlignAndStack[FileNames[FileNameJoin[{outputDir,targetDir,"red*.fit"}]],FileNameJoin[{outputDir,"MasterBias.fit"}],​​FileNameJoin[{outputDir,"MasterDark.fit"}],​​FileNameJoin[{outputDir,"Red.fit"}]];
dark frame: c:\users\tom\pictures\6_28_2 - Copy\MasterDark.fit
bias frame: c:\users\tom\pictures\6_28_2 - Copy\MasterBias.fit
target frame: c:\users\tom\pictures\6_28_2 - Copy\M27\red_Wed_29_Jun_2022_10.fit
Aligning 10 images
Stacking 10 images
Writing out stacked image: c:\users\tom\pictures\6_28_2 - Copy\Red.fit

Frames taken through green filter

In[]:=
greenFile=AlignAndStack[FileNames[FileNameJoin[{outputDir,targetDir,"green*.fit"}]],FileNameJoin[{outputDir,"MasterBias.fit"}],​​FileNameJoin[{outputDir,"MasterDark.fit"}],​​FileNameJoin[{outputDir,"Green.fit"}]];
dark frame: c:\users\tom\pictures\6_28_2 - Copy\MasterDark.fit
bias frame: c:\users\tom\pictures\6_28_2 - Copy\MasterBias.fit
target frame: c:\users\tom\pictures\6_28_2 - Copy\M27\green_Wed_29_Jun_2022_10.fit
Aligning 10 images
Stacking 10 images
Writing out stacked image: c:\users\tom\pictures\6_28_2 - Copy\Green.fit

Frames taken through blue filter

In[]:=
blueFile=AlignAndStack[FileNames[FileNameJoin[{outputDir,targetDir,"blue*.fit"}]],FileNameJoin[{outputDir,"MasterBias.fit"}],​​FileNameJoin[{outputDir,"MasterDark.fit"}],​​FileNameJoin[{outputDir,"Blue.fit"}]];
dark frame: c:\users\tom\pictures\6_28_2 - Copy\MasterDark.fit
bias frame: c:\users\tom\pictures\6_28_2 - Copy\MasterBias.fit
target frame: c:\users\tom\pictures\6_28_2 - Copy\M27\blue_Wed_29_Jun_2022_10.fit
Aligning 10 images
Stacking 10 images
Writing out stacked image: c:\users\tom\pictures\6_28_2 - Copy\Blue.fit

Frames taken through clear (luminance) filter

In[]:=
lumFile=AlignAndStack[FileNames[FileNameJoin[{outputDir,targetDir,"lum*.fit"}]],FileNameJoin[{outputDir,"MasterBias.fit"}],​​FileNameJoin[{outputDir,"MasterDark.fit"}],​​FileNameJoin[{outputDir,"Lum.fit"}]];
dark frame: c:\users\tom\pictures\6_28_2 - Copy\MasterDark.fit
bias frame: c:\users\tom\pictures\6_28_2 - Copy\MasterBias.fit
target frame: c:\users\tom\pictures\6_28_2 - Copy\M27\Luminance_Wed_29_Jun_2022_10.fit
Aligning 10 images
Stacking 10 images
Writing out stacked image: c:\users\tom\pictures\6_28_2 - Copy\Lum.fit

Build the Final Image

Import the FITS files for each color channel plus luminance

In[]:=
components=Import/@{lumFile,redFile,greenFile,blueFile};

Use the IRAS ZSCALE Algorithm to Stretch the Image Data

In[]:=
scaled=ZScaleStretchImage[#[1]]&/@components;
In[]:=
scaled

,
,
,


Create the final color composite

In[]:=
finalImage=ColorCombineLRGB[scaled]
Out[]=

Further Enhancements

You can collect the color frames at 2x2 binning, and collect 4 times the light per virtual pixel. ColorCombineLRGB will scale and align the color frames to match the luminance frame.
You can also incorporate flat frames to compensate for light falloff with larger sensors and dust on the various filters.BrightnessEqualize[im,flatfield] will remove these artifacts.