rescale4dl 0.1.1__py3-none-any.whl

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.
rescale4dl/__init__.py ADDED
File without changes
rescale4dl/batch.py ADDED
@@ -0,0 +1,209 @@
1
+ import os
2
+ import numpy as np
3
+
4
+ from typing import List
5
+ from tkinter import filedialog as fd
6
+ from tifffile import imread, imwrite
7
+
8
+ from .utils import check_crop_img, crop_with_padding
9
+ from .blurring import gaussian_blur
10
+ from .downscaling import binning_img, binning_label
11
+ from .upscaling import upsample_img, upsample_labels
12
+ from tqdm import tqdm
13
+
14
+
15
+
16
+ def downsample_batch(input_folder_path: str, input_folder_name: str, downsampling_factor: int, keep_dims: bool = False, mode: str = "sum"):
17
+ """Downsamples a batch of images by a given factor. The last two dimensions of the array are binned.
18
+ Creates new folders outside input_folder to store the results.
19
+ :param input_folder_path: path to folder containing an "images" folder and a "labels" folder. Images inside both folders should have the same name.
20
+ :param downsampling_factor: factor used to bin dimensions
21
+ :para keep_dims: whether to keep the original dimensions or just blur the image (defaults to False)
22
+ :param mode: can be either sum, max or mean, defaults to sum if not specified or not valid mode
23
+ """
24
+
25
+ if keep_dims:
26
+ new_dataset_path = os.path.join(os.path.dirname(os.path.dirname(input_folder_path)), "Processed", f"{input_folder_name}_downsampled_{downsampling_factor}_mode_{mode}_same_dims")
27
+ else:
28
+ new_dataset_path = os.path.join(os.path.dirname(os.path.dirname(input_folder_path)), "Processed", f"{input_folder_name}_downsampled_{downsampling_factor}_mode_{mode}_diff_dims")
29
+
30
+ new_images_path = os.path.join(new_dataset_path, "Images")
31
+ new_labels_path = os.path.join(new_dataset_path, "Labels")
32
+
33
+ if not os.path.exists(new_dataset_path):
34
+ os.mkdir(new_dataset_path)
35
+ os.mkdir(new_images_path)
36
+ os.mkdir(new_labels_path)
37
+
38
+ for img_name in os.listdir(os.path.join(input_folder_path, "Images")):
39
+ img = imread(os.path.join(input_folder_path, "Images", img_name)).astype(np.float32)
40
+ img = check_crop_img(img, downsampling_factor)
41
+ lbl = imread(os.path.join(input_folder_path, "Labels", img_name)).astype(np.float32)
42
+ lbl = check_crop_img(lbl, downsampling_factor)
43
+ imwrite(os.path.join(new_images_path, img_name), binning_img(img, downsampling_factor, keep_dims=keep_dims, mode=mode))
44
+ if keep_dims:
45
+ imwrite(os.path.join(new_labels_path, img_name), lbl.astype(np.uint16))
46
+ else:
47
+ imwrite(os.path.join(new_labels_path, img_name), binning_label(lbl, downsampling_factor).astype(np.uint16))
48
+
49
+
50
+ def upsample_batch(input_folder_path: str, input_folder_name: str, magnification: int, keep_dims: bool = False):
51
+ """Upsamples a batch of images by the magnification param using Catmull-rom interpolation and labels using Nearest-neighbor.
52
+ Creates new folders outside input_folder to store the results.
53
+ :param input_folder_path: path to folder containing an "images" folder and a "labels" folder. Images inside both folders should have the same name.
54
+ :param magnification: upscaling factor
55
+ :para keep_dims: whether to keep the original dimensions or just blur the image (defaults to False)
56
+ """
57
+
58
+ if keep_dims:
59
+ new_dataset_path = os.path.join(os.path.dirname(os.path.dirname(input_folder_path)), "Processed", f"{input_folder_name}_upsampled_{magnification}_same_dims")
60
+ else:
61
+ new_dataset_path = os.path.join(os.path.dirname(os.path.dirname(input_folder_path)), "Processed", f"{input_folder_name}_upsampled_{magnification}_diff_dims")
62
+
63
+ new_images_path = os.path.join(new_dataset_path, "Images")
64
+ new_labels_path = os.path.join(new_dataset_path, "Labels")
65
+
66
+ if not os.path.exists(new_dataset_path):
67
+ os.mkdir(new_dataset_path)
68
+ os.mkdir(new_images_path)
69
+ os.mkdir(new_labels_path)
70
+
71
+ for img_name in os.listdir(os.path.join(input_folder_path, "Images")):
72
+ img = imread(os.path.join(input_folder_path, "Images", img_name)).astype(np.float32)
73
+ lbl = imread(os.path.join(input_folder_path, "Labels", img_name)).astype(np.float32)
74
+ imwrite(os.path.join(new_images_path, img_name), upsample_img(img, magnification, keep_dims=keep_dims))
75
+ imwrite(os.path.join(new_labels_path, img_name), upsample_labels(lbl, magnification, keep_dims=keep_dims).astype(np.uint16))
76
+
77
+
78
+ def blur_batch(input_folder_path: str, input_folder_name: str, gaussian_sigma: float):
79
+ """Applies Gaussian blur to a batch of images.
80
+ Creates new folders outside input_folder to store the results.
81
+ :param input_folder_path: path to folder containing an "images" folder and a "labels" folder. Images inside both folders should have the same name.
82
+ :param gaussians: list of standard deviations
83
+ """
84
+
85
+ new_dataset_path = os.path.join(os.path.dirname(os.path.dirname(input_folder_path)), "Processed", f"{input_folder_name}_blurred_{gaussian_sigma}")
86
+ new_images_path = os.path.join(new_dataset_path, "Images")
87
+ new_labels_path = os.path.join(new_dataset_path, "Labels")
88
+
89
+ if not os.path.exists(new_dataset_path):
90
+ os.mkdir(new_dataset_path)
91
+ os.mkdir(new_images_path)
92
+ os.mkdir(new_labels_path)
93
+
94
+ for img_name in os.listdir(os.path.join(input_folder_path, "Images")):
95
+ img = imread(os.path.join(input_folder_path, "Images", img_name)).astype(np.float32)
96
+ lbl = imread(os.path.join(input_folder_path, "Labels", img_name)).astype(np.float32)
97
+ imwrite(os.path.join(new_images_path, img_name), gaussian_blur(img, gaussian_sigma))
98
+ imwrite(os.path.join(new_labels_path, img_name), lbl.astype(np.uint16))
99
+
100
+
101
+ def process_batch(input_folder_path: str, input_folder_name: str, downsampling_factors: List[int], magnifications: List[int], gaussians: List[float], modes: List[str] = ["sum", "mean"]):
102
+ """Performs all downstream preprocessing on a single dataset"""
103
+
104
+ for mag in magnifications:
105
+ upsample_batch(
106
+ input_folder_path,
107
+ input_folder_name,
108
+ mag
109
+ )
110
+ upsample_batch(
111
+ input_folder_path,
112
+ input_folder_name,
113
+ mag,
114
+ keep_dims=True
115
+ )
116
+
117
+ for mode in modes:
118
+ for dsf in downsampling_factors:
119
+ downsample_batch(
120
+ input_folder_path,
121
+ input_folder_name,
122
+ dsf,
123
+ keep_dims=True,
124
+ mode=mode
125
+ )
126
+ downsample_batch(
127
+ input_folder_path,
128
+ input_folder_name,
129
+ dsf,
130
+ keep_dims=False,
131
+ mode=mode
132
+ )
133
+
134
+ for gau in gaussians:
135
+ blur_batch(
136
+ input_folder_path,
137
+ input_folder_name,
138
+ gau
139
+ )
140
+
141
+
142
+ def process_all_datasets(datasets_path: str, downsampling_factor: List[int], magnification: List[int], gaussians: List[float], modes: List[str] = ["sum", "mean"]):
143
+ """Performs all downstream preprocessing on all datasets in a folder"""
144
+
145
+ if datasets_path is None:
146
+ datasets_path = fd.askdirectory()
147
+
148
+ if not os.path.exists(os.path.join(os.path.dirname(datasets_path), "Processed")):
149
+ os.mkdir(os.path.join(os.path.dirname(datasets_path), "Processed"))
150
+
151
+ for fld in os.listdir(datasets_path):
152
+ if os.path.isdir(os.path.join(datasets_path, fld)):
153
+ process_batch(
154
+ os.path.join(datasets_path, fld),
155
+ fld,
156
+ downsampling_factor,
157
+ magnification,
158
+ gaussians,
159
+ modes
160
+ )
161
+
162
+ # Core Processing Functions
163
+ def rescale_image(image: np.ndarray, factor: int, mode: str) -> np.ndarray:
164
+ """Rescale image using specified method"""
165
+ if mode == "down":
166
+ return binning_img(image, factor, keep_dims=False, mode="mean")
167
+ elif mode == "up":
168
+ return upsample_img(image, factor, keep_dims=False)
169
+ else:
170
+ raise ValueError(f"Invalid scale mode: {mode}. Use 'up' or 'down'")
171
+
172
+
173
+ # Processing Pipeline
174
+ def rescale_and_crop_image(INPUT_DIR, OUTPUT_DIR, SCALE_FACTOR, SCALE_MODE, TARGET_SHAPE, SAVE_SCALED):
175
+ # Create output directories
176
+ os.makedirs(os.path.join(OUTPUT_DIR, "scaled"), exist_ok=True)
177
+ os.makedirs(os.path.join(OUTPUT_DIR, "final"), exist_ok=True)
178
+ # Get image list
179
+ images = [f for f in os.listdir(INPUT_DIR) if f.lower().endswith('.tif')]
180
+
181
+ for filename in images:
182
+ # Load image
183
+ img_path = os.path.join(INPUT_DIR, filename)
184
+ image = np.squeeze(imread(img_path))
185
+
186
+ # Rescale
187
+ scaled = rescale_image(image, SCALE_FACTOR, SCALE_MODE)
188
+
189
+ # Save intermediate
190
+ if SAVE_SCALED:
191
+ imwrite(os.path.join(OUTPUT_DIR, "scaled", f"scaled_{SCALE_MODE}_{SCALE_FACTOR}_{filename}"), scaled)
192
+
193
+ # Crop/Pad
194
+ final_image = crop_with_padding(scaled, TARGET_SHAPE)
195
+
196
+ # Save result
197
+ imwrite(os.path.join(OUTPUT_DIR, "final", f"processed_{SCALE_MODE}_{SCALE_FACTOR}_{filename}"), final_image)
198
+
199
+
200
+ def rescale_and_crop(INPUT_DIR, OUTPUT_DIR, SCALE_FACTOR, TARGET_SHAPE, SAVE_SCALED):
201
+ for s in tqdm(SCALE_FACTOR, desc="Rescaling and cropping"):
202
+ SCALE_MODE = "down" if s<1 else "up"
203
+ if s < 1:
204
+ SCALE_MODE = "down"
205
+ s = np.floor(1/s)
206
+ else:
207
+ SCALE_MODE = "up"
208
+ print(s)
209
+ rescale_and_crop_image(INPUT_DIR, OUTPUT_DIR, np.int8(s), SCALE_MODE, TARGET_SHAPE, SAVE_SCALED)
rescale4dl/blurring.py ADDED
@@ -0,0 +1,14 @@
1
+ import numpy as np
2
+ from skimage.filters import gaussian
3
+
4
+
5
+ def gaussian_blur(img: np.array, sigma: float):
6
+ """
7
+ Apply Gaussian blur to an image.
8
+ Parameters:
9
+ img (ndarray): The input image.
10
+ sigma (float): The standard deviation for Gaussian kernel.
11
+ Returns:
12
+ ndarray: The blurred image.
13
+ """
14
+ return gaussian(img, sigma=sigma)
@@ -0,0 +1,67 @@
1
+ import numpy as np
2
+
3
+ from skimage.transform import rescale
4
+ from nanopyx.core.transform.binning import rebin_2d
5
+ from nanopyx.core.transform._le_convolution import Convolution
6
+
7
+
8
+
9
+ def binning_downsize(img: np.array, downsampling_factor: int, mode: str = "sum"):
10
+ """Bins a 2D array by a given factor. The last two dimensions of the array are binned.
11
+ :param arr: numpy array with any shape as long as last two dimensions are y, x (example: time, channel, z, y, x)
12
+ :param bin_factor: factor used to bin dimensions
13
+ :param mode: can be either sum or mean, defaults to sum if not specified or not valid mode
14
+ :return: binned array
15
+ """
16
+ return rebin_2d(img, downsampling_factor, mode=mode)
17
+
18
+
19
+ def binning_blur(img: np.array, downsampling_factor: int, mode: str = "sum"):
20
+ """Blurs a 2D array by a given factor using binning. The last two dimensions of the array are binned.
21
+ :param arr: numpy array with any shape as long as last two dimensions are y, x (example: time, channel, z, y, x)
22
+ :param bin_factor: factor used to bin dimensions
23
+ :param mode: can be either sum or mean, defaults to sum if not specified or not valid mode
24
+ :return: binned array
25
+ """
26
+ conv = Convolution()
27
+
28
+ if mode not in ["sum", "mean"]:
29
+ mode = "sum"
30
+
31
+ if mode == "sum":
32
+ kernel = np.ones((downsampling_factor, downsampling_factor), dtype=np.float32)
33
+ return np.asarray(
34
+ conv.run(img.astype(np.float32), kernel),
35
+ dtype=np.float32
36
+ )
37
+
38
+ elif mode == "mean":
39
+ kernel = np.ones((
40
+ downsampling_factor, downsampling_factor),
41
+ dtype=np.float32) / (
42
+ downsampling_factor**2
43
+ )
44
+ return np.asarray(
45
+ conv.run(img.astype(np.float32), kernel),
46
+ dtype=np.float32
47
+ )
48
+
49
+
50
+ def binning_img(img: np.array, downsampling_factor: int, keep_dims: bool = False, mode: str = "sum"):
51
+ """Bins a 2D array by a given factor. The last two dimensions of the array are binned.
52
+ :param arr: numpy array with any shape as long as last two dimensions are y, x (example: time, channel, z, y, x)
53
+ :param bin_factor: factor used to bin dimensions
54
+ :param keep_dims: whether to keep the original dimensions or just blur the image (defaults to False)
55
+ :param mode: can be either sum or mean, defaults to sum if not specified or not valid mode
56
+ :return: binned array
57
+ """
58
+ if keep_dims:
59
+ return binning_blur(img, downsampling_factor, mode=mode)
60
+ else:
61
+ return binning_downsize(img, downsampling_factor, mode=mode)
62
+
63
+ def binning_label(img: np.array, downsampling_factor: int):
64
+ """Bins a 2D array by a given factor using Nearest-neighbor.
65
+ :params img: Input image, should be a 2-d np array.
66
+ :params downsampling_factor: factor used to bin dimensions"""
67
+ return rescale(img, 1/downsampling_factor, anti_aliasing=False, order=0).astype(np.uint16)
File without changes
File without changes
@@ -0,0 +1,111 @@
1
+ # imports
2
+ import ast
3
+ import numpy as np
4
+ import pandas as pd
5
+ from typing import Optional
6
+ from ..utils import get_csv_dict
7
+
8
+
9
+ def microscope_FOV_area(
10
+ path_metrics_csv: str,
11
+ dataset_name: str,
12
+ dataset_name_match_dict: Optional[dict] = {
13
+ "Deepbacs_instance": "deepbacs",
14
+ "Saureus": "saureus",
15
+ "Saureus_WT_PC190723": "saureus_mix",
16
+ "Worm_instance": "worm",
17
+ },
18
+ ) -> float:
19
+ """
20
+ Function to calculate the area of the microscope FOV.
21
+
22
+ Args:
23
+ path_metrics_csv (str): path to the csv with the pdf metrics.
24
+ dataset_name (str): dataset instance name.
25
+ dataset_name_match_dict (Optional[dict]): dictionary to match dataset names to the sample name from the model in the csv.
26
+
27
+ Returns:
28
+ FOV_area (float): area in pixels of the microscope FOV.
29
+ """
30
+ # Read CSVs
31
+ pdf_metrics_csv = pd.read_csv(path_metrics_csv)
32
+
33
+ # Filter dataframe for specific dataset and OG sampling
34
+ pdf_metrics_csv = pdf_metrics_csv[
35
+ pdf_metrics_csv["sample"] == dataset_name_match_dict[dataset_name]
36
+ ]
37
+ pdf_metrics_csv = pdf_metrics_csv[pdf_metrics_csv["sampling"] == "og"]
38
+
39
+ # Convert string to values and calculate FOV area from Image dimensions
40
+ pdf_metrics_csv["img_dimensions"] = pdf_metrics_csv["img_dimensions"].apply(
41
+ ast.literal_eval
42
+ )
43
+ pdf_metrics_csv["FOV_area"] = pdf_metrics_csv["img_dimensions"].apply(
44
+ lambda x: x[0] * x[1]
45
+ )
46
+
47
+ return pdf_metrics_csv["FOV_area"].values[0]
48
+
49
+
50
+ def obj_per_microscope_FOV(
51
+ microscope_FOV: float,
52
+ folder_path: str,
53
+ dataset_name: str,
54
+ save_csv: Optional[bool] = False,
55
+ ) -> pd.DataFrame:
56
+ """
57
+ Function to calculate the number of objects per microscope FOV.
58
+
59
+ Args:
60
+ path_metrics_csv (str): path to the csv with the metrics.
61
+ folder_path (str): path to the folder with the csvs.
62
+ dataset_name (str): dataset instance name.
63
+ save_csv (bool): whether to save the values in a csv.
64
+
65
+ Returns:
66
+ dataframe with the median and mean number of objects per microscope FOV.
67
+ """
68
+ # Empty Dataframe
69
+ px_per_obj = pd.DataFrame()
70
+
71
+ # Get dictionary of CSVs in folder
72
+ csv_dict = get_csv_dict(folder_path)
73
+
74
+ # Read CSVs
75
+ per_obj_csv = pd.read_csv(csv_dict[dataset_name][0])
76
+
77
+ # Calculate median and mean values of GT area per FOV
78
+ px_per_obj["GT_area_median"] = per_obj_csv.groupby(
79
+ ["Grand_Parent_Folder", "File_name"]
80
+ )["GT_area"].median()
81
+ px_per_obj["GT_area_mean"] = (
82
+ per_obj_csv.groupby(["Grand_Parent_Folder", "File_name"])["GT_area"]
83
+ .mean()
84
+ .round(2)
85
+ )
86
+
87
+ # Calculate the number of objects per FOV
88
+ px_per_obj["Obj_per_FOV_median"] = (
89
+ microscope_FOV / px_per_obj["GT_area_median"]
90
+ ).round(0)
91
+ px_per_obj["Obj_per_FOV_mean"] = (
92
+ microscope_FOV / px_per_obj["GT_area_mean"]
93
+ ).round(0)
94
+
95
+ if save_csv:
96
+ # Save DataFrame to CSV in the dataset folder
97
+ px_per_obj.to_csv(
98
+ folder_path + "/" + dataset_name + "/" + dataset_name + "_obj_per_FOV.csv"
99
+ )
100
+
101
+ print(
102
+ "CSV saved in "
103
+ + folder_path
104
+ + "/"
105
+ + dataset_name
106
+ + "/"
107
+ + dataset_name
108
+ + "_obj_per_FOV.csv"
109
+ )
110
+
111
+ return px_per_obj[["Obj_per_FOV_mean", "Obj_per_FOV_median"]]
File without changes