Repository navigation
Conversation
Analysis for project
|
| Tool | Category | Rule | Count | |
|---|---|---|---|---|
| black | Style | Incorrect formatting, autoformat by running qlty fmt. |
3 | ❌ |
| ruff | Lint | f-string without any placeholders | 1 | ❌ |
| qlty | Structure | Function with high complexity (count = 19): mergePartArea | 1 | |
| qlty | Structure | Deeply nested control flow (level = 4) | 1 |
@qltysh one-click actions:
- Auto-fix formatting (
qlty fmt && git push)
|
Coverage Impact ⬆️ Merging this pull request will increase total coverage on Modified Components (1)
Modified Files with Diff Coverage (3)
🤖 Increase coverage with AI coding...🚦 See full report on Qlty Cloud » 🛟 Help
|
| if adaptStop or adaptDet or adaptEnt: | ||
| dem, fields = adaptDEM(dem, fields, cfg) | ||
| # adapt DEM considering erosion and deposition every time step | ||
| # (normals and area of the DEM are only updated if the surface changed more than thresholdAdaptSfc) |
There was a problem hiding this comment.
is this condition used so that normals are computed less often? what about a condition that these are only recomputed for the cells that are affected?
There was a problem hiding this comment.
Yes,
I've also tried to only update normals and area in affected area. But I'm not sure if this is worth it since the code becomes much more complicated.
(with an extra function like:)
def updateNormalAndAreaMesh(dem, changedMask, num, maxFraction=0.5):
""" Recompute normals and cell area only in the cells of changedMask and in their neighbour cells
changedMask can contain any number of separate patches (connected regions of True cells). Each patch is
computed on its own cutout of the DEM (bounding box of the patch grown by 2 cells: 1 ring of neighbour
cells to update and 1 ring of cells providing the elevations needed for their normals) and only the cells
of the patch and their neighbour cells are copied back. Cutouts of different patches may overlap.
If the cutouts together cover more than maxFraction of the DEM,
normals and area are recomputed for the full DEM.
Parameters
----------
dem: dict
dem dict with header, rasterData (already updated elevation), Nx, Ny, Nz, areaRaster
changedMask: 2D boolean numpy array
True where normals and area are updated (together with the direct neighbour cells of these cells),
num: int
chose between 4, 6 or 8 (using then 4, 6 or 8 triangles) or
1 to use the simple cross product method (with the diagonals)
maxFraction: float
if the cutouts together are larger than this fraction of the DEM, do a full recompute
Returns
-------
dem: dict
dem dict with updated Nx, Ny, Nz, areaRaster and outOfDEM
"""
if not np.any(changedMask):
return dem
z = dem['rasterData']
n, m = z.shape
rows = np.flatnonzero(np.any(changedMask, axis=1))
cols = np.flatnonzero(np.any(changedMask, axis=0))
# box: bounding box of all changed cells grown by 2 cells (1 ring of neighbour cells to update and
# 1 ring of cells whose elevation is needed for the normals of these neighbours), clipped to the DEM
br0 = max(rows[0] - 2, 0)
br1 = min(rows[-1] + 2, n - 1) + 1
bc0 = max(cols[0] - 2, 0)
bc1 = min(cols[-1] + 2, m - 1) + 1
box = (slice(br0, br1), slice(bc0, bc1))
# every connected (8-neighbourhood) region of changed cells is one patch
labels, _ = ndimage.label(changedMask[box], structure=np.ones((3, 3), dtype=bool))
patchSlices = ndimage.find_objects(labels)
# cutout of each patch: bounding box of the patch grown by 2 cells, clipped to the DEM
# (indices relative to box)
nb, mb = labels.shape
cutouts = []
nCellsCutouts = 0
for sl in patchSlices:
r0 = max(sl[0].start - 2, 0)
r1 = min(sl[0].stop + 2, nb)
c0 = max(sl[1].start - 2, 0)
c1 = min(sl[1].stop + 2, mb)
cutouts.append((slice(r0, r1), slice(c0, c1)))
nCellsCutouts += (r1 - r0) * (c1 - c0)
if nCellsCutouts > maxFraction * n * m:
dem = geoTrans.getNormalMesh(dem, num=num)
dem = getAreaMesh(dem, num)
return dem
# copy arrays to not modify arrays that might be referenced elsewhere
keys = ['Nx', 'Ny', 'Nz', 'areaRaster']
newArrays = {key: np.array(dem[key], dtype=float, copy=True) for key in keys}
if 'outOfDEM' in dem:
outOfDEM = dem['outOfDEM'].reshape(n, m).copy()
else:
outOfDEM = np.isnan(z)
zBox = z[box]
for label, cut in enumerate(cutouts, start=1):
# compute normals and area on the cutout of this patch; the outer ring of the cutout has wrong
# (border) values, but it is never written unless it coincides with the real DEM border, where it is
# correct. The cutout contains the current elevation of all cells (also of other patches), so cells
# that are updated by several patches get the same (correct) value
zSub = zBox[cut]
nSub, mSub = zSub.shape
subDem = {'header': {'nrows': nSub, 'ncols': mSub, 'cellsize': dem['header']['cellsize']},
'rasterData': zSub}
subDem = geoTrans.getNormalMesh(subDem, num=num)
subDem = getAreaMesh(subDem, num)
# cells to update: cells of this patch and all their (8) neighbour cells
patchPad = np.pad(labels[cut] == label, 1)
writeMask = np.zeros((nSub, mSub), dtype=bool)
for di in range(3):
for dj in range(3):
writeMask |= patchPad[di:di + nSub, dj:dj + mSub]
for key in keys:
newArrays[key][box][cut][writeMask] = subDem[key][writeMask]
outOfDEM[box][cut][writeMask] = np.isnan(zSub)[writeMask]
dem.update(newArrays)
dem['outOfDEM'] = outOfDEM.flatten()
return dem

standardTests are ident, except
avaHofSnowGlideTestPR Checklist
Please confirm before requesting review:
pytestlocally without failsConfirm before the final merge/rebase into master