civet.extraction

Submodule for more specific types which are relevant to surface-extraction subroutines of the CIVET pipeline.

 1"""
 2Submodule for more specific types which are relevant to
 3surface-extraction subroutines of the CIVET pipeline.
 4"""
 5
 6from civet.extraction.side import Side
 7from civet.extraction.hemisphere import HemisphereMask
 8from civet.extraction.surfaces import RegularSurface, IrregularSurface, Tetra
 9from civet.extraction.starting_models import SurfaceModel
10
11__all__ = [
12    'HemisphereMask',
13    'Side',
14    'RegularSurface',
15    'IrregularSurface',
16    'Tetra',
17    'SurfaceModel'
18]
class HemisphereMask(civet.minc.GenericMask[ForwardRef('HemisphereMask')]):
19class HemisphereMask(GenericMask['HemisphereMask']):
20    """
21    Represents a binary mask of a brain hemisphere (either left or right).
22    """
23
24    def just_sphere_mesh(self, side: Optional[Side] = None, subsample: bool = False) -> IrregularSurface:
25        """
26        Just run `sphere_mesh`, which produces a mesh with non-standard connectivity.
27        """
28        model = self.get_model_for(side)
29        return self.prepare_for_sphere_mesh(model).sphere_mesh(subsample)
30
31    def smoothen_using_mincmorph(self, iterations: int = 5, lower: float = 2.5, upper: float = 4.5) -> 'HemisphereMask':
32        """
33        Simplify connectivity of this mask using mincmorph.
34        This is a preprocessing step to `sphere_mesh`.
35
36        https://github.com/aces/surface-extraction/blob/7c9c5987a2f8f5fdeb8d3fd15f2f9b636401d9a1/scripts/marching_cubes.pl.in#L166-L184
37        """
38        if not isinstance(iterations, int) or iterations < 0:
39            raise ValueError(f'iterations {iterations} < 0')
40
41        smoother = self
42
43        for _ in range(iterations):
44            ngh_count = smoother.mincmorph_convolve_u8()
45            expression = f'if(A[1]<{lower})' r'{0}else{' f'if(A[1]>{upper})' r'{1}else{A[0]}}'
46            smoother = self.minccalc_u8(expression, ngh_count)
47
48        return smoother
49
50    def prepare_for_sphere_mesh(self, initial_model: SurfaceModel) -> 'SphereMeshMask':
51        """
52        Prepare this mask for `sphere_mesh`.
53
54        https://github.com/aces/surface-extraction/blob/7c9c5987a2f8f5fdeb8d3fd15f2f9b636401d9a1/scripts/marching_cubes.pl.in#L189-L207
55
56        Parameters
57        ----------
58        initial_model: SurfaceModel
59            bounds
60        """
61        filled = self.minccalc_u8('out=1')
62        surface_mask_vol = initial_model.surface_mask2(filled)
63        resampled = surface_mask_vol.mincresample(filled)
64        overlap = self.minccalc_u8('if(A[0]>0.5||A[1]>0.5){1}else{0}', resampled)
65        dilated = overlap.dilate_volume(1, 26, 1)
66        added = self.minccalc_u8('A[0]+A[1]', dilated)
67        defragged = added.reshape255().mincdefrag(2, 19)
68        return SphereMeshMask(defragged)
69
70    @staticmethod
71    def get_model_for(side: Optional[Side] = None) -> SurfaceModel:
72        """
73        Transform `WHITE_MODEL_320` as necessary in preparation for use with `sphere_mesh`.
74
75        https://github.com/aces/surface-extraction/blob/7c9c5987a2f8f5fdeb8d3fd15f2f9b636401d9a1/scripts/marching_cubes.pl.in#L118-L135
76        """
77        initial_model = WHITE_MODEL_320
78        if side is Side.LEFT:
79            return initial_model.slide_left()
80        elif side is Side.RIGHT:
81            return initial_model.flip_x().slide_right()
82        else:
83            return initial_model

Represents a binary mask of a brain hemisphere (either left or right).

def just_sphere_mesh( self, side: Optional[civet.extraction.Side] = None, subsample: bool = False) -> civet.extraction.IrregularSurface:
24    def just_sphere_mesh(self, side: Optional[Side] = None, subsample: bool = False) -> IrregularSurface:
25        """
26        Just run `sphere_mesh`, which produces a mesh with non-standard connectivity.
27        """
28        model = self.get_model_for(side)
29        return self.prepare_for_sphere_mesh(model).sphere_mesh(subsample)

Just run sphere_mesh, which produces a mesh with non-standard connectivity.

def smoothen_using_mincmorph( self, iterations: int = 5, lower: float = 2.5, upper: float = 4.5) -> civet.extraction.HemisphereMask:
31    def smoothen_using_mincmorph(self, iterations: int = 5, lower: float = 2.5, upper: float = 4.5) -> 'HemisphereMask':
32        """
33        Simplify connectivity of this mask using mincmorph.
34        This is a preprocessing step to `sphere_mesh`.
35
36        https://github.com/aces/surface-extraction/blob/7c9c5987a2f8f5fdeb8d3fd15f2f9b636401d9a1/scripts/marching_cubes.pl.in#L166-L184
37        """
38        if not isinstance(iterations, int) or iterations < 0:
39            raise ValueError(f'iterations {iterations} < 0')
40
41        smoother = self
42
43        for _ in range(iterations):
44            ngh_count = smoother.mincmorph_convolve_u8()
45            expression = f'if(A[1]<{lower})' r'{0}else{' f'if(A[1]>{upper})' r'{1}else{A[0]}}'
46            smoother = self.minccalc_u8(expression, ngh_count)
47
48        return smoother

Simplify connectivity of this mask using mincmorph. This is a preprocessing step to sphere_mesh.

https://github.com/aces/surface-extraction/blob/7c9c5987a2f8f5fdeb8d3fd15f2f9b636401d9a1/scripts/marching_cubes.pl.in#L166-L184

def prepare_for_sphere_mesh( self, initial_model: civet.extraction.SurfaceModel) -> civet.extraction.hemisphere.SphereMeshMask:
50    def prepare_for_sphere_mesh(self, initial_model: SurfaceModel) -> 'SphereMeshMask':
51        """
52        Prepare this mask for `sphere_mesh`.
53
54        https://github.com/aces/surface-extraction/blob/7c9c5987a2f8f5fdeb8d3fd15f2f9b636401d9a1/scripts/marching_cubes.pl.in#L189-L207
55
56        Parameters
57        ----------
58        initial_model: SurfaceModel
59            bounds
60        """
61        filled = self.minccalc_u8('out=1')
62        surface_mask_vol = initial_model.surface_mask2(filled)
63        resampled = surface_mask_vol.mincresample(filled)
64        overlap = self.minccalc_u8('if(A[0]>0.5||A[1]>0.5){1}else{0}', resampled)
65        dilated = overlap.dilate_volume(1, 26, 1)
66        added = self.minccalc_u8('A[0]+A[1]', dilated)
67        defragged = added.reshape255().mincdefrag(2, 19)
68        return SphereMeshMask(defragged)
@staticmethod
def get_model_for( side: Optional[civet.extraction.Side] = None) -> civet.extraction.SurfaceModel:
70    @staticmethod
71    def get_model_for(side: Optional[Side] = None) -> SurfaceModel:
72        """
73        Transform `WHITE_MODEL_320` as necessary in preparation for use with `sphere_mesh`.
74
75        https://github.com/aces/surface-extraction/blob/7c9c5987a2f8f5fdeb8d3fd15f2f9b636401d9a1/scripts/marching_cubes.pl.in#L118-L135
76        """
77        initial_model = WHITE_MODEL_320
78        if side is Side.LEFT:
79            return initial_model.slide_left()
80        elif side is Side.RIGHT:
81            return initial_model.flip_x().slide_right()
82        else:
83            return initial_model
class Side(enum.Enum):
 5class Side(Enum):
 6    """
 7    Brain hemisphere side.
 8    """
 9    LEFT = 'left'
10    RIGHT = 'right'

Brain hemisphere side.

LEFT = <Side.LEFT: 'left'>
RIGHT = <Side.RIGHT: 'right'>
Inherited Members
enum.Enum
name
value
class RegularSurface(civet.obj.GenericSurface[~_RS], typing.Generic[~_RS]):
31class RegularSurface(GenericSurface[_RS], Generic[_RS]):
32    """
33    Represents a mesh (`.obj`) with standard connectivity.
34
35    Typically, standard connectivity means 81,920 triangles, 41,962
36    vertices. By convention, the file name for such a surface should
37    have the suffix `_81920.obj`.
38
39    A general definition for "standard connectivity" would be a
40    polygonal mesh of *N* triangles where 320 and 4 are common
41    denominators of *N*.
42    """
43    @classmethod
44    def create_tetra(cls, tetra: Tetra) -> 'RegularSurface[RegularSurface]':
45        return cls(tetra)

Represents a mesh (.obj) with standard connectivity.

Typically, standard connectivity means 81,920 triangles, 41,962 vertices. By convention, the file name for such a surface should have the suffix _81920.obj.

A general definition for "standard connectivity" would be a polygonal mesh of N triangles where 320 and 4 are common denominators of N.

@classmethod
def create_tetra( cls, tetra: civet.extraction.Tetra) -> civet.extraction.RegularSurface[civet.extraction.RegularSurface]:
43    @classmethod
44    def create_tetra(cls, tetra: Tetra) -> 'RegularSurface[RegularSurface]':
45        return cls(tetra)
class IrregularSurface(civet.obj.GenericSurface[~_IS], typing.Generic[~_IS]):
48class IrregularSurface(GenericSurface[_IS], Generic[_IS]):
49    """
50    Represents a mesh (`.obj`) with irregular connectivity.
51    """
52    def interpolate_with_sphere(
53            self,
54            side: Optional[Side] = None,
55            n_inflate: Optional[int] = None,
56            n_smooth: Optional[int] = None
57    ) -> RegularSurface:
58        """
59        Resample this surface to have a standard number of 81,920 triangles.
60
61        If `n_inflate` and `n_smooth` are given, use `inflate_to_sphere_implicit` instead
62        of `inflate_to_sphere`.
63        """
64        options = []
65        if side is not None:
66            options += ['-' + side.value]
67        if (n_inflate is None) ^ (n_smooth is None):
68            raise ValueError('both n_inflate and n_smooth must be specified')
69        if n_inflate is not None and n_smooth is not None:
70            options += ['-inflate', str(n_inflate), str(n_smooth)]
71
72        class InterpolatedFromSphere(RegularSurface):
73            def command(self, output: str | PathLike
74                        ) -> Sequence[str | PathLike | AbstractDataCommand]:
75                return 'interpolate_surface_with_sphere.pl', *options, self.input, output
76        return InterpolatedFromSphere(self)

Represents a mesh (.obj) with irregular connectivity.

def interpolate_with_sphere( self, side: Optional[civet.extraction.Side] = None, n_inflate: Optional[int] = None, n_smooth: Optional[int] = None) -> civet.extraction.RegularSurface:
52    def interpolate_with_sphere(
53            self,
54            side: Optional[Side] = None,
55            n_inflate: Optional[int] = None,
56            n_smooth: Optional[int] = None
57    ) -> RegularSurface:
58        """
59        Resample this surface to have a standard number of 81,920 triangles.
60
61        If `n_inflate` and `n_smooth` are given, use `inflate_to_sphere_implicit` instead
62        of `inflate_to_sphere`.
63        """
64        options = []
65        if side is not None:
66            options += ['-' + side.value]
67        if (n_inflate is None) ^ (n_smooth is None):
68            raise ValueError('both n_inflate and n_smooth must be specified')
69        if n_inflate is not None and n_smooth is not None:
70            options += ['-inflate', str(n_inflate), str(n_smooth)]
71
72        class InterpolatedFromSphere(RegularSurface):
73            def command(self, output: str | PathLike
74                        ) -> Sequence[str | PathLike | AbstractDataCommand]:
75                return 'interpolate_surface_with_sphere.pl', *options, self.input, output
76        return InterpolatedFromSphere(self)

Resample this surface to have a standard number of 81,920 triangles.

If n_inflate and n_smooth are given, use inflate_to_sphere_implicit instead of inflate_to_sphere.

@dataclass(frozen=True)
class Tetra(civet.bases.DataSource):
20@dataclass(frozen=True)
21class Tetra(DataSource):
22
23    center: tuple[float, float, float] = (0.0, 0.0, 0.0)
24    radius: tuple[float, float, float] = (1.0, 1.0, 1.0)
25    triangles: int = 81920
26
27    def command(self, output: str | PathLike) -> Sequence[str | PathLike]:
28        return 'create_tetra', output, *self.center, *self.radius
Tetra( center: tuple[float, float, float] = (0.0, 0.0, 0.0), radius: tuple[float, float, float] = (1.0, 1.0, 1.0), triangles: int = 81920)
def command(self, output: str | os.PathLike) -> Sequence[Union[str, os.PathLike]]:
27    def command(self, output: str | PathLike) -> Sequence[str | PathLike]:
28        return 'create_tetra', output, *self.center, *self.radius
12class SurfaceModel(RegularSurface['SurfaceModel']):
13    """
14    Represents a surface data file from the `$MNI_DATAPATH/surface-extraction` directory.
15    """
16    @classmethod
17    def get_model(cls, name: str) -> Optional['SurfaceModel']:
18        data_paths = MNI_DATAPATH.split(':')
19        possible_models = map(lambda b: Path(b) / 'surface-extraction' / name, data_paths)
20        actual_model_paths = filter(lambda p: p.is_file(), possible_models)
21        actual_models = map(cls, actual_model_paths)
22        return next(actual_models, None)

Represents a surface data file from the $MNI_DATAPATH/surface-extraction directory.

@classmethod
def get_model( cls, name: str) -> Optional[civet.extraction.SurfaceModel]:
16    @classmethod
17    def get_model(cls, name: str) -> Optional['SurfaceModel']:
18        data_paths = MNI_DATAPATH.split(':')
19        possible_models = map(lambda b: Path(b) / 'surface-extraction' / name, data_paths)
20        actual_model_paths = filter(lambda p: p.is_file(), possible_models)
21        actual_models = map(cls, actual_model_paths)
22        return next(actual_models, None)