Skip to content

volume.py

activate(schema_name, *, create_schema=True, create_tables=True, linking_module=None)

Activate this schema

Parameters:

Name Type Description Default
schema_name str

schema name on the database server to activate the zstack element

required
create_schema bool

when True (default), create schema in the database

True
create_tables bool

when True (default), create schema tables in the database if they do not yet exist.

True
linking_module str

A string containing the module name or module

None
Tables

Scan: A parent table to Volume

Functions

get_volume_root_data_dir: Returns absolute path for root data director(y/ies) with all volumetric data, as a list of string(s). get_volume_tif_file: When given a scan key (dict), returns the full path to the TIF file of the volumetric data associated with a given scan.

Source code in element_zstack/volume.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def activate(
    schema_name: str,
    *,
    create_schema: bool = True,
    create_tables: bool = True,
    linking_module: str = None,
):
    """Activate this schema

    Args:
        schema_name (str): schema name on the database server to activate the `zstack` element
        create_schema (bool): when True (default), create schema in the database
        if it does not yet exist.
        create_tables (bool): when True (default), create schema tables in the database if they do not yet exist.
        linking_module (str): A string containing the module name or module
        containing the required dependencies to activate the schema.

    Tables:
        Scan: A parent table to Volume
    Functions:
        get_volume_root_data_dir: Returns absolute path for root data
        director(y/ies) with all volumetric data, as a list of string(s).
        get_volume_tif_file: When given a scan key (dict), returns the full path
        to the TIF file of the volumetric data associated with a given scan.
    """

    if isinstance(linking_module, str):
        linking_module = importlib.import_module(linking_module)
    assert inspect.ismodule(
        linking_module
    ), "The argument 'linking_module' must be a module's name or a module"

    global _linking_module
    _linking_module = linking_module

    schema.activate(
        schema_name,
        create_schema=create_schema,
        create_tables=create_tables,
        add_objects=_linking_module.__dict__,
    )

get_volume_root_data_dir()

Fetches absolute data path to volume data directories.

The absolute path here is used as a reference for all downstream relative paths used in DataJoint.

Returns:

Type Description
list

A list of the absolute path(s) to volume data directories.

Source code in element_zstack/volume.py
65
66
67
68
69
70
71
72
73
74
75
76
77
def get_volume_root_data_dir() -> list:
    """Fetches absolute data path to volume data directories.

    The absolute path here is used as a reference for all downstream relative paths used in DataJoint.

    Returns:
        A list of the absolute path(s) to volume data directories.
    """
    root_directories = _linking_module.get_volume_root_data_dir()
    if isinstance(root_directories, (str, Path)):
        root_directories = [root_directories]

    return root_directories

get_volume_tif_file(scan_key)

Retrieve the full path to the TIF file of the volumetric data associated with a given scan.

Parameters:

Name Type Description Default
scan_key dict

Primary key of a Scan entry.

required

Returns:

Type Description
(str, Path)

Full path to the TIF file of the volumetric data (Path or str).

Source code in element_zstack/volume.py
80
81
82
83
84
85
86
87
def get_volume_tif_file(scan_key: dict) -> (str, Path):
    """Retrieve the full path to the TIF file of the volumetric data associated with a given scan.
    Args:
        scan_key: Primary key of a Scan entry.
    Returns:
        Full path to the TIF file of the volumetric data (Path or str).
    """
    return _linking_module.get_volume_tif_file(scan_key)

Volume

Bases: dj.Imported

Details about the volumetric microscopic imaging scans.

Attributes:

Name Type Description
Scan foreign key

Primary key from imaging.Scan.

px_width int

total number of voxels in the x dimension.

px_height int

total number of voxels in the y dimension.

px_depth int

total number of voxels in the z dimension.

depth_mean_brightness longblob

optional, mean brightness of each slice across

volume_file_path str

Relative path of the volumetric data with shape (z, y, x)

Source code in element_zstack/volume.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
@schema
class Volume(dj.Imported):
    """Details about the volumetric microscopic imaging scans.

    Attributes:
        Scan (foreign key): Primary key from `imaging.Scan`.
        px_width (int): total number of voxels in the x dimension.
        px_height (int): total number of voxels in the y dimension.
        px_depth (int): total number of voxels in the z dimension.
        depth_mean_brightness (longblob): optional, mean brightness of each slice across
        the depth (z) dimension of the stack.
        volume_file_path (str): Relative path of the volumetric data with shape (z, y, x)
    """

    definition = """
    -> Scan
    ---
    px_width: int # total number of voxels in x dimension
    px_height: int # total number of voxels in y dimension
    px_depth: int # total number of voxels in z dimension
    depth_mean_brightness=null: longblob  # mean brightness of each slice across the depth (z) dimension of the stack
    volume_file_path: varchar(255)  # Relative path of the volumetric data with shape (z, y, x)
    """

    def make(self, key):
        """Populate the Volume table with volumetric microscopic imaging data."""
        volume_file_path = get_volume_tif_file(key)
        volume_data = TiffFile(volume_file_path).asarray()

        root_dir = find_root_directory(get_volume_root_data_dir(), volume_file_path)
        volume_relative_path = Path(volume_file_path).relative_to(root_dir).as_posix()

        self.insert1(
            dict(
                **key,
                volume_file_path=volume_relative_path,
                px_width=volume_data.shape[2],
                px_height=volume_data.shape[1],
                px_depth=volume_data.shape[0],
                depth_mean_brightness=volume_data.mean(axis=(1, 2)),
            )
        )

make(key)

Populate the Volume table with volumetric microscopic imaging data.

Source code in element_zstack/volume.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def make(self, key):
    """Populate the Volume table with volumetric microscopic imaging data."""
    volume_file_path = get_volume_tif_file(key)
    volume_data = TiffFile(volume_file_path).asarray()

    root_dir = find_root_directory(get_volume_root_data_dir(), volume_file_path)
    volume_relative_path = Path(volume_file_path).relative_to(root_dir).as_posix()

    self.insert1(
        dict(
            **key,
            volume_file_path=volume_relative_path,
            px_width=volume_data.shape[2],
            px_height=volume_data.shape[1],
            px_depth=volume_data.shape[0],
            depth_mean_brightness=volume_data.mean(axis=(1, 2)),
        )
    )

VoxelSize

Bases: dj.Manual

Voxel size information about a volume in millimeters.

Attributes:

Name Type Description
Volume foreign key

Primary key from Volume.

width float

Voxel size in mm in the x dimension.

height float

Voxel size in mm in the y dimension.

depth float

Voxel size in mm in the z dimension.

Source code in element_zstack/volume.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
@schema
class VoxelSize(dj.Manual):
    """Voxel size information about a volume in millimeters.

    Attributes:
        Volume (foreign key): Primary key from `Volume`.
        width (float): Voxel size in mm in the x dimension.
        height (float): Voxel size in mm in the y dimension.
        depth (float): Voxel size in mm in the z dimension.
    """

    definition = """
    -> Volume
    ---
    width: float # voxel size in mm in the x dimension
    height: float # voxel size in mm in the y dimension
    depth: float # voxel size in mm in the z dimension
    """

SegmentationParamSet

Bases: dj.Lookup

Parameter set used for segmentation of the volumetric microscopic imaging scan.

Attributes:

Name Type Description
paramset_idx int

Unique parameter set identifier.

segmentation_method str

Name of the segmentation method (e.g.

paramset_desc str

Optional. Parameter set description.

params longblob

Parameter set. Dictionary of all applicable

paramset_hash uuid

A universally unique identifier for the parameter set.

Source code in element_zstack/volume.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
@schema
class SegmentationParamSet(dj.Lookup):
    """Parameter set used for segmentation of the volumetric microscopic imaging
    scan.

    Attributes:
        paramset_idx (int): Unique parameter set identifier.
        segmentation_method (str): Name of the segmentation method (e.g.
        cellpose).
        paramset_desc (str): Optional. Parameter set description.
        params (longblob): Parameter set. Dictionary of all applicable
        parameters for the segmentation method.
        paramset_hash (uuid): A universally unique identifier for the parameter set.
    """

    definition = """
    paramset_idx: int
    ---
    segmentation_method: varchar(32)
    paramset_desc="": varchar(256)
    params: longblob
    paramset_hash: uuid
    unique index (paramset_hash)
    """

    @classmethod
    def insert_new_params(
        cls,
        segmentation_method: str,
        params: dict,
        paramset_desc: str = "",
        paramset_idx: int = None,
    ):
        """Inserts new parameters into the table.

        Args:
            segmentation_method (str): name of the segmentation method (e.g. cellpose)
            params (dict): segmentation parameters
            paramset_desc (str, optional): description of the parameter set
            paramset_idx (int, optional): Unique parameter set ID. Defaults to None.
        """
        if paramset_idx is None:
            paramset_idx = (
                dj.U().aggr(cls, n="max(paramset_idx)").fetch1("n") or 0
            ) + 1

        param_dict = {
            "segmentation_method": segmentation_method,
            "paramset_desc": paramset_desc,
            "params": params,
            "paramset_idx": paramset_idx,
            "paramset_hash": dict_to_uuid(
                {**params, "segmentation_method": segmentation_method}
            ),
        }
        param_query = cls & {"paramset_hash": param_dict["paramset_hash"]}

        if param_query:  # If the specified param-set already exists
            existing_paramset_idx = param_query.fetch1("paramset_idx")
            if (
                existing_paramset_idx == paramset_idx
            ):  # If the existing set has the same paramset_idx: job done
                return
            else:  # If not same name: human error, trying to add the same paramset with different name
                raise dj.DataJointError(
                    f"The specified param-set already exists"
                    f" - with paramset_idx: {existing_paramset_idx}"
                )
        else:
            if {"paramset_idx": paramset_idx} in cls.proj():
                raise dj.DataJointError(
                    f"The specified paramset_idx {paramset_idx} already exists,"
                    f" please pick a different one."
                )
            cls.insert1(param_dict)

insert_new_params(segmentation_method, params, paramset_desc='', paramset_idx=None) classmethod

Inserts new parameters into the table.

Parameters:

Name Type Description Default
segmentation_method str

name of the segmentation method (e.g. cellpose)

required
params dict

segmentation parameters

required
paramset_desc str

description of the parameter set

''
paramset_idx int

Unique parameter set ID. Defaults to None.

None
Source code in element_zstack/volume.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
@classmethod
def insert_new_params(
    cls,
    segmentation_method: str,
    params: dict,
    paramset_desc: str = "",
    paramset_idx: int = None,
):
    """Inserts new parameters into the table.

    Args:
        segmentation_method (str): name of the segmentation method (e.g. cellpose)
        params (dict): segmentation parameters
        paramset_desc (str, optional): description of the parameter set
        paramset_idx (int, optional): Unique parameter set ID. Defaults to None.
    """
    if paramset_idx is None:
        paramset_idx = (
            dj.U().aggr(cls, n="max(paramset_idx)").fetch1("n") or 0
        ) + 1

    param_dict = {
        "segmentation_method": segmentation_method,
        "paramset_desc": paramset_desc,
        "params": params,
        "paramset_idx": paramset_idx,
        "paramset_hash": dict_to_uuid(
            {**params, "segmentation_method": segmentation_method}
        ),
    }
    param_query = cls & {"paramset_hash": param_dict["paramset_hash"]}

    if param_query:  # If the specified param-set already exists
        existing_paramset_idx = param_query.fetch1("paramset_idx")
        if (
            existing_paramset_idx == paramset_idx
        ):  # If the existing set has the same paramset_idx: job done
            return
        else:  # If not same name: human error, trying to add the same paramset with different name
            raise dj.DataJointError(
                f"The specified param-set already exists"
                f" - with paramset_idx: {existing_paramset_idx}"
            )
    else:
        if {"paramset_idx": paramset_idx} in cls.proj():
            raise dj.DataJointError(
                f"The specified paramset_idx {paramset_idx} already exists,"
                f" please pick a different one."
            )
        cls.insert1(param_dict)

SegmentationTask

Bases: dj.Manual

Defines the method and parameter set which will be used to segment a volume in the downstream Segmentation table. This table currently supports triggering segmentation with cellpose.

Attributes:

Name Type Description
Volume foreign key

Primary key from Volume.

SegmentationParamSet foreign key

Primary key from

segmentation_output_dir str

Optional. Output directory of the

task_mode enum

Trigger computes segmentation or load imports existing results.

Source code in element_zstack/volume.py
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
@schema
class SegmentationTask(dj.Manual):
    """Defines the method and parameter set which will be used to segment a volume in the downstream `Segmentation` table.  This table currently supports triggering segmentation with `cellpose`.

    Attributes:
        Volume (foreign key): Primary key from `Volume`.
        SegmentationParamSet (foreign key): Primary key from
        `SegmentationParamSet`.
        segmentation_output_dir (str): Optional. Output directory of the
        segmented results relative to the root data directory.
        task_mode (enum): `Trigger` computes segmentation or `load` imports existing results.
    """

    definition = """
    -> Volume
    -> SegmentationParamSet
    ---
    segmentation_output_dir='': varchar(255)  #  Output directory of the segmented results relative to root data directory
    task_mode='load': enum('load', 'trigger')
    """

Segmentation

Bases: dj.Computed

Performs segmentation on the volume (and with the method and parameter set) defined in the SegmentationTask table.

Attributes:

Name Type Description
SegmentationTask foreign key

Primary key from SegmentationTask.

Source code in element_zstack/volume.py
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
@schema
class Segmentation(dj.Computed):
    """Performs segmentation on the volume (and with the method and parameter set) defined in the `SegmentationTask` table.

    Attributes:
        SegmentationTask (foreign key): Primary key from `SegmentationTask`.
    """

    definition = """
    -> SegmentationTask
    """

    class Mask(dj.Part):
        """Details of the masks identified from the segmentation.

        Attributes:
            Segmentation (foreign key): Primary key from `Segmentation`.
            mask (int): Unique mask identifier.
            mask_npix (int): Number of pixels in the mask.
            mask_center_x (float): Center x coordinate in pixels.
            mask_center_y (float): Center y coordinate in pixels.
            mask_center_z (float): Center z coordinate in pixels.
            mask_xpix (longblob): X coordinates in pixels.
            mask_ypix (longblob): Y coordinates in pixels.
            mask_zpix (longblob): Z coordinates in pixels.
            mask_weights (longblob): Weights of the mask at the indices above.
        """

        definition = """ # A mask produced by segmentation.
        -> master
        mask            : smallint
        ---
        mask_npix       : int       # number of pixels in ROIs
        mask_center_x   : float     # X component of the 3D mask centroid in pixel units
        mask_center_y   : float     # Y component of the 3D mask centroid in pixel units
        mask_center_z   : float     # Z component of the 3D mask centroid in pixel units
        mask_xpix       : longblob  # x coordinates in pixels units
        mask_ypix       : longblob  # y coordinates in pixels units
        mask_zpix       : longblob  # z coordinates in pixels units
        mask_weights    : longblob  # weights of the mask at the indices above
        """

    def make(self, key):
        """Populate the Segmentation and Segmentation.Mask tables with results of cellpose segmentation."""

        task_mode, seg_method, output_dir, params = (
            SegmentationTask * SegmentationParamSet & key
        ).fetch1(
            "task_mode", "segmentation_method", "segmentation_output_dir", "params"
        )
        output_dir = find_full_path(get_volume_root_data_dir(), output_dir).as_posix()
        if task_mode == "trigger" and seg_method.lower() == "cellpose":
            from cellpose import models as cellpose_models

            volume_relative_path = (Volume & key).fetch1("volume_file_path")
            volume_file_path = find_full_path(
                get_volume_root_data_dir(), volume_relative_path
            ).as_posix()
            volume_data = TiffFile(volume_file_path).asarray()

            model = cellpose_models.CellposeModel(model_type=params["model_type"])
            cellpose_results = model.eval(
                [volume_data],
                diameter=params["diameter"],
                channels=params.get("channels", [[0, 0]]),
                min_size=params["min_size"],
                z_axis=0,
                do_3D=params["do_3d"],
                anisotropy=params["anisotropy"],
                progress=True,
            )
            masks, flows, styles = cellpose_results

            mask_entries = []
            for mask_id in set(masks[0].flatten()) - {0}:
                mask = np.argwhere(masks[0] == mask_id)
                mask_zpix, mask_ypix, mask_xpix = mask.T
                mask_npix = mask.shape[0]
                mask_center_z, mask_center_y, mask_center_x = mask.mean(axis=0)
                mask_weights = np.full_like(mask_zpix, 1)
                mask_entries.append(
                    {
                        **key,
                        "mask": mask_id,
                        "mask_npix": mask_npix,
                        "mask_center_x": mask_center_x,
                        "mask_center_y": mask_center_y,
                        "mask_center_z": mask_center_z,
                        "mask_xpix": mask_xpix,
                        "mask_ypix": mask_ypix,
                        "mask_zpix": mask_zpix,
                        "mask_weights": mask_weights,
                    }
                )
        else:
            raise NotImplementedError

        self.insert1(key)
        self.Mask.insert(mask_entries)

Mask

Bases: dj.Part

Details of the masks identified from the segmentation.

Attributes:

Name Type Description
Segmentation foreign key

Primary key from Segmentation.

mask int

Unique mask identifier.

mask_npix int

Number of pixels in the mask.

mask_center_x float

Center x coordinate in pixels.

mask_center_y float

Center y coordinate in pixels.

mask_center_z float

Center z coordinate in pixels.

mask_xpix longblob

X coordinates in pixels.

mask_ypix longblob

Y coordinates in pixels.

mask_zpix longblob

Z coordinates in pixels.

mask_weights longblob

Weights of the mask at the indices above.

Source code in element_zstack/volume.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
class Mask(dj.Part):
    """Details of the masks identified from the segmentation.

    Attributes:
        Segmentation (foreign key): Primary key from `Segmentation`.
        mask (int): Unique mask identifier.
        mask_npix (int): Number of pixels in the mask.
        mask_center_x (float): Center x coordinate in pixels.
        mask_center_y (float): Center y coordinate in pixels.
        mask_center_z (float): Center z coordinate in pixels.
        mask_xpix (longblob): X coordinates in pixels.
        mask_ypix (longblob): Y coordinates in pixels.
        mask_zpix (longblob): Z coordinates in pixels.
        mask_weights (longblob): Weights of the mask at the indices above.
    """

    definition = """ # A mask produced by segmentation.
    -> master
    mask            : smallint
    ---
    mask_npix       : int       # number of pixels in ROIs
    mask_center_x   : float     # X component of the 3D mask centroid in pixel units
    mask_center_y   : float     # Y component of the 3D mask centroid in pixel units
    mask_center_z   : float     # Z component of the 3D mask centroid in pixel units
    mask_xpix       : longblob  # x coordinates in pixels units
    mask_ypix       : longblob  # y coordinates in pixels units
    mask_zpix       : longblob  # z coordinates in pixels units
    mask_weights    : longblob  # weights of the mask at the indices above
    """

make(key)

Populate the Segmentation and Segmentation.Mask tables with results of cellpose segmentation.

Source code in element_zstack/volume.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
def make(self, key):
    """Populate the Segmentation and Segmentation.Mask tables with results of cellpose segmentation."""

    task_mode, seg_method, output_dir, params = (
        SegmentationTask * SegmentationParamSet & key
    ).fetch1(
        "task_mode", "segmentation_method", "segmentation_output_dir", "params"
    )
    output_dir = find_full_path(get_volume_root_data_dir(), output_dir).as_posix()
    if task_mode == "trigger" and seg_method.lower() == "cellpose":
        from cellpose import models as cellpose_models

        volume_relative_path = (Volume & key).fetch1("volume_file_path")
        volume_file_path = find_full_path(
            get_volume_root_data_dir(), volume_relative_path
        ).as_posix()
        volume_data = TiffFile(volume_file_path).asarray()

        model = cellpose_models.CellposeModel(model_type=params["model_type"])
        cellpose_results = model.eval(
            [volume_data],
            diameter=params["diameter"],
            channels=params.get("channels", [[0, 0]]),
            min_size=params["min_size"],
            z_axis=0,
            do_3D=params["do_3d"],
            anisotropy=params["anisotropy"],
            progress=True,
        )
        masks, flows, styles = cellpose_results

        mask_entries = []
        for mask_id in set(masks[0].flatten()) - {0}:
            mask = np.argwhere(masks[0] == mask_id)
            mask_zpix, mask_ypix, mask_xpix = mask.T
            mask_npix = mask.shape[0]
            mask_center_z, mask_center_y, mask_center_x = mask.mean(axis=0)
            mask_weights = np.full_like(mask_zpix, 1)
            mask_entries.append(
                {
                    **key,
                    "mask": mask_id,
                    "mask_npix": mask_npix,
                    "mask_center_x": mask_center_x,
                    "mask_center_y": mask_center_y,
                    "mask_center_z": mask_center_z,
                    "mask_xpix": mask_xpix,
                    "mask_ypix": mask_ypix,
                    "mask_zpix": mask_zpix,
                    "mask_weights": mask_weights,
                }
            )
    else:
        raise NotImplementedError

    self.insert1(key)
    self.Mask.insert(mask_entries)