Source code for SatelliteCameraViewer.core

""" core """

import math

import numpy as np
from matplotlib.figure import Figure
from matplotlib.collections import PathCollection
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import cartopy.crs as ccrs
from astropy.coordinates import SkyCoord

from .SatelliteCamera import SatelliteCameraError
from .BSC5Stars import BSC5Stars
from .NikonD5Camera import NikonD5Camera
from .DoCameraImage import DoCameraImage
from .DoCubesatViewer import DoCubesatViewer

from .ui import UserInterface
from .misc import arcseconds_to_radians, ra_fix, mag_map, split_plot_mollweide_line_ra_dec_deg, split_plot_mollweide_line
from .ecliptic import ecliptic, body, galactic_plane, moon_illumination
from .constellation_database import ConstellationDatabase
from .stars_in_polygon_icrs import stars_in_polygon_icrs

#
# Do this to debug:
# .../matplotlib/projections/geo.py:397: RuntimeWarning: invalid value encountered in arcsin
# theta = np.arcsin(y / np.sqrt(2))

[docs] class CoreCode: """ CoreCode - the core drawing code for the stars - also includes logic for the user interface :param ui: User interface class :type root: UserInterface """ # https://matplotlib.org/stable/gallery/color/named_colors.html#css-colors _COLORS = { 'fig-facecolor': '#ebebeb', 'sky-facecolor': 'whitesmoke', 'sky-grid': 'lightgrey', 'sky-axis': 'dimgrey', 'sky-label': 'black', 'sky-ecliptic': 'dimgrey', 'sky-galactic': 'saddlebrown', 'earth-facecolor': 'whitesmoke', 'earth-coastline': 'lightgrey', 'earth-grid': 'lightgrey', 'earth-marker': 'red', 'earth': 'red', 'plot-center': 'darkgreen', 'plot-corners': 'darkblue', 'plot-polygon': 'orange', 'plot-hull': 'orange', 'plot-pixels': 'cornflowerblue', 'plot-pixels-corner': 'royalblue', 'stars': 'black', 'stars-found': 'magenta', 'constellations': 'red', 'sun': 'darkorange', 'moon': 'dimgrey', 'match': 'red', } _verbose = False def __init__(self, ui:UserInterface=None): """ CoreCode - the core drawing code for the stars - also includes logic for the user interface :param ui: User interface class :type ui: UserInterface """ if ui is None: raise ValueError('CoreCode() needs ui value') from None self._ui = ui self._timer_id = None # starfield self._create_starfield_subplot() self._paint_starfield_axis() # earth self._create_earth_subplot() self._paint_earth_axis() # camera self._nikon = NikonD5Camera() self._sun = None self._moon = None self.plot_ecliptic() self.plot_sun_moon() if False: # not needed presently becaused the number of stars plotted is so low self.plot_galactic_plane() self._items_plotted = [] self._starfield_centerline_plot = None self._starfield_centerline_data = None self._earthtrack = [] self._earthtrack_plot = None self._earthtrack_mark = None self._switch_accelerate_time = False self._switch_match_stars = False self._switch_earth_vector = False self._pointing = 'vv' self._stars_plot = None mag = 5.0 self._scbsc5 = StarsConstellationsBSC5(mag) # register ourselves with the UI self._ui.core = self @property def ui(self): """ ui """ return self._ui @property def nikon(self): """ nikon - return NikonD5 class. """ return self._nikon
[docs] def plot_in_tk(self, parent, which, row=0, col=0, padx=0, pady=0, sticky='nsew'): """ plot_in_tk """ if which == 'earth': fig = self._earth_fig elif which == 'starfield': fig = self._starfield_fig else: raise ValueError('%s: invalid which value in plot_in_tk()' % (which)) from None canvas = FigureCanvasTkAgg(fig, master=parent) canvas.get_tk_widget().grid(row=row, column=row, padx=padx, pady=pady, sticky=sticky) if which == 'earth': self._earth_canvas = canvas elif which == 'starfield': self._starfield_canvas = canvas else: raise ValueError('%s: invalid which value in plot_in_tk()' % (which)) from None canvas.draw()
[docs] def cubesat_viewer_register(self, u=3, label=None, w=400, h=300): """ cubesat_viewer_register - setup Cubesat. """ self._cv = DoCubesatViewer(u=u, label=label, w=w, h=h)
[docs] def camera_image_register(self, label=None, nx=400, ny=300, w=400, h=300): """ camera_image_register - setup Camera Image. """ self._ci = DoCameraImage(label=label, nx=nx, ny=ny, w=w, h=h)
def _create_starfield_subplot(self): """ _create_starfield_subplot - core logic to build the pyplot area. """ # plt.style.use('classic') # plt.rcParams['toolbar'] = 'None' # was 'toolbar2' but we don't need the controls self._starfield_fig = Figure(figsize=(14.0, 7.04), dpi=65, layout='tight') self._starfield_fig.patch.set_linewidth(0) self._starfield_fig.tight_layout(pad=0.0, h_pad=0.0, w_pad=0.0) self._starfield_fig.set_layout_engine(layout='tight') self._starfield_fig.patch.set_facecolor(self._COLORS['fig-facecolor']) # projection='mollweide' is an equal-area, pseudocylindrical projection where parallels are straight lines. self._starfield_ax = self._starfield_fig.add_subplot(1, 1, 1, projection='mollweide') self._starfield_ax.set_facecolor(self._COLORS['sky-facecolor']) def _paint_starfield_axis(self): """ _paint_starfield_axis - x and y axis (which is really ra and dec). """ # match font to rest of user interface (and hence system) font_size = self._ui.font['size'] # grid lines self._starfield_ax.grid(color=self._COLORS['sky-grid'], alpha=0.5) # x axis (ra) # 24 hours is 360 degrees - one hour is 15 degrees. Mark every other hour (i.e 30 degrees) # note that this projection is -180 to +180 - we need to handle that later on - for now we simply change the lables # When dealing with astronomical coordinates the right ascension increases eastward, located conventionally left on celestial charts. x_values = [math.radians(v) for v in range(-180,180+30,30)] x_labels = ['', '10h','8h','6h','4h','2h','0h','22h','20h','18h','16h','14h', ''] self._starfield_ax.set_xticks(x_values, x_labels, rotation='vertical', color=self._COLORS['sky-axis'], alpha=0.5, fontdict={'fontsize':font_size, 'horizontalalignment':'center', 'verticalalignment':'center'} ) self._starfield_ax.set_xlabel('Right Ascension (hours)', color=self._COLORS['sky-label'], fontdict={'fontsize':font_size, 'horizontalalignment':'center', 'verticalalignment':'top'} ) # y axis (dec) y_values = [math.radians(v) for v in range(-90,90+10,10)] y_labels = [str(v) for v in range(-90,90+10,10)] # remove 90 and 80 and -80 and -90 (as they don't draw correctly - and aren't needed) for ii in [0, 1, -1, -2]: y_labels[ii] = '' self._starfield_ax.set_yticks(y_values, y_labels, color=self._COLORS['sky-axis'], fontdict={'fontsize':font_size, 'horizontalalignment':'center', 'verticalalignment':'center'} ) self._starfield_ax.set_ylabel('Declination (degrees)', color=self._COLORS['sky-label'], fontdict={'fontsize':font_size, 'horizontalalignment':'right', 'verticalalignment':'center'} ) def _create_earth_subplot(self): """ _create_earth_subplot - core logic to build the pyplot area. """ self._earth_fig = Figure(figsize=(3.0, 3.00), dpi=65, layout='tight') self._earth_fig.patch.set_linewidth(0) self._earth_fig.tight_layout(pad=0.0, h_pad=0.0, w_pad=0.0) self._earth_fig.set_layout_engine(layout='tight') self._earth_fig.patch.set_facecolor(self._COLORS['fig-facecolor']) self._earth_ax = self._earth_fig.add_subplot(1, 1, 1, projection=ccrs.Robinson()) self._earth_ax.set_facecolor(self._COLORS['earth-facecolor']) def _paint_earth_axis(self): """ _paint_earth_axis - x and y axis. """ #self._earth_ax.set_extent([-180, 180, -90, 90], crs=projection()) self._earth_ax.set_global() self._earth_ax.coastlines(resolution='110m', color=self._COLORS['earth-coastline']) self._earth_ax.grid(color=self._COLORS['earth-grid'], alpha=0.5) #self._earth_ax.gridlines(draw_labels=True) # self._earth_ax.get_xaxis().set_visible(False) # self._earth_ax.get_yaxis().set_visible(False)
[docs] def update_starfield_and_more(self): """ update_starfield_and_more - primary logic to redraw the sky plot. """ # Key Details for Mollweide Projection in Matplotlib: # Input Data Formats: Data must be in radians, not degrees, for proper positioning # X-Axis (Longitude): Ranges from -PI to +PI # Y-Axis (Latitude): Ranges from -PI/2 to +PI/2 # Plotting with longitude (x) and latitude (y) in radians # plt.plot(lon_in_radians, lat_in_radians) # clean up from previous run self.items_plotted_clear() # center self.plot_starfield_centerline_data() self.plot_starfield_centerline() # adjust camera/satellite attitude if self._pointing == 'vv': # follow satellite path (it's velocity vector) self.nikon.camera.choose_attitude( self._pointing, # XYZ for satellite == set by vv (velocity vector) # XYZ for camera == roll, pitch, yaw # but cubesat implements the camera on the +Y side - TODO cam_yaw_deg=self.ui.rpy_values_deg['roll'], cam_pitch_deg=self.ui.rpy_values_deg['pitch'], cam_roll_deg=self.ui.rpy_values_deg['yaw'], ) else: self.nikon.camera.choose_attitude( self._pointing, # XYZ for satellite == roll, pitch, yaw sat_yaw_deg=self.ui.rpy_values_deg['roll'], sat_pitch_deg=self.ui.rpy_values_deg['pitch'], sat_roll_deg=self.ui.rpy_values_deg['yaw'], # XYZ for camera == roll, pitch, yaw cam_yaw_deg=0.0, cam_pitch_deg=90.0, # Cubesat implements the camera on the +Y side cam_roll_deg=0.0, ) # other forms of pointing - yet to be coded #self.nikon.camera.choose_attitude('star', star_ra_deg=100, star_dec_deg=70) #self.nikon.camera.choose_attitude('nadir') #self.nikon.camera.choose_attitude('ground', earth_lat_deg=36.974117, earth_lon_deg=-122.030792) # border p = self.plot_border_vectors() self.items_plotted_add(p) # camera view /box/polygon show_box = False show_poly = False show_hull = False if show_box: # A box does not show edges correctly p = self.plot_corners() self.items_plotted_add(p) if show_poly: # Four dots in the corners p = self.plot_polygons() self.items_plotted_add(p) if show_hull: p = self.plot_hull() self.items_plotted_add(p) # matrix of pixels (a matrix of pixels) p = self.plot_pixels(nsteps=1) self.items_plotted_add(p) earth_ra_deg, earth_dec_deg = self.nikon.earth_center_radec_simple() earth_radius_deg = self.nikon.earth_angular_radius() if self._switch_match_stars: # Star chart p = self.plot_matched_closest_star() if p: self.items_plotted_add(p) # Camera p = self.camera_image_matched_stars() if p: self.items_plotted_add(p) # Earth intercept (maybe) earth_ra_rad = math.radians(float(earth_ra_deg)) earth_dec_rad = math.radians(float(earth_dec_deg)) earth_ra_rad = ra_fix([earth_ra_rad])[0] # s = ... TODO s = earth_radius_deg * 3.0 * self._starfield_fig.dpi p = self._starfield_ax.scatter([earth_ra_rad], [earth_dec_rad], facecolors='none', edgecolors=self._COLORS['earth'], alpha=1.0, s=s) self.items_plotted_add(p) try: earth_points_deg = self.nikon.camera_fov_intercept_earth(border_step=10) print('camera_fov_intercept_earth():', 'len(earth_points_deg) =', len(earth_points_deg)) self.plot_earth_outline(earth_points_deg) # pixels = self.nikon.camera_fov_intercept_earth() # print('camera_fov_intercept_earth():', 'pixels =', pixels) except SatelliteCameraError as e: # print('camera_fov_intercept_earth():', e) pass # Lat, long & altitude of satellite sat_lon_deg, sat_lat_deg, sat_alt_km = self.nikon.sat_lon_lat_alt() self.plot_earthtrack_dot(sat_lon_deg, sat_lat_deg) # Shadow ? shadow = self.nikon.sat_in_eclipse() if self._switch_earth_vector: # TODO - this is debug only at present v1 = self.nikon.earth_center_vector() v2 = self.nikon.earth_center_vector_icrs() earth_ra_deg, earth_dec_deg = self.nikon.earth_center_radec_simple() cam_earth_ra_deg, cam_earth_dec_deg = self.nikon.earth_center_radec() print('Earth: vector = [%6.3f, %6.3f, %6.3f] %s' % (v1[0], v1[1], v1[2], str(v2).replace('\n',' ')), end='') print(' ra,dec = [%6.2f,%6.2f] [%6.2f,%6.2f]' % (earth_ra_deg, earth_dec_deg, cam_earth_ra_deg, cam_earth_dec_deg)) # build return string - showing camera info angular_width, angular_height, solid_angle_steradians = self.nikon.camera_fov_angular_width_height() s = '' s += '[%5.1f,%5.1f] %5.1f Km | %s\n' % (sat_lon_deg, sat_lat_deg, sat_alt_km, self.nikon.obs_time.strftime('%Y-%m-%d %H:%M:%S %Z')) s += '%s\n' % (str(self.nikon.camera)) s += '[%5.1f,%5.1f] @ %5.1f Km radius | %.1f deg width by %.1f deg height |' % (earth_ra_deg, earth_dec_deg, earth_radius_deg, angular_width, angular_height) s += ' %.1f%% of whole sky' % (100.0*solid_angle_steradians/(4*math.pi)) if shadow: s += ' | shadow' self.ui.camera_info(s)
[docs] def plot_sun_moon(self): """ plot_sun_moon - add sun and moon to the sky plot. """ fraction = moon_illumination(self.nikon.obs_time) moon_ra_rad, moon_dec_rad = body('moon', self.nikon.obs_time) moon_ra_rad = ra_fix([moon_ra_rad])[0] if self._moon: self._moon.set_offsets([[moon_ra_rad, moon_dec_rad]]) else: self._moon = self._starfield_ax.scatter([moon_ra_rad], [moon_dec_rad], color=self._COLORS['moon'], alpha=1.0, s=50.0) sun_ra_rad, sun_dec_rad = body('sun', self.nikon.obs_time) sun_ra_rad = ra_fix([sun_ra_rad])[0] # sun diameter is 0.533 degrees if self._sun: self._sun.set_offsets([[sun_ra_rad, sun_dec_rad]]) else: self._sun = self._starfield_ax.scatter([sun_ra_rad], [sun_dec_rad], color=self._COLORS['sun'], alpha=1.0, s=300.0)
[docs] def plot_earth_outline(self, earth_points_deg): """ plot_earth_outline - add outline of earth to the sky plot. """ ra_rad = [math.radians(float(v[0])) for v in earth_points_deg] dec_rad = [math.radians(float(v[1])) for v in earth_points_deg] ra_rad = ra_fix(ra_rad) return self._starfield_ax.plot(ra_rad, dec_rad, label='Earth Outline', alpha=0.25, color=self._COLORS['earth'], linewidth=4.0)
[docs] def plot_galactic_plane(self): """ plot_galactic_plane - add galactic plan line to the sky plot. """ ra_dec_rad = galactic_plane() segments = split_plot_mollweide_line(ra_dec_rad[0], ra_dec_rad[1], is_radians=True) for ra_rad, dec_rad in segments: ra_rad = ra_fix(ra_rad) _ = self._starfield_ax.plot(ra_rad, dec_rad, label='Galactic Plane', alpha=0.25, color=self._COLORS['sky-galactic'], linewidth=30.0) #, linestyle='dashed')
[docs] def plot_ecliptic(self): """ plot_ecliptic - add ecliptic line to the sky plot """ ra_dec_rad = ecliptic() segments = split_plot_mollweide_line(ra_dec_rad[0], ra_dec_rad[1], is_radians=True) for ra_rad, dec_rad in segments: ra_rad = ra_fix(ra_rad) _ = self._starfield_ax.plot(ra_rad, dec_rad, label='Ecliptic Plane', alpha=0.75, color=self._COLORS['sky-ecliptic'], linewidth=0.75, linestyle='dotted')
[docs] def plot_stars(self): """ plot_star - add all the stars to the sky plot """ # BSC5 stars stars_ra_rad, stars_dec_rad, stars_mag = self._scbsc5.get_stars() stars_ra_rad = ra_fix(stars_ra_rad) stars_size_pixels = mag_map(stars_mag) const_ra_rad, const_dec_rad, const_mag = self._scbsc5.get_constellations() const_ra_rad = ra_fix(const_ra_rad) const_size_pixels = mag_map(const_mag) p1 = self._starfield_ax.scatter(stars_ra_rad, stars_dec_rad, s=stars_size_pixels, alpha=1, color=self._COLORS['stars'], zorder=5) p2 = self._starfield_ax.scatter(const_ra_rad, const_dec_rad, s=const_size_pixels, alpha=1, color=self._COLORS['constellations'], zorder=5) return p1, p2
[docs] def plot_earthtrack_dot(self, lon_deg:float, lat_deg:float): """ plot_earthtrack_dot """ # We significantly round down because the earth map is tiny on the screen and we don't need many decimal places lon_deg = float(lon_deg.round(1)) lat_deg = float(lat_deg.round(1)) self._earthtrack = self._earthtrack[-63:] if len(self._earthtrack) == 0 or self._earthtrack[-1] != (lon_deg, lat_deg): self._earthtrack.append((lon_deg, lat_deg)) if self._earthtrack_plot is None: self._earthtrack_plot = self._earth_ax.plot(self._earthtrack, color=self._COLORS['earth-marker'], alpha=0.5, linewidth=2.0, transform=ccrs.Geodetic()) self._earthtrack_mark = self._earth_ax.plot(lon_deg, lat_deg, color=self._COLORS['earth-marker'], alpha=1.0, marker='o', markersize=6, transform=ccrs.Geodetic()) else: self._earthtrack_plot[0].set_data([v[0] for v in self._earthtrack], [v[1] for v in self._earthtrack]) self._earthtrack_mark[0].set_data([lon_deg], [lat_deg])
[docs] def plot_earthtrack_dot_clear(self): """ plot_earthtrack_dot_clear """ self._earthtrack = [] if self._earthtrack_plot is not None: self._earthtrack_plot[0].set_data([], []) self._earthtrack_mark[0].set_data([], [])
[docs] def draw(self): """ draw - flush everything to the screen. """ self._starfield_canvas.draw() self._earth_canvas.draw()
[docs] def items_plotted_clear(self): """ items_plotted_clear """ if len(self._items_plotted) == 0: return for p in self._items_plotted: if isinstance(p, PathCollection): p.remove() elif isinstance(p, list): p[0].remove() self._items_plotted = []
[docs] def items_plotted_add(self, p): """ items_plotted_add """ if isinstance(p, list): self._items_plotted += p else: self._items_plotted.append(p)
[docs] def plot_starfield_centerline_data(self): """ plot_starfield_centerline_data """ ra_deg, dec_deg = self.nikon.pixel_to_radec(self.nikon.camera.nx/2, self.nikon.camera.ny/2) ra_rad = math.radians(ra_deg) dec_rad = np.array([math.radians(dec_deg)]) ra_rad = ra_fix(np.array([ra_rad])) new_data = np.array([np.array([ra_rad[0], dec_rad[0]])]) if self._starfield_centerline_data is None: self._starfield_centerline_data = new_data else: self._starfield_centerline_data = np.append(self._starfield_centerline_data, new_data, axis=0)
# we keep this one on the screen - so processed differently
[docs] def plot_starfield_centerline(self): """ plot_starfield_centerline """ if self._starfield_centerline_plot is None: # this is an empty plot p = self._starfield_ax.plot([], [], color=self._COLORS['plot-center'], alpha=1.0, linewidth=1.5, zorder=10) self._starfield_centerline_plot = p if self._starfield_centerline_data is None: return # trim data (63 is a random number) self._starfield_centerline_data = self._starfield_centerline_data[-63:] # print('center_line_data =', np.degrees(self._starfield_centerline_data)) # self._starfield_centerline_plot[0].set_data(self._starfield_centerline_data[:, 0], self._starfield_centerline_data[:, 1]) segments = split_plot_mollweide_line(np.degrees(self._starfield_centerline_data[:, 0]), np.degrees(self._starfield_centerline_data[:, 1])) # TODO - off by one error still! all_ra_rad = [] all_dec_rad = [] for ra_rad, dec_rad in segments: # ra_rad = ra_fix(ra_rad) all_ra_rad.extend(ra_rad[1:]) all_dec_rad.extend(dec_rad[1:]) if len(all_ra_rad) == 1: all_ra_rad.extend(ra_rad) all_dec_rad.extend(dec_rad) all_ra_rad.append(np.nan) all_dec_rad.append(np.nan) # print('all_ra_rad =', np.degrees(all_ra_rad)) # print('all_dec_rad =', np.degrees(all_dec_rad)) self._starfield_centerline_plot[0].set_data(all_ra_rad, all_dec_rad)
[docs] def plot_starfield_centerline_clear(self): """ plot_starfield_centerline_clear """ self._starfield_centerline_data = None self._starfield_centerline_plot[0].set_data([], [])
[docs] def plot_corners(self): """ plot_corners """ box, _ = self.nikon.camera_fov_radec_box() corners = [ box['top_left'], box['top_right'], box['bottom_right'], box['bottom_left'], box['top_left'], # close rectangle back to first point ] ra_rad = [math.radians(v['ra_deg']) for v in corners] dec_rad = [math.radians(v['dec_deg']) for v in corners] ra_rad = ra_fix(ra_rad) # not fixed for wrap yet p = self._starfield_ax.plot(ra_rad, dec_rad, color=self._COLORS['plot-corners'], alpha=0.25, linewidth=3.0, linestyle='dashed') return p
[docs] def plot_polygons(self): """ plot_polygons """ _, polygon = self.nikon.camera_fov_radec_box() ra_rad = [math.radians(float(v)) for v in polygon[0]] dec_rad = [math.radians(float(v)) for v in polygon[1]] ra_rad = ra_fix(ra_rad) p = self._starfield_ax.scatter(ra_rad, dec_rad, color=self._COLORS['plot-polygon'], alpha=0.5, s=40.0) return p
[docs] def plot_hull(self): """ plot_hull """ hull_coords, _ = self.nikon.camera_fov_convex_hull() ra_rad = [math.radians(float(v)) for v in hull_coords[0]] dec_rad = [math.radians(float(v)) for v in hull_coords[1]] ra_rad = ra_fix(ra_rad) # not fixed for wrap yet p = self._starfield_ax.plot(ra_rad, dec_rad, color=self._COLORS['plot-hull'], alpha=0.15, linewidth=2.0, linestyle='solid') return p
[docs] def plot_pixels(self, nsteps=3): """ plot_pixels """ pixels = self.nikon.sensor_to_radec(nsteps=nsteps) ra_rad = [math.radians(v[0]) for v in pixels.values()] dec_rad = [math.radians(v[1]) for v in pixels.values()] ra_rad = ra_fix(ra_rad) # all points p3 = self._starfield_ax.scatter(ra_rad, dec_rad, color=self._COLORS['plot-pixels'], alpha=0.75, s=20.0) # learing left point/corner p1 = self._starfield_ax.scatter(ra_rad[nsteps:nsteps+1], dec_rad[nsteps:nsteps+1], color=self._COLORS['plot-pixels-corner'], alpha=1.0, s=100.0) # learing right point/corner p2 = self._starfield_ax.scatter(ra_rad[-1:], dec_rad[-1], color=self._COLORS['plot-pixels-corner'], alpha=1.0, s=100.0) return [p1,p2,p3]
[docs] def plot_border_vectors(self): """ plot_border_vectors """ border_vectors = self.nikon.camera_fov_border_vectors(border_step=25) plots = [] for ra_rad, dec_rad in split_plot_mollweide_line_ra_dec_deg(border_vectors): ra_rad = ra_fix(ra_rad) p = self._starfield_ax.plot(ra_rad, dec_rad, color='red', alpha=1.0, linewidth=1.5, linestyle='solid') plots.append(p) return plots
def _match_stars_in_polygon(self): """ match_stars_in_polygon """ border_vectors = self.nikon.camera_fov_border_vectors(border_step=10) # look for stars ... inside_mask = stars_in_polygon_icrs(self._scbsc5.skycoords, border_vectors) found_stars = self._scbsc5.skycoords[inside_mask] return found_stars, inside_mask
[docs] def camera_image_matched_stars(self): """ camera_image_matched_stars """ # now find all stars inside camara view ... found_stars, inside_mask = self._match_stars_in_polygon() if len(found_stars) == 0: # paint an empty image self._ci.stars() return None # all the stars (will be masked before use) stars_ra_rad, stars_dec_rad, stars_mag = self._scbsc5.get_stars() stars_ra_rad = stars_ra_rad[inside_mask] stars_dec_rad = stars_dec_rad[inside_mask] stars_mag = stars_mag[inside_mask] # camera image def build_xy_list(found_stars): """ build_xy_list """ xy_list = [] for star in found_stars: ra_deg = star.ra.degree dec_deg = star.dec.degree try: px, py = self.nikon.radec_to_pixel(ra_deg, dec_deg) except SatelliteCameraError as e: # outside the view of the camera - should not happen if polygon is accurate continue xy_list.append((px,py)) if len(found_stars) != len(xy_list): print('build_xy_list():', len(found_stars), '!=', len(xy_list)) return xy_list def build_mag_bucket_list(stars_mag): """ build_mag_bucket_list """ bucket = {} for mag in stars_mag: try: bucket[int(mag)] += 1 except KeyError: bucket[int(mag)] = 1 s = sorted(['%d:%d' % (k, v) for k,v in bucket.items()]) return '[' + ', '.join(s) + ']' self._ci.stars(xy_list=build_xy_list(found_stars), mag_list=stars_mag) #self._ci.outline() s = 'Camera image has %d stars' % len(found_stars) s += '\nMagnitude: ' + build_mag_bucket_list(stars_mag) self.ui.misc_text(s) stars_ra_rad = ra_fix(stars_ra_rad) stars_size_pixels = mag_map(stars_mag) # plot p1 = self._starfield_ax.scatter(stars_ra_rad, stars_dec_rad, s=stars_size_pixels, alpha=1, color=self._COLORS['stars-found'], zorder=5) return p1
[docs] def plot_matched_closest_star(self): """ plot_matched_closest_star """ # look for matched (closest) star matches = self._match_against_bsc5() ra_rad = [] dec_rad = [] for m in matches: center_ra_rad = m['coords'].ra.radian center_dec_rad = m['coords'].dec.radian star_ra_rad = m['star_coord'].ra.radian star_dec_rad = m['star_coord'].dec.radian star = matches[0]['bsc5'] if star.constellation: c = ConstellationDatabase.fullmatch(star.constellation) c = c[0].constellation + ' (' + c[0].meaning + ')' else: c = None s = 'camera [%.1f,%.1f] deg\n star [%.1f,%.1f] deg +/- %.3f deg\n %s%s @ %.1f mag' % ( math.degrees(center_ra_rad), math.degrees(center_dec_rad), math.degrees(star_ra_rad), math.degrees(star_dec_rad), math.degrees(arcseconds_to_radians(m['separation_arcsec'])), star.name if star.name else '-', ' in ' + c if c else '', star.mag ) self.ui.star_found_text(s) ra_rad.append(center_ra_rad) dec_rad.append(center_dec_rad) ra_rad.append(star_ra_rad) dec_rad.append(star_dec_rad) # TODO - only one for now break # TODO powered down for now - does not unpaint itself (yet) if self._switch_match_stars: star = matches[0]['bsc5'] diameter = mag_map(star.mag, multiplier=4.0) ra_rad = ra_fix(ra_rad) p1 = self._starfield_ax.plot(ra_rad, dec_rad, label='Star match', alpha=1.0, color=self._COLORS['match'], linewidth=3.0) p2 = self._starfield_ax.scatter(ra_rad[-1:], dec_rad[-1:], facecolors='none', edgecolors=self._COLORS['match'], s=diameter) p3 = self._starfield_ax.scatter(ra_rad[0:1], dec_rad[0:1], facecolors=self._COLORS['plot-center'], edgecolors=self._COLORS['plot-center'], s=60) return [p1, p2, p3] return None
[docs] def do_match_stars(self, value): """ do_match_stars """ match_stars_show = value self._switch_match_stars = match_stars_show if match_stars_show: # also show stars if not showing self._do_stars_real(False) self.ui.stars_button_set(True) self._do_stars_real(True) else: self._ci.reset() s = '' self.ui.star_found_text(s) self.ui.misc_text(s)
[docs] def do_earth_vector(self, value): """ do_earth_vector """ self._switch_earth_vector = value
[docs] def do_mag(self, value): """ do_mag """ self._do_stars_real(False) self._scbsc5.max_mag = value # turn on stars button self.ui.stars_button_set(True) self._do_stars_real(True)
[docs] def do_focal_length(self, focal_length): """ do_focal_length """ self.nikon.camera.reload(focal_length_mm=float(focal_length)) # refresh everything self.update_starfield_and_more() self.draw()
[docs] def do_accelerate(self, value): """ do_accelerate """ self._switch_accelerate_time = value # reset line data - otherwise it's messy self.plot_starfield_centerline_clear() self.draw() # reset timer interval self.timer_reset()
[docs] def do_stars(self, value): """ do_stars """ self._do_stars_real(value)
def _do_stars_real(self, value): """ do_stars_real """ self.do_stars_clear() if value: # show stars self._stars_plot = self.plot_stars() self.draw()
[docs] def do_stars_clear(self): """ do_stars_clear """ if self._stars_plot is not None: for p in self._stars_plot: p.remove() self._stars_plot = None
[docs] def do_satellite_selection(self, val): """ do_satellite_selection """ self.nikon.find_tle(val) # remove satellite track self.plot_starfield_centerline_clear() self.plot_earthtrack_dot_clear() # refresh everything self.update_starfield_and_more() self.draw()
[docs] def do_satellite_attitude(self, val): """ do_satellite_attitude """ self._pointing = val self.plot_starfield_centerline_clear() self.update_starfield_and_more() self.draw()
[docs] def do_rpy(self, val, k): """ do_rpy """ self.ui.rpy_values_deg[k] = float(val) # refresh everything self.update_starfield_and_more() self.draw() self._cv.update_orientation( # XYZ should be r,p,y - but camera is on +Y side - so swap pitch & roll for some reason self.ui.rpy_values_deg['roll'], self.ui.rpy_values_deg['pitch'], self.ui.rpy_values_deg['yaw'] )
[docs] def do_reset(self): """ do_reset """ # RESET focus focal_length = 50 self.ui.focal_length_buttons_set(focal_length=focal_length) self.nikon.camera.reload(focal_length_mm=float(focal_length)) # RESET star magnitude mag = 5.0 self.ui.star_mag_buttons_set(mag=mag) self._scbsc5.max_mag = mag # RESET accelerate self.ui.accelerate_button_set(False) self._switch_accelerate_time = False # RESET show stars self.do_stars_clear() self.ui.stars_button_set(False) # RESET rpy for k in self.ui.rpy_values_deg: self.ui.rpy_values_deg[k] = 0.0 self.ui.v_sliders[k].set(0.0) # RESET match self._switch_match_stars = False self.ui.match_stars_button_set(False) # RESET earth self.plot_earthtrack_dot_clear() # RESET text boxes s = '' self.ui.star_found_text(s) self.ui.misc_text(s) # RESET camera image self._ci.reset() # Now redraw everything - by reseting the timer. self.plot_starfield_centerline_clear() # self.update_starfield_and_more() # self.draw() self._cv.update_orientation( self.ui.rpy_values_deg['pitch'], self.ui.rpy_values_deg['roll'], self.ui.rpy_values_deg['yaw'] ) # RESET timer interval self.timer_reset()
def _match_against_bsc5(self): """ _match_against_bsc5 """ # see if we actually match any stars within the view from the camera? center_pixel_x = self.nikon.camera.nx/2 center_pixel_y = self.nikon.camera.ny/2 ra_deg, dec_deg = self.nikon.pixel_to_radec(center_pixel_x, center_pixel_y) # center # create a (for now) array of length one to test against pixels = {(center_pixel_x, center_pixel_y): (ra_deg, dec_deg)} # pixel_list & coords_list need to be in-sync (obviously) pixel_list = list(pixels.keys()) coords_list = list(pixels.values()) # get into SkyCoords's with ICRS coords_list_icrs = SkyCoord(coords_list, unit='deg', frame='icrs') # For each image direction, find nearest star in catalog idx, sep2d, _ = coords_list_icrs.match_to_catalog_sky(self._scbsc5.skycoords) # Return matches with separation info matches = [] for ii in range(len(coords_list_icrs)): matches.append({ 'pixel': pixel_list[ii], 'coords': coords_list_icrs[ii], 'bsc5': self._scbsc5.stars[idx[ii]], 'star_coord': self._scbsc5.skycoords[idx[ii]], 'separation_arcsec': sep2d[ii].arcsec, }) return matches
[docs] def timer_reset(self): """ timer_reset """ if self._timer_id is not None: self._ui.root.after_cancel(self._timer_id) self._timer_id = None # reprime self.timer_went_off()
[docs] def timer_went_off(self): """ timer_went_off """ self._timer_id = None # update the time and recaculate the attitude (based on the new time) # and deal with time interval and timers if self._switch_accelerate_time is True: # accelerate - i.e. jump time ahead quickly - TODO this value should be based on orbital params seconds = (60 - self.nikon.obs_time.second) + 60 self.nikon.adjust_by_seconds(seconds) # hence step is half a second timer_step = 500 else: # realtime self.nikon.now() # hence step is five seconds timer_step = 5 * 1000 # now repaint/update sky self.update_starfield_and_more() self.plot_sun_moon() # add sun and moon move when time changes (oh so slightly) self.draw() # and finally, setup trigger the next timer self._timer_id = self._ui.root.after(timer_step, lambda: self.timer_went_off())
[docs] class StarsConstellationsBSC5: """ StarsConstellationsBSC5 """ def __init__(self, mag=5.0): """ StarsConstellationsBSC5 """ self._bsc5 = BSC5Stars(max_mag=mag) # self.stars = self._bsc5.stars # self.skycoords = self._bsc5.skycoords @property def stars(self): """ stars """ return self._bsc5.stars @property def skycoords(self): """ skycoords """ return self._bsc5.skycoords @property def max_mag(self): """ max_mag """ return self._bsc5.max_mag @max_mag.setter def max_mag(self, value): """ max_mag """ self._bsc5.max_mag = value
[docs] def get_stars(self): """ stars """ stars_ra_rad, stars_dec_rad, stars_mag = self._bsc5.get_stars() return stars_ra_rad, stars_dec_rad, stars_mag
[docs] def get_constellations(self, constellations=None): """ constellations """ if constellations is None: # Orion and Sagittarius becuase they are not next to each other constellations = ['Ori', 'Sgr'] const_ra_rad, const_dec_rad, const_mag = self._bsc5.get_constellations(constellations=constellations) return const_ra_rad, const_dec_rad, const_mag