44import csv
55import cv2
66import argparse
7- from typing import List , Tuple , Dict
7+ from typing import List , Dict , Tuple
8+ from multiprocessing import Pool , cpu_count
89
910def 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
9982def 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
11490if __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