Skip to content

Commit 60e3108

Browse files
committed
added multiprocessing to STAR file writing
1 parent d7ef028 commit 60e3108

1 file changed

Lines changed: 48 additions & 72 deletions

File tree

partinet/process_utils/star_file.py

Lines changed: 48 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -4,64 +4,71 @@
44
import csv
55
import cv2
66
import argparse
7-
from typing import List, Tuple, Dict
7+
from typing import List, Dict, Tuple
8+
from multiprocessing import Pool, cpu_count
89

910
def yolo_to_starfile(yolo_coords: Dict[str, float], image_width: int, image_height: int, diameters: List[int]) -> Tuple[int, int, int]:
10-
"""
11-
Convert YOLO bounding box coordinates to STAR file format.
12-
13-
Args:
14-
yolo_coords (dict): Dictionary with YOLO bounding box attributes.
15-
image_width (int): Width of the image.
16-
image_height (int): Height of the image.
17-
diameters (list): List to append the calculated diameters.
18-
19-
Returns:
20-
tuple: (x_center, y_center, diameter) in STAR file format.
21-
"""
2211
x_center = math.ceil(yolo_coords['x_centre'] * image_width)
2312
y_center = math.ceil(yolo_coords['y_centre'] * image_height)
2413
width = yolo_coords['width'] * image_width
2514
height = yolo_coords['height'] * image_height
26-
27-
# Calculate the diameter
2815
diameter = math.ceil(max(width, height))
2916
diameters.append(diameter)
30-
3117
return x_center, y_center, diameter
3218

33-
def generate_output(labels: pd.DataFrame, filename: str, star_writer: csv.writer, diameters: List[int], img_width: int, img_height: int) -> None:
19+
def generate_output(labels: pd.DataFrame, filename: str, img_width: int, img_height: int) -> List[Tuple[str, int, int, int]]:
3420
"""
35-
Write particle data to the STAR file.
36-
37-
Args:
38-
labels (pd.DataFrame): DataFrame containing particle bounding box data.
39-
filename (str): Name of the micrograph file.
40-
star_writer (csv.writer): CSV writer object for the STAR file.
41-
diameters (list): List to track calculated diameters.
42-
img_width (int): Width of the image.
43-
img_height (int): Height of the image.
21+
Generate rows for the STAR file for a single image
4422
"""
23+
diameters: List[int] = []
24+
output_rows = []
4525
for _, row in labels.iterrows():
4626
x_centre, y_centre, diameter = yolo_to_starfile(row, img_width, img_height, diameters)
47-
star_writer.writerow([filename, x_centre, y_centre, diameter])
27+
star_filename = f"{filename}.mrc"
28+
output_rows.append((star_filename, x_centre, y_centre, diameter))
29+
return output_rows
4830

49-
def main(labels_path: str, images_path: str, star_out_path: str, conf_thresh: float) -> None:
31+
def process_image(args_tuple) -> List[Tuple[str, int, int, int]]:
5032
"""
51-
Main function to process YOLO prediction labels and generate a STAR file.
52-
53-
Args:
54-
labels_path (str): Path to the directory containing YOLO label files.
55-
images_path (str): Path to the directory containing images.
56-
star_out_path (str): Path to the output STAR file.
57-
conf_thresh (float): Minimum confidence threshold for predictions.
33+
Process a single image and return STAR rows
5834
"""
59-
diameters: List[int] = []
35+
image_file, labels_path, images_path, conf_thresh = args_tuple
36+
filename = os.path.splitext(image_file)[0]
37+
label_file_path = os.path.join(labels_path, f"{filename}.txt")
38+
39+
if not os.path.exists(label_file_path):
40+
print(f"Warning: Label file not found for image {image_file}. Skipping.")
41+
return []
6042

43+
# Read the image to get dimensions
44+
image = cv2.imread(os.path.join(images_path, image_file))
45+
if image is None:
46+
print(f"Warning: Could not read image {image_file}. Skipping.")
47+
return []
48+
img_width, img_height = image.shape[1], image.shape[0]
49+
50+
# Read YOLO labels
51+
custom_headers = ['class', 'x_centre', 'y_centre', 'width', 'height', 'conf']
52+
labels = pd.read_csv(label_file_path, header=None, names=custom_headers, sep=' ')
53+
labels = labels[labels['conf'] > float(conf_thresh)]
54+
55+
# Generate STAR rows
56+
return generate_output(labels, filename, img_width, img_height)
57+
58+
def main(labels_path: str, images_path: str, star_out_path: str, conf_thresh: float) -> None:
59+
image_files = os.listdir(images_path)
60+
args_list = [(img_file, labels_path, images_path, conf_thresh) for img_file in image_files]
61+
62+
# Use all available CPUs
63+
with Pool(cpu_count()) as pool:
64+
results = pool.map(process_image, args_list)
65+
66+
# Flatten the results
67+
all_rows = [row for result in results for row in result]
68+
69+
# Write STAR file
6170
with open(star_out_path, "w") as star_file:
6271
star_writer = csv.writer(star_file, delimiter=' ')
63-
64-
# Write STAR file header
6572
star_writer.writerow([])
6673
star_writer.writerow(["data_"])
6774
star_writer.writerow([])
@@ -70,47 +77,16 @@ def main(labels_path: str, images_path: str, star_out_path: str, conf_thresh: fl
7077
star_writer.writerow(["_rlnCoordinateX", "#2"])
7178
star_writer.writerow(["_rlnCoordinateY", "#3"])
7279
star_writer.writerow(["_rlnDiameter", "#4"])
73-
74-
for i, image_file in enumerate(os.listdir(images_path), start=1):
75-
print(f"Processing image {i}: {image_file}")
76-
77-
filename = os.path.splitext(image_file)[0]
78-
label_file_path = os.path.join(labels_path, f"{filename}.txt")
79-
80-
if not os.path.exists(label_file_path):
81-
print(f"Warning: Label file not found for image {image_file}. Skipping.")
82-
continue
83-
84-
# Read the image to get dimensions
85-
image = cv2.imread(os.path.join(images_path, image_file))
86-
img_width, img_height = image.shape[1], image.shape[0]
87-
88-
# Read YOLO labels
89-
custom_headers = ['class', 'x_centre', 'y_centre', 'width', 'height', 'conf']
90-
labels = pd.read_csv(label_file_path, header=None, names=custom_headers, sep=' ')
91-
92-
# Filter labels by confidence threshold
93-
labels = labels[labels['conf'] > float(conf_thresh)]
94-
95-
# Generate output for the STAR file
96-
star_filename = f"{filename}.mrc"
97-
generate_output(labels, star_filename, star_writer, diameters, img_width, img_height)
80+
star_writer.writerows(all_rows)
9881

9982
def parse_args() -> argparse.Namespace:
100-
"""
101-
Parse command-line arguments.
102-
103-
Returns:
104-
argparse.Namespace: Parsed command-line arguments.
105-
"""
106-
parser = argparse.ArgumentParser(description="Generate STAR file from PartiNet predictions")
83+
parser = argparse.ArgumentParser(description="Generate STAR file from YOLO predictions")
10784
parser.add_argument("--labels", required=True, help="Path to the labels directory")
10885
parser.add_argument("--images", required=True, help="Path to the images directory")
10986
parser.add_argument("--output", required=True, help="Path to the output STAR file")
11087
parser.add_argument("--conf", required=True, type=float, help="Minimum confidence threshold for predictions")
111-
11288
return parser.parse_args()
11389

11490
if __name__ == "__main__":
11591
args = parse_args()
116-
main(args.labels, args.images, args.output, args.conf)
92+
main(args.labels, args.images, args.output, args.conf)

0 commit comments

Comments
 (0)