def test_b23(self): known = 3 * (self._t)**2 * (1 - self._t) i, p = 2, 3 calc = bp.bernstein_polynomial(i, p, self._nti) self.assertTrue(self.same(known, calc))
def test_b33(self): known = (self._t)**3 i, p = 3, 3 calc = bp.bernstein_polynomial(i, p, self._nti) self.assertTrue(self.same(known, calc))
def test_b12(self): known = 2 * self._t * (1 - self._t) i, p = 1, 2 calc = bp.bernstein_polynomial(i, p, self._nti) self.assertTrue(self.same(known, calc))
def test_b22(self): known = (self._t)**2 i, p = 2, 2 calc = bp.bernstein_polynomial(i, p, self._nti) self.assertTrue(self.same(known, calc))
def test_b01_and_verbose(self): known = 1 - self._t i, p = 0, 1 verbose = True calc = bp.bernstein_polynomial(i, p, self._nti, verbose=verbose) self.assertTrue(self.same(known, calc))
def test_b11(self): known = self._t i, p = 1, 1 calc = bp.bernstein_polynomial(i, p, self._nti) self.assertTrue(self.same(known, calc))
def test_b00_input_out_of_range_and_verbose(self): i, p = 0, 0 verbose = True calc = bp.bernstein_polynomial(i, p, self._nti, verbose=verbose) self.assertIsNone(calc)
def test_b44(self): known = (self._t)**4 i, p = 4, 4 calc = bp.bernstein_polynomial(i, p, self._nti) self.assertTrue(self.same(known, calc))
def test_b34(self): known = 4 * (self._t)**3 * (1 - self._t) i, p = 3, 4 calc = bp.bernstein_polynomial(i, p, self._nti) self.assertTrue(self.same(known, calc))
def __init__(self, config: str, verbose: bool = True): # abbreviations: # cp: control point; collection of control points forms the control net # cn: control net, composed of control points # n_cp: number of control points (int) per net # n_nets: number of control nets (int) if not Path(config).is_file(): sys.exit(f"Error: cannot find file {config}") STEM = Path(config).stem config_dir = Path(config).parent if verbose: class_name = type(self).__name__ print(f"This is {class_name}:") print(f" processing config file: {config}") print(f" located at: {config_dir}") with open(config) as fin: kwargs = json.load(fin) # config parameters without defaults, user specification required config_schema = ( "bezier-type", "data-path", "control-nets", "control-points", ) # check .json input schema for kw in config_schema: key = kwargs.get(kw, None) if not key: sys.exit( f'Error: keyword "{kw}" not found in config input file.') bezier_type = kwargs.get("bezier-type") bezier_types = ("curve", "surface", "solid") if bezier_type not in bezier_types: sys.exit(f'Error: bezier-type "{bezier_type}" not supported.') data_path = kwargs.get("data-path") if data_path == ".": data_path = config_dir # .csv files are in same folder as .json file data_path_expanded = Path(data_path).expanduser() cp_file = kwargs.get("control-points") cn_file = kwargs.get("control-nets") # number of time interval divisions nti_divisions # gives rise to the number of time intervals nti as # 2**nti_divisions = nti # 1 -> 2**1 = 2 (default) # 2 -> 2**2 = 4 # 3 -> 2**3 = 8 # 4 -> 2**4 = 64 # etc. nti_divisions = kwargs.get("nti_divisions", 1) if nti_divisions < 1: print("Error: number of time interval divisions (nti_divisions)") print("must be a positive integer, [1, 2, 3 ...].") sys.exit(f"Currint nti_divisions = {nti_divisions}") # control point data # do not set alpha, let scatter3D control this, which # automatically provides an implied depth of field # control_points_alpha = kwargs.get("control-alpha", 0.5) control_points_color = kwargs.get("control-points-color", "red") control_points_label = kwargs.get("control-points-label", False) control_points_label_color = kwargs.get("control-points-label-color", "black") # control_points_alpha = kwargs.get("control-points-alpha", 0.5) control_points_marker = kwargs.get("control-points-marker", "o") control_points_path = kwargs.get("control-points-path", False) control_points_shown = kwargs.get("control-points-shown", True) control_points_size = kwargs.get("control-points-size", 50) # draw a specific control net (or nets) control_nets_shown = kwargs.get("control-nets-shown", [0]) control_nets_linestyle = kwargs.get("control-nets-linestyle", "dashed") control_nets_linewidth = kwargs.get("control-nets-linewidth", 1.0) # Bezier interpolation data # the alpha channel (transparency) for the Bezier curve, # surface, or volume when rendered # do not set alpha, let scatter3D control this, which # automatically provides an implied depth of field # # bezier_points_alpha = kwargs.get("bezier-alpha", 0.9) bezier_points_color = kwargs.get("bezier-points-color", "blue") bezier_points_shown = kwargs.get("bezier-points-shown", True) bezier_points_size = kwargs.get("bezier-points-size", 10) # bezier_lines_shown = kwargs.get("bezier-lines-shown", False) bezier_lines_color = kwargs.get("bezier-lines-color", "black") bezier_linewidth = kwargs.get("bezier-linewidth", 1.0) surface_triangulation = kwargs.get("surface-triangulation", False) # surface surface_t0_uv_triangulation = kwargs.get("surface-t0-uv-triangulation", False) # volume surface_t1_uv_triangulation = kwargs.get("surface-t1-uv-triangulation", False) # volume surface_u0_vt_triangulation = kwargs.get("surface-u0-vt-triangulation", False) # volume surface_u1_vt_triangulation = kwargs.get("surface-u1-vt-triangulation", False) # volume surface_v0_tu_triangulation = kwargs.get("surface-v0-tu-triangulation", False) # volume surface_v1_tu_triangulation = kwargs.get("surface-v1-tu-triangulation", False) # volume triangulation_alpha = kwargs.get("triangulation-alpha", 1.0) # surface or volume xlabel = kwargs.get("xlabel", "x") ylabel = kwargs.get("ylabel", "y") zlabel = kwargs.get("zlabel", "z") xlim = kwargs.get("xlim", None) ylim = kwargs.get("ylim", None) zlim = kwargs.get("zlim", None) camera_elevation = kwargs.get("camera-elevation", None) camera_azimuth = kwargs.get("camera-azimuth", None) XTICKS = kwargs.get("xticks", None) YTICKS = kwargs.get("yticks", None) ZTICKS = kwargs.get("zticks", None) Z_AXIS_LABEL_INVERTED = kwargs.get("z-axis-label-inverted", True) INTERACTIVE = kwargs.get("interactive", False) # True shows plot, False does not SERIALZE = kwargs.get("serialize", False) LATEX = kwargs.get("latex", False) if LATEX: rc("font", **{ "family": "serif", "serif": ["Computer Modern Roman"] }) rc("text", usetex=True) # check existence of path and files # if not Path(data_path_expanded).is_dir(): if not data_path_expanded.is_dir(): sys.exit(f"Error: cannot find path: {data_path}") # cp_path_file = data_path_expanded + cp_file cp_path_file = data_path_expanded.joinpath(cp_file) # if not Path(cp_path_file).is_file(): if not cp_path_file.is_file(): sys.exit(f"Error: cannot find control points file: {cp_path_file}") # cn_path_file = data_path_expanded + cn_file cn_path_file = data_path_expanded.joinpath(cn_file) # if not Path(cn_path_file).is_file(): if not cn_path_file.is_file(): sys.exit(f"Error: cannot find control net file: {cn_path_file}") # file io data type specification data_type_string = "float" data_type_int = "i8" data_type_delimiter = "," n_headers = 0 # no headers in the csv files # avoid "magic" numbers, assign (0, 1, 2) to variables idx, idy, idz = (0, 1, 2) # column indices from .csv file with open(cp_path_file) as fin: data = np.genfromtxt( cp_path_file, dtype=data_type_string, delimiter=data_type_delimiter, skip_header=n_headers, ) cp_x = data[:, idx] cp_y = data[:, idy] cp_z = data[:, idz] with open(cn_path_file) as fin: nets = np.genfromtxt( cn_path_file, dtype=data_type_int, delimiter=data_type_delimiter, skip_header=n_headers, ) if len(nets.shape) == 1: # handle special case of single net # https://numpy.org/devdocs/user/absolute_beginners.html#how-to-convert-a-1d-array-into-a-2d-array-how-to-add-a-new-axis-to-an-array nets = np.expand_dims(nets, axis=0) # otherwise, we have two or more nets, dims are ok n_nets, n_cp = nets.shape # number (control nets, control points) if verbose: print(f"Number of control nets: {n_nets}") print(f"Number of control points per net: {n_cp}") fig = plt.figure(figsize=plt.figaspect(1.0), dpi=100) # fig = plt.figure(figsize=(6.5, 3.25), dpi=DPI) ax = fig.gca(projection="3d") # ax = plt.axes(projection="3d") # ax.plot3D(cp_x, cp_y, cp_z) # ax.scatter3D(cp_x, cp_y, cp_z, edgecolor="blue", # facecolor=(0, 0, 0, 0), s=10) # example for control_nets_shown # [0]: show the first control net # [0, 2]: show the first and third control nets for net in [nets[k] for k in control_nets_shown]: net_x = [cp_x[i] for i in net] net_y = [cp_y[i] for i in net] net_z = [cp_z[i] for i in net] if control_points_path: ax.plot3D( net_x, net_y, net_z, color=control_points_color, linestyle=control_nets_linestyle, linewidth=control_nets_linewidth, alpha=control_points_alpha, ) if control_points_label or control_points_shown: for i, cp_index in enumerate(net): if control_points_shown: ax.scatter3D( net_x[i], net_y[i], net_z[i], edgecolor=control_points_color, facecolor="white", alpha=control_points_alpha, marker=control_points_marker, s=control_points_size, ) if control_points_label: ax.text( net_x[i], net_y[i], net_z[i], str(cp_index), color=control_points_label_color, ) if verbose: print( f"control net node {i} is control point {cp_index}" ) if bezier_points_shown or bezier_lines_shown: x, y, z = (0.0, 0.0, 0.0) # assumes number of control points same along each axis # for either 2D (or 3D, to come); 2D uses square root # and 3D will use cube root # n_cp_per_axis = int(np.sqrt(len(net))) # 1D case: if bezier_type == "curve": n_cp_per_axis = len(net) elif bezier_type == "surface": n_cp_per_axis = int(np.sqrt(len(net))) elif bezier_type == "solid": n_cp_per_axis = int(np.cbrt(len(net))) p_degree = n_cp_per_axis - 1 # polynomial degree # nti = 2 # segment [0, 1] into nti intervals # nti = 2**4 # segment [0, 1] into nti intervals # segment [0, 1] into nti intervals nti = 2**nti_divisions # curve, surface, or solid t = np.linspace(0, 1, num=nti + 1, endpoint=True) # surface or solid u = np.linspace(0, 1, num=nti + 1, endpoint=True) # solid, won't use b/c solid triangulation will be 8 surface triangulations # v = np.linspace(0, 1, num=nti + 1, endpoint=True) if bezier_type == "curve": Point = net for i in np.arange(n_cp_per_axis): b_i = bp.bernstein_polynomial(i, p_degree, nti) if verbose: print(f"b{i} = {b_i}") x += b_i * cp_x[Point[i]] y += b_i * cp_y[Point[i]] z += b_i * cp_z[Point[i]] if bezier_type == "surface": Point = np.reshape(net, (n_cp_per_axis, n_cp_per_axis)) for i in np.arange(n_cp_per_axis): for j in np.arange(n_cp_per_axis): b_i = bp.bernstein_polynomial(i, p_degree, nti) b_j = bp.bernstein_polynomial(j, p_degree, nti) bij = np.outer(b_i, b_j) if verbose: print(f"bij = {bij}") x += bij * cp_x[Point[i][j]] y += bij * cp_y[Point[i][j]] z += bij * cp_z[Point[i][j]] # triangulate parameter space, # determine the triangles, cf # https://matplotlib.org/3.1.1/gallery/mplot3d/trisurf3d_2.html if surface_triangulation: # convention here is reverse of the (x, y) convention of # mesh grid, see # https://numpy.org/doc/stable/reference/generated/numpy.meshgrid.html u, t = np.meshgrid(u, t) u, t = u.flatten(), t.flatten() tri = mtri.Triangulation(u, t) ax.plot_trisurf( x.flatten(), y.flatten(), z.flatten(), triangles=tri.triangles, alpha=triangulation_alpha, ) if verbose: print("Triangulation is complete.") if bezier_type == "solid": Point = np.reshape( net, (n_cp_per_axis, n_cp_per_axis, n_cp_per_axis)) for i in np.arange(n_cp_per_axis): for j in np.arange(n_cp_per_axis): for k in np.arange(n_cp_per_axis): b_i = bp.bernstein_polynomial(i, p_degree, nti) b_j = bp.bernstein_polynomial(j, p_degree, nti) b_k = bp.bernstein_polynomial(k, p_degree, nti) bjk = np.outer(b_j, b_k) bijk = np.reshape(np.outer( b_i, bjk), (len(b_i), len(b_j), len(b_k))) if verbose: print(f"bijk = {bijk}") x += bijk * cp_x[Point[i][j][k]] y += bijk * cp_y[Point[i][j][k]] z += bijk * cp_z[Point[i][j][k]] if (surface_t0_uv_triangulation or surface_t1_uv_triangulation or surface_u0_vt_triangulation or surface_u1_vt_triangulation or surface_v0_tu_triangulation or surface_v1_tu_triangulation): # convention here is reverse of the (x, y) convention of # mesh grid, see # https://numpy.org/doc/stable/reference/generated/numpy.meshgrid.html u, t = np.meshgrid(u, t) u, t = u.flatten(), t.flatten() tri = mtri.Triangulation(u, t) if surface_t0_uv_triangulation: ax.plot_trisurf( x[0].flatten(), y[0].flatten(), z[0].flatten(), triangles=tri.triangles, alpha=triangulation_alpha, ) if surface_t1_uv_triangulation: ax.plot_trisurf( x[-1].flatten(), y[-1].flatten(), z[-1].flatten(), triangles=tri.triangles, alpha=triangulation_alpha, ) if surface_u0_vt_triangulation: ax.plot_trisurf( x[:, 0].flatten(), y[:, 0].flatten(), z[:, 0].flatten(), triangles=tri.triangles, alpha=triangulation_alpha, ) if surface_u1_vt_triangulation: ax.plot_trisurf( x[:, -1].flatten(), y[:, -1].flatten(), z[:, -1].flatten(), triangles=tri.triangles, alpha=triangulation_alpha, ) if surface_v0_tu_triangulation: ax.plot_trisurf( x[:, :, 0].flatten(), y[:, :, 0].flatten(), z[:, :, 0].flatten(), triangles=tri.triangles, alpha=triangulation_alpha, ) if surface_v1_tu_triangulation: ax.plot_trisurf( x[:, :, -1].flatten(), y[:, :, -1].flatten(), z[:, :, -1].flatten(), triangles=tri.triangles, alpha=triangulation_alpha, ) if verbose: print("Triangulation is complete.") if bezier_points_shown: ax.scatter3D( x.flatten(), y.flatten(), z.flatten(), color=bezier_points_color, s=bezier_points_size, ) if bezier_lines_shown: ax.plot( x.flatten(), y.flatten(), z.flatten(), color=bezier_lines_color, linewidth=bezier_linewidth, ) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) ax.set_zlabel(zlabel) if Z_AXIS_LABEL_INVERTED: ax.zaxis.set_rotate_label(False) ax.zaxis.label.set_rotation(90) if XTICKS: ax.set_xticks(XTICKS) if YTICKS: ax.set_yticks(YTICKS) if ZTICKS: ax.set_zticks(ZTICKS) # fix coming in matplotlib 3.3.1 (current stable version is 3.2.2) # https://github.com/matplotlib/matplotlib/pull/17515 # ax.set_box_aspect([1, 1, 1]) # ax.set_proj_type('ortho') # optional - default is perspective (shown in image above) # set_axes_equal(ax) # IMPORTANT - this is also required # fig = plt.gcf() # fig.set_size_inches(5, 5) # ax.set_box_aspect(1) if xlim: ax.set_xlim(xlim) if ylim: ax.set_ylim(ylim) if zlim: ax.set_zlim(zlim) ax.view_init(elev=camera_elevation, azim=camera_azimuth) # plt.ioff() # interactivity off if INTERACTIVE: plt.show() else: plt.show(block=False) if SERIALZE: extension = ".pdf" # or ".svg" filename = STEM + extension fig.savefig(filename, bbox_inches="tight", pad_inches=0) print(f"Serialized file to {filename}")
def __init__(self, config): self.INITIALIZED = False # get client configuration, or if not specified, set to default configuration DEGREE = config.get("degree", 1) NTI_BISECTIONS = config.get("number-time-interval-bisections", 1) DISPLAY = config.get("display", True) AZIMUTH = config.get("camera-azimuth", 15) # degrees ELEVATION = config.get("camera-elevation", 15) # degrees DPI = config.get("dots-per-inch", 100) LATEX = config.get("latex", True) SERIALIZE = config.get("serialize", True) VERBOSE = config.get("verbose", True) Z_AXIS_LABEL_INVERTED = config.get("z-axis-label-inverted", True) if LATEX: rc("font", **{ "family": "serif", "serif": ["Computer Modern Roman"] }) rc("text", usetex=True) # DEGREE = 1 # e.g., p=1 linear, p=2 quadratic, p=3 cubic cp_t = np.arange(DEGREE + 1) # control points in the t direction cp_u = np.arange(DEGREE + 1) # control points in the u direction # control points in (t, u) space cp_tu = tuple((i, j) for i in cp_t for j in cp_u) cp_ts = [z[0] / DEGREE for z in cp_tu] cp_us = [z[1] / DEGREE for z in cp_tu] cp_bs = [0 for z in cp_tu] # plot control points at zero for the B axis # number of time intervals # e.g., 4 time intervals implies 5 evaluation points along an axis, t or u # nti = 2 ** 1 # for minimal interpolation # nti = 2 ** 1 # for LaTeX figures nti = 2**NTI_BISECTIONS # azimuth, elevation = (-15, 15) # degrees # azimuth, elevation = (15, 15) # degrees # azimuth, elevation = (-75, 15) # degrees # bases = np.array([np.array([])]) if VERBOSE: print(f"Computing Bezier surface with degree p={DEGREE}") print(f"with number of time intervals nti={nti}") for i in cp_t: for j in cp_u: b_i = bp.bernstein_polynomial(i, DEGREE, nti) b_j = bp.bernstein_polynomial(j, DEGREE, nti) bij = np.outer(b_i, b_j) if VERBOSE: print(f"i={i}, j={j}") print(f"bij = {bij}") # fig = plt.figure() fig = plt.figure(figsize=plt.figaspect(1.0), dpi=DPI) # fig = plt.figure(figsize=(6.5, 3.25), dpi=DPI) ax = fig.gca(projection="3d") # ax.view_init(elevation, azimuth) ax.view_init(ELEVATION, AZIMUTH) plt.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0) t = np.linspace(0, 1, nti + 1) # for projecting onto [t, B] and [u, B] planes zeros = [0 for i in range(len(t))] # on the (t, u) grid: tij = np.outer( t, np.ones(nti + 1)) # all of the t values from [0, 1] uij = np.outer(np.ones(nti + 1), t) # all of the u values from [0, 1] if VERBOSE: print(f"tij = {tij}") print(f"uij = {uij}") # plot the control net in the [t, u] plane ax.scatter3D(cp_ts, cp_us, cp_bs, edgecolor="black", facecolor="black", s=20) # plot the Bezier basis functions in the [u, B] plane for II in cp_t: ax.plot3D( t, zeros, bp.bernstein_polynomial(II, DEGREE, nti), color="gray", linewidth=0.5, ) # and highlight the current basis function ax.plot3D( t, zeros, bp.bernstein_polynomial(i, DEGREE, nti), color="red", linewidth=1.5, ) # plot the Bezier basis functions in the [t, B] plane for JJ in cp_u: ax.plot3D( zeros, t, bp.bernstein_polynomial(JJ, DEGREE, nti), color="gray", linewidth=0.5, ) # and highlight the current basis function ax.plot3D( zeros, t, bp.bernstein_polynomial(j, DEGREE, nti), color="green", linewidth=1.5, ) # surf = ax.plot_surface(tij, uij, bij, alpha=0.8) ax.plot_surface(tij, uij, bij, alpha=0.8) ax.set_xlabel(r"$t$") ax.set_ylabel(r"$u$") ax.set_zlabel(r"$B^{" + str(DEGREE) + "}_{" + str(i) + ", " + str(j) + "}(t, u)$") if Z_AXIS_LABEL_INVERTED: ax.zaxis.set_rotate_label(False) ax.zaxis.label.set_rotation(90) ax.set_xlim([0, 1]) ax.set_ylim([0, 1]) ax.set_zlim([0, 1]) ax.xaxis.set_major_locator(MultipleLocator(0.25)) ax.yaxis.set_major_locator(MultipleLocator(0.25)) ax.zaxis.set_major_locator(MultipleLocator(0.25)) if DISPLAY: # plt.show() plt.show(block=False) if SERIALIZE: extension = ".pdf" # or '.svg' bstring = "B(p=" + str(DEGREE) + ")_" + str(i) + "_" bstring += str(j) + extension # fig.savefig(bstring, bbox_inches="tight") fig.savefig(bstring, bbox_inches="tight", pad_inches=0) print(f"Serialized file as {bstring}") self.INITIALIZED = True