def fill_state(self): state = {} state.update({"time": self.time_mgr.get_sec()}) #state.update({"packjpeg": self.packjpeg}) #state.update({"daymode": self.daymode}) if self.websock_randid != 0: state.update({"rand_id": self.websock_randid}) if self.img is not None and self.cam_err == False: state.update({"expo_code": self.expo_code}) elif self.img is None: state.update({"expo_code": star_finder.EXPO_NO_IMG}) elif self.cam_err: state.update({"expo_code": star_finder.EXPO_CAMERA_ERR}) stable_solution = self.stable_solution() if stable_solution is not None: stable_solution.get_pole_coords() # update time state.update({"solution": stable_solution.to_jsonobj()}) state.update({"star_x": stable_solution.Polaris.cx}) state.update({"star_y": stable_solution.Polaris.cy}) state.update({"pole_x": stable_solution.x}) state.update({"pole_y": stable_solution.y}) state.update({"rotation": stable_solution.get_rotation()}) state.update({ "polaris_ra": (stable_solution.polaris_ra_dec[0] * 360.0) / 24.0 }) state.update({"pix_per_deg": stable_solution.pix_per_deg}) else: state.update({"solution": False}) if self.stars is not None: star_list = self.stars if len(star_list) > 50: star_list = blobstar.sort_brightness(star_list)[0:50] state.update({"stars": blobstar.to_jsonobj(star_list)}) state.update({"polar_clock": self.time_mgr.get_angle()}) state.update({"max_stars": self.max_stars}) if self.extdisp is not None: state.update({"oled_avail": self.extdisp.oled_ok}) state.update({"gps_avail": self.extdisp.gps_ok}) # diagnostic info state.update({"frm_cnt": self.frm_cnt}) if self.debug: state.update({"diag_cnt": self.diag_cnt}) state.update({"diag_dur_all": self.dur_all}) state.update({"diag_dur_sol": self.solu_dur}) state.update({"diag_mem_alloc": gc.mem_alloc()}) state.update({"diag_mem_free": gc.mem_free()}) if self.img_stats is not None: state.update({"img_mean": self.img_stats.mean()}) state.update({"img_stdev": self.img_stats.stdev()}) state.update({"img_max": self.img_stats.max()}) state.update({"img_min": self.img_stats.min()}) return state
def handle_getstate(self, client_stream, req, headers, content): if self.debug: print("handle_getstate") self.handle_query(client_stream, req, reply=False, save=False) state = {} state.update({"time": self.time_mgr.get_sec()}) state.update({"packjpeg": self.packjpeg}) state.update({"daymode": self.daymode}) if self.img is not None and self.cam_err == False: state.update({"expo_code": self.expo_code}) elif self.img is None: state.update({"expo_code": star_finder.EXPO_NO_IMG}) elif self.cam_err: state.update({"expo_code": star_finder.EXPO_CAMERA_ERR}) stable_solution = self.stable_solution() if stable_solution is not None: stable_solution.get_pole_coords() # update time state.update({"solution": stable_solution.to_jsonobj()}) state.update({"star_x": stable_solution.Polaris.cx}) state.update({"star_y": stable_solution.Polaris.cy}) state.update({"pole_x": stable_solution.x}) state.update({"pole_y": stable_solution.y}) state.update({"rotation": stable_solution.get_rotation()}) state.update({ "polaris_ra": (stable_solution.polaris_ra_dec[0] * 360.0) / 24.0 }) state.update({"pix_per_deg": stable_solution.pix_per_deg}) else: state.update({"solution": False}) if self.stars is not None: star_list = self.stars if len(star_list) > 50: star_list = blobstar.sort_brightness(star_list)[0:50] state.update({"stars": blobstar.to_jsonobj(star_list)}) state.update({"polar_clock": self.time_mgr.get_angle()}) state.update({"max_stars": self.max_stars}) # diagnostic info state.update({"frm_cnt": self.frm_cnt}) if self.debug: state.update({"diag_cnt": self.diag_cnt}) state.update({"diag_dur_all": self.dur_all}) state.update({"diag_dur_sol": self.solu_dur}) state.update({"diag_mem_alloc": gc.mem_alloc()}) state.update({"diag_mem_free": gc.mem_free()}) if self.img_stats is not None: state.update({"img_mean": self.img_stats.mean()}) state.update({"img_stdev": self.img_stats.stdev()}) state.update({"img_max": self.img_stats.max()}) state.update({"img_min": self.img_stats.min()}) json_str = ujson.dumps(state) client_stream.write( captive_portal.default_reply_header( content_type="application/json", content_length=len(json_str)) + json_str) client_stream.close() if self.use_leds: red_led.on()
def solve(self, polaris_ra_dec=(2.960856, 89.349278)): # polaris_ra_dec default to values at Jan 1 2020 # supply new values according to current date self.polaris_ra_dec = polaris_ra_dec self.x = None self.y = None self.lam_umi = None self.solu_time = 0 self.solu_time = int(round(pyb.millis() // 1000)) if len(self.star_list) < SCORE_REQUIRED: return False # impossible to have a solution if not enough stars # Polaris is the brightest object in the potential field of view, so it's faster to start with it brite_sorted = blobstar.sort_brightness(self.star_list) # limit the search for the first few possibilities if len(brite_sorted) > self.search_limit: brite_sorted = brite_sorted[0:self.search_limit] # iterate through all posibilities, brightest first for i in brite_sorted: # we are guessing "i" is Polaris for this iteration i.score_list = [] i.score = 0 i.penalty = 0 i.rotation = 0 i.rot_angi_sum = 0 i.rot_angj_sum = 0 i.rot_dist_sum = 0 i.pix_calibration = [] i.lam_umi = None ang_tol = 4 for j in self.star_list: j.set_ref_star( i ) # this is required for all entries in the list, so that sort_dist can work # set_ref_star also computes the vector to the ref star and caches the result dist_sorted = blobstar.sort_dist( self.star_list) # sorted closest-to-Polaris first if self.debug: print("center star (%.1f , %.1f)" % (i.cx, i.cy)) dbgi = 0 for dbg in dist_sorted: print("[%u]: (%.1f , %.1f) -> (%.1f , %.1f)" % (dbgi, dbg.cx, dbg.cy, dbg.ref_star_dist, dbg.ref_star_angle)) dbgi += 1 rot_ang = None # without a known reference angle, use the first angle we encounter to establish a reference angle # rot_ang is set after the match is made # these are used for the penalizing later max_dist = 0 min_brite = -1 idx_tbl = 0 idx_blobs_start = 1 # start at [1] because [0] is supposed to be Polaris len_tbl = len(STARS_NEAR_POLARIS) len_blobs = len(dist_sorted) while idx_tbl < len_tbl: # skip stars that might have too similar of a vector distance if the reference angle is not established yet # it is unlikely that this logic is actually useful in real life if rot_ang is None and idx_tbl >= 1: if abs(STARS_NEAR_POLARIS[idx_tbl][1] - STARS_NEAR_POLARIS[idx_tbl - 1][1]) <= 2: idx_tbl += 1 continue idx_blobs = idx_blobs_start # previous blobs (closer-to-Polaris) will be ignored while idx_blobs < len_blobs: k = dist_sorted[idx_blobs] match = False if dist_match(k.ref_star_dist, STARS_NEAR_POLARIS[idx_tbl][1]): if self.debug: print("dist matched [%s , %u] %.1f %.1f %.1f" % (STARS_NEAR_POLARIS[idx_tbl][0], idx_blobs, STARS_NEAR_POLARIS[idx_tbl][1], k.ref_star_dist, abs(k.ref_star_dist - STARS_NEAR_POLARIS[idx_tbl][1]))) if rot_ang is None: # without a known reference angle, use the first angle we encounter to establish a reference angle # rot_ang is set after the match is made match = True if self.debug: print( "first angle match [%s , %u] %.1f %.1f %.1f" % (STARS_NEAR_POLARIS[idx_tbl][0], idx_blobs, STARS_NEAR_POLARIS[idx_tbl][2], k.ref_star_angle, angle_diff( k.ref_star_angle, STARS_NEAR_POLARIS[idx_tbl][2]))) else: adj_ang = ang_normalize( STARS_NEAR_POLARIS[idx_tbl][2] + rot_ang) if angle_match(k.ref_star_angle, adj_ang, tol=ang_tol): match = True if ang_tol > 1: ang_tol -= 1 if self.debug: print("angle matched ", end="") else: if self.debug: print("angle match failed ", end="") if self.debug: print( "[%s , %u] %.1f %.1f %.1f %.1f %.1f" % (STARS_NEAR_POLARIS[idx_tbl][0], idx_blobs, STARS_NEAR_POLARIS[idx_tbl][2], k.ref_star_angle, angle_diff(k.ref_star_angle, adj_ang), rot_ang, adj_ang)) if match: # each match is a further star, which means more precise angle # compute (and update) the weighted average of the angle offset rot_ang = angle_diff(k.ref_star_angle, STARS_NEAR_POLARIS[idx_tbl][2]) unitvector = [ math.cos(math.radians(rot_ang)), math.sin(math.radians(rot_ang)) ] i.rot_angi_sum += unitvector[0] * k.ref_star_dist i.rot_angj_sum += unitvector[1] * k.ref_star_dist i.rot_dist_sum += k.ref_star_dist rot_ang = math.degrees( math.atan2(i.rot_angj_sum / i.rot_dist_sum, i.rot_angi_sum / i.rot_dist_sum)) i.rotation = rot_ang if STARS_NEAR_POLARIS[idx_tbl][0] == "* lam UMi": i.lam_umi = k if k.ref_star_dist > max_dist: max_dist = k.ref_star_dist # establishes maximum matching area if k.brightness < min_brite or min_brite < 0: min_brite = k.brightness # establishes minimum matching brightness # measured vs supposed distances may be different, track the differences # this will account for distortion and focus-breathing i.pix_calibration.append( k.ref_star_dist / STARS_NEAR_POLARIS[idx_tbl][1]) # all previous (closer-to-Polaris) entries to be ignored on the next loop idx_blobs_start = idx_blobs # doing this will prevent potential out-of-order matches #i.score_list.append(STARS_NEAR_POLARIS[idx_tbl][0]) # save the name to the list of matches (score) i.score_list.append(k) if self.debug: print("score %u , new rotation %.1f" % (len(i.score_list), rot_ang)) idx_blobs += 1 idx_tbl += 1 # penalty function is optional if ENABLE_PENALTY: # go through all blobs again to see if we should penalize for mystery stars # if a star is brighter than some of the stars we've been able to match against # then it's a mystery star, and makes the solution less confident idx_blobs = 1 while idx_blobs < len_blobs: k = dist_sorted[idx_blobs] if k.ref_star_dist < max_dist and k.brightness > min_brite: # within the area and also brighter than expected # does it match an entry in the table? (some of the table entries were ignored previously, so we have to do the whole check again) in_database = False idx_tbl = 0 len_tbl = len(STARS_NEAR_POLARIS) while idx_tbl < len_tbl: if dist_match( k.ref_star_dist, STARS_NEAR_POLARIS[idx_tbl] [1]) and angle_match( k.ref_star_angle, ang_normalize( STARS_NEAR_POLARIS[idx_tbl][2] + rot_ang)): in_database = True break idx_tbl += 1 if in_database == False: is_hot = False # check if it's a hot pixel for hp in self.hot_pixels: d = math.sqrt(((k.cx - hp[0])**2) + ((k.cy - hp[1])**2)) if d < 2.0: is_hot = True break if is_hot == False: i.penalty += 1 if self.debug: print("penalty (%.1f , %.1f)" % (k.cx, k.cy)) idx_blobs += 1 # calculate score accounting for penalty i.score = len(i.score_list) - i.penalty # end of the for loop that goes from brightest to dimmest # each entry of that list will now have a "score" (number of matches) # find the one that has the most matches score_sorted = sorted(brite_sorted, key=sort_score_func, reverse=True) self.star_list = None # garbage collect if score_sorted[0].score < SCORE_REQUIRED: return False # not enough matches, no solution self.solved = True # store the solution states self.Polaris = score_sorted[0] self.rotation = score_sorted[0].rotation self.rotation = ang_normalize(self.rotation + 180.0) # everything needs to be flipped self.stars_matched = score_sorted[0].score_list # for debug purposes self.penalty = score_sorted[0].penalty self.lam_umi = score_sorted[0].lam_umi dist_calibration = 0 for i in score_sorted[0].pix_calibration: dist_calibration += i dist_calibration /= len(score_sorted[0].pix_calibration) self.pix_per_deg = PIXELS_PER_DEGREE * dist_calibration return True