Skip to content

Commit d98b484

Browse files
committedJul 18, 2023
Temp check in
1 parent 3a10e6d commit d98b484

12 files changed

+3497
-3368
lines changed
 

‎Image_based/HandleFileNames.py

+16
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,22 @@ def get_image_name(self, path, index, b_add_tag=True):
193193

194194
return im_name
195195

196+
def get_edge_image_name(self, path, index, b_add_tag=True):
197+
""" Get the image name corresponding to the index given by (subdirectory index, image index, -)
198+
@param path should be one of self.path, self.path_calculated, or path_debug
199+
@param index (tuple, either 2 dim or 3 dim, index into sorted lists)
200+
@param b_add_tag - add the image tag, y/n
201+
@return full image name with path"""
202+
203+
im_name = path
204+
if len(self.sub_dirs[index[0]]) > 0:
205+
im_name = im_name + self.sub_dirs[index[0]] + "/"
206+
im_name = im_name + self.image_names[index[0]][index[1]] + "_edge"
207+
if b_add_tag:
208+
im_name = im_name + self.image_tag
209+
210+
return im_name
211+
196212
def get_mask_name(self, path, index, b_add_tag=True):
197213
""" Get the mask name corresponding to the index given by (subdirectory index, image index, mask name, mask id)
198214
@param path should be one of self.path, self.path_calculated, or path_debug

‎Image_based/bezier_cyl_2d.py

+14-13
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,13 @@ def __init__(self, start_pt=None, end_pt=None, radius=1, mid_pt=None):
4747
else:
4848
self.p1 = np.array(mid_pt)
4949
self.start_radius = radius
50-
self.end_radius = 100
51-
#want to pass every single point from start to end and return radius along that path
50+
self.end_radius = radius
51+
5252
def radius(self, t):
53-
return (1-t)*(self.start_radius) + (t*self.end_radius)
54-
55-
53+
""" Radius is a linear interpolation of two end radii
54+
@param t - t between 0 and 1"""
55+
return (1 - t) * self.start_radius + t * self.end_radius
56+
5657
@staticmethod
5758
def _orientation(start_pt, end_pt):
5859
"""Set the orientation and ensure left-right or down-up
@@ -103,22 +104,22 @@ def edge_pts(self, t):
103104
right_pt = [pt[0] + vec_step[1], pt[1] - vec_step[0]]
104105
return left_pt, right_pt
105106

106-
def edge_offset_pt(self, t, perc_in_out, dir):
107+
def edge_offset_pt(self, t, perc_in_out, direction):
107108
""" Go in/out of the edge point a given percentage
108109
@param t - t value along the curve (in range 0, 1)
109110
@param perc_in_out - if 1, get point on edge. If 0.5, get halfway to centerline. If 2.0 get 2 width
110-
@param dir - 'Left' is the left direction, 'Right' is the right direction
111+
@param direction - 'Left' is the left direction, 'Right' is the right direction
111112
@return numpy array x,y """
112113
pt_edge = self.pt_axis(t)
113114
vec_tang = self.tangent_axis(t)
114115
vec_step = perc_in_out * self.radius(t) * vec_tang / np.sqrt(vec_tang[0] * vec_tang[0] + vec_tang[1] * vec_tang[1])
115116

116-
if dir == "Left":
117+
if direction == "Left":
117118
return np.array([pt_edge[0] + vec_step[1], pt_edge[1] - vec_step[0]])
118119
return np.array([pt_edge[0] - vec_step[1], pt_edge[1] + vec_step[0]])
119120

120121
@staticmethod
121-
def _rect_in_image(im, r, pad=2):
122+
def rect_in_image(im, r, pad=2):
122123
""" See if the rectangle is within the image boundaries
123124
@im - image (for width and height)
124125
@r - the rectangle
@@ -233,7 +234,7 @@ def interior_rects_mask(self, image_shape, step_size=40, perc_width=0.3):
233234
return ret_im_mask
234235

235236
@staticmethod
236-
def _image_cutout(im, rect, step_size, height):
237+
def image_cutout(im, rect, step_size, height):
237238
"""Cutout a warped bit of the image and return it
238239
@param im - the image rect is in
239240
@param rect - four corners of the rectangle to cut out
@@ -430,9 +431,9 @@ def make_mask_image(self, im_mask, step_size=20, perc_fuzzy=0.2):
430431
@param perc_fuzzy How much of the boundary to make fuzzy
431432
"""
432433
self.draw_interior_rects_filled(im_mask, b_solid=True,
433-
col_solid=(255, 255, 255),
434-
step_size=step_size,
435-
perc_width=1.0)
434+
col_solid=(255, 255, 255),
435+
step_size=step_size,
436+
perc_width=1.0)
436437
self.draw_boundary_rects_filled(im_mask, b_solid=True,
437438
col_solid=(128, 128, 128),
438439
step_size=step_size,

‎Image_based/extract_curves.py

+45-30
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,12 @@
88
# - Store as t, percentage in/out from radius (so we can re-use at different scales)
99

1010
import numpy as np
11-
from glob import glob
12-
import csv
1311
import cv2
1412
import json
1513
from os.path import exists
16-
from bezier_cyl_2d import BezierCyl2D
1714
from line_seg_2d import LineSeg2D
18-
from fit_bezier_cyl_2d_edge import FitBezierCyl2DEdge
1915
from HandleFileNames import HandleFileNames
16+
from fit_bezier_cyl_2d_edge import FitBezierCyl2DEdge
2017

2118

2219
class ExtractCurves:
@@ -53,7 +50,7 @@ def __init__(self, fname_rgb_image, fname_edge_image, fname_mask_image, params=N
5350
if b_recalc or not fname_calculated or not exists(self.fname_full_edge_stats):
5451
# Recalculate and write
5552
self.edge_stats = ExtractCurves.full_edge_stats(self.bezier_edge.image_edge,
56-
self.bezier_edge.fit_bezier_edge,
53+
self.bezier_edge.bezier_crv_fit_to_edge,
5754
self.params)
5855
# Write out the bezier curve
5956
if fname_calculated:
@@ -83,30 +80,34 @@ def __init__(self, fname_rgb_image, fname_edge_image, fname_mask_image, params=N
8380

8481
if fname_debug:
8582
# Draw the mask with the initial and fitted curve
86-
im_covert_back = cv2.cvtColor(self.bezier_edge.edge_image, cv2.COLOR_GRAY2RGB)
87-
im_rgb = cv2.copyTo(self.bezier_edge.rgb_image)
83+
im_covert_back = cv2.cvtColor(self.bezier_edge.image_edge, cv2.COLOR_GRAY2RGB)
84+
im_rgb = np.copy(self.bezier_edge.image_rgb)
8885
for pix in self.edge_stats["pixs_edge"]:
8986
im_covert_back[pix[0], pix[1], :] = (255, 0, 0)
9087
im_rgb[pix[0], pix[1], :] = (255, 255, 255)
9188

9289
for do_both_crv, do_both_name in [(self.left_curve, "Left"), (self.right_curve, "Right")]:
9390
for pt_e1, pt_e2 in zip(do_both_crv[0:-1], do_both_crv[1:]):
94-
pt1 = self.bezier_edge.bezier_fit.edge_offset_pt(pt_e1[0], pt_e1[1], do_both_name)
95-
pt2 = self.bezier_edge.bezier_fit.edge_offset_pt(pt_e2[0], pt_e2[1], do_both_name)
91+
pt1 = self.bezier_edge.bezier_crv_fit_to_edge.edge_offset_pt(pt_e1[0], pt_e1[1], do_both_name)
92+
pt2 = self.bezier_edge.bezier_crv_fit_to_edge.edge_offset_pt(pt_e2[0], pt_e2[1], do_both_name)
9693
LineSeg2D.draw_line(im_covert_back, pt1, pt2, color=(255, 255, 0))
9794
LineSeg2D.draw_line(im_rgb, pt1, pt2, color=(255, 255, 0))
9895
im_both = np.hstack([im_covert_back, im_rgb])
9996
cv2.imwrite(fname_debug, im_both)
10097

101-
@staticmethod def full_edge_stats(image_edge, bezier_edge, params):
98+
@staticmethod
99+
def full_edge_stats(image_edge, bezier_edge, params):
102100
""" Get the best pixel offset (if any) for each point along the edge
103101
@param image_edge - the edge image
104102
@param bezier_edge - the Bezier curve
105103
@param params - parameters for extraction"""
106104
bdry_rects1, ts1 = bezier_edge.boundary_rects(step_size=params["step_size"], perc_width=params["perc_width"])
107105
bdry_rects2, ts2 = bezier_edge.boundary_rects(step_size=params["step_size"], perc_width=params["perc_width"], offset=True)
108106
n_bdry1 = len(bdry_rects1)
109-
t_step = (ts1[-1] - ts1[0]) / (len(bdry_rects1) // 2)
107+
try:
108+
t_step = ts1[2] - ts1[0]
109+
except IndexError:
110+
t_step = 1.0
110111

111112
bdry_rects1.extend(bdry_rects2)
112113
ts1.extend(ts2)
@@ -120,22 +121,30 @@ def __init__(self, fname_rgb_image, fname_edge_image, fname_mask_image, params=N
120121
for i_rect, r in enumerate(bdry_rects1):
121122
# b_rect_inside = BezierCyl2D._rect_in_image(image_edge, r, pad=2)
122123

123-
im_warp, tform3_back = bezier_edge._image_cutout(image_edge, r, step_size=width, height=height)
124+
im_warp, tform3_back = bezier_edge.image_cutout(image_edge, r, step_size=width, height=height)
124125
# Actual hough transform on the cut-out image
125126
lines = cv2.HoughLines(im_warp, 1, np.pi / 180.0, 10)
126127

128+
# Check for any lines in the cutout image
129+
if lines is None:
130+
continue
131+
# .. and check if any of those are horizontal
132+
ret_pts = FitBezierCyl2DEdge.get_horizontal_lines_from_hough(lines, tform3_back, width, height)
133+
if ret_pts is []:
134+
continue
127135
i_side = i_rect % 2
128136

129137
if i_rect == n_bdry1:
130-
t_step = (ts2[-1] - ts2[0]) / (len(bdry_rects2) // 2)
138+
try:
139+
t_step = ts2[2] - ts2[0]
140+
except IndexError:
141+
t_step = 1.0
131142

132-
if not lines:
133-
continue
134143
max_y = im_warp.max(axis=0)
135144

136-
ts_seg = np.linspace(ts1[i_rect] - t_step * 0.5, ts1[i_rect] - t_step * 0.5, len(max_y))
145+
ts_seg = np.linspace(ts1[i_rect] - t_step * 0.5, ts1[i_rect] + t_step * 0.5, len(max_y))
137146
for i_col, y in enumerate(max_y):
138-
if max_y > params["edge_max"]:
147+
if y > params["edge_max"]:
139148
ids = np.where(im_warp[:, i_col] == y)
140149
if i_side == 0:
141150
tag = "left"
@@ -146,13 +155,17 @@ def __init__(self, fname_rgb_image, fname_edge_image, fname_mask_image, params=N
146155
h_max = (1 + params["perc_width"]) * bezier_edge.radius(ts1[i_col])
147156
ret_stats[tag + "_perc"].append(h_min + h_max + ids[0] / width)
148157

149-
p1_in = np.transpose(np.array([ids[0], i_col, 1.0]))
158+
p1_in = np.transpose(np.array([ids[0][0], i_col, 1.0]))
150159
p1_back = tform3_back @ p1_in
151160

152161
ret_stats["pixs_edge"].append([p1_back[0], p1_back[1]])
153162

154-
sort(zip(ret_stats["ts_left"], ret_stats["left_perc"]), key=0)
155-
sort(zip(ret_stats["ts_right"], ret_stats["right"]), key=0)
163+
np.array([ret_stats["ts_left"], ret_stats["left_perc"]])
164+
np.sort(axis=0)
165+
ret_stats["ts_left"] = list(np.array[0, :])
166+
ret_stats["left_perc"] = list(np.array[1, :])
167+
# sort(zip(ret_stats["ts_left"], ret_stats["left_perc"]), key=0)
168+
# sort(zip(ret_stats["ts_right"], ret_stats["right"]), key=0)
156169
return ret_stats
157170

158171
@staticmethod
@@ -168,35 +181,37 @@ def curves_from_stats(stats_edge, params):
168181
for ts, ps in [(stats_edge["ts_left"], stats_edge["Left_perc"]), (stats_edge["ts_right"], stats_edge["Right_perc"])]:
169182
ps_filter = np.array(ps)
170183
for i_filter in range(0, params["n_filter"]):
171-
np.filter(ps_filter)
184+
# np.filter(ps_filter)
172185
ts_crvs = np.linspace(0, 1, params["n_samples"])
173186
ps_crvs = np.interp(ts_crvs, ts, ps_filter)
174187
crvs.append([(t, p) for t, p in zip(ts_crvs, ps_crvs)])
175188
return crvs[0], crvs[1]
176189

177190

178191
if __name__ == '__main__':
179-
#path_bpd = "./data/trunk_segmentation_names.json"
192+
# path_bpd = "./data/trunk_segmentation_names.json"
180193
path_bpd = "./data/forcindy_fnames.json"
181194
all_files = HandleFileNames.read_filenames(path_bpd)
182195

183196
b_do_debug = True
184-
b_do_recalc = False
197+
b_do_recalc = True
185198
for ind in all_files.loop_masks():
186-
mask_fname = all_files.get_mask_name(path=all_files.path, index=ind, b_add_tag=True)
187199
rgb_fname = all_files.get_image_name(path=all_files.path, index=ind, b_add_tag=True)
188-
edge_fname = all_files.get_image_name(path=all_files.path_calculated, index=ind, b_add_tag=False) + "_edge.png"
189-
mask_fname_debug = all_files.get_mask_name(path=all_files.path_debug, index=ind, b_add_tag=False)
200+
edge_fname = all_files.get_edge_image_name(path=all_files.path_calculated, index=ind, b_add_tag=True)
201+
mask_fname = all_files.get_mask_name(path=all_files.path, index=ind, b_add_tag=True)
202+
ec_fname_debug = all_files.get_mask_name(path=all_files.path_debug, index=ind, b_add_tag=False)
190203
if not b_do_debug:
191-
mask_fname_debug = ""
204+
ec_fname_debug = ""
192205
else:
193-
mask_fname_debug = mask_fname_debug + "_crv.png"
206+
ec_fname_debug = ec_fname_debug + "_extract_profile.png"
194207

195-
mask_fname_calculate = all_files.get_mask_name(path=all_files.path_calculated, index=ind, b_add_tag=False)
208+
ec_fname_calculate = all_files.get_mask_name(path=all_files.path_calculated, index=ind, b_add_tag=False)
196209

197210
if not exists(mask_fname):
198211
raise ValueError(f"Error, file {mask_fname} does not exist")
199212
if not exists(rgb_fname):
200213
raise ValueError(f"Error, file {rgb_fname} does not exist")
201214

202-
b_stats = ExtractCurves(rgb_fname, edge_fname, mask_fname, mask_fname_calculate, mask_fname_debug, b_recalc=b_do_recalc)
215+
profile_crvs = ExtractCurves(rgb_fname, edge_fname, mask_fname,
216+
fname_calculated=ec_fname_calculate,
217+
fname_debug=ec_fname_debug, b_recalc=b_do_recalc)

‎Image_based/fit_bezier_cyl_2d_edge.py

+272-361
Large diffs are not rendered by default.

‎Image_based/fit_bezier_cyl_2d_mask.py

+7-12
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def __init__(self, fname_mask_image, fname_calculated=None, fname_debug=None, b_
4141
# This is the curve that will be fit to the mask
4242
self.bezier_crv_fit_to_mask = FitBezierCyl2D(self.bezier_crv_initial)
4343

44-
# Fit a quad to the mask, using the end points of the base image as a starting point
44+
# Create the calculated file names
4545
print(f"Fitting bezier curve to mask image {fname_mask_image}")
4646
self.fname_bezier_cyl_initial = None # The actual quadratic bezier
4747
self.fname_bezier_cyl_fit_to_mask = None # The actual quadratic bezier
@@ -98,7 +98,7 @@ def __init__(self, fname_mask_image, fname_calculated=None, fname_debug=None, b_
9898
cv2.imwrite(fname_debug, im_both)
9999

100100
self.score = self.score_mask_fit(self.stats_dict.mask_image)
101-
print(f"Mask {mask_fname}, score {self.score}")
101+
print(f"Mask {fname_mask_image}, score {self.score}")
102102

103103
@staticmethod
104104
def create_bezier_crv_from_eigen_vectors(stats):
@@ -131,17 +131,17 @@ def _adjust_bezier_crv_by_mask(fit_bezier_crv, im_mask, step_size=40, perc_width
131131
@param step_size - how many pixels to step along
132132
@param perc_width - how much wider than the radius to look in the mask
133133
@returns how much the points moved"""
134-
height = int(fit_bezier_crv.radius_2d)
134+
height = int(fit_bezier_crv.radius(0.5))
135135
rects, ts = fit_bezier_crv.interior_rects(step_size=step_size, perc_width=perc_width)
136136

137137
# Set up the matrix - include the 3 current points plus the centers of the mask
138138
a_constraints, b_rhs = fit_bezier_crv.setup_least_squares(ts)
139139

140140
x_grid, y_grid = np.meshgrid(range(0, step_size), range(0, height))
141141
for i, r in enumerate(rects):
142-
b_rect_inside = BezierCyl2D._rect_in_image(im_mask, r, pad=2)
142+
b_rect_inside = BezierCyl2D.rect_in_image(im_mask, r, pad=2)
143143

144-
im_warp, tform_inv = fit_bezier_crv._image_cutout(im_mask, r, step_size=step_size, height=height)
144+
im_warp, tform_inv = fit_bezier_crv.image_cutout(im_mask, r, step_size=step_size, height=height)
145145
if b_rect_inside and np.sum(im_warp > 0) > 0:
146146
x_mean = np.mean(x_grid[im_warp > 0])
147147
y_mean = np.mean(y_grid[im_warp > 0])
@@ -182,17 +182,12 @@ def score_mask_fit(self, im_mask):
182182
# edges
183183
im_bezier_mask = np.zeros((self.stats_dict.mask_image.shape[0], self.stats_dict.mask_image.shape[1]), np.uint8)
184184

185-
self.bezier_crv_fit_to_mask.make_mask_image(im_bezier_mask,
186-
step_size=10,
187-
perc_fuzzy=0.25)
185+
self.bezier_crv_fit_to_mask.make_mask_image(im_bezier_mask, step_size=10, perc_fuzzy=0.25)
188186

189187
pixs_in_mask_not_bezier = np.logical_and(im_mask > 0, im_bezier_mask == 0)
190188
pixs_in_bezier_not_mask = np.logical_and(im_mask == 0, im_bezier_mask == 255)
191189
pixs_in_both_masks = np.logical_and(im_mask > 0, im_bezier_mask == 255)
192190
pixs_in_union = np.logical_or(np.logical_or(pixs_in_bezier_not_mask, pixs_in_mask_not_bezier), pixs_in_both_masks)
193-
n_in_mask_not_bezier = np.count_nonzero(pixs_in_mask_not_bezier)
194-
n_in_bezier_not_mask = np.count_nonzero(pixs_in_bezier_not_mask)
195-
n_in_both_masks = np.count_nonzero(pixs_in_both_masks)
196191
n_in_union = np.count_nonzero(pixs_in_union)
197192
if n_in_union == 0:
198193
return 0
@@ -201,7 +196,7 @@ def score_mask_fit(self, im_mask):
201196

202197

203198
if __name__ == '__main__':
204-
#path_bpd = "./data/trunk_segmentation_names.json"
199+
# path_bpd = "./data/trunk_segmentation_names.json"
205200
path_bpd = "./data/forcindy_fnames.json"
206201
all_files = HandleFileNames.read_filenames(path_bpd)
207202

‎bezier_cyl_3d.py

+28-8
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ def __init__(self, p1=(0.0, 0.0, 0.0), p2=(0.5, 0.75, 0.5), p3=(1.0, 1.0, 1.0),
2424
@param end_radius - ending radius"""
2525

2626
# Information about the current branch/trunk
27-
self.pt1 = p1
28-
self.pt2 = p2
29-
self.pt3 = p3
27+
self.pt1 = np.array(p1)
28+
self.pt2 = np.array(p2)
29+
self.pt3 = np.array(p3)
3030
self.start_radii = start_radius
3131
self.end_radii = end_radius
3232

@@ -37,6 +37,24 @@ def __init__(self, p1=(0.0, 0.0, 0.0), p2=(0.5, 0.75, 0.5), p3=(1.0, 1.0, 1.0),
3737
self.vertex_locs = np.zeros((self.n_along, self.n_around, 3))
3838
self.make_mesh()
3939

40+
def copy(self, bezier_crv=None, b_compute_mesh=False):
41+
"""Return a copy of self - mostly just copy, rather than =, the points/vertices
42+
@param bezier_crv - use this bezier curve versus making a new one
43+
@param b_compute_mesh - compute/copy the mesh, y/n
44+
@return: New curve"""
45+
if bezier_crv is None:
46+
bezier_crv = BezierCyl3D(np.copy(self.pt1), np.copy(self.pt2), np.copy(self.pt3), self.start_radii, self.end_radii)
47+
for k, v in self.__dict__:
48+
try:
49+
if v.size > 1:
50+
pass
51+
except TypeError:
52+
setattr(bezier_crv, k, v)
53+
54+
if b_compute_mesh:
55+
bezier_crv.vertex_locs = np.copy(self.vertex_locs)
56+
return bezier_crv
57+
4058
def n_vertices(self):
4159
return self.n_along * self.n_around
4260

@@ -51,9 +69,9 @@ def set_pts(self, pt1, pt2, pt3):
5169
@param pt2 Mid point
5270
@param pt3 End point
5371
"""
54-
self.pt1 = pt1
55-
self.pt2 = pt2
56-
self.pt3 = pt3
72+
self.pt1 = np.array(pt1)
73+
self.pt2 = np.array(pt2)
74+
self.pt3 = np.array(pt3)
5775

5876
def set_pts_from_pt_tangent(self, pt1, vec1, pt3):
5977
"""Set the points from a starting point/tangent
@@ -132,7 +150,7 @@ def _calc_radii(self):
132150

133151
def _calc_cyl_vertices(self):
134152
"""Calculate the cylinder vertices"""
135-
pt = np.ones(shape=(4))
153+
pt = np.ones(shape=(4,))
136154
radii = self._calc_radii()
137155

138156
for it, t in enumerate(np.linspace(0, 1.0, self.n_along)):
@@ -175,6 +193,8 @@ def write_json(self, fname):
175193
@param fname - file name"""
176194
fix_nparray = []
177195
for k, v in self.__dict__.items():
196+
if k == "vertex_locs":
197+
continue
178198
try:
179199
if v.size == 3:
180200
fix_nparray.append([k, v])
@@ -207,7 +227,7 @@ def read_json(fname, bezier_crv=None, compute_mesh=True):
207227
setattr(bezier_crv, k, v)
208228
except TypeError:
209229
setattr(bezier_crv, k, v)
210-
230+
bezier_crv.set_dims(bezier_crv.n_along, bezier_crv.n_around)
211231
if compute_mesh:
212232
bezier_crv.make_mesh()
213233
return bezier_crv

‎bezier_cyl_3d_with_detail.py

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/usr/bin/env python3
2+
3+
# a 3D Bezier branch/trunk
4+
# Inherits from 3D bezier curve
5+
# - The 3D bezier curve
6+
# - Start and end radii
7+
# Adds
8+
# - placing buds/bud geometry
9+
# - profile curve - making the outside "bumpy"
10+
#
11+
# Can:
12+
# - Evaluate points along the curve
13+
# - Make a cylinderical mesh
14+
# - Project self into an image
15+
16+
import numpy as np
17+
from json import load
18+
from bezier_cyl_3d import BezierCyl3D
19+
from scipy.spatial.transform import Rotation as R
20+
21+
22+
class BezierCyl3DWithDetail(BezierCyl3D):
23+
_profiles = None
24+
_type_names = {"trunk", "sidebranch", "branch"}
25+
_bud_shape = None
26+
_data_dir = "./data/"
27+
28+
def __init__(self, bezier_crv=None):
29+
""" Initialize a 3D curve, built from a quadratic Bezier with radii
30+
@param bezier_crv - 3D bezier curve to start with"""
31+
32+
if bezier_crv:
33+
super(BezierCyl3DWithDetail, self).__init__()
34+
bezier_crv.copy(bezier_crv=self, b_compute_mesh=True)
35+
else:
36+
super(BezierCyl3DWithDetail, self).__init__()
37+
38+
self.start_is_junction = False
39+
self.end_is_bud = False
40+
self.start_bud = 0.7
41+
self.bud_angle = 0.8 * np.pi / 2
42+
self.bud_length = 0.1
43+
44+
# Read in global data/profiles
45+
BezierCyl3DWithDetail._read_profiles(self._data_dir)
46+
BezierCyl3DWithDetail._make_bud_shape()
47+
48+
@staticmethod
49+
def _read_profiles(data_dir):
50+
""" Read in all the profile curves for the various branch types"""
51+
if BezierCyl3DWithDetail._profiles is not None:
52+
return
53+
54+
BezierCyl3DWithDetail._profiles = {}
55+
for t in BezierCyl3DWithDetail._type_names:
56+
try:
57+
fname = data_dir + "/" + t + "_profiles.json"
58+
with open(fname, "r") as fp:
59+
BezierCyl3DWithDetail._profiles[t] = load(fp)
60+
except FileNotFoundError:
61+
pass
62+
63+
@staticmethod
64+
def _make_bud_shape():
65+
if BezierCyl3DWithDetail._bud_shape is None:
66+
n_pts = 10
67+
BezierCyl3DWithDetail._bud_shape = np.zeros((2, n_pts))
68+
BezierCyl3DWithDetail._bud_shape[0, :] = np.linspace(0, 1.0, n_pts)
69+
BezierCyl3DWithDetail._bud_shape[0, -2] = 0.5 * BezierCyl3DWithDetail._bud_shape[0, -2] + 0.5 * BezierCyl3DWithDetail._bud_shape[0, -1]
70+
BezierCyl3DWithDetail._bud_shape[1, 0] = 1.0
71+
BezierCyl3DWithDetail._bud_shape[1, 1] = 0.95
72+
BezierCyl3DWithDetail._bud_shape[1, 2] = 1.05
73+
BezierCyl3DWithDetail._bud_shape[1, 3] = 1.1
74+
BezierCyl3DWithDetail._bud_shape[1, 4] = 1.05
75+
BezierCyl3DWithDetail._bud_shape[1, 5] = 0.8
76+
BezierCyl3DWithDetail._bud_shape[1, 6] = 0.7
77+
BezierCyl3DWithDetail._bud_shape[1, 7] = 0.5
78+
BezierCyl3DWithDetail._bud_shape[1, 8] = 0.3
79+
BezierCyl3DWithDetail._bud_shape[1, 9] = 0.0
80+
81+
def _make_cyl(self, profiles):
82+
""" Make a 3D generalized cylinder
83+
@param profiles - variations to the radii """
84+
if profiles:
85+
self._calc_cyl_vertices()
86+
else:
87+
self._calc_cyl_vertices()
88+
89+
def set_radii_and_junction(self, start_radius=1.0, end_radius=1.0, b_start_is_junction=False, b_end_is_bud=False):
90+
""" Set the radius of the branch
91+
@param start_radius - radius at pt1
92+
@param end_radius - radius at pt3
93+
@param b_start_is_junction - is the start of the curve a junction?
94+
@param b_end_is_bud - is the end a bud? """
95+
self.set_radii(start_radius, end_radius)
96+
self.start_is_junction = b_start_is_junction
97+
self.end_is_bud = b_end_is_bud
98+
99+
def make_branch_segment(self, pt1, pt2, pt3, radius_start, radius_end, start_is_junction, end_is_bud):
100+
""" Output a 3D generalized cylinder"""
101+
self.set_pts(pt1, pt2, pt3)
102+
self.set_radii_and_junction(start_radius=radius_start, end_radius=radius_end, b_start_is_junction=start_is_junction, b_end_is_bud=end_is_bud)
103+
try:
104+
self._make_cyl(BezierCyl3DWithDetail._profiles["sidebranches"])
105+
except KeyError:
106+
self._make_cyl(None)
107+
108+
def place_buds(self, locs):
109+
""" Position and orientation of buds,
110+
@param locs - t along, radius loc tuples in a list
111+
@
112+
@return [(pt1, pt2, pt3) """
113+
114+
ts = np.linspace(0, 1, self.n_along)
115+
radii = self._calc_radii()
116+
117+
pt = np.ones((4,))
118+
zero_pt = np.ones((4,))
119+
zero_pt[0:3] = 0.0
120+
vec = np.zeros((4,))
121+
ret_list = []
122+
for loc in locs:
123+
mat = self.frenet_frame(loc[0])
124+
r = np.interp(loc[0], ts, radii)
125+
pt_on_crv = mat @ zero_pt
126+
pt[0] = np.cos(loc[1]) * r
127+
pt[1] = np.sin(loc[1]) * r
128+
pt[2] = 0
129+
pt_on_srf = mat @ pt
130+
vec[0] = np.cos(loc[1])
131+
vec[1] = np.sin(loc[1])
132+
vec[2] = 0
133+
vec_rotate = np.cross(vec[0:3], np.array([0, 0, 1]))
134+
vec_rotate = vec_rotate / np.linalg.norm(vec_rotate)
135+
mat_rotate_bud = R.from_rotvec(self.bud_angle * vec_rotate)
136+
# Note - newer versions use as_matrix
137+
mat_rot = mat_rotate_bud.as_dcm()
138+
vec[0:3] = mat_rot @ vec[0:3]
139+
vec_on_crv = mat @ vec
140+
vec_on_crv = vec_on_crv * (self.bud_length / np.linalg.norm(vec_on_crv))
141+
pt_end_bud = pt_on_srf + vec_on_crv
142+
pt_mid = 0.7 * pt_on_srf + 0.3 * pt_on_crv
143+
ret_list.append((pt_mid[0:3], pt_on_srf[0:3], pt_end_bud[0:3]))
144+
145+
return ret_list
146+
147+
148+
if __name__ == '__main__':
149+
150+
branch = BezierCyl3DWithDetail()
151+
152+
branch.set_pts([506.5, 156.0, 0.0], [457.49999996771703, 478.9999900052037, 0.0], [521.5, 318.0, 0.0])
153+
branch.set_radii_and_junction(start_radius=10.5, end_radius=8.25, b_start_is_junction=True, b_end_is_bud=False)
154+
branch.write_mesh("data/jos.obj")
155+
156+
branch.set_pts([-0.5, 0.0, 0.0], [0.0, 0.1, 0.05], [0.5, 0.0, 0.0])
157+
branch.set_radii_and_junction(start_radius=0.5, end_radius=0.25, b_start_is_junction=True, b_end_is_bud=False)
158+
branch.write_mesh("data/cyl.obj")
159+
160+
branch.set_dims(n_along=30, n_radial=32)
161+
branch.set_pts([-0.5, 0.0, 0.0], [0.0, 0.1, 0.05], [0.5, 0.0, 0.0])
162+
branch.set_radii_and_junction(start_radius=0.1, end_radius=0.075, b_start_is_junction=False, b_end_is_bud=True)
163+
branch.write_mesh("data/cyl_bud.obj")
164+
165+
bud_loc = branch.place_buds(((0.2, 0), (0.3, np.pi/4), (0.4, 3.0 * np.pi/4)))
166+
bud = BezierCyl3DWithDetail()
167+
bud.start_bud = 0.2
168+
for i, b in enumerate(bud_loc):
169+
bud.set_pts(b[0], b[1], b[2])
170+
bud.make_branch_segment(b[0], b[1], b[2], radius_start=0.025, radius_end=0.03, start_is_junction=False, end_is_bud=True)
171+
bud.write_mesh(f"data/bud_{i}.obj")

‎data/bud_0.obj

+448-448
Large diffs are not rendered by default.

‎data/bud_1.obj

+448-448
Large diffs are not rendered by default.

‎data/bud_2.obj

+448-448
Large diffs are not rendered by default.

‎data/cyl.obj

+640-640
Large diffs are not rendered by default.

‎data/cyl_bud.obj

+960-960
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.