Skip to content

Commit d5c6819

Browse files
committed
Cleaner code and improved documentation
1 parent e3f65c4 commit d5c6819

20 files changed

Lines changed: 268 additions & 122 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ Key features
99

1010
- **Templates with different levels of features and complexity**
1111
- Several templates ready to use: base, advanced and retro-compatible.
12-
- Multiple configuration possibilities: basic constants, command line arguments or YAML configuration files.
12+
- Multiple configuration possibilities: basic constants, command line arguments or YAML configuration files (all optional).
1313
- Simple logging mechanism.
1414
- File iteration embedded functionality.
15-
- Template pipeline coordination (advanced).
15+
- Multi-CPU parallelisation with progress bar (advanced).
16+
- Thread-safe compiled CSV result with automatic header (advanced).
1617
- **Examples based on these templates with real functionality**
1718
- Readily available code to perform several requested tasks by the lab members.
1819
- Simple installation and usage.

examples/cell_cropper/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The code expects you to perform certain steps to run with your data:
3232

3333
To run HPA cell cropper you have to gather the information about the sets of images you want to process. HPA cell cropper reads `path_list.csv` to locate each set of images, in the following .csv format:
3434

35-
`r_image,y_image,b_image,g_image,segmentation_mask,crop_folder,output_prefix`
35+
`r_image,y_image,b_image,g_image,cell_mask,nuclei_mask,crop_folder,output_prefix`
3636

3737
- `r_image`: the microtubules targeting marker FOV image.
3838
- `y_image`: the ER targeting marker FOV image.
@@ -71,6 +71,8 @@ Output
7171
------
7272

7373
Cell cropper generates in the chosen crop_folder the following files:
74-
- `[output_prefix]_cell[X]_crop_[red|yellow|blue|green].png`: a cropped cell from the FOV.
75-
- `[output_prefix]_cell[X]_crop_masked_[red|yellow|blue|green].png`: a cropped and masked cell from the FOV (if the mask_cell option was selected).
76-
- Additionally, a `crop_info.csv` file will be created containing all generated cell crops bboxes for convenience.
74+
- `[output_prefix]cell[X]_crop_[red|yellow|blue|green].png`: a cropped cell from the FOV.
75+
- `[output_prefix]cell[X]_crop_masked_[red|yellow|blue|green].png`: a cropped and masked cell from the FOV (if the `mask_cell` option was selected).
76+
- `[output_prefix]cell[X]_cellmask.png`: the binary cell mask for that crop (if the `crop_mask` option was selected).
77+
- `[output_prefix]cell[X]_nucleimask.png`: the binary nuclei mask for that crop (if the `crop_mask` option was selected and a nuclei mask was provided).
78+
- Additionally, a `crop_info.csv` file will be created in the base folder containing all generated cell crops bounding boxes for convenience.

examples/cell_cropper/cell_cropper.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import warnings
22
import numpy as np
3-
from imageio.v2 import imsave
43
from skimage.measure import regionprops
54
from scipy.ndimage.morphology import grey_dilation
65
import pandas as pd
76
import image_utils
7+
import cv2
88

99

1010
warnings.simplefilter(action="ignore", category=FutureWarning)
@@ -83,28 +83,28 @@ def generate_crops(image_stack, cell_mask, nuclei_mask, crop_size, crop_bitdepth
8383
this_cell_mask[this_cell_mask == region.label] = 1
8484
this_cell_mask = grey_dilation(this_cell_mask, size=7)
8585
curr_cell_mask, _ = safe_crop(this_cell_mask, fixed_bbox)
86-
imsave(f"{output_folder}/{output_prefix}cell{region.label}_cellmask.png", np.uint8(curr_cell_mask * 255))
86+
cv2.imwrite(f"{output_folder}/{output_prefix}cell{region.label}_cellmask.png", np.uint8(curr_cell_mask * 255))
8787
if nuclei_mask is not None:
8888
this_nuclei_mask = nuclei_mask.copy()
8989
this_nuclei_mask[this_nuclei_mask != region.label] = 0
9090
this_nuclei_mask[this_nuclei_mask == region.label] = 1
9191
this_nuclei_mask = grey_dilation(this_nuclei_mask, size=7)
9292
curr_nuclei_mask, _ = safe_crop(this_nuclei_mask, fixed_bbox)
93-
imsave(f"{output_folder}/{output_prefix}cell{region.label}_nucleimask.png", np.uint8(curr_nuclei_mask * 255))
93+
cv2.imwrite(f"{output_folder}/{output_prefix}cell{region.label}_nucleimask.png", np.uint8(curr_nuclei_mask * 255))
9494

9595
for curr_img_index in range(len(image_stack)):
9696
if curr_img_index != 0:
9797
image_cp = image_stack[curr_img_index][0].copy()
9898

9999
cell_crop, _ = safe_crop(image_cp, fixed_bbox)
100-
imsave(f"{output_folder}/{output_prefix}cell{region.label}_crop_" + colors[curr_img_index] + ".png", image_utils.convert_bitdepth(cell_crop, crop_bitdepth))
100+
cv2.imwrite(f"{output_folder}/{output_prefix}cell{region.label}_crop_" + colors[curr_img_index] + ".png", image_utils.convert_bitdepth(cell_crop, crop_bitdepth))
101101

102102
if mask_cell:
103103
this_cell_mask = cell_mask == region.label
104104
this_cell_mask = grey_dilation(this_cell_mask, size=7)
105105
image_cp[this_cell_mask == 0] = 0
106106
cell_mask_crop, _ = safe_crop(image_cp, fixed_bbox)
107-
imsave(f"{output_folder}/{output_prefix}cell{region.label}_crop_masked_" + colors[curr_img_index] + ".png", image_utils.convert_bitdepth(cell_mask_crop, crop_bitdepth))
107+
cv2.imwrite(f"{output_folder}/{output_prefix}cell{region.label}_crop_masked_" + colors[curr_img_index] + ".png", image_utils.convert_bitdepth(cell_mask_crop, crop_bitdepth))
108108

109109
new_center = (crop_size // 2, crop_size // 2)
110110
new_bbox = (

examples/cell_cropper/process.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
config = { "log": logger}
1818

1919
# If you want to use constants with your script, add them here
20-
config["crop_size"] = 1024
21-
config["crop_bitdepth"] = 8
20+
config["crop_size"] = 640
21+
config["crop_bitdepth"] = 16
2222
config["crop_mask"] = True
23-
config["mask_cell"] = False
23+
config["mask_cell"] = True
2424

2525
# Log the start time and the final configuration so you can keep track of what you did
2626
config["log"].info('Start: ' + datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"))
@@ -31,7 +31,8 @@
3131

3232
# If we provide a "path_list.csv" file, we run our code for each pair of input/output sub-folders
3333
if os.path.exists("./path_list.csv"):
34-
path_list = open("./path_list.csv", 'r')
34+
with open("./path_list.csv", 'r') as path_list:
35+
path_list = path_list.readlines()
3536

3637
df = pd.DataFrame(columns=['id', 'cell', 'x1', 'y1', 'x2', 'y2'])
3738

examples/cellpose_segmentation/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ All images can be relative or absolute paths, or directly URLs. You can also ski
4343
Check the following `path_list.csv` content as an example:
4444

4545
```
46-
#nuclei_image,cyto_image,output_folder,output_prefix
46+
#nuclei_image,cyto_image1,cyto_image2,output_folder,output_prefix
4747
input/nuc1.tif,input/mt1.tif,,output,example1_
4848
#input/nuc2.tif,input/mt2.tif,,output,example2_
4949
input/nuc3.tif,input/mt3.tif,,output,example3_

examples/cellpose_segmentation/process.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434

3535
# If we provide a "path_list.csv" file, we run our code for each pair of input/output sub-folders
3636
if os.path.exists("./path_list.csv"):
37-
path_list = open("./path_list.csv", 'r')
37+
with open("./path_list.csv", 'r') as f:
38+
path_list = f.readlines()
3839

3940
for curr_set in path_list:
4041
if curr_set.strip() != "" and not curr_set.startswith("#"):

examples/micronuclei_segmentation/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ The code expects you to perform certain steps to run with your data:
3131
- Locate the line `config["eccentricity_tolerance"] = 0.9` and change the value of your maximum allowed eccentricity shape of the micronuclei (0 - circle, 1 - flattened ellipse).
3232
- Locate the line `config["solidity_tolerance"] = 0.1` and change the value of your minimum allowed solidity shape of the micronuclei (0 - totally fragmented, 1 - totally convex hull shape).
3333
- Locate the line `config["intensity_ratio_tolerance"] = 0.2` and change the value of your maximum allowed variance between the micronuclei average intensity and it's related nuclei average intensity (0 - no variance, 1 totally different)
34+
- Locate the line `config["overlapping_acceptance_ratio"] = 0.2` and change the value of the maximum allowed overlap ratio between a micronuclei candidate and its parent nucleus (0 - no overlap accepted, 1 - full overlap accepted).
3435

3536
To run micronuclei segmentation you have to gather the information about the sets of images you want to process. Micronuclei segmentation reads `path_list.csv` to locate each set of images, in the following .csv format:
3637

examples/micronuclei_segmentation/process.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434

3535
# If we provide a "path_list.csv" file, we run our code for each pair of input/output sub-folders
3636
if os.path.exists("./path_list.csv"):
37-
path_list = open("./path_list.csv", 'r')
37+
with open("./path_list.csv", 'r') as f:
38+
path_list = f.readlines()
3839

3940
df = pd.DataFrame(columns=["id", "micnuc_label", "nuclei_label", "micnuc_area", "micnuc_centroid_xy", "micnuc_eccentricity", "micnuc_solidity", "micnuc_intensity", "nuc_area", "nuc_centroid_xy", "nuc_eccentricity", "nuc_solidity", "nuc_intensity"])
4041

examples/otsu_explorer/README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ Setup
2525
The code expects you to perform certain steps to run with your data:
2626

2727
- Edit and modify `process.py` file:
28-
- Locate the line `config["input_path"] = "./input"` and change the path value with your desired input directory. This directory should contain all your images OR all the subfolders present in `path_list.csv` (see below).
29-
- Locate the line `config["output_path"] = "./output"` and change the path value with your desired output directory. This directory will contain the resulting generated images OR the resulting subfolders stated `path_list.csv` (see below). OBS: it will be created if it does not exists
28+
- Locate the line `config["input_directory"] = "./input"` and change the path value with your desired input directory. This directory should contain all your images OR all the subfolders present in `path_list.csv` (see below).
29+
- Locate the line `config["output_directory"] = "./output"` and change the path value with your desired output directory. This directory will contain the resulting generated images OR the resulting subfolders stated `path_list.csv` (see below). OBS: it will be created if it does not exists
3030
- You can copy all your images in the stated input folder OR use a detailed `path_list.csv` listing:
3131
- If you want to copy all your images in the input folder, just delete the `path_list.csv` file. OBS: don't choose an output folder inside the stated input folder if you are going this way!
3232
- If you want to use a detailed `path_list.csv` listing:
@@ -47,3 +47,13 @@ Running the code
4747

4848
Once you have activated your virtual environment and modified all the desired parameters (see section **Setup**, just run the code with `python process.py`. OBS: you have to run the `process.py` file, which will on its turn call the rest of the code.
4949

50+
51+
52+
Output
53+
------
54+
55+
Otsu explorer generates in the chosen output_directory the following files, for each input image:
56+
- `[image_name]_otsu_3.png`: the image thresholded using multi-Otsu with 3 bins.
57+
- `[image_name]_otsu_4.png`: the image thresholded using multi-Otsu with 4 bins.
58+
- `[image_name]_otsu_5.png`: the image thresholded using multi-Otsu with 5 bins.
59+

examples/otsu_explorer/process.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
config = { "log": logger}
1616

1717
# If you want to use constants with your script, add them here
18-
config["input_path"] = "./input"
19-
config["output_path"] = "./output"
18+
config["input_directory"] = "./input"
19+
config["output_directory"] = "./output"
2020

2121
# Log the start time and the final configuration so you can keep track of what you did
2222
config["log"].info('Start: ' + datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"))
@@ -25,21 +25,20 @@
2525
config["log"].info('----------')
2626

2727

28-
# We create the output folder
29-
os.makedirs(config["output_path"], exist_ok=True)
3028
# If we provide a "path_list.csv" file, we run our code for each pair of input/output sub-folders
3129
if os.path.exists("./path_list.csv"):
32-
path_list = open("./path_list.csv", 'r')
33-
for curr_path in path_list:
34-
if curr_path.strip() != "" and not curr_path.startswith("#"):
35-
curr_input_path = config["input_path"] + "/" + curr_path.strip().split(",")[0]
36-
curr_output_path = config["output_path"] + "/" + curr_path.strip().split(",")[1]
37-
os.makedirs(curr_output_path, exist_ok=True)
38-
39-
otsu_explorer.otsu_explorer(config, curr_input_path, curr_output_path)
30+
with open("./path_list.csv", 'r') as path_list:
31+
for curr_path in path_list:
32+
if curr_path.strip() != "" and not curr_path.startswith("#"):
33+
curr_input_path = os.path.join(config["input_directory"], curr_path.strip().split(",")[0])
34+
curr_output_path = os.path.join(config["output_directory"], curr_path.strip().split(",")[1])
35+
os.makedirs(curr_output_path, exist_ok=True)
36+
37+
otsu_explorer.otsu_explorer(config, curr_input_path, curr_output_path)
4038
# If we DON'T provide a "path_list.csv" file, we run our code once directly over the base input/output folder
4139
else:
42-
otsu_explorer.otsu_explorer(config, config["input_path"], config["output_path"])
40+
os.makedirs(config["output_directory"], exist_ok=True)
41+
otsu_explorer.otsu_explorer(config, config["input_directory"], config["output_directory"])
4342

4443
config["log"].info('----------')
45-
config["log"].info('End: ' + datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"))
44+
config["log"].info('End: ' + datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S"))

0 commit comments

Comments
 (0)