def set_samples(self, xyz_input): """ Set the observed data as [N,3] array, x,y, and depth """ # Get the mapping of sample location to dual cell, with # potentially multiple samples mapping to the same cell. xyz_to_cell = np.zeros(len(xyz_input), np.int32) - 1 for i, xyz in enumerate(xyz_input): if i % 1000 == 0: print("%d/%d" % (i, len(xyz_input))) # select by cell in the original grid to be sure that # samples fall within that "control volume" c = self.t_dual.select_cells_nearest(xyz[:2], inside=True) if c is not None: xyz_to_cell[i] = c # make sure we're starting clean self.t_net.nodes['value'][:] = np.nan # Install the averages onto the values field of the t_net nodes for c, hits in utils.enumerate_groups(xyz_to_cell): if c not in self.dual_to_net_node: continue # not part of the clipped region n = self.dual_to_net_node[c] # This could be smarter about locally fitting a surface # and get a proper circumcenter value. self.t_net.nodes['value'][n] = xyz_input[:, 2][hits].mean()
def recon(xy, k=1000, eps=0.5, interp='rbf'): target = [0, xy[0], xy[1]] nbr_dists, nbrs = kdt_txy.query(target, k=k) nbrs = np.array(nbrs) if 0: # trim to a single, closest sample per track nbr_tracks = all_to_track_i[nbrs, 0] slim_nbrs = [] for k, idxs in utils.enumerate_groups(nbr_tracks): best = np.argmin(nbr_dists[idxs]) slim_nbrs.append(nbrs[idxs[best]]) nbrs = np.array(slim_nbrs) nbr_z = xyzs[all_to_track_i[nbrs, 0], 2] if interp == 'rbf': try: rbf = Rbf(all_txy[nbrs, 0], all_txy[nbrs, 1], all_txy[nbrs, 2], nbr_z, epsilon=eps, function='linear') except np.linalg.LinAlgError: print("Linear algebra error") return np.nan z_pred = rbf(*target) elif interp == 'mlr': clf = linear_model.LinearRegression() clf.fit(all_txy[nbrs, :], nbr_z) z_pred = clf.predict([target])[0] elif interp == 'griddata': z_pred = scipy.interpolate.griddata(all_txy[nbrs, :], nbr_z, target, rescale=True) elif interp == 'krige': points = all_txy[nbrs, :] values = nbr_z gp = GaussianProcess(theta0=0.1, thetaL=.001, thetaU=1., nugget=0.001) gp.fit(all_txy[nbrs, :], nbr_z) z_pred = gp.predict([target])[0] elif interp == 'idw': delta = all_txy[nbrs, :] - np.asarray(target) delta[:, 0] *= 10 # rescale streamdistance? dists = utils.mag(delta) weights = (dists + eps)**(-2) weights = weights / weights.sum() z_pred = (weights * nbr_z).sum() return z_pred
def filter_fixes_by_speed(fish,max_speed=5.0): # pre-allocate, in case there are indices that # have no fixes. posns=[ [] ]*fish.dims['index'] fix_xy=np.c_[ fish.fix_x.values, fish.fix_y.values ] for key,grp in utils.enumerate_groups(fish.fix_idx.values): posns[key].append(fix_xy[grp]) for idx in range(fish.dims['index']): if len(posns[idx])<2: continue # only filtering out multi-fixes for pxy in posns[idx]: for pxy_previous in posns[idx-1]: dist=utils.dist(pxy-pxy_previous) dt=HERE - but use YAPS instead
def gen_aggregation_shp(model): pnts = model.grid.cells_centroid() # make this deterministic np.random.seed(37) centroids, labels = vq.kmeans2(pnts, k=20, iter=5, minit='points') permute = np.argsort(np.random.random(labels.max() + 1)) # Make a shapefile out of that polys = [] for k, grp in utils.enumerate_groups(labels): grp_poly = ops.cascaded_union( [model.grid.cell_polygon(i) for i in grp]) assert grp_poly.type == 'Polygon', "Hmm - add code to deal with multipolygons" polys.append(grp_poly) agg_shp_fn = "dwaq_aggregation.shp" wkb2shp.wkb2shp(agg_shp_fn, polys, overwrite=True) return agg_shp_fn
def disp_array(self): hydro = self.hydro hydro.infer_2d_elements() hydro.infer_2d_links() K = np.zeros(self.hydro.n_exch, np.float64) Qaccum = np.zeros(hydro.n_2d_links, np.float64) Aaccum = np.zeros(hydro.n_2d_links, np.float64) accum_count = 0 for ti in range(self.ti_start, self.ti_stop): t_sec = hydro.t_secs[ti] flows = [h.flows(t_sec) for h in [self.hydro_tidal, self.hydro]] flow_hp = flows[0] - flows[1] # depth-integrate flow_hor = flow_hp[:self.hydro_tidal.n_exch_x] link_flows = np.bincount(hydro.exch_to_2d_link['link'], hydro.exch_to_2d_link['sgn'] * flow_hor) Qaccum += link_flows**2 Aaccum += np.bincount(hydro.exch_to_2d_link['link'], hydro.areas(t_sec)[:self.hydro.n_exch_x]) accum_count += 1 rms_flows = np.sqrt(Qaccum / accum_count) mean_A = Aaccum / accum_count Lexch = hydro.exchange_lengths.sum(axis=1)[:hydro.n_exch_x] L = [ Lexch[exchs[0]] for l, exchs in utils.enumerate_groups( hydro.exch_to_2d_link['link']) ] # This is a rough scaling. # rms_flows has units of m3/s. normalize # by dividing by average flux area, and multiply by the distance # between cell centers. link_K = self.K_scale * rms_flows * L / mean_A K[:self.hydro.n_exch_x] = link_K[self.hydro.exch_to_2d_link['link']] log.info("Median dispersion coefficient: %g" % (np.median(K))) return K
def match_log_likelihood(next_a,next_b,matches, tnums_a,tnums_b,tags_a,tags_b, verbose=False, max_shift=20.0,max_drift=0.005,max_delta=0.500): """ Estimate the likelihood of a given set of matches being 'correct' up to next_a and next_b, are possible. In the case that constraints are violated, if specific culprits can be identified return them as a tuple ( [bad a samples], [bad b samples] ) otherwise, return False. matches: a list of tuples [ (a_idx,b_idx), ... ] noting which pings are potentially the same. next_a,next_b: index into tnums_a, tnums_b for the next detection not yet considered. tnums_a,tnums_b: timestamps for the pings. tags_a,tags_b: tag ids (numbers, not strings) for the pings. max_shift: two clocks can never be more than this many seconds apart, including both shift and drift. This is not 100% enforced, but in some cases this condition is used to prune the search. max_drift: unitless drift allowed. 0.001 would be a drift of 1ppt, such that every 1000 seconds, clock a is allowed to lose or gain 1 second relative to clock b. max_delta: the limit on how far apart detections can be and still be considered the same ping. This should be a generous upper bound on the travel time, probably scaled up by a factor of 2 (the sync between two clocks might be driven by a ping from A->B, and then we look at the error of a ping B->A, so the expected error is twice the travel time). """ # special case -- when nothing matches, at least disallow # the search from getting too far ahead on one side or the # other. Can't force exact chronological order. if len(matches)==0: # is a too far ahead of b? HERE if next_a>0 and next_b<len(tnums_b) and tnums_a[next_a-1]>tnums_b[next_b] + max_shift: # we already looked at an a that comes after the next b. return ([next_a-1],[next_b]) if next_b>0 and next_a<len(tnums_a) and tnums_b[next_b-1]>tnums_a[next_a] + max_shift: # already looked at a b that comes after the next a return ([next_a],[next_b-1]) # No matches to consider, and next_a and next_b are okay, so carry on. return True amatches=np.asarray(matches) # Keep us honest, that the caller didn't try to sneak a bad match in. assert np.all( tags_a[amatches[:,0]]==tags_b[amatches[:,1]] ) # evaluate whether a matched sequence is within tolerance a_times=tnums_a[amatches[:,0]] b_times=tnums_b[amatches[:,1]] if len(a_times)>1: mb=np.polyfit(a_times,b_times,1) # Be a little smarter about the slope when fitting very short # series. else: mb=[1.0,b_times[0]-a_times[0]] # Rather than treating too large a drift as an error, instead adjust # the fit to the max allowable slope, and below we'll see # if the max_error becomes too large. Otherwise fits to short strings of # data may misconstrue noise as drift and get an erroneously large drift. max_slope=1+max_drift min_slope=1./max_slope recalc=False if mb[0]<min_slope: mb[0]=min_slope recalc=True elif mb[0]>max_slope: mb[0]=max_slope recalc=True if recalc: new_b=np.mean(b_times-mb[0]*a_times) if verbose: print(f"Adjusted slope to be within allowable range, intercept {mb[1]}=>{new_b}") mb[1]=new_b if verbose: print(f"Drift is {1-mb[0]:.4e}") print(f"Shift is {np.mean(a_times-b_times):.3f}") print("Max error is %.3f"%np.abs(np.polyval(mb,a_times)-b_times).max()) # don't use the intercept -- it's the extrapolated time at tnum epoch. if not np.abs(np.mean(a_times-b_times))<max_shift: return False # shift is too much abs_errors=np.abs(np.polyval(mb,a_times)-b_times) if abs_errors.max() >= max_delta: bad_match=np.argmax(abs_errors) bad_pair=( [matches[bad_match][0]], [matches[bad_match][1]] ) # print("Abs error too great - bad_pair is ",bad_pair) return bad_pair # resulting travel times are too much. # to consider: instead of just going through next_a and next_b, # this could go through the later of the adjusted times dtype=[('tnum',np.float64), ('matched',np.bool8), ('tag',np.int32), ('src','S1'), ('srci',np.int32)] a_full=np.zeros(next_a,dtype=dtype) b_full=np.zeros(next_b,dtype=dtype) a_full['tnum']=np.polyval(mb,tnums_a[:next_a]) b_full['tnum']=tnums_b[:next_b] a_full['matched'][amatches[:,0]]=True b_full['matched'][amatches[:,1]]=True a_full['tag']=tags_a[:next_a] b_full['tag']=tags_b[:next_b] a_full['src']='a' b_full['src']='b' a_full['srci']=np.arange(next_a) b_full['srci']=np.arange(next_b) combined=np.concatenate([a_full,b_full]) order=np.argsort(combined['tnum']) combined=combined[order] # This part has to be done per tag for tagn,idxs in utils.enumerate_groups(combined['tag']): if len(idxs)<2: continue deltas=np.diff( combined['tnum'][idxs] ) matched=np.minimum( combined['matched'][idxs][:-1], combined['matched'][idxs][1:] ) if verbose: if np.any(~matched): min_delta=deltas[~matched].min() else: min_delta=np.nan print(f"[%s] %3d total detect. Min delta %.3f vs. interval %.3f for tag %s"%(all_tags[tagn], len(idxs), min_delta, tagn_interval[tagn], tagn)) # evaluate all, sort, look at diff(time) if np.any(~matched): # the 0.8 is some slop. bad=(~matched) & (deltas < 0.8*tagn_interval[tagn]) bad_a=[] bad_b=[] for bad_idx in np.nonzero(bad)[0]: # deltas[bad_idx] is bad # That's from idxs[bad_idx],idxs[bad_idx+1] for bad_i in [bad_idx,bad_idx+1]: i=idxs[bad_i] if combined['src'][i]==b'a': bad_a.append( combined['srci'][i] ) elif combined['src'][i]==b'b': bad_b.append( combined['srci'][i] ) else: assert False if bad.sum()>0: bad_a=list(np.unique(bad_a)) bad_b=list(np.unique(bad_b)) # print("Per tag delta is bad. Pair is ",(bad_a,bad_b)) return (bad_a,bad_b) return True
xyz_input = adcp_xyz.copy() if src == 'dem': # Rather than use the ADCP data directly, during testing # use its horizontal distribution, but pull "truth" from the # DEM xyz_input[:, 2] = dem(xyz_input[:, :2]) if cluster: linkage = 'complete' n_clusters = 3000 clustering = AgglomerativeClustering(linkage=linkage, n_clusters=n_clusters) clustering.fit(xyz_input[:, :2]) group_xyz = np.zeros((n_clusters, 3)) for grp, members in utils.enumerate_groups(clustering.labels_): group_xyz[grp] = xyz_input[members].mean(axis=0) xyz_input = group_xyz ## # This is written out by merge_maps.py, currently just for one timestamp. ds = xr.open_dataset('merged_map.nc') g = unstructured_grid.UnstructuredGrid.from_ugrid(ds) ## if 0: iz = InterpZhangDual(g, u=ds.ucxa.values, v=ds.ucya.values,
xyz_dense[:, 2] = dem(xyz_dense[:, :2]) ## from sklearn.cluster import AgglomerativeClustering xyzs = xyz_dense.copy() if cluster: linkage = 'complete' n_clusters = 3000 clustering = AgglomerativeClustering(linkage=linkage, n_clusters=n_clusters) clustering.fit(xyzs[:, :2]) group_xyz = np.zeros((n_clusters, 3)) for grp, members in utils.enumerate_groups(clustering.labels_): group_xyz[grp] = xyzs[members].mean(axis=0) xyzs = group_xyz if quant_xy: quant_scale = 1.0 # m # There are some repeated values in there, and the RBF is probably # poorly conditioned even for closely spaced samples. # round all coordinates to 10cm, and remove dupes. # go even further, round to 1m but average. xyz_quant = xyzs.copy() xyz_quant[:, 0] = quant_scale * np.round(xyz_quant[:, 0] / quant_scale) xyz_quant[:, 1] = quant_scale * np.round(xyz_dense[:, 1] / quant_scale) counts = np.zeros(len(xyz_quant), np.int32)
def disp_array(self): self.hydro.infer_2d_elements() self.hydro.infer_2d_links() # first calculate all time steps, just in 2D. Q = np.zeros((len(self.hydro.t_secs), self.hydro.n_2d_links), np.float64) A = np.zeros((len(self.hydro.t_secs), self.hydro.n_2d_links), np.float64) for ti in utils.progress(range(len(self.hydro.t_secs))): t_sec = self.hydro.t_secs[ti] flows = [ hydro.flows(t_sec) for hydro in [self.hydro_tidal, self.hydro] ] flow_hp = flows[0] - flows[1] # depth-integrate flow_hor = flow_hp[:self.hydro_tidal.n_exch_x] link_flows = np.bincount( self.hydro.exch_to_2d_link['link'], self.hydro.exch_to_2d_link['sgn'] * flow_hor) Q[ti, :] = link_flows**2 A[ti, :] = np.bincount( self.hydro.exch_to_2d_link['link'], self.hydro.areas(t_sec)[:self.hydro.n_exch_x]) dt_s = np.median(np.diff(self.hydro.t_secs)) winsize = int(self.lowpass_days * 86400 / dt_s) # These are a little slow. 10s? # could streamline this some since we later only use a fraction of the values. # clip here is because in some cases the values are very low and # and some roundoff is creating negatives. Qlp = filters.lowpass_fir(Q, winsize=winsize, axis=0).clip(0) Alp = filters.lowpass_fir(A, winsize=winsize, axis=0).clip(0) rms_flows = np.sqrt(Qlp) mean_A = Alp Lexch = self.hydro.exchange_lengths.sum(axis=1)[:self.hydro.n_exch_x] L = [ Lexch[exchs[0]] for l, exchs in utils.enumerate_groups( self.hydro.exch_to_2d_link['link']) ] # This is just a placeholder. A proper scaling needs to account for # cell size. rms_flows has units of m3/s. probably that should be normalized # by dividing by average flux area, and possibly multiplying by the distance # between cell centers. that doesn't seem quite right. link_K = self.K_scale * rms_flows * L / mean_A # this is computed for every time step, but we can trim that down # it's lowpassed at winsize. Try stride of half winsize. # That was used for the first round of tests, but it looks a bit # sparse. K_stride = winsize // 4 K2D = link_K[::K_stride, :] K_t_secs = self.hydro.t_secs[::K_stride] if self.amp_factor != 1.0: Kbar = K2D.mean(axis=0) K2D = (Kbar[None, :] + self.amp_factor * (K2D - Kbar[None, :])).clip(0) K = np.zeros((len(K_t_secs), self.hydro.n_exch), np.float64) # and then project to 3D K[:, :self.hydro.n_exch_x] = K2D[:, self.hydro.exch_to_2d_link['link']] if 0: # DEBUGGING # verify that I can get back to the previous, constant in time # run. log.warning("Debugging K") Kconst = super(KautoUnsteady, self).disp_array() K[:, :] = Kconst[None, :] log.info("Median dispersion coefficient: %g" % (np.median(K))) return K_t_secs, K
def test_matches(next_a,next_b,matches, tnums_a,tnums_b,tags_a,tags_b, verbose=False, max_shift=20.0,max_drift=0.005,max_delta=0.500, max_bad_pings=0): """ return True if the matches so far, having considered up to next_a and next_b, are possible. Search state: matches: a list of tuples [ (a_idx,b_idx), ... ] noting which pings are potentially the same. next_a,next_b: index into tnums_a, tnums_b for the next detection not yet considered. tnums_a,tnums_b: timestamps for the pings. tags_a,tags_b: tag ids (numbers, not strings) for the pings. max_shift: two clocks can never be more than this many seconds apart, including both shift and drift. This is not 100% enforced, but in some cases this condition is used to prune the search. max_drift: unitless drift allowed. 0.001 would be a drift of 1ppt, such that every 1000 seconds, clock a is allowed to lose or gain 1 second relative to clock b. max_delta: the limit on how far apart detections can be and still be considered the same ping. This should be a generous upper bound on the travel time, probably scaled up by a factor of 2 (the sync between two clocks might be driven by a ping from A->B, and then we look at the error of a ping B->A, so the expected error is twice the travel time). """ # special case -- when nothing matches, at least disallow # the search from getting too far ahead on one side or the # other. Can't force exact chronological order. # proceed in raw chronological order if len(matches)==0: # is a too far ahead of b? if next_a>0 and next_b<len(tnums_b) and tnums_a[next_a-1]>tnums_b[next_b] + max_shift: # we already looked at an a that comes after the next b. return False if next_b>0 and next_a<len(tnums_a) and tnums_b[next_b-1]>tnums_a[next_a] + max_shift: # already looked at a b that comes after the next a return False # No matches to consider, and next_a and next_b are okay, so carry on. return True amatches=np.asarray(matches) # Keep us honest, that the caller didn't try to sneak a bad match in. assert np.all( tags_a[amatches[:,0]]==tags_b[amatches[:,1]] ) # evaluate whether a matched sequence is within tolerance a_times=tnums_a[amatches[:,0]] b_times=tnums_b[amatches[:,1]] if len(a_times)>1: mb=np.polyfit(a_times,b_times,1) # Be a little smarter about the slope when fitting very short # series. else: mb=[1.0,b_times[0]-a_times[0]] if 0: if not np.abs(np.log10(mb[0]))<np.log10(1+max_drift): return False # drift is too much else: # Rather than treating this as an error, instead adjust # the fit to the max allowable slope, and below we'll see # if the max_error becomes too large max_slope=1+max_drift min_slope=1./max_slope recalc=False if mb[0]<min_slope: mb[0]=min_slope recalc=True elif mb[0]>max_slope: mb[0]=max_slope recalc=True if recalc: new_b=np.mean(b_times-mb[0]*a_times) if verbose: print(f"Adjusted slope to be within allowable range, intercept {mb[1]}=>{new_b}") mb[1]=new_b if verbose: print(f"Drift is {1-mb[0]:.4e}") print(f"Shift is {np.mean(a_times-b_times):.3f}") print("Max error is %.3f"%np.abs(np.polyval(mb,a_times)-b_times).max()) # don't use the intercept -- it's the extrapolated time at tnum epoch. if not np.abs(np.mean(a_times-b_times))<max_shift: return False # shift is too much max_error=np.abs(np.polyval(mb,a_times)-b_times).max() if not max_error < max_delta: return False # resulting travel times are too much. # to consider: instead of just going through next_a and next_b, # this could go through the later of the adjusted times dtype=[('tnum',np.float64), ('matched',np.bool8), ('tag',np.int32)] a_full=np.zeros(next_a,dtype=dtype) b_full=np.zeros(next_b,dtype=dtype) a_full['tnum']=np.polyval(mb,tnums_a[:next_a]) b_full['tnum']=tnums_b[:next_b] a_full['matched'][amatches[:,0]]=True b_full['matched'][amatches[:,1]]=True a_full['tag']=tags_a[:next_a] b_full['tag']=tags_b[:next_b] combined=np.concatenate([a_full,b_full]) order=np.argsort(combined['tnum']) combined=combined[order] # This part has to be done per tag: n_bad=0 # running tally of number of bad pings for tagn,idxs in utils.enumerate_groups(combined['tag']): if len(idxs)<2: continue deltas=np.diff( combined['tnum'][idxs] ) matched=np.minimum( combined['matched'][idxs][:-1], combined['matched'][idxs][1:] ) if verbose: if np.any(~matched): min_delta=deltas[~matched].min() else: min_delta=np.nan print(f"[%s] %3d total detect. Min delta %.3f vs. interval %.3f for tag %s"%(all_tags[tagn], len(idxs), min_delta, tagn_interval[tagn], tagn)) # evaluate all, sort, look at diff(time) if np.any(~matched): n_bad+=(deltas[~matched] < 0.8*tagn_interval[tagn]).sum() if n_bad>max_bad_pings: # the 0.8 is some slop. return False elif (n_bad>0) and verbose: print("%d bad pings, but that is allowed"%n_bad) return True
# Transition matrix df['b0'] = xy_to_bin(df.loc[:, ['x0', 'y0']].values) df['bN'] = xy_to_bin(df.loc[:, ['xN', 'yN']].values) ## M = np.zeros((Nbins - 1, Nbins - 1), np.float64) for idx, row in df.iterrows(): M[int(row['b0']), int(row['bN'])] += 1 # normalize those by initial masses bins0 = np.searchsorted(bins, parts0['x'][:, 0]) - 1 # initial mass in each bin mass0 = np.array([len(idxs) for _, idxs in utils.enumerate_groups(bins0)]) # first column normalized by first mass, second by second etc. M[:, :] *= (1. / mass0)[None, :] ## fig = plt.figure(3) fig.clf() fig, ax = plt.subplots(1, 1, num=3) img = ax.imshow(M) plt.colorbar(img) ## # that looks right. # from here,
## # Fabricate a really basic aggregation from scipy.cluster import vq pnts = model.grid.cells_centroid() # make this deterministic np.random.seed(37) centroids, labels = vq.kmeans2(pnts, k=20, iter=5, minit='points') permute = np.argsort(np.random.random(labels.max() + 1)) # Make a shapefile out of that polys = [] for k, grp in utils.enumerate_groups(labels): grp_poly = ops.cascaded_union([model.grid.cell_polygon(i) for i in grp]) assert grp_poly.type == 'Polygon', "Hmm - add code to deal with multipolygons" polys.append(grp_poly) agg_shp_fn = "dwaq_aggregation.shp" wkb2shp.wkb2shp(agg_shp_fn, polys, overwrite=True) ## import matplotlib.pyplot as plt plt.figure(1).clf() ax = plt.gca() model.grid.plot_cells(values=permute[labels], ax=ax) ax.axis('equal') for poly in polys:
# enforce ordering of dimensions for safety scalar_value=scalar.isel(time=t_idx).transpose('layer','face').values volume=volumes.isel(time=t_idx).transpose('layer','face').values # need to filter out the -999 values first. valid=(scalar_value!=-999) num=(scalar_value*volume*valid).sum(axis=0) den=(volume*valid).sum(axis=0) #empty=(den<=0) #num[empty]=0 #den[empty]=1.0 #scalar_2d= num/den # And aggregate for agg_idx,members in utils.enumerate_groups(aggregator.elt_global_to_agg_2d): # print(".",end='') agg_num=num[members].sum() agg_den=den[members].sum() if agg_den<=0.0: agg_scalar[t_idx,agg_idx]=0.0 else: agg_scalar[t_idx,agg_idx] = agg_num/agg_den ds=g_agg.write_to_xarray() ds['time']=('time',),scalar_map.time.values ds['scalar']=('time','face'),agg_scalar ds.attrs['name']=scalar_name ds.to_netcdf( nc_fn ) # -with_bc2: the 2 indicates that it has concentrations, too.
dt=HERE - but use YAPS instead ## this_fish=cleaned[ cleaned.TagID==fish_tag.lower() ] ## # when there are two solutions, connect by a segment # This happens 36 times for 7ADB, out of 155 fixes. segs=[] fix_xy=np.c_[ fish3.fix_x.values, fish3.fix_y.values ] for key,grp in utils.enumerate_groups(fish3.fix_idx.values): if len(grp)==1: continue segs.append( fix_xy[grp,:] ) print(f"{len(segs)} locations have multiple solutions") ## plt.figure(1).clf() fig,ax=plt.subplots(1,1,num=1) scat=ax.scatter(fix_xy[:,0],fix_xy[:,1],12,color='g') ax.axis('equal') ax.plot( this_fish.X_UTM, this_fish.Y_UTM, 'k.') img.plot(ax=ax,zorder=-5)
# For each of these that is "sufficiently large", # pull a tile of low-bay-connected pixels, expand by 1. # overlap that with the new feature to get potential connecting pixels. # increment those pixels with the area of the connected feature new_wet = (high_labels == high_bay_label) & (low_labels != low_bay_label) new_labels, new_n_features = ndimage.label(new_wet) new_labels_f = new_labels.astype(np.float64) new_labels_f[~new_wet] = np.nan new_labeled = field.SimpleGrid(extents=leveed_dem.extents, F=new_labels_f) new_bay_label = new_labeled(seed_xy) nrows, ncols = new_labels.shape new_label_extents = np.zeros((new_n_features + 1, 4), np.int32) for feat, idxs in utils.enumerate_groups(new_labels.ravel()): if feat == 0: continue r = idxs // ncols c = idxs % ncols new_label_extents[feat] = [r.min(), r.max(), c.min(), c.max()] ## pad = 3 pad_extents = new_label_extents.copy() pad_extents[:, 0] = np.maximum(0, new_label_extents[:, 0] - pad) pad_extents[:, 1] = np.minimum(new_labels.shape[0], new_label_extents[:, 1] + pad) pad_extents[:, 2] = np.maximum(0, new_label_extents[:, 2] - pad) pad_extents[:, 3] = np.minimum(new_labels.shape[1], new_label_extents[:, 3] + pad)