def precompute(self, b, theta, bo, ro): # Ingest self.ingest(b, theta, bo, ro) # Illumination matrix self.IA1 = self.illum().dot(self.A1) # Get integration code & limits self.kappa, self.lam, self.xi, self.code = get_angles( self.b, self.theta, self.costheta, self.sintheta, self.bo, self.ro, ) # Compute the three primitive integrals if necessary if self.code not in [ FLUX_ZERO, FLUX_SIMPLE_OCC, FLUX_SIMPLE_REFL, FLUX_SIMPLE_OCC_REFL, ]: self.P = compute_P(self.ydeg + 1, self.bo, self.ro, self.kappa) self.Q = compute_Q(self.ydeg + 1, self.lam) self.T = compute_T(self.ydeg + 1, self.b, self.theta, self.xi) else: self.P = None self.Q = None self.T = None
def rotate_camera(self, dx, dy): dx = 2 * self.r * -dx dy = 2 * self.r * -dy self._direction, self._top = geometry.rotate( self.direction, self.top, dx, dy) self.direction = self.direction self.cache = None return map(lambda x: (x * 180 / math.pi) % 360, geometry.get_angles(self.direction))
def precompute(self, b, theta, bo, ro): # Ingest self.ingest(b, theta, bo, ro) # Get integration code & limits self.kappa, self.lam, self.xi, self.code = get_angles( self.b, self.theta, self.costheta, self.sintheta, self.bo, self.ro) self.phi = self.kappa - np.pi / 2 # Illumination matrix self.IA1 = self.illum().dot(self.A1) # Compute the three primitive integrals self.P = np.zeros((self.ydeg + 2)**2) self.Q = np.zeros((self.ydeg + 2)**2) self.T = np.zeros((self.ydeg + 2)**2) n = 0 for l in range(self.ydeg + 2): for m in range(-l, l + 1): self.P[n] = self.Plm(l, m) self.Q[n] = self.Qlm(l, m) self.T[n] = self.Tlm(l, m) n += 1
def app(input_file, output_file): gauge = process_image.Gauge() host = housekeeping.OS() # setup image reuse_circle = housekeeping.test_time() im = host.get_image(DEF.SAMPLE_PATH, input_file) gray_im = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY) prep = gray_im # copy of grayscale image to be preprocessed for angle detection mask = np.zeros_like(prep) canvas = np.zeros_like(im) # output image np.copyto(canvas, im) error_screen = np.zeros_like(im) height, width = gray_im.shape assert height == DEF.HEIGHT assert width == DEF.WIDTH # mask_bandpass is a mask of the gauge obtained from histogram peaks hist0, mask_bandpass = hist_an.bandpass(gray_im) # prep is binary image ready for line detection prep = process_image.blur_n_threshold(prep, do_blur=1) prep = process_image.erode_n_dilate(prep) # host.write_image(("mask_bandpass.jpg", mask_bandpass)) # Hough transforms if reuse_circle: print "reusing previous circle" circle_x, circle_y, radius = housekeeping.return_circle() circle_x = int(circle_x) circle_y = int(circle_y) else: circles = hough_transforms.hough_c(gray_im) for c in circles[:1]: # scale factor makes applicable area slightly larger just to be safe we are not missing anything cv2.circle(mask, (c[0], c[1]), int(c[2] * DEF.CIRCLE_SCALE_FACTOR), (255, 255, 255), -1) prep = cv2.bitwise_and(prep, mask) cv2.circle(canvas, (c[0], c[1]), c[2], (0, 255, 0), DEF.THICKNESS) circle_x = c[0] circle_y = c[1] radius = int(c[2]) cv2.circle(mask, (int(circle_x), int(circle_y)), int(radius * DEF.CIRCLE_SCALE_FACTOR), (255, 255, 255), -1) cv2.circle(canvas, (int(circle_x), int(circle_y)), radius, (0, 255, 0), DEF.THICKNESS) cv2.circle(canvas, (int(circle_x), int(circle_y - 12)), 25, (0, 255, 0), DEF.THICKNESS) prep = cv2.bitwise_and(prep, mask) prep = cv2.bitwise_and(mask_bandpass, prep) thresholded = np.copy(prep) prep = process_image.find_contour(prep, draw=True) lines = hough_transforms.hough_l(prep) for line in lines[:10]: for (rho, theta) in line: # blue for infinite lines (only draw the 2 strongest) x0 = np.cos(theta) * rho y0 = np.sin(theta) * rho pt1 = (int(x0 + (height + width) * (-np.sin(theta))), int(y0 + (height + width) * np.cos(theta))) pt2 = (int(x0 - (height + width) * (-np.sin(theta))), int(y0 - (height + width) * np.cos(theta))) gauge.lines.append((pt1, pt2)) gauge.angles_in_radians.append(theta) gauge.angles_in_degrees.append(geometry.rad_to_degrees(theta)) # cv2.line(canvas, pt1, pt2, (255, 255, 0), DEF.THICKNESS / 3) # check standard deviation of angles to catch angle wrap-around (359 to 0) angle_std = np.std(gauge.angles_in_degrees) if angle_std > 50: for i in range(len(gauge.angles_in_degrees)): if gauge.angles_in_degrees[i] < 20: gauge.angles_in_degrees[i] += 180 gauge.angles_in_radians[i] += np.pi angle_index = geometry.get_angles(gauge.angles_in_degrees) if len(gauge.lines) < 2: EX.error_output(error_screen, err_msg="unable to find needle from lines") sys.exit("unable to find needle form lines") line_found = False for this_pair in angle_index: # print "angle1: ", gauge.angles_in_degrees[this_pair[0]] # print "angle2: ", gauge.angles_in_degrees[this_pair[1]] print this_pair line1 = geometry.make_line(gauge.lines[this_pair[0]]) line2 = geometry.make_line(gauge.lines[this_pair[1]]) intersecting_pt = geometry.intersection(line1, line2) if intersecting_pt is not None: print "Intersection detected:", intersecting_pt cv2.circle(canvas, intersecting_pt, 10, DEF.RED, 3) else: print "No single intersection point detected" # Although we found a line coincident with the gauge needle, # we need to find which of the two direction it is pointing # guess1 and guess2 are 180 degrees apart avg_theta = (gauge.angles_in_radians[this_pair[0]] + gauge.angles_in_radians[this_pair[1]]) / 2 guess1 = [avg_theta, (0, 0)] guess2 = [avg_theta + np.pi, (0, 0)] if guess2[0] > 2 * np.pi: # in case adding pi made it greater than 2pi guess2[0] -= 2 * np.pi print "guess1: ", guess1[0] print "guess2: ", guess2[0] guess1[1] = (int(circle_x + radius * np.sin(guess1[0])), int(circle_y - radius * np.cos(guess1[0]))) guess2[1] = (int(circle_x + radius * np.sin(guess2[0])), int(circle_y - radius * np.cos(guess2[0]))) # find the distance between our guess and intersection of gauge needle lines # the guess that is closer to the intersection is the correct one dist1 = math.hypot(intersecting_pt[0] - guess1[1][0], intersecting_pt[1] - guess1[1][1]) dist2 = math.hypot(intersecting_pt[0] - guess2[1][0], intersecting_pt[1] - guess2[1][1]) print "dist1: ", dist1 print "dist2: ", dist2 if dist1 < DEF.MAX_DISTANCE or dist2 < DEF.MAX_DISTANCE: line_found = True if dist1 < dist2: correct_guess = guess1 else: correct_guess = guess2 if line_found is True: cv2.circle(canvas, guess1[1], 10, DEF.GREEN, 3) cv2.circle(canvas, guess2[1], 10, DEF.BLUE, 3) # cv2.line(canvas, gauge.lines[this_pair[0]][0], gauge.lines[this_pair[0]][1], (255, 0, 0), # DEF.THICKNESS) # cv2.line(canvas, gauge.lines[this_pair[1]][0], gauge.lines[this_pair[1]][1], (255, 0, 0), # DEF.THICKNESS) break if line_found is False: EX.error_output(error_screen, err_msg="no positive pair") needle_angle = 0.0 else: ref_offset = np.pi needle_angle = geometry.rad_to_degrees(correct_guess[0] - ref_offset) if needle_angle < 0.0: needle_angle += 360 print "original_guess: ", needle_angle angle_adjustment_set = np.arange(needle_angle - 5.0, needle_angle + 5.0, 0.1) sum_pixels = [] for adj_angle in angle_adjustment_set: gauge.update_needle(adj_angle) gauge.get_needle((circle_x, circle_y - 10)) overlap = cv2.bitwise_and(thresholded, gauge.needle_canvas) sum_pixels.append(np.sum(overlap / 255)) adjustment_index = sum_pixels.index(max(sum_pixels)) needle_angle = angle_adjustment_set[adjustment_index] gauge.update_needle(needle_angle) gauge.get_needle((circle_x, circle_y - 10)) needle_rgb = cv2.cvtColor(gauge.needle_canvas, cv2.COLOR_GRAY2BGR) canvas = cv2.add(canvas, needle_rgb / 2) print "updated_guess: ", needle_angle pressure = np.interp( needle_angle, [DEF.GAUGE_MIN['angle'], DEF.GAUGE_MAX['angle']], [DEF.GAUGE_MIN['pressure'], DEF.GAUGE_MAX['pressure']]) pressure_str = str(round(pressure, 2)) print "pressure = ", pressure display_values(canvas, pressure_str) final_line_pt1 = ( int(circle_x + radius * np.sin(geometry.degrees_to_radians(needle_angle))), int(circle_y - radius * np.cos(geometry.degrees_to_radians(needle_angle)))) final_line_pt2 = ( int(circle_x + radius * np.sin(geometry.degrees_to_radians(needle_angle + 180))), int(circle_y - radius * np.cos(geometry.degrees_to_radians(needle_angle + 180)))) cv2.line(canvas, final_line_pt1, final_line_pt2, DEF.RED, thickness=DEF.THICKNESS * 2) prep = cv2.bitwise_and(prep, mask) # plt.subplot(231) # plt.imshow(cv2.cvtColor(im, cv2.COLOR_BGR2RGB)) # plt.subplot(232) # plt.imshow(255 - mask_bandpass, 'gray') # plt.subplot(233) # plt.imshow(prep, 'gray') # plt.subplot(234) # plt.imshow(cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB)) # plt.subplot(235) # plt.plot(hist0) # plt.xlim([0, 256]) # plt.ylim([0, 50000]) # plt.show() cv2.circle(prep, (circle_x, circle_y), 25, (0, 255, 0), DEF.THICKNESS) prep = cv2.cvtColor(prep, cv2.COLOR_GRAY2BGR) canvas = np.concatenate((im, canvas), axis=1) host.write_to_file('w', "Pressure: " + pressure_str + " MPa", "Temperature: " + str(host.read_temp())) host.write_to_file('a', "circle_x:" + str(circle_x), "circle_y:" + str(circle_y), "radius:" + str(radius)) # pass in tuples ("filename.ext", img_to_write) image_path = re.sub('file', str(output_file), DEF.OUTPUT_PATH) host.write_image((image_path, canvas)) image_path = re.sub('file', str(output_file), DEF.THRESH_PATH) host.write_image((image_path, thresholded))
def app(input_file, output_file): gauge = process_image.Gauge() host = housekeeping.OS() # setup image reuse_circle = housekeeping.test_time() im = host.get_image(DEF.SAMPLE_PATH, input_file) gray_im = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY) prep = gray_im # copy of grayscale image to be preprocessed for angle detection mask = np.zeros_like(prep) canvas = np.zeros_like(im) # output image np.copyto(canvas, im) error_screen = np.zeros_like(im) height, width = gray_im.shape assert height == DEF.HEIGHT assert width == DEF.WIDTH # mask_bandpass is a mask of the gauge obtained from histogram peaks hist0, mask_bandpass = hist_an.bandpass(gray_im) # prep is binary image ready for line detection prep = process_image.blur_n_threshold(prep, do_blur=1) prep = process_image.erode_n_dilate(prep) # host.write_image(("mask_bandpass.jpg", mask_bandpass)) # Hough transforms if reuse_circle: print "reusing previous circle" circle_x, circle_y, radius = housekeeping.return_circle() circle_x = int(circle_x) circle_y = int(circle_y) else: circles = hough_transforms.hough_c(gray_im) for c in circles[:1]: # scale factor makes applicable area slightly larger just to be safe we are not missing anything cv2.circle(mask, (c[0], c[1]), int(c[2] * DEF.CIRCLE_SCALE_FACTOR), (255, 255, 255), -1) prep = cv2.bitwise_and(prep, mask) cv2.circle(canvas, (c[0], c[1]), c[2], (0, 255, 0), DEF.THICKNESS) circle_x = c[0] circle_y = c[1] radius = int(c[2]) cv2.circle(mask, (int(circle_x), int(circle_y)), int(radius * DEF.CIRCLE_SCALE_FACTOR), (255, 255, 255), -1) cv2.circle(canvas, (int(circle_x), int(circle_y)), radius, (0, 255, 0), DEF.THICKNESS) cv2.circle(canvas, (int(circle_x), int(circle_y-12)), 25, (0, 255, 0), DEF.THICKNESS) prep = cv2.bitwise_and(prep, mask) prep = cv2.bitwise_and(mask_bandpass, prep) thresholded = np.copy(prep) prep = process_image.find_contour(prep, draw=True) lines = hough_transforms.hough_l(prep) for line in lines[:10]: for (rho, theta) in line: # blue for infinite lines (only draw the 2 strongest) x0 = np.cos(theta) * rho y0 = np.sin(theta) * rho pt1 = (int(x0 + (height + width) * (-np.sin(theta))), int(y0 + (height + width) * np.cos(theta))) pt2 = (int(x0 - (height + width) * (-np.sin(theta))), int(y0 - (height + width) * np.cos(theta))) gauge.lines.append((pt1, pt2)) gauge.angles_in_radians.append(theta) gauge.angles_in_degrees.append(geometry.rad_to_degrees(theta)) # cv2.line(canvas, pt1, pt2, (255, 255, 0), DEF.THICKNESS / 3) # check standard deviation of angles to catch angle wrap-around (359 to 0) angle_std = np.std(gauge.angles_in_degrees) if angle_std > 50: for i in range(len(gauge.angles_in_degrees)): if gauge.angles_in_degrees[i] < 20: gauge.angles_in_degrees[i] += 180 gauge.angles_in_radians[i] += np.pi angle_index = geometry.get_angles(gauge.angles_in_degrees) if len(gauge.lines) < 2: EX.error_output(error_screen, err_msg="unable to find needle from lines") sys.exit("unable to find needle form lines") line_found = False for this_pair in angle_index: # print "angle1: ", gauge.angles_in_degrees[this_pair[0]] # print "angle2: ", gauge.angles_in_degrees[this_pair[1]] print this_pair line1 = geometry.make_line(gauge.lines[this_pair[0]]) line2 = geometry.make_line(gauge.lines[this_pair[1]]) intersecting_pt = geometry.intersection(line1, line2) if intersecting_pt is not None: print "Intersection detected:", intersecting_pt cv2.circle(canvas, intersecting_pt, 10, DEF.RED, 3) else: print "No single intersection point detected" # Although we found a line coincident with the gauge needle, # we need to find which of the two direction it is pointing # guess1 and guess2 are 180 degrees apart avg_theta = (gauge.angles_in_radians[this_pair[0]] + gauge.angles_in_radians[this_pair[1]]) / 2 guess1 = [avg_theta, (0, 0)] guess2 = [avg_theta + np.pi, (0, 0)] if guess2[0] > 2 * np.pi: # in case adding pi made it greater than 2pi guess2[0] -= 2 * np.pi print "guess1: ", guess1[0] print "guess2: ", guess2[0] guess1[1] = (int(circle_x + radius * np.sin(guess1[0])), int(circle_y - radius * np.cos(guess1[0]))) guess2[1] = (int(circle_x + radius * np.sin(guess2[0])), int(circle_y - radius * np.cos(guess2[0]))) # find the distance between our guess and intersection of gauge needle lines # the guess that is closer to the intersection is the correct one dist1 = math.hypot(intersecting_pt[0] - guess1[1][0], intersecting_pt[1] - guess1[1][1]) dist2 = math.hypot(intersecting_pt[0] - guess2[1][0], intersecting_pt[1] - guess2[1][1]) print "dist1: ", dist1 print "dist2: ", dist2 if dist1 < DEF.MAX_DISTANCE or dist2 < DEF.MAX_DISTANCE: line_found = True if dist1 < dist2: correct_guess = guess1 else: correct_guess = guess2 if line_found is True: cv2.circle(canvas, guess1[1], 10, DEF.GREEN, 3) cv2.circle(canvas, guess2[1], 10, DEF.BLUE, 3) # cv2.line(canvas, gauge.lines[this_pair[0]][0], gauge.lines[this_pair[0]][1], (255, 0, 0), # DEF.THICKNESS) # cv2.line(canvas, gauge.lines[this_pair[1]][0], gauge.lines[this_pair[1]][1], (255, 0, 0), # DEF.THICKNESS) break if line_found is False: EX.error_output(error_screen, err_msg="no positive pair") needle_angle = 0.0 else: ref_offset = np.pi needle_angle = geometry.rad_to_degrees(correct_guess[0] - ref_offset) if needle_angle < 0.0: needle_angle += 360 print "original_guess: ", needle_angle angle_adjustment_set = np.arange(needle_angle-5.0,needle_angle+5.0,0.1) sum_pixels = [] for adj_angle in angle_adjustment_set: gauge.update_needle(adj_angle) gauge.get_needle((circle_x, circle_y-10)) overlap = cv2.bitwise_and(thresholded, gauge.needle_canvas) sum_pixels.append(np.sum(overlap/255)) adjustment_index = sum_pixels.index(max(sum_pixels)) needle_angle = angle_adjustment_set[adjustment_index] gauge.update_needle(needle_angle) gauge.get_needle((circle_x, circle_y-10)) needle_rgb = cv2.cvtColor(gauge.needle_canvas, cv2.COLOR_GRAY2BGR) canvas = cv2.add(canvas, needle_rgb/2) print "updated_guess: ", needle_angle pressure = np.interp(needle_angle, [DEF.GAUGE_MIN['angle'], DEF.GAUGE_MAX['angle']], [DEF.GAUGE_MIN['pressure'], DEF.GAUGE_MAX['pressure']]) pressure_str = str(round(pressure, 2)) print "pressure = ", pressure display_values(canvas, pressure_str) final_line_pt1 = (int(circle_x + radius * np.sin(geometry.degrees_to_radians(needle_angle))), int(circle_y - radius * np.cos(geometry.degrees_to_radians(needle_angle)))) final_line_pt2 = (int(circle_x + radius * np.sin(geometry.degrees_to_radians(needle_angle + 180))), int(circle_y - radius * np.cos(geometry.degrees_to_radians(needle_angle + 180)))) cv2.line(canvas, final_line_pt1, final_line_pt2, DEF.RED, thickness=DEF.THICKNESS*2) prep = cv2.bitwise_and(prep, mask) # plt.subplot(231) # plt.imshow(cv2.cvtColor(im, cv2.COLOR_BGR2RGB)) # plt.subplot(232) # plt.imshow(255 - mask_bandpass, 'gray') # plt.subplot(233) # plt.imshow(prep, 'gray') # plt.subplot(234) # plt.imshow(cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB)) # plt.subplot(235) # plt.plot(hist0) # plt.xlim([0, 256]) # plt.ylim([0, 50000]) # plt.show() cv2.circle(prep, (circle_x, circle_y), 25, (0, 255, 0), DEF.THICKNESS) prep = cv2.cvtColor(prep, cv2.COLOR_GRAY2BGR) canvas = np.concatenate((im, canvas), axis=1) host.write_to_file('w', "Pressure: " + pressure_str + " MPa", "Temperature: " + str(host.read_temp())) host.write_to_file('a', "circle_x:" + str(circle_x), "circle_y:" + str(circle_y), "radius:" + str(radius)) # pass in tuples ("filename.ext", img_to_write) image_path = re.sub('file', str(output_file), DEF.OUTPUT_PATH) host.write_image((image_path, canvas)) image_path = re.sub('file', str(output_file), DEF.THRESH_PATH) host.write_image((image_path, thresholded))
def visualize(b, theta, bo, ro, res=4999): # Find angles of intersection kappa, lam, xi, code = get_angles(b, theta, np.cos(theta), np.sin(theta), bo, ro) phi = kappa - np.pi / 2 print(code) # Equation of half-ellipse x = np.linspace(-1, 1, 1000) y = b * np.sqrt(1 - x**2) x_t = x * np.cos(theta) - y * np.sin(theta) y_t = x * np.sin(theta) + y * np.cos(theta) # Shaded regions p = np.linspace(-1, 1, res) xpt, ypt = np.meshgrid(p, p) cond1 = xpt**2 + (ypt - bo)**2 < ro**2 # inside occultor cond2 = xpt**2 + ypt**2 < 1 # inside occulted xr = xpt * np.cos(theta) + ypt * np.sin(theta) yr = -xpt * np.sin(theta) + ypt * np.cos(theta) cond3 = yr > b * np.sqrt(1 - xr**2) # above terminator img_day_occ = np.zeros_like(xpt) img_day_occ[cond1 & cond2 & cond3] = 1 img_night_occ = np.zeros_like(xpt) img_night_occ[cond1 & cond2 & ~cond3] = 1 img_night = np.zeros_like(xpt) img_night[~cond1 & cond2 & ~cond3] = 1 # Plot if len(lam): fig, ax = plt.subplots(1, 3, figsize=(14, 5)) fig.subplots_adjust(left=0.025, right=0.975, bottom=0.05, top=0.825) ax[0].set_title("T", color="r") ax[1].set_title("P", color="r") ax[2].set_title("Q", color="r") else: fig, ax = plt.subplots(1, 2, figsize=(9, 5)) fig.subplots_adjust(left=0.025, right=0.975, bottom=0.05, top=0.825) ax[0].set_title("T", color="r") ax[1].set_title("P", color="r") # Labels for i in range(len(phi)): ax[0].annotate( r"$\xi_{} = {:.1f}^\circ$".format(i + 1, xi[i] * 180 / np.pi), xy=(0, 0), xycoords="axes fraction", xytext=(5, 25 - i * 20), textcoords="offset points", fontsize=10, color="C0", ) ax[1].annotate( r"$\phi_{} = {:.1f}^\circ$".format(i + 1, phi[i] * 180 / np.pi), xy=(0, 0), xycoords="axes fraction", xytext=(5, 25 - i * 20), textcoords="offset points", fontsize=10, color="C0", ) for i in range(len(lam)): ax[2].annotate( r"$\lambda_{} = {:.1f}^\circ$".format(i + 1, lam[i] * 180 / np.pi), xy=(0, 0), xycoords="axes fraction", xytext=(5, 25 - i * 20), textcoords="offset points", fontsize=10, color="C0", ) # Draw basic shapes for axis in ax: axis.axis("off") axis.add_artist(plt.Circle((0, bo), ro, fill=False)) axis.add_artist(plt.Circle((0, 0), 1, fill=False)) axis.plot(x_t, y_t, "k-", lw=1) axis.set_xlim(-1.25, 1.25) axis.set_ylim(-1.25, 1.25) axis.set_aspect(1) axis.imshow( img_day_occ, origin="lower", extent=(-1, 1, -1, 1), alpha=0.25, cmap=LinearSegmentedColormap.from_list("cmap1", [(0, 0, 0, 0), "k"], 2), ) axis.imshow( img_night_occ, origin="lower", extent=(-1, 1, -1, 1), alpha=0.5, cmap=LinearSegmentedColormap.from_list("cmap1", [(0, 0, 0, 0), "k"], 2), ) axis.imshow( img_night, origin="lower", extent=(-1, 1, -1, 1), alpha=0.75, cmap=LinearSegmentedColormap.from_list("cmap1", [(0, 0, 0, 0), "k"], 2), ) # Draw integration paths if len(phi): for k in range(0, len(phi) // 2 + 1, 2): # T # This is the *actual* angle along the ellipse xi_p = np.arctan(np.abs(b) * np.tan(xi)) xi_p[xi_p < 0] += np.pi if np.abs(b) < 1e-4: ax[0].plot( [ np.cos(xi[k]) * np.cos(theta), np.cos(xi[k + 1]) * np.cos(theta), ], [ np.cos(xi[k]) * np.sin(theta), np.cos(xi[k + 1]) * np.sin(theta), ], color="r", lw=2, zorder=3, ) else: if xi_p[k] > xi_p[k + 1]: # TODO: CHECK ME xi_p[[k, k + 1]] = xi_p[[k + 1, k]] arc = Arc( (0, 0), 2, 2 * np.abs(b), theta * 180 / np.pi, np.sign(b) * xi_p[k + 1] * 180 / np.pi, np.sign(b) * xi_p[k] * 180 / np.pi, color="r", lw=2, zorder=3, ) ax[0].add_patch(arc) # P arc = Arc( (0, bo), 2 * ro, 2 * ro, 0, phi[k] * 180 / np.pi, phi[k + 1] * 180 / np.pi, color="r", lw=2, zorder=3, ) ax[1].add_patch(arc) if len(lam): # Q arc = Arc( (0, 0), 2, 2, 0, lam[0] * 180 / np.pi, lam[1] * 180 / np.pi, color="r", lw=2, zorder=3, ) ax[2].add_patch(arc) # Draw axes ax[0].plot( [-np.cos(theta), np.cos(theta)], [-np.sin(theta), np.sin(theta)], color="k", ls="--", lw=0.5, ) ax[0].plot([0], [0], "C0o", ms=4, zorder=4) ax[1].plot( [-ro, ro], [bo, bo], color="k", ls="--", lw=0.5, ) ax[1].plot([0], [bo], "C0o", ms=4, zorder=4) if len(lam): ax[2].plot( [-1, 1], [0, 0], color="k", ls="--", lw=0.5, ) ax[2].plot(0, 0, "C0o", ms=4, zorder=4) # Draw points of intersection & angles sz = [0.25, 0.5, 0.75, 1.0] for i, xi_i in enumerate(xi): # -- T -- # xi angle ax[0].plot( [0, np.cos(np.sign(b) * xi_i + theta)], [0, np.sin(np.sign(b) * xi_i + theta)], color="C0", lw=1, ) # tangent line x0 = np.cos(xi_i) * np.cos(theta) y0 = np.cos(xi_i) * np.sin(theta) ax[0].plot( [x0, np.cos(np.sign(b) * xi_i + theta)], [y0, np.sin(np.sign(b) * xi_i + theta)], color="k", ls="--", lw=0.5, ) # mark the polar angle ax[0].plot( [np.cos(np.sign(b) * xi_i + theta)], [np.sin(np.sign(b) * xi_i + theta)], "C0o", ms=4, zorder=4, ) # draw and label the angle arc if np.sin(xi_i) != 0: angle = sorted([theta, np.sign(b) * xi_i + theta]) arc = Arc( (0, 0), sz[i], sz[i], 0, angle[0] * 180 / np.pi, angle[1] * 180 / np.pi, color="C0", lw=0.5, ) ax[0].add_patch(arc) ax[0].annotate( r"$\xi_{}$".format(i + 1), xy=( 0.5 * sz[i] * np.cos(0.5 * np.sign(b) * xi_i + theta), 0.5 * sz[i] * np.sin(0.5 * np.sign(b) * xi_i + theta), ), xycoords="data", xytext=( 7 * np.cos(0.5 * np.sign(b) * xi_i + theta), 7 * np.sin(0.5 * np.sign(b) * xi_i + theta), ), textcoords="offset points", ha="center", va="center", fontsize=8, color="C0", ) # points of intersection? tol = 1e-7 for phi_i in phi: x_phi = ro * np.cos(phi_i) y_phi = bo + ro * np.sin(phi_i) x_xi = np.cos(theta) * np.cos(xi_i) - b * np.sin(theta) * np.sin( xi_i) y_xi = np.sin(theta) * np.cos(xi_i) + b * np.cos(theta) * np.sin( xi_i) if np.abs(y_phi - y_xi) < tol and np.abs(x_phi - x_xi) < tol: ax[0].plot( [ro * np.cos(phi_i)], [bo + ro * np.sin(phi_i)], "C0o", ms=4, zorder=4, ) for i, phi_i in enumerate(phi): # -- P -- # points of intersection ax[1].plot( [0, ro * np.cos(phi_i)], [bo, bo + ro * np.sin(phi_i)], color="C0", ls="-", lw=1, ) ax[1].plot( [ro * np.cos(phi_i)], [bo + ro * np.sin(phi_i)], "C0o", ms=4, zorder=4, ) # draw and label the angle arc angle = sorted([0, phi_i]) arc = Arc( (0, bo), sz[i], sz[i], 0, angle[0] * 180 / np.pi, angle[1] * 180 / np.pi, color="C0", lw=0.5, ) ax[1].add_patch(arc) ax[1].annotate( r"${}\phi_{}$".format("-" if phi_i < 0 else "", i + 1), xy=( 0.5 * sz[i] * np.cos(0.5 * (phi_i % (2 * np.pi))), bo + 0.5 * sz[i] * np.sin(0.5 * (phi_i % (2 * np.pi))), ), xycoords="data", xytext=( 7 * np.cos(0.5 * (phi_i % (2 * np.pi))), 7 * np.sin(0.5 * (phi_i % (2 * np.pi))), ), textcoords="offset points", ha="center", va="center", fontsize=8, color="C0", zorder=4, ) for i, lam_i in zip(range(len(lam)), lam): # -- Q -- # points of intersection ax[2].plot( [0, np.cos(lam_i)], [0, np.sin(lam_i)], color="C0", ls="-", lw=1, ) ax[2].plot( [np.cos(lam_i)], [np.sin(lam_i)], "C0o", ms=4, zorder=4, ) # draw and label the angle arc angle = sorted([0, lam_i]) arc = Arc( (0, 0), sz[i], sz[i], 0, angle[0] * 180 / np.pi, angle[1] * 180 / np.pi, color="C0", lw=0.5, ) ax[2].add_patch(arc) ax[2].annotate( r"${}\lambda_{}$".format("-" if lam_i < 0 else "", i + 1), xy=( 0.5 * sz[i] * np.cos(0.5 * lam_i), 0.5 * sz[i] * np.sin(0.5 * lam_i), ), xycoords="data", xytext=( 7 * np.cos(0.5 * lam_i), 7 * np.sin(0.5 * lam_i), ), textcoords="offset points", ha="center", va="center", fontsize=8, color="C0", zorder=4, ) return fig, ax