Skip to content

Commit

Permalink
Merge pull request #52 from scriptorron/50-ccdciel-can-not-do-exposur…
Browse files Browse the repository at this point in the history
…es-in-a-loop

reworked CCD_ABORT_EXPOSURE; fixed Fast Exposure
  • Loading branch information
scriptorron authored Oct 20, 2023
2 parents 94c2b4a + 841eae4 commit 5dea1dc
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 28 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
- use lxml for construction of indi_pylibcamera.xml
- renamed SwitchVector FRAME_TYPE(FRAMETYPE_RAW, FRAMETYPE_PROC) to CCD_CAPTURE_FORMAT(INDI_RAW, INDI_RGB)
to better support AstroDMX
- removed "setSitchVector CCD_ABORT_EXPOSURE" after each exposure start (that did not allow CCDciel to run
exposures in loop)
- reworked handling of CCD_ABORT_EXPOSURE
- fixed Fast Exposure

2.2.0
- fixed Bayer pattern order for HQ camera (pycamera2 or libcamera have change they way they report Bayer pattern order),
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,15 @@ A detailed description of the camera controls can be found in appendix C of the
Please be aware that most of the camera controls affect processed (RGB) pictures only. Do not use automatic exposure
control or AWB when you plan to stack images.

## Client Software

### KStars/EKOS
The driver is developed and tested with KStars/EKOS.

### CCDciel
Since version 2.3.0 the driver works with CCDciel. But you need to stop the preview loop before you start image
captures, otherwise CCDciel will tell you that it can not start exposures. The same happens when using
indi_simulator_ccd as camera driver.

## Known Limitations
- The maximum exposure time of the V1 camera is about 1 second. This limitation is caused by libcamera and the kernel
Expand Down
36 changes: 16 additions & 20 deletions src/indi_pylibcamera/CameraControl.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,13 @@ def __init__(self, parent, config):
self.Sig_Do = threading.Event() # do an action
self.Sig_ActionExpose = threading.Event() # single or fast exposure
self.Sig_ActionExit = threading.Event() # exit exposure loop
self.Sig_ActionAbort = threading.Event() # abort running exposure
self.Sig_CaptureDone = threading.Event()
# exposure loop in separate thread
self.Sig_ActionExit.clear()
self.Sig_ActionExpose.clear()
self.Sig_Do.clear()
self.Sig_ActionAbort.clear()
self.ExposureThread = None


Expand Down Expand Up @@ -606,18 +608,6 @@ def createRgbFits(self, array, metadata):
hdul = fits.HDUList([hdu])
return hdul

def checkAbort(self):
"""check if client has aborted the exposure
Reset CCD_FAST_COUNT FRAMES and acknowledge the abort.
"""
if self.parent.knownVectors["CCD_ABORT_EXPOSURE"]["ABORT"].value == ISwitchState.ON:
self.parent.setVector("CCD_FAST_COUNT", "FRAMES", value=0, state=IVectorState.OK)
self.parent.setVector("CCD_ABORT_EXPOSURE", "ABORT", value=ISwitchState.OFF, state=IVectorState.OK)
self.parent.setVector("CCD_EXPOSURE", "CCD_EXPOSURE_VALUE", value=0, state=IVectorState.OK)
return True
return False

def __ExposureLoop(self):
"""exposure loop
Expand Down Expand Up @@ -653,18 +643,17 @@ def __ExposureLoop(self):
"""
while True:
with self.parent.knownVectorsLock:
self.checkAbort()
DoFastExposure = self.parent.knownVectors["CCD_FAST_TOGGLE"]["INDI_ENABLED"].value == ISwitchState.ON
FastCount_Frames = self.parent.knownVectors["CCD_FAST_COUNT"]["FRAMES"].value
if not DoFastExposure or (FastCount_Frames < 1):
# prepare for next exposure
if FastCount_Frames < 1:
self.parent.setVector("CCD_FAST_COUNT", "FRAMES", value=1, state=IVectorState.OK)
# wait for next action
self.Sig_ActionAbort.clear()
self.Sig_Do.wait()
self.Sig_Do.clear()
if self.Sig_ActionExpose.is_set():
self.parent.setVector("CCD_ABORT_EXPOSURE", "ABORT", value=ISwitchState.OFF, state=IVectorState.OK)
self.Sig_ActionExpose.clear()
if self.Sig_ActionExit.is_set():
# exit exposure loop
Expand Down Expand Up @@ -733,9 +722,9 @@ def __ExposureLoop(self):
self.picam2.stop_()
self.parent.setVector("CCD_EXPOSURE", "CCD_EXPOSURE_VALUE", value=0, state=IVectorState.OK)
return
with self.parent.knownVectorsLock:
Abort = self.checkAbort()
if not Abort:
if self.Sig_ActionAbort.is_set():
self.Sig_ActionAbort.clear()
else:
# get (non-blocking!) frame and meta data
self.Sig_CaptureDone.clear()
ExpectedEndOfExposure = time.time() + self.present_CameraSettings.ExposureTime
Expand All @@ -745,6 +734,7 @@ def __ExposureLoop(self):
)
with self.parent.knownVectorsLock:
PollingPeriod_s = self.parent.knownVectors["POLLING_PERIOD"]["PERIOD_MS"].value / 1e3
Abort = False
while ExpectedEndOfExposure - time.time() > PollingPeriod_s:
# exposure count down
self.parent.setVector(
Expand All @@ -758,9 +748,9 @@ def __ExposureLoop(self):
self.parent.setVector("CCD_EXPOSURE", "CCD_EXPOSURE_VALUE", value=0, state=IVectorState.OK)
return
# allow to abort exposure
with self.parent.knownVectorsLock:
Abort = self.checkAbort()
Abort = self.Sig_ActionAbort.is_set()
if Abort:
self.Sig_ActionAbort.clear()
break
# allow exposure to finish earlier than expected (for instance when in fast exposure mode)
if self.Sig_CaptureDone.is_set():
Expand All @@ -781,8 +771,10 @@ def __ExposureLoop(self):
self.picam2.stop_()
self.parent.setVector("CCD_EXPOSURE", "CCD_EXPOSURE_VALUE", value=0, state=IVectorState.OK)
return
if self.Sig_ActionAbort.is_set():
self.Sig_ActionAbort.clear()
Abort = True
with self.parent.knownVectorsLock:
Abort = Abort or self.checkAbort()
DoFastExposure = self.parent.knownVectors["CCD_FAST_TOGGLE"]["INDI_ENABLED"].value == ISwitchState.ON
FastCount_Frames = self.parent.knownVectors["CCD_FAST_COUNT"]["FRAMES"].value
if not DoFastExposure:
Expand Down Expand Up @@ -846,6 +838,10 @@ def startExposure(self, exposuretime):
raise RuntimeError("Try ro start exposure without having exposure loop running!")
self.ExposureTime = exposuretime
self.Sig_ActionExpose.set()
self.Sig_ActionAbort.clear()
self.Sig_ActionExit.clear()
self.Sig_Do.set()

def abortExposure(self):
self.Sig_ActionExpose.clear()
self.Sig_ActionAbort.set()
38 changes: 30 additions & 8 deletions src/indi_pylibcamera/indi_pylibcamera.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,29 @@ def __init__(self, parent):
)


class AbortVector(ISwitchVector):
"""INDI SwitchVector to abort exposure
"""

def __init__(self, parent):
self.parent = parent
super().__init__(
device=self.parent.device, timestamp=self.parent.timestamp, name="CCD_ABORT_EXPOSURE",
elements=[
ISwitch(name="ABORT", label="Abort", value=ISwitchState.OFF),
],
label="Abort", group="Main Control",
rule=ISwitchRule.ATMOST1, is_savable=False,
)

def set_byClient(self, values: dict):
super().set_byClient(values = values)
if self.get_OnSwitches()[0] == "ABORT":
self.parent.setVector("CCD_FAST_COUNT", "FRAMES", value=0, state=IVectorState.OK)
self.parent.setVector("CCD_EXPOSURE", "CCD_EXPOSURE_VALUE", value=0, state=IVectorState.OK)
self.parent.abortExposure()
self.parent.setVector("CCD_ABORT_EXPOSURE", "ABORT", value=ISwitchState.OFF, state=IVectorState.OK)

class PrintSnoopedValuesVector(ISwitchVector):
"""Button that prints all snooped values as INFO in log
"""
Expand Down Expand Up @@ -776,14 +799,7 @@ def openCamera(self):
self.CameraVectorNames.append("CCD_EXPOSURE")
#
self.checkin(
ISwitchVector(
device=self.device, timestamp=self.timestamp, name="CCD_ABORT_EXPOSURE",
elements=[
ISwitch(name="ABORT", label="Abort", value=ISwitchState.OFF),
],
label="Abort", group="Main Control",
rule=ISwitchRule.ATMOST1, is_savable=False,
),
AbortVector(parent=self),
send_defVector=True,
)
self.CameraVectorNames.append("CCD_ABORT_EXPOSURE")
Expand Down Expand Up @@ -1347,6 +1363,12 @@ def startExposure(self, exposuretime):
self.CameraThread.startExposure(exposuretime)


def abortExposure(self):
"""abort a running exposure
"""
self.CameraThread.abortExposure()


# main entry point
def main():
device = indi_pylibcamera(config=read_config())
Expand Down

0 comments on commit 5dea1dc

Please sign in to comment.