diff --git a/.gitignore b/.gitignore index 2dc53ca..874361d 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,5 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ +data +results \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 6bb36c1..1b85645 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,6 @@ recursive-exclude test recursive-exclude assets -recursive-exclude .github \ No newline at end of file +recursive-exclude .github +recursive-exclude utils +recursive-exclude experiments +recursive-exclude examples \ No newline at end of file diff --git a/ecut/annealing.py b/ecut/annealing.py index f7cff15..4cc7a1f 100644 --- a/ecut/annealing.py +++ b/ecut/annealing.py @@ -18,8 +18,8 @@ class MorphAnneal: - def __init__(self, swc: ListNeuron, min_gap=10., radius_gap=0, min_step=5., min_step_ratio=.5, step_size=.5, - epsilon=1e-7, res=(1,1,1), drop_len=40.): + def __init__(self, swc: ListNeuron, min_gap=10., radius_gap=.5, min_step=5., min_step_ratio=.5, step_size=.5, + epsilon=1e-7, res=(1,1,1), drop_len=20.): self.morph = Morphology(swc) self._min_gap = min_gap self._eps = epsilon diff --git a/ecut/base_types.py b/ecut/base_types.py index d6bea05..1f37b91 100644 --- a/ecut/base_types.py +++ b/ecut/base_types.py @@ -20,9 +20,7 @@ def __init__(self): self.end1_adj = set() # multiple other nodes, connected or close self.end2_adj = set() self.traversed = set() # for the simplification of the problem - - self.source = None # the neuron it belongs to - self.likelihood = 0 # the likelihood of this fragment belonging to this neuron + self.source = {} # the neuron it belongs to, source id -> likelihood class BaseNode: @@ -49,13 +47,24 @@ def update(self, **kwargs): class BaseCut: - def __init__(self, swc: ListNeuron, soma: list[int], verbose=False): + def __init__(self, swc: ListNeuron, soma: list[int], res, likelihood_thr=None, verbose=False): + """ + + :param swc: swc tree + :param soma: list of soma id + :param res: resolution in x, y, z + :param likelihood_thr: the minimum likelihood allowed for a fragment to be attached to a neuron, left as None + to attach it to just the biggest. When multiple sources share a common or big enough likelihood, all of them will be considered. + :param verbose: + """ self._verbose = verbose self._swc = dict([(t[0], t) for t in swc]) + self.res = np.array(res) self._soma = soma self._fragment: dict[int, BaseFragment] = {} self._fragment_trees: dict[int, dict[int, BaseNode]] = {} self._problem: pulp.LpProblem | None = None + self._likelihood_thr: float = likelihood_thr @property def swc(self): @@ -77,36 +86,59 @@ def export_swc(self, partition=True): :return: an swc or a dict of swc """ if not partition: - tree = [list(t) for t in self._swc.values()] + tree = dict([(t[0], list(t)) for t in self._swc.values()]) + tag = dict(zip(self._soma, range(len(self._soma)))) for frag in self._fragment.values(): for i in frag.nodes: - tree[i][1] = frag.source - tree = [tuple(t) for t in tree] + a = list(frag.source.values()) + b = list(frag.source.keys()) + a = np.argmax(a) + tree[i][1] = tag[b[a]] + tree = [tuple(t) for t in tree.values()] return tree trees = dict([(i, {(-1, 1): (1, *self._swc[i][1:6], -1)}) for i in self._soma]) for frag_id, frag in self._fragment.items(): - frag_node = self._fragment_trees[frag.source][frag_id] - nodes = self._fragment[frag_id].nodes - if not frag_node.reverse: - nodes = nodes[::-1] - par_frag_id = frag_node.parent - if par_frag_id == -1: - last_id = -1, 1 - else: - par_frag_node = self._fragment_trees[frag.source][par_frag_id] - par_nodes = self._fragment[par_frag_id].nodes - if par_frag_node.reverse: - last_id = par_frag_id, par_nodes[-1] + candid = [] + a = list(frag.source.values()) + b = list(frag.source.keys()) + if self._likelihood_thr is None: # max only mode + m = None + for i in np.argsort(a)[::-1]: + if m is not None and m > a[i]: + break + candid.append(b[i]) + m = a[i] + else: # thresholding mode, bigger than this will all be considered + for i in np.argsort(a)[::-1]: + if a[i] < self._likelihood_thr: + break + candid.append(b[i]) + + # for each candid source, append the frag nodes + for src in candid: + frag_node = self._fragment_trees[src][frag_id] + nodes = self._fragment[frag_id].nodes + if not frag_node.reverse: + nodes = nodes[::-1] + par_frag_id = frag_node.parent + if par_frag_id == -1: + last_id = -1, 1 else: - last_id = par_frag_id, par_nodes[0] - tree = trees[frag.source] - for i in nodes: - n = list(self._swc[i]) - n[6] = last_id - n[0] = len(tree) + 1 - tree[(frag_id, i)] = tuple(n) - last_id = frag_id, i + par_frag_node = self._fragment_trees[src][par_frag_id] + par_nodes = self._fragment[par_frag_id].nodes + if par_frag_node.reverse: + last_id = par_frag_id, par_nodes[-1] + else: + last_id = par_frag_id, par_nodes[0] + + tree = trees[src] + for i in nodes: + n = list(self._swc[i]) + n[6] = last_id + n[0] = len(tree) + 1 + tree[(frag_id, i)] = tuple(n) + last_id = frag_id, i for s, t in trees.items(): for k, v in t.items(): @@ -126,9 +158,17 @@ def _linear_programming(self): # finding variables for fragment/soma pairs that require solving scores = {} # var_i_s, i: fragment id, s: soma id for i, frag in self._fragment.items(): - scores[i] = {} - for s in frag.traversed: - scores[i][s] = pulp.LpVariable(f'Score_{i}_{s}', 0) # non-negative + if len(frag.traversed) > 1: # mixed sources + scores[i] = {} + for s in frag.traversed: + scores[i][s] = pulp.LpVariable(f'Score_{i}_{s}', 0) # non-negative + elif len(frag.traversed) == 1: + scores[i] = {} + for s in frag.traversed: + scores[i][s] = pulp.LpVariable(f'Score_{i}_{s}', 1, 1) # const + else: + pass + # raise ValueError('') # objective func: cost * score self._problem += pulp.lpSum( @@ -136,7 +176,6 @@ def _linear_programming(self): self._fragment_trees[s][i].cost * score for s, score in frag_vars.items() ) for i, frag_vars in scores.items() ), "Global Penalty" - # constraints for i, frag_vars in scores.items(): self._problem += (pulp.lpSum(score for score in frag_vars.values()) == 1, @@ -147,20 +186,17 @@ def _linear_programming(self): self._problem += score <= scores[p][s], \ f"Tree Topology Enforcement for Score_{i}_{s}" - self._problem.solve() + self._problem.solve(pulp.PULP_CBC_CMD(msg=0)) + + for frag in self._fragment.values(): + frag.source = dict.fromkeys(frag.traversed, 1) for variable in self._problem.variables(): frag_id, src = variable.name.split('_')[1:] frag_id, src = int(frag_id), int(src) frag = self._fragment[frag_id] - if frag.source is None or frag.likelihood < variable.varValue: - frag.source = src - frag.likelihood = variable.varValue - - for frag in self._fragment.values(): - if frag.source is None: - frag.source = list(frag.traversed)[0] - frag.likelihood = 1 + assert src in frag.source + frag.source[src] = variable.varValue if self._verbose: print("Finished linear programming.") diff --git a/ecut/error_prune.py b/ecut/error_prune.py index 6714abc..8facabd 100644 --- a/ecut/error_prune.py +++ b/ecut/error_prune.py @@ -3,28 +3,25 @@ from scipy.spatial import distance_matrix from scipy.interpolate import interp1d from .morphology import Morphology +from sklearn.decomposition import PCA class ErrorPruning: - def __init__(self, res=(.25, .25, 1.), soma_radius=10., anchor_reach=(2., 10.), gap_thr_ratio=1., epsilon=1e-7): + def __init__(self, res=(.25, .25, 1.), soma_radius=10., anchor_dist=5., epsilon=1e-7): """ :param res: image resolution in micrometers, (x, y, z) :param soma_radius: expected soma radius in micrometers, within which errors are not counted - :param anchor_reach: the distances of the anchor to the branch node, the anchor is meant to accurately - estimate angles. (near, far). near: the near end of the anchor, far: the far end of the anchor, - :param gap_thr_ratio: + :param anchor_dist: the distances of the anchor to the branch node :param epsilon: value lower than this will be regarded as 0 """ self._res = res self._soma_radius = soma_radius - self._near_anchor = anchor_reach[0] - self._far_anchor = anchor_reach[1] - self._gap_thr_ratio = gap_thr_ratio + self._far_anchor = anchor_dist self._eps = epsilon - def _length(self, p1, p2=(0, 0, 0), axis=None): + def _length(self, p1: list | np.ndarray, p2=(0, 0, 0), axis=None): if not isinstance(p1, np.ndarray): p1 = np.array(p1) return np.linalg.norm((p1 - p2) * self._res, axis=axis) @@ -41,8 +38,7 @@ def _vector_angles(self, p, ch): out = [*map(lambda x: math.acos(max(min(x, 1), -1)) * 180 / math.pi, cos_ch)] return np.array(out) - def _find_point(self, morph: Morphology, pt: np.ndarray, idx: np.ndarray, is_parent: bool, - dist_thr: float, pt_rad: float, return_center_point: bool): + def _find_point(self, morph: Morphology, ct: np.ndarray, idx: np.ndarray, is_parent: bool, dist_thr: float, ct_rad: float): """ Find the point of exact `dist` to the start pt on tree structure. args are: - pt: the start point, [coordinate] @@ -51,13 +47,21 @@ def _find_point(self, morph: Morphology, pt: np.ndarray, idx: np.ndarray, is_par if a furcation points encounted, then break - morph: Morphology object for current tree - dist: distance threshold - - return_center_point: whether to return the point with exact distance or - geometric point of all traced nodes """ + # init d = 0 - pts = [pt] - rad = [pt_rad] + if is_parent and morph.pos_dict[idx][6] != -1: + pts = [np.array(morph.pos_dict[idx][2:5])] + rad = [np.array(morph.pos_dict[idx][5])] + idx = morph.pos_dict[idx][6] + elif not is_parent and idx in morph.unifurcation: + pts = [np.array(morph.pos_dict[idx][2:5])] + rad = [np.array(morph.pos_dict[idx][5])] + idx = morph.child_dict[idx][0] + else: + pts = [ct] + rad = [ct_rad] while True: new_p = np.array(morph.pos_dict[idx][2:5]) new_r = morph.pos_dict[idx][5] @@ -76,24 +80,9 @@ def _find_point(self, morph: Morphology, pt: np.ndarray, idx: np.ndarray, is_par else: idx = morph.child_dict[idx][0] - # interpolate to find the exact point - dd = d - dist_thr - if dd < 0: - pt_a = new_p - else: # extrapolate - dcur = self._length(new_p, pts[-1]) - ratio = (dcur - dd) / (dcur + self._eps) - pt_a = pts[-1] + (new_p - pts[-1]) * ratio - r_a = rad[-1] + (new_r - rad[-1]) * ratio - pts.append(pt_a) - rad.append(r_a) - - if return_center_point: - pt_a = np.mean(pts, axis=0) - - return pt_a, pts, rad + return pts, rad, idx - def _get_anchors(self, morph: Morphology, ind: list[int] | int, dist_thr: float, step_size=0.5): + def _get_anchors(self, morph: Morphology, ind: list[int] | int, dist_thr: float): """ get anchors for a set of swc nodes to calculate angles, suppose they are one, their center is their mean coordinate, @@ -129,43 +118,40 @@ def _get_anchors(self, morph: Morphology, ind: list[int] | int, dist_thr: float, protrude = np.array(list(protrude)) # com_node == center can cause problem for spline # for finding anchor_p, you must input sth different from the center to get the right pt list - if self._length(center, morph.pos_dict[com_node][2:5]) <= self._eps: - p = morph.pos_dict[com_node][6] - # p can be -1 if the com_node is root - # but when this happens, com_node can hardly == center - # this case is dispelled when finding crossings - else: - p = com_node - anchor_p, pts_p, rad_p = self._find_point(morph, center, p, True, dist_thr, center_radius, False) - res = [self._find_point(morph, center, i, False, dist_thr, center_radius, False) for i in protrude] - anchor_ch, pts_ch, rad_ch = [i[0] for i in res], [i[1] for i in res], [i[2] for i in res] - gap_thr = np.mean(rad_p) * self._gap_thr_ratio - interp_ch = [] - for pts in pts_ch: - pp = [pts[0]] - j = 1 - dist_cum = [0] - while j < len(pts): - new_d = self._length(pts[j], pp[-1]) - if new_d > self._eps and new_d + dist_cum[-1] != dist_cum[-1]: - dist_cum.append(dist_cum[-1] + new_d) - pp.append(pts[j]) - j += 1 - if len(pp) > 1: - f = interp1d(dist_cum, pp, 'quadratic' if len(pp) > 2 else 'linear', 0, fill_value='extrapolate') - interp_ch.append(f) - step = step_size - while step <= dist_thr: - pts = [i(step) for i in interp_ch] - if len(pts) < 2: - break - gap = distance_matrix(pts, pts) - gap = np.median(gap[np.triu_indices_from(gap, 1)]) - if gap > gap_thr: - break - center = np.mean(pts, axis=0) - step += step_size - return center, anchor_p, anchor_ch, protrude, rad_p, rad_ch + pts_p, rad_p, last_p = self._find_point(morph, center, com_node, True, dist_thr, center_radius) + res = [self._find_point(morph, center, i, False, dist_thr, center_radius) for i in protrude] + pts_ch, rad_ch, last_ch = [i[0] for i in res], [i[1] for i in res], [i[2] for i in res] + return com_node, pts_p, pts_ch, protrude, rad_p, rad_ch, last_p, last_ch + + @staticmethod + def line_fit_pca(pts_list: list[np.ndarray]) -> np.ndarray: + """ + fit 3D points to a straight line. + :param pts_list: a list of 3D connected points + :return: a 3D vector fitted to the list + """ + pca = PCA(n_components=1) + pca.fit(pts_list) + line_direction = pca.components_[0] + temp = pts_list[-1] - pts_list[0] + if temp.dot(line_direction) < 0: + line_direction = -line_direction + return line_direction + + def get_angle(self, pts_list1: list[np.ndarray], pts_list2: list[np.ndarray]): + """ + The angle between 2 vectors (fitted from 2 point lists), but supplementary. + the vectors share the start point, but to make it fit for scoring, its supplementary is returned. + so a smaller angle means a more straight connection. + + :param pts_list1: a list of 3D points for one branch + :param pts_list2: a list of 3D points for another branch + :return: an angle in arc + """ + vec1 = self.line_fit_pca(pts_list1) * self._res + vec2 = self.line_fit_pca(pts_list2) * self._res + cos = vec1.dot(vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2)) + return math.acos(max(min(cos, 1), -1)) * 180 / math.pi def branch_prune(self, morph, angle_thr=80, radius_amp=1.5): """ @@ -182,19 +168,10 @@ def branch_prune(self, morph, angle_thr=80, radius_amp=1.5): if self._length(cs, morph.pos_dict[n][2:5]) <= self._soma_radius: continue # branch, no soma, away from soma - center, anchor_p, anchor_ch, protrude, rad_p, rad_ch = self._get_anchors(morph, n, self._far_anchor) - _, near_p, near_ch, _, _, _ = self._get_anchors(morph, n, self._near_anchor) - vec_p = np.array(anchor_p) - near_p - vec_ch = np.array(anchor_ch) - near_ch - if self._length(vec_p) < self._eps: - vec_p = np.array(anchor_p) - center - mask = self._length(vec_ch, axis=-1) < self._eps - vec_ch[mask] = vec_ch[mask] - center - - # strange anchor distance can't be considered for pruning - angles = self._vector_angles(vec_p, vec_ch) - radius_p = np.median(rad_p) - radius_ch = np.array([np.median(rad) for rad in rad_ch]) + _, pts_p, pts_ch, protrude, rad_p, rad_ch, _, _ = self._get_anchors(morph, n, self._far_anchor) + angles = np.array([self.get_angle(pts_p, c) for c in pts_ch]) + radius_p = np.mean(rad_p) + radius_ch = np.array([np.mean(rad) for rad in rad_ch]) rm_ind |= set(protrude[(angles < angle_thr) | (radius_ch > radius_p * radius_amp)]) return rm_ind @@ -231,57 +208,96 @@ def _find_mega_crossing(self, morph: Morphology, dist_thr): # merge return [set.union(*[set(t) for t in chains if t[-1] == head]) for head in np.unique([i[-1] for i in chains])] - def crossover_prune(self, morph: Morphology, dist_thr=2., angle_thr=120, check_bif=False): + def crossover_prune(self, morph: Morphology, dist_thr=2., angle_thr1=60, angle_thr2=90, check_bif=False, + no_multi=True, short_tips_thr=5.): """ Prune crossovers by angle. :param morph: morphology wrapped swc tree :param dist_thr: the max distance between nearby branch nodes in a mega crossover. - :param angle_thr: branches less than this angle will only be removed when aligned with another branch + :param angle_thr1: branches less than this angle will take away a best fit branch + :param angle_thr2: branches less than this angle will take away a best fit branch than parent :param check_bif: if checking bifurcation, in this mode only bifurcation will be checked and pruning is not forced. + :param no_multi: ensure no multifurcation, start removing from tips + :param short_tips_thr: drop short tips below this threshold before pruning. :return: nodes to prune. """ crossings = self._find_mega_crossing(morph, dist_thr) cs = np.array(morph.pos_dict[morph.idx_soma][2:5]) if check_bif: - to_check = [i for i in morph.bifurcation - set.union(*crossings) - if self._length(cs, morph.pos_dict[i][2:5]) > self._soma_radius] + if len(crossings) > 0: + crossings = set.union(*crossings) + else: + crossings = set() + to_check = [i for i in morph.bifurcation - crossings if self._length(cs, morph.pos_dict[i][2:5]) > self._soma_radius] else: to_check = crossings rm_ind = set() for x in to_check: # angle - center, anchor_p, anchor_ch, protrude, _, _ = self._get_anchors(morph, x, self._far_anchor) - _, near_p, near_ch, _, _, _ = self._get_anchors(morph, x, self._near_anchor) - vec_p = np.array(anchor_p) - near_p - vec_ch = np.array(anchor_ch) - near_ch - if self._length(vec_p) < self._eps: - vec_p = np.array(anchor_p) - center - mask = self._length(vec_ch, axis=-1) < self._eps - vec_ch[mask] = vec_ch[mask] - center - - angles = self._vector_angles(vec_p, vec_ch) + com_node, pts_p, pts_ch, protrude, rad_p, rad_ch, last_p, last_ch = self._get_anchors(morph, x, self._far_anchor) + rad_p = np.mean(rad_p) + rad_ch = [np.mean(c) for c in rad_ch] + rad_diff = [abs(c - rad_p) for c in rad_ch] + angles = np.array([self.get_angle(pts_p, c) for c in pts_ch]) rm = set() + + for i, c, pts in zip(protrude, last_ch, pts_ch): + if i in rm: + continue + if c in morph.tips: + l = self._length(pts[1:], pts[:-1], axis=1).sum() + if l < short_tips_thr: + rm.add(i) + order = np.argsort(angles) for i in order: # starting from the worst angle if protrude[i] in rm: continue - if angles[i] <= angle_thr: - new_angles = self._vector_angles(vec_ch[i], vec_ch) - for k in np.flip(np.argsort(new_angles ** 2 / angles)): - if new_angles[k] > angles[k] and protrude[k] not in rm: - rm |= set(protrude[[i, k]]) + if angles[i] < angle_thr1: + new_angles = np.array([self.get_angle(pts_ch[i], c) for c in pts_ch]) + for k in np.argsort(angles - new_angles): + new_rad_diff = abs(rad_ch[i] - rad_ch[k]) + if new_rad_diff < rad_diff[k] and protrude[k] not in rm: + rm.add(protrude[i]) + rm.add(protrude[k]) + break + elif angles[i] < angle_thr2: + new_angles = np.array([self.get_angle(pts_ch[i], c) for c in pts_ch]) + for k in np.argsort(angles - new_angles): + new_rad_diff = abs(rad_ch[i] - rad_ch[k]) + if new_angles[k] > angles[k] and new_rad_diff < rad_diff[k] and protrude[k] not in rm: + rm.add(protrude[i]) + rm.add(protrude[k]) + break else: break - left = len(angles) - len(rm) - if left > 2: # ensure bifurcation - for i in order: - if protrude[i] in rm: - continue - rm.add(protrude[i]) - left -= 1 - if left <= 2: - break + if no_multi: + left = len(angles) - len(rm) + if left > 2: # ensure bifurcation + # first, remove short tips + for i, c in zip(protrude, last_ch): + if i in rm: + continue + if c in morph.tips: + rm.add(i) + left -= 1 + if left <= 2: + break + else: + for i in np.argsort(rad_diff)[::-1]: + if protrude[i] in rm: + continue + rm.add(protrude[i]) + left -= 1 + if left <= 2: + break + # remove upstream of protrudes + for i in list(rm): + i = morph.pos_dict[i][6] + while i != -1 and (i in morph.unifurcation or set(morph.child_dict[i]).issubset(rm)): + rm.add(i) + i = morph.pos_dict[i][6] rm_ind |= rm return rm_ind diff --git a/ecut/gcut_utils/distribution.py b/ecut/gcut_utils/distribution.py index 778333f..14605f5 100644 --- a/ecut/gcut_utils/distribution.py +++ b/ecut/gcut_utils/distribution.py @@ -69,8 +69,8 @@ def create_cdf(self): cdf_coefficients = pdf_integral.c cdf_coefficients[-1] = constant self.cdf = np.poly1d(cdf_coefficients) - print('cdf: cdf(0) = {}, cdf(pi) = {}'.format(self.cdf(self.scale(0)), - self.cdf(self.scale(np.pi)))) + # print('cdf: cdf(0) = {}, cdf(pi) = {}'.format(self.cdf(self.scale(0)), + # self.cdf(self.scale(np.pi)))) def scale(self, val): return (val - self._mean) / self._std diff --git a/ecut/graph_cut.py b/ecut/graph_cut.py index 6c5523b..29546ac 100644 --- a/ecut/graph_cut.py +++ b/ecut/graph_cut.py @@ -3,7 +3,7 @@ from .swc_handler import get_child_dict from sklearn.neighbors import KDTree from ._queue import PriorityQueue -from .base_types import BaseCut +from .base_types import BaseCut, ListNeuron from .graph_metrics import EnsembleMetric, EnsembleNode, EnsembleFragment @@ -18,8 +18,8 @@ class ECut(BaseCut): """ - def __init__(self, swc: list[tuple], soma: list[int], children: dict[set] = None, - adjacency: dict[int, set] | float = 5., metric=EnsembleMetric(), *args, **kwargs): + def __init__(self, swc: ListNeuron, soma: list[int], children: dict[set] = None, res=(1., 1., 1.), + adjacency: dict[int, set] | tuple[float] = (5., 10), metric=EnsembleMetric(), *args, **kwargs): """ :param swc: swc tree, whose id should match the line number @@ -28,13 +28,13 @@ def __init__(self, swc: list[tuple], soma: list[int], children: dict[set] = None :param adjacency: close non-connecting neighbours :param metric: the metric to compute the cost on each fragment """ - super().__init__(swc, soma, *args, **kwargs) + super().__init__(swc, soma, res, *args, **kwargs) self._metric = metric self._children = self._get_children() if children is None else children if isinstance(adjacency, dict): self._adjacency = adjacency - else: - self._adjacency = self._get_adjacency(adjacency) + elif isinstance(adjacency, tuple): + self._adjacency = self._get_adjacency(*adjacency) self._end2frag: dict[int, set[int]] | None = None def _get_children(self) -> dict[int, set]: @@ -49,19 +49,26 @@ def _get_children(self) -> dict[int, set]: children[t[0]] = set() return children - def _get_adjacency(self, dist: float) -> dict[int, set]: + def _get_adjacency(self, dist1: float, dist2: float) -> dict[int, set]: """ Generate an adjacency map from the current tree. Parent and children are excluded. - :param dist: the distance threshold to consider connection between 2 nodes. + :param dist1: the distance threshold to consider connection between 2 nodes. + :param dist2: the distance threshold to consider connection between 2 critical nodes. :return: the adjacency dictionary """ - kd = KDTree([t[2:5] for t in self._swc.values()]) + kd = KDTree([np.array(t[2:5]) * self.res for t in self._swc.values()]) + crits = [t for t in self._swc.values() if len(self._children[t[0]]) != 1] + kd_c = KDTree([np.array(t[2:5]) * self.res for t in crits]) keys = np.array(list(self._swc.keys())) - inds, dists = kd.query_radius([t[2:5] for t in self._swc.values()], dist, return_distance=True) + keys_c = np.array([t[0] for t in crits]) + inds = kd.query_radius([np.array(t[2:5]) * self.res for t in self._swc.values()], dist1) + inds_c = kd_c.query_radius([np.array(t[2:5]) * self.res for t in crits], dist2) adjacency = {} - for k, i, d in zip(self._swc.values(), inds, dists): - adjacency[k[0]] = set(keys[i[d < dist]]) - {k[0], k[6]} - self._children[k[0]] + for k, i in zip(self._swc.values(), inds): + adjacency[k[0]] = set(keys[i]) - {k[0], k[6]} - self._children[k[0]] + for k, i in zip(crits, inds_c): + adjacency[k[0]] |= set(keys_c[i]) - {k[0]} # ensure it's undirected graph for k, v in adjacency.items(): for i in v: @@ -124,7 +131,7 @@ def _extract_fragment(self): # and non-connecting but close fragment ends will also be considered for k, v in self._fragment.items(): end1 = v.nodes[0] - v.end1_adj = self._end2frag[end1] - {k} # omit self + v.end1_adj = self._end2frag[end1] - {k} # omit self for i in self._adjacency[end1]: # find adjacent nodes v.end1_adj |= self._end2frag[i] # add any frag related end2 = v.nodes[-1] diff --git a/ecut/graph_metrics.py b/ecut/graph_metrics.py index 067c7cc..3f639d6 100644 --- a/ecut/graph_metrics.py +++ b/ecut/graph_metrics.py @@ -23,8 +23,8 @@ def __init__(self, id): class EnsembleMetric(BaseMetric): - def __init__(self, gof_weight=1., angle_weight=1., radius_weight=1., anchor_dist=20., avg_branch_len=100., - distribution=Distribution(), epsilon=1e-10): + def __init__(self, gof_weight=1., angle_weight=4., radius_weight=2., anchor_dist=20., avg_branch_len=50., + distribution=Distribution(), epsilon=1e-7, soma_radius=20.): """ :param gof_weight: the weight of the global gof metric :param angle_weight: the weight of the local angle metric @@ -41,34 +41,43 @@ def __init__(self, gof_weight=1., angle_weight=1., radius_weight=1., anchor_dist self._anchor_dist = anchor_dist self.avg_branch_len = avg_branch_len self._epsilon = epsilon + self._soma_radius = soma_radius distribution.load_distribution() def init_fragment(self, cut, frag: EnsembleFragment): # calculate path length pts_list = np.array([cut.swc[i][2:5] for i in frag.nodes]) - frag.path_len = np.linalg.norm(pts_list[1:] - pts_list[:-1], axis=1).sum() + frag.path_len = np.linalg.norm((pts_list[1:] - pts_list[:-1]) * cut.res, axis=1).sum() + + def _get_len(self, pts_list, res): + pts_list = np.array(pts_list) + diff = (pts_list[1:] - pts_list[:-1]) * res + return np.linalg.norm(diff, axis=1).sum() def __call__(self, cut, soma, frag_par, frag_ch, reverse): # the angle calculation is based on the pca pc1 of the two point lists # there's a case where pc1 DNE, it returns a vector of (1,0,0) pts_par, radius_par = self._path_upstream(cut, soma, frag_par) pts_ch, radius_ch = self._path_within(cut, frag_ch, not reverse) - angle = self.get_angle(pts_par, pts_ch) / np.pi * self._angle_weight - radius = np.mean(radius_ch) / (np.mean(radius_ch) + np.mean(radius_par)) * self._radius_weight + par_len = self._get_len(pts_par, cut.res) + ch_len = self._get_len(pts_ch, cut.res) + conf = np.sqrt(par_len * ch_len) / self._anchor_dist + angle = self.get_angle(pts_par, pts_ch, cut.res) / np.pi + radius = max(np.mean(radius_ch) - np.mean(radius_par), 0) / np.mean(radius_ch) pts_list = [cut.swc[i][2:5] for i in cut.fragment[frag_ch].nodes] if not reverse: pts_list = pts_list[::-1] frag_node: EnsembleNode = cut.fragment_trees[soma][frag_par] frag: EnsembleFragment = cut.fragment[frag_ch] - ret = {'path_dist': frag_node.path_dist + frag.path_len} - frag_gof = self.get_gof(pts_list, cut.swc[soma][2:5]) - pseudo_order = self.avg_branch_len / ret['path_dist'] # farther branches will be more even in probability - frag_gof_prob = self._distribution.probability(frag_gof) * min(1, np.log(1 + pseudo_order)) + frag_gof = self.get_gof(pts_list, cut.swc[soma][2:5], cut.res) + pseudo_order = frag_node.path_dist / self.avg_branch_len # farther branches will be more even in probability + frag_gof_prob = self._distribution.probability(frag_gof) * min(1, np.log(1 + 1 / (pseudo_order + self._epsilon))) # no suppressing short branches + ret = {'path_dist': frag_node.path_dist + frag.path_len} ret['gof_cost'] = frag_node.gof_cost + (1 - frag_gof_prob) * frag.path_len - ret['cost'] = angle * self._angle_weight + radius * self._radius_weight + \ - ret['gof_cost'] / ret['path_dist'] * self._gof_weight # equals avg gof along the path + ret['cost'] = (angle * self._angle_weight * + radius * self._radius_weight) * conf + \ + ret['gof_cost'] / max(self._soma_radius, ret['path_dist']) * self._gof_weight # equals avg gof along the path return ret def _path_upstream(self, cut, soma: int, frag_id: int): @@ -109,7 +118,7 @@ def _path_within(self, cut, frag_id: int, reverse: bool, path_dist=0., return_di pts_list.append(np.array(cut.swc[i][2:5])) radius_list.append(cut.swc[i][5]) if len(pts_list) > 1: - path_dist += np.linalg.norm(pts_list[-2] - pts_list[-1]) + path_dist += np.linalg.norm((pts_list[-2] - pts_list[-1]) * cut.res) if path_dist > self._anchor_dist: break # stop when exceeding the anchor dist if return_distance: @@ -127,9 +136,12 @@ def line_fit_pca(pts_list: list[np.ndarray]) -> np.ndarray: pca = PCA(n_components=1) pca.fit(pts_list) line_direction = pca.components_[0] + temp = pts_list[-1] - pts_list[0] + if temp.dot(line_direction) < 0: + line_direction = -line_direction return line_direction - def get_angle(self, pts_list1: list[np.ndarray], pts_list2: list[np.ndarray]): + def get_angle(self, pts_list1: list[np.ndarray], pts_list2: list[np.ndarray], res): """ The angle between 2 vectors (fitted from 2 point lists), but supplementary. the vectors share the start point, but to make it fit for scoring, its supplementary is returned. @@ -139,9 +151,9 @@ def get_angle(self, pts_list1: list[np.ndarray], pts_list2: list[np.ndarray]): :param pts_list2: a list of 3D points for another branch :return: an angle in arc """ - vec1 = -EnsembleMetric.line_fit_pca(pts_list1) - vec2 = EnsembleMetric.line_fit_pca(pts_list2) - vec3 = pts_list2[0] - pts_list1[0] + vec1 = -self.line_fit_pca(pts_list1) * res + vec2 = self.line_fit_pca(pts_list2) * res + vec3 = (pts_list2[0] - pts_list1[0]) * res if np.linalg.norm(vec3) > self._epsilon: cos1 = vec1.dot(vec3) / (np.linalg.norm(vec1) * np.linalg.norm(vec3)) cos2 = vec2.dot(vec3) / (np.linalg.norm(vec2) * np.linalg.norm(vec3)) @@ -150,12 +162,12 @@ def get_angle(self, pts_list1: list[np.ndarray], pts_list2: list[np.ndarray]): cos = vec1.dot(vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2)) return np.arccos(np.clip(cos, -1, 1)) - def get_gof(self, pts_list: list[tuple[float, float, float]], soma: np.ndarray) -> float: + def get_gof(self, pts_list: list[tuple[float, float, float]], soma: np.ndarray, res) -> float: pts_list = np.array(pts_list) - tan = pts_list[1:] - pts_list[:-1] + tan = (pts_list[1:] - pts_list[:-1]) * res tan_norm = np.linalg.norm(tan, axis=1, keepdims=True) tan /= tan_norm + self._epsilon - ps = pts_list[:-1] - soma + ps = (pts_list[:-1] - soma) * res ps_norm = np.linalg.norm(ps, axis=1, keepdims=True) ps /= ps_norm + self._epsilon proj = np.clip(np.sum(tan * ps, axis=1), -1, 1) diff --git a/ecut/soma_detection.py b/ecut/soma_detection.py index c72e439..ef321e8 100644 --- a/ecut/soma_detection.py +++ b/ecut/soma_detection.py @@ -97,7 +97,7 @@ def predict(self, img, res: list[float], thr=None) -> list[np.ndarray]: class DetectTiledImage: def __init__(self, tile_size=(256, 256, 64), omit_border=(16, 16, 4), merge_dist=15, - base_detector=DetectImage(), nproc=1): + base_detector=DetectImage(), nproc=None): """ :param tile_size: The size of a single tile, indexed by x, y, z. @@ -133,19 +133,28 @@ def predict(self, img: np.ndarray, res: list[float]) -> list[np.ndarray]: x = np.linspace(hf[2], img.shape[2] - hf[2], steps[2], dtype=int) jobs = [] - with Pool(self._nproc) as p: + prefilter = [] + if self._nproc is not None: + with Pool(self._nproc) as p: + for zz in z: + for yy in y: + for xx in x: + s = (zz, yy, xx) - hf + e = (zz, yy, xx) + hf + tile = img[s[0]: e[0], s[1]: e[1], s[2]: e[2]] + jobs.append(p.apply_async(DetectTiledImage.process_find_soma, + (self._find_soma, tile, res, thr, s, self._omit_border, self._tile_size))) + + for i in tqdm(jobs): + prefilter.extend(i.get()) + else: for zz in z: for yy in y: for xx in x: s = (zz, yy, xx) - hf e = (zz, yy, xx) + hf tile = img[s[0]: e[0], s[1]: e[1], s[2]: e[2]] - jobs.append(p.apply_async(DetectTiledImage.process_find_soma, - (self._find_soma, tile, res, thr, s, self._omit_border, self._tile_size))) - - prefilter = [] - for i in tqdm(jobs): - prefilter.extend(i.get()) + prefilter.extend(self.process_find_soma(self._find_soma, tile, res, thr, s, self._omit_border, self._tile_size)) if len(prefilter) == 0: return [] @@ -156,7 +165,7 @@ def predict(self, img: np.ndarray, res: list[float]) -> list[np.ndarray]: class DetectTracingMask: - def __init__(self, min_radius=2, merge_dist=15, diam_range=(5, 20)): + def __init__(self, min_radius=2., merge_dist=15., diam_range=(5., 20.)): """ :param min_radius: the minimum radius of the swc nodes to consider, in micrometer @@ -178,7 +187,8 @@ def predict(self, swc: ListNeuron, res: list[float]) -> list[np.ndarray]: candid = [t for t in swc if t[5] * sf >= self._min_radius] pos = np.array([t[2:5] for t in candid]) rad = np.array([t[5] for t in candid]) - + if len(pos) == 0: + return [] db = DBSCAN(self._merge_dist, min_samples=1) db.fit(pos * res) labels = db.labels_ # Get cluster labels. diff --git a/ecut/swc_handler.py b/ecut/swc_handler.py index 384958c..b0ceecd 100644 --- a/ecut/swc_handler.py +++ b/ecut/swc_handler.py @@ -10,6 +10,7 @@ import re import numpy as np from copy import deepcopy +from queue import SimpleQueue NEURITE_TYPES = { @@ -385,3 +386,31 @@ def get_soma_from_swc(swcfile): soma_str = re.search('.* -1\n', fp.read()).group() soma = soma_str.split() return soma + + +def sort_swc(tree: list, root=1): + ch_dict = get_child_dict(tree) + ind = get_index_dict(tree) + count = 1 + temp = list(tree[ind[root]]) + temp[6] = -1 + temp[0] = count + new_tree = [tuple(temp)] + new_dict = {root: 1} + + q = SimpleQueue() + q.put_nowait(root) + + while not q.empty(): + head = tree[ind[q.get_nowait()]][0] + if head in ch_dict: + for i in ch_dict[head]: + count += 1 + temp = list(tree[ind[i]]) + temp[0] = count + temp[6] = new_dict[head] + new_tree.append(tuple(temp)) + new_dict[i] = count + q.put_nowait(i) + return new_tree + diff --git a/example/app2_processing.py b/example/app2_processing.py index 272a977..0896df4 100644 --- a/example/app2_processing.py +++ b/example/app2_processing.py @@ -9,33 +9,36 @@ if __name__ == '__main__': # tree = swc_handler.parse_swc('../test/data/gcut_input.swc_sorted.swc') - tree = swc_handler.parse_swc(r'D:\rectify\my_app2\18452_26569_3425_5509.swc') - - # detect soma - d = DetectTracingMask(5) - soma = d.predict(tree, [.3, .3, 1.]) + tree = swc_handler.parse_swc(r'D:\rectify\my_app2\17302_14358_42117_2799.swc') + tree = [t for t in tree if not (t[1] == t[2] == t[3] == 0)] + # tree = swc_handler.parse_swc(r'D:\rectify\my_app2\15257_16445_16836_4489.swc') + maxr = max([t[5] for t in tree]) * .3 + rad = max(maxr * .5, 5.) + centers = DetectTracingMask(rad, 20.).predict(tree, [.3, .3, 1]) # anneal a = MorphAnneal(tree) tree = a.run() + # graph cut + if len(centers) < 1: + centers = [[512, 512, 128]] kd = KDTree([t[2:5] for t in tree]) - inds = kd.query(soma, return_distance=False) + inds = kd.query(centers, return_distance=False) inds = [tree[i[0]][0] for i in inds] print(inds) - - # graph cut e = ECut(tree, inds) e.run() trees = e.export_swc() - # pruning for k, v in trees.items(): - p = ErrorPruning([.25, .25, 1], anchor_reach=(5., 20.)) + v = swc_handler.sort_swc(v) + p = ErrorPruning([.3,.3,1], anchor_dist=20., soma_radius=10.) morph = Morphology(v) - a = p.branch_prune(morph, 45, 2) - b = p.crossover_prune(morph, 5, 90) - # c = p.crossover_prune(morph, check_bif=True) - t = swc_handler.prune(v, a | b) - swc_handler.write_swc(t, f'../test/data/ whole_{k}.swc') \ No newline at end of file + a = p.branch_prune(morph, 60, 1.5) + b = p.crossover_prune(morph, 2, 60, 90, short_tips_thr=10., no_multi=False) + c = p.crossover_prune(morph, 2, 60, 90, check_bif=True, short_tips_thr=10.) + v = swc_handler.prune(v, a | b | c) + swc_handler.write_swc(v, f'../test/data/multi_{k}.swc') + \ No newline at end of file diff --git a/example/batch_1891.py b/example/batch_1891.py index 47a8fbf..31d0eef 100644 --- a/example/batch_1891.py +++ b/example/batch_1891.py @@ -1,63 +1,63 @@ -from ecut import swc_handler -from ecut.annealing import MorphAnneal -from ecut.graph_cut import ECut -from ecut.soma_detection import DetectTracingMask -from sklearn.neighbors import KDTree -from ecut.error_prune import ErrorPruning -from ecut.morphology import Morphology -from traceback import print_exc - - -def main(args): - in_path, out_path = args - try: - tree = [t for t in swc_handler.parse_swc(in_path) if not (t[1] == t[2] == t[3] == 0)] - - # detect soma - d = DetectTracingMask(3) - soma = d.predict(tree, [.3, .3, 1]) - - # anneal - a = MorphAnneal(tree) - tree = a.run() - - # map soma - kd = KDTree([t[2:5] for t in tree]) - inds = kd.query(soma, return_distance=False) - inds = [tree[i[0]][0] for i in inds] - - # graph cut - if len(inds) > 1: - e = ECut(tree, inds) - e.run() - trees = e.export_swc() - else: - trees = {0: tree} - - # pruning - for k, v in trees.items(): - p = ErrorPruning([.3, .3, 1], anchor_reach=(5., 20.)) - morph = Morphology(v) - a = p.branch_prune(morph, 45, 2) - b = p.crossover_prune(morph, 5, 90) - # c = p.crossover_prune(morph, check_bif=True) - t = swc_handler.prune(v, a | b) - swc_handler.write_swc(t, str(out_path) + f'_{k}.swc') - except: - print_exc() - print(in_path) - - -if __name__ == '__main__': - from pathlib import Path - from tqdm import tqdm - from multiprocessing import Pool - indir = Path('D:/rectify/my_app2') - outdir = Path('D:/rectify/pruned') - outdir.mkdir(exist_ok=True) - files = sorted(indir.glob('*.swc')) - outfiles = [outdir / f.name for f in files] - arglist = [*zip(files, outfiles)] - with Pool(12) as p: - for i in tqdm(p.imap(main, arglist), total=len(arglist)): +from ecut import swc_handler +from ecut.annealing import MorphAnneal +from ecut.graph_cut import ECut +from ecut.soma_detection import DetectTracingMask +from sklearn.neighbors import KDTree +from ecut.error_prune import ErrorPruning +from ecut.morphology import Morphology +from traceback import print_exc + + +def main(args): + in_path, out_path = args + try: + tree = [t for t in swc_handler.parse_swc(in_path) if not (t[1] == t[2] == t[3] == 0)] + + # detect soma + d = DetectTracingMask(3) + soma = d.predict(tree, [.3, .3, 1]) + + # anneal + a = MorphAnneal(tree) + tree = a.run() + + # map soma + kd = KDTree([t[2:5] for t in tree]) + inds = kd.query(soma, return_distance=False) + inds = [tree[i[0]][0] for i in inds] + + # graph cut + if len(inds) > 1: + e = ECut(tree, inds) + e.run() + trees = e.export_swc() + else: + trees = {0: tree} + + # pruning + for k, v in trees.items(): + p = ErrorPruning([.3, .3, 1], anchor_reach=(5., 20.)) + morph = Morphology(v) + a = p.branch_prune(morph, 45, 2) + b = p.crossover_prune(morph, 5, 90) + # c = p.crossover_prune(morph, check_bif=True) + t = swc_handler.prune(v, a | b) + swc_handler.write_swc(t, str(out_path) + f'_{k}.swc') + except: + print_exc() + print(in_path) + + +if __name__ == '__main__': + from pathlib import Path + from tqdm import tqdm + from multiprocessing import Pool + indir = Path('D:/rectify/my_app2') + outdir = Path('D:/rectify/pruned') + outdir.mkdir(exist_ok=True) + files = sorted(indir.glob('*.swc')) + outfiles = [outdir / f.name for f in files] + arglist = [*zip(files, outfiles)] + with Pool(12) as p: + for i in tqdm(p.imap(main, arglist), total=len(arglist)): pass \ No newline at end of file diff --git a/experiment/batch_1891.py b/experiment/batch_1891.py new file mode 100644 index 0000000..f591c84 --- /dev/null +++ b/experiment/batch_1891.py @@ -0,0 +1,82 @@ +from ecut import swc_handler +from ecut.annealing import MorphAnneal +from ecut.graph_cut import ECut +from ecut.soma_detection import * +from sklearn.neighbors import KDTree +from ecut.error_prune import ErrorPruning +from ecut.morphology import Morphology +from traceback import print_exc +from pathlib import Path +from v3dpy.loaders import PBD + +img_dir = Path(r"D:\rectify\crop_8bit") + + +def main(args): + in_path, out_path = args + try: + tree = [t for t in swc_handler.parse_swc(in_path) if not (t[1] == t[2] == t[3] == 0)] + res = [.3, .3, 1.] + + # detect soma + # img = PBD().load(r"D:\rectify\crop_8bit\18453_9442_3817_6561.v3dpbd")[0] + # centers_list = [] + # centers = DetectImage().predict(img, res) + # centers_list.append(centers) + # centers = DetectTiledImage([300, 300, 200]).predict(img, res) + # centers_list.append(centers) + # maxr = max([t[5] for t in tree]) * res[0] + # centers = DetectTracingMask(maxr * .75, maxr * 3).predict(tree, res) + # centers_list.append(centers) + # centers = DetectDistanceTransform().predict(img, res) + # centers_list.append(centers) + # centers = DetectTiledImage(base_detector=DetectDistanceTransform()).predict(img, res) + # centers_list.append(centers) + # centers = soma_consensus(*centers_list, res=res) + + maxr = max([t[5] for t in tree]) * res[0] + rad = max(maxr * .5, 5.) + centers = DetectTracingMask(rad, 20.).predict(tree, res) + + # anneal + a = MorphAnneal(tree) + tree = a.run() + + # graph cut + if len(centers) < 1: + centers = [[512, 512, 128]] + kd = KDTree([t[2:5] for t in tree]) + inds = kd.query(centers, return_distance=False) + inds = [tree[i[0]][0] for i in inds] + e = ECut(tree, inds) + e.run() + trees = e.export_swc() + + # pruning + for k, v in trees.items(): + v = swc_handler.sort_swc(v) + # p = ErrorPruning(res, anchor_dist=20., soma_radius=10.) + # morph = Morphology(v) + # a = p.branch_prune(morph, 60, 1.5) + # b = p.crossover_prune(morph, 2, 60, 90, short_tips_thr=10., no_multi=False) + # c = p.crossover_prune(morph, 2, 60, 90, check_bif=True, short_tips_thr=10.) + # v = swc_handler.prune(v, a | b | c) + swc_handler.write_swc(v, str(out_path) + f'_{k}.swc') + except: + print_exc() + print(in_path) + + +if __name__ == '__main__': + from tqdm import tqdm + from multiprocessing import Pool + + indir = Path('D:/rectify/my_app2') + outdir = Path('D:/rectify/pruned_3') + outdir.mkdir(exist_ok=True) + files = sorted(indir.glob('*.swc')) + outfiles = [outdir / f.name for f in files] + arglist = [*zip(files, outfiles)] + with Pool(12) as p: + for i in tqdm(p.imap(main, arglist), total=len(arglist)): + pass \ No newline at end of file diff --git a/experiment/eval.py b/experiment/eval.py new file mode 100644 index 0000000..28d77cb --- /dev/null +++ b/experiment/eval.py @@ -0,0 +1,85 @@ +""" +Comparing APP2 reconstruction before and after pruning + +categorized as sparse (863) and dense image blocks, using the tracing result of NIEND enhanced images. + +Here are the pairs: + +sparse against GS +sparse prune against GS +dense against GS +dense prune against GS + +""" + +# evaluate different reconstruction against gold standard + +from utils.metrics import DistanceEvaluation +import pandas as pd +from pathlib import Path +import sys +import os + + +wkdir = Path(r"D:\rectify") +pruned_path = wkdir / 'pruned_3' + + + +class HidePrint: + def __enter__(self): + self._original_stdout = sys.stdout + sys.stdout = open(os.devnull, 'w') + + def __exit__(self, exc_type, exc_val, exc_tb): + sys.stdout.close() + sys.stdout = self._original_stdout + + +def main(name): + man = wkdir / 'manual' / name + before = wkdir / 'my_app2' / name + afters = pruned_path.glob(f'{before.stem}*') + de = DistanceEvaluation(15) + with HidePrint(): + before = de.run(before, man) if before.exists() else None + ret = { + 'before_recall': 1 - before[2, 1] if before is not None else 0, + 'before_precision': 1 - before[2, 0] if before is not None else 1, + } + for after in afters: + after = de.run(after, man) if after.exists() else None + with HidePrint(): + recall = 1 - after[2, 1] if after is not None else 0 + precision = 1 - after[2, 0] if after is not None else 1 + if 'after_recall' not in ret or ret['after_recall'] < recall: + ret['after_recall'] = recall + ret['after_precision'] = precision + return ret + + +if __name__ == '__main__': + from multiprocessing import Pool + from tqdm import tqdm + + # get the sparse and dense labels + files = [i.name for i in (wkdir / 'manual').glob('*.swc')] + tab = pd.read_csv(wkdir / 'filter.csv', index_col=0) + + # main('18457_14455_13499_5478.swc') + with Pool(14) as p: + # sparse + sparse = [*filter(lambda f: tab.at[f, 'sparse'] == 1, files)] + res = [] + for r in tqdm(p.imap(main, sparse), total=len(sparse)): + res.append(r) + pd.DataFrame.from_records(res, index=sparse).to_csv('../results/eval_sparse.csv') + + # dense + dense = [*filter(lambda f: tab.at[f, 'sparse'] == 0, files)] + res = [] + for r in tqdm(p.imap(main, dense), total=len(dense)): + res.append(r) + pd.DataFrame.from_records(res, index=dense).to_csv('../results/eval_dense.csv') + + diff --git a/experiment/plot.ipynb b/experiment/plot.ipynb new file mode 100644 index 0000000..394725f --- /dev/null +++ b/experiment/plot.ipynb @@ -0,0 +1,190 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Plot precision & recall" + ], + "metadata": { + "collapsed": false + }, + "id": "8348ff5edb3bd621" + }, + { + "cell_type": "code", + "source": [ + "import pandas as pd\n", + "from matplotlib import ticker\n", + "import seaborn as sns\n", + "\n", + "\n", + "df1 = pd.read_csv(\"../results/eval_sparse.csv\", index_col=0)\n", + "df2 = pd.read_csv(\"../results/eval_dense.csv\", index_col=0)\n", + "df1['before_f1'] = 2 * (df1['before_precision'] * df1['before_recall']) / (df1['before_precision'] + df1['before_recall'] + .00001)\n", + "df1['after_f1'] = 2 * (df1['after_precision'] * df1['after_recall']) / (df1['after_precision'] + df1['after_recall'] + .00001)\n", + "df2['before_f1'] = 2 * (df2['before_precision'] * df2['before_recall']) / (df2['before_precision'] + df2['before_recall'] + .00001)\n", + "df2['after_f1'] = 2 * (df2['after_precision'] * df2['after_recall']) / (df2['after_precision'] + df2['after_recall'] + .00001)\n", + "df1['type'] = 'sparse'\n", + "df2['type'] = 'dense'\n", + "df = pd.concat([df1, df2], axis=0)\n", + "prec = df.reset_index().melt(id_vars=['index', 'type'], var_name='stat', value_name='value', value_vars=['before_precision', 'after_precision'])\n", + "recall = df.reset_index().melt(id_vars=['index', 'type'], var_name='stat', value_name='value', value_vars=['before_recall', 'after_recall'])\n", + "f1 = df.reset_index().melt(id_vars=['index', 'type'], var_name='stat', value_name='value', value_vars=['before_f1', 'after_f1'])\n", + "\n", + "\n", + "def convert_pvalue_to_asterisks(pvalue):\n", + " if pvalue <= 0.0001:\n", + " return \"****\"\n", + " elif pvalue <= 0.001:\n", + " return \"***\"\n", + " elif pvalue <= 0.01:\n", + " return \"**\"\n", + " elif pvalue <= 0.05:\n", + " return \"*\"\n", + " return \"ns\"\n", + "\n" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-04-24T11:27:36.002419Z", + "start_time": "2024-04-24T11:27:35.974900Z" + } + }, + "id": "41ba084b8b450e5c", + "outputs": [], + "execution_count": 5 + }, + { + "cell_type": "code", + "source": [ + "from scipy.stats import ttest_ind\n", + "\n", + "def test(ax, x1, x2, y, h, fs, a, b):\n", + " ax.plot([x1, x1, x2, x2], [y, y+h, y+h, y], lw=1, c=\"k\")\n", + " stat,p_value = ttest_ind(a, b)\n", + " ax.text((x1+x2)*.5, y - h, convert_pvalue_to_asterisks(p_value), ha='center', va='bottom', color=\"k\")\n", + "\n", + "# plot precision\n", + "sns.set(font_scale=1, style='white')\n", + "import matplotlib.pyplot as plt\n", + "fig, axs = plt.subplots(1, 3, figsize=(12, 4), dpi=300)\n", + "plt.subplots_adjust(wspace=0.4)\n", + "sns.despine(fig, top=True, right=True)\n", + "\n", + "# precision\n", + "ax = sns.barplot(data=prec, hue='stat', x='type', y='value', ax=axs[1], legend=False)\n", + "ax.set_xticks([*range(2)], ['Sparse', 'Dense'])\n", + "ax.set_ylabel(None)\n", + "ax.set_title('Precision')\n", + "ax.yaxis.set_major_formatter(ticker.PercentFormatter(xmax=1, decimals=0))\n", + "ax.tick_params(left=True, direction='out')\n", + "ax.set_xlabel(None)\n", + "# test(ax, 0, 4, 1.15 - .05, .02, 'small', df['before_precision'], df['my_precision'])\n", + "# test(ax, 3, 4, 1.15 - .12, .02, 'small', df['after_precision'], df['my_precision'])\n", + "# ax.text(0, m['raw_precision']+.03, f\"{m['raw_precision']*100:.1f}%\", ha='center', va='bottom', fontsize=15)\n", + "# ax.text(1, m['ada_precision']+.03, f\"{m['ada_precision']*100:.1f}%\", ha='center', va='bottom', fontsize=15)\n", + "# ax.text(2, m['mul_precision']+.03, f\"{m['mul_precision']*100:.1f}%\", ha='center', va='bottom', fontsize=15)\n", + "# ax.text(3, m['guo_precision']+.03, f\"{m['guo_precision']*100:.1f}%\", ha='center', va='bottom', fontsize=15)\n", + "# ax.text(4, m['my_precision']+.03, f\"{m['my_precision']*100:.1f}%\", ha='center', va='bottom', fontsize=15)\n", + "\n", + "# recall\n", + "ax = sns.barplot(data=recall, hue='stat', x='type', y='value', ax=axs[2])\n", + "ax.set_xticks([*range(2)], ['Sparse', 'Dense'])\n", + "ax.set_ylabel(None)\n", + "ax.set_title('Recall')\n", + "ax.yaxis.set_major_formatter(ticker.PercentFormatter(xmax=1, decimals=0))\n", + "ax.tick_params(left=True, direction='out')\n", + "ax.set_xlabel(None)\n", + "\n", + "# f1\n", + "ax = sns.barplot(data=f1, hue='stat', x='type', y='value', ax=axs[0], legend=False)\n", + "ax.set_xticks([*range(2)], ['Sparse', 'Dense'])\n", + "ax.set_ylabel(None)\n", + "ax.set_title('F1')\n", + "ax.set_ylim(0, 1)\n", + "ax.tick_params(left=True, direction='out')\n", + "ax.set_xlabel(None)\n", + "handles, labels = plt.gca().get_legend_handles_labels()\n", + "plt.legend(handles=handles[:2], labels=['Before', 'After'], loc='lower right')" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-04-24T12:47:04.832898Z", + "start_time": "2024-04-24T12:47:03.934179Z" + } + }, + "id": "2883af7f9a2b9634", + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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qFEjVa1a1ZCLjo5WVFSUkzoCAAAAAABlycMPP2yT++OPP5zQCQAAhefq7AYAAMCtc+3aNb3yyis5XmvVqpXef/99nT59upi7AgAAAAAgd3Xq1FFkZKQhFxUVpeDgYLv3+PPPP7Vnzx7FxsaqSpUquvfeex1an5KSogMHDigqKkpXrlyRyWRS+fLlFRoaqsaNG8vd3d3uvexx+fJlHT16VOfOnVNiYqK8vLwUEBCgOnXqqHbt2jKbzUV6v9zExMTo4MGDunDhghISEuTh4aGAgABVqFBBjRs3lq+vb7H0kV1CQoL279+vqKgoxcbGys3NTeXLl1eVKlVuyb+PG06dOqWjR48qKipKFotFQUFBqlKlipo1ayYPD49bck8AAAAAgHPl9PwgISHB4X3S09P122+/6cKFC7p69aoyMjJUoUIFBQcHq2nTpvLy8iqKdq1iY2N19OhR/fnnn0pISJC7u7v8/f111113qV69ekXy3vncuXM6efKk4uLiFB8fr5SUFHl4eCgwMFChoaGqX7++054dAAByxpA4AABljJ+fn95++211795dkhgSBwAAAACUKDl9SGqxWAzxmjVrNHr0aGvcv39/jRkzRunp6Zo0aZJWrlxpWGMymdS6dWu9+eabqlWrVq73/vnnn7VgwQL98ssvSk1NzbHG29tbDz30kIYOHap69eo5+udZpaWl6euvv9YXX3yR589W+/n5qWvXrhowYICqVKmS5579+vXT7t27rfHTTz+tKVOm5LkmPT1dq1at0po1a3To0KFc68xmsxo1aqTHH39cPXr0kLe3d577StKoUaO0du1aa9yiRQstXrw433XS3//Ov/vuOy1atEgHDx60+e/ADeXKlVOrVq300ksvqWnTpnbtLUlt2rTR+fPnrfGePXvk5+enjIwMrVy5Up9//rnOnj2b6z1bt26t4cOHq0aNGnbfEwAAAABQ8sXGxtrk7HkPfMPhw4c1f/587dixQ0lJSTnWeHh4qGXLlnrppZfUsmXLAveamZmp7777TsuWLdOvv/6a63tnLy8vderUSQMHDtRdd93l0D327t2r1atX65dfftGFCxfyrDWbzWrWrJn69u2rDh06yMXFxaF7AQCKHkPiAACUEW5uburVq5eGDRum8uXLO7sdAAAAAAByFB8fb5Oz933su+++qzVr1tjkMzMz9cMPP6h+/fo5DolHRUVpzJgx+vHHH/O9R1JSkjZt2qTvv/9eXbt21bvvvuvw6V87d+7UmDFjDEPKubl27ZoWLVqk1atX61//+pd69Ojh0L3ycvr0aQ0bNkxnzpzJt9ZisWj//v3av3+//ve//+m9997TI488UmS93Oz48eMaOXKkTp48mW9tcnKyNm/erM2bN6tt27aaOHFigZ97nD9/XsOHD9eRI0fyveeGDRv0/fffa8SIERo4cGCB7gcAAAAAKHmOHz9uk6tcuXK+6xISEjRu3Dht2LBBWVlZedampqZq+/bt2r59u1q3bq1JkyY5/F726NGjGj16dI79ZpeSkqIvv/xS33zzjYYNG6Zhw4blu+bSpUsaM2aMduzYYXdPFotFu3fv1u7du3Xvvfdq5syZqlChgt3rAQBFz+TsBgAAwK1Vrlw59e3bVxs3btQ777zDgDgAAAAAoMTKysqyGdB1dXVVpUqV8l27adOmHAfEb9axY0eb3IkTJ9SzZ0+7BsRvlpmZqTVr1qh///66evWq3euWLl2qAQMG2DUgfrPk5GS98847mjdvnkPrcvPnn3+qd+/edg2IZxcdHa2XX35ZW7ZsKZJebrZu3Tp1797drgHx7DZv3qyePXvq1KlTDq+9cOGC+vbtm++A+M3S09M1depULVu2zOH7AQAAAABKnsuXL+uHH34w5Ewmk5o3b57nuosXL6pPnz5av359vgPi2W3dulW9evXSn3/+afeazZs369lnn7VrQPxm6enp+uijj/Tuu+/mWXf69Gk988wzDg2IZ7dnzx4NHjxYGRkZBd4DAFB4nCQOAEAp5u3trR9//FE+Pj7ObgUAAAAAgHxt375dMTExhlyjRo3yPak7NTVVU6ZMybOmdu3aqlmzpiF3/vx59evXz+b0cn9/f3Xp0kUtWrRQSEiILBaLLl68qB9//FGbNm1ScnKytfbgwYMaNGiQVqxYIXd39zx72LhxoyZMmGCTDwoK0hNPPKGWLVuqUqVKyszM1IkTJ7R27Vrt37/fUPvhhx+qbt26hTrFOysrS//4xz8UFxdnyDdp0kQdO3ZUrVq15O/vLxcXF125ckX79+/Xl19+qcuXL1trLRaLxowZo4iIiCJ77rBjxw6NHj1a6enphryfn586deqkBx54QCEhIUpLS1NkZKS2bNmizZs3G35O+9y5c+rfv7/WrFmj4OBgu+/95ptv6uLFi9a4RYsWat++verWrSsvLy9dunRJO3bs0Ndff2349y9J06ZNU4cOHRQUFFTAvxwAAAAA4GwWi0XvvvuuzXvSRx55RAEBAbmuS0hIUL9+/XTu3DlD3tPTU507d1arVq1UpUoVmc1mRUVFaefOndqwYYPhPfnZs2c1YMAAffXVV/L398+zz3379un111+3Gb729vbWE088oQceeMB68vmZM2e0YcMGbd++3VC7cuVK1a1bV3369LHZ//r163rllVd05coVQz4wMFCdO3dW06ZNVaVKFXl5eSkpKUkXL17Uvn37bP4mSTpy5IiWL1+ufv365fk3AQBuHYbEAQAoxcxmMwPiAAAAAIDbQkJCgqZOnWqTb9u2bb5rN2zYoMTERGt89913q0mTJrJYLDp58qT27t1rc4p4enq6RowYYTMg3qNHD40aNcrm/XTTpk3VqVMnvfHGGxozZozh5PEjR47o/fff1zvvvJNrj9HR0Tle79mzp95++22b+zVp0kTPPvusli5dqvDwcGVmZlqvjR07Vps3b853KD03u3bt0sGDBw25t99+WwMHDsyx/pFHHtFLL72kkSNHauvWrdZ8TEyM9TT1wrp48aKGDx9u82F8x44d9e6779r8Mlrz5s3VtWtXHT9+XP/85z914sQJ67UrV67ojTfe0OLFi+Xqat/HIKdPn5YkVaxYUVOmTNGDDz5ouN6wYUO1bdtWL774ooYOHao//vjDei0pKUlffPGFXn75ZYf+ZgAAAABAyRAdHa2JEyca3vNKf3/e/tprr+W5dsyYMTYD4q1bt1Z4eLgqVKhgU9+uXTu9/vrrmjRpkr766itr/sKFCxo1apTmzJmT671SU1P11ltv2QyIt2nTRhMmTFDFihUN+caNG6tr166KiIjQW2+9pdTUVOu1999/X48//rjN++1FixZZ3yPf8PDDD+vDDz+Ur69vjn117txZI0aM0NixY/Xtt98arq1Zs4YhcQBwIobEAQBwgvPnz6tOnTqF2uOxxx7T7Nmzi6gjAAAAAACc5+zZsxo5cqROnTplyAcEBKhXr175rr8xIO7p6alp06apXbt2hutnzpyRt7e3Ibdy5UodOHDAkHvllVfy/fA3ODhYc+fO1YgRI/Tdd99Z80uWLFGfPn1055135rhuzpw5hkF2SRo4cKDefvvtPO/Xt29fXbt2TTNn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XoU1jz/+eJxyyinx9NNPx4IFC2LdunWxbNmyeO+992LYsGHxs5/9LObPn1+ncwEA8P3WpklmZDXadAA8q1FatG26+R9RAgAAAPD94CRxAAAAIBo3blzh2iGHHBK5ubnV7nHUUUfFddddl6jz8/Pjyy+/jJ122ikivg2dl7dmzZoaz7l69eqkumnTpjXusSnlTxHv379/tG3bNun+xIkTY9iwYVFWVpa41q5du0RQPCLiiy++iIEDB8a//vWvSk8h3xKDBg2K/v3710mv8vLz8+Pkk0+u874AANStRulp0b1NTsxYVPX/D929TU6kpzlJHKC8Tz/9NGbMmBFLly6Nli1bRq9evaJ79+5b3O/tt9+Od955JyIiOnbsGP369aurUQEAAOqckDgAAAAQrVq1qnCtR48eNerRunXr6NChQ3z99deJa19//XUiJL7xeyxfvrzGc268p65C2BERH374YbzxxhsR8e0J5RdccEHS/bVr18bw4cMTAfHOnTvHqFGjokePHlFaWhovvvhi/P73v4+ioqLIy8uLm2++OUaNGlUns+Xm5tYosA8AQOrpt2ebWLD8qyhYu77CvVaNM6Lfnm3qYSoglZWWlsbUqVNj/PjxMW3atMjLy4tly5ZFbm5udOjQIfbff/846aSTavz5wQZFRUXx0ksvxaRJk2LmzJlRUFAQxcXF0apVq9htt93iiCOOiL59+27Rv4fnzp0bQ4YMiQ8++KDCvV69esVNN90Uu+++e416lpSUxPXXXx/z5s2LiIgRI0bUeC4AAIBtSUgcAAAAiNatW0d6enqUlpYmrrVs2bLGfVq1apUUEi8oKEi87tChQ9LaJUuW1Lj/4sWLk+qNT/qujdGjRyde9+vXL9q1a5d0f/z48Ym/LT09PcaMGZM4fSw9PT1OPPHEWL58eQwbNiwiIl555ZVYsGBBdOrUqc5mBADg+6tpVqMYfED7eG3Osnj/q1VRUhbRKC2i9w65cVTXltE0q1F9jwikkJkzZ8YNN9wQH330UYV7BQUFUVBQEB9//HE88sgjceSRR8aIESNq9EPuN954I6699tr45ptvKtzLy8uLvLy8eOONN+Luu++O4cOHR58+farde+7cuTFgwIDEE782Nm3atDj99NPjnnvuiYMOOqjafV944YVEQLxjx47Rt2/fau8FAACoD+n1PQAAAABQ/zIzMyuE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+x9//PFJ9YQJEypdVz6EHhHRpEmTKns2btw48XrVqlVVrit/ivgvfvGLpH0AAAANXcbmlwAAUBeKi4vrdB0AbA1ZWVlx2223xTnnnBNFRUUR8e1JXKeffnoMGjQofv7zn0fnzp0jImL+/Pnx5JNPxsMPP5z0f79atmwZv//976t8jz59+sThhx+e9AXxzTffHO+//36cf/75sfvuu0daWlrMmzcvHnvssXj88cejtLQ0sbZnz55x2mmn1fpvLX+KeJ8+fWLXXXfd5PoePXrEN998ExER//73v2PdunUVHjE9ffr0yMvLS9S9evWq9ZwAAACwNb355puxfv36RH388cdHRkb1owT77bdf7Lfffptc88477yTVBx54YLX79+7dOzIyMhIzTp8+PQoKCqJVq1ZJ6zYOcJf/LGFj5T/HyMzMrHTNa6+9Fp988klERLRq1SoGDhxY7ZkBAAAaAieJAwAAAEl69+4do0ePTgpAr1mzJsaMGRNHH3109O7dO/bdd984+uij489//nPSF6vZ2dkxatSo6Nix4ybf4w9/+EPstttuSdfGjRsXffv2jZ49e0avXr2iT58+8dhjjyV9qbv99tvHnXfeWaMvqyvzxRdfxMsvvxwREWlpaRVON69Mnz59Eq+XLFkSt912W9L9wsLCGDFiRKLOzMys0cloAAAAUB8++OCDpPqggw6q0/6LFi2KgoKCRJ2ZmRndunWr9v7s7OzYZZddEnVZWVl8/PHHFdY1b948qV6xYkWl/UpLS5PuNWvWrNJ15U8RP/fcczd5MjkAAEBDJCQOAAAAVHD44YfHX/7yl+jSpUuFe6tXr67wCOeIiHbt2sXYsWOr9WVyy5Yt4+GHH4599923wr2ioqIoLCyscH3nnXeORx99NDp16lTNv6Jq9957byJ8ftRRR0X37t03u+fYY4+NDh06JOrHHnsszjjjjBg7dmyMHj06TjrppJg2bVrift++fWP77bev9awAAACwNX300UdJ9Z577lmn/efMmZNUd+7cucY//t7484m5c+dWWFM+SF7Z+24wb968pB+8b7wvImLKlCkxc+bMiIho0aJFnHnmmTWaFwAAoCGo3bFbAAAAQMraf//9Y9y4cfGXv/wlnnvuuZg3b16l67bbbrs49dRT4/zzz6/y9K2q9v3tb3+LJ598Mh555JFKv+DdsO7000+PCy64oMKjo7fEvHnz4sUXX0zU1TlFPOLbx1YPHz48LrzwwigpKYmIiKlTp8bUqVMrrN1xxx3jmmuuqfWsAAAAsLWV//d+kyZNok2bNom6oKAg/vWvf8WUKVPis88+i+XLl0dubm506NAhDj300DjxxBMrPClsYwsXLkyq27dvX+MZ27Vrt8meG9a0bds28vPzIyJi4sSJ8fOf/7zCukmTJiXV++yzT4U15U8RP/vssyM3N7fGMwMAANQ3IXEAAACgStnZ2XHJJZfEJZdcEp9++ml88cUXkZeXF+vWrYuWLVtG9+7do0ePHjU+AWyD9PT0OP300+P000+Pzz//PGbNmhX5+flRXFwcLVu2jN133z323HPPLe5fmfvuuy8R8v7xj38ce+21V7X3HnrooXHHHXfEkCFDKj1NPSJijz32iPvvv98XyAAAADR4RUVFkZeXl6jbtm0bERGlpaXx5z//Oe69994KT/sqKCiIgoKC+Pjjj+PBBx+MU045JX77299W+cPxpUuXJtXbbbddjeds3bp1hRkqc+yxx8Zf//rXiPg2JD5hwoQ45phjEve//PLLeOCBBxJ1z549Y6eddkrq8cYbbySeFNasWbM4++yzazwvAABAQyAkDgAAAFTL7rvvHrvvvvtW69+tW7fo1q3bVusfETF//vx4/vnnE/Uvf/nLGvc47rjjYp999omxY8fG66+/Hl9//XU0btw4unXrFieeeGKccsopdRpqBwAAgK2loKAgysrKEnVubm6sW7cuLr300pgyZcpm95eWlsZTTz0VH374YTz44IOx/fbbV1izbNmypHpLflTdtGnTpHr58uWVrhs4cGA8+eSTUVxcHBERl112WZxwwgmx9957x1dffRXPPPNMrFq1KrF+8ODBFXqMGTMm8frMM8+M5s2b13jeurJq1apYvXp1nffdcNo6AACQ2nxjCQAAAHxvdO7cOT766KNa9+nQoUNce+21ce2119bBVAAAAFA/Ng5wZ2VlxW9+85ukgPgOO+wQRxxxRHTu3DmKi4vjs88+i8mTJyeFrWfNmhWDBg2KZ555JnJycpJ6rlu3Lqne+H51bK7nBl27do1LL7007rjjjoj4NsT+wgsvxAsvvFBhbb9+/eLoo49OuvbWW2/F+++/HxERTZo0iXPOOafGs9alhx56KEaPHl2vMwAAAN9dDTokPmfOnPjHP/4R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qKo2bdrUaH+rVq0qPE6qoKCgVjMtWLAgqd4QXJ84cWL06dMnrrrqqnjllVdi4cKFUVRUFKtWrYq5c+fGM888E+edd16cddZZ8cUXX9RqBgAAAAAAAADY2gYOHJh0cFpxcXGce+658emnn1a55/3334+rr7466drRRx9d5VO6L7nkkqR67Nix8fjjj1fZf9KkSXHLLbdU6JGVlVXlnuqYPXt2vPLKKxERkZ6eHhdffHGVa7OysqJ9+/aJeuOnpJdX/l75/5YAAAD1JWPzS7au1atXJz12KSIiNze3Rj3S0tIiJycn6fTwFStW1GqujUPmubm5MXr06PjTn/5Urf3vvPNODBgwIO65557Yb7/9ajVLeatWrar1KemVyc/Pr/OeAECytLRGdboOAAAAAADqQlZWVtx2221xzjnnJL6/z8/Pj9NPPz0GDRoUP//5z6Nz584RETF//vx48skn4+GHH47i4uJEj5YtW8bvf//7Kt+jT58+cfjhh8eUKVMS126++eZ4//334/zzz4/dd9890tLSYt68efHYY4/F448/HqWlpYm1PXv2jNNOO63Wf2v5U8T79OkTu+666ybX9+jRI7755puIiPj3v/8d69ati+zs7KQ106dPj7y8vETdq1evWs8JAABQW/UeEl+3bl2Fazk5OTXu06RJk6Tw9Nq1a2s117Jly5Lqp59+Oikg3qxZszjiiCOiW7dukZmZGfPnz4/JkyfHV199lVizfPnyOP/88+Ppp5+Obt261WqeDR566KEYPXp0nfQCALatRo2bR1p6RpSVrq9yTVp6RjRq7DGUAAAAAABsW717947Ro0fHpZdemvgef82aNTFmzJgYM2ZMNG3aNMrKymLNmjUV9mZnZ8eoUaOiY8eOm3yPP/zhD3HWWWfFZ599lrg2bty4GDduXGRlZUWjRo2isLCwwr7tt98+7rzzzsjIqF3E4YsvvoiXX345Ir49jG7j080r06dPn5g4cWJERCxZsiRuu+22uO666xL3CwsLY8SIEYk6MzMzjj322FrNCQAAUBfS63uA8r8s3mBL/mHXqFHyiZvr11cdvqqOjf/h+cc//jHx+tRTT41JkybFbbfdFhdeeGEMGjQobrjhhpgwYUL85je/SZqlsLAwLr/88gqnpQMA3z9paemR1XzTj5jMat4p0tLq/f9FAwAAAADge+jwww+Pv/zlL9GlS5cK91avXl1pQLxdu3YxduzYOOiggzbbv2XLlvHwww/HvvvuW+FeUVFRpQHxnXfeOR599NHo1GnTn69Xx7333ps4nfyoo46K7t27b3bPscceGx06dEjUjz32WJxxxhkxduzYGD16dJx00kkxbdq0xP2+ffvG9ttvX+tZAQAAaqveE0glJSUVrqWn13ysjfeUf+zUltg4vL6hvuiii2L48OHRvHnFEz4zMjLi/PPPjz/84Q9J1+fMmRPPPPNMreYBAFJD8x0PjvSs3ErvpWflRvMdD97GEwEAAAAAwP/Zf//9Y9y4cXH55ZfHjjvuWOW67bbbLi666KJ48cUXY7/99qt2/+222y7+9re/xdChQ2PnnXfe5Lpf/epX8dxzz21yXXXNmzcvXnzxxURdnVPEIyIaN24cw4cPTzosburUqXHrrbfGn/70p/jf//6XuL7jjjvGNddcU+tZAQAA6kLtnsVUFwNUcmp4SUlJjU8T3zjUnZmZWau5KnPAAQfEr3/9682u++lPfxr/+c9/4tlnn01cGzt2bAwcOLDOZwIAvlvSMxpH692Oj1Vffxhrl86OKCuNSEuPxq13jdwO+0R6RuP6HhEAAAAAgO+57OzsuOSSS+KSSy6JTz/9NL744ovIy8uLdevWRcuWLaN79+7Ro0ePLXpKeMS3h8Cdfvrpcfrpp8fnn38es2bNivz8/CguLo6WLVvG7rvvHnvuuecW96/MfffdlzjE7sc//nHstdde1d576KGHxh133BFDhgyp9DT1iIg99tgj7r///sjNrfygGAAAgG2t3kPiWVlZFa4VFxdHdnZ2jfqsX79+s31rIiMjI4qKipKuXX755ZGWllat/YMHD45//vOfUVZWFhER8+fPjy+++CJ22WWXWs0FAHz3NcpuFi12OjSadT4wERJPb1T3P3ADAAAAAIDa2n333WP33Xffav27desW3bp122r9I779vv75559P1L/85S9r3OO4446LffbZJ8aOHRuvv/56fP3119G4cePo1q1bnHjiiXHKKafUaagdAACgtur9XyjNmjWLtLS0RJg6ImL16tU1/nXt6tWrk+qmTZvWaq6cnJykkPh2221Xo0dk7bTTTtG1a9eYPXt24trUqVNrHRIfNGhQ9O/fv1Y9KpOfnx8nn3xynfcFAKomGA4AAAAAAFtf586d46OPPqp1nw4dOsS1114b1157bR1MBQAAsHXVe0i8UaNG0bx581i+fHni2ooVK6Jdu3bV7rF27doKp35vt912tZqrZcuWSTPV5FFT5feUD4l/9dVXtZopIiI3N9fjqQAAAAAAAAAAAACAKqXX9wAREe3bt0+qFy9eXKP9+fn5SXVWVla0aNGiVjO1bds2qW7ZsmWNe7Rq1SqpXrZsWS0mAgAAAAAAAAAAAADYvAYREu/cuXNSvWDBghrt33j9TjvtVNuRKsy0du3aGvdIT0/+z1taWlqrmQAAAAAAAAAAAAAANqdBhMS7d++eVM+ZM6dG+7/44oukumvXrrWeabfddkuq8/LyatxjxYoVSfWWnEYOAAAAAAAAAAAAAFATDSIk3rNnz6R6+vTpNdo/bdq0pHrfffet7Uixzz77JNWzZs2q8Wnis2fPTqrr4oRzAAAAAAAAAAAAAIBNaRAh8f333z8yMzMT9YcffhjLly+v1t6SkpL4z3/+k3Tt4IMPrvVM++yzTzRr1ixRFxYWxr///e9q71+5cmV8/PHHSdd69epV67kAAAAAAAAAAAAAADalQYTEmzZtGoceemiiXr9+fTz77LPV2vvaa6/FkiVLEnW3bt2iW7dutZ4pIyMj+vTpk3Rt7NixUVZWVq39//jHP6KoqChR77zzzrHLLrvUei4AAAAAAAAAAAAAgE1pECHxiIhTTjklqb7vvvviq6++2uSeFStWxG233ZZ0rX///nU205lnnhlpaWmJ+v33349nnnlms/u++eabGDNmTNK1AQMG1NlcAAAAAAAAAAAAAABVaTAh8aOOOip23333RL1s2bK46KKLYvHixZWuX716dVx++eXxv//9L3GtQ4cOcdppp1X5Hs8++2x079496X9vv/12lev33HPPOOGEE5Ku3XTTTfHCCy9UuWfx4sVxwQUXxIoVKxLX2rVrt8m5AAAAAAAAAAAAAADqSoMJiaelpcXQoUMjPf3/Rpo1a1b07ds3nn766Vi1alVERKxbty7Gjx8fp5xySrz55ptJPa6//vrIzs6u07muu+666NixY6IuLi6O3/zmN/G73/0uZsyYEaWlpRERsXz58njiiSfixBNPjM8++6zC35WTk1OncwEAAAAAAAAAAAAAVCajvgcob999942hQ4fGDTfckLiWn58f1113XVx33XXRrFmzWLVqVZSVlVXYO3jw4Djq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cmy33XY17p2Xlxc33HBDTJ48ucK9rl27xo033hgHHnhgjfuOHDky3nnnnYiIuOSSS2q8HwAAAAAAAGBrEhIHAAAAtsjTTz+dCIhvqRkzZsQ111wTs2fPrnBvyZIlsWTJknjnnXdizJgx8bvf/S4GDhxY7d5Lly6N008/PRYsWFDp/Tlz5sSgQYPi1ltvjRNPPLHafadOnZoIiDdt2jTOOeecau8FAAAAAAAA2BbS63sAAAAA4Lvnm2++iVtvvbVWPSZPnhynn356pQHxja1duzZuuummuOqqq6KsrKxa/a+99toKAfFmzZpFRsb//Wa+pKQkhgwZEp999lm15x4zZkzi9RlnnBEtW7as9l4AAAAAAACAbUFIHAAAAKix66+/PlauXLnF+z/66KO47LLLori4OHEtMzMzjjrqqLj88svjt7/9bZx88snRtGnTpH3jxo2LUaNGbbb/e++9F5MmTUrUu+66azz77LOJU8AvueSSxL3i4uK45ZZbqjX3tGnT4s0334yIiCZNmsSgQYOqtQ8AAAAAAABgW8rY/BIAAACA//Pss8/G66+/vsX7i4uL46qrroqioqLEtW7dusVdd90VXbt2TVo7ZMiQ+P3vfx+vvvpq4tp9990Xhx56aOy///5VvsczzzyTeJ2TkxMPPvhgdOjQISIimjZtGpdffnkUFBTEE088ERERb731Vvzvf/+LLl26bHL20aNHJ14PGDAgWrduXY2/GAAAAAAAAGDbcpI4AAAAUG2LFi2q9qnbVXnyySdj7ty5ibpdu3YxduzYCgHxiIhmzZrF3XffHcccc0zS9bvvvnuT7/Hvf/878fqoo45KBMTLO/fccxOvy8rK4u23395kz+nTpyf6Nm7cOM4777xNrgcAAAAAAACoL0LiAAAAQLXdeOONsWLFioiISEtLq/H+srKyeOihh5KuXXPNNbH99ttXuSctLS1uvfXWaNu2beLaO++8E1OnTq10fX5+fuTn5yfqXr16Vbpup512ilatWiXqjz/+eJOz33PPPYnX/fv3jzZt2mxyPQAAAAAAAEB9ERIHAAAAquW5556LyZMnJ+rTTjutxj3efvvtWLBgQaLu0KFDHHfccZvd17Rp0zj77LOTro0bN67SteUD4hERO+ywQ5V9y99bsmRJles++uijxN+elZUV559//mZnBgAAAAAAAKgvQuIAAADAZuXl5cXIkSMTdY8ePeK8886rcZ8pU6Yk1cccc0ykp1fv44mNw+QTJ06MsrKyCuvWrFmTVDdp0qTKno0bN068XrVqVZXrxowZk3h9yimnRLt27TY7LwAAAAAAAEB9ERIHAAAANuvGG2+M5cuXR0REZmZmjBw5strh7vLefvvtpPrAAw+s9t4uXbpE+/btE3V+fn588sknFdaVD35HRJSWllbZs7i4OPE6MzOz0jWffvppTJo0KbHmwgsvrPbMAAAAAAAAAPVBSBwAAADYpOeffz4Rko6IGDx4cHTv3r3GfUpKSmL27NlJ1/bcc88a9dhjjz2S6pkzZ1ZY07x586R6Q7i9MsuWLUu8btasWaVr7rnnnsSJ5T//+c+jQ4cO1R0XAAAAAAAAoF4IiQMAAABVWrx4cYwYMSJR77bbbjF48OAt6rVgwYJYt25dom7cuHGNA9ddunRJqufOnVthzQ477BDZ2dmJ+osvvqi0V2FhYXz11VeJepdddqmw5vPPP49XX301IiIyMjKcIg4AAAAAAAB8JwiJAwAAAFUaOnRo4rTtRo0axciRIyMrK2uLepUPZEdEtGvXLtLS0mrUo127dkn1woULK6zJyMhIOnF84sSJlfaaMmVKrF+/PlH36tWrwpryp4ifdNJJ0blz5xrNCwAAAAAAAFAfMup7AAAAAKBhevHFF2PChAmJ+txzz4299957i/stWbIkqd5uu+1q3KN169ZJdUFBQaXrjj322Jg2bVpERHzyySfx8MMPx7nnnps0y+23356ot99++zjggAOSesyZMyfGjx8fEd8G5C+66KIazwsAAADUj3nz5sXUqVNjyZIlkZubG3vttVf07Nmzxj9Y3+CTTz5J/BC9WbNmSZ8zAAAANERC4gAAAEAFS5YsiWHDhiXqnXbaKS6//PJa9dxwIvkGubm5Ne7RtGnTpHr58uWVruvbt2/cf//9ife85ZZb4r///W/88Ic/jKVLl8azzz4b+fn5ifXnnXdeZGQkf0xy7733RmlpaUREnHDCCbHjjjvWeN66smrVqli9enWd9y3/3wAAAACq47zzzos33ngjIiI6duwYkyZNqnGP0tLSeO211+LVV1+N6dOnR35+fhQVFUXz5s2ja9eu8aMf/ShOPvnkLfqBeV5eXtxwww0xefLkCve6du0aN954Yxx44IE17jty5Mh45513IiLikksuqfF+AACAbU1IHAAAAKjgpptuSpzSnZaWFiNGjIjs7Oxa9Vy3bl1SnZOTU+MeG+/ZuOcGrVu3juuuuy6uvvrqxLXJkydX+gXxD3/4wzj77LOTrs2dOzdeeumliIhIT0+v91PEH3rooRg9enS9zgAAAABPP/10IiC+pWbMmBHXXHNNzJ49u8K9JUuWxJIlS+Kdd96JMWPGxO9+97sYOHBgtXsvXbo0Tj/99FiwYEGl9+fMmRODBg2KW2+9NU488cRq9506dWoiIN60adM455xzqr0XAACgvqTX9wAAAABAw/LSSy/FK6+8kqgHDhwY++23X637FhUVJdUbn9xdHRvvKS4urnLtiSeeGDfddFNkZmZWuebwww+Pe++9N9LTkz8iue+++6KkpCQiIo477rjo2rVrjWcFAACAVPLNN9/ErbfeWqsekydPjtNPP73SgPjG1q5dGzfddFNcddVVUVZWVq3+1157bYWAeLNmzZI+TygpKYkhQ4bEZ599Vu25x4wZk3h9xhlnRMuWLau9FwAAoL44SRwAAABIWLp0aQwbNixRd+zYMa666qo66b0hdL3BxsHs6khLS0uqN/cl8YABA2L//fePhx9+OP7zn/9Efn5+5Obmxl577RUnn3xyHH/88RX2zJ8/P8aNG5d4v4svvrjGcwIAAECquf7662PlypVbvP+jjz6Kyy67LOkH35mZmXHYYYdFjx49Ijs7O+bMmRPjx4+P1atXJ9aMGzcuOnXqFFdcccUm+7/33nsxadKkRL3rrrvGH/7wh9hrr71i9erV8eCDD8Y999wTEd/+6PyWW26Jhx56aLNzT5s2Ld58882IiGjSpEkMGjSoRn83AABAfRESBwAAABJuvvnmWLp0aaIeNmxYNG3atE56b3yi98ah8epYv379JntWpmvXrknB98259957E+9zzDHHxG677VZhzfz58+PRRx+N119/PRYtWhQZGRmx4447xjHHHBNnnnlm5ObmVvv9AAAAoKF79tln4/XXX9/i/cXFxXHVVVclPWWsW7ducdddd1V4eteQIUPi97//fbz66quJa/fdd18ceuihsf/++1f5Hs8880zidU5OTjz44IPRoUOHiIho2rRpXH755VFQUBBPPPFERES89dZb8b///S+6dOmyydlHjx6deD1gwIBo3bp1Nf5iAACA+lfzI7sAAACAlPTqq6/Gyy+/nKhPPvnkOOSQQ+qsf1ZWVlJd/uSw6to4JL5xz9pauHBhPP/884n6kksuqbBmwoQJcdJJJ8Wjjz4aX375ZRQWFsbKlStj5syZceedd8bxxx8fM2bMqNO5AAAAoL4sWrQobrnlllr1ePLJJ2Pu3LmJul27djF27NgKAfGIiGbNmsXdd98dxxxzTNL1u+++e5Pv8e9//zvx+qijjkoExMs799xzE6/Lysri7bff3mTP6dOnJ/o2btw4zjvvvE2uBwAAaEicJA4AAABEQUFBDB06NFFvv/32cc0119TpezRr1iypXrNmTY17lH/cdETU2SnnG9x///2J8PoRRxwRe+yxR9L96dOnx5VXXpl08lmbNm0iImLx4sUREZGXlxfnnHNOPP3005V+2b0lBg0aFP3796+TXuXl5+fHySefXOd9AQAASB033nhjrFixIiIi0tLSoqysrEb7y8rK4qGHHkq6ds0118T2229f5Z60tLS49dZbY9q0aZGfnx8REe+8805MnTo19ttvvwrr8/PzE+siInr16lVp35122ilatWoVBQUFERHx8ccfb3L2e+65J/G6f//+ic8AAAAAvguExAEAAIAYNmxYLFmyJFEPHTo0mjdvXqfv0apVq6R6+fLlNe6x8Z66fMTz119/Hc8++2yi3vgU8bKysrj55psTAfFWrVrFHXfcEQcffHBERLzxxhtx1VVXxbJly2L16tUxZMiQeOqpp+pkttzc3MjNza2TXgAAAFBdzz33XEyePDlRn3baafH3v/+9Rj3efvvtWLBgQaLu0KFDHHfccZvd17Rp0zj77LPj9ttvT1wbN25clSHx8nbYYYcq++6www6JkHj5z0I29tFHHyX+9qysrDj//PM3OzMAAEBDIiQOAAAA33NvvfVWvPjii4m6Y8eOkZeXF0888cQm9y1btiypXr16dYU9Bx54YOyyyy4RERUe87ypL2KrsuG07g3atm1b4x5VKX+K+I9+9KPo2bNn0v2pU6fGjBkzEvWtt96aCIhHRBx66KHx//w//09cdNFFERHx4Ycfxrvvvhv7779/nc0IAAAA20peXl6MHDkyUffo0SPOO++8GofEp0yZklQfc8wxkZ6eXq29xx13XFJIfOLEiXHjjTdGWlpa0rqNn1bWpEmTKns2btw48XrVqlVVrhszZkzi9SmnnBLt2rWr1swA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jRpVe/8BBxwQ7733XqJ+/fXX47DDDquz+Tb26KOPxgcffJCof/Ob30SHDh222vsBAABAQ9SpU6eYNWtWrfscf/zxcfzxx8f8+fNjxowZkZ+fH2vXro3mzZvHrrvuGvvss0+dfgH86KOPxsqVKyMiYrfddoujjz662nu7d+8eDz74YPzqV7+KgoKCStd06tQp7r///thhhx3qZF4AAADYGgoKCmLo0KGJevvtt49rrrmmTt+jWbNmSfWaNWtq3GPDk8A2qKtTzje4//77E+H1I444IvbYY4+k+9OnT48rr7wyioqKEtfatGkTERGLFy+OiIi8vLw455xz4umnn46uXbvW2WyDBg2K/v3711m/DfLz8+Pkk0+u874AAEDD0iBC4uVP2or49jHPNbHx+g2Pfd4a5s2bF3feeWeiPuCAA2LAgAFb7f0AAADg+6Jz587RuXPnrfoeq1atikcffTRRX3zxxZGWllajHvvtt1+8/PLL8fDDD8drr70WCxYsiEaNGsXOO+8cxx13XJx55pnRuHHjuh4dAAAA6tSwYcNiyZIliXro0KHRvHnzOn2PVq1aJdXLly+vcY+N97Ru3bpWM5X39ddfx7PPPpuoNz5FvKysLG6++eZEQLxVq1Zxxx13xMEHHxwREW+88UZcddVVsWzZsli9enUMGTIknnrqqTqbLzc3N3Jzc+usHwAA8P3SIELic+bMSap32WWXGu3v0qVLUj137txaz1SZsrKy+P3vfx9r166NiIicnJwYMWJEjb9MBgAAAOpHbm5uvPPOO7Xu06pVq7jiiiviiiuuqIOpAAAAYNt666234sUXX0zUHTv+f+zdd5SV1bk/8GcqAwxNerMBIoqIxl5iQVFMNAaNiJpibITEqDG50WgiKmLvxJpYgz0aBSsKEUnsDVQEpSgIAtKnANN+f/DjhDMFZpiBGQ6fz1p33fPsd+/9PpzrumudOd+z386xYMGCePTRR9e7bunSpUl1fn5+hTX77rtv4jv/8k/kXjeUXl1rT+teq23btjXeoyrrniJ+8MEHR58+fZKuv/feezF58uREfe211yYC4hERBx10UFxzzTUxZMiQiIj4+OOP491334299967znoEAADYWA0iJD537tykun379jVa365du6R67a906/oxUw899FC89957ifr888+vEFAHAAAAAAAAgIZs3rx5SfU333wTw4YNq/E+S5curbDu6quvToTEyz8xbMGCBbF69erIzs6u9j3mzJmTVG+//fY17rMy8+fPj3/+85+J+te//nWFOS+++GLidadOneKQQw6pMOewww6Lzp07xzfffBMRES+//LKQOAAA0CA0iJD44sWLk+o2bdrUaH2rVq0iPT09SktLE2NLliyp05D4119/HTfffHOi7tu3b/zsZz+rs/2rKy8vL/Lz8+t834ULF9b5ngAAAAAAAABsvZo1axadOnVKHBxXUlISM2fOjJ49e1Z7j/JPJu/WrVud9HbvvffG6tWrIyJi//33jz322KPCnEmTJiVe77bbblXutfvuuydC4uuePA4AAFCf6j0knp+fn/jgtVZubm6N9khLS4vGjRsnhaeXL19eJ/1FRJSVlcUll1wShYWFERGRnZ0dI0aMiPT09Dq7R3Xdf//9MXLkyM1+XwAAAAAAAACoqd122y3p6eKTJk2qdkh85cqVMXXq1ESdnp4effv2rXVPCxcujCeeeCJRV3aKeETE7NmzE687duxY5X4dOnRIvC5/8jkAAEB9qfeQ+KpVqyqMNW7cuMb7NGnSJCkkvnLlylr1ta5Ro0bFO++8k6iHDh1aZ79OBgAAAAAAAIDNaeDAgTFw4MAar5szZ07069cvUXfu3DnGjRu33jUHHnhgvPzyy4l6woQJ8ZOf/KRa93vzzTejqKgoUe+yyy7RokWLGnZd0d/+9rdEVmGfffaJvffeu9J5eXl5idfryzE0atQo8bouD7QDAACojc1/FHY5636gWyszs+bZ9YyMjKS6uLh4o3ta1+zZs+PGG29M1L169YqzzjqrTvYGAAAAAAAAgFTWr1+/pAzA+PHjY8GCBdVa+/jjjyfVxxxzTK37WbRoUdK+Q4cOrXLuun2XlpZWOW/dp6enpaXVskMAAIC6Ue8h8ZKSkgpj6ek1b6v8mvV9QKuusrKyuOSSS6KgoCAi1nwAHDFixEaF2AEAAAAAAABga9OmTZs49NBDE3VRUVEMHz58g+vGjx8f48ePT9SNGjWK448/vtb9/P3vf4/CwsKIiNhjjz1i//33r3Jus2bNEq+XLVtW5bx1rzVv3rzWPQIAANSFeg+JVxa4riw4viHlTyTPysra6J7WevTRR+Ptt99O1GeeeWbssssutd4XAAAAAAAAALYWv/rVr5JO2H755ZfjhhtuiLKyskrnT5o0KX7/+98njZ166qnRunXrWvWxePHiePTRRxP1+k4Rj4jYdtttE6+nT59e5bx1r3Xp0qUWHQIAANSdej8SOzs7u8JYUVFRNGrUqEb7FBcXb3Dfmvjmm2/i+uuvT9TdunWLX//617Xasy6cfvrpcdJJJ9X5vgsXLowTTjihzvcFAAAAAAAAYOvWu3fvOPnkk5MC2vfee29MmTIlhg4dGn379o2MjIyYP39+PPHEE3HPPffE6tWrE3O7dOmywUB3ddx///2JJ4n36dMnvv/972+w7/fffz8iIj766KP47rvvok2bNklzFixYEJMmTUrUffv2rXWfAAAAdaHeQ+LNmjWLtLS0pF8I5+fnR25ubo32yc/PT6qbNm1aq74uueSSxIfD9PT0GDFiRK2D53UhNze3xu8NAAAAAAAAANSniy++OGbNmhVvvvlmYmzixIkxceLEyMzMjOzs7MR39Otq2rRp3HLLLdGsWbNa3X/p0qUxatSoRF2d0Hn//v3jwQcfjIg1h90NGzYsbr311sjIyIiINU9Jv+KKK5Kelv6DH/ygVn0CAADUlfT6biAjIyOaN2+eNLZ8+fIa7bFy5cqkXxFHRK0eM/XYY48lfTD92c9+5te+AAAAAAAAALCRGjVqFHfccUccfvjhFa4VFxdXGhBv165d3HfffbHbbrvV+v4PPPBA4vC5XXbZJQ477LANrtlrr72id+/eiXrs2LExcODAuOeee+Kee+6JgQMHxtixYxPXDzjggDrpFQAAoC7U+0niEREdOnSIZcuWJervvvsuevToUe31CxcuTKqzs7OjRYsWG9XL8uXL47rrrkvUmZmZ0aFDh6THXlWl/Gnmo0ePjpYtWybqHXbYIfbbb7+N6gsAAAAAAAAAtmRNmjSJO++8M1588cW4995749NPP610Xm5ubpxwwgkxdOjQpO/cN9by5cvjH//4R6Kuzinia40YMSIGDRoUhYWFERHx+eefx+eff15hXqtWreKqq66qda8AAAB1pUGExLt27RpTp05N1HPmzKnR+vLzt99++43uZfny5Ulh7+Li4rjmmms2aq9bbrklqf7xj38sJA4AAAAAAADAFqlLly5J3+1vrAEDBsSAAQNi9uzZMXny5Fi4cGGsXLkymjdvHt27d4/dd989srOz66DjNR566KFYsWJFRETstNNOccQRR1R7bc+ePeNvf/tb/OY3v4klS5ZUOqdLly5x9913R6dOneqkXwAAgLrQIELiPXv2jFdffTVRT58+vUbrZ8yYkVR369atTvoCAAAAAAAAADaNrl27RteuXTfpPfLy8uKhhx5K1L/61a8iLS2tRnvstdde8eKLL8YDDzwQr732WsyZMycyMjJihx12iKOPPjpOO+20yMnJqevWAQAAaqVBhMT79OmTVE+aNKlG6z/66KOkeo899qhtSwAAAAAAAADAFi43NzfeeeedWu/TqlWruOCCC+KCCy6og64AAAA2vQYREt97770jKysrioqKIiLi448/jmXLlkWLFi02uLakpCT+85//JI0dcMABG91LbR6Pdfjhh8c333yTqF977bXo0qXLRvcCAAAAAAAAAAAAAFBT6fXdQERE06ZN46CDDkrUxcXF8fTTT1dr7WuvvRaLFi1K1D169IgePXrUeY8AAAAAAAAAAAAAAFuCBhESj4g48cQTk+q77ror5s6du941y5cvj+uvvz5p7KSTTqrz3gAAAAAAAAAAAAAAthQNJiTer1+/2HnnnRP10qVLY8iQIfHdd99VOj8/Pz/OO++8+PrrrxNjHTt2jEGDBlV5j6effjp69uyZ9D9vv/123f0jAAAAAAAAAAAAAADqWYMJiaelpcWwYcMiPf1/LU2dOjWOP/74ePLJJyMvLy8iIlatWhUvvfRSnHjiifHf//43aY8///nP0ahRo83aNwAAAAAAAAAAAABAQ5JZ3w2sa4899ohhw4bFX/7yl8TYwoUL49JLL41LL700mjVrFnl5eVFWVlZh7TnnnBP9+vXbnO0CAAAAAAAAAAAAADQ4DSokHhExaNCgiIgYMWJErFy5MunaihUrKsxPT0+PoUOHxrnnnrtZ+gMAAAAAAAAAAAAAaMjS67uBygwaNCjGjBkTAwYMiJycnCrnHXjggTFq1CgBcQAAAAAAAAAAAACA/6/BnSS+VteuXeOWW26JgoKCePfdd2P+/PmxePHiyMnJiU6dOsWee+4Zbdq0qdGeAwcOjIEDB26ijiPGjRu3yfYGAAAAAAAAAAAAAKiOBhsSX6tJkyZxyCGH1HcbAAAAAAAAAAAAAABbhPT6bgAAAAAAAAAAAAAAgLojJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFCIkDAAAAAAAAAAAAAKQQIXEAAAAAAAAAAAAAgBQiJA4AAAAAAAAAAAAAkEKExAEAAAAAAAAAAAAAUoiQOAAAAAAAAAAAAABAChESBwAAAAAAAAAAAABIIULiAAAAAAAAAAAAAAApREgcAAAAAAAAAAAAACCFZNZ3AwAAAACb0ueffx6TJ0+OxYsXR8uWLaNv377Rs2fPjd7v7bffjnfeeSciIjp37hwDBw6sq1YBAAAAAAAA6oSQOAAAAFCp0tLSeP311+ONN96IDz/8MBYuXBhLly6NRo0aRatWraJ79+6x3377xYABA6J9+/YbdY/Vq1fHCy+8EOPGjYtPPvkklixZEkVFRdGqVavYaaed4rDDDovjjz8+cnNza7z3zJkz4+KLL44PP/ywwrW+ffvG5ZdfHjvvvHON9iwpKYk///nP8dVXX0VExFVXXVXjvgAAAAAAAAA2NSFxAAAAoILXXnstrrvuupg1a1aFa0VFRZGXlxezZ8+O8ePHx0033RSDBg2K3/3ud9G4ceNq32PixIlxySWXxLffflvh2oIFC2LBggUxceLEuO2222L48OHRv3//au89c+bMOPnkk2Pp0qWVXv/oo49i8ODBcccdd8T+++9f7X1Hjx6dCIh37tw5jj/++GqvBQAAAAAAANhc0uu7AQAAAKBhufbaa2Po0KGVBsQrs2rVqnjooYdi8ODBlQa+K/PYY4/FmWeeWa35y5Yti3PPPTduuOGGau1dWloaF1xwQYWAePPmzSM9/X9/CikoKIjzzz8/Fi5cWO1977rrrkR99tlnR2am398DAAAAAAAADY9vMgEAAICEkSNHxn333Zc0lp6eHnvttVfsueee0bp16ygsLIxp06bFhAkTYvny5Yl5U6ZMibPOOisee+yxaNq0aZX3GD9+fAwbNizKysoSYzk5OXHEEUdEz549IyJi6tSp8corr8Tq1asTc+69997Ybrvt4ic/+cl6/w1jxoyJKVOmJOrvfe97MWLEiNh+++1jyZIlceONN8aTTz4ZERFLly6NW2+9NYYPH77B9+aFF16ImTNnRkREx44dY+DAgRtcAwAAAAAAAFAfhMQBAACAiIiYPHlyjBw5Mmlsl112iWuuuSYR3l7XihUr4sYbb4xHH300MTZt2rS45ppr4sorr6z0HkuXLo2LL744KSC+1157xU033RTt27dPmjt//vz43e9+F++9915i7PLLL4999tkntttuuyr/HU899VTiddu2beOee+6J3NzciIho1apVDB8+PObPnx8TJkyIiIjnn38+LrroosScypSVlSWdIn7WWWdFdnZ2lfMBAAAAAAAA6lP6hqcAAAAAW4MbbrghKbzdq1evGDVqVKUB8YiIZs2axbBhw2LIkCFJ4//85z9jxowZla65++67Y8mSJYm6Z8+ecc8991QIiEdEtG/fPu67777YfffdE2NFRUVxxx13VPlvKCwsTAqVn3DCCZWGv3/xi18kXhcUFMSkSZOq3DMi4qWXXoovvvgiItYEz0888cT1zgcAAAAAAACoT0LiAAAAQMydOzfeeuutRJ2WlhbXXnttNGnSZINrzzvvvKQgeUlJSTz33HMV5uXn58djjz2WNDZ8+PBo2rRplXs3atQobr311mjcuHFibPTo0TF79uxK50+dOjVKSkoS9boB83X17ds30tLSEvVnn31WZQ9lZWVJwfQzzzwzGjVqVOV8AAAAoP59/vnn8eSTT8bdd98djz/+eEydOrVW+7399ttx++23x+233x5PP/10HXUJAACw6WTWdwMAAABA/Xv11VeT6n333bfKE8TLS09Pj8GDB8ewYcMSYxMnTozzzz8/ad5LL70UBQUFiXrPPfeMPn36bHD/jh07xnHHHRePP/54RKwJob/00ktx1llnVZi7cOHCpLpTp06V7tm0adNo0aJFLF26NCIiFi1aVOX9x44dG9OmTYuIiNatW8fJJ5+8wZ4BAABgS1VaWhqvv/56vPHGG/Hhhx/GwoULY+nSpdGoUaNo1apVdO/ePfbbb78YMGBApU8Gq47Vq1fHCy+8EOPGjYtPPvkklixZEkVFRdGqVavYaaed4rDDDovjjz++0qeDbcjMmTPj4osvjg8//LDCtb59+8bll18eO++8c432LCkpiT//+c/x1VdfRUTEVVddVeO+AAAANjchcQAAAKDCSdr77bdfjdaXD3vPmTOnwpzXX389qT7qqKOqvf+AAQMSIfGINcHtykLi64bQI2K9J6Hn5OQkXufl5VU5b91TxH/5y18mrQMAAIBU8tprr8V1110Xs2bNqnCtqKgo8vLyYvbs2TF+/Pi46aabYtCgQfG73/0u6QlgGzJx4sS45JJL4ttvv61wbcGCBbFgwYKYOHFi3HbbbTF8+PDo379/tfeeOXNmnHzyyYkfhZf30UcfxeDBg+OOO+6I/fffv9r7jh49OhEQ79y5cxx//PHVXgsAAFBf0uu7AQAAAKD+zZs3L6nu0qVLjdaX/zJ4+fLlFea88847SfW+++5b7f333HPPyMz832/dJ02aFEuWLKkwr3yAu7S0tMo9i4qKEq+zsrIqnfPaa6/FlClTIiKiVatWccopp1S7ZwAAANiSXHvttTF06NBKA+KVWbVqVTz00EMxePDgSgPflXnsscfizDPPrNb8ZcuWxbnnnhs33HBDtfYuLS2NCy64oEJAvHnz5pGe/r9oREFBQZx//vkVnka2vn3vuuuuRH322Wcn/Y0CAACgofLJBQAAAIgHH3ww8vLy4ttvv41vv/02evToUaP15UPmzZo1S6rnz5+fFOrOysqq0T0aNWoUO+64Y0ybNi0iIsrKyuKzzz6LAw88MGle8+bNk+rKwuoRa77gXfda+X7XWvcU8V/84hfrPZkcAAAAtlQjR46M++67L2ksPT099tprr9hzzz2jdevWUVhYGNOmTYsJEyYkfaaeMmVKnHXWWfHYY49F06ZNq7zH+PHjY9iwYVFWVpYYy8nJiSOOOCJ69uwZERFTp06NV155JVavXp2Yc++998Z2220XP/nJT9b7bxgzZkzih94REd/73vdixIgRsf3228eSJUvixhtvjCeffDIiIpYuXRq33nprDB8+fIPvzQsvvBAzZ86MiIiOHTvGwIEDN7gGAACgIRASBwAAACIiIjc3N7p37x7du3ev8dqJEycm1R06dEiqp0+fnlR37dq1xqdubbvttomQeMSaR0iXD4nvuOOOFe7bp0+fCnt99dVXSSeJl18XEfH666/HJ598EhERLVq0iNNOO61G/QIAAMCWYPLkyTFy5MiksV122SWuueaaRHh7XStWrIgbb7wxHn300cTYtGnT4pprrokrr7yy0nssXbo0Lr744qSA+F577RU33XRTtG/fPmnu/Pnz43e/+1289957ibHLL7889tlnn9huu+2q/Hc89dRTiddt27aNe+65J3JzcyNizdPBhg8fHvPnz48JEyZERMTzzz8fF110UWJOZcrKypJOET/rrLMiOzu7yvkAAAANSfqGpwAAAABUrbCwMJ5++umksf333z+p/uabb5Lq8iHy6ij/pXH5PdfOadu2baJ+9dVXK91r3LhxSfXuu+9eYc66p4j/7Gc/W++XxgAAALCluuGGG5LC27169YpRo0ZVGhCPWPM0rmHDhsWQIUOSxv/5z3/GjBkzKl1z9913Jz1hrGfPnnHPPfdU+Kwfseaz/X333Zf0Wb2oqCjpc3p5hYWFSaHyE044odLP8b/4xS8SrwsKCmLSpElV7hkR8dJLL8UXX3wREWuC5yeeeOJ65wMAADQkDTokPn369LjuuuviJz/5Sey3336x6667xp577hnHHXdcDBs2LD744IPN3tMnn3wSN998c5x22mlxyCGHRN++fWP33XePQw89NE499dS49dZbkx5hBQAAAKnu7rvvjqVLlyaN9evXL6levHhxUt26desa32ebbbZJqtf9cnldRx11VOL1q6++GmPHjk26PmvWrLjnnnsSdZ8+fWL77bdPmjNx4sT46KOPImLNl98/+9nPatwvAAAANHRz586Nt956K1GnpaXFtddeG02aNNng2vPOOy8pSF5SUhLPPfdchXn5+fnx2GOPJY0NHz48mjZtWuXejRo1iltvvTUaN26cGBs9enTMnj270vlTp06NkpKSRF3Zj8EjIvr27RtpaWmJ+rPPPquyh7KysqRg+plnnhmNGjWqcj4AAEBDU7PnOm8meXl5MWLEiHj66aeTfrEcEVFcXBxTp06NqVOnxqOPPhpHHnlkXHXVVdGiRYtN2tP06dNj2LBh8c4771R6fd68eTFv3rx477334o477ojDDjssLr300ujSpcsm7QsAAADq06RJk+Jvf/tb0livXr1i7733ThorHyLfmFO5y395vGzZskrnnXLKKfH4449HUVFRRET89re/jR/84Aex2267xdy5c+Opp56KvLy8xPxzzjmnwh5//etfE69PO+20aN68eY37rUt5eXmRn59f5/suXLiwzvcEAABgy1H+CVz77rtvlSeIl5eenh6DBw+OYcOGJcYmTpwY559/ftK8l156KQoKChL1nnvuGX369Nng/h07dozjjjsuHn/88YhYE0J/6aWX4qyzzqowt/zn206dOlW6Z9OmTaNFixaJv1MsWrSoyvuPHTs2pk2bFhFrfux+8sknb7BnAACAhqTBhcQXL14cv/jFL2Lq1KnVmj927Nj45JNP4oEHHqhw6lddefHFF+Piiy+OwsLCaq8ZP358fPjhh3HbbbfFvvvuu0n6AgAAgPq0cOHCOPfccxNh7LX++Mc/Vpi7atWqpHrdk8Cqq/ya8nuu1a1btzj33HPjpptuioiI0tLSGD16dIwePbrC3IEDB8YRRxyRNPbmm28mnl7WpEmT+PnPf17jXuva/fffHyNHjqzvNgAAAEgx5U/S3m+//Wq0vnzYe86cORXmvP7660n1uk8A25ABAwYkQuIRa/IBlYXE1w2hR8R6T0LPyclJvF73R+TlrXuK+C9/+cukdQAAAFuCBhUSLy4ujqFDh1YIiPfo0SMOPfTQaNOmTXz33Xcxfvz4+PLLLxPX582bF0OHDo0nnnhio04iW58333wz/vCHP1T4wrtHjx5xwAEHRKdOnaKkpCRmz54dEyZMiG+++SYxZ+nSpfGrX/0qHn/88ejRo0ed9gUAAAD1admyZXHGGWfEt99+mzQ+aNCg2H///SvMX716dVKdmVnzP0lkZGQk1eU/q6/rnHPOiZUrV8Zdd90VpaWllc454YQT4sorr6wwvu4p4qeeemq0atWqxr0CAADAlmDevHlJdU2flF3+B93Lly+vMKf807prcsjannvuGZmZmVFcXBwRa55otmTJkgqf1csHuKv6W0BE8t8TsrKyKp3z2muvxZQpUyIiolWrVnHKKadUu2cAAICGokGFxO+666748MMPE3VWVlb85S9/iZNOOilp3u9///t48skn48orr0ycGjZ9+vQYMWJEjBgxos76KSgoqBAQb9myZVx11VUVThmLWPN4q8cffzyuvvrqxJff+fn5cd5558Xo0aMrfJkNAAAAW6Lly5fHmWeeWeFH3rvuumtccsklla4pKSlJqtPT02t83/JrysrK1jv/vPPOi8MOOywefPDBePfdd2Px4sXRsmXL2GOPPWLw4MFxwAEHVFjzzjvvxLvvvhsRa77o/uUvf1njPgEAAGBL8eCDD0ZeXl58++238e2339b48LPyIfNmzZol1fPnz48lS5Yk6qysrBrdo1GjRrHjjjvGtGnTImLN3wI+++yzOPDAA5PmNW/ePKmuLKwesSY8vu618v2ute4p4r/4xS/WezI5AABAQ9VgQuLfffdd/P3vf08aGz58eBx//PGVzv/JT34SLVq0iN/+9reJL4X/9a9/xTnnnBPbbbddnfR0//33x8KFCxN106ZN45FHHolu3bpVOj8jIyNOOeWU6NKlSwwZMiTxBfj06dPj6aefjp/85Cd10hcAAADUl0WLFsUZZ5yROE1rrc6dO8edd94ZjRo1qnRd+ZO5yofGq2PtqWFV7VmZPn36xI033ljte6x7ivjJJ58c22yzTYU5n3/+efzjH/+IN998MxYuXBiNGzeObt26xTHHHBMnnXRSZGdnV/t+AAAAUN9yc3Oje/fu0b179xqvnThxYlLdoUOHpHr69OlJddeuXWv8dLFtt902ERKPiJg5c2aFkPiOO+5Y4b59+vSpsNdXX32VdEhc+XUREa+//np88sknERHRokWLOO2002rULwAAQENR82O7NpFHH300CgoKEvX3v//9KgPia/Xv3z/pA1lJSUncfffdddbTM888k1RfeOGFVQbE1/X9738/Bg8evN69AAAAYEsze/bsOPXUUysExNu2bRt///vfo3379lWuLR+cXvcL2eoqHxKv6zD2Bx98EG+99VZErDmp7IwzzqgwZ9SoUXHiiSfGk08+GXPmzIlVq1bF0qVL4/33348rr7wyfvSjH8Xs2bPrtC8AAABoiAoLC+Ppp59OGtt///2T6m+++SapLh8ir47yf28ov+faOW3btk3Ur776aqV7jRs3LqnefffdK8xZ9xTxn/3sZ5Gbm1ujfgEAABqKBnOSePkQ9emnn16tdWeddVY88sgjiRPIXnnllbj88surdZrY+nz++edJX+o2adIkTjjhhGqvP+WUU+If//hHov7oo48iLy/PB0gAAAC2SJMnT44hQ4bEd999lzTerl27ePDBB2OHHXZY7/ryj29e94fi1ZWfn59UN23atMZ7rM+6p4ifdNJJSV8uR6z5gvnKK69MPNEsYs2X0GuD4hERM2bMiFNOOSWeffbZSk8h31inn356nHTSSXW231oLFy6s0d87AAAAYK2777478Xl4rX79+iXVixcvTqpbt25d4/uU/3y9ZMmSSucdddRRie/oX3311Rg7dmwceeSRieuzZs2Ke+65J1H36dMntt9++6Q9Jk6cGB999FFErPlbxs9+9rMa9wsAANBQNIiQ+BdffJH0a9+WLVvGvvvuW6217du3j759+8b7778fERErVqyIN998M77//e/XqqfPPvssqe7bt2/k5ORUe323bt2iadOmiS+wS0pKYt68edGjR49a9QUAAACb27hx4+LCCy+sEOzu3LlzPPDAA7HttttucI9WrVol1cuWLatxH+XX1GUI++OPP048Ijs7OzvOOuuspOsrV66M4cOHJwLiXbt2jVtuuSV69+4dpaWl8fzzz8ef/vSnWL16dSxYsCCuuOKKuOWWW+qsv9zcXD88BwAAoMGYNGlS/O1vf0sa69WrV+y9995JY+VD5Bvz2bb8j8Sr+pvCKaecEo8//nji6WW//e1v4wc/+EHstttuMXfu3HjqqaciLy8vMf+cc86psMe6PyA/7bTTonnz5jXuty7l5eVV+NF8XVi4cGGd7wkAADQ8DSIk/vbbbyfV3/ve9yIjI6Pa6/fZZ59ESDwiYsKECbUOic+dOzep7tKlS433aNKkSdIHto35AhwAAADq0yOPPBLDhw9PPMFrre7du8ff//73aj8mumPHjkn1okWLatxL+VPMy5/0XRsjR45MvB44cGCFR1m/9NJLMW/evIiISE9Pj7/+9a/Rs2fPRH3sscfGsmXL4sorr4yIiJdffjnmzJmzUX9PAAAAgIZs4cKFce655ybC2Gv98Y9/rDB31apVSXXjxo1rfL/ya8rvuVa3bt3i3HPPjZtuuikiIkpLS2P06NExevToCnMHDhwYRxxxRNLYm2++GR988EFErPmu/+c//3mNe61r999/f9LfLAAAAGoivb4biIj4/PPPk+pddtmlRuvLz//kk09q3dNvfvObmDx5cowdOzYefvjhOPXUU2u0fuXKlRUenVXfvzIGAACAmhg5cmRcfvnlFQLie+yxRzzyyCPVDohHrDl5e11z5sypcT/l15R/JPTGmjx5ckyYMCEiIrKysuLss8+uMOfFF19MvN5zzz0TAfF1nXjiiYkvrktLS2Ps2LF10h8AAAA0FMuWLYszzjgjvv3226TxQYMGxf77719h/urVq5PqzMyan2NX/oC58uH0dZ1zzjkxdOjQSE+vOgpxwgknxPDhwyuMr3uK+KmnnlrhqWgAAABbmgZxkvj06dOT6h133LFG68s/1nrmzJm17ilizeOlt91222o9Nru8t956q8KX6OVPIQMAAICG6oYbboh77723wvjhhx8eN998c+Tk5NRovx133DGysrISX+TOnz8/8vLyavSY6RkzZiTV3bt3r1EPVVn3S+Af/ehH0blz5wpzJk2alHjdp0+fSvfJycmJnXbaKT7++OOIWBM+BwAAgFSxfPnyOPPMM2PqdDR8GQAAeTdJREFU1KlJ47vuumtccsklla4p/535+sLbVSm/pqysbL3zzzvvvDjssMPiwQcfjHfffTcWL14cLVu2jD322CMGDx4cBxxwQIU177zzTrz77rsRsebk8l/+8pc17hMAAKChaRAh8blz5ybVNQ1Tt2vXLqleunRp5OfnR9OmTWvd28YaNWpUUr3rrrtGixYt6qkbAAAAqL6RI0dWGhAfNGhQXHbZZRVO8KqO7Ozs6NmzZ+LpX2VlZTF58uRKTxmrzNdff530xK6WLVvGDjvsUOM+ypsyZUqMHz8+ItacZjZkyJAKc/Ly8pLu3bFjxyr369ChQyIkvjGnpQMAAEBDtGjRojjjjDNiypQpSeOdO3eOO++8Mxo1alTpuqysrKS6fGi8OoqLi9e7Z2X69OkTN954Y7Xvse4PyE8++eTYZpttKsz5/PPP4x//+Ee8+eabsXDhwmjcuHF069YtjjnmmDjppJMiOzu72vcDAADYHBpESHzdL1ojItq0aVOj9a1atYr09PQoLS1NjC1ZsqTeQuJvvvlm4jHVax1++OF1sndeXl7k5+fXyV7rWrhwYZ3vCQAAwJbnmWeeidtvv73C+JAhQ+KCCy6o1d4HHnhgIiQeEfH6669XOyRe/nP2vvvuu1Gnj5W37pfAxx57bHTt2rXCnPKfwxs3blzlfut+Kb58+fJa9wcAAAD1bfbs2XHWWWdVeKJ327Zt4+9///t6D4ErH5xe+4SxmigfEq/rMPYHH3wQb731VkSs+Vx/xhlnVJgzatSouPrqq5P6X7VqVbz//vvx/vvvx6hRo+Kee+6p9O8KAAAA9aXeQ+L5+fmxevXqpLGaPGo6IiItLS0aN26c9KVtfX0Ru2zZsvjzn/+cNNaoUaM46aST6mT/+++/P0aOHFknewEAAMC6Zs2aFVdccUWF8QsvvDDOPvvsWu/fv3//uPvuuxP1c889F+eff37k5OSsd11ZWVk88cQTSWPHHHNMrfuZOnVqvPrqqxERkZGREeecc06l8zIzk/98su6P1Mtb928caWlpte4RAAAA6tPkyZNjyJAh8d133yWNt2vXLh588MENPuWrWbNmSXVBQUGNeyj/4+26Pixu3R+Qn3TSSdG2bduk66+++mpceeWVUVZWlhhr3759rFq1KpYuXRoRETNmzIhTTjklnn322UpPId9Yp59+ep1lDda1cOHCOOGEE+p8XwAAoGGp95D4qlWrKoyt70SuqjRp0iTpw+HKlStr1dfGKCkpiQsvvDBmz56dNH7GGWdEu3btNns/AAAAUF0lJSXxhz/8ocKXteecc06dBMQjInr37h29evVKPJp60aJFceutt8Yf//jH9a4bNWpUTJ06NVG3adOmTp7YdccddyS+4B0wYECVX2yX/0J72bJlVe657rXmzZvXukcAAACoL+PGjYsLL7ywwt8KOnfuHA888EBsu+22G9yjVatWSfX6PlNXpfyaugxhf/zxxzFx4sSIWHNC+VlnnZV0feXKlTF8+PDE3w+6du0at9xyS/Tu3TtKS0vj+eefjz/96U+xevXqWLBgQVxxxRVxyy231Fl/ubm5NT5kDwAAYK3aP5e5lip7nFT5E7qqIyMjI6ku/8ipTa2srCz+9Kc/xRtvvJE0vtNOO8WvfvWrzdoLAAAA1NQLL7wQkyZNSho7+OCD44ILLqjT+wwdOjSpvu+++2LUqFFVzh83blxcffXVFfao7aOlv/zyy3j55ZcjIiI9PX29n92zs7OjQ4cOiXr69OlVzl33WpcuXWrVIwAAANSXRx55JH7zm99UCIh37949HnnkkWoFxCMiOnbsmFQvWrSoxr2UP8W8/EnftbHuU7wHDhwY7du3T7r+0ksvxbx58yJizd8P/vrXv0bv3r0T9bHHHpv04/eXX3455syZU2f9AQAA1Ea9nyReUlJSYSw9vebZ9fJr1vfo57pWVlYWl112WfzrX/9KGm/WrFncdttttf7iGgAAADa1e+65p8JY796947HHHtvoPY899tgKp131798/DjnkkHj99dcTY1dccUV88MEHceaZZ8bOO+8caWlp8dVXX8XDDz8co0aNSvqM36dPnxg0aNBG97TWuqeI9+/fP7p3777e+b17945vv/02IiLeeOONWLVqVTRq1ChpzqRJk2LBggWJum/fvrXuEwAAADa3kSNHxu23315hfI899oi77747WrRoUe29unbtmlRvTIC6/Jrtt9++xntUZvLkyTFhwoSIiMjKyqr0SWovvvhi4vWee+4ZPXv2rDDnxBNPjBtuuCEKCwujtLQ0xo4dG6effnqd9AgAAFAb9R4Sr+zU8JKSkhqfJl7+RPKsrKxa9VVdJSUlcfHFF8ezzz5b4f4jR46s8lHVAAAA0FB8/PHHMW3atArjd955Z632Pfjggyt9JPJ1110XP/3pT5PuOWbMmBgzZkxkZ2dHRkZGFBYWVljXrl27uPnmmzfqCWTrmjFjRuJL3rS0tAqnm1emf//+8eqrr0bEmlPPrr/++rj00ksT1wsLC+Oqq65K1FlZWXHUUUfVqk8AAADY3G644Ya49957K4wffvjhcfPNN0dOTk6N9ttxxx0jKysr8X3+/PnzIy8vr9K/F1RlxowZSfWGfuhdXX/9618Tr3/0ox9F586dK8xZ96lrffr0qXSfnJyc2GmnneLjjz+OiDXhcwAAgIag3kPilZ2yXVRUVOE0rg0pLi7e4L51beXKlfG73/0uXnvttaTxzMzMuPnmm2O//far83uefvrpcdJJJ9X5vgsXLowTTjihzvcFAACg4Xv77bc36/1atmwZDzzwQPz617+ODz/8MOna6tWrK12zww47xJ133hldunSp9f3vvPPOxOnk/fr1q/QUsPKOOuqouPnmmxOPmH744YdjypQp0a9fvygoKIhnn302vv7668T8448/Ptq1a1frXgEAAGBzGTlyZKUB8UGDBsVll10WGRkZNd4zOzs7evbsGZ988klErHlK9+TJk2P//fev1vqvv/46Fi9enKhbtmxZJwe1TZkyJcaPHx8Ra77fHzJkSIU5eXl5Sffu2LFjlft16NAhERLfmNPSAQAANoV6D4k3a9Ys0tLSEo94jojIz8+v0S+H165ZV9OmTeukv6osXrw4hgwZkvigt1ZWVlbceOONceSRR26S++bm5tb4vQEAAID1WRt83pxat24djzzySDz++OPx4IMPxsyZM6ucN3jw4DjrrLNqfFpZZb766qt4/vnnE3V1ThGPWHMq2PDhw+Pss8+OkpKSiIh477334r333qswd7vttouLLrqo1r0CAADA5vLMM8/E7bffXmF8yJAhccEFF9Rq7wMPPDAREo+IeP3116sdEp8wYUJSve+++0Z6enqt+olIPkX82GOPja5du1aYUz6D0Lhx4yr3W/cQvOXLl9e6PwAAgLpQ7yHxjIyMaN68eSxbtiwxtnz58mjfvn2191i5cmWFk8Zat25dZz2WN2vWrDjrrLOSTgiLWPPB77bbbotDDz10k90bAAAA6tpll10Wl1122Wa/b3p6egwePDgGDx4cX3zxRUydOjUWLlwYRUVF0bJly9h5551jl112iczMuvvzxV133ZUIeR966KGx6667VnvtQQcdFDfddFNcfPHFUVBQUOmcXr16xd133+0H3gAAAGwxZs2aFVdccUWF8QsvvDDOPvvsWu/fv3//uPvuuxP1c889F+eff/4GfwxeVlYWTzzxRNLYMcccU+t+pk6dGq+++mpErMkrnHPOOZXOK//3iLVPJavMunmFtLS0WvcIAABQF+o9JB6x5tFL64bEv/vuu+jRo0e11y9cuDCpzs7OjhYtWtRZf+v64IMP4le/+lUsXbo0aTw3NzfuuOOO2HfffTfJfQEAACCV9ejRo0Z/C9gYs2fPjueeey5R//rXv67xHkcffXTsvvvucd9998WECRNi3rx5kZOTEz169Ihjjz02TjzxxDoNtQMAAMCmVFJSEn/4wx8q/Bj6nHPOqZOAeERE7969o1evXjFlypSIiFi0aFHceuut8cc//nG960aNGhVTp05N1G3atInDDz+81v3ccccdiSedDxgwIHbYYYdK5zVr1iypXjfTUN6615o3b17rHgEAAOpCg/jWsmvXrkkf7ubMmVOj9eXnb7/99nXRVgUTJkyIc889N1auXJk03rZt27j33nujV69em+S+AAAAQO117do1Pv3001rv07Fjx7jkkkvikksuqYOuAAAAoP688MILMWnSpKSxgw8+OC644II6vc/QoUPj3HPPTdT33XdfdOnSJU499dRK548bNy6uvvrqCntkZ2fXqo8vv/wyXn755YhY84SzX/3qV1XOzc7Ojg4dOsS3334bERHTp0+vcu6617p06VKrHgEAAOpKgwiJ9+zZM/E4p4j1f7iqzIwZM5Lqbt261Ulf63rttdfivPPOi6KioqTx7bffPv72t79F165d6/yeAAAAAAAAALCp3HPPPRXGevfuHY899thG73nsscdGbm5u0lj//v3jkEMOiddffz0xdsUVV8QHH3wQZ555Zuy8886RlpYWX331VTz88MMxatSoKC0tTczt06dPDBo0aKN7WmvdU8T79+8f3bt3X+/83r17J0Lib7zxRqxatSoaNWqUNGfSpEmxYMGCRN23b99a9wkAAFAXGkRIvE+fPkl1+V8qb8hHH32UVO+xxx61bSnJxIkTKw2I77777nHXXXfFNttsU6f3AwAAAAAAAIBN6eOPP45p06ZVGL/zzjtrte/BBx9cISQeEXHdddfFT3/606R7jhkzJsaMGRPZ2dmRkZERhYWFFda1a9cubr755sjMrF28YcaMGfHiiy9GRERaWloMHTp0g2v69++fOPBu0aJFcf3118ell16auF5YWBhXXXVVos7KyoqjjjqqVn0CAADUlfT6biAiYu+9946srKxE/fHHH8eyZcuqtbakpCT+85//JI0dcMABddbbtGnT4re//W2FgPiBBx4YDzzwgIA4AAAAAAAAAFuct99+e7Per2XLlvHAAw9Ueujb6tWrKw2I77DDDvHQQw9Fly5dan3/O++8M3E6eb9+/aJnz54bXHPUUUdFx44dE/XDDz8cp556atx3330xcuTIOO6445IOtTv++OOjXbt2te4VAACgLjSIkHjTpk3joIMOStTFxcXx9NNPV2vta6+9FosWLUrUPXr0iB49etRJX6tXr44LL7ww8vPzk8YPP/zwuOuuu6JJkyZ1ch8AAAAAAAAA2JzmzZu32e/ZunXreOSRR2LYsGGxww47rHfeb37zm/jXv/613nnV9dVXX8Xzzz+fqKtzinhERE5OTgwfPjwyMjISY++9915ce+21cfvtt8fXX3+dGN9uu+3ioosuqnWvAAAAdaV2z2OqQyeeeGKMHz8+Ud91111x1FFHRadOnapcs3z58rj++uuTxk466aQ66+nmm2+u8HitffbZJ2699dbIzs6us/sAAAAAAAAAwOZ02WWXxWWXXbbZ75uenh6DBw+OwYMHxxdffBFTp06NhQsXRlFRUbRs2TJ23nnn2GWXXSIzs+7iDHfddVeUlJRERMShhx4au+66a7XXHnTQQXHTTTfFxRdfHAUFBZXO6dWrV9x9992Rm5tbJ/0CAADUhQYTEu/Xr1/svPPO8fnnn0dExNKlS2PIkCFx3333RZs2bSrMz8/Pj/POOy/pl7kdO3aMQYMGVXmPp59+Oi6++OKksYceeij23XffCnO/+eabeOihh5LG2rdvH7fddpuAOAAAAAAAAADUUl0+Kbwqs2fPjueeey5R//rXv67xHkcffXTsvvvucd9998WECRNi3rx5kZOTEz169Ihjjz02TjzxxDoNtQPA1iYrMy3S09Lquw3qSVZmWhQXFyeNlWVkR2TKaVIzZRnZFf5bWistLS3S09MjbSv7/zUN5lNKWlpaDBs2LE455ZQoLS2NiIipU6fG8ccfH+edd14MGDAgcnNzY9WqVTF+/Pi49dZbY8aMGUl7/PnPf45GjRrVST/33Xdfhf9Y9txz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8YWCg4N17do1q32bNGmiwMBAdenSJdvHuefs2bP68ssvtXnzZsXHx1ttYzKZ1LhxYz311FMaNGiQ3N3d7R5fKpjXCQAAICvkAZbIA8gDABiRB5AHAAAAx0MeYIk8gDwAgBF5AHkASienop4AgILRunVrQzk5OVkjRozQL7/8YnEXcUHavn27+vXrp23btlldAEp37/D98MMP1a9fP4WHh9s1bnJysmbNmqXevXtr+fLl2S4AJSkqKkpLly7VE088oW3btuXoeUjSBx98oKVLl1o8j/T0dO3cudNwt29sbKxefPFFvf7669kezRIfH68tW7Zo+PDhmjx5shITE+2az4ULF9S3b19NnTrV5gJQurtQPHz4sMaNG6cXXnjBfBccAEjSww8/bChHREToxo0bNtunpKRoxowZevrpp7VhwwabCxvp7jdELFiwQF27dtWuXbtyNb+TJ0+qT58+Wrhwoc0FoCSdOHFCEyZM0CuvvKKUlJRsx122bJn69OmjtWvX2lwASnff40+cOKFZs2apZ8+eOnXqlF3zLuzXCQAA4B7yAEvkAeQBACyRB5AHAAAAx0IeYIk8gDwAgCXyAPIAlD5sEgccVNeuXeXt7W2ou3nzpiZOnKgnnnhCCxcu1IULFwp0DqdOndLLL7+smJgYQ33ZsmWtHut05swZDR48WBEREVmOm56erkmTJmnx4sVWF5aenp6qUKGCzSOa4uLiNHHixGwfJ6Nff/1V3377rc3r7du3V9WqVSXdXXSNGzfO6ge5q6urfHx8LP63uWfdunV68803s53P0aNHNWjQIJ08edLimrOzs8qXLy8nJ8u3+F27dmnQoEH6888/s30MAKVDw4YNLeqOHz9utW18fLzGjh2rpUuXKi0tzeK6t7e3ypQpY1F//fp1jRkzRmvWrMnR3CIiIvTiiy9afF55enravGP3559/1owZM7Ic93//+5+mT59udbHo7e0tHx8fubhYHrhz6dIlDR8+XFevXs1y/MJ+nQAAADIiDyAPIA8AYA/yACPyAAAAUNKRB5AHkAcAsAd5gBF5AEoDNokDDqps2bIaP3681Wvnz5/XJ598om7duqlr16565513tH79+izvwMqNN99803xnlK+vr6ZNm6b9+/frwIEDOnbsmBYvXqyHHnrI0OfatWt6+eWXlZycbHPcNWvWaOPGjYa6hx56SEFB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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 9 + }, + { + "cell_type": "code", + "outputs": [ + { + "data": { + "text/plain": "([], [])" + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "handles, labels" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-03-25T12:41:19.201746Z", + "start_time": "2024-03-25T12:41:19.193747Z" + } + }, + "id": "62be2d4d86bf327", + "execution_count": 32 + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/test/test_prune.py b/test/test_prune.py index fc3ddc4..547e526 100644 --- a/test/test_prune.py +++ b/test/test_prune.py @@ -9,7 +9,7 @@ def setUp(self): self.swc = parse_swc('data/anneal_output.swc') def test_prune(self): - pruner = ErrorPruning([.25, .25, 1], anchor_reach=(5., 20.)) + pruner = ErrorPruning([.25, .25, 1], anchor_dist=(5., 20.)) morph = Morphology(self.swc) a = pruner.branch_prune(morph, 45) b = pruner.crossover_prune(morph, 5) diff --git a/test/test_soma.py b/test/test_soma.py index b94d101..643fb2b 100644 --- a/test/test_soma.py +++ b/test/test_soma.py @@ -62,8 +62,17 @@ def test5_tile_dt(self): plt.show() def test6_consensus(self): + path = r"D:\rectify\crop_8bit\18453_9442_3817_6561.v3dpbd" + img = PBD().load(path)[0] centers_list = [] centers = DetectImage().predict(self.img, self.res) + fig, ax = plt.subplots() + ax.imshow(img.max(axis=0), cmap='gray') + for p in centers: + ax.plot(p[2], p[1], '.r', markersize=15) + plt.show() + plt.title('Image') + centers_list.append(centers) centers = DetectTiledImage([300, 300, 200], nproc=16).predict( self.img, [.25, .25, 1.]) @@ -77,8 +86,7 @@ def test6_consensus(self): centers_list.append(centers) centers = soma_consensus(*centers_list, res=self.res) print(centers) - path = r"D:\rectify\crop_8bit\18453_9442_3817_6561.v3dpbd" - img = PBD().load(path)[0] + fig, ax = plt.subplots() ax.imshow(img.max(axis=0), cmap='gray') for p in centers: diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/math_utils.py b/utils/math_utils.py new file mode 100644 index 0000000..1d36720 --- /dev/null +++ b/utils/math_utils.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +#================================================================ +# Copyright (C) 2021 Yufeng Liu (Braintell, Southeast University). All rights reserved. +# +# Filename : math_utils.py +# Author : Yufeng Liu +# Date : 2021-07-19 +# Description : +# +#================================================================ + +import math +import numpy as np +from scipy.spatial import distance_matrix +from sklearn.neighbors import KDTree + + +def calc_included_angles_from_vectors(vecs1, vecs2, return_rad=False, epsilon=1e-7, spacing=None, return_cos=False): + if vecs1.ndim == 1: + vecs1 = vecs1.reshape((1,-1)) + if vecs2.ndim == 1: + vecs2 = vecs2.reshape((1,-1)) + + if spacing is not None: + spacing_reshape = np.array(spacing).reshape(1,-1) + # rescale vectors according to spacing + vecs1 = vecs1 * spacing_reshape + vecs2 = vecs2 * spacing_reshape + + inner = (vecs1 * vecs2).sum(axis=1) + norms = np.linalg.norm(vecs1, axis=1) * np.linalg.norm(vecs2, axis=1) + cos_ang = inner / (norms + epsilon) + + if return_cos: + return_val = cos_ang + else: + rads = np.arccos(np.clip(cos_ang, -1, 1)) + if return_rad: + return_val = rads + else: + return_val = np.rad2deg(rads) + return return_val + + +def calc_included_angles_from_coords(anchor_coords, coords1, coords2, return_rad=False, epsilon=1e-7, spacing=None, return_cos=False): + anchor_coords = np.array(anchor_coords) + coords1 = np.array(coords1) + coords2 = np.array(coords2) + v1 = coords1 - anchor_coords + v2 = coords2 - anchor_coords + angs = calc_included_angles_from_vectors( + v1, v2, return_rad=return_rad, + epsilon=epsilon, spacing=spacing, + return_cos=return_cos) + return angs + + +def memory_safe_min_distances(voxels1, voxels2, num_thresh=50000, return_index=False): + # verified + nv1 = len(voxels1) + nv2 = len(voxels2) + if (nv1 > num_thresh) or (nv2 > num_thresh): + # use block wise calculation + vq1 = [voxels1[i*num_thresh:(i+1)*num_thresh] for i in range(int(math.ceil(nv1/num_thresh)))] + vq2 = [voxels2[i*num_thresh:(i+1)*num_thresh] for i in range(int(math.ceil(nv2/num_thresh)))] + + dists1 = np.ones(nv1) * 1000000. + dists2 = np.ones(nv2) * 1000000. + if return_index: + min_indices1 = np.ones(nv1) * -1 + min_indices2 = np.ones(nv2) * -1 + for i,v1 in enumerate(vq1): + idx00 = i * num_thresh + idx01 = i * num_thresh + len(v1) + for j,v2 in enumerate(vq2): + idx10 = j * num_thresh + idx11 = j * num_thresh + len(v2) + + d = distance_matrix(v1, v2) + dmin1 = d.min(axis=1) + dmin0 = d.min(axis=0) + dists1[idx00:idx01] = np.minimum(dmin1, dists1[idx00:idx01]) + dists2[idx10:idx11] = np.minimum(dmin0, dists2[idx10:idx11]) + if return_index: + dargmin1 = np.argmin(d, axis=1) + dargmin0 = np.argmin(d, axis=0) + mask1 = np.nonzero(dmin1 < dists1[idx00:idx01]) + min_indices1[idx00:idx01][mask1[0]] = dargmin1[mask1[0]] + idx00 + mask0 = np.nonzero(dmin0 < dists2[idx10:idx11]) + min_indices2[idx10:idx11][mask0[0]] = dargmin0[mask0[0]] + idx10 + else: + pdist = distance_matrix(voxels1, voxels2) + dists1 = pdist.min(axis=1) + dists2 = pdist.min(axis=0) + if return_index: + min_indices1 = pdist.argmin(axis=1) + min_indices2 = pdist.argmin(axis=0) + + if return_index: + return dists1, dists2, min_indices1, min_indices2 + else: + return dists1, dists2 + + +def min_distances_between_two_sets(voxels1, voxels2, topk=1, reciprocal=True, return_index=False): + """ + We should use kd-tree instead of brute-force method for large-scale data inputs. Arguments are: + @params voxels1: coordinates of points, np.ndarray in shape[N, 3] + @params voxels2: coordinates of points, np.ndarray in shape[M, 3] + @params topk: the number of top-ranking match + @params reciprocal: whether to calculate 2->1, except for 1->2 + @params return_index: whehter to return the indices of points with minimal distances + """ + tree2 = KDTree(voxels2, leaf_size=2) + dmin1, imin1 = tree2.query(voxels1, k=topk) + if reciprocal: + tree1 = KDTree(voxels1, leaf_size=2) + dmin2, imin2 = tree1.query(voxels2, k=topk) + if return_index: + return dmin1, dmin2, imin1, imin2 + else: + return dmin1, dmin2 + else: + if return_index: + return dmin1, imin1 + else: + return dmin1 + + + diff --git a/utils/metrics.py b/utils/metrics.py new file mode 100644 index 0000000..744bf6d --- /dev/null +++ b/utils/metrics.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python +import os + +#================================================================ +# Copyright (C) 2023 Yufeng Liu (Braintell, Southeast University). All rights reserved. +# +# Filename : metrics.py +# Author : Yufeng Liu +# Date : 2023-04-04 +# Description : +# +#================================================================ + +import numpy as np + +from utils.swc_handler import tree_to_voxels, parse_swc +from utils.math_utils import min_distances_between_two_sets + +class DistanceEvaluation(object): + def __init__(self, dsa_thr=2., resample1=True, resample2=True): + self.dsa_thr = dsa_thr + self.resample1 = resample1 + self.resample2 = resample2 + + def calc_dist(self, voxels1, voxels2): + ds = { + 'ESA': None, + 'DSA': None, + 'PDS': None, + } + + dists1, dists2 = min_distances_between_two_sets(voxels1, voxels2, reciprocal=True, return_index=False) + for key in ds.keys(): + if key == 'DSA': + dists1_ = dists1[dists1 > self.dsa_thr] + dists2_ = dists2[dists2 > self.dsa_thr] + if dists1_.shape[0] == 0: + dists1_ = np.array([0.]) + if dists2_.shape[0] == 0: + dists2_ = np.array([0.]) + elif key == 'PDS': + dists1_ = (dists1 > self.dsa_thr).astype(np.float32) + dists2_ = (dists2 > self.dsa_thr).astype(np.float32) + elif key == 'ESA': + dists1_ = dists1 + dists2_ = dists2 + ds[key] = dists1_.mean(), dists2_.mean(), (dists1_.sum() + dists2_.sum()) / (len(dists1) + len(dists2)) + ds = np.array(list(ds.values())) + return ds + + def run(self, reconfile, gsfile): + if type(reconfile) is str or isinstance(reconfile, os.PathLike): + tree1 = parse_swc(reconfile) + else: + tree1 = reconfile + if type(gsfile) is str or isinstance(reconfile, os.PathLike): + tree2 = parse_swc(gsfile) + else: + tree2 = gsfile + #print(f'#nodes for recon and gs: {len(tree1)}, {len(tree2)}') + + if self.resample1: + voxels1 = tree_to_voxels(tree1, crop_box=(10000,10000,10000)) + else: + voxels1 = np.array([node[2:5] for node in tree1]) + if self.resample2: + voxels2 = tree_to_voxels(tree2, crop_box=(10000,10000,10000)) + else: + voxels2 = np.array([node[2:5] for node in tree2]) + + if len(voxels1) == 0 or len(voxels2) == 0: + print(len(voxels1), len(voxels2)) + return None + + ds = self.calc_dist(voxels1, voxels2) + return ds + + +if __name__ == '__main__': + gsfile = '/home/lyf/Research/cloud_paper/micro_environ/benchmark/gs_crop/18452_4536_x11274_y21067.swc' + reconfile = '/home/lyf/Research/cloud_paper/micro_environ/benchmark/recon1891_weak/18452/5642_10537_2271.swc' + de = DistanceEvaluation() + ds = de.run(reconfile, gsfile) + print(ds) + + diff --git a/utils/swc_handler.py b/utils/swc_handler.py new file mode 100644 index 0000000..d5419cf --- /dev/null +++ b/utils/swc_handler.py @@ -0,0 +1,430 @@ +"""*================================================================ +* Copyright (C) 2021 Yufeng Liu (Braintell, Southeast University). All rights reserved. +* +* Filename : swc_handler.py +* Author : Yufeng Liu +* Date : 2021-03-15 +* Description : +* +================================================================*""" +import re +import numpy as np +from copy import deepcopy +from skimage.draw import line_nd + +NEURITE_TYPES = { + 'soma': [1], + 'axon': [2], + 'basal dendrite': [3], + 'apical dendrite': [4], + 'dendrite': [3,4], +} + + +def load_spacings(spacing_file, zxy_order=False): + """ + Load the spacing information for each brain. The spacing here refers to + the resolution along x,y,z axes. + """ + spacing_dict = {} + with open(spacing_file, 'r') as fp: + for line in fp.readlines(): + line = line.strip() + if not line: continue + ctxts = line.split(',') + brain_id = ctxts[0] + if not brain_id.isdigit(): + continue # the brain is encoded as digits + + brain_id = int(brain_id) + spacing = tuple(map(float, ctxts[1:])) + if zxy_order: + spacing = (spacing[2],spacing[0],spacing[1]) + spacing_dict[brain_id] = spacing + + return spacing_dict + + +def parse_swc(swc_file): + tree = [] + with open(swc_file) as fp: + for line in fp.readlines(): + line = line.strip() + if not line: continue + if line[0] == '#': continue + idx, type_, x, y, z, r, p = line.split()[:7] + idx = int(idx) + type_ = int(type_) + x = float(x) + y = float(y) + z = float(z) + r = float(r) + p = int(p) + tree.append((idx, type_, x, y, z, r, p)) + + return tree + + +def write_swc(tree, swc_file, header=tuple()): + if header is None: + header = [] + with open(swc_file, 'w') as fp: + for s in header: + if not s.startswith("#"): + s = "#" + s + if not s.endswith("\n") or not s.endswith("\r"): + s += "\n" + fp.write(s) + fp.write(f'##n type x y z r parent\n') + for leaf in tree: + idx, type_, x, y, z, r, p = leaf + fp.write(f'{idx:d} {type_:d} {x:.5f} {y:.5f} {z:.5f} {r:.1f} {p:d}\n') + + +def find_soma_node(tree, p_soma=-1, p_idx_in_leaf=6): + for leaf in tree: + if leaf[p_idx_in_leaf] == p_soma: + #print('Soma: ', leaf) + return leaf[0] + #raise ValueError("Could not find the soma node!") + return -99 + + +def find_soma_index(tree, p_soma=-1): + for i, leaf in enumerate(tree): + if leaf[6] == p_soma: + return i + #raise ValueError("find_soma_index: Could not find the somma node!") + return -99 + + +def get_child_dict(tree, p_idx_in_leaf=6): + child_dict = {} + for leaf in tree: + p_idx = leaf[p_idx_in_leaf] + if p_idx in child_dict: + child_dict[p_idx].append(leaf[0]) + else: + child_dict[p_idx] = [leaf[0]] + return child_dict + + +def get_index_dict(tree): + index_dict = {} + for i, leaf in enumerate(tree): + idx = leaf[0] + index_dict[idx] = i + return index_dict + + +def is_in_box(x, y, z, imgshape): + """ + imgshape must be in (z,y,x) order + """ + if x < 0 or y < 0 or z < 0 or \ + x > imgshape[2] - 1 or \ + y > imgshape[1] - 1 or \ + z > imgshape[0] - 1: + return False + return True + +def is_in_bbox(x, y, z, zyxzyx): + """ + zyxzyx is bbox in format of [(zmin, ymin, xmin), (zmax, ymax, xmax)] + """ + (zmin, ymin, xmin), (zmax, ymax, xmax) = zyxzyx + if x < xmin or y < ymin or z < zmin or \ + x > xmax or \ + y > ymax or \ + z > zmax: + return False + return True + +def prune(tree: list, ind_set: set): + """ + prune all nodes given by ind_set in morph + """ + child_dict = get_child_dict(tree) + index_dict = get_index_dict(tree) + tree = deepcopy(tree) + for i in ind_set: + q = [] + ind = index_dict[i] + if tree[ind] is None: + continue + tree[ind] = None + if i in child_dict: + q.extend(child_dict[i]) + while len(q) > 0: + head = q.pop(0) + ind = index_dict[head] + if tree[ind] is None: + continue + tree[ind] = None + if head in child_dict: + q.extend(child_dict[head]) + return [t for t in tree if t is not None] + + +def trim_swc(tree_orig, imgshape, keep_candidate_points=True, bfs=True): + """ + Trim the out-of-box and non_connecting leaves + """ + if bfs: + ib = set(t[0] for t in tree_orig if is_in_box(*t[2:5], imgshape)) + if keep_candidate_points: + child_dict = get_child_dict(tree_orig) + ib = ib.union(*(child_dict[i] for i in ib if i in child_dict)) + return prune(tree_orig, set(t[0] for t in tree_orig) - ib) + + def traverse_leaves(idx, child_dict, good_points, cand_pints, pos_dict): + leaf = pos_dict[idx] + p_idx, ib = leaf[-2:] + + if (p_idx in good_points) or (p_idx == -1): + if ib: + good_points.add(idx) # current node + else: + cand_points.add(idx) + return + + if idx not in child_dict: + return + + for new_idx in child_dict[idx]: + traverse_leaves(new_idx, child_dict, good_points, cand_pints, pos_dict) + + # execute trimming + pos_dict = {} + tree = deepcopy(tree_orig) + for i, leaf in enumerate(tree_orig): + idx, type_, x, y, z, r, p = leaf + leaf = (idx, type_, x, y, z, r, p, is_in_box(x,y,z,imgshape)) + pos_dict[idx] = leaf + tree[i] = leaf + + good_points = set() # points and all its upstream parents are in-box + cand_points = set() # all upstream parents are in-box, itself not + # initialize the visited set with soma, whose parent index is -1 + soma_idx = None + for leaf in tree: + if leaf[-2] == -1: + soma_idx = leaf[0] + break + #print(soma_idx) + + child_dict = {} + for leaf in tree: + if leaf[-2] in child_dict: + child_dict[leaf[-2]].append(leaf[0]) + else: + child_dict[leaf[-2]] = [leaf[0]] + # do DFS searching + #print(soma_idx) + traverse_leaves(soma_idx, child_dict, good_points, cand_points, pos_dict) + #print("#good/#cand/#total:", len(good_points), len(cand_points), len(pos_dict)) + + # return the tree, (NOTE: without order) + tree_trim = [] + if keep_candidate_points: + keep_points = good_points | cand_points + else: + keep_points = good_points + + for i, leaf in enumerate(tree): + idx = leaf[0] + if idx in keep_points: + tree_trim.append(leaf[:-1]) + + return tree_trim + + +def trim_out_of_box(tree_orig, imgshape, keep_candidate_points=True): + """ + Trim the out-of-box leaves + """ + # execute trimming + child_dict = {} + for leaf in tree_orig: + if leaf[-1] in child_dict: + child_dict[leaf[-1]].append(leaf[0]) + else: + child_dict[leaf[-1]] = [leaf[0]] + + pos_dict = {} + for i, leaf in enumerate(tree_orig): + pos_dict[leaf[0]] = leaf + + tree = [] + for i, leaf in enumerate(tree_orig): + idx, type_, x, y, z, r, p = leaf + ib = is_in_box(x,y,z,imgshape) + if ib: + tree.append(leaf) + elif keep_candidate_points: + if p in pos_dict and is_in_box(*pos_dict[p][2:5], imgshape): + tree.append(leaf) + elif idx in child_dict: + for ch_leaf in child_dict[idx]: + if is_in_box(*pos_dict[ch_leaf][2:5], imgshape): + tree.append(leaf) + break + return tree + + +def get_specific_neurite(tree, type_id): + if (not isinstance(type_id, list)) and (not isinstance(type_id, tuple)): + type_id = (type_id,) + + new_tree = [] + for leaf in tree: + if leaf[1] in type_id: + new_tree.append(leaf) + return new_tree + + +def shift_swc(swc_file, sx, sy, sz): + if type(swc_file) == list: + tree = swc_file + else: + tree = parse_swc(swc_file) + new_tree = [] + for node in tree: + idx, type_, x, y, z, r, p = node + x = x - sx + y = y - sy + z = z - sz + node = (idx, type_, x, y, z, r, p) + new_tree.append(node) + return new_tree + + +def scale_swc(swc_file, scale): + if type(swc_file) == list: + tree = swc_file + else: + tree = parse_swc(swc_file) + if isinstance(scale, (int, float)): + scale_x, scale_y, scale_z = scale, scale, scale + elif isinstance(scale, tuple) or isinstance(scale, list): + scale_x, scale_y, scale_z = scale + else: + raise NotImplementedError(f"Type of parameter scale {type(scale)} is not supported!") + + new_tree = [] + for node in tree: + idx, type_, x, y, z, r, p = node + x *= scale_x + y *= scale_y + z *= scale_z + node = (idx, type_, x, y, z, r, p) + new_tree.append(node) + return new_tree + +def flip_swc(swc_file, axis='y', dim=None): + if type(swc_file) == list: + tree = swc_file + else: + tree = parse_swc(swc_file) + + new_tree = [] + for node in tree: + idx, type_, x, y, z, r, p = node + if axis == 'x': + x = dim - x + elif axis == 'y': + y = dim - y + elif axis == 'z': + z = dim - z + node = (idx, type_, x, y, z, r, p) + new_tree.append(node) + return new_tree + +def crop_tree_by_bbox(morph, bbox, keep_candidate_points=True): + """ + Crop swc by trim all nodes out-of-bbox. This function differs from `trim_out_of_box` it does + not assume center cropping + """ + if isinstance(morph, list): + mtree = morph + else: + mtree = morph.tree + + tree = [] + for i, leaf in enumerate(morph.tree): + idx, type_, x, y, z, r, p = leaf[:7] + ib = is_in_bbox(x,y,z,bbox) + if ib: + tree.append(leaf) + if keep_candidate_points and (idx in morph.child_dict): + for ch_leaf in morph.child_dict[idx]: + if not is_in_bbox(*morph.pos_dict[ch_leaf][2:5], bbox): + tree.append(morph.pos_dict[ch_leaf]) + return tree + + +def tree_to_voxels(tree, crop_box): + # crop_box in (z,y,x) order + # initialize position dict + pos_dict = {} + new_tree = [] + for i, leaf in enumerate(tree): + idx, type_, x, y, z, r, p = leaf + leaf_new = (*leaf, is_in_box(x,y,z,crop_box)) + pos_dict[leaf[0]] = leaf_new + new_tree.append(leaf_new) + tree = new_tree + + xl, yl, zl = [], [], [] + for _, leaf in pos_dict.items(): + idx, type_, x, y, z, r, p, ib = leaf + if p == -1: continue # soma + + if p not in pos_dict: + continue + + parent_leaf = pos_dict[p] + if (not ib) and (not parent_leaf[ib]): + print('All points are out of box! do trim_swc before!') + raise ValueError + + # draw line connecting each pair + cur_pos = leaf[2:5] + par_pos = parent_leaf[2:5] + lin = line_nd(cur_pos[::-1], par_pos[::-1], endpoint=True) + xl.extend(list(lin[2])) + yl.extend(list(lin[1])) + zl.extend(list(lin[0])) + + voxels = [] + for (xi,yi,zi) in zip(xl,yl,zl): + if is_in_box(xi,yi,zi,crop_box): + voxels.append((xi,yi,zi)) + # remove duplicate points + voxels = np.array(list(set(voxels)), dtype=np.float32) + return voxels + + +def rm_disconnected(tree: list, anchor: int): + roots = [t[0] for t in tree if t[6] == -1] + ch = get_child_dict(tree) + idx = get_index_dict(tree) + flag = np.zeros(len(tree), dtype=int) + for r in roots: + q = [r] + while len(q) > 0: + head = q.pop(0) + flag[idx[head]] = r + if head in ch: + q.extend(ch[head]) + ind = flag[idx[anchor]] + return prune(tree, set(t[0] for t, f in zip(tree, flag) if f != ind)) + +def get_soma_from_swc(swcfile): + # fast parse swc information + # only for swc, not eswc + with open(swcfile) as fp: + soma_str = re.search('.* -1\n', fp.read()).group() + soma = soma_str.split() + return soma +