Source code for openalea.rsml.matching

"""
Simple matching functionalities for RSML MTG trees
"""

import numpy as _np

from openalea.rsml.misc import plant_vertices

[docs] def match_plants(t1,t2, max_distance=None): """ Find a 1-to-1 matching between plants in `t1` & `t2` The matching is done to minimize distance between plants "seed": the mean of the first node of all primary axes. This function returns: - the set of matched (t1-plant-id,t2-plant-id,distance) - the set of unmatch t1 plant id - the set of unmatch t2 plant id The matching is done usinf `one_to_one_match` """ from operator import truediv as div # compute seed position of plants in t # ------------------------------------ def seeds_position(t): """ return dict of (plant-id, (x, y)) """ plants = plant_vertices(t) seed_pos = {} geometry = t.property('geometry') for plant in plants: axes = t.component_roots(plant) apos = list(zip(*[geometry[axe][0] for axe in axes])) spos = list(map(div, list(map(sum,apos)), list(map(len,apos)))) seed_pos[plant] = spos return seed_pos seed1 = seeds_position(t1) seed2 = seeds_position(t2) # make distance matrix # -------------------- I1 = list(seed1.keys()) I2 = list(seed2.keys()) X1 = _np.array(list(seed1.values()))[:,None,:] # (n1, 1,k-dim) X2 = _np.array(list(seed2.values()))[None,:,:] # ( 1,n2,k-dim) D = ((X1-X2)**2).sum(axis=-1)**.5 # (n1,n2) matched,u1,u2 = one_to_one_match(D,max_distance=max_distance) # convert array id to plant id matched = set((I1[p1],I2[p2],d) for p1,p2,d in matched) unmatched1 = set(I1[p1] for p1 in u1) unmatched2 = set(I2[p2] for p2 in u2) return matched, unmatched1, unmatched2
[docs] def match_roots(t1,t2, plant_match, max_distance=None): """ Iteratively match root axes following the trees topology This function iteratively match root axes from 1st to last (topological) order. The matching is selected using `one_to_one_match` using hausdorff distance. :Inputs: t1: 1st rsml-mtg t2: 2nd rsml-mtg plant_match: match between plants in `t1` and `t2` :Outputs: - the set of root matches as (t1_root_id, t2_root_id, hausdorff-distance) - the set of unmatched root in t1 - the set of unmatched root in t2 Note: Children of unmatched elements are not listed in unmatched sets """ match = set() queue = set() unmatched1 = set() unmatched2 = set() def find_match(axes1, axes2): if len(axes1) and len(axes2): m,u1,u2 = _match_root_axes(t1=t1,axes1=axes1,t2=t2,axes2=axes2, max_distance=max_distance) else: m = [] u1 = axes1 u2 = axes2 match.update(m) queue.update(m) unmatched1.update(u1) unmatched2.update(u2) for p1,p2,d in plant_match: axes1 = t1.component_roots(p1) axes2 = t2.component_roots(p2) find_match(axes1,axes2) while len(queue): r1,r2,d = queue.pop() axes1 = t1.children(r1) axes2 = t2.children(r2) find_match(axes1,axes2) return match, unmatched1, unmatched2
def _match_root_axes(t1, axes1, t2, axes2, max_distance=None): """ return "best" match between axes in `axes1` and in `axes2` """ from openalea.rsml.misc import hausdorff_distance # construct distance matrix geom1 = t1.property('geometry') geom2 = t2.property('geometry') D = _np.zeros((len(axes1),len(axes2))) axe2_poly = {} for i,axe1 in enumerate(axes1): p1 = _np.array(geom1[axe1]) for j,axe2 in enumerate(axes2): if axe2 in axe2_poly: p2 = axe2_poly[axe2] else: p2 = _np.array(geom2[axe2]) axe2_poly[axe2] = p2 D[i,j] = hausdorff_distance(p1.T,p2.T) # compute and return matching m, u1, u2 = one_to_one_match(D, max_distance=max_distance) matches = set((axes1[i],axes2[j],d) for i,j,d in m) unmatched1 = set(axes1[i] for i in u1) unmatched2 = set(axes2[j] for j in u2) return matches, unmatched1, unmatched2
[docs] def one_to_one_match(distance, max_distance=None): """ select minimal 1-to-1 matching in `distance` matrix, iteratively Iteratively select the match `(i,j)` with minimum value in `distance` but only if both `i` and `j` were not matched at a previous step. If `max_distance` is not None, don't match pairs with a distance value higher than given `max_distance` Return: - the set match {(i0,j0,d0),(i1,j1,d1),...} where i*,j*,d* are the 1st, 2nd indices, and their distance - the set of unmatch i - the set of unmatch j example:: d = np.random.randint(0,10,5,4) m,i,j = direct_match(d) print '\n'.join(str(ij)+' matched with d='+str(di) for ij,di in zip(m,d[zip(*m)])) """ distance = _np.asarray(distance) ni,nj = distance.shape distance = distance.ravel() order = distance.argsort() ij = list(zip(*_np.unravel_index(order,(ni,nj)))) d = distance[order].tolist() if max_distance is None: max_distance=d[-1] match = set() mi = set() mj = set() for (i,j),dij in zip(ij,d): if dij>max_distance or len(mi)==ni or len(mj)==nj: break elif i in mi or j in mj: continue match.add((i,j,dij)) mi.add(i) mj.add(j) unmatched_i = mi.symmetric_difference(list(range(ni))) unmatched_j = mj.symmetric_difference(list(range(nj))) return match, unmatched_i, unmatched_j