Skip to content

Commit 9bdba1f

Browse files
committed
Merge branch 'telrun-dev' of https://github.com/WWGolay/pyscope into telrun-dev
2 parents 0321e30 + 145731a commit 9bdba1f

File tree

6 files changed

+123
-32
lines changed

6 files changed

+123
-32
lines changed

.github/workflows/pypi-publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
steps:
2525
- uses: actions/checkout@v4
2626
- name: Set up Python
27-
uses: actions/setup-python@v4
27+
uses: actions/setup-python@v5
2828
with:
2929
python-version: '3.x'
3030
- name: Install dependencies
@@ -34,4 +34,4 @@ jobs:
3434
- name: Build package
3535
run: python -m build
3636
- name: pypi-publish
37-
uses: pypa/gh-action-pypi-publish@v1.8.10
37+
uses: pypa/gh-action-pypi-publish@v1.8.11

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,4 @@ dmypy.json
140140
*OmniSim*
141141
!coverage.xml
142142
docs/source/api/auto_api/
143+
pgHardware

pyscope/observatory/ascom_camera.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import logging
22

3+
import numpy as np
4+
from astropy.time import Time
5+
36
from .ascom_device import ASCOMDevice
47
from .camera import Camera
58

@@ -15,17 +18,54 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"):
1518
device_number=device_number,
1619
protocol=protocol,
1720
)
21+
self._last_exposure_duration = None
22+
self._last_exposure_start_time = None
23+
self._image_data_type = None
24+
self._DoTranspose = True
25+
self._camera_time = True
1826

1927
def AbortExposure(self):
2028
logger.debug(f"ASCOMCamera.AbortExposure() called")
2129
self._device.AbortExposure()
2230

31+
def SetImageDataType(self):
32+
"""Determine the data type of the image array based on the MaxADU property.
33+
34+
This method is called automatically when the ImageArray property is called
35+
if it has not already been set (initializes to `None`).
36+
It will choose from the following data types based on the MaxADU property:
37+
38+
- numpy.uint8 : (if MaxADU <= 255)
39+
- numpy.uint16 : (default if MaxADU is not defined, or if MaxADU <= 65535)
40+
- numpy.uint32 : (if MaxADU > 65535)
41+
42+
See Also
43+
--------
44+
numpy.uint8
45+
numpy.uint16
46+
numpy.uint32
47+
MaxADU : ASCOM Camera interface property `ASCOM Documentation <https://ascom-standards.org/Help/Developer/html/P_ASCOM_DriverAccess_Camera_MaxADU.htm>`_
48+
"""
49+
logger.debug(f"ASCOMCamera.SetImageDataType() called")
50+
try:
51+
max_adu = self.MaxADU
52+
if max_adu <= 255:
53+
self._image_data_type = np.uint8
54+
elif max_adu <= 65535:
55+
self._image_data_type = np.uint16
56+
else:
57+
self._image_data_type = np.uint32
58+
except:
59+
self._image_data_type = np.uint16
60+
2361
def PulseGuide(self, Direction, Duration):
2462
logger.debug(f"ASCOMCamera.PulseGuide({Direction}, {Duration}) called")
2563
self._device.PulseGuide(Direction, Duration)
2664

2765
def StartExposure(self, Duration, Light):
2866
logger.debug(f"ASCOMCamera.StartExposure({Duration}, {Light}) called")
67+
self._last_exposure_duration = Duration
68+
self._last_exposure_start_time = str(Time.now())
2969
self._device.StartExposure(Duration, Light)
3070

3171
def StopExposure(self):
@@ -85,6 +125,11 @@ def CameraYSize(self):
85125
logger.debug(f"ASCOMCamera.CameraYSize property called")
86126
return self._device.CameraYSize
87127

128+
@property
129+
def CameraTime(self):
130+
logger.debug(f"ASCOMCamera.CameraTime property called")
131+
return self._camera_time
132+
88133
@property
89134
def CanAbortExposure(self):
90135
logger.debug(f"ASCOMCamera.CanAbortExposure property called")
@@ -213,7 +258,14 @@ def HeatSinkTemperature(self):
213258
@property
214259
def ImageArray(self):
215260
logger.debug(f"ASCOMCamera.ImageArray property called")
216-
return self._device.ImageArray
261+
img_array = self._device.ImageArray
262+
# Convert to numpy array and check if it is the correct data type
263+
if self._image_data_type is None:
264+
self.SetImageDataType()
265+
img_array = np.array(img_array, dtype=self._image_data_type)
266+
if self._DoTranspose:
267+
img_array = np.transpose(img_array)
268+
return img_array
217269

218270
@property
219271
def ImageReady(self):
@@ -228,12 +280,34 @@ def IsPulseGuiding(self):
228280
@property
229281
def LastExposureDuration(self):
230282
logger.debug(f"ASCOMCamera.LastExposureDuration property called")
231-
return self._device.LastExposureDuration
283+
last_exposure_duration = self._device.LastExposureDuration
284+
if last_exposure_duration is None or last_exposure_duration == 0:
285+
last_exposure_duration = self.LastInputExposureDuration
286+
self._camera_time = False
287+
return last_exposure_duration
232288

233289
@property
234290
def LastExposureStartTime(self):
235291
logger.debug(f"ASCOMCamera.LastExposureStartTime property called")
236-
return self._device.LastExposureStartTime
292+
last_time = self._device.LastExposureStartTime
293+
""" This code is needed to handle the case of the ASCOM ZWO driver
294+
which returns an empty string instead of None if the camera does not
295+
support the property """
296+
return (
297+
last_time
298+
if last_time != "" and last_time != None
299+
else self._last_exposure_start_time
300+
)
301+
302+
@property
303+
def LastInputExposureDuration(self):
304+
logger.debug(f"ASCOMCamera.LastInputExposureDuration property called")
305+
return self._last_exposure_duration
306+
307+
@LastInputExposureDuration.setter
308+
def LastInputExposureDuration(self, value):
309+
logger.debug(f"ASCOMCamera.LastInputExposureDuration property set to {value}")
310+
self._last_exposure_duration = value
237311

238312
@property
239313
def MaxADU(self):

pyscope/observatory/maxim.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import platform
33
import time
44

5+
from astropy.time import Time
6+
57
from .autofocus import Autofocus
68
from .camera import Camera
79
from .device import Device
@@ -124,7 +126,7 @@ def PulseGuide(self, Direction, Duration):
124126
def StartExposure(self, Duration, Light):
125127
logger.debug(f"StartExposure called with Duration={Duration}, Light={Light}")
126128
self._last_exposure_duration = Duration
127-
self._last_exposure_start_time = time.time()
129+
self._last_exposure_start_time = str(Time.now())
128130
self._com_object.Expose(Duration, Light)
129131

130132
def StopExposure(self):

pyscope/observatory/observatory.py

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,11 +1171,10 @@ def save_last_image(
11711171
logger.exception("Image is not ready, cannot be saved")
11721172
return False
11731173

1174-
if (
1175-
self.camera.ImageArray is None
1176-
or len(self.camera.ImageArray) == 0
1177-
or len(self.camera.ImageArray[0]) == 0
1178-
):
1174+
# Read out the image array
1175+
img_array = self.camera.ImageArray
1176+
1177+
if img_array is None or len(img_array) == 0 or len(img_array) == 0:
11791178
logger.exception("Image array is empty, cannot be saved")
11801179
return False
11811180

@@ -1184,9 +1183,9 @@ def save_last_image(
11841183
hdr["SIMPLE"] = True
11851184
hdr["BITPIX"] = (16, "8 unsigned int, 16 & 32 int, -32 & -64 real")
11861185
hdr["NAXIS"] = (2, "number of axes")
1187-
hdr["NAXIS1"] = (len(self.camera.ImageArray), "fastest changing axis")
1186+
hdr["NAXIS1"] = (len(img_array), "fastest changing axis")
11881187
hdr["NAXIS2"] = (
1189-
len(self.camera.ImageArray[0]),
1188+
len(img_array[0]),
11901189
"next to fastest changing axis",
11911190
)
11921191
hdr["BSCALE"] = (1, "physical=BZERO + BSCALE*array_value")
@@ -1226,7 +1225,7 @@ def save_last_image(
12261225
for hist in history:
12271226
hdr["HISTORY"] = hist
12281227

1229-
hdu = fits.PrimaryHDU(self.camera.ImageArray, header=hdr)
1228+
hdu = fits.PrimaryHDU(img_array, header=hdr)
12301229
hdu.writeto(filename, overwrite=overwrite)
12311230

12321231
if do_fwhm:
@@ -2377,13 +2376,17 @@ def _read_out_kwargs(self, dictionary):
23772376
"filter_focus_offsets", self.filter_focus_offsets
23782377
)
23792378

2380-
self.rotator_reverse = dictionary.get("rotator_reverse", self.rotator_reverse)
2381-
self.rotator_min_angle = dictionary.get(
2382-
"rotator_min_angle", self.rotator_min_angle
2383-
)
2384-
self.rotator_max_angle = dictionary.get(
2385-
"rotator_max_angle", self.rotator_max_angle
2386-
)
2379+
# Not sure if this if statement is a good idea here...
2380+
if dictionary.get("rotator_driver", self.rotator_driver) is not None:
2381+
self.rotator_reverse = dictionary.get(
2382+
"rotator_reverse", self.rotator_reverse
2383+
)
2384+
self.rotator_min_angle = dictionary.get(
2385+
"rotator_min_angle", self.rotator_min_angle
2386+
)
2387+
self.rotator_max_angle = dictionary.get(
2388+
"rotator_max_angle", self.rotator_max_angle
2389+
)
23872390

23882391
self.min_altitude = dictionary.get("min_altitude", self.min_altitude)
23892392
self.settle_time = dictionary.get("settle_time", self.settle_time)
@@ -2424,6 +2427,7 @@ def camera_info(self):
24242427
"JD": (None, "Julian date"),
24252428
"MJD": (None, "Modified Julian date"),
24262429
"MJD-OBS": (None, "Modified Julian date"),
2430+
"CAMTIME": (None, "Exposure time from camera (T) or user (F)"),
24272431
"EXPTIME": (None, "Exposure time [seconds]"),
24282432
"EXPOSURE": (None, "Exposure time [seconds]"),
24292433
"SUBEXP": (None, "Subexposure time [seconds]"),
@@ -2520,8 +2524,13 @@ def camera_info(self):
25202524
except:
25212525
pass
25222526
try:
2523-
info["EXPTIME"] = (self.camera.ExposureTime, info["EXPTIME"][1])
2524-
info["EXPOSURE"] = (self.camera.ExposureTime, info["EXPOSURE"][1])
2527+
last_exposure_duration = self.camera.LastExposureDuration
2528+
info["EXPTIME"] = (last_exposure_duration, info["EXPTIME"][1])
2529+
info["EXPOSURE"] = (last_exposure_duration, info["EXPOSURE"][1])
2530+
except:
2531+
pass
2532+
try:
2533+
info["CAMTIME"] = (self.camera.CameraTime, info["CAMTIME"][1])
25252534
except:
25262535
pass
25272536
try:
@@ -3686,7 +3695,9 @@ def latitude(self, value):
36863695
self._latitude = (
36873696
coord.Latitude(value) if value is not None or value != "" else None
36883697
)
3689-
self.telescope.SiteLatitude = self._latitude.deg
3698+
# If connected, set the telescope site latitude
3699+
if self.telescope.Connected:
3700+
self.telescope.SiteLatitude = self._latitude.deg
36903701
self._config["site"]["latitude"] = (
36913702
self._latitude.to_string(unit=u.degree, sep="dms", precision=5)
36923703
if self._latitude is not None
@@ -3706,7 +3717,8 @@ def longitude(self, value):
37063717
if value is not None or value != ""
37073718
else None
37083719
)
3709-
self.telescope.SiteLongitude = self._longitude.deg
3720+
if self.telescope.Connected:
3721+
self.telescope.SiteLongitude = self._longitude.deg
37103722
self._config["site"]["longitude"] = (
37113723
self._longitude.to_string(unit=u.degree, sep="dms", precision=5)
37123724
if self._longitude is not None
@@ -3857,7 +3869,7 @@ def cover_calibrator_alt(self):
38573869
def cover_calibrator_alt(self, value):
38583870
logger.debug(f"Observatory.cover_calibrator_alt = {value} called")
38593871
self._cover_calibrator_alt = (
3860-
min(max(float(value), 0), 90) if value is not None or value != "" else None
3872+
min(max(float(value), 0), 90) if value is not None and value != "" else None
38613873
)
38623874
self._config["cover_calibrator"]["cover_calibrator_alt"] = (
38633875
str(self._cover_calibrator_alt)
@@ -3874,7 +3886,9 @@ def cover_calibrator_az(self):
38743886
def cover_calibrator_az(self, value):
38753887
logger.debug(f"Observatory.cover_calibrator_az = {value} called")
38763888
self._cover_calibrator_az = (
3877-
min(max(float(value), 0), 360) if value is not None or value != "" else None
3889+
min(max(float(value), 0), 360)
3890+
if value is not None and value != ""
3891+
else None
38783892
)
38793893
self._config["cover_calibrator"]["cover_calibrator_az"] = (
38803894
str(self._cover_calibrator_az)

requirements.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
alpyca == 2.0.4
22
astroplan == 0.9.1
3-
astropy == 5.3.4
3+
astropy == 6.0.0
44
astroquery == 0.4.6
55
astroscrappy == 1.1.0
66
click == 8.1.7
77
cmcrameri == 1.7.0
88
markdown == 3.5.1
99
matplotlib == 3.8.2
10-
numpy == 1.26.2
11-
paramiko == 3.3.1
12-
photutils == 1.9.0
10+
numpy == 1.26.3
11+
paramiko == 3.4.0
12+
photutils == 1.10.0
1313
prettytable == 3.9.0
1414
pywin32 == 306;platform_system=='Windows'
1515
scikit-image == 0.22.0
1616
scipy == 1.11.4
1717
smplotlib == 0.0.9
1818
timezonefinder == 6.2.0
19-
tksheet == 6.2.9
19+
tksheet == 6.3.5
2020
tqdm == 4.66.1

0 commit comments

Comments
 (0)