diff --git a/.gitmodules b/.gitmodules index 13f0bf5b..20dbecfc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "python/PiFinder/tetra3"] path = python/PiFinder/tetra3 - url = https://github.com/esa/tetra3.git + url = https://github.com/smroid/cedar-solve branch = no_big_files diff --git a/bin/cedar-detect-server-aarch64 b/bin/cedar-detect-server-aarch64 new file mode 100755 index 00000000..78906eb2 Binary files /dev/null and b/bin/cedar-detect-server-aarch64 differ diff --git a/bin/cedar-detect-server-arm64 b/bin/cedar-detect-server-arm64 new file mode 100755 index 00000000..d8b9c570 Binary files /dev/null and b/bin/cedar-detect-server-arm64 differ diff --git a/python/PiFinder/camera_debug.py b/python/PiFinder/camera_debug.py index d28d4bfb..fce31111 100644 --- a/python/PiFinder/camera_debug.py +++ b/python/PiFinder/camera_debug.py @@ -30,7 +30,7 @@ def __init__(self, exposure_time) -> None: self.path = utils.pifinder_dir / "test_images" self.exposure_time = exposure_time self.gain = 10 - self.image = Image.open(self.path / "pifinder_debug.png") + self.image = Image.open(self.path / "debug.png") self.initialize() def initialize(self) -> None: diff --git a/python/PiFinder/catalogs.py b/python/PiFinder/catalogs.py index ebb7ad13..bc9ffe65 100644 --- a/python/PiFinder/catalogs.py +++ b/python/PiFinder/catalogs.py @@ -4,8 +4,6 @@ import pytz from typing import List, Dict, DefaultDict, Optional import numpy as np -import pandas as pd -from collections import defaultdict from sklearn.neighbors import BallTree import PiFinder.calc_utils as calc_utils diff --git a/python/PiFinder/integrator.py b/python/PiFinder/integrator.py index f88de933..a4251c24 100644 --- a/python/PiFinder/integrator.py +++ b/python/PiFinder/integrator.py @@ -23,9 +23,9 @@ def imu_moved(imu_a, imu_b): Compares two IMU states to determine if they are the 'same' if either is none, returns False """ - if imu_a == None: + if imu_a is None: return False - if imu_b == None: + if imu_b is None: return False # figure out the abs difference @@ -37,8 +37,13 @@ def imu_moved(imu_a, imu_b): return False -def integrator(shared_state, solver_queue, console_queue): +def integrator(shared_state, solver_queue, console_queue, is_debug=False): try: + logger = logging.getLogger() + if is_debug: + logger.setLevel(logging.DEBUG) + logging.debug("Starting Integrator") + solved = { "RA": None, "Dec": None, @@ -74,7 +79,6 @@ def integrator(shared_state, solver_queue, console_queue): next_image_solve = None try: next_image_solve = solver_queue.get(block=False) - logging.debug("Next image solve is %s", next_image_solve) except queue.Empty: pass diff --git a/python/PiFinder/main.py b/python/PiFinder/main.py index fc26e16c..181bc982 100644 --- a/python/PiFinder/main.py +++ b/python/PiFinder/main.py @@ -200,7 +200,7 @@ def wake_screen(screen_brightness, shared_state, cfg) -> int: return orig_power_state -def main(script_name=None, show_fps=False): +def main(script_name=None, show_fps=False, verbose=False): """ Get this show on the road! """ @@ -213,8 +213,8 @@ def main(script_name=None, show_fps=False): # Instantiate base keyboard class for keycode keyboard_base = keyboard_interface.KeyboardInterface() - # Set path for test images - root_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) + os_detail, platform, arch = utils.get_os_info() + logging.info(f"PiFinder running on {os_detail}, {platform}, {arch}") # init queues console_queue = Queue() @@ -247,6 +247,7 @@ def main(script_name=None, show_fps=False): ui_state.set_hint_timeout(cfg.get_option("hint_timeout")) ui_state.set_active_list_to_history_list() shared_state.set_ui_state(ui_state) + shared_state.set_arch(arch) # Normal logging.debug("Ui state in main is" + str(shared_state.ui_state())) console = UIConsole(display_device, None, shared_state, command_queues, cfg) console.write("Starting....") @@ -284,7 +285,7 @@ def main(script_name=None, show_fps=False): server_process = Process( target=server.run_server, - args=(keyboard_queue, gps_queue, shared_state), + args=(keyboard_queue, gps_queue, shared_state, verbose), ) server_process.start() @@ -326,7 +327,7 @@ def main(script_name=None, show_fps=False): console.update() solver_process = Process( target=solver.solver, - args=(shared_state, solver_queue, camera_image, console_queue), + args=(shared_state, solver_queue, camera_image, console_queue, verbose), ) solver_process.start() @@ -335,7 +336,7 @@ def main(script_name=None, show_fps=False): console.update() integrator_process = Process( target=integrator.integrator, - args=(shared_state, solver_queue, console_queue), + args=(shared_state, solver_queue, console_queue, verbose), ) integrator_process.start() @@ -824,4 +825,4 @@ def main(script_name=None, show_fps=False): fh.setLevel(logger.level) logger.addHandler(fh) - main(args.script, args.fps) + main(args.script, args.fps, args.verbose) diff --git a/python/PiFinder/server.py b/python/PiFinder/server.py index e0b886fc..8ca2953c 100644 --- a/python/PiFinder/server.py +++ b/python/PiFinder/server.py @@ -454,5 +454,5 @@ def update_gps(self): self.altitude = None -def run_server(q, gps_q, shared_state): - Server(q, gps_q, shared_state) +def run_server(q, gps_q, shared_state, verbose=False): + Server(q, gps_q, shared_state, verbose) diff --git a/python/PiFinder/solver.py b/python/PiFinder/solver.py index bcd079ee..904dc25c 100644 --- a/python/PiFinder/solver.py +++ b/python/PiFinder/solver.py @@ -7,16 +7,30 @@ * If solved, emits solution into queue """ +import numpy as np import time import logging +import sys +from time import perf_counter as precision_timestamp -from PiFinder.tetra3 import Tetra3 +import cedar_detect_client + +import PiFinder.tetra3 from PiFinder import utils +sys.path.append(str(utils.tetra3_dir)) + +# Select method used for star detection and centroiding. True for cedar-detect, +# False for Tetra3. +USE_CEDAR_DETECT = True + -def solver(shared_state, solver_queue, camera_image, console_queue): +def solver(shared_state, solver_queue, camera_image, console_queue, is_debug=False): logging.getLogger("tetra3.Tetra3").addHandler(logging.NullHandler()) - t3 = Tetra3(utils.astro_data_dir / "pifinder_fov10-5_m7_hip.npz") + logging.debug("Starting Solver") + t3 = PiFinder.tetra3.tetra3.Tetra3( + str(utils.cwd_dir / "PiFinder/tetra3/tetra3/data/default_database.npz") + ) last_solve_time = 0 solved = { "RA": None, @@ -25,35 +39,74 @@ def solver(shared_state, solver_queue, camera_image, console_queue): "solve_time": None, "cam_solve_time": 0, } + + if USE_CEDAR_DETECT: + cedar_detect = cedar_detect_client.CedarDetectClient( + binary_path=str(utils.cwd_dir / "../bin/cedar-detect-server-") + + shared_state.arch() + ) try: while True: if shared_state.power_state() <= 0: time.sleep(0.5) # use the time the exposure started here to - # reject images startede before the last solve + # reject images started before the last solve # which might be from the IMU last_image_metadata = shared_state.last_image_metadata() if ( last_image_metadata["exposure_end"] > (last_solve_time) and last_image_metadata["imu_delta"] < 0.1 ): - solve_image = camera_image.copy() + img = camera_image.copy() + img = img.convert(mode="L") + np_image = np.asarray(img, dtype=np.uint8) - new_solve = t3.solve_from_image( - solve_image, - fov_estimate=10.2, - fov_max_error=0.5, - solve_timeout=500, - target_pixel=shared_state.solve_pixel(), + t0 = precision_timestamp() + if USE_CEDAR_DETECT: + centroids = cedar_detect.extract_centroids( + np_image, sigma=8, max_size=8, use_binned=True + ) + else: + logging.info("Falling back to Tetra3 for centroiding") + centroids = tetra3.get_centroids_from_image(np_image) + t_extract = (precision_timestamp() - t0) * 1000 + logging.debug( + "File %s, extracted %d centroids in %.2fms" + % ("camera", len(centroids), t_extract) ) - solved |= new_solve + if len(centroids) == 0: + logging.debug("No stars found, skipping") + return + else: + solution = t3.solve_from_centroids( + centroids, + (512, 512), + fov_estimate=10.2, + fov_max_error=1.0, + match_max_error=0.005, + return_matches=True, + target_pixel=shared_state.solve_pixel(), + solve_timeout=1000, + ) + + if "matched_centroids" in solution: + # Don't clutter printed solution with these fields. + # del solution['matched_centroids'] + # del solution['matched_stars'] + del solution["matched_catID"] + del solution["pattern_centroids"] + del solution["epoch_equinox"] + del solution["epoch_proper_motion"] + del solution["cache_hit_fraction"] + + solved |= solution - total_tetra_time = solved["T_extract"] + solved["T_solve"] + total_tetra_time = t_extract + solved["T_solve"] if total_tetra_time > 1000: console_queue.put(f"SLV: Long: {total_tetra_time}") - if solved["RA"] != None: + if solved["RA"] is not None: # map the RA/DEC to the target pixel RA/DEC solved["RA"] = solved["RA_target"] solved["Dec"] = solved["Dec_target"] diff --git a/python/PiFinder/state.py b/python/PiFinder/state.py index 79728cec..b3bc53f5 100644 --- a/python/PiFinder/state.py +++ b/python/PiFinder/state.py @@ -145,6 +145,7 @@ def __init__(self): self.__datetime_time = None self.__screen = None self.__solve_pixel = config.Config().get_option("solve_pixel") + self.__arch = None def serialize(self, output_file): with open(output_file, "wb") as f: @@ -169,6 +170,12 @@ def power_state(self): def set_power_state(self, v): self.__power_state = v + def arch(self): + return self.__arch + + def set_arch(self, v): + self.__arch = v + def solve_state(self): return self.__solve_state diff --git a/python/PiFinder/tetra3 b/python/PiFinder/tetra3 index a8442fca..9da414c6 160000 --- a/python/PiFinder/tetra3 +++ b/python/PiFinder/tetra3 @@ -1 +1 @@ -Subproject commit a8442fca3ccbbb1ac408b2033dd31929e28684c8 +Subproject commit 9da414c6ce4f377046fb9866ae4038b2da8f7a4c diff --git a/python/PiFinder/ui/preview.py b/python/PiFinder/ui/preview.py index 9b621cb8..9ba7e567 100644 --- a/python/PiFinder/ui/preview.py +++ b/python/PiFinder/ui/preview.py @@ -4,24 +4,26 @@ This module contains all the UI Module classes """ -import uuid -import os -import time -from PIL import Image, ImageDraw, ImageFont, ImageChops, ImageOps -from PiFinder.ui.fonts import Fonts as fonts -from PiFinder import tetra3 -from numpy import ndarray - +from PiFinder.ui.base import UIModule from PiFinder.image_util import ( gamma_correct_high, gamma_correct_med, gamma_correct_low, subtract_background, ) -from PiFinder.ui.base import UIModule +import numpy as np +import time +from PIL import Image, ImageChops, ImageOps +from PiFinder.ui.fonts import Fonts as fonts +from PiFinder import utils +import sys + +sys.path.append(str(utils.tetra3_dir)) class UIPreview(UIModule): + from PiFinder import tetra3 + __title__ = "CAMERA" __button_hints__ = { "B": "Align", @@ -79,7 +81,7 @@ def __init__(self, *args): # the centroiding returns an ndarray # so we're initialiazing one here - self.star_list = ndarray((0, 2)) + self.star_list = np.empty((0, 2)) self.highlight_count = 0 def set_exp(self, option): @@ -139,8 +141,8 @@ def draw_star_selectors(self): for _i in range(self.highlight_count): raw_y, raw_x = self.star_list[_i] - star_x = int(raw_x / 2) - star_y = int(raw_y / 2) + star_x = int(raw_x / 4) + star_y = int(raw_y / 4) x_direction = 1 x_text_offset = 6 @@ -188,13 +190,9 @@ def update(self, force=False): # Fetch Centroids before image is altered # Do this at least once to get a numpy array in # star_list - if self.align_mode: - cent_image_obj = image_obj.resize((256, 256)) - _t = time.time() - self.star_list = tetra3.get_centroids_from_image( - cent_image_obj, - sigma_mode="local_median_abs", - filtsize=11, + if self.align_mode and self.shared_state and self.shared_state.solution(): + self.star_list = np.array( + self.shared_state.solution()["matched_centroids"] ) # Resize diff --git a/python/PiFinder/utils.py b/python/PiFinder/utils.py index e03b4a4f..b96301d1 100644 --- a/python/PiFinder/utils.py +++ b/python/PiFinder/utils.py @@ -14,7 +14,35 @@ def create_path(apath: Path): cwd_dir = Path.cwd() pifinder_dir = Path("..") astro_data_dir = pifinder_dir / "astro_data" +tetra3_dir = pifinder_dir / "python/PiFinder/tetra3/tetra3" data_dir = Path(Path.home(), "PiFinder_data") pifinder_db = astro_data_dir / "pifinder_objects.db" observations_db = data_dir / "observations.db" debug_dump_dir = data_dir / "solver_debug_dumps" + + +def get_os_info(): + import platform + + platform_system = platform.system() + + # Get the architecture (e.g., '64bit', 'ARM') + architecture = platform.machine() + + # For more details, including the specific distribution on Linux + if platform_system == "Linux": + lib = "N/A" + version = "N/A" + libc_ver = (lib, version) + try: + libc_ver = platform.libc_ver(lib=lib, version=version) + except AttributeError: + pass + os_detail = f"{platform_system} ({libc_ver[0]} {libc_ver[1]})" + elif platform_system == "Darwin": + os_detail = f"macOS ({platform.mac_ver()[0]})" + elif platform_system == "Windows": + os_detail = f"Windows ({platform.win32_ver()})" + else: + os_detail = "N/A" + return os_detail, platform_system, architecture diff --git a/requirements.txt b/requirements.txt index 63100867..ae2db874 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,5 @@ sh==1.14.3 skyfield==1.45 timezonefinder==6.1.9 tqdm==4.65.0 -alpyca +grpcio==1.60.0 +protobuf==4.25.2 diff --git a/test_images/debug.png b/test_images/debug.png new file mode 120000 index 00000000..869e023f --- /dev/null +++ b/test_images/debug.png @@ -0,0 +1 @@ +random.png \ No newline at end of file diff --git a/test_images/orion_bad.png b/test_images/orion_bad.png new file mode 100644 index 00000000..963260c0 Binary files /dev/null and b/test_images/orion_bad.png differ diff --git a/test_images/pleiades.png b/test_images/pleiades.png new file mode 100644 index 00000000..baa1062e Binary files /dev/null and b/test_images/pleiades.png differ diff --git a/test_images/random.png b/test_images/random.png new file mode 100644 index 00000000..bdf02366 Binary files /dev/null and b/test_images/random.png differ