From d66ae4519a7b172303e918ddb2a6f8184c6c3582 Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Sat, 6 Jan 2024 19:37:33 -0600 Subject: [PATCH 01/12] Working on real hardware --- pyscope/observatory/observatory.py | 39 +++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/pyscope/observatory/observatory.py b/pyscope/observatory/observatory.py index aafee518..982433af 100644 --- a/pyscope/observatory/observatory.py +++ b/pyscope/observatory/observatory.py @@ -794,6 +794,7 @@ def connect_all(self): self.telescope.Connected = True if self.telescope.Connected: + print("Telescope connected") logger.info("Telescope connected") else: logger.warning("Telescope failed to connect") @@ -1224,7 +1225,16 @@ def save_last_image( for hist in history: hdr["HISTORY"] = hist - hdu = fits.PrimaryHDU(self.camera.ImageArray, header=hdr) + # By default, the ImageArray is 32 bit int, at least on my system + # of a ZWO 290MM Mini - we need to convert it to 16 bit unsigned int + img_array = np.array(self.camera.ImageArray).astype(np.uint16) + print(img_array.shape) + print(img_array.dtype) + # Transpose the image array to match the FITS standard + # as well as the axis order defined above + img_array = np.transpose(img_array) + + hdu = fits.PrimaryHDU(img_array, header=hdr) hdu.writeto(filename, overwrite=overwrite) if do_fwhm: @@ -2375,13 +2385,15 @@ def _read_out_kwargs(self, dictionary): "filter_focus_offsets", self.filter_focus_offsets ) - self.rotator_reverse = dictionary.get("rotator_reverse", self.rotator_reverse) - self.rotator_min_angle = dictionary.get( - "rotator_min_angle", self.rotator_min_angle - ) - self.rotator_max_angle = dictionary.get( - "rotator_max_angle", self.rotator_max_angle - ) + # Not sure if this if statement is a good idea here... + if dictionary.get("rotator_driver", self.rotator_driver) is not None: + self.rotator_reverse = dictionary.get("rotator_reverse", self.rotator_reverse) + self.rotator_min_angle = dictionary.get( + "rotator_min_angle", self.rotator_min_angle + ) + self.rotator_max_angle = dictionary.get( + "rotator_max_angle", self.rotator_max_angle + ) self.min_altitude = dictionary.get("min_altitude", self.min_altitude) self.settle_time = dictionary.get("settle_time", self.settle_time) @@ -3684,7 +3696,9 @@ def latitude(self, value): self._latitude = ( coord.Latitude(value) if value is not None or value != "" else None ) - self.telescope.SiteLatitude = self._latitude.deg + # If connected, set the telescope site latitude + if self.telescope.Connected: + self.telescope.SiteLatitude = self._latitude.deg self._config["site"]["latitude"] = ( self._latitude.to_string(unit=u.degree, sep="dms", precision=5) if self._latitude is not None @@ -3704,7 +3718,8 @@ def longitude(self, value): if value is not None or value != "" else None ) - self.telescope.SiteLongitude = self._longitude.deg + if self.telescope.Connected: + self.telescope.SiteLongitude = self._longitude.deg self._config["site"]["longitude"] = ( self._longitude.to_string(unit=u.degree, sep="dms", precision=5) if self._longitude is not None @@ -3855,7 +3870,7 @@ def cover_calibrator_alt(self): def cover_calibrator_alt(self, value): logger.debug(f"Observatory.cover_calibrator_alt = {value} called") self._cover_calibrator_alt = ( - min(max(float(value), 0), 90) if value is not None or value != "" else None + min(max(float(value), 0), 90) if value is not None and value != "" else None ) self._config["cover_calibrator"]["cover_calibrator_alt"] = ( str(self._cover_calibrator_alt) @@ -3872,7 +3887,7 @@ def cover_calibrator_az(self): def cover_calibrator_az(self, value): logger.debug(f"Observatory.cover_calibrator_az = {value} called") self._cover_calibrator_az = ( - min(max(float(value), 0), 360) if value is not None or value != "" else None + min(max(float(value), 0), 360) if value is not None and value != "" else None ) self._config["cover_calibrator"]["cover_calibrator_az"] = ( str(self._cover_calibrator_az) From 3519fb410ab6c5abf8ca3f37eb922e236ed9013f Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Sat, 6 Jan 2024 19:54:40 -0600 Subject: [PATCH 02/12] Remove unneeded print statements --- .gitignore | 1 + pyscope/observatory/observatory.py | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 0ba60be8..84d4a9e0 100755 --- a/.gitignore +++ b/.gitignore @@ -140,3 +140,4 @@ dmypy.json *OmniSim* !coverage.xml docs/source/api/auto_api/ +pgHardware \ No newline at end of file diff --git a/pyscope/observatory/observatory.py b/pyscope/observatory/observatory.py index 982433af..32521db1 100644 --- a/pyscope/observatory/observatory.py +++ b/pyscope/observatory/observatory.py @@ -794,7 +794,6 @@ def connect_all(self): self.telescope.Connected = True if self.telescope.Connected: - print("Telescope connected") logger.info("Telescope connected") else: logger.warning("Telescope failed to connect") @@ -1228,8 +1227,6 @@ def save_last_image( # By default, the ImageArray is 32 bit int, at least on my system # of a ZWO 290MM Mini - we need to convert it to 16 bit unsigned int img_array = np.array(self.camera.ImageArray).astype(np.uint16) - print(img_array.shape) - print(img_array.dtype) # Transpose the image array to match the FITS standard # as well as the axis order defined above img_array = np.transpose(img_array) From 856824af74c57bb1f46364916fa4a15f39507f19 Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Sat, 6 Jan 2024 21:06:40 -0600 Subject: [PATCH 03/12] Fix issue 125 add ASCOM EXPTIME --- pyscope/observatory/ascom_camera.py | 12 ++++++++++++ pyscope/observatory/observatory.py | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pyscope/observatory/ascom_camera.py b/pyscope/observatory/ascom_camera.py index a2acf305..602eb7c8 100644 --- a/pyscope/observatory/ascom_camera.py +++ b/pyscope/observatory/ascom_camera.py @@ -15,6 +15,7 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): device_number=device_number, protocol=protocol, ) + self._LastInputExposureDuration = None def AbortExposure(self): logger.debug(f"ASCOMCamera.AbortExposure() called") @@ -26,6 +27,7 @@ def PulseGuide(self, Direction, Duration): def StartExposure(self, Duration, Light): logger.debug(f"ASCOMCamera.StartExposure({Duration}, {Light}) called") + self.LastInputExposureDuration = Duration self._device.StartExposure(Duration, Light) def StopExposure(self): @@ -234,6 +236,16 @@ def LastExposureDuration(self): def LastExposureStartTime(self): logger.debug(f"ASCOMCamera.LastExposureStartTime property called") return self._device.LastExposureStartTime + + @property + def LastInputExposureDuration(self): + logger.debug(f"ASCOMCamera.LastInputExposureDuration property called") + return self._LastInputExposureDuration + + @LastInputExposureDuration.setter + def LastInputExposureDuration(self, value): + logger.debug(f"ASCOMCamera.LastInputExposureDuration property set to {value}") + self._LastInputExposureDuration = value @property def MaxADU(self): diff --git a/pyscope/observatory/observatory.py b/pyscope/observatory/observatory.py index 32521db1..ec296231 100644 --- a/pyscope/observatory/observatory.py +++ b/pyscope/observatory/observatory.py @@ -2417,6 +2417,17 @@ def camera_info(self): self.camera.Connected = True except: return {"CONNECT": (False, "Camera connection")} + + # If ASCOM camera, EXPTIME and EXPOSURE should be obtained + # from camera.LastExposureDuration + if self._camera_driver == "ASCOMCamera": + try: + last_exposure_duration = self.camera.LastExposureDuration + except: + last_exposure_duration = self.camera.LastInputExposureDuration + else: + last_exposure_duration = None + info = { "CAMCON": (True, "Camera connection"), "CAMREADY": (self.camera.ImageReady, "Image ready"), @@ -2431,8 +2442,8 @@ def camera_info(self): "JD": (None, "Julian date"), "MJD": (None, "Modified Julian date"), "MJD-OBS": (None, "Modified Julian date"), - "EXPTIME": (None, "Exposure time [seconds]"), - "EXPOSURE": (None, "Exposure time [seconds]"), + "EXPTIME": (last_exposure_duration, "Exposure time [seconds]"), + "EXPOSURE": (last_exposure_duration, "Exposure time [seconds]"), "SUBEXP": (None, "Subexposure time [seconds]"), "XBINNING": (self.camera.BinX, "Image binning factor in width"), "YBINNING": (self.camera.BinY, "Image binning factor in height"), From 76f811700abd892205ef7985be3d6da3a5ca820f Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Sun, 7 Jan 2024 11:23:35 -0600 Subject: [PATCH 04/12] working exposure time on ZWO ASI290, added CAMTIME flag --- pyscope/observatory/observatory.py | 38 +++++++++++++++++------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/pyscope/observatory/observatory.py b/pyscope/observatory/observatory.py index ec296231..f43b3b9e 100644 --- a/pyscope/observatory/observatory.py +++ b/pyscope/observatory/observatory.py @@ -2416,18 +2416,7 @@ def camera_info(self): try: self.camera.Connected = True except: - return {"CONNECT": (False, "Camera connection")} - - # If ASCOM camera, EXPTIME and EXPOSURE should be obtained - # from camera.LastExposureDuration - if self._camera_driver == "ASCOMCamera": - try: - last_exposure_duration = self.camera.LastExposureDuration - except: - last_exposure_duration = self.camera.LastInputExposureDuration - else: - last_exposure_duration = None - + return {"CONNECT": (False, "Camera connection")} info = { "CAMCON": (True, "Camera connection"), "CAMREADY": (self.camera.ImageReady, "Image ready"), @@ -2442,8 +2431,9 @@ def camera_info(self): "JD": (None, "Julian date"), "MJD": (None, "Modified Julian date"), "MJD-OBS": (None, "Modified Julian date"), - "EXPTIME": (last_exposure_duration, "Exposure time [seconds]"), - "EXPOSURE": (last_exposure_duration, "Exposure time [seconds]"), + "CAMTIME": (None, "Exposure time from camera (T) or user (F)"), + "EXPTIME": (None, "Exposure time [seconds]"), + "EXPOSURE": (None, "Exposure time [seconds]"), "SUBEXP": (None, "Subexposure time [seconds]"), "XBINNING": (self.camera.BinX, "Image binning factor in width"), "YBINNING": (self.camera.BinY, "Image binning factor in height"), @@ -2538,8 +2528,24 @@ def camera_info(self): except: pass try: - info["EXPTIME"] = (self.camera.ExposureTime, info["EXPTIME"][1]) - info["EXPOSURE"] = (self.camera.ExposureTime, info["EXPOSURE"][1]) + # If ASCOM camera, EXPTIME and EXPOSURE should be obtained + # from camera.LastExposureDuration (if that property exists or is not 0.0). + if self._camera_driver == "ASCOMCamera": + try: + last_exposure_duration = self.camera.LastExposureDuration + cam_time = True + if last_exposure_duration == 0: + last_exposure_duration = self.camera.LastInputExposureDuration + cam_time = False + except: + last_exposure_duration = self.camera.LastInputExposureDuration + cam_time = False + else: + last_exposure_duration = None + cam_time = False + info["EXPTIME"] = (last_exposure_duration, info["EXPTIME"][1]) + info["EXPOSURE"] = (last_exposure_duration, info["EXPOSURE"][1]) + info["CAMTIME"] = (cam_time, info["CAMTIME"][1]) except: pass try: From 4aa4021a803c346637fb54bb045503ee2b63b857 Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Sun, 7 Jan 2024 11:58:00 -0600 Subject: [PATCH 05/12] Use datetime to get backup exposure start time --- pyscope/observatory/ascom_camera.py | 14 +++++++++----- pyscope/observatory/maxim.py | 4 +++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pyscope/observatory/ascom_camera.py b/pyscope/observatory/ascom_camera.py index 602eb7c8..551ce4d2 100644 --- a/pyscope/observatory/ascom_camera.py +++ b/pyscope/observatory/ascom_camera.py @@ -1,4 +1,5 @@ import logging +from datetime import datetime as dt from .ascom_device import ASCOMDevice from .camera import Camera @@ -15,7 +16,8 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): device_number=device_number, protocol=protocol, ) - self._LastInputExposureDuration = None + self._last_exposure_duration = None + self._last_exposure_start_time = None def AbortExposure(self): logger.debug(f"ASCOMCamera.AbortExposure() called") @@ -27,7 +29,8 @@ def PulseGuide(self, Direction, Duration): def StartExposure(self, Duration, Light): logger.debug(f"ASCOMCamera.StartExposure({Duration}, {Light}) called") - self.LastInputExposureDuration = Duration + self._last_exposure_duration = Duration + self._last_exposure_start_time = str(dt.utcnow()) self._device.StartExposure(Duration, Light) def StopExposure(self): @@ -235,17 +238,18 @@ def LastExposureDuration(self): @property def LastExposureStartTime(self): logger.debug(f"ASCOMCamera.LastExposureStartTime property called") - return self._device.LastExposureStartTime + last_time = self._device.LastExposureStartTime + return last_time if last_time != '' and last_time != None else self._last_exposure_start_time @property def LastInputExposureDuration(self): logger.debug(f"ASCOMCamera.LastInputExposureDuration property called") - return self._LastInputExposureDuration + return self._last_exposure_duration @LastInputExposureDuration.setter def LastInputExposureDuration(self, value): logger.debug(f"ASCOMCamera.LastInputExposureDuration property set to {value}") - self._LastInputExposureDuration = value + self._last_exposure_duration = value @property def MaxADU(self): diff --git a/pyscope/observatory/maxim.py b/pyscope/observatory/maxim.py index caf1a6b9..95859b3a 100755 --- a/pyscope/observatory/maxim.py +++ b/pyscope/observatory/maxim.py @@ -1,6 +1,8 @@ import logging import platform import time +from win32com.client import Dispatch +from datetime import datetime as dt from .autofocus import Autofocus from .camera import Camera @@ -124,7 +126,7 @@ def PulseGuide(self, Direction, Duration): def StartExposure(self, Duration, Light): logger.debug(f"StartExposure called with Duration={Duration}, Light={Light}") self._last_exposure_duration = Duration - self._last_exposure_start_time = time.time() + self._last_exposure_start_time = str(dt.utcnow()) self._com_object.Expose(Duration, Light) def StopExposure(self): From cdd0a04f8b37af1998030399d11928c68e920ab1 Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Sun, 7 Jan 2024 17:15:12 -0600 Subject: [PATCH 06/12] run black and isort --- pyscope/observatory/ascom_camera.py | 10 +++++++--- pyscope/observatory/maxim.py | 3 ++- pyscope/observatory/observatory.py | 10 +++++++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pyscope/observatory/ascom_camera.py b/pyscope/observatory/ascom_camera.py index 551ce4d2..0ab63d00 100644 --- a/pyscope/observatory/ascom_camera.py +++ b/pyscope/observatory/ascom_camera.py @@ -239,13 +239,17 @@ def LastExposureDuration(self): def LastExposureStartTime(self): logger.debug(f"ASCOMCamera.LastExposureStartTime property called") last_time = self._device.LastExposureStartTime - return last_time if last_time != '' and last_time != None else self._last_exposure_start_time - + return ( + last_time + if last_time != "" and last_time != None + else self._last_exposure_start_time + ) + @property def LastInputExposureDuration(self): logger.debug(f"ASCOMCamera.LastInputExposureDuration property called") return self._last_exposure_duration - + @LastInputExposureDuration.setter def LastInputExposureDuration(self, value): logger.debug(f"ASCOMCamera.LastInputExposureDuration property set to {value}") diff --git a/pyscope/observatory/maxim.py b/pyscope/observatory/maxim.py index 95859b3a..ec871f02 100755 --- a/pyscope/observatory/maxim.py +++ b/pyscope/observatory/maxim.py @@ -1,9 +1,10 @@ import logging import platform import time -from win32com.client import Dispatch from datetime import datetime as dt +from win32com.client import Dispatch + from .autofocus import Autofocus from .camera import Camera from .device import Device diff --git a/pyscope/observatory/observatory.py b/pyscope/observatory/observatory.py index f43b3b9e..a89e9039 100644 --- a/pyscope/observatory/observatory.py +++ b/pyscope/observatory/observatory.py @@ -2384,7 +2384,9 @@ def _read_out_kwargs(self, dictionary): # Not sure if this if statement is a good idea here... if dictionary.get("rotator_driver", self.rotator_driver) is not None: - self.rotator_reverse = dictionary.get("rotator_reverse", self.rotator_reverse) + self.rotator_reverse = dictionary.get( + "rotator_reverse", self.rotator_reverse + ) self.rotator_min_angle = dictionary.get( "rotator_min_angle", self.rotator_min_angle ) @@ -2416,7 +2418,7 @@ def camera_info(self): try: self.camera.Connected = True except: - return {"CONNECT": (False, "Camera connection")} + return {"CONNECT": (False, "Camera connection")} info = { "CAMCON": (True, "Camera connection"), "CAMREADY": (self.camera.ImageReady, "Image ready"), @@ -3901,7 +3903,9 @@ def cover_calibrator_az(self): def cover_calibrator_az(self, value): logger.debug(f"Observatory.cover_calibrator_az = {value} called") self._cover_calibrator_az = ( - min(max(float(value), 0), 360) if value is not None and value != "" else None + min(max(float(value), 0), 360) + if value is not None and value != "" + else None ) self._config["cover_calibrator"]["cover_calibrator_az"] = ( str(self._cover_calibrator_az) From 75f65254f34b331313b027f363deeef854cf8a2b Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Sun, 7 Jan 2024 19:13:04 -0600 Subject: [PATCH 07/12] Remove unneeded win32com --- pyscope/observatory/maxim.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyscope/observatory/maxim.py b/pyscope/observatory/maxim.py index ec871f02..1341b090 100755 --- a/pyscope/observatory/maxim.py +++ b/pyscope/observatory/maxim.py @@ -3,8 +3,6 @@ import time from datetime import datetime as dt -from win32com.client import Dispatch - from .autofocus import Autofocus from .camera import Camera from .device import Device From 57316197e2129737b120ac51bd3bb7cd8bbdc05c Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Sun, 7 Jan 2024 21:49:26 -0600 Subject: [PATCH 08/12] Add SetImageDataType to ascom_camera --- pyscope/observatory/ascom_camera.py | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/pyscope/observatory/ascom_camera.py b/pyscope/observatory/ascom_camera.py index 0ab63d00..b0307b25 100644 --- a/pyscope/observatory/ascom_camera.py +++ b/pyscope/observatory/ascom_camera.py @@ -1,5 +1,6 @@ import logging from datetime import datetime as dt +import numpy as np from .ascom_device import ASCOMDevice from .camera import Camera @@ -18,11 +19,42 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): ) self._last_exposure_duration = None self._last_exposure_start_time = None + self._image_data_type = None def AbortExposure(self): logger.debug(f"ASCOMCamera.AbortExposure() called") self._device.AbortExposure() + def SetImageDataType(self): + """Determine the data type of the image array based on the MaxADU property. + + This method is called automatically when the ImageArray property is called + if it has not already been set (initializes to `None`). + It will choose from the following data types based on the MaxADU property: + + - numpy.uint8 : (if MaxADU <= 255) + - numpy.uint16 : (default if MaxADU is not defined, or if MaxADU <= 65535) + - numpy.uint32 : (if MaxADU > 65535) + + See Also + -------- + numpy.uint8 + numpy.uint16 + numpy.uint32 + MaxADU : ASCOM Camera interface property `ASCOM Documentation `_ + """ + logger.debug(f"ASCOMCamera.SetImageDataType() called") + try: + max_adu = self.MaxADU + if max_adu <= 255: + self._image_data_type = np.uint8 + elif max_adu <= 65535: + self._image_data_type = np.uint16 + else: + self._image_data_type = np.uint32 + except: + self._image_data_type = np.uint16 + def PulseGuide(self, Direction, Duration): logger.debug(f"ASCOMCamera.PulseGuide({Direction}, {Duration}) called") self._device.PulseGuide(Direction, Duration) @@ -218,6 +250,11 @@ def HeatSinkTemperature(self): @property def ImageArray(self): logger.debug(f"ASCOMCamera.ImageArray property called") + img_array = self._device.ImageArray + # Convert to numpy array and check if it is the correct data type + if self._image_data_type is None: + self.SetImageDataType() + img_array = np.array(img_array, dtype=self._image_data_type) return self._device.ImageArray @property From 1628ecd00b9831dc36c919a3af804625f5c9b700 Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:07:09 -0600 Subject: [PATCH 09/12] Moved image transpose to ASCOM Camera driver, improved obs.save_last_image --- pyscope/observatory/ascom_camera.py | 9 ++++++++- pyscope/observatory/observatory.py | 20 ++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/pyscope/observatory/ascom_camera.py b/pyscope/observatory/ascom_camera.py index b0307b25..b39265a8 100644 --- a/pyscope/observatory/ascom_camera.py +++ b/pyscope/observatory/ascom_camera.py @@ -1,5 +1,6 @@ import logging from datetime import datetime as dt + import numpy as np from .ascom_device import ASCOMDevice @@ -20,6 +21,7 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): self._last_exposure_duration = None self._last_exposure_start_time = None self._image_data_type = None + self._DoTranspose = True def AbortExposure(self): logger.debug(f"ASCOMCamera.AbortExposure() called") @@ -255,7 +257,9 @@ def ImageArray(self): if self._image_data_type is None: self.SetImageDataType() img_array = np.array(img_array, dtype=self._image_data_type) - return self._device.ImageArray + if self._DoTranspose: + img_array = np.transpose(img_array) + return img_array @property def ImageReady(self): @@ -276,6 +280,9 @@ def LastExposureDuration(self): def LastExposureStartTime(self): logger.debug(f"ASCOMCamera.LastExposureStartTime property called") last_time = self._device.LastExposureStartTime + """ This code is needed to handle the case of the ASCOM ZWO driver + which returns an empty string instead of None if the camera does not + support the property """ return ( last_time if last_time != "" and last_time != None diff --git a/pyscope/observatory/observatory.py b/pyscope/observatory/observatory.py index a89e9039..c34600ca 100644 --- a/pyscope/observatory/observatory.py +++ b/pyscope/observatory/observatory.py @@ -1168,11 +1168,14 @@ def save_last_image( if not self.camera.ImageReady: logger.exception("Image is not ready, cannot be saved") return False + + # Read out the image array + img_array = self.camera.ImageArray if ( - self.camera.ImageArray is None - or len(self.camera.ImageArray) == 0 - or len(self.camera.ImageArray[0]) == 0 + img_array is None + or len(img_array) == 0 + or len(img_array) == 0 ): logger.exception("Image array is empty, cannot be saved") return False @@ -1182,9 +1185,9 @@ def save_last_image( hdr["SIMPLE"] = True hdr["BITPIX"] = (16, "8 unsigned int, 16 & 32 int, -32 & -64 real") hdr["NAXIS"] = (2, "number of axes") - hdr["NAXIS1"] = (len(self.camera.ImageArray), "fastest changing axis") + hdr["NAXIS1"] = (len(img_array), "fastest changing axis") hdr["NAXIS2"] = ( - len(self.camera.ImageArray[0]), + len(img_array[0]), "next to fastest changing axis", ) hdr["BSCALE"] = (1, "physical=BZERO + BSCALE*array_value") @@ -1224,13 +1227,6 @@ def save_last_image( for hist in history: hdr["HISTORY"] = hist - # By default, the ImageArray is 32 bit int, at least on my system - # of a ZWO 290MM Mini - we need to convert it to 16 bit unsigned int - img_array = np.array(self.camera.ImageArray).astype(np.uint16) - # Transpose the image array to match the FITS standard - # as well as the axis order defined above - img_array = np.transpose(img_array) - hdu = fits.PrimaryHDU(img_array, header=hdr) hdu.writeto(filename, overwrite=overwrite) From 404e03daf1535722bbf7d5dd6c4278b7a3b92749 Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:09:34 -0600 Subject: [PATCH 10/12] Run black formatter --- pyscope/observatory/observatory.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pyscope/observatory/observatory.py b/pyscope/observatory/observatory.py index c34600ca..8bccb5d0 100644 --- a/pyscope/observatory/observatory.py +++ b/pyscope/observatory/observatory.py @@ -1168,15 +1168,11 @@ def save_last_image( if not self.camera.ImageReady: logger.exception("Image is not ready, cannot be saved") return False - + # Read out the image array img_array = self.camera.ImageArray - if ( - img_array is None - or len(img_array) == 0 - or len(img_array) == 0 - ): + if img_array is None or len(img_array) == 0 or len(img_array) == 0: logger.exception("Image array is empty, cannot be saved") return False From 2bf796d7c109a594da0e0e38cda9cbf4246f3d55 Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:13:20 -0600 Subject: [PATCH 11/12] Moved camera EXPTIME check from observatory to ASCOM driver --- pyscope/observatory/ascom_camera.py | 12 +++++++++++- pyscope/observatory/observatory.py | 21 +++++---------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/pyscope/observatory/ascom_camera.py b/pyscope/observatory/ascom_camera.py index b39265a8..79145838 100644 --- a/pyscope/observatory/ascom_camera.py +++ b/pyscope/observatory/ascom_camera.py @@ -22,6 +22,7 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): self._last_exposure_start_time = None self._image_data_type = None self._DoTranspose = True + self._camera_time = True def AbortExposure(self): logger.debug(f"ASCOMCamera.AbortExposure() called") @@ -124,6 +125,11 @@ def CameraYSize(self): logger.debug(f"ASCOMCamera.CameraYSize property called") return self._device.CameraYSize + @property + def CameraTime(self): + logger.debug(f"ASCOMCamera.CameraTime property called") + return self._camera_time + @property def CanAbortExposure(self): logger.debug(f"ASCOMCamera.CanAbortExposure property called") @@ -274,7 +280,11 @@ def IsPulseGuiding(self): @property def LastExposureDuration(self): logger.debug(f"ASCOMCamera.LastExposureDuration property called") - return self._device.LastExposureDuration + last_exposure_duration = self._device.LastExposureDuration + if last_exposure_duration is None or last_exposure_duration == 0: + last_exposure_duration = self.LastInputExposureDuration + self._camera_time = False + return last_exposure_duration @property def LastExposureStartTime(self): diff --git a/pyscope/observatory/observatory.py b/pyscope/observatory/observatory.py index 8bccb5d0..e526962d 100644 --- a/pyscope/observatory/observatory.py +++ b/pyscope/observatory/observatory.py @@ -2522,24 +2522,13 @@ def camera_info(self): except: pass try: - # If ASCOM camera, EXPTIME and EXPOSURE should be obtained - # from camera.LastExposureDuration (if that property exists or is not 0.0). - if self._camera_driver == "ASCOMCamera": - try: - last_exposure_duration = self.camera.LastExposureDuration - cam_time = True - if last_exposure_duration == 0: - last_exposure_duration = self.camera.LastInputExposureDuration - cam_time = False - except: - last_exposure_duration = self.camera.LastInputExposureDuration - cam_time = False - else: - last_exposure_duration = None - cam_time = False + last_exposure_duration = self.camera.LastExposureDuration info["EXPTIME"] = (last_exposure_duration, info["EXPTIME"][1]) info["EXPOSURE"] = (last_exposure_duration, info["EXPOSURE"][1]) - info["CAMTIME"] = (cam_time, info["CAMTIME"][1]) + except: + pass + try: + info["CAMTIME"] = (self.camera.CameraTime, info["CAMTIME"][1]) except: pass try: From 614a7bdd62eddacac5bcbd0e8c3d759937606ef4 Mon Sep 17 00:00:00 2001 From: pgriffin17 <31374077+pgriffin17@users.noreply.github.com> Date: Tue, 9 Jan 2024 08:08:19 -0600 Subject: [PATCH 12/12] Change datetime to astropy.time --- pyscope/observatory/ascom_camera.py | 4 ++-- pyscope/observatory/maxim.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pyscope/observatory/ascom_camera.py b/pyscope/observatory/ascom_camera.py index 79145838..f01eec37 100644 --- a/pyscope/observatory/ascom_camera.py +++ b/pyscope/observatory/ascom_camera.py @@ -1,7 +1,7 @@ import logging -from datetime import datetime as dt import numpy as np +from astropy.time import Time from .ascom_device import ASCOMDevice from .camera import Camera @@ -65,7 +65,7 @@ def PulseGuide(self, Direction, Duration): def StartExposure(self, Duration, Light): logger.debug(f"ASCOMCamera.StartExposure({Duration}, {Light}) called") self._last_exposure_duration = Duration - self._last_exposure_start_time = str(dt.utcnow()) + self._last_exposure_start_time = str(Time.now()) self._device.StartExposure(Duration, Light) def StopExposure(self): diff --git a/pyscope/observatory/maxim.py b/pyscope/observatory/maxim.py index 1341b090..3fa703f9 100755 --- a/pyscope/observatory/maxim.py +++ b/pyscope/observatory/maxim.py @@ -1,7 +1,8 @@ import logging import platform import time -from datetime import datetime as dt + +from astropy.time import Time from .autofocus import Autofocus from .camera import Camera @@ -125,7 +126,7 @@ def PulseGuide(self, Direction, Duration): def StartExposure(self, Duration, Light): logger.debug(f"StartExposure called with Duration={Duration}, Light={Light}") self._last_exposure_duration = Duration - self._last_exposure_start_time = str(dt.utcnow()) + self._last_exposure_start_time = str(Time.now()) self._com_object.Expose(Duration, Light) def StopExposure(self):