Source code for cerr.contour.contour_processing

import numpy as np
from cerr import plan_container as pc
from cerr.utils import uid
import cerr.contour.rasterseg as rs
from shapelysmooth import chaikin_smooth
from shapelysmooth import catmull_rom_smooth
from shapelysmooth import taubin_smooth
import copy

'''
smooth_structure(planC, struct_num, replace_original = True, name_suffix = "", tol = 4, taubin_mu = 0.8, taubin_factor = 0.8, catmull_alpha = 1)
Description: Get structure object from planC/container. Apply 2D smoothing. Option: replace original structure (default=True), or insert new structure
Returns: updated planC
'''

[docs] def smoothStructure(planC, struct_idx, replace_original = True, name_suffix ="", tol = 4, taubin_mu = 0.8, taubin_factor = 0.8, catmull_alpha = 1): """Apply 2-D contour smoothing to every segment of a structure in planC. Deep-copies the structure at ``struct_idx``, applies piecewise smoothing to each 2-D contour segment via :func:`smooth2DContour`, regenerates raster segments, and either replaces the original structure in ``planC`` or appends a new one depending on ``replace_original``. Args: planC (cerr.plan_container.PlanC): pyCERR plan container object. struct_idx (int): Index of the structure to smooth in ``planC.structure``. replace_original (bool, optional): When ``True`` (default) the smoothed structure overwrites the original entry. When ``False`` the smoothed structure is appended as a new entry. name_suffix (str, optional): String appended to the structure name of the smoothed copy. Defaults to ``""``. tol (int, optional): Minimum gap (in vertices) between jagged regions for them to be treated as separate segments. Defaults to ``4``. taubin_mu (float, optional): Mu parameter for Taubin smoothing of jagged regions. Defaults to ``0.8``. taubin_factor (float, optional): Factor (lambda) for Taubin smoothing. Defaults to ``0.8``. catmull_alpha (float, optional): Alpha parameter for Catmull-Rom interpolation of smooth regions. Defaults to ``1``. Returns: cerr.plan_container.PlanC: The updated plan container with the smoothed structure in place (or appended). """ struct_obj = copy.deepcopy(planC.structure[struct_idx]) for contour_orig in struct_obj.contour: if contour_orig != []: for seg in contour_orig.segments: C = seg.points z_coord = C[0][2] X, tr = smooth2DContour(C, tol, taubin_mu, taubin_factor, catmull_alpha) if X.shape[1] == 2: Z = z_coord * np.ones((X.shape[0],1)) seg.points = np.hstack((X,Z)) struct_obj.strUID = uid.createUID("structure") struct_obj.rasterSegments = rs.generateRastersegs(struct_obj, planC) struct_obj.structureName = struct_obj.structureName + name_suffix if not replace_original: planC.structure.append(struct_obj) else: planC.structure[struct_idx] = struct_obj return planC
''' smooth_2D_contour(C, tol = 4, taubin_mu = 0.8, taubin_factor = 0.8, catmull_alpha = 1) Description: Function will piecewise-smooth a closed contour C. Returns: Smoothed Contour X, range indices of jagged regions in original contour, taubin_range '''
[docs] def smooth2DContour(C, tol = 4, taubin_mu = 0.8, taubin_factor = 0.8, catmull_alpha = 1): """Piecewise-smooth a closed 2-D contour in place. Identifies "jagged" vertices — those where the cosine of the interior angle (``dot(v1, v2) / (|v1| |v2|)``) is either very small (near-perpendicular turns) or very close to 1 (near-collinear, i.e. micro-steps) — groups them into contiguous jagged regions, and applies Taubin smoothing to each jagged region while using Catmull-Rom interpolation on the smooth regions in between. If no jagged vertices are detected the original 2-D coordinates are returned unchanged. Args: C (np.ndarray): Contour vertex array of shape ``(N, 3)`` or ``(N, 2)``. When 3 columns are present the third (z) column is stripped before processing and not included in the output. tol (int, optional): Maximum vertex-index gap allowed within a single jagged region. Adjacent jagged vertices separated by more than ``tol`` indices are split into separate regions. Defaults to ``4``. taubin_mu (float, optional): Mu parameter for Taubin smoothing. Defaults to ``0.8``. taubin_factor (float, optional): Factor (lambda) parameter for Taubin smoothing. Defaults to ``0.8``. catmull_alpha (float, optional): Alpha parameter for Catmull-Rom interpolation. Defaults to ``1``. Returns: tuple: - **piecewise_segs** (np.ndarray): Smoothed contour vertices as an array of shape ``(M, 2)`` containing only the x/y coordinates. Returns the original ``(N, 2)`` array unchanged when no jagged segments are found. - **taubin_range** (list[list[int]] | list): List of ``[start, end]`` vertex-index pairs identifying each jagged region that was Taubin-smoothed. Empty list when no jagged segments were found. """ if C.shape[1] == 3: Cxy = np.delete(C,2,1) elif C.shape[2] == 2: Cxy = C N = C.shape[0] C_sinT = np.ndarray(shape = (N-1,1)) C_jagg = [] for i in range(1,N-1): x1 = C[i,:2] - C[i-1,:2] x2 = C[i+1,:2] - C[i,:2] sinT = np.dot(x1,x2) / (np.linalg.norm(x1) * np.linalg.norm(x2)) C_sinT[i-1,0] = sinT if sinT < 0.005 or sinT > 0.995: C_jagg.append(i) range_lists = [] if C_jagg == []: print('No jagged segments') return Cxy, [] idx0 = C_jagg[0] idxf = 0 for i in range(len(C_jagg)-1): if np.absolute(C_jagg[i+1] - C_jagg[i]) > tol and np.absolute(C_jagg[i+1] - C_jagg[i]) != 0: idxf = C_jagg[i] if idxf != idx0: range_lists.append([idx0,idxf]) idx0 = C_jagg[i+1] if idxf < idx0: idxf = C_jagg[-1] range_lists.append([idx0,idxf]) taubin_range = range_lists if taubin_range[0][0] > 0: cf = taubin_range[0][0] if taubin_range[-1][1] != N: c0 = taubin_range[-1][1] C1 = C[c0:N-1,0:2] C2 = C[0:cf+1,0:2] C_tmp = np.concatenate((C1,C2)) else: C_tmp = C[0:cf,0:2] geom = list(map(tuple,C_tmp)) C_catmull = np.asarray(catmull_rom_smooth(geom,alpha=catmull_alpha)) piecewise_segs = C_catmull else: piecewise_segs = C[0,0:2] for x in range(len(taubin_range)): t = taubin_range[x] t0 = t[0] tf = t[1] if tf - t0 > 1: geom = list(map(tuple, C[t0:tf+1,0:2])) C_taubin = np.asarray(taubin_smooth(geom,mu = taubin_mu, factor = taubin_factor)) piecewise_segs = np.concatenate((piecewise_segs, C_taubin)) c0 = tf + 1 if x + 1 < len(taubin_range): t2 = taubin_range[x+1] cf = t2[0] geom = list(map(tuple, C[c0:cf + 1,0:2])) C_catmull = np.asarray(catmull_rom_smooth(geom,alpha=catmull_alpha)) piecewise_segs = np.concatenate((piecewise_segs, C_catmull)) return piecewise_segs, taubin_range