pynkowski.stats.minkowski

This submodule contains the functions to compute the Minkowski Functionals of a field.

  1'''This submodule contains the functions to compute the Minkowski Functionals of a field.'''
  2import numpy as np
  3from scipy.special import comb, gamma
  4from .utils_st import subsample_us, define_ubins
  5from ..data.base_da import DataField
  6from ..theory.base_th import TheoryField
  7
  8try:
  9    from tqdm.auto import tqdm
 10except:
 11    tqdm = lambda x: x
 12    print('tqdm not loaded')
 13
 14def _MF_prefactor(d,j):
 15    """Compute the prefactor in the definition of Minkowski Functionals. This factor multiplies the integral of the curvatures.
 16
 17    Parameters
 18    ----------
 19    d : int
 20        The dimension of the ambient space (e.g., 2 for the sphere).
 21        
 22    j : int
 23        The index of the Minkowski functional (`j>=1`).
 24    
 25    Returns
 26    -------
 27    prefactor : float
 28        The prefactor in the definition of MFs.
 29    
 30    """
 31    return 1./ (comb(d,j) * j * np.pi**(j/2.) / gamma(j/2. +1.))
 32    
 33
 34def V0(field, us, edges=False, verbose=True):
 35    """Compute the first Minkowski Functional, $V_0$, normalized by the volume of the space.
 36
 37    Parameters
 38    ----------
 39    field : DataField or TheoryField
 40        Field on which to compute $V_0$, which can be a theoretical field or a data field.
 41        
 42    us : np.array
 43        The thresholds where $V_0$ is computed.
 44        
 45    edges : bool, optional
 46        If False (default), the given `us` is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins.
 47        If True, input `us` is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. 
 48        In the latter case, the effective thresholds are the central value of the given bins.
 49        
 50    verbose : bool, optional
 51        If True (default), progress bars are shown for the computations on data.
 52    
 53    Returns
 54    -------
 55    V0 : np.array
 56        The values of the first Minkowski Functional at the given thresholds.
 57    
 58    """
 59    us = np.atleast_1d(us)
 60    us, dus = define_ubins(us, edges)
 61    
 62    if isinstance(field, TheoryField):
 63        us = subsample_us(us, dus)
 64        try:
 65            return np.nanmean(field.V0(us), axis=1)
 66        except AttributeError:
 67            raise NotImplementedError(f"The theoretical expectation of V0 for {field.name} fields is not implemented. If you know an expression, please get in touch.")
 68        
 69    if isinstance(field, DataField):
 70        try:
 71            return field._V0(us, dus, verbose=verbose)
 72        except AttributeError:                
 73            stat = np.zeros_like(us)
 74            for ii in tqdm(np.arange(us.shape[0]), disable=not verbose):
 75                stat[ii] = np.mean((field.field > us[ii])[field.mask])
 76            return stat
 77        
 78    else:
 79        raise TypeError(f"The field must be either TheoryField or DataField (or a subclass).")
 80    
 81
 82def V1(field, us, edges=False, verbose=True):
 83    """Compute the second Minkowski Functional, $V_1$, normalized by the volume of the space.
 84
 85    Parameters
 86    ----------
 87    field : DataField or TheoryField
 88        Field on which to compute $V_1$, which can be a theoretical field or a data field.
 89        
 90    us : np.array
 91        The thresholds where $V_1$ is computed.
 92        
 93    edges : bool, optional
 94        If False (default), the given `us` is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins.
 95        If True, input `us` is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. 
 96        In the latter case, the effective thresholds are the central value of the given bins.
 97        
 98    verbose : bool, optional
 99        If True (default), progress bars are shown for the computations on data.
100    
101    Returns
102    -------
103    V1 : np.array
104        The values of the second Minkowski Functional at the given thresholds.
105    
106    """
107    us = np.atleast_1d(us)
108    us, dus = define_ubins(us, edges)
109    
110    if isinstance(field, TheoryField):
111        us = subsample_us(us, dus)
112        try:
113            return np.nanmean(field.V1(us), axis=1)
114        except AttributeError:
115            raise NotImplementedError(f"The theoretical expectation of V1 for {field.name} fields is not implemented. If you know an expression, please get in touch.") from None
116        
117    elif isinstance(field, DataField):
118        try:
119            return field._V1(us, dus, verbose=verbose)
120        except AttributeError:
121            if field.first_der is None:
122                field.get_first_der()
123                
124            stat = np.zeros_like(us)
125            grad_modulus = np.sqrt(np.sum((field.first_der)**2., axis=0))
126            for ii in tqdm(np.arange(us.shape[0]), disable=not verbose):
127                this_mask = (us[ii] + dus[ii]/2. > field.field) & (us[ii] - dus[ii]/2. <= field.field)
128                stat[ii] = np.mean((grad_modulus*this_mask/dus[ii])[field.mask])
129            return _MF_prefactor(field.dim, 1) * stat
130        
131    else:
132        raise TypeError(f"The field must be either TheoryField or DataField (or a subclass).")
133        
134        
135  
136def general_curvature(field, order):
137    """Computes the symmetrised polynomial of the principal curvatures of order `order`, multiplied by the modulus of the gradient, at every pixel.
138
139    Parameters
140    ----------
141    field : DataField
142        Field on which to compute the general curvatures.
143        
144    order : int
145        Order of the polynomial of the curvatures.
146    
147    Returns
148    -------
149    curvature : np.array
150        The value of the polynomial at each pixel. 
151        
152        For example:
153        `field.dim = 2`, `order=1` : geodesic curvature $\kappa$
154        `field.dim = 3`, `order=1` : mean curvature (H = ($\kappa_1$ + $\kappa_2$)/2 )
155        `field.dim = 3`, `order=2` : Gaussian curvature (K = $\kappa_1 \cdot \kappa_2$)
156    
157    """
158    assert isinstance(field, DataField), "The field must be a DataField (or subclass)"
159    assert field.dim > 1, "The field must be at least 2D"
160    if order >= field.dim:
161        raise ValueError("The order of the polynomial has to be less than the dimension of the field.")
162        
163    if order == 0:
164        return np.sqrt(np.sum(field.first_der**2., axis=0))
165    
166    if field.dim == 2:
167        if order == 1: # Geodesic curvature κ  (times |∇f|)
168            num = 2.*field.first_der[0]*field.first_der[1]*field.second_der[2] - field.first_der[0]**2.*field.second_der[1] - field.first_der[1]**2.*field.second_der[0]
169            den = field.first_der[0]**2. + field.first_der[1]**2.
170            return num / den
171            
172    if field.dim == 3:
173        if order == 1: # Mean curvature (times 2), 2H = κ_1 + κ_2   (times |∇f|)
174            mod_grad = np.sqrt(np.sum(field.first_der**2., axis=0))
175            hessian = np.array([[field.second_der[0], field.second_der[3], field.second_der[4]],
176                                [field.second_der[3], field.second_der[1], field.second_der[5]],
177                                [field.second_der[4], field.second_der[5], field.second_der[2]]])
178            norm_grad = (field.first_der / mod_grad)
179            mean_curvature = np.einsum('j...,jk...,k...->...', norm_grad, hessian, norm_grad) - np.trace(hessian, axis1=0, axis2=1)
180            return mean_curvature
181            
182        if order == 2: # Gaussian curvature, K = κ_1 · κ_2   (times |∇f|)
183            mod_grad = np.sqrt(np.sum(field.first_der**2., axis=0))
184            norm_grad = (field.first_der / mod_grad)
185            norm_second_der = (field.second_der / mod_grad)
186            extended_hessian_det = np.linalg.det(np.array([[norm_second_der[0], norm_second_der[3], norm_second_der[4], norm_grad[0]],
187                                                           [norm_second_der[3], norm_second_der[1], norm_second_der[5], norm_grad[1]],
188                                                           [norm_second_der[4], norm_second_der[5], norm_second_der[2], norm_grad[2]],
189                                                           [norm_grad[0], norm_grad[1], norm_grad[2], np.zeros_like(norm_grad[0])]]).T).T
190            return -extended_hessian_det*mod_grad
191
192        
193    if field.dim > 3:
194        raise NotImplementedError("The computation of principal curvatures in not implemented for spaces with 4 or more dimensions. If you need this, please get in touch.")
195        # I am not sure if there is a easy expression for the principal curvatures of implicit surfaces in higher dimensions. Typically, one would need to create
196        # the second fundamental form of the surface with the orthonormal bais of the tangent space; its eigenvalues are the principal curvatures at that point.
197        
198
199    
200def V2(field, us, edges=False, verbose=True):
201    """Compute the third Minkowski Functional, $V_2$, normalized by the volume of the space.
202
203    Parameters
204    ----------
205    field : DataField or TheoryField
206        Field on which to compute $V_2$, which can be a theoretical field or a data field. Its dimension must be at least 2.
207        
208    us : np.array
209        The thresholds where $V_2$ is computed.
210        
211    edges : bool, optional
212        If False (default), the given `us` is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins.
213        If True, input `us` is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. 
214        In the latter case, the effective thresholds are the central value of the given bins.
215        
216    verbose : bool, optional
217        If True (default), progress bars are shown for the computations on data.
218    
219    Returns
220    -------
221    V2 : np.array
222        The values of the second Minkowski Functional at the given thresholds.
223    
224    """
225    if field.dim<2:
226        raise ValueError(f"V2 is defined for fields with at least 2 dimensions, but this field is {field.dim}D.")
227        
228    us = np.atleast_1d(us)
229    us, dus = define_ubins(us, edges)
230    
231    if isinstance(field, TheoryField):
232        us = subsample_us(us, dus)
233        try:
234            return np.nanmean(field.V2(us), axis=1)
235        except AttributeError:
236            raise NotImplementedError(f"The theoretical expectation of V2 for {field.name} fields is not implemented. If you know an expression, please get in touch.") from None
237        
238    elif isinstance(field, DataField):
239        try:
240            return field._V2(us, dus, verbose=verbose)
241        except AttributeError:
242            if field.first_der is None:
243                field.get_first_der()
244            if field.second_der is None:
245                field.get_second_der()
246                
247            stat = np.zeros_like(us)
248            curv = general_curvature(field, 1)
249            for ii in tqdm(np.arange(us.shape[0]), disable=not verbose):
250                this_mask = (us[ii] + dus[ii]/2. > field.field) & (us[ii] - dus[ii]/2. <= field.field)
251                stat[ii] = np.mean((curv*this_mask/dus[ii])[field.mask])
252            return _MF_prefactor(field.dim, 2) * stat
253        
254    else:
255        raise TypeError(f"The field must be either TheoryField or DataField (or a subclass).")
256        
257
258def V3(field, us, edges=False, verbose=True):
259    """Compute the third Minkowski Functional, $V_3$, normalized by the volume of the space.
260
261    Parameters
262    ----------
263    field : DataField or TheoryField
264        Field on which to compute $V_3$, which can be a theoretical field or a data field. Its dimension must be at least 3.
265        
266    us : np.array
267        The thresholds where $V_3$ is computed.
268        
269    edges : bool, optional
270        If False (default), the given `us` is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins.
271        If True, input `us` is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. 
272        In the latter case, the effective thresholds are the central value of the given bins.
273        
274    verbose : bool, optional
275        If True (default), progress bars are shown for the computations on data.
276    
277    Returns
278    -------
279    V3 : np.array()
280        The values of the second Minkowski Functional at the given thresholds.
281    
282    """
283    if field.dim<3:
284        raise ValueError(f"V3 is defined for fields with at least 3 dimensions, but this field is {field.dim}D.")
285        
286    us = np.atleast_1d(us)
287    us, dus = define_ubins(us, edges)
288    
289    if isinstance(field, TheoryField):
290        us = subsample_us(us, dus)
291        try:
292            return np.nanmean(field.V3(us), axis=1)
293        except AttributeError:
294            raise NotImplementedError(f"The theoretical expectation of V3 for {field.name} fields is not implemented. If you know an expression, please get in touch.") from None
295        
296    elif isinstance(field, DataField):
297        try:
298            return field._V3(us, dus, verbose=verbose)
299        except AttributeError:
300            if field.first_der is None:
301                field.get_first_der()
302            if field.second_der is None:
303                field.get_second_der()
304                
305            stat = np.zeros_like(us)
306            curv = general_curvature(field, 2)
307            for ii in tqdm(np.arange(us.shape[0]), disable=not verbose):
308                this_mask = (us[ii] + dus[ii]/2. > field.field) & (us[ii] - dus[ii]/2. <= field.field)
309                stat[ii] = np.mean((curv*this_mask/dus[ii])[field.mask])
310            return _MF_prefactor(field.dim, 3) * stat
311        
312    else:
313        raise TypeError(f"The field must be either TheoryField or DataField (or a subclass).")
314
315
316__all__ = ["V0", "V1", "V2", "V3", "general_curvature"]
317
318__docformat__ = "numpy"
def V0(field, us, edges=False, verbose=True):
35def V0(field, us, edges=False, verbose=True):
36    """Compute the first Minkowski Functional, $V_0$, normalized by the volume of the space.
37
38    Parameters
39    ----------
40    field : DataField or TheoryField
41        Field on which to compute $V_0$, which can be a theoretical field or a data field.
42        
43    us : np.array
44        The thresholds where $V_0$ is computed.
45        
46    edges : bool, optional
47        If False (default), the given `us` is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins.
48        If True, input `us` is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. 
49        In the latter case, the effective thresholds are the central value of the given bins.
50        
51    verbose : bool, optional
52        If True (default), progress bars are shown for the computations on data.
53    
54    Returns
55    -------
56    V0 : np.array
57        The values of the first Minkowski Functional at the given thresholds.
58    
59    """
60    us = np.atleast_1d(us)
61    us, dus = define_ubins(us, edges)
62    
63    if isinstance(field, TheoryField):
64        us = subsample_us(us, dus)
65        try:
66            return np.nanmean(field.V0(us), axis=1)
67        except AttributeError:
68            raise NotImplementedError(f"The theoretical expectation of V0 for {field.name} fields is not implemented. If you know an expression, please get in touch.")
69        
70    if isinstance(field, DataField):
71        try:
72            return field._V0(us, dus, verbose=verbose)
73        except AttributeError:                
74            stat = np.zeros_like(us)
75            for ii in tqdm(np.arange(us.shape[0]), disable=not verbose):
76                stat[ii] = np.mean((field.field > us[ii])[field.mask])
77            return stat
78        
79    else:
80        raise TypeError(f"The field must be either TheoryField or DataField (or a subclass).")

Compute the first Minkowski Functional, $V_0$, normalized by the volume of the space.

Parameters
  • field (DataField or TheoryField): Field on which to compute $V_0$, which can be a theoretical field or a data field.
  • us (np.array): The thresholds where $V_0$ is computed.
  • edges (bool, optional): If False (default), the given us is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins. If True, input us is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. In the latter case, the effective thresholds are the central value of the given bins.
  • verbose (bool, optional): If True (default), progress bars are shown for the computations on data.
Returns
  • V0 (np.array): The values of the first Minkowski Functional at the given thresholds.
def V1(field, us, edges=False, verbose=True):
 83def V1(field, us, edges=False, verbose=True):
 84    """Compute the second Minkowski Functional, $V_1$, normalized by the volume of the space.
 85
 86    Parameters
 87    ----------
 88    field : DataField or TheoryField
 89        Field on which to compute $V_1$, which can be a theoretical field or a data field.
 90        
 91    us : np.array
 92        The thresholds where $V_1$ is computed.
 93        
 94    edges : bool, optional
 95        If False (default), the given `us` is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins.
 96        If True, input `us` is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. 
 97        In the latter case, the effective thresholds are the central value of the given bins.
 98        
 99    verbose : bool, optional
100        If True (default), progress bars are shown for the computations on data.
101    
102    Returns
103    -------
104    V1 : np.array
105        The values of the second Minkowski Functional at the given thresholds.
106    
107    """
108    us = np.atleast_1d(us)
109    us, dus = define_ubins(us, edges)
110    
111    if isinstance(field, TheoryField):
112        us = subsample_us(us, dus)
113        try:
114            return np.nanmean(field.V1(us), axis=1)
115        except AttributeError:
116            raise NotImplementedError(f"The theoretical expectation of V1 for {field.name} fields is not implemented. If you know an expression, please get in touch.") from None
117        
118    elif isinstance(field, DataField):
119        try:
120            return field._V1(us, dus, verbose=verbose)
121        except AttributeError:
122            if field.first_der is None:
123                field.get_first_der()
124                
125            stat = np.zeros_like(us)
126            grad_modulus = np.sqrt(np.sum((field.first_der)**2., axis=0))
127            for ii in tqdm(np.arange(us.shape[0]), disable=not verbose):
128                this_mask = (us[ii] + dus[ii]/2. > field.field) & (us[ii] - dus[ii]/2. <= field.field)
129                stat[ii] = np.mean((grad_modulus*this_mask/dus[ii])[field.mask])
130            return _MF_prefactor(field.dim, 1) * stat
131        
132    else:
133        raise TypeError(f"The field must be either TheoryField or DataField (or a subclass).")

Compute the second Minkowski Functional, $V_1$, normalized by the volume of the space.

Parameters
  • field (DataField or TheoryField): Field on which to compute $V_1$, which can be a theoretical field or a data field.
  • us (np.array): The thresholds where $V_1$ is computed.
  • edges (bool, optional): If False (default), the given us is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins. If True, input us is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. In the latter case, the effective thresholds are the central value of the given bins.
  • verbose (bool, optional): If True (default), progress bars are shown for the computations on data.
Returns
  • V1 (np.array): The values of the second Minkowski Functional at the given thresholds.
def V2(field, us, edges=False, verbose=True):
201def V2(field, us, edges=False, verbose=True):
202    """Compute the third Minkowski Functional, $V_2$, normalized by the volume of the space.
203
204    Parameters
205    ----------
206    field : DataField or TheoryField
207        Field on which to compute $V_2$, which can be a theoretical field or a data field. Its dimension must be at least 2.
208        
209    us : np.array
210        The thresholds where $V_2$ is computed.
211        
212    edges : bool, optional
213        If False (default), the given `us` is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins.
214        If True, input `us` is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. 
215        In the latter case, the effective thresholds are the central value of the given bins.
216        
217    verbose : bool, optional
218        If True (default), progress bars are shown for the computations on data.
219    
220    Returns
221    -------
222    V2 : np.array
223        The values of the second Minkowski Functional at the given thresholds.
224    
225    """
226    if field.dim<2:
227        raise ValueError(f"V2 is defined for fields with at least 2 dimensions, but this field is {field.dim}D.")
228        
229    us = np.atleast_1d(us)
230    us, dus = define_ubins(us, edges)
231    
232    if isinstance(field, TheoryField):
233        us = subsample_us(us, dus)
234        try:
235            return np.nanmean(field.V2(us), axis=1)
236        except AttributeError:
237            raise NotImplementedError(f"The theoretical expectation of V2 for {field.name} fields is not implemented. If you know an expression, please get in touch.") from None
238        
239    elif isinstance(field, DataField):
240        try:
241            return field._V2(us, dus, verbose=verbose)
242        except AttributeError:
243            if field.first_der is None:
244                field.get_first_der()
245            if field.second_der is None:
246                field.get_second_der()
247                
248            stat = np.zeros_like(us)
249            curv = general_curvature(field, 1)
250            for ii in tqdm(np.arange(us.shape[0]), disable=not verbose):
251                this_mask = (us[ii] + dus[ii]/2. > field.field) & (us[ii] - dus[ii]/2. <= field.field)
252                stat[ii] = np.mean((curv*this_mask/dus[ii])[field.mask])
253            return _MF_prefactor(field.dim, 2) * stat
254        
255    else:
256        raise TypeError(f"The field must be either TheoryField or DataField (or a subclass).")

Compute the third Minkowski Functional, $V_2$, normalized by the volume of the space.

Parameters
  • field (DataField or TheoryField): Field on which to compute $V_2$, which can be a theoretical field or a data field. Its dimension must be at least 2.
  • us (np.array): The thresholds where $V_2$ is computed.
  • edges (bool, optional): If False (default), the given us is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins. If True, input us is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. In the latter case, the effective thresholds are the central value of the given bins.
  • verbose (bool, optional): If True (default), progress bars are shown for the computations on data.
Returns
  • V2 (np.array): The values of the second Minkowski Functional at the given thresholds.
def V3(field, us, edges=False, verbose=True):
259def V3(field, us, edges=False, verbose=True):
260    """Compute the third Minkowski Functional, $V_3$, normalized by the volume of the space.
261
262    Parameters
263    ----------
264    field : DataField or TheoryField
265        Field on which to compute $V_3$, which can be a theoretical field or a data field. Its dimension must be at least 3.
266        
267    us : np.array
268        The thresholds where $V_3$ is computed.
269        
270    edges : bool, optional
271        If False (default), the given `us` is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins.
272        If True, input `us` is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. 
273        In the latter case, the effective thresholds are the central value of the given bins.
274        
275    verbose : bool, optional
276        If True (default), progress bars are shown for the computations on data.
277    
278    Returns
279    -------
280    V3 : np.array()
281        The values of the second Minkowski Functional at the given thresholds.
282    
283    """
284    if field.dim<3:
285        raise ValueError(f"V3 is defined for fields with at least 3 dimensions, but this field is {field.dim}D.")
286        
287    us = np.atleast_1d(us)
288    us, dus = define_ubins(us, edges)
289    
290    if isinstance(field, TheoryField):
291        us = subsample_us(us, dus)
292        try:
293            return np.nanmean(field.V3(us), axis=1)
294        except AttributeError:
295            raise NotImplementedError(f"The theoretical expectation of V3 for {field.name} fields is not implemented. If you know an expression, please get in touch.") from None
296        
297    elif isinstance(field, DataField):
298        try:
299            return field._V3(us, dus, verbose=verbose)
300        except AttributeError:
301            if field.first_der is None:
302                field.get_first_der()
303            if field.second_der is None:
304                field.get_second_der()
305                
306            stat = np.zeros_like(us)
307            curv = general_curvature(field, 2)
308            for ii in tqdm(np.arange(us.shape[0]), disable=not verbose):
309                this_mask = (us[ii] + dus[ii]/2. > field.field) & (us[ii] - dus[ii]/2. <= field.field)
310                stat[ii] = np.mean((curv*this_mask/dus[ii])[field.mask])
311            return _MF_prefactor(field.dim, 3) * stat
312        
313    else:
314        raise TypeError(f"The field must be either TheoryField or DataField (or a subclass).")

Compute the third Minkowski Functional, $V_3$, normalized by the volume of the space.

Parameters
  • field (DataField or TheoryField): Field on which to compute $V_3$, which can be a theoretical field or a data field. Its dimension must be at least 3.
  • us (np.array): The thresholds where $V_3$ is computed.
  • edges (bool, optional): If False (default), the given us is assumed to be an array of uniformly distributed thresholds, which are taken as the central values of the bins. If True, input us is assumed to be a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform distributions of thresholds. In the latter case, the effective thresholds are the central value of the given bins.
  • verbose (bool, optional): If True (default), progress bars are shown for the computations on data.
Returns
  • V3 (np.array()): The values of the second Minkowski Functional at the given thresholds.
def general_curvature(field, order):
137def general_curvature(field, order):
138    """Computes the symmetrised polynomial of the principal curvatures of order `order`, multiplied by the modulus of the gradient, at every pixel.
139
140    Parameters
141    ----------
142    field : DataField
143        Field on which to compute the general curvatures.
144        
145    order : int
146        Order of the polynomial of the curvatures.
147    
148    Returns
149    -------
150    curvature : np.array
151        The value of the polynomial at each pixel. 
152        
153        For example:
154        `field.dim = 2`, `order=1` : geodesic curvature $\kappa$
155        `field.dim = 3`, `order=1` : mean curvature (H = ($\kappa_1$ + $\kappa_2$)/2 )
156        `field.dim = 3`, `order=2` : Gaussian curvature (K = $\kappa_1 \cdot \kappa_2$)
157    
158    """
159    assert isinstance(field, DataField), "The field must be a DataField (or subclass)"
160    assert field.dim > 1, "The field must be at least 2D"
161    if order >= field.dim:
162        raise ValueError("The order of the polynomial has to be less than the dimension of the field.")
163        
164    if order == 0:
165        return np.sqrt(np.sum(field.first_der**2., axis=0))
166    
167    if field.dim == 2:
168        if order == 1: # Geodesic curvature κ  (times |∇f|)
169            num = 2.*field.first_der[0]*field.first_der[1]*field.second_der[2] - field.first_der[0]**2.*field.second_der[1] - field.first_der[1]**2.*field.second_der[0]
170            den = field.first_der[0]**2. + field.first_der[1]**2.
171            return num / den
172            
173    if field.dim == 3:
174        if order == 1: # Mean curvature (times 2), 2H = κ_1 + κ_2   (times |∇f|)
175            mod_grad = np.sqrt(np.sum(field.first_der**2., axis=0))
176            hessian = np.array([[field.second_der[0], field.second_der[3], field.second_der[4]],
177                                [field.second_der[3], field.second_der[1], field.second_der[5]],
178                                [field.second_der[4], field.second_der[5], field.second_der[2]]])
179            norm_grad = (field.first_der / mod_grad)
180            mean_curvature = np.einsum('j...,jk...,k...->...', norm_grad, hessian, norm_grad) - np.trace(hessian, axis1=0, axis2=1)
181            return mean_curvature
182            
183        if order == 2: # Gaussian curvature, K = κ_1 · κ_2   (times |∇f|)
184            mod_grad = np.sqrt(np.sum(field.first_der**2., axis=0))
185            norm_grad = (field.first_der / mod_grad)
186            norm_second_der = (field.second_der / mod_grad)
187            extended_hessian_det = np.linalg.det(np.array([[norm_second_der[0], norm_second_der[3], norm_second_der[4], norm_grad[0]],
188                                                           [norm_second_der[3], norm_second_der[1], norm_second_der[5], norm_grad[1]],
189                                                           [norm_second_der[4], norm_second_der[5], norm_second_der[2], norm_grad[2]],
190                                                           [norm_grad[0], norm_grad[1], norm_grad[2], np.zeros_like(norm_grad[0])]]).T).T
191            return -extended_hessian_det*mod_grad
192
193        
194    if field.dim > 3:
195        raise NotImplementedError("The computation of principal curvatures in not implemented for spaces with 4 or more dimensions. If you need this, please get in touch.")
196        # I am not sure if there is a easy expression for the principal curvatures of implicit surfaces in higher dimensions. Typically, one would need to create
197        # the second fundamental form of the surface with the orthonormal bais of the tangent space; its eigenvalues are the principal curvatures at that point.

Computes the symmetrised polynomial of the principal curvatures of order order, multiplied by the modulus of the gradient, at every pixel.

Parameters
  • field (DataField): Field on which to compute the general curvatures.
  • order (int): Order of the polynomial of the curvatures.
Returns
  • curvature (np.array): The value of the polynomial at each pixel.

    For example: field.dim = 2, order=1 : geodesic curvature $\kappa$ field.dim = 3, order=1 : mean curvature (H = ($\kappa_1$ + $\kappa_2$)/2 ) field.dim = 3, order=2 : Gaussian curvature (K = $\kappa_1 \cdot \kappa_2$)