Sharpcap is so very close to being a killer slitless spectroscopy data acquisition platform... there is just one tiny feature missing here.
This year Slitless spectoscopy is getting a lot more traction, with data analysis tools like Rspec, SpecINT and Bass Project readily available. AAVSO now has a monthly meeting on slitless spectrosopy (like with the SA100 for example), also people have used the Seestar for slitless spectroscopy. The trick is however, to capture a (faint) diffraction pattern accurately; in focus (not the star but the spectrum features), not over exposed, and relatively close (so it can be XY shift stacked afterwards). I use Sharpcap Quick capture to create SER files that I stack in SIRIL and process in Bass Project. However, to see if my focus and exposure is right, I need to go through my whole workflow today, in the field (hot or cold nights), its not optimal. So usually I end up collecting more data and throwing some away the next day. Unfortunate!
Here is what could change my life.
- allow us to select a region of interest, and plot the pixel values along the columns (gray values or green channel) vs. pixels
Most people in spectro use MONO, so that is easy to "sum". For OSC images, it would have to be debayered in that ROI and turned to grey scale (NTSC formula : 0.299 ∙ Red + 0.587 ∙ Green + 0.114 ∙ Blue); it seems to be similar to "MONO Bin" on some OSC astro cameras. If debayering is hard, it would also be very workable to only allow this feature in PNGs. Because this is for setup only, and data acquisition in SER/FITS will not change the choices for focus, gain, exposure.
I have looked in ImageJ - there you can load up a fits (MONO) or PNG (OSC or MONO) and Analyze\Profile. It gives you the file attached in this post. I would look at how deep the Balmer lines are, deep means well in focus. Using ImageJ however, I have to look at the latest file from Sharpcap and keep opening the file in ImageJ. I am considering to write a small python script that would look at the latest file and "remember" the ROI to plot - a hack on a hack.
Would this be a new feature in Sharpcap? Or is there a way to use existing functionality to do the same?
with kind regards,
Matt
Plot profile - slitless spectroscopy
Forum rules
'+1' posts are welcome in this area of the forums to indicate your support for a particular feature suggestion. Suggestions that get the most +1's will be seriously considered for inclusion in future versions of SharpCap.
'+1' posts are welcome in this area of the forums to indicate your support for a particular feature suggestion. Suggestions that get the most +1's will be seriously considered for inclusion in future versions of SharpCap.
-
- Posts: 12
- Joined: Sun Sep 03, 2023 6:11 am
Plot profile - slitless spectroscopy
- Attachments
-
- ImageJSirius.png (224.21 KiB) Viewed 433 times
- admin
- Site Admin
- Posts: 13520
- Joined: Sat Feb 11, 2017 3:52 pm
- Location: Vale of the White Horse, UK
- Contact:
Re: Plot profile - slitless spectroscopy
Hi Matt,
thanks for explaining this. Can I make sure that I understand the request properly...
Basically, for the selection area, SharpCap would look at each column of pixels in the selection and pick a value for that column - this might be either the average pixel value above some background level or (maybe easier) the maximum pixel value in the column. Then plot a graph of that information.
It doesn't sound very complicated when written like that, so I just want to make sure I'm seeing all of the problem and not just part of it
cheers,
Robin
thanks for explaining this. Can I make sure that I understand the request properly...
Basically, for the selection area, SharpCap would look at each column of pixels in the selection and pick a value for that column - this might be either the average pixel value above some background level or (maybe easier) the maximum pixel value in the column. Then plot a graph of that information.
It doesn't sound very complicated when written like that, so I just want to make sure I'm seeing all of the problem and not just part of it
cheers,
Robin
-
- Posts: 12
- Joined: Sun Sep 03, 2023 6:11 am
Re: Plot profile - slitless spectroscopy
Thanks Robin,
yes that's absolutely it; just taking the max of all the pixels per column in the selection area. You could make it more advanced by subtraction the background (for example subtract the average of the first 10 pixels from the edges), but at this stage that is not very important and would just bring more complexity.
The reason why this (just column sum or max) is so powerful is that in slitless spectroscopy it is best practice to always have our slitless diffraction spectra exactly horizontal on the CMOS. We do that by (in hardware) lining up the diffraction grating with the CMOS sensor.
thank you,
Matt
yes that's absolutely it; just taking the max of all the pixels per column in the selection area. You could make it more advanced by subtraction the background (for example subtract the average of the first 10 pixels from the edges), but at this stage that is not very important and would just bring more complexity.
The reason why this (just column sum or max) is so powerful is that in slitless spectroscopy it is best practice to always have our slitless diffraction spectra exactly horizontal on the CMOS. We do that by (in hardware) lining up the diffraction grating with the CMOS sensor.
thank you,
Matt
-
- Posts: 417
- Joined: Sun Oct 13, 2019 10:52 am
- Location: Germany
Re: Plot profile - slitless spectroscopy
Hello Matt,
You can have a look at this script ...
It was a script that I started to write before the occultation of Betelgeuse last December.
The idea was to change the focus of the telescope during the occultation.
A fainter star than Betelgeuse was focused (for example: Best Focus at 24500) and then move to Betelgeuse
Defocus the telescope until no overexposure (in this case defocus at 25500).
During the occultation, the focuser should refocus (from 25500 to 24500 and back) during the dimming of the spot.
The script was not completely finish and during November and December *zero* night for testing !
So I did not use the script during the occultation of Betelgeuse.
In the script ... you can see (and learn) how to catch a frame and perform some processing.
The script activates the selection red rectangle. You can move and re-dimension it with the mouse.
The graphic shows the maximum value of the spot from the red rectangle.
You can modify it for showing the average value of the spectrum.
The focuser part can be deleted. If you have something ... I can do a "review" of your script
Regards,
Jean-Francois
You can have a look at this script ...
Code: Select all
# *******************************************************************************************
#
# SharpCap script "Betelgeuse_2023-11-13.py"
# 2023/11/13 Jean-Francois
#
# Version: 1.0.0: First version
#
# The script works only with a SharpCap PRO version.
#
# *******************************************************************************************
import time
import datetime
import math
import clr
clr.AddReference("Oxyplot")
clr.AddReference("OxyPlot.WindowsForms ")
clr.AddReference("System.Drawing")
clr.AddReference("System.Windows.Forms")
import OxyPlot
import System.Drawing
import System.Drawing.Drawing2D
import System.Windows.Forms
from System import Array
from System.Drawing import *
from System.Drawing.Drawing2D import GraphicsPath, CustomLineCap, SmoothingMode, LineJoin
from System.Windows.Forms import *
from System.Threading import Thread, ThreadStart, ApartmentState, ParameterizedThreadStart
Mean = 0.0
t0 = 0.0
M00 = 1
M10 = 1
M01 = 1
threshold = 4000
Time_frame = []
Max_frame = []
Surf_spot = []
Surf_value = []
Surf_Ratio = []
dumpdata = False
whitePen = Pen(Color.White, 1)
Pen.DashPattern.SetValue(whitePen, (5, 5))
if SharpCap.SelectedCamera.Controls.ColourSpace.Value == "MONO8":
I_min = 50
I_max = 200
Max_line = 220
Focus_line = 150
if SharpCap.SelectedCamera.Controls.ColourSpace.Value == "MONO16":
I_min = 20000
I_max = 40000
Max_line = 60000
Focus_line = 25000
start_pos = SharpCap.Focusers.SelectedFocuser.Position
#best_focus = 73200
#max_focus = 74400
best_focus = 24500
max_focus = 25500
def beforeframehandler(sender, args):
global M00, M10, M01, whitePen
ROI_X = SharpCap.Transforms.SelectionRect.X
ROI_Y = SharpCap.Transforms.SelectionRect.Y
dbitmap = args.Frame.GetDrawableBitmap()
gbitmap = dbitmap.GetGraphics()
try:
radius = int(math.sqrt(Surf_spot[-1]/6.28))
centerX = ROI_X + M10/M00 - 1
centerY = ROI_Y + M01/M00 - 1
print("Radius : ",radius, " Center: ", centerX, " ", centerY)
gbitmap.DrawEllipse(whitePen, centerX - radius, centerY - radius, radius + radius, radius + radius)
except:
print("Error Bildmap")
gbitmap.Dispose()
dbitmap.Dispose()
return()
def framehandler(sender, args):
global Mean, t0, Time_frame, Max_frame, Surf_spot, Surf_value, Surf_ratio, dumpdata, start_pos, delta_focus, best_focus, max_focus
global M00, M10, M01, whitePen, threshold
form.labelRectangle.Text = "Rectangle: " + str(SharpCap.Transforms.SelectionRect.Width) + " x " \
+ str(SharpCap.Transforms.SelectionRect.Height)
if (dumpdata):
try:
cutout = args.Frame.CutROI(SharpCap.Transforms.SelectionRect)
dt = (datetime.datetime.now() - t0).total_seconds()
Time_frame.append(dt)
data = cutout.ToFloats(0)
info = cutout.Info
temp = Array[System.Single](info.Height,info.Width)
actual_max = 0
#sum_spalten = []
#sum_zeilen = []
for i in range(info.Height):
for j in range(info.Width):
value = data[i,j]
if value > actual_max:
actual_max = value
Max_frame.append(actual_max)
for i in range(1, info.Height - 1):
for j in range(1, info.Width - 1):
temp[i,j] = data[i,0] + ((j - 1)*(data[i,info.Width-1] - data[i,0])/(info.Width - 1)) + \
data[0,j] + ((i - 1)*(data[info.Height-1,j] - data[0,j])/(info.Height - 1))
temp[i,j] = data[i,j] - temp[i,j]/2.0
M00 = 1
M10 = 0
M01 = 0
surface = 0
value = 0
#threshold = 4000
threshold = int(form.textBoxThreshold.Text)
for i in range(info.Height):
for j in range(info.Width):
M00 += temp[i,j]
M01 += (i + 1) * temp[i,j]
M10 += (j + 1) * temp[i,j]
if temp[i,j] > threshold:
surface += 1
value += temp[i,j]
Surf_spot.append(surface)
Surf_value.append(value)
# Surf_ratio.append(value/surface)
print("Max: ", actual_max, ", Centroid: ", M10/M00, ", ", M01/M00, " surface: ", surface)
# print("Preceding surface: ", Surf_spot[-2])
print("Focuser moving :", SharpCap.Focusers.SelectedFocuser.IsMoving)
form.ShowOxyGraphic()
cutout.Release()
if (form.checkbox_focuser.Checked):
if not SharpCap.Focusers.SelectedFocuser.IsMoving:
factor = float(value/Surf_value[0])
neu_pos = int((max_focus - best_focus)*math.sqrt(factor) + best_focus)
print("Pos: ", neu_pos, " , surface: ", surface, " , value: ", value, " , factor: ", factor)
if (factor < 0.5):
#neu_pos = int((max_focus - best_focus)*math.sqrt(factor) + best_focus)
#Focus_Thread(neu_pos)
SharpCap.Focusers.SelectedFocuser.Move(neu_pos)
print()
print("<0.5 Pos: ", neu_pos, " factor: ", factor)
#Surf_value[0] = value
#max_focus = neu_pos
#time.sleep(0.1)
if (factor > 1.1):
#neu_pos = int((max_focus - best_focus)*math.sqrt(factor) + best_focus)
#Focus_Thread(neu_pos)
SharpCap.Focusers.SelectedFocuser.Move(neu_pos)
print()
print("> 1.1 Pos: ", neu_pos, " factor: ", factor)
except:
print("error")
cutout.Release()
return()
def Start_Surface(sender, args):
global Surf_spot, Surf_value, Surf_ratio
cutout = args.Frame.CutROI(SharpCap.Transforms.SelectionRect)
data = cutout.ToFloats(0)
info = cutout.Info
temp = Array[System.Single](info.Height,info.Width)
for i in range(1, info.Height - 1):
for j in range(1, info.Width - 1):
temp[i,j] = data[i,0] + ((j - 1)*(data[i,info.Width-1] - data[i,0])/(info.Width - 1)) + \
data[0,j] + ((i - 1)*(data[info.Height-1,j] - data[0,j])/(info.Height - 1))
temp[i,j] = data[i,j] - temp[i,j]/2.0
threshold = 5000
#threshold = 50
surface = 0
value = 0
for i in range(info.Height):
for j in range(info.Width):
if temp[i,j] > threshold:
surface += 1
value += temp[i,j]
Surf_spot.append(surface)
Surf_value.append(value)
Surf_ratio.append(value/surface)
print("Surface:", Surf_spot, " Value: ", Surf_value, " Ratio: ", Surf_ratio)
cutout.Release()
return()
def mean(xs):
return sum(xs) / len(xs)
def stddev(xs):
m = sum(xs) / len(xs)
var = sum(abs(x - m) for x in xs) / len(xs)
return math.sqrt(var)
# *****************************************************************************
def Frame_measurement(self):
global t0, Time_frame, Max_frame, Surf_spot, Surf_value, Surf_ratio, dumpdata, start_pos, delta_focus, best_focus, de_focus
self.Monitoring.Text = "Monitoring running"
self.Monitoring.BackColor = Color.Red
self.Monitoring.Enabled = False
Time_frame = []
Max_frame = []
Surf_spot = []
Surf_value = []
Surf_ratio = []
dumpdata = True
t0 = datetime.datetime.now()
print()
print("Frame Max Monitoring:")
print("******************")
start_pos = SharpCap.Focusers.SelectedFocuser.Position
best_focus = int(self.textBoxBestFocus.Text)
de_focus = int(self.textBoxDeFocus.Text) #SharpCap.Focusers.SelectedFocuser.Position
#SharpCap.SelectedCamera.FrameCaptured += Start_Surface
#time.sleep(0.004*float(SharpCap.SelectedCamera.Controls.Exposure.Value))
#print("Start Surface : ", Surf_spot)
#SharpCap.SelectedCamera.FrameCaptured -= Start_Surface
SharpCap.SelectedCamera.FrameCaptured += framehandler
if (form.checkbox_surface.Checked):
SharpCap.SelectedCamera.BeforeFrameDisplay += beforeframehandler
temp = 0.0
while temp < float(self.textBoxTime.Text):
time.sleep(1.0)
temp += 1.0
if dumpdata == False:
break
print()
print("Start Position : ", start_pos)
print("End Position : ", SharpCap.Focusers.SelectedFocuser.Position)
print()
print("Number: ", len(Time_frame))
print()
dumpdata = False
SharpCap.SelectedCamera.FrameCaptured -= framehandler
if (form.checkbox_surface.Checked):
SharpCap.SelectedCamera.BeforeFrameDisplay -= beforeframehandler
print("End Surface : ", Surf_spot)
print("End Value : ", Surf_value)
print("End Ratio : ", Surf_ratio)
print()
print("Measurement done")
self.Monitoring.Text = "Start Measurement"
self.Monitoring.BackColor = Color.Gainsboro
self.Monitoring.Enabled = True
#file_path = "C:\\Data\\"
#file_name = file_path + "Mean_monitoring_" + str(t0).split()[1].split(".")[0].replace(":","-") + ".txt"
#file = open(file_name, "w")
#for index in range(len(Time_frame)):
# file.write(str("%.4f" % Time_frame[index]) + " " + str("%.4f" % Mean_frame[index]) + "\n")
#file.close()
return()
# *****************************************************************************
class MonitoringMeanMenuForm(Form):
def __init__(self):
self.SuspendLayout()
self.InitializeComponent()
self.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi
self.AutoScaleDimensions = SizeF(96, 96)
self.ResumeLayout()
def InitializeComponent(self):
global t0, Time_frame, Max_frame, Surf_spot, Surf_value, Surf_ratio, dumpdata, start_pos, delta_focus, best_focus, de_focus
self.Text = "Frame Mean Monitoring"
self.ClientSize = System.Drawing.Size(700, 450)
self.TopMost = True
self.Monitoring = Button()
self.labelTime = Label()
self.textBoxTime = TextBox()
self.labelThreshold = Label()
self.textBoxThreshold = TextBox()
self.button_StartStop = Button()
self.button_Exit = Button()
self.labelBestFocus = Label()
self.textBoxBestFocus = TextBox()
self.labelDeFocus = Label()
self.textBoxDeFocus = TextBox()
self.labelRectangle = Label()
self.checkbox_surface = CheckBox()
self.checkbox_focuser = CheckBox()
self.Monitoring.Text = "Start Measurement"
self.Monitoring.Location = Point(20, 10)
self.Monitoring.Click += self.Frame_monitor
self.Monitoring.AutoSize = True
self.labelTime.Text = "Measurement time [s]:"
self.labelTime.Location = Point(150, 14)
self.labelTime.Name = "labelTime"
self.labelTime.AutoSize = True
self.textBoxTime.Text = "1"
self.textBoxTime.Location = Point(280, 12)
self.textBoxTime.Name = "textBoxTime"
self.textBoxTime.Size = Size(40, 20)
self.textBoxTime.AutoSize = True
self.labelThreshold.Text = "Threshold:"
self.labelThreshold.Location = Point(350, 14)
self.labelThreshold.Name = "labelThreshold"
self.labelThreshold.AutoSize = True
self.textBoxThreshold.Text = str(threshold)
self.textBoxThreshold.Location = Point(415, 12)
self.textBoxThreshold.Name = "textBoxTime"
self.textBoxThreshold.Size = Size(40, 20)
self.textBoxThreshold.AutoSize = True
self.button_StartStop.Text = "Start"
self.button_StartStop.Location = Point(500, 10)
self.button_StartStop.Click += self.StartStop
self.button_StartStop.AutoSize = True
self.button_Exit.Text = "Exit"
self.button_Exit.Location = Point(600, 10)
self.button_Exit.Click += self.Exit
self.button_Exit.AutoSize = True
self.labelBestFocus.Text = "Best Focus:"
self.labelBestFocus.Location = Point(20, 50)
self.labelBestFocus.Name = "labelBestFocus"
self.labelBestFocus.AutoSize = True
self.textBoxBestFocus.Text = str(best_focus)
self.textBoxBestFocus.Location = Point(90, 48)
self.textBoxBestFocus.Name = "textBoxBestFocus"
self.textBoxBestFocus.Size = Size(60, 20)
self.labelDeFocus.Text = "De-Focus:"
self.labelDeFocus.Location = Point(180, 50)
self.labelDeFocus.Name = "labelBestFocus"
self.labelDeFocus.AutoSize = True
self.textBoxDeFocus.Text = str(max_focus)
self.textBoxDeFocus.Location = Point(250, 48)
self.textBoxDeFocus.Name = "textBoxDeFocus"
self.textBoxDeFocus.Size = Size(60, 20)
self.labelRectangle.Text = "Rectangle:"
self.labelRectangle.Location = Point(350, 50)
self.labelRectangle.Name = "labelRectangle"
self.labelRectangle.AutoSize = True
self.checkbox_surface.Text = "View Spot Surface"
self.checkbox_surface.Location = Point(500, 50)
self.checkbox_surface.AutoSize = True
self.checkbox_focuser.Text = "Move Focuser"
self.checkbox_focuser.Location = Point(500, 75)
self.checkbox_focuser.AutoSize = True
self.plot = OxyPlot.WindowsForms.PlotView()
self.plot.Location = System.Drawing.Point(10, 100)
self.plot.Size = System.Drawing.Size(680, 340)
self.Controls.Add(self.Monitoring)
self.Controls.Add(self.labelTime)
self.Controls.Add(self.textBoxTime)
self.Controls.Add(self.labelThreshold)
self.Controls.Add(self.textBoxThreshold)
self.Controls.Add(self.button_StartStop)
self.Controls.Add(self.button_Exit)
self.Controls.Add(self.labelBestFocus)
self.Controls.Add(self.textBoxBestFocus)
self.Controls.Add(self.labelDeFocus)
self.Controls.Add(self.textBoxDeFocus)
self.Controls.Add(self.labelRectangle)
self.Controls.Add(self.checkbox_surface)
self.Controls.Add(self.checkbox_focuser)
self.Controls.Add(self.plot)
Time_frame = []
Max_frame = []
Surf_spot = []
Surf_value = []
Surf_ratio = []
t0 = datetime.datetime.now()
SharpCap.Transforms.SelectTransform("ROI Selection") # Show the red selection rectangle
def ShowOxyGraphic(self):
global Time_frame, Mean_frame
self.plotmodel = OxyPlot.PlotModel()
if SharpCap.SelectedCamera.Controls.ColourSpace.Value == "MONO8":
self.YAxis = OxyPlot.Axes.LinearAxis(Position = OxyPlot.Axes.AxisPosition.Right, Minimum = 0.0, Maximum = 260)
if SharpCap.SelectedCamera.Controls.ColourSpace.Value == "MONO16":
self.YAxis = OxyPlot.Axes.LinearAxis(Position = OxyPlot.Axes.AxisPosition.Right, Minimum = 0.0, Maximum = 66000)
self.points_Max = []
for i in range(0,len(Time_frame)):
self.points_Max.Add(OxyPlot.DataPoint(Time_frame[i],Max_frame[i]))
self.serie = OxyPlot.Series.LineSeries()
self.serie.Color = OxyPlot.OxyColors.Red
self.serie.MarkerSize = 1
self.serie.MarkerType = OxyPlot.MarkerType.Circle
self.serie.StrokeThickness = 1
self.serie.ItemsSource = self.points_Max
self.plotmodel.Axes.Add(self.YAxis)
self.plotmodel.Series.Add(self.serie)
self.plot.Model = self.plotmodel
def Frame_monitor(self, sender, event):
th = Thread(ParameterizedThreadStart(Frame_measurement))
th.SetApartmentState(ApartmentState.STA)
th.Start(self)
def Exit(self, sender, event):
print("Exit monitoring script")
SharpCap.Transforms.SelectTransform(None) # Hide the red selection rectangle
SharpCap.SelectedCamera.FrameCaptured -= framehandler
self.Close()
def StartStop(self, sender, event):
global dumpdata
if (self.button_StartStop.Text == "Start"):
self.button_StartStop.Text = "Stop"
print("Start")
self.checkbox_surface.Enabled = False
self.checkbox_focuser.Enabled = False
dumpdata = True
SharpCap.SelectedCamera.FrameCaptured += framehandler
if (form.checkbox_surface.Checked):
SharpCap.SelectedCamera.BeforeFrameDisplay += beforeframehandler
else: #self.button_StartStop.Text == "Stop"
self.button_StartStop.Text = "Start"
print("Stop")
self.checkbox_surface.Enabled = True
self.checkbox_focuser.Enabled = True
dumpdata = False
SharpCap.SelectedCamera.FrameCaptured -= framehandler
if (form.checkbox_surface.Checked):
SharpCap.SelectedCamera.BeforeFrameDisplay -= beforeframehandler
def Measurement_Stop(self, sender):
global dumpdata
dumpdata = False
SharpCap.SelectedCamera.FrameCaptured -= framehandler
SharpCap.SelectedCamera.BeforeFrameDisplay -= beforeframehandler
print("Stop")
#def launch_form():
form = MonitoringMeanMenuForm()
form.StartPosition = FormStartPosition.CenterScreen
form.TopMost = True
form.FormClosing += Measurement_Stop
form.Show()
#SharpCap.AddCustomButton(" Mean__Monitoring |", None, "Mean__Monitoring", launch_form)
The idea was to change the focus of the telescope during the occultation.
A fainter star than Betelgeuse was focused (for example: Best Focus at 24500) and then move to Betelgeuse
Defocus the telescope until no overexposure (in this case defocus at 25500).
During the occultation, the focuser should refocus (from 25500 to 24500 and back) during the dimming of the spot.
The script was not completely finish and during November and December *zero* night for testing !
So I did not use the script during the occultation of Betelgeuse.
In the script ... you can see (and learn) how to catch a frame and perform some processing.
The script activates the selection red rectangle. You can move and re-dimension it with the mouse.
The graphic shows the maximum value of the spot from the red rectangle.
You can modify it for showing the average value of the spectrum.
The focuser part can be deleted. If you have something ... I can do a "review" of your script
Regards,
Jean-Francois
-
- Posts: 417
- Joined: Sun Oct 13, 2019 10:52 am
- Location: Germany
Re: Plot profile - slitless spectroscopy
One point more ...
The graphic shows the maximum value on the red rectangle during the movement of the rectangle over the image during 10 seconds.
The graphic shows the maximum value on the red rectangle during the movement of the rectangle over the image during 10 seconds.
-
- Posts: 12
- Joined: Sun Sep 03, 2023 6:11 am
Re: Plot profile - slitless spectroscopy
Many thanks Jean-Francois! I have to chew a bit on this, very helpful!