Skip to content

Commit

Permalink
Automated cropping of larvae from 4x Acquifer images
Browse files Browse the repository at this point in the history
  • Loading branch information
macromeer committed Nov 20, 2024
1 parent 7a3bed4 commit 98eca14
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 6 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@
- [Quick installation](https://github.com/MercaderLabAnatomy/T-MIDAS?tab=readme-ov-file#installation-ubuntu)


T-MIDAS is built on established image processing libraries such as [scikit-image](https://github.com/scikit-image/scikit-image), [py-clesperanto](https://github.com/clEsperanto/pyclesperanto_prototype) and [CuPy](https://github.com/cupy/cupy).

All dependencies are listed [here](https://github.com/MercaderLabAnatomy/T-MIDAS/blob/main/scripts/install_dependencies.py).

See [acknowledgements and citations](https://github.com/MercaderLabAnatomy/T-MIDAS?tab=readme-ov-file#acknowledgements-and-citations) for further information.
T-MIDAS is built on established image processing libraries. All dependencies are listed [here](https://github.com/MercaderLabAnatomy/T-MIDAS/blob/main/scripts/install_dependencies.py). See [acknowledgements and citations](https://github.com/MercaderLabAnatomy/T-MIDAS?tab=readme-ov-file#acknowledgements-and-citations) for further information.

## Text-based User Interface
![image](https://github.com/MercaderLabAnatomy/T-MIDAS/assets/99955854/ef71315b-726d-4a2f-9546-d326aba513dd)
Expand All @@ -43,6 +39,7 @@ More detailed information is provided via text-based user interface.
[7] Split color channels (2D or 3D, time series)
[8] Merge color channels (2D or 3D, time series)
[9] Convert RGB images to Napari label images
[10] Crop out zebrafish larvae from 4x Acquifer images (multicolor but requires brightfield)

[2] Image Segmentation
[1] Segment bright spots (2D or 3D, time series)
Expand Down Expand Up @@ -103,6 +100,8 @@ This project relies on several open-source libraries and tools. We would like to

- [NumPy](https://numpy.org/): Harris, C.R., Millman, K.J., van der Walt, S.J. et al. Array programming with NumPy. Nature 585, 357–362 (2020). DOI: 10.1038/s41586-020-2649-2

- [CuPy](https://github.com/cupy/cupy): Nishino, R. O. Y. U. D., & Loomis, S. H. C. (2017). Cupy: A numpy-compatible library for nvidia gpu calculations. 31st confernce on neural information processing systems, 151(7).

- [scikit-image](https://scikit-image.org/): van der Walt, S., Schönberger, J. L., Nunez-Iglesias, J., Boulogne, F., Warner, J. D., Yager, N., ... & Yu, T. (2014). scikit-image: image processing in Python. PeerJ, 2, e453.

- [tifffile](https://pypi.org/project/tifffile/): Gohlke, C. (2021). tifffile (Version X.X.X) [Software]. Available from https://pypi.org/project/tifffile/
Expand Down
115 changes: 115 additions & 0 deletions scripts/crop_acquifer_larvae.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import os
import numpy as np
import argparse
import tifffile as tf
from skimage.measure import regionprops, label
import warnings
import re
from collections import defaultdict
from tqdm import tqdm
warnings.filterwarnings("ignore")
import pyclesperanto_prototype as cle

def parse_args():
parser = argparse.ArgumentParser(description='Extract elongated ROIs from Acquifer TIF files and save them as multi-color TIF files.')
parser.add_argument('--input', type=str, help='Path to the folder containing the TIF files.')
parser.add_argument('--padding', type=int, default=50, help='Padding around the ROI (default: 50)')
return parser.parse_args()

def safe_divide(a, b, default=0):
return a / b if b != 0 else default

def get_roi(image, padding):
if image is None or image.size == 0:
return (0, 0, 0, 0) # Return a default ROI if the image is None or empty

# Normalize and convert to uint8
image_min, image_max = np.min(image), np.max(image)
if image_max > image_min:
image = ((image - image_min) / (image_max - image_min) * 255).astype(np.uint8)
else:
image = np.zeros_like(image, dtype=np.uint8) # All black image if min == max

image1_gbp = cle.gaussian_blur(image, None, 10.0, 10.0, 0.0)
image3_vsp = cle.variance_sphere(image1_gbp, None, 1.0, 1.0, 0.0)
labels = cle.threshold_otsu(image3_vsp)

# Get ROIs
props = regionprops(labels, intensity_image=image)
y0, x0, y1, x1 = props[0].bbox
minr, minc = max(0, y0 - padding), max(0, x0 - padding)
maxr, maxc = min(labels.shape[0], y1 + padding), min(labels.shape[1], x1 + padding)

return (minr, minc, maxr - minr, maxc - minc)

def group_files(input_folder):
pattern = r'^-([A-Z]\d+)--PO(\d+)'
"""
This pattern captures all files that correspond
to the same well and PO (position)
cf. https://www.acquifer.de/resources/metadata/
This means that all groups will be cropped based
on the ROI (+padding) of the first CO1 (brightfield)
file found in the group.
"""
files_grouped_by_well_and_position = defaultdict(list)

for filename in os.listdir(input_folder):
if filename.endswith('.tif'):
match = re.match(pattern, filename)
if match:
key = f"{match.group(1)}--PO{match.group(2)}"
files_grouped_by_well_and_position[key].append(filename)

return files_grouped_by_well_and_position

def process_files(input_folder, output_dir, padding):
files_grouped_by_well_and_position = group_files(input_folder)
print(f"Found {len(files_grouped_by_well_and_position)} groups of files.")

for group_key, files in tqdm(files_grouped_by_well_and_position.items(), desc="Processing file groups"):
try:
# Find the CO1 file in the group
co1_file = next((f for f in files if '--CO1--' in f), None)
if not co1_file:
print(f"No CO1 file found for group {group_key}. Skipping.")
continue

# Load CO1 image and get ROI
with tf.TiffFile(os.path.join(input_folder, co1_file)) as tif:
co1_image = tif.asarray()
roi = get_roi(co1_image, padding)

# Process all files in the group
for file in files:
with tf.TiffFile(os.path.join(input_folder, file)) as tif:
channel_image = tif.asarray()

# Crop and normalize
y, x, h, w = roi
cropped_image = channel_image[y:y+h, x:x+w]
normalized_image = ((cropped_image - cropped_image.min()) / (cropped_image.max() - cropped_image.min()) * 65535).astype(np.uint16)

# Save cropped image
output_filename = f"{os.path.splitext(file)[0]}_cropped.tif"
output_path = os.path.join(output_dir, output_filename)
tf.imwrite(output_path, normalized_image, compression='zlib')
print(f"Saved: {output_filename}")

except Exception as e:
print(f"Error processing group {group_key}: {str(e)}")

def main():
args = parse_args()
input_folder = args.input
padding = args.padding
output_dir = os.path.join(input_folder, "processed_tifs")

if not os.path.exists(output_dir):
os.makedirs(output_dir)

process_files(input_folder, output_dir, padding)

if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion scripts/user_welcome.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def image_preprocessing():
print("[7] Split color channels (2D or 3D, also time series)")
print("[8] Merge color channels (2D or 3D, also time series)")
print("[9] Convert RGB images to label images")
print("[10] Crop out zebrafish larvae from Acquifer images (multicolor but requires brightfield)")
print("[10] Crop out zebrafish larvae from 4x Acquifer images (multicolor but requires brightfield)")
print("[r] Return to Main Menu")
print("[x] Exit \n")

Expand Down

0 comments on commit 98eca14

Please sign in to comment.