My BATCH Script
Posted: Wed Nov 25, 2020 10:56 am
Hi all,
When I first started testing SharpCap to be my imaging software I choose it because of a controversal, but at the sane time coherent, aspect:
It was very simple in the interface compared with (for me) disturbing fancy modern interfaces but ...
was extremelly powerfull in the scripting aspect
I program in any language as a hobby (I have a logical mind), but in none at same time as I don't have the formal knowledge (as a starting point). I started searching for info on how to make use of all the power suplied by the ironpython embeded in SharpCap but, sincerelly, it was not an easy task. I did not find practically any example of the (I'm sure exist) many powerfull scripts that must be running out there. IMHO examples work better than explanations.
Sadly I took the same path and developed for my own use a couple of scripts that I find very usefull for my DSO imaging tasks but kept them for my own use.
May they be an example for some learning scripters as me I will do my bit uploading them to this forum, also in the wish of seeing some others with solved functions that may inspire me.
Here we go with the first one. It will be surely full of inefficient programing roundabouts but it works for its purpose (any improvements or comments on how to make it cleaner are greatlly appreciated).
This formal script will:
* Create a button (with a funny icon) that will call the script dialog; on closing it will, instead, hide, so you can call it again .. and again from the button
* Make a series of images at given temperature, exposure time and gain
* Betwen frames it can:
* Dither via PHD2
* Check temperature (two different ways: file created by TEMPer unit and ASCOM interface: may you need help on this just ask)
* If temp threshold is achieved it can move focus proportionally to a given amount or autofocus (pending)
* PHD2 can be paused, a fixed amount of time, during focusing to avoid the crazyness of moving a primary mirror
* Once finished it can warmup the camera, park the telescope and close the dome(pending)
Use it, modify it, get solutions or inspiration from it.
Here an image of the interface:
http://nto.org.es/SHARED_GLOBAL/Dialog.JPG
When I first started testing SharpCap to be my imaging software I choose it because of a controversal, but at the sane time coherent, aspect:
It was very simple in the interface compared with (for me) disturbing fancy modern interfaces but ...
was extremelly powerfull in the scripting aspect
I program in any language as a hobby (I have a logical mind), but in none at same time as I don't have the formal knowledge (as a starting point). I started searching for info on how to make use of all the power suplied by the ironpython embeded in SharpCap but, sincerelly, it was not an easy task. I did not find practically any example of the (I'm sure exist) many powerfull scripts that must be running out there. IMHO examples work better than explanations.
Sadly I took the same path and developed for my own use a couple of scripts that I find very usefull for my DSO imaging tasks but kept them for my own use.
May they be an example for some learning scripters as me I will do my bit uploading them to this forum, also in the wish of seeing some others with solved functions that may inspire me.
Here we go with the first one. It will be surely full of inefficient programing roundabouts but it works for its purpose (any improvements or comments on how to make it cleaner are greatlly appreciated).
This formal script will:
* Create a button (with a funny icon) that will call the script dialog; on closing it will, instead, hide, so you can call it again .. and again from the button
* Make a series of images at given temperature, exposure time and gain
* Betwen frames it can:
* Dither via PHD2
* Check temperature (two different ways: file created by TEMPer unit and ASCOM interface: may you need help on this just ask)
* If temp threshold is achieved it can move focus proportionally to a given amount or autofocus (pending)
* PHD2 can be paused, a fixed amount of time, during focusing to avoid the crazyness of moving a primary mirror
* Once finished it can warmup the camera, park the telescope and close the dome(pending)
Use it, modify it, get solutions or inspiration from it.
Code: Select all
import os
import clr
import time
import sys
import socket
clr.AddReference("SharpCap")
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from SharpCap.UI import CaptureLimitType
from System.Windows.Forms import *
from System.Drawing import Point, Color, Font, FontStyle, Size, Image, Icon
from System.Environment import GetFolderPath, SpecialFolder
from System.Threading import Thread
from System.IO.Ports import SerialPort
from System import Activator
from System import Type
from guider import Guider
desktop_folder = GetFolderPath(SpecialFolder.Desktop)
wait_precision = 1 # In second
aborded = False
#Parametros PHD2
settlePixels = 2.0
settleTime = 10.0
settleTimeout = 30.0
ditherPixels = 4.0
#Parametros Camara
temperaturacmos = -15
def capture_dither(
Name="CAPTURA",
Expo=0,
Dither=True,
Count=0,
Output=desktop_folder,
Temp=False,
Threshold=0,
Steps=0,
TDelay=0,
WarmUp=False,
Park=False,
Tsource=0,
CloseObs=False,
Pause=False,
Gain=100,
Cmostemp=0,
dialog=None
):
def wait(second):
if dialog:
return dialog.wait_abord(second)
else:
Thread.Sleep(int(second * 1000))
def wait_status(second):
if dialog:
return dialog.wait_abord_status(second)
else:
Thread.Sleep(int(second * 1000))
def log(text):
if dialog:
dialog.process.log(time.strftime("%H%M%S") + " - " + text)
archivolog = Output + "\\log" + time.strftime("%Y%m%d") + ".txt"
logfile = open(archivolog, "a")
logfile.write(time.strftime("%H%M%S") + " - " + text + "\n")
logfile.close()
else:
print text
def log_status(text):
if dialog:
dialog.process.log_status(text)
archivolog = Output + "\\log" + time.strftime("%Y%m%d") + ".txt"
logfile = open(archivolog, "a")
logfile.write(time.strftime("%H%M%S") + " - " + text + "\n")
logfile.close()
else:
print text
def log_error(text):
if dialog:
dialog.paint_alert()
dialog.process.log_error(time.strftime("%H%M%S") + " - " + text)
archivolog = Output + "\\log" + time.strftime("%Y%m%d") + ".txt"
logfile = open(archivolog, "a")
logfile.write(time.strftime("%H%M%S") + " - " + text + "\n")
logfile.close()
else:
print text
try:
#Prepare camera
SharpCap.SelectedCamera.Controls.OutputFormat.Value = "FITS files (*.fits)"
SharpCap.TargetName = Name
SharpCap.CaptureFolder = os.path.join(Output)
SharpCap.SelectedCamera.Controls.Exposure.Value = Expo
SharpCap.SelectedCamera.Controls.Gain.Value = Gain
#Cool down camera
log("Cooling down camera")
_TEMP = SharpCap.SelectedCamera.Controls.Find(lambda x: x.Name == "Temperature")
_COOLER = SharpCap.SelectedCamera.Controls.Find(lambda x: x.Name == "Cooler")
_TARGET = SharpCap.SelectedCamera.Controls.Find(lambda x: x.Name == "Target Temperature")
_ANTIDEW = SharpCap.SelectedCamera.Controls.Find(lambda x: x.Name == "Anti Dew Heater")
_POWER = SharpCap.SelectedCamera.Controls.Find(lambda x: x.Name == "Cooler Power")
_TARGET.Value = temperaturacmos
_COOLER.Value = "On"
_ANTIDEW.Value = "On"
while True:
if _TEMP.Value < Cmostemp + 0.5:
break
log_status("Temp " + str(_TEMP.Value) + " ºC - Power " + str(_POWER.Value) + "%")
Application.DoEvents()
time.sleep(1)
log("Shot temperature reached")
#Activate accesories
SharpCap.Focusers.SelectedFocuser.Connected = True
TemperHum = Activator.CreateInstance(Type.GetTypeFromProgID("ASCOM.OCH.ObservingConditions"))
PHD2 = Guider("localhost")
PHD2.Connect()
#Get actual temp
Fuente = "TEMPer"
if Tsource:
Fuente = "TEMPHUM"
if Tsource:
TemperHum.Connected = True
while TemperHum.Temperature is 0:
wait(0.01)
oldtemp = round(TemperHum.Temperature, 1)
else :
archivoTEMP = Output + "\\TEMPerX\\1.csv"
with open(archivoTEMP, "r") as f1:
for line in f1: pass
last_line = line
cadena = last_line[20:]
entero = cadena[0:cadena.find(",")]
decimal = cadena[cadena.find(",")+1:cadena.find(",")+3]
oldtemp = float(entero) + float(decimal) / 100
#Capture sequence
for i in range(Count):
if not aborded:
if Temp:
if Tsource:
actualtemp = round(TemperHum.Temperature, 1)
else :
with open(archivoTEMP, "r") as f1:
for line in f1: pass
last_line = line
cadena = last_line[20:]
entero = cadena[0:cadena.find(",")]
decimal = cadena[cadena.find(",")+1:cadena.find(",")+3]
actualtemp = float(entero) + float(decimal) / 100
os.remove(archivoTEMP)
log("Temp " + Fuente + ": " + str(actualtemp) + "ºC")
diferencia = actualtemp - oldtemp
log("Diference : " + str(diferencia) + "ºC")
if (abs(diferencia) > Threshold):
if Pause:
log("Pausing PHD2")
PHD2.Pause()
if Steps != 0:
movingsteps = int(diferencia * Steps)
position = SharpCap.Focusers.SelectedFocuser.Position
log("Moving focus " + str(movingsteps) + " steps")
SharpCap.Focusers.SelectedFocuser.Move(position + movingsteps)
log("Temperature delay " + str(TDelay) + " sec")
wait(TDelay)
log("New focus position " + str(SharpCap.Focusers.SelectedFocuser.Position))
oldtemp = actualtemp
else:
log("Performing autofocus")
autofocus()
log("New focus position " + str(SharpCap.Focusers.SelectedFocuser.Position))
if Pause:
log("Resuming PHD2")
PHD2.Unpause()
log("Guiding PHD2")
wait(2)
else:
log("No temperature correction")
if Dither:
log("Dithering")
PHD2.Dither(ditherPixels, settlePixels, settleTime, settleTimeout)
log("Waiting for settle")
while True:
s = PHD2.CheckSettling()
if s.Done:
break
log_status("Dist " + str(s.Distance) + "/" + str(s.SettlePx) + " Time " + str(s.Time))# + "/" + str(s.SettleTime))
Application.DoEvents()
time.sleep(1)
log("Guiding PHD2")
log_status("Process")
log("Starting image " + str(i+1) + " of " + str(Count))
SharpCap.SelectedCamera.RestartPreview()
wait(5)
SharpCap.SelectedCamera.CaptureConfig.CaptureLimitType = CaptureLimitType.FrameLimited
SharpCap.SelectedCamera.CaptureConfig.CaptureLimitValue = 1
SharpCap.SelectedCamera.CaptureConfig.CaptureSequenceCount = 1
SharpCap.SelectedCamera.CaptureConfig.CaptureSequenceInterval = 0
SharpCap.SelectedCamera.PrepareToCapture()
SharpCap.SelectedCamera.RunCapture()
wait_status(Expo)
log_status("Process")
log("Captured image " + str(i+1) + " of " + str(Count))
PHD2.Disconnect()
TemperHum.Connected = False
if not aborded:
if WarmUp:
log("Warming up")
warmupcamera()
log("Warm up complete")
if Park:
log("Parking telescope")
SharpCap.Mounts.SelectedMount.Connected = True
SharpCap.Mounts.SelectedMount.Park()
PAUSE = 5
while SharpCap.Mounts.SelectedMount.Altitude > 2:
while SharpCap.Mounts.SelectedMount.Azimuth < 230 or SharpCap.Mounts.SelectedMount.Azimuth > 234:
time.sleep(PAUSE)
log("Telescope parked")
if CloseObs:
sock = socket.create_connection(('192.168.1.154', 6969))
sock.sendall("CLOSE#")
except:
import traceback
log_error(traceback.format_exc())
finally:
if dialog:
dialog.end_capture()
dialog.paint_finish()
log("End Capture")
def warmupcamera():
_TEMP = SharpCap.SelectedCamera.Controls.Find(lambda x: x.Name == "Temperature")
_COOLER = SharpCap.SelectedCamera.Controls.Find(lambda x: x.Name == "Cooler")
_TARGET = SharpCap.SelectedCamera.Controls.Find(lambda x: x.Name == "Target Temperature")
_ANTIDEW = SharpCap.SelectedCamera.Controls.Find(lambda x: x.Name == "Anti Dew Heater")
_POWER = SharpCap.SelectedCamera.Controls.Find(lambda x: x.Name == "Cooler Power")
TEMPSTEP = 5
PAUSE = 5
_TARGET.Value = _TEMP.Value + TEMPSTEP
while _POWER.Value > 0 and _TARGET.Value < 30:
while _TEMP.Value < _TARGET.Value:
time.sleep(PAUSE)
_TARGET.Value = _TARGET.Value + TEMPSTEP
_COOLER.Value = "Off"
_ANTIDEW.Value = "Off"
def autofocus():
A=1
class ValueInput(Panel):
def __init__(self, label, default_value=""):
super(ValueInput, self).__init__()
self.Height = 30
self.default_value = default_value
label_widget = Label()
label_widget.Text = label +":"
label_widget.Dock = DockStyle.Right
label_widget.AutoSize = True
label_widget.Padding = Padding(2)
input_widget = TextBox()
input_widget.Text = str(default_value)
input_widget.Dock = DockStyle.Right
self.Controls.Add(label_widget)
self.Controls.Add(input_widget)
self.label = label_widget
self.input = input_widget
def resize_to_contents(self):
self.Width = self.label.Width + self.input.Width
def get_label(self):
return self.label.Text[:-1]
def get_value(self):
return self.input.Text
def get_value_as_int(self):
return int(self.input.Text)
def get_value_as_float(self):
return float(self.input.Text)
class ComboInput(Panel):
def __init__(self, label, default_value="TEMPer"):
super(ComboInput, self).__init__()
self.Height = 30
self.default_value = default_value
label_widget = Label()
label_widget.Text = label +":"
label_widget.Dock = DockStyle.Right
label_widget.AutoSize = True
label_widget.Padding = Padding(2)
input_widget = ComboBox()
input_widget.Items.Add("TEMPer")
input_widget.Items.Add("TEMPHUM")
input_widget.SelectedItem = str(default_value)
input_widget.Dock = DockStyle.Right
self.Controls.Add(label_widget)
self.Controls.Add(input_widget)
self.label = label_widget
self.input = input_widget
def resize_to_contents(self):
self.Width = self.label.Width + self.input.Width
def get_label(self):
return self.label.Text[:-1]
def get_value(self):
return self.input.SelectedIndex
class FolderPicker(FlowLayoutPanel):
def __init__(self, label, default_value=""):
super(FolderPicker, self).__init__()
self.Height = 30
self.WrapContents = False
self.default_value = default_value
label_widget = Label()
label_widget.Text = label +":"
label_widget.AutoSize = True
label_widget.Padding = Padding(5)
input_widget = TextBox()
input_widget.Text = str(default_value)
input_widget.Dock = DockStyle.Right
browse = Button()
browse.Text = "Browse"
browse.Height = 22
browse.Click += self.get_picked_folder
self.Controls.Add(label_widget)
self.Controls.Add(input_widget)
self.Controls.Add(browse)
self.label = label_widget
self.input = input_widget
self.browse = browse
def resize_to_contents(self):
self.Width = self.label.Width + self.input.Width + self.browse.Width + 30
pass
def get_label(self):
return self.label.Text[:-1]
def get_value(self):
return self.input.Text
def get_picked_folder(self, *args):
folder_picker = FolderBrowserDialog()
folder_picker.Description = "Target Folder"
if self.input.Text != "":
folder_picker.SelectedPath = self.input.Text
if folder_picker.ShowDialog():
self.input.Text = folder_picker.SelectedPath
class LabeledCheckBox(Panel):
def __init__(self, label, default_value=True):
super(LabeledCheckBox, self).__init__()
self.Height = 30
self.default_value = default_value
label_widget = Label()
label_widget.Text = label +":"
label_widget.Dock = DockStyle.Right
label_widget.AutoSize = True
label_widget.Padding = Padding(7)
input_widget = CheckBox()
input_widget.Checked = default_value
input_widget.Dock = DockStyle.Right
input_widget.AutoSize = True
self.Controls.Add(label_widget)
self.Controls.Add(input_widget)
self.label = label_widget
self.input = input_widget
def resize_to_contents(self):
self.Width = self.label.Width
def get_label(self):
return self.label.Text[:-1]
def get_value(self):
return self.input.Checked
class StatePanel(Panel):
def __init__(self, label, width, height):
super(StatePanel, self).__init__()
self.Width = width
self.Height = height
label_widget = Label()
label_widget.Text = label
label_widget.Dock = DockStyle.Top
label_widget.Height = 20
label_widget.BackColor = Color.Gray
label_widget.Font = Font(label_widget.Font, FontStyle.Bold)
label_widget.Padding = Padding(3)
state_widget = RichTextBox()
state_widget.Dock = DockStyle.Top
state_widget.Height = height - label_widget.Height
state_widget.BackColor = Color.Gray
state_widget.Multiline = True
state_widget.ScrollBars = RichTextBoxScrollBars.Vertical
state_widget.WordWrap = True
state_widget.ReadOnly = True
self.Controls.Add(state_widget)
self.Controls.Add(label_widget)
self.label = label_widget
self.info = state_widget
def log(self, text, error=False):
start_selection = self.info.Text.Length
self.info.ReadOnly = False
self.info.AppendText(str(text)+"\n")
self.info.ReadOnly = True
if error:
# Change text color for error
self.info.SelectionStart = start_selection
self.info.SelectionLength = self.info.Text.Length - start_selection
self.info.SelectionColor = Color.Red
# Scroll down the Text box to the bottom
self.info.SelectionStart = self.info.Text.Length
self.info.ScrollToCaret()
def log_status(self, text):
self.label.Text = (str(text))
def log_error(self, text):
self.log(text, True)
def clear_log(self):
self.info.ReadOnly = False
self.info.Text = ""
self.info.ReadOnly = True
class CaptureDither(Form):
def __init__(self):
self.Text = "NTO Batch"
self.TopMost = True
self.Width = 722
self.Height = 300
self.BackColor = Color.FromName("Black")
self.ForeColor = Color.FromName("Orange")
self.object_name = ValueInput("Name", default_value="OBJETO")
self.object_name.Location = Point(-40, 10)
self.object_name.input.Width = 100
self.sequences = ValueInput("Number", default_value=24)
self.sequences.Location = Point(110, 10)
self.sequences.input.MaxLength = 5
self.sequences.input.Width = 40
self.exposition = ValueInput("Exposition", default_value=300)
self.exposition.Location = Point(260, 10)
self.exposition.input.MaxLength = 5
self.exposition.input.Width = 40
self.dither = LabeledCheckBox("Dither", default_value=True)
self.dither.Location = Point(-40, 34)
self.cmostemp = ValueInput("CMOS Temp", default_value=-15)
self.cmostemp.Location = Point(110, 40)
self.cmostemp.input.MaxLength = 5
self.cmostemp.input.Width = 40
self.gain = ValueInput("Gain", default_value=100)
self.gain.Location = Point(260, 40)
self.gain.input.MaxLength = 5
self.gain.input.Width = 40
self.temp = LabeledCheckBox("Temp correction", default_value=True)
self.temp.Location = Point(-40, 64)
self.pause = LabeledCheckBox("Pause on focus", default_value=True)
self.pause.Location = Point(110, 64)
self.tdelay = ValueInput("Focus delay", default_value=20)
self.tdelay.Location = Point(260, 70)
self.tdelay.input.MaxLength = 5
self.tdelay.input.Width = 40
self.threshold = ValueInput("Threshold ºC", default_value=0.5)
self.threshold.Location = Point(-40, 100)
self.threshold.input.MaxLength = 5
self.threshold.input.Width = 40
self.steps = ValueInput("Steps per ºC", default_value=25)
self.steps.Location = Point(110, 100)
self.steps.input.MaxLength = 5
self.steps.input.Width = 40
self.tsource = ComboInput("Temp source", default_value="TEMPer")
self.tsource.Location = Point(260, 100)
self.tsource.input.Width = 70
self.warmup = LabeledCheckBox("Warm Up Camera", default_value=False)
self.warmup.Location = Point(-40, 128)
self.park = LabeledCheckBox("Park Telescope", default_value=False)
self.park.Location = Point(110, 128)
self.closeobs = LabeledCheckBox("Close Obs", default_value=False)
self.closeobs.Location = Point(260, 128)
# Folder picker
self.target_folder = FolderPicker("Output Folder", default_value=desktop_folder)
self.target_folder.Location = Point(10, 160)
self.target_folder.input.Width = 276
self.target_folder.resize_to_contents()
# Process
self.process = StatePanel("Process", width=210, height=232)
self.process.Location = Point(480, 10)
self.process.BorderStyle = BorderStyle.FixedSingle
# Button
self.start = Button()
self.start.Text = 'Start Capturing'
self.start.Width = 440
self.start.Height = 46
self.start.Location = Point(20, 196)
self.start.Font = Font(self.start.Font, FontStyle.Bold)
self.start.Click += self.start_capture
self.Controls.Add(self.object_name)
self.Controls.Add(self.sequences)
self.Controls.Add(self.exposition)
self.Controls.Add(self.dither)
self.Controls.Add(self.cmostemp)
self.Controls.Add(self.gain)
self.Controls.Add(self.temp)
self.Controls.Add(self.pause)
self.Controls.Add(self.tdelay)
self.Controls.Add(self.threshold)
self.Controls.Add(self.steps)
self.Controls.Add(self.tsource)
self.Controls.Add(self.warmup)
self.Controls.Add(self.park)
self.Controls.Add(self.closeobs)
self.Controls.Add(self.target_folder)
self.Controls.Add(self.process)
self.Controls.Add(self.start)
self.processing = False
def OnFormClosing(self, e, *args):
e.Cancel = True
form.Hide()
def start_capture(self, *args):
if self.processing:
global aborded
aborded = True
self.end_capture(aborded)
return
self.process.clear_log()
# Get and convert values from the Windows form into usable types
name = self.object_name.get_value()
exposition = self.get_float_from(self.exposition)
if exposition is None: return
cmostemp = self.get_float_from(self.cmostemp)
if cmostemp is None: return
gain = self.get_float_from(self.gain)
if gain is None: return
dither = self.dither.get_value()
sequences = self.get_int_from(self.sequences)
if sequences is None: return
output = self.target_folder.get_value()
temp = self.temp.get_value()
threshold = self.get_float_from(self.threshold)
if threshold is None: return
steps = self.get_float_from(self.steps)
if steps is None: return
tdelay = self.get_float_from(self.tdelay)
if tdelay is None: return
warmup = self.warmup.get_value()
park = self.park.get_value()
tsource = self.tsource.get_value()
closeobs = self.closeobs.get_value()
pause = self.pause.get_value()
# Call the capture command
self.processing = True
self.start.Text = "Abort Capturing"
capture_dither(
name,
exposition,
dither,
sequences,
output,
temp,
threshold,
steps,
tdelay,
warmup,
park,
tsource,
closeobs,
pause,
gain,
cmostemp,
self
)
def end_capture(self, aborded=False):
self.processing = False
self.start.Text = "Start Capturing"
if aborded:
self.process.log_error("Capture Aborted!")
def wait_abord(self, second):
if not self.processing:
return True
for _ in range(int(second/wait_precision)):
Thread.Sleep(int(wait_precision*1000))
Application.DoEvents()
if not self.processing:
break
if not self.processing:
return True
return False
def wait_abord_status(self, second):
if not self.processing:
return True
acumulado = 0
for _ in range(int(second/wait_precision)):
Thread.Sleep(int(wait_precision*1000))
acumulado = acumulado + 1
self.process.log_status("Capturing : " + str(int(acumulado)) + "/" + str(int(second)))
Application.DoEvents()
if not self.processing:
break
if not self.processing:
return True
return False
def get_int_from(self, attribute):
try:
return attribute.get_value_as_int()
except:
self.process.log_error(
"[Error] Wrong value type for '%s', expected integer: %s"%\
(attribute.get_label(), attribute.get_value())
)
return None
def get_float_from(self, attribute):
try:
return attribute.get_value_as_float()
except:
self.process.log_error(
"[Error] Wrong value type for '%s', expected float: %s"%\
(attribute.get_label(), attribute.get_value())
)
return None
def paint_alert(self):
self.BackColor = Color.FromName("Red")
def paint_finish(self):
if self.BackColor is not Color.FromName("Red"):
self.BackColor = Color.FromName("Lime")
form = CaptureDither()
form.Icon = Icon("C:\Program Files (x86)\SharpCap 3.2\Icons\Batch_icon.ico")
def show_script():
form.Show()
SharpCap.AddCustomButton("BATCH", Image.FromFile("C:\Program Files (x86)\SharpCap 3.2\Icons\Batch_icon.ico"), None, show_script)
http://nto.org.es/SHARED_GLOBAL/Dialog.JPG