def _complex_dblquad(func, a, b, gfun, hfun, kwargs={}): """Integrate real and imaginary part of the given function.""" if cython_imported and cython.compiled: # pure python formulation of: cdef void *f_ptr = <void*>func f_ptr = cython.declare(cython.p_void, cython.cast(cython.p_void, func)) func_capsule = PyCapsule_New(f_ptr, cython.NULL, cython.NULL) current_module = sys.modules[__name__] ll_real_2d_func_c = LowLevelCallable.from_cython(current_module, '_real_2d_func_c', func_capsule) ll_imag_2d_func_c = LowLevelCallable.from_cython(current_module, '_imag_2d_func_c', func_capsule) real, real_tol = dblquad(ll_real_2d_func_c, a, b, gfun, hfun, **kwargs) imag, imag_tol = dblquad(ll_imag_2d_func_c, a, b, gfun, hfun, **kwargs) else: real, real_tol = dblquad( _real_2d_func, a, b, gfun, hfun, (func,), **kwargs) imag, imag_tol = dblquad( _imag_2d_func, a, b, gfun, hfun, (func,), **kwargs) return real + 1j*imag, real_tol, imag_tol
def generate_current_integrand( width: float, field_to_k_factor: float, current_distribution: np.ndarray, phase_distribution: np.ndarray, ): """Integrand to compute the current through a junction at a given field. """ step = width / len(current_distribution) @cfunc(float64(intc, CPointer(float64))) def real_current_integrand(n, args): """Cfunc to be used with quad to calculate the current from the distribution. """ pos, field = args[0], args[1] x = int(pos // step) return current_distribution[x] * np.cos( (phase_distribution[x] + field_to_k_factor * pos) * field) @cfunc(float64(intc, CPointer(float64))) def imag_current_integrand(n, args): """Cfunc to be used with quad to calculate the current from the distribution. """ pos, field = args[0], args[1] x = int(pos // step) return current_distribution[x] * np.sin( (phase_distribution[x] + field_to_k_factor * pos) * field) return ( LowLevelCallable(real_current_integrand.ctypes), LowLevelCallable(imag_current_integrand.ctypes), )
def __count_votes(im, out, size, invert): """ Count the votes for each pixel in a size-by-size region around each one, saving the totals to out. If invert is supplied, every pixel less than the values for it, otherwise every pixel greater than it votes for it. REFERENCES 1. Eramian M and Mould D, 2005, "Histogram Equalization using Neighborhood Metrics", Proceedings of the Second Canadian Conference on Computer and Robot Vision. """ if is_on_gpu(im): voting = __get_count_votes_cupy_kernel(invert) else: # Try to use the Cython functions if possible - they are ~160x faster! try: from scipy import LowLevelCallable import hist.exact.__basic as cy voting = LowLevelCallable.from_cython( cy, 'vote_lesser' if invert else 'vote_greater') except ImportError: # Fallback from numpy import empty, greater, less compare = greater if invert else less tmp = empty(size**im.ndim, bool) mid = tmp.size // 2 voting = lambda x: compare(x[mid], x, tmp).sum() # Once we get the appropriate voting function we can call generic filter get_ndimage_module(im).generic_filter(im, voting, size, output=out)
def jit_integrand_function(integrand_function): """ decorator function to compile numerical integration routine This function is used to build AMPS functions as pre-compiled routines for faster evalution. From the StackOverflow answer: https://stackoverflow.com/questions/49683653/how-to-pass-additional-parameters-to-numba-cfunc-passed-as-lowlevelcallable-to-s We are using the function signature: double func(int n, double *xx) Where n is the length of xx array, xx[0] is the variable to be integrated over. The rest is the extra arguments that would normally be passed in 'args' of integrator 'scipy.integrate.quad' Usage: @jit_integrand_function def f(x, *args): a = args[0] b = args[1] return np.exp(-a*x/b) quad(f, 0, pi, args=(a,b)) """ jitted_function = jit(integrand_function, nopython=True) @cfunc(float64(intc, CPointer(float64))) def wrapped(n, xx): """ """ return jitted_function(xx[0], xx[1], xx[2], xx[3]) return LowLevelCallable(wrapped.ctypes)
def filter_std(image, size, sigma=1): """ explicitly filters an nd image based on deviation by local stdev over local mean. return mask of points that are sigma local stdev over(under) the local mean if sigma is positiv(negativ) """ import numpy as np import ctypes import scipy.ndimage as ndi from numba import cfunc, carray from numba.core.types import intc, intp, float64, voidptr, CPointer from scipy import LowLevelCallable @cfunc(intc(CPointer(float64), intp, CPointer(float64), voidptr), fastmath=True) def _std(values_ptr, len_values, result, data): values = carray(values_ptr, (len_values, ), dtype=float64) accumx = 0 accumx2 = 0 sigma = carray(data, (1, ), dtype=float64)[0] for x in values: accumx += x accumx2 += x * x mean = accumx / len_values std = np.sqrt((accumx2 / len_values) - mean**2) result[0] = sigma * std + mean return 1 csigma = ctypes.c_double(sigma) ptr = ctypes.cast(ctypes.pointer(csigma), ctypes.c_void_p) res = image > ndi.generic_filter(image, LowLevelCallable(_std.ctypes, ptr), size) if sigma < 0: res = ~res return res
def blur_map(image, window=11, sv_num=5): if window < 1: raise ValueError('Window width must be a positive integer.') bm = generic_filter(image, LowLevelCallable(blur_detection.ctypes), window, (sv_num, )) bm = normalize(bm) return bm
def std(image, size): """ a moving std calculation on an nd image """ import numpy as np import ctypes import scipy.ndimage as ndi from numba import cfunc, carray from numba.core.types import intc, intp, float64, voidptr, CPointer from scipy import LowLevelCallable @cfunc(intc(CPointer(float64), intp, CPointer(float64), voidptr), fastmath=True) def _std(values_ptr, len_values, result): values = carray(values_ptr, (len_values, ), dtype=float64) accumx = 0 accumx2 = 0 for x in values: accumx += x accumx2 += x * x mean = accumx / len_values std = np.sqrt((accumx2 / len_values) - mean**2) result[0] = std return 1 res = ndi.generic_filter(image, LowLevelCallable(_std.ctypes), size) return res
def jit_integrand_function(integrand_function): jitted_function = numba.jit(integrand_function, nopython=True) @cfunc(float64(intc, CPointer(float64))) def wrapped(n, xx): values = carray(xx,n) return jitted_function(values) return LowLevelCallable(wrapped.ctypes)
def erosion_fast(image, footprint): out = ndimage.generic_filter( image, LowLevelCallable.from_cython(sys.modules['test9'], name='erosion_kernel'), footprint=footprint ) return out
def _reduced_formula(k, t, v, kappa, a, nu, rho): params = np.array([k, t, v, kappa, a, nu, rho]).ctypes.data_as(c_void_p) f = LowLevelCallable(_integrand.ctypes, params, 'double (double, void *)') c = 1 - quad(f, 0, np.inf)[0] / pi common._assert_no_arbitrage(1., c, np.exp(k)) return c
def jit_filter_function(filter_function): jitted_function = numba.jit(filter_function, nopython=True) @cfunc(intc(CPointer(float64), intp, CPointer(float64), voidptr)) def wrapped(values_ptr, len_values, result, data): values = carray(values_ptr, (len_values,), dtype=float64) result[0] = jitted_function(values) return 1 return LowLevelCallable(wrapped.ctypes)
def jit_integrand_function(integrand_function): """Based on https://stackoverflow.com/a/49732825/4779220""" jitted_function = numba.jit(integrand_function, nopython=True, nogil=True) @numba.cfunc(nut.float64(nut.intc, nut.CPointer(nut.float64))) def wrapped(n, xx): # TODO: nicer way to not hard code number of args? `*carray()` may not expand correctly return jitted_function(xx[0], xx[1], xx[2], xx[3], xx[4], xx[5]) return LowLevelCallable(wrapped.ctypes)
def Li(O, Ok_0): SetGlobals() test_result = _integrate.IntegrateFunc(ctypes.c_double(2.34), ctypes.c_void_p()) print("Test result: %f" % (test_result)) new_f = LowLevelCallable(_integrate.IntegrateFunc) l, error = integrate.quad(new_f, H0_av - n*s, H0_av + n*s, epsabs=0, epsrel=1.49e-08) #Set error bounds? return l/(math.sqrt(2*pi*(s**2)))
def jit_geometric_function(geometric_function): jitted_function = numba.jit(geometric_function, nopython=True) @cfunc(intc(CPointer(intp), CPointer(float64), intc, intc, voidptr)) def wrapped(output_ptr, input_ptr, output_rank, input_rank, user_data): output_coords = carray(output_ptr, (output_rank, ), dtype=intp) input_coords = carray(input_ptr, (output_rank, ), dtype=float64) jitted_function(output_coords, input_coords) return 1 return LowLevelCallable(wrapped.ctypes)
def jit_filter_function(filter_function): """Decorator for use with scipy.ndimage.generic_filter.""" jitted_function = numba.jit(filter_function, nopython=True) @cfunc(intc(CPointer(float64), intp, CPointer(float64), voidptr)) def wrapped(values_ptr, len_values, result, data): values = carray(values_ptr, (len_values, ), dtype=float64) result[0] = jitted_function(values) return 1 return LowLevelCallable(wrapped.ctypes)
def _make_jitted_integrand(integrand): jitted = numba.njit(integrand) # This is the function that scipy.integrate.quad can call. @numba.cfunc( numba.types.float64(numba.types.intc, numba.types.CPointer(numba.types.float64))) def integrand_cfunc(N, XX): vals = numba.carray(XX, N) return jitted(vals) return LowLevelCallable(integrand_cfunc.ctypes)
def approximate_poisson_normal(data_count, alphas=None, betas=None, use_c=False): ''' Compute the likelihood of a marginalized poisson-gamma function, using a single normal distribution instead of the convolution of gamma function This formula can be used when the MC counts are really high, and where the gamma function throws infinite values ''' from scipy.integrate import quad import numpy as np gamma_mean = np.sum(alphas/betas) gamma_sigma = np.sqrt(np.sum(alphas/betas**2.)) # # Define integration range as +- 5 sigma # lower = max(0,gamma_mean-5*gamma_sigma) upper = gamma_mean+5*gamma_sigma # # integrate over the boundaries # if use_c: import os, ctypes import numpy as np from scipy import integrate, LowLevelCallable lib = ctypes.CDLL(os.path.abspath('/groups/icecube/bourdeet/pisa/pisa/utils/poisson_normal.so')) lib.approximate_gamma_poisson_integrand.restype = ctypes.c_double lib.approximate_gamma_poisson_integrand.argtypes = (ctypes.c_int, ctypes.POINTER(ctypes.c_double), ctypes.c_void_p) # Define the parameters params = (ctypes.c_double*3)() params[0] = data_count params[1] = gamma_mean params[2] = gamma_sigma user_data = ctypes.cast(params, ctypes.c_void_p) func = LowLevelCallable(lib.approximate_gamma_poisson_integrand, user_data) LH = quad(func, lower, upper)[0] #print('lower ',lower,' upper: ',upper,' LH: ',LH) else: LH = quad(approximate_poisson_normal_python, lower, upper, args=(data_count, gamma_mean, gamma_sigma))[0] #print('lower ',lower,' upper: ',upper,' data_count: ',data_count,' mean: ', gamma_mean, ' sigma: ',gamma_sigma, ' LH: ',LH) LH = max(SMALL_POS,LH) return np.log(LH)
def jit_psf5args(func): """A complicated piece of code used by numba to compile the PSF. This one works for `gaussianWithConstantSkyPsf()` """ jitted_function = njit(func) @cfunc(float64(intc, CPointer(float64))) def wrapped(n, xx): return jitted_function(xx[0], xx[1], xx[2], xx[3], xx[4], xx[5], xx[6]) return LowLevelCallable(wrapped.ctypes)
def jit_filter_function(filter_function): """Decorator for use with scipy.ndimage.generic_filter.""" jitted_function = numba.jit(filter_function, nopython=True) @cfunc(intc(CPointer(float64), intp, CPointer(float64), voidptr)) def wrapped(values_ptr, len_values, result, data): values = carray(values_ptr, (len_values,), dtype=float64) result[0] = jitted_function(values) return 1 sig = None if sys.platform == "win32": sig = "int (double *, npy_intp, double *, void *)" return LowLevelCallable(wrapped.ctypes, signature=sig)
def _integrand_function(integrand_function): """Wrap `integrand_function` as a `LowLevelCallable` to be used with quad. `integrand_function` has to have the signature (float, complex) -> float. This speeds up integration by removing call overhead. However only float arguments can be passed to and from the function. """ @numba.cfunc(nb_t.float64(nb_t.intc, nb_t.CPointer(nb_t.float64))) def wrapped(__, xx): return integrand_function(xx[0], xx[1] + xx[2]) return LowLevelCallable(wrapped.ctypes)
def jit_geometric_function(geometric_function): jitted_function = numba.jit(geometric_function, nopython=True) @cfunc(intc(CPointer(intp), CPointer(float64), intc, intc, voidptr)) def wrapped(output_ptr, input_ptr, output_rank, input_rank, user_data): output_coords = carray(output_ptr, (output_rank,), dtype=intp) input_coords = carray(input_ptr, (output_rank,), dtype=float64) jitted_function(output_coords, input_coords) return 1 # needs to be tested # sig = None # if sys.platform == "win32": # sig = "int (double *, double *, int, int, void *)" return LowLevelCallable(wrapped.ctypes, signature=sig)
def bilateral_filter(im, size=None, sigma_r=None, sigma_d=1, **kwargs): """ Bilaterally filter an image. Uses Gaussian kernels for the spatial and intensity filters. im is the image to filter, must be grayscale but can be any dimension size is the kernel size, must be odd and >=3, defaults to int(max(5, 2*ceil(3*sigma_d)+1)). sigma_r is the range/intensity standard deviation, defaults to image standard deviation. sigma_d is the domain/spatial standard deviation, default to 1. other keyword arguments are passed to scipy.ndimage.generic_filter. This attempts to use a Cython optimized function if possible. Additionally in common cases many parts are computed to greatly speed up the function. REFERENCES 1. Tomasi C and Manduchi R, 1998, "Bilateral filtering for gray and color images". Sixth International Conference on Computer Vision. pp. 839–846. 2. R A and Wilscu M, 2008, "Enhancing Contrast in Color Images Using Bilateral Filter and Histogram Equalization Using Wavelet Coefficients", 2008 Second International Conference on Future Generation Communication and Networking Symposia. """ from scipy.ndimage import generic_filter if sigma_r is None: sigma_r = im.std() if size is None: size = int(max(5, 2*ceil(3*sigma_d)+1)) elif size < 3 or size%2 != 1: raise ValueError(size) # Calculate the kernels spatial, scale, inten_lut = __bilateral_kernels(im.dtype, im.ndim, size, sigma_r, sigma_d) try: # Try to import Cython optimized code - 20 to 75x faster from scipy import LowLevelCallable import hist.exact.__bilateral_cy as cy _bilateral_filter = LowLevelCallable.from_cython( cy, 'bilateral_filter' if inten_lut is None else 'bilateral_filter_inten_lut', cy.get_user_data(spatial, scale, inten_lut)) # pylint: disable=c-extension-no-member except ImportError: # Fallback to pure Python function # Note: it seems the pure Python function actually gets slower with the intensity LUT def _bilateral_filter(data): diff = data - data[data.size // 2] weight = exp(diff*diff*scale) * spatial return dot(data, weight) / weight.sum() return generic_filter(im, _bilateral_filter, size, **kwargs)
def jit_integrand(integrand_function): jitted_function = numba.jit(integrand_function, nopython=True) no_args = len(inspect.getfullargspec(integrand_function).args) wrapped = None if no_args == 4: def wrapped(n, xx): return jitted_function(xx[0], xx[1], xx[2], xx[3]) elif no_args == 5: def wrapped(n, xx): return jitted_function(xx[0], xx[1], xx[2], xx[3], xx[4]) elif no_args == 6: def wrapped(n, xx): return jitted_function(xx[0], xx[1], xx[2], xx[3], xx[4], xx[5]) elif no_args == 7: def wrapped(n, xx): return jitted_function(xx[0], xx[1], xx[2], xx[3], xx[4], xx[5], xx[6]) elif no_args == 8: def wrapped(n, xx): return jitted_function(xx[0], xx[1], xx[2], xx[3], xx[4], xx[5], xx[6], xx[7]) elif no_args == 9: def wrapped(n, xx): return jitted_function(xx[0], xx[1], xx[2], xx[3], xx[4], xx[5], xx[6], xx[7], xx[8]) elif no_args == 10: def wrapped(n, xx): return jitted_function(xx[0], xx[1], xx[2], xx[3], xx[4], xx[5], xx[6], xx[7], xx[8], xx[9]) elif no_args == 11: def wrapped(n, xx): return jitted_function(xx[0], xx[1], xx[2], xx[3], xx[4], xx[5], xx[6], xx[7], xx[8], xx[9], xx[10]) cf = cfunc(float64(intc, CPointer(float64))) return LowLevelCallable(cf(wrapped).ctypes)
def setup(self): from math import sin self.f_python = lambda x: sin(x) self.f_cython = from_cython(_test_ccallback_cython, "sine") lib = ctypes.CDLL(clib_test.__file__) self.f_ctypes = lib._multivariate_sin self.f_ctypes.restype = ctypes.c_double self.f_ctypes.argtypes = (ctypes.c_int, ctypes.c_double ) # sic -- for backward compat if cffi is not None: voidp = ctypes.cast(self.f_ctypes, ctypes.c_void_p) address = voidp.value ffi = cffi.FFI() self.f_cffi = LowLevelCallable( ffi.cast("double (*)(int, double *)", address))
def setup(self): from math import sin self.f_python = lambda x: sin(x) self.f_cython = from_cython(_ccallback_c, "sine") try: from scipy.integrate.tests.test_quadpack import get_clib_test_routine self.f_ctypes = get_clib_test_routine('_multivariate_sin', ctypes.c_double, ctypes.c_int, ctypes.c_double) except ImportError: lib = ctypes.CDLL(clib_test.__file__) self.f_ctypes = lib._multivariate_sin self.f_ctypes.restype = ctypes.c_double self.f_ctypes.argtypes = (ctypes.c_int, ctypes.c_double) if cffi is not None: voidp = ctypes.cast(self.f_ctypes, ctypes.c_void_p) address = voidp.value ffi = cffi.FFI() self.f_cffi = LowLevelCallable(ffi.cast("double (*)(int, double *)", address))
def single_part(q, omega, elfdata): if q == 0: return 0 # Find lower integration boundary kf_min = max(0, (2 * omega - q * q) / (2 * q), (q * q - 2 * omega) / (2 * q)) omega0_min = math.sqrt(kf_min**3 * 4 / (3 * math.pi)) # Pack the ELF data into a block of memory, to pass to the single_integrand # function through scipy's LowLevelCallable interface. d = struct.pack('ddddq%dd' % elfdata[2], q, omega, *(elfdata[0:3]), *elfdata[3]) user_data = ctypes.cast(d, ctypes.c_void_p) ll_integrand = LowLevelCallable(single_integrand.ctypes, user_data) # Ignore all integration warnings. Yes, I know this is horrible. # Fact is that even with convergence warnings, you get decent results. with warnings.catch_warnings(): warnings.filterwarnings('ignore', category=IntegrationWarning) return quad(ll_integrand, omega0_min, math.inf)[0]
def _computeParameters(self): self.theta = sp.sympify(self.theta) self.b = sp.sympify(self.b) self.delta = sp.sympify(self.delta) self.epsilon = sp.sympify(self.epsilon) self.eosDisplayName = self.eosDisplayName self.eosInfo = self.eosInfo self._PsymbolicFromVT = R_IG * self.T / (self.V - self.b) - self.theta / ( (self.V - self.b) * (self.V ** 2 + self.delta * self.V + self.epsilon) ) self._ZsymbolicFromVT = self.V / (self.V - self.b) - ( self.V * self.theta / (R_IG * self.T) ) / (self.V ** 2 + self.V * self.delta + self.epsilon) self._dZdTsymbolicFromVT = sp.diff(self._ZsymbolicFromVT, self.T) self._numf_ZfromVT = njit()( sp.lambdify([self.V, self.T], self._ZsymbolicFromVT, modules="numpy") ) self._numf_dZdTfromVT = njit()( sp.lambdify([self.V, self.T], self._dZdTsymbolicFromVT, modules="numpy") ) self._Bl = self.b * self.P / (R_IG * self.T) self._deltal = self.delta * self.P / (R_IG * self.T) self._thetal = self.theta * self.P / (R_IG * self.T) ** 2 self._epsilonl = self.epsilon * (self.P / (R_IG * self.T)) ** 2 # coefficients Z**3 + a0*Z**2 + a1*Z + a2 = 0 self._a0 = self._deltal - self._Bl - 1 self._a1 = self._thetal + self._epsilonl - self._deltal * (self._Bl + 1) self._a2 = -(self._epsilonl * (self._Bl + 1) + self._thetal * self._Bl) self._numf_a0 = njit()(sp.lambdify([self.P, self.T], self._a0, modules="numpy")) self._numf_a1 = njit()(sp.lambdify([self.P, self.T], self._a1, modules="numpy")) self._numf_a2 = njit()(sp.lambdify([self.P, self.T], self._a2, modules="numpy")) self.tmp_cfunc = None self.tmp_cfunc2 = None c_sig = types.double(types.intc, types.CPointer(types.double)) exec( "self.tmp_cfunc = lambda n, data: {:s}".format( str((1 - self._ZsymbolicFromVT) / self.V) .replace("V", "data[0]") .replace("T", "data[1]") .replace("Abs", "np.abs") .replace("sign", "np.sign") ) ) qf = cfunc(c_sig)(self.tmp_cfunc) self._qnf = LowLevelCallable(qf.ctypes) exec( "self.tmp_cfunc2 = lambda n, data: {:s}".format( str(self.T * self._dZdTsymbolicFromVT / self.V) .replace("V", "data[0]") .replace("T", "data[1]") .replace("Abs", "np.abs") .replace("sign", "np.sign") ) ) tf = cfunc(c_sig)(self.tmp_cfunc2) self._numf_UR = LowLevelCallable(tf.ctypes)
gamma2 = 3.92 delta = 0.1 return phi0 * (100. / e)**gamma1 * \ (1. + (eb/e)**((gamma1 - gamma2) / delta))**(-delta) phi_e_bg_dampe_jit = jit(phi_e_bg_dampe, nopython=True) @cfunc(float64(float64)) def phi_e_bg_dampe_cfunc(e): return phi_e_bg_dampe_jit(e) phi_e_bg_dampe_llc = LowLevelCallable(phi_e_bg_dampe_cfunc.ctypes) def phi_e_bg_alt(e): """Background model for the e+ e- flux from Ge et al, arXiv:1712.02744 Note ----- The data in several bins is excluded from the background fit. See Fig. 1 in the reference above. Parameters ---------- e : float e+ e- energie in GeV
# ctypes.c_double: ctype data type, in C data type: double, in python data type: float # restype: specifies the return type -> in this case a C double/ python float lib.f.restype = ctypes.c_double # argtypes: It is possible to specify the required argument types of functions exported from DLLs by setting # the argtypes attribute (first argument of the function is an integer, second argument is a double and # third argument is a void) # WICHTIG: void Funktion darf keinen Rückgabewert haben! lib.f.argtypes = (ctypes.c_int, ctypes.POINTER(ctypes.c_double)) # ctypes.cast(obj, type): This function is similar to the cast operator in C. # It returns a new instance of type which points to the same memory block as 'obj'. # 'type' must be a pointer type, and 'obj' must be an object that can be interpreted as a pointer. # user_data = ctypes.cast(ctypes.pointer(c), ctypes.c_void_p) func = LowLevelCallable(lib.f) f_dsnb = np.array([0, 0.1, 0.2, 0.3, 0.4, 0.3, 0.15, 0.05, 0, 0]) f_ccatmo = np.array([0.1, 0.2, 0.35, 0.45, 0.5, 0.6, 0.7, 0.8, 0.85, 0.9]) f_reactor = np.array([5, 2, 1, 0, 0, 0, 0, 0, 0, 0]) f_data = np.array([1, 0, 0, 0, 1, 0, 0, 1, 0, 0]) fraction = np.array([f_dsnb, f_ccatmo, f_reactor, f_data], dtype='float') # integrate the function integral = integrate.nquad( func, [[0.5, 1.5], [0.5, 1.5], [0.5, 1.5], [0, 0.1], [1, 1.1], [1, 1.1], [2, 2.1], [2, 2.1], [3, 3.1], [3, 3.1], [4, 4.1]]) # print integral-value: print(integral)
import numpy as np import pandas as pd from scipy import LowLevelCallable from scipy.integrate import quad from toolz import memoize import textnets as tn from .corpus import TidyText from .fca import FormalContext from .viz import decorate_plot try: from . import _ext # type: ignore integrand = LowLevelCallable.from_cython(_ext, "df_integrand") except ImportError: def integrand(x: float, degree: int) -> float: """Fallback version of integrand function for the disparity filter.""" return (1 - x) ** (degree - 2) warn("Could not import compiled extension, backbone extraction will be slow.") class TextnetBase: """ Base class for `Textnet` and `ProjectedTextnet`. Attributes ----------