ifsegment 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ifsegment-0.0.1/PKG-INFO +14 -0
- ifsegment-0.0.1/pyproject.toml +28 -0
- ifsegment-0.0.1/setup.cfg +4 -0
- ifsegment-0.0.1/src/ifsegment/__init__.py +0 -0
- ifsegment-0.0.1/src/ifsegment/cli.py +33 -0
- ifsegment-0.0.1/src/ifsegment/modules/cyto_segment.py +66 -0
- ifsegment-0.0.1/src/ifsegment/modules/io_utils.py +69 -0
- ifsegment-0.0.1/src/ifsegment/modules/normalizations.py +48 -0
- ifsegment-0.0.1/src/ifsegment/modules/nucleus_segment.py +163 -0
- ifsegment-0.0.1/src/ifsegment/modules/protein_quantification.py +89 -0
- ifsegment-0.0.1/src/ifsegment/run_cyto_mask.py +4 -0
- ifsegment-0.0.1/src/ifsegment/run_mask.py +4 -0
- ifsegment-0.0.1/src/ifsegment/run_quant.py +6 -0
- ifsegment-0.0.1/src/ifsegment.egg-info/PKG-INFO +14 -0
- ifsegment-0.0.1/src/ifsegment.egg-info/SOURCES.txt +18 -0
- ifsegment-0.0.1/src/ifsegment.egg-info/dependency_links.txt +1 -0
- ifsegment-0.0.1/src/ifsegment.egg-info/entry_points.txt +2 -0
- ifsegment-0.0.1/src/ifsegment.egg-info/requires.txt +5 -0
- ifsegment-0.0.1/src/ifsegment.egg-info/top_level.txt +1 -0
- ifsegment-0.0.1/tests/test_file_io.py +20 -0
ifsegment-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ifsegment
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Cell segmentation and protein quantification from immunofluorescence images
|
|
5
|
+
Author: Chris Viets
|
|
6
|
+
Project-URL: Homepage, https://github.com/cviets/ifsegment
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/cviets/ifsegment/issues
|
|
8
|
+
Requires-Python: >=3.6
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: aicspylibczi>=3.1.1
|
|
12
|
+
Requires-Dist: tqdm
|
|
13
|
+
Requires-Dist: scikit-image
|
|
14
|
+
Requires-Dist: scipy
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ifsegment"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Cell segmentation and protein quantification from immunofluorescence images"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.6"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Chris Viets" }
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"numpy",
|
|
16
|
+
"aicspylibczi>=3.1.1",
|
|
17
|
+
# "aicsimageio @ git+https://github.com/cviets/aicsimageio@main",
|
|
18
|
+
"tqdm",
|
|
19
|
+
"scikit-image",
|
|
20
|
+
"scipy"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
"Homepage" = "https://github.com/cviets/ifsegment"
|
|
25
|
+
"Bug Tracker" = "https://github.com/cviets/ifsegment/issues"
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
ifsegment = "ifsegment.cli:main"
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from .run_cyto_mask import main as cyto_main
|
|
3
|
+
from .run_mask import main as mask_main
|
|
4
|
+
from .run_quant import main as quant_main
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
parser = argparse.ArgumentParser(
|
|
8
|
+
prog="ifsegment",
|
|
9
|
+
description="Cell segmentation from IF images",
|
|
10
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
|
11
|
+
)
|
|
12
|
+
subparsers = parser.add_subparsers(dest="command", required=False)
|
|
13
|
+
|
|
14
|
+
mask_parser = subparsers.add_parser("mask", help="Generate nuclear and cytoplasmic masks", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
15
|
+
mask_parser.add_argument("-i", "--input", type=str, required=True, help="Path to directory with .czi images")
|
|
16
|
+
mask_parser.add_argument("-o", "--output", type=str, required=True, help="Path to directory to save masks")
|
|
17
|
+
mask_parser.add_argument("-n", "--nuclear", type=int, default=0, help="Nuclear channel number (0-indexed)")
|
|
18
|
+
mask_parser.add_argument("-c", "--cytoplasmic", type=int, default=3, help="Cytoplasmic channel number (0-indexed)")
|
|
19
|
+
mask_parser.add_argument("-m", "--mode", type=str, default="max", help="z-projection type (choose from 'max' or 'mean')")
|
|
20
|
+
|
|
21
|
+
quant_parser = subparsers.add_parser("quantify", help="Quantify protein fluorescence in nucleus and cytoplasm", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
22
|
+
quant_parser.add_argument("-i", "--input", type=str, required=True, help="Path to CZI images")
|
|
23
|
+
quant_parser.add_argument("-m", "--masks", type=str, required=True, help="Path to mask tiff files")
|
|
24
|
+
quant_parser.add_argument("-o", "--output", type=str, required=True, help="Path to output CSV file to store data")
|
|
25
|
+
quant_parser.add_argument("-c", "--channels", type=int, nargs="+", default=[1, 2], help="Channel numbers to quantify (0-indexed)")
|
|
26
|
+
quant_parser.add_argument("-md", "--mode", type=str, default="max", help="z-projection type (choose from 'max' or 'mean')")
|
|
27
|
+
|
|
28
|
+
args = parser.parse_args()
|
|
29
|
+
|
|
30
|
+
if args.command == "mask":
|
|
31
|
+
mask_main(args.input, args.output, args.nuclear, args.cytoplasmic, args.mode)
|
|
32
|
+
elif args.command == "quantify":
|
|
33
|
+
quant_main(args.input, args.masks, args.output, args.channels, args.mode)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from .io_utils import read_czi, get_czi_in_folder, write_tiff, get_well_from_file
|
|
3
|
+
from .normalizations import minmax_percentile, clip_512, zstack
|
|
4
|
+
from aicsimageio import AICSImage
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
from skimage.morphology import remove_small_objects, remove_small_holes, diamond
|
|
7
|
+
from skimage.measure import label
|
|
8
|
+
from scipy.ndimage import binary_closing, binary_dilation, binary_erosion
|
|
9
|
+
|
|
10
|
+
def cyto_segment_array(image: np.ndarray[np.float64]) -> np.ndarray[np.bool_]:
|
|
11
|
+
"""
|
|
12
|
+
Create cytoplasmic segmentation of input 2D image
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
-----------
|
|
16
|
+
image: np.ndarray[np.float64]
|
|
17
|
+
Input image as numpy array
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-----------
|
|
21
|
+
mask : np.ndarray[np.bool_]
|
|
22
|
+
Mask of image
|
|
23
|
+
"""
|
|
24
|
+
assert image.ndim == 2
|
|
25
|
+
|
|
26
|
+
# we are interested in the non-one values
|
|
27
|
+
# (want to ignore super outlier-y bright spots)
|
|
28
|
+
mu = np.mean(image[image < 1])
|
|
29
|
+
std = np.std(image[image < 1])
|
|
30
|
+
|
|
31
|
+
# hard coded: mask according to mean +/- 5 std for non-bright spots
|
|
32
|
+
thresh = min(0.75, mu + 5*std)
|
|
33
|
+
mask = image > thresh
|
|
34
|
+
|
|
35
|
+
diamond_strel = diamond(1)
|
|
36
|
+
mask = binary_dilation(mask, diamond_strel, iterations=3)
|
|
37
|
+
mask = remove_small_holes(mask, 1200, connectivity=2)
|
|
38
|
+
mask = binary_erosion(mask, diamond_strel, iterations=3)
|
|
39
|
+
|
|
40
|
+
mask = remove_small_objects(mask, min_size=600, connectivity=2)
|
|
41
|
+
|
|
42
|
+
return mask
|
|
43
|
+
|
|
44
|
+
def czi_preprocess(czi_image: AICSImage, cyto_channel: int, mode:str="max") -> np.ndarray[np.float64]:
|
|
45
|
+
|
|
46
|
+
img_dask = czi_image.get_image_dask_data("ZYX", T=0, C=cyto_channel)
|
|
47
|
+
img_numpy = img_dask.compute()
|
|
48
|
+
img_numpy = zstack(img_numpy, 0, mode)
|
|
49
|
+
|
|
50
|
+
# HARD CODED: normalize 0 percentile to -1 and 95 percentile to 1
|
|
51
|
+
img_numpy = clip_512(img_numpy)
|
|
52
|
+
img_numpy = minmax_percentile(img_numpy, 0, 95)
|
|
53
|
+
return img_numpy
|
|
54
|
+
|
|
55
|
+
def cyto_segment_czi(czi_image: AICSImage, cyto_channel: int, mode:str="max") -> np.ndarray[np.bool_]:
|
|
56
|
+
preprocessed = czi_preprocess(czi_image, cyto_channel, mode)
|
|
57
|
+
return cyto_segment_array(preprocessed)
|
|
58
|
+
|
|
59
|
+
def cyto_segment_folder(path_to_folder: str, output_folder:str, cyto_channel: int, mode:str="max") -> None:
|
|
60
|
+
czi_paths = get_czi_in_folder(path_to_folder)
|
|
61
|
+
for czi_path in tqdm(czi_paths, desc="Cytoplasm masks"):
|
|
62
|
+
img = read_czi(czi_path)
|
|
63
|
+
well_name = get_well_from_file(czi_path)
|
|
64
|
+
mask = cyto_segment_czi(img, cyto_channel, mode)
|
|
65
|
+
|
|
66
|
+
write_tiff(mask, output_folder, well_name)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from aicsimageio import AICSImage
|
|
2
|
+
import os
|
|
3
|
+
from glob import glob
|
|
4
|
+
import re
|
|
5
|
+
import tifffile
|
|
6
|
+
import numpy as np
|
|
7
|
+
from typing import List, Union
|
|
8
|
+
import csv
|
|
9
|
+
|
|
10
|
+
def read_czi(path_to_file: str) -> AICSImage:
|
|
11
|
+
return AICSImage(path_to_file)
|
|
12
|
+
|
|
13
|
+
def read_tiff(path_to_file: str) -> Union[np.ndarray[np.float64], np.ndarray[np.bool_]]:
|
|
14
|
+
return tifffile.imread(path_to_file)
|
|
15
|
+
|
|
16
|
+
def get_czi_in_folder(path_to_folder: str) -> List[str]:
|
|
17
|
+
"""
|
|
18
|
+
Given a folder path, returns all CZI files in that folder
|
|
19
|
+
"""
|
|
20
|
+
path_to_folder = os.path.expanduser(path_to_folder)
|
|
21
|
+
return glob(os.path.join(path_to_folder, "*.czi"))
|
|
22
|
+
|
|
23
|
+
def get_well_from_file(filename: str) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Given an input file name (auto-generated by CZI software), returns well name
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
filename : str
|
|
30
|
+
Path to input czi file
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
---------
|
|
34
|
+
well : str
|
|
35
|
+
Name of well specified in file name
|
|
36
|
+
"""
|
|
37
|
+
pattern = r"\.czi"
|
|
38
|
+
match = re.search(pattern, filename)
|
|
39
|
+
idx = match.start() - 3
|
|
40
|
+
return filename[idx:match.start()]
|
|
41
|
+
|
|
42
|
+
def write_tiff(data: np.ndarray, output_dir: str, file_name: str) -> None:
|
|
43
|
+
output_dir = os.path.expanduser(output_dir)
|
|
44
|
+
if not os.path.isdir(output_dir):
|
|
45
|
+
os.mkdir(output_dir)
|
|
46
|
+
full_file = os.path.join(output_dir, file_name+".tiff")
|
|
47
|
+
tifffile.imwrite(full_file, data)
|
|
48
|
+
|
|
49
|
+
def get_mask_path(path_to_masks, well_name):
|
|
50
|
+
masks = glob(os.path.join(path_to_masks, "*.tiff"))
|
|
51
|
+
for mask in masks:
|
|
52
|
+
head, tail = os.path.split(mask)
|
|
53
|
+
if well_name in tail:
|
|
54
|
+
return mask
|
|
55
|
+
|
|
56
|
+
raise FileNotFoundError(f"No mask for well {well_name} found")
|
|
57
|
+
|
|
58
|
+
def write_to_csv(data, path_to_csv):
|
|
59
|
+
base, ext = os.path.splitext(path_to_csv)
|
|
60
|
+
if ext != ".csv":
|
|
61
|
+
if ext == "" and os.path.isdir(base):
|
|
62
|
+
path_to_csv = os.path.join(path_to_csv, "ifsegment_data.csv")
|
|
63
|
+
else:
|
|
64
|
+
path_to_csv = base + ".csv"
|
|
65
|
+
|
|
66
|
+
with open(path_to_csv, newline='', mode='w') as csvfile:
|
|
67
|
+
writer = csv.writer(csvfile)
|
|
68
|
+
for row in data:
|
|
69
|
+
writer.writerow([str(elt) for elt in row])
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
def minmax(inp):
|
|
4
|
+
|
|
5
|
+
if inp.ndim > 2:
|
|
6
|
+
return np.array([minmax(elt) for elt in inp])
|
|
7
|
+
|
|
8
|
+
min_new = -1
|
|
9
|
+
max_new = 1
|
|
10
|
+
|
|
11
|
+
original_min = np.min(inp)
|
|
12
|
+
original_max = np.max(inp)
|
|
13
|
+
original_range = original_max - original_min
|
|
14
|
+
|
|
15
|
+
new_range = max_new - min_new
|
|
16
|
+
|
|
17
|
+
if original_range == 0:
|
|
18
|
+
return inp
|
|
19
|
+
|
|
20
|
+
return ((inp - original_min) / original_range) * new_range + min_new
|
|
21
|
+
|
|
22
|
+
def minmax_percentile(inp, pmin, pmax):
|
|
23
|
+
if inp.ndim > 2:
|
|
24
|
+
return np.array([minmax_percentile(elt, pmin, pmax) for elt in inp])
|
|
25
|
+
|
|
26
|
+
min_val = np.percentile(inp, pmin)
|
|
27
|
+
max_val = np.percentile(inp, pmax)
|
|
28
|
+
clipped = np.clip(inp, min_val, max_val)
|
|
29
|
+
return minmax(clipped)
|
|
30
|
+
|
|
31
|
+
def clip_512(inp):
|
|
32
|
+
if inp.ndim > 2:
|
|
33
|
+
return np.array([clip_512(elt) for elt in inp])
|
|
34
|
+
min_val = 512
|
|
35
|
+
max_val = np.max(inp)
|
|
36
|
+
return np.clip(inp, min_val, max_val)
|
|
37
|
+
|
|
38
|
+
def zstack(image, axis, mode):
|
|
39
|
+
mode = mode.lower()
|
|
40
|
+
assert mode in {"max", "average", "avg", "mean"}
|
|
41
|
+
|
|
42
|
+
# take z-stack
|
|
43
|
+
if mode == "max":
|
|
44
|
+
out = np.max(image, axis=axis)
|
|
45
|
+
else:
|
|
46
|
+
out = np.mean(image, axis=axis)
|
|
47
|
+
|
|
48
|
+
return out
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from .cyto_segment import cyto_segment_czi
|
|
2
|
+
from aicsimageio import AICSImage
|
|
3
|
+
import numpy as np
|
|
4
|
+
from .normalizations import minmax_percentile, clip_512, zstack
|
|
5
|
+
from .io_utils import get_czi_in_folder, read_czi, get_well_from_file, write_tiff
|
|
6
|
+
from tqdm import tqdm
|
|
7
|
+
from skimage.measure import label, regionprops
|
|
8
|
+
from skimage.morphology import remove_small_objects, remove_small_holes, diamond
|
|
9
|
+
from skimage.segmentation import find_boundaries
|
|
10
|
+
from typing import Tuple
|
|
11
|
+
import copy
|
|
12
|
+
from scipy.ndimage import distance_transform_edt
|
|
13
|
+
from scipy.ndimage import binary_closing
|
|
14
|
+
|
|
15
|
+
def nucleus_preprocess(czi_image: AICSImage, nuclear_channel: int, cyto_channel: int, mode:str) -> np.ndarray[np.bool_]:
|
|
16
|
+
"""
|
|
17
|
+
Generates nuclear mask from input AICS Image (from read_czi)
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
-----------
|
|
21
|
+
czi_image : AICSImage
|
|
22
|
+
AICSImage object containing CZI image information
|
|
23
|
+
nuclear_channel : int
|
|
24
|
+
Nuclear marker channel number (0-indexed) (eg, Hoechst)
|
|
25
|
+
cyto_channel : int
|
|
26
|
+
Cytoplasmic marker channel number (0-indexed), used to mask nuclei with associated cytoplasm
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
---------
|
|
30
|
+
masks : np.ndarray[np.bool_]
|
|
31
|
+
Segmentation array containing cytoplasmic and nuclear masks
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
cyto_mask = cyto_segment_czi(czi_image, cyto_channel, mode)
|
|
35
|
+
|
|
36
|
+
img_dask = czi_image.get_image_dask_data("ZYX", T=0, C=nuclear_channel)
|
|
37
|
+
img_numpy = img_dask.compute()
|
|
38
|
+
img_numpy = zstack(img_numpy, axis=0, mode=mode)
|
|
39
|
+
|
|
40
|
+
# HARD CODED: normalize 0 percentile to -1 and 98 percentile to 1
|
|
41
|
+
img_numpy = clip_512(img_numpy)
|
|
42
|
+
img_numpy = minmax_percentile(img_numpy, 0, 98)
|
|
43
|
+
|
|
44
|
+
return img_numpy, cyto_mask
|
|
45
|
+
|
|
46
|
+
def nuc_segment_array(
|
|
47
|
+
image: np.ndarray[np.float64],
|
|
48
|
+
cyto_mask: np.ndarray[np.bool_]
|
|
49
|
+
) -> Tuple[np.ndarray[np.bool_], int]:
|
|
50
|
+
"""
|
|
51
|
+
Create nuclear segmentation of input 2D image
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
-----------
|
|
55
|
+
image: np.ndarray[np.float64]
|
|
56
|
+
Input image as numpy array
|
|
57
|
+
cyto_mask: np.ndarray[np.bool_]
|
|
58
|
+
Cytoplasm mask (used to determine valid nuclei)
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-----------
|
|
62
|
+
mask_out : np.ndarray[np.bool_]
|
|
63
|
+
Mask of image
|
|
64
|
+
num_cells: int
|
|
65
|
+
Number of nuclei counted
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
assert image.ndim == 2
|
|
69
|
+
|
|
70
|
+
# we are interested in the non-one values
|
|
71
|
+
# (want to ignore super outlier-y bright spots)
|
|
72
|
+
mu = np.mean(image[image < 1])
|
|
73
|
+
std = np.std(image[image < 1])
|
|
74
|
+
|
|
75
|
+
# hard coded: mask according to mean +/- 5 std for non-bright spots
|
|
76
|
+
thresh = min(0, mu + 5*std)
|
|
77
|
+
mask = image > thresh
|
|
78
|
+
|
|
79
|
+
mask = remove_small_objects(mask, min_size=300, connectivity=2)
|
|
80
|
+
# mask = remove_small_holes(mask, 1200, connectivity=2)
|
|
81
|
+
|
|
82
|
+
# remove nuclei not touching cell cytoplasm (usually means no neurites)
|
|
83
|
+
cyto_dt = distance_transform_edt(~cyto_mask)
|
|
84
|
+
mask_out = np.zeros_like(mask, dtype=np.bool_)
|
|
85
|
+
|
|
86
|
+
labeled_nuclei = label(mask)
|
|
87
|
+
regions = regionprops(labeled_nuclei)
|
|
88
|
+
num_cells = 0
|
|
89
|
+
for region in tqdm(regions, desc="Validating nuclei"):
|
|
90
|
+
|
|
91
|
+
cur_nucleus = region.image
|
|
92
|
+
boundary = find_boundaries(cur_nucleus, mode="outer")
|
|
93
|
+
temp_dt = cyto_dt[region.slice]
|
|
94
|
+
_temp_dt = copy.deepcopy(temp_dt)
|
|
95
|
+
temp_dt[~boundary] = -1
|
|
96
|
+
distances = temp_dt[temp_dt != -1]
|
|
97
|
+
|
|
98
|
+
# majority rules
|
|
99
|
+
if len(distances[distances==0]) >= 0.5 * len(distances):
|
|
100
|
+
num_cells += 1
|
|
101
|
+
mask_out[region.slice] = cur_nucleus
|
|
102
|
+
|
|
103
|
+
cyto_dt[region.slice] = _temp_dt
|
|
104
|
+
|
|
105
|
+
mask_out = binary_closing(mask_out, diamond(1), iterations=1)
|
|
106
|
+
mask_out = remove_small_holes(mask_out, 100, connectivity=2)
|
|
107
|
+
return mask_out, num_cells
|
|
108
|
+
|
|
109
|
+
def remove_unconnected_cyto(cyto_mask:np.ndarray[np.bool_], nuc_mask: np.ndarray[np.bool_]) -> np.ndarray[np.bool_]:
|
|
110
|
+
"""
|
|
111
|
+
Remove cytoplasmic objects not touching or overlapping nuclear objects
|
|
112
|
+
"""
|
|
113
|
+
mask_out = np.zeros_like(cyto_mask)
|
|
114
|
+
labeled_cyto = label(cyto_mask)
|
|
115
|
+
regions = regionprops(labeled_cyto)
|
|
116
|
+
nuc_dt = distance_transform_edt(~nuc_mask)
|
|
117
|
+
for region in tqdm(regions, desc="Validating cytoplasm"):
|
|
118
|
+
|
|
119
|
+
cur_cyto = region.image
|
|
120
|
+
temp_dt = nuc_dt[region.slice]
|
|
121
|
+
_temp_dt = copy.deepcopy(temp_dt)
|
|
122
|
+
temp_dt[~cur_cyto] = -1
|
|
123
|
+
distances = temp_dt[temp_dt != -1]
|
|
124
|
+
|
|
125
|
+
if min(distances) <= 1:
|
|
126
|
+
mask_out[region.slice] = cur_cyto
|
|
127
|
+
|
|
128
|
+
nuc_dt[region.slice] = _temp_dt
|
|
129
|
+
|
|
130
|
+
return mask_out
|
|
131
|
+
|
|
132
|
+
def fill_holes_trinary(trinary_mask):
|
|
133
|
+
binary_mask = trinary_mask > 0
|
|
134
|
+
binary_mask_filled = remove_small_holes(binary_mask, 200)
|
|
135
|
+
holes = np.logical_and(binary_mask_filled, ~binary_mask)
|
|
136
|
+
mask_out = trinary_mask
|
|
137
|
+
mask_out[holes] = 2
|
|
138
|
+
return mask_out
|
|
139
|
+
|
|
140
|
+
def segment_folder(path_to_folder: str, output_folder: str, nuclear_channel: int, cyto_channel: int, mode:str) -> None:
|
|
141
|
+
czi_paths = get_czi_in_folder(path_to_folder)
|
|
142
|
+
cell_counts = [None]*len(czi_paths)
|
|
143
|
+
for i, czi_path in enumerate(tqdm(czi_paths, desc="Masking cells")):
|
|
144
|
+
img = read_czi(czi_path)
|
|
145
|
+
well_name = get_well_from_file(czi_path)
|
|
146
|
+
preprocessed_img, cyto_mask = nucleus_preprocess(img, nuclear_channel, cyto_channel, mode)
|
|
147
|
+
write_tiff(preprocessed_img, output_folder, well_name + "_PREPROCESSED_NUC")
|
|
148
|
+
nuc_mask, num_cells = nuc_segment_array(preprocessed_img, cyto_mask)
|
|
149
|
+
cyto_mask = remove_unconnected_cyto(cyto_mask, nuc_mask)
|
|
150
|
+
mask = np.zeros_like(nuc_mask, dtype=np.float64)
|
|
151
|
+
|
|
152
|
+
# trinary image: nuc = 1, cyto = 2
|
|
153
|
+
mask[cyto_mask] = 2
|
|
154
|
+
# nuc_mask should override cyto_mask
|
|
155
|
+
mask[nuc_mask] = 1
|
|
156
|
+
|
|
157
|
+
mask = fill_holes_trinary(mask)
|
|
158
|
+
|
|
159
|
+
write_tiff(mask, output_folder, well_name)
|
|
160
|
+
cell_counts[i] = num_cells
|
|
161
|
+
|
|
162
|
+
return cell_counts
|
|
163
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from typing import Tuple, List
|
|
3
|
+
from .io_utils import get_czi_in_folder, get_well_from_file, get_mask_path, read_czi, read_tiff, write_to_csv
|
|
4
|
+
from .normalizations import zstack
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
|
|
7
|
+
def quantify_channels(image: np.ndarray[np.float64], mask: np.ndarray[np.float64]) -> Tuple[np.ndarray[np.bool_]]:
|
|
8
|
+
"""
|
|
9
|
+
Quantify nuclear and cytoplasmic protein fluorescence
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
-----------
|
|
13
|
+
image : np.ndarray[np.float64]
|
|
14
|
+
Input image as numpy array (CYX with C containing channels of interest)
|
|
15
|
+
mask : np.ndarray[np.float64]
|
|
16
|
+
Trinary mask (2D array, YX, 0 = background, 1 = nucleus, 2 = cytoplasm)
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-----------
|
|
20
|
+
fluor : np.ndarray[np.float64]
|
|
21
|
+
C-by-3 array containing [total, nuclear, cytoplasmic] fluorescence for each protein
|
|
22
|
+
"""
|
|
23
|
+
# NOTE: it is important not to modify the pixel values in the image itself since we don't want to mess with the data
|
|
24
|
+
nuc_mask = mask==1
|
|
25
|
+
cyto_mask = mask==2
|
|
26
|
+
|
|
27
|
+
if image.ndim == 2:
|
|
28
|
+
image = np.expand_dims(image, 0)
|
|
29
|
+
|
|
30
|
+
fluor = np.zeros(shape=(len(image), 3))
|
|
31
|
+
|
|
32
|
+
# compute mean intensities for each channel
|
|
33
|
+
for i, channel in enumerate(image):
|
|
34
|
+
fluor[i, 0] = np.mean(channel[np.logical_or(nuc_mask, cyto_mask)])
|
|
35
|
+
fluor[i, 1] = np.mean(channel[nuc_mask])
|
|
36
|
+
fluor[i, 2] = np.mean(channel[cyto_mask])
|
|
37
|
+
|
|
38
|
+
return fluor
|
|
39
|
+
|
|
40
|
+
def quantify_folder(path_to_images: str, path_to_masks: str, path_to_save: str, channels: List[int], mode:str):
|
|
41
|
+
"""
|
|
42
|
+
Parameters
|
|
43
|
+
-----------
|
|
44
|
+
path_to_images : str
|
|
45
|
+
Path to czi images
|
|
46
|
+
path_to_masks : str
|
|
47
|
+
Path to tiff trinary masks
|
|
48
|
+
path_to_save : str
|
|
49
|
+
Path to save data (CSV file)
|
|
50
|
+
channels : List[int]
|
|
51
|
+
List of channel indices (0-indexed) to quantify fluorescence
|
|
52
|
+
mode : str
|
|
53
|
+
Mode for z-projecting (max or mean)
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-----------
|
|
57
|
+
None
|
|
58
|
+
"""
|
|
59
|
+
image_paths = get_czi_in_folder(path_to_images)
|
|
60
|
+
|
|
61
|
+
# for each channel, we measure total, nuclear, cytoplasmic, and N/C ratio
|
|
62
|
+
data = np.zeros(shape=(len(image_paths), 1+4*len(channels)),dtype=object)
|
|
63
|
+
|
|
64
|
+
for idx, image_path in enumerate(tqdm(image_paths, desc="Measuring all images")):
|
|
65
|
+
well_name = get_well_from_file(image_path)
|
|
66
|
+
mask_path = get_mask_path(path_to_masks, well_name)
|
|
67
|
+
image_czi = read_czi(image_path)
|
|
68
|
+
image_dask = image_czi.get_image_dask_data("CZYX", C=channels)
|
|
69
|
+
image_numpy = image_dask.compute()
|
|
70
|
+
image = zstack(image_numpy, 1, mode)
|
|
71
|
+
mask = read_tiff(mask_path)
|
|
72
|
+
data[idx, 0] = well_name
|
|
73
|
+
|
|
74
|
+
fluor = quantify_channels(image, mask)
|
|
75
|
+
for idx_j in range(len(channels)):
|
|
76
|
+
total = fluor[idx_j, 0]
|
|
77
|
+
nuclear = fluor[idx_j, 1]
|
|
78
|
+
cytoplasmic = fluor[idx_j, 2]
|
|
79
|
+
data[idx, 4*idx_j+1] = total
|
|
80
|
+
data[idx, 4*idx_j+2] = nuclear
|
|
81
|
+
data[idx, 4*idx_j+3] = cytoplasmic
|
|
82
|
+
data[idx, 4*idx_j+4] = nuclear/cytoplasmic
|
|
83
|
+
|
|
84
|
+
header = ["Well"]
|
|
85
|
+
for channel in channels:
|
|
86
|
+
channel_string = "Ch"+str(channel)
|
|
87
|
+
header += [channel_string+"_TOT", channel_string+"_N", channel_string+"_C", channel_string+"_N/C"]
|
|
88
|
+
data = np.vstack((header, data))
|
|
89
|
+
write_to_csv(data, path_to_save)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ifsegment
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Cell segmentation and protein quantification from immunofluorescence images
|
|
5
|
+
Author: Chris Viets
|
|
6
|
+
Project-URL: Homepage, https://github.com/cviets/ifsegment
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/cviets/ifsegment/issues
|
|
8
|
+
Requires-Python: >=3.6
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: aicspylibczi>=3.1.1
|
|
12
|
+
Requires-Dist: tqdm
|
|
13
|
+
Requires-Dist: scikit-image
|
|
14
|
+
Requires-Dist: scipy
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
src/ifsegment/__init__.py
|
|
3
|
+
src/ifsegment/cli.py
|
|
4
|
+
src/ifsegment/run_cyto_mask.py
|
|
5
|
+
src/ifsegment/run_mask.py
|
|
6
|
+
src/ifsegment/run_quant.py
|
|
7
|
+
src/ifsegment.egg-info/PKG-INFO
|
|
8
|
+
src/ifsegment.egg-info/SOURCES.txt
|
|
9
|
+
src/ifsegment.egg-info/dependency_links.txt
|
|
10
|
+
src/ifsegment.egg-info/entry_points.txt
|
|
11
|
+
src/ifsegment.egg-info/requires.txt
|
|
12
|
+
src/ifsegment.egg-info/top_level.txt
|
|
13
|
+
src/ifsegment/modules/cyto_segment.py
|
|
14
|
+
src/ifsegment/modules/io_utils.py
|
|
15
|
+
src/ifsegment/modules/normalizations.py
|
|
16
|
+
src/ifsegment/modules/nucleus_segment.py
|
|
17
|
+
src/ifsegment/modules/protein_quantification.py
|
|
18
|
+
tests/test_file_io.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ifsegment
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from src.ifsegment.modules.io_utils import read_czi, get_well_from_file, get_czi_in_folder
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
def test_read_czi(inp, save_to):
|
|
5
|
+
img = read_czi(inp)
|
|
6
|
+
print(type(img), img.shape)
|
|
7
|
+
np.save(save_to, img)
|
|
8
|
+
|
|
9
|
+
def test_well_from_file(filename):
|
|
10
|
+
print(get_well_from_file(filename))
|
|
11
|
+
|
|
12
|
+
def test_get_czi_from_folder(path_to_czi_files):
|
|
13
|
+
return get_czi_in_folder(path_to_czi_files)
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
path = "/media/cviets/Chris2/Exp008J-01"
|
|
17
|
+
return test_get_czi_from_folder(path)
|
|
18
|
+
|
|
19
|
+
if __name__ == '__main__':
|
|
20
|
+
print(main())
|