|
1 | | -#!/usr/bin/python3.8 |
2 | | -# -*- coding: utf-8 -*- |
3 | | -''' |
| 1 | +"""Split RAW format into N image slices |
4 | 2 | Created on Jul 27, 2018 |
5 | 3 |
|
6 | 4 | @author: Ni Jiang, Tim Parker |
7 | | -''' |
| 5 | +""" |
8 | 6 |
|
9 | 7 | import logging |
10 | 8 | import os |
|
22 | 20 |
|
23 | 21 |
|
24 | 22 | def slice_to_img(args, slice, x, y, bitdepth, image_bitdepth, old_min, old_max, new_min, new_max, ofp): |
25 | | - """Convert byte data of a slice into an image |
| 23 | + """Convert byte data of a slice into an image |
26 | 24 |
|
27 | | - Args: |
28 | | - args (Namespace): arguments object |
29 | | - slice (numpy.ndarray): slice data |
30 | | - x (int): width of the slice as an image |
31 | | - y (int): height of the slice as an image |
32 | | - ofp (str): intended output path of the image to be saved |
| 25 | + Args: |
| 26 | + args (Namespace): arguments object |
| 27 | + slice (numpy.ndarray): slice data |
| 28 | + x (int): width of the slice as an image |
| 29 | + y (int): height of the slice as an image |
| 30 | + ofp (str): intended output path of the image to be saved |
33 | 31 |
|
34 | | - """ |
35 | | - slice = slice.reshape([y,x]) |
| 32 | + """ |
| 33 | + slice = slice.reshape([y, x]) |
36 | 34 |
|
37 | | - if bitdepth != image_bitdepth: |
38 | | - slice = scale(slice, old_min, old_max, new_min, new_max) |
39 | | - slice = np.floor(slice) |
| 35 | + if bitdepth != image_bitdepth: |
| 36 | + slice = scale(slice, old_min, old_max, new_min, new_max) |
| 37 | + slice = np.floor(slice) |
| 38 | + |
| 39 | + if not args.dryrun: |
| 40 | + Image.fromarray(slice.astype(image_bitdepth)).save(ofp) |
40 | 41 |
|
41 | | - if not args.dryrun: |
42 | | - Image.fromarray(slice.astype(image_bitdepth)).save(ofp) |
43 | 42 |
|
44 | 43 | def extract_slices(args, fp): |
45 | | - """Extract each slice of a volume, one by one and save it as an image |
46 | | -
|
47 | | - Args: |
48 | | - args (Namespace): arguments object |
49 | | -
|
50 | | - """ |
51 | | - def update(*args): |
52 | | - pbar.update() |
53 | | - |
54 | | - # Set an images directory for the volume |
55 | | - imgs_dir = os.path.splitext(fp)[0] |
56 | | - logging.debug(f"Slices directory: '{imgs_dir}'") |
57 | | - dat_fp = f"{os.path.splitext(fp)[0]}.dat" |
58 | | - logging.debug(f"DAT filepath: '{dat_fp}'") |
59 | | - |
60 | | - # Create images directory if does not exist |
61 | | - try: |
62 | | - if not os.path.exists(imgs_dir): |
63 | | - os.makedirs(imgs_dir) |
64 | | - # else: |
65 | | - # logging.warning(f"Output directory for slices already exists '{imgs_dir}'.") |
66 | | - except: |
67 | | - raise |
68 | | - # Images directory is created and ready |
69 | | - else: |
70 | | - # Get dimensions of the volume |
71 | | - metadata = dat.read(dat_fp) |
72 | | - x, y, z = metadata['xdim'], metadata['ydim'], metadata['zdim'] |
73 | | - xth, yth, zth = metadata['x_thickness'], metadata['y_thickness'], metadata['z_thickness'] |
74 | | - logging.debug(f"Volume dimensions: <{x}, {y}, {z}> for '{fp}'") |
75 | | - logging.debug(f"Slice thicknesses: <{xth}, {yth}, {zth}> for '{fp}'") |
76 | | - |
77 | | - bitdepth = determine_bit_depth(fp, (x,y,z)) |
78 | | - logging.debug(f"Detected bit depth '{bitdepth}' for '{fp}'") |
79 | | - |
80 | | - # Pad the index for the slice in its filename based on the |
81 | | - # number of digits for the total count of slices |
82 | | - digits = len(str(z)) |
83 | | - num_format = '{:0'+str(digits)+'d}' |
84 | | - |
85 | | - # Set slice dimensions |
86 | | - img_size = x * y |
87 | | - offset = img_size * np.dtype(bitdepth).itemsize |
88 | | - |
89 | | - # Determine scaling parameters per volume for output images |
90 | | - # Equate the image format to numpy dtype |
91 | | - if args.format == 'tif': |
92 | | - image_bitdepth = 'uint16' |
93 | | - elif args.format == 'png': |
94 | | - image_bitdepth = 'uint8' |
95 | | - else: |
96 | | - image_bitdepth = 'uint8' |
97 | | - |
98 | | - # Construct transformation function |
99 | | - # If input bitdepth is an integer, get the max and min with iinfo |
100 | | - if np.issubdtype(np.dtype(bitdepth), np.integer): |
101 | | - old_min = np.iinfo(np.dtype(bitdepth)).min |
102 | | - old_max = np.iinfo(np.dtype(bitdepth)).max |
103 | | - # Otherwise, assume float32 input |
104 | | - else: |
105 | | - old_min, old_max = find_float_range(fp, dtype=bitdepth, buffer_size=offset) |
106 | | - # If output image bit depth is an integer, get the max and min with iinfo |
107 | | - if np.issubdtype(np.dtype(image_bitdepth), np.integer): |
108 | | - new_min = np.iinfo(np.dtype(image_bitdepth)).min |
109 | | - new_max = np.iinfo(np.dtype(image_bitdepth)).max |
110 | | - # Otherwise, assume float32 output |
111 | | - else: |
112 | | - new_min = np.finfo(np.dtype(image_bitdepth)).min |
113 | | - new_max = np.finfo(np.dtype(image_bitdepth)).max |
114 | | - |
115 | | - logging.debug(f"{bitdepth} ({old_min}, {old_max}) -> {image_bitdepth} ({new_min}, {new_max})") |
116 | | - |
117 | | - # Extract data from volume, slice-by-slice |
118 | | - slices = [] |
119 | | - |
120 | | - description = f"Extracting slices from {os.path.basename(fp)} ({bitdepth})" |
121 | | - pbar = tqdm(total = z, desc=description) |
122 | | - with open(fp, 'rb') as ifp: |
123 | | - # Dedicate N CPUs for processing |
124 | | - with Pool(args.threads) as p: |
125 | | - # For each slice in the volume... |
126 | | - for i in range(0, z): |
127 | | - # Read slice data, and set job data for each process |
128 | | - ifp.seek(i*offset) |
129 | | - chunk = np.fromfile(ifp, dtype=bitdepth, count = img_size, sep="") |
130 | | - ofp = os.path.join(imgs_dir, f"{os.path.splitext(os.path.basename(fp))[0]}_{num_format.format(i)}.{args.format}") |
131 | | - # Check if the image already exists |
132 | | - if os.path.exists(ofp) and not args.force: |
133 | | - pbar.update() |
134 | | - continue |
135 | | - p.apply_async(slice_to_img, args=(args, chunk, x, y, bitdepth, image_bitdepth, old_min, old_max, new_min, new_max, ofp), callback=update) |
136 | | - p.close() |
137 | | - p.join() |
138 | | - pbar.close() |
139 | | - pbar = None |
| 44 | + """Extract each slice of a volume, one by one and save it as an image |
| 45 | +
|
| 46 | + Args: |
| 47 | + args (Namespace): arguments object |
| 48 | +
|
| 49 | + """ |
| 50 | + def update(*args): |
| 51 | + pbar.update() |
| 52 | + |
| 53 | + # Set an images directory for the volume |
| 54 | + imgs_dir = os.path.splitext(fp)[0] |
| 55 | + logging.debug(f"Slices directory: '{imgs_dir}'") |
| 56 | + dat_fp = f"{os.path.splitext(fp)[0]}.dat" |
| 57 | + logging.debug(f"DAT filepath: '{dat_fp}'") |
| 58 | + |
| 59 | + # Create images directory if does not exist |
| 60 | + try: |
| 61 | + if not os.path.exists(imgs_dir): |
| 62 | + os.makedirs(imgs_dir) |
| 63 | + # else: |
| 64 | + # logging.warning(f"Output directory for slices already exists '{imgs_dir}'.") |
| 65 | + except: |
| 66 | + raise |
| 67 | + # Images directory is created and ready |
| 68 | + else: |
| 69 | + # Get dimensions of the volume |
| 70 | + metadata = dat.read(dat_fp) |
| 71 | + x, y, z = metadata['xdim'], metadata['ydim'], metadata['zdim'] |
| 72 | + xth, yth, zth = metadata['x_thickness'], metadata['y_thickness'], metadata['z_thickness'] |
| 73 | + logging.debug(f"Volume dimensions: <{x}, {y}, {z}> for '{fp}'") |
| 74 | + logging.debug(f"Slice thicknesses: <{xth}, {yth}, {zth}> for '{fp}'") |
| 75 | + |
| 76 | + bitdepth = determine_bit_depth(fp, (x, y, z)) |
| 77 | + logging.debug(f"Detected bit depth '{bitdepth}' for '{fp}'") |
| 78 | + |
| 79 | + # Pad the index for the slice in its filename based on the |
| 80 | + # number of digits for the total count of slices |
| 81 | + digits = len(str(z)) |
| 82 | + num_format = '{:0'+str(digits)+'d}' |
| 83 | + |
| 84 | + # Set slice dimensions |
| 85 | + img_size = x * y |
| 86 | + offset = img_size * np.dtype(bitdepth).itemsize |
| 87 | + |
| 88 | + # Determine scaling parameters per volume for output images |
| 89 | + # Equate the image format to numpy dtype |
| 90 | + if args.format == 'tif': |
| 91 | + image_bitdepth = 'uint16' |
| 92 | + elif args.format == 'png': |
| 93 | + image_bitdepth = 'uint8' |
| 94 | + else: |
| 95 | + image_bitdepth = 'uint8' |
| 96 | + |
| 97 | + # Construct transformation function |
| 98 | + # If input bitdepth is an integer, get the max and min with iinfo |
| 99 | + if np.issubdtype(np.dtype(bitdepth), np.integer): |
| 100 | + old_min = np.iinfo(np.dtype(bitdepth)).min |
| 101 | + old_max = np.iinfo(np.dtype(bitdepth)).max |
| 102 | + # Otherwise, assume float32 input |
| 103 | + else: |
| 104 | + old_min, old_max = find_float_range( |
| 105 | + fp, dtype=bitdepth, buffer_size=offset) |
| 106 | + # If output image bit depth is an integer, get the max and min with iinfo |
| 107 | + if np.issubdtype(np.dtype(image_bitdepth), np.integer): |
| 108 | + new_min = np.iinfo(np.dtype(image_bitdepth)).min |
| 109 | + new_max = np.iinfo(np.dtype(image_bitdepth)).max |
| 110 | + # Otherwise, assume float32 output |
| 111 | + else: |
| 112 | + new_min = np.finfo(np.dtype(image_bitdepth)).min |
| 113 | + new_max = np.finfo(np.dtype(image_bitdepth)).max |
| 114 | + |
| 115 | + logging.debug( |
| 116 | + f"{bitdepth} ({old_min}, {old_max}) -> {image_bitdepth} ({new_min}, {new_max})") |
| 117 | + |
| 118 | + # Extract data from volume, slice-by-slice |
| 119 | + slices = [] |
| 120 | + |
| 121 | + description = f"Extracting slices from {os.path.basename(fp)} ({bitdepth})" |
| 122 | + pbar = tqdm(total=z, desc=description) |
| 123 | + with open(fp, 'rb') as ifp: |
| 124 | + # Dedicate N CPUs for processing |
| 125 | + with Pool(args.threads) as p: |
| 126 | + # For each slice in the volume... |
| 127 | + for i in range(0, z): |
| 128 | + # Read slice data, and set job data for each process |
| 129 | + ifp.seek(i*offset) |
| 130 | + chunk = np.fromfile(ifp, dtype=bitdepth, |
| 131 | + count=img_size, sep="") |
| 132 | + ofp = os.path.join( |
| 133 | + imgs_dir, f"{os.path.splitext(os.path.basename(fp))[0]}_{num_format.format(i)}.{args.format}") |
| 134 | + # Check if the image already exists |
| 135 | + if os.path.exists(ofp) and not args.force: |
| 136 | + pbar.update() |
| 137 | + continue |
| 138 | + p.apply_async(slice_to_img, args=(args, chunk, x, y, bitdepth, image_bitdepth, |
| 139 | + old_min, old_max, new_min, new_max, ofp), callback=update) |
| 140 | + p.close() |
| 141 | + p.join() |
| 142 | + pbar.close() |
| 143 | + pbar = None |
| 144 | + |
140 | 145 |
|
141 | 146 | def main(args): |
142 | | - start_time = time() |
143 | | - |
144 | | - # Collect all volumes and validate their metadata |
145 | | - try: |
146 | | - # Gather all files |
147 | | - args.files = [] |
148 | | - for p in args.path: |
149 | | - for root, dirs, files in os.walk(p): |
150 | | - for filename in files: |
151 | | - args.files.append(os.path.join(root, filename)) |
152 | | - |
153 | | - # Append any loose, explicitly defined paths to .RAW files |
154 | | - args.files.extend([ f for f in args.path if f.endswith('.raw') ]) |
155 | | - |
156 | | - # Get all RAW files |
157 | | - args.files = [ f for f in args.files if f.endswith('.raw') ] |
158 | | - logging.debug(f"All files: {args.files}") |
159 | | - args.files = list(set(args.files)) # remove duplicates |
160 | | - logging.info(f"Found {len(args.files)} volume(s).") |
161 | | - logging.debug(f"Unique files: {args.files}") |
162 | | - |
163 | | - # Validate that a DAT file exists for each volume |
164 | | - for fp in args.files: |
165 | | - dat_fp = f"{os.path.splitext(fp)[0]}.dat" # .DAT filepath |
166 | | - logging.debug(f"Validating DAT file: '{dat_fp}'") |
167 | | - # Try to extract the dimensions to make sure that the file exists |
168 | | - dat.read(dat_fp) |
169 | | - except Exception as err: |
170 | | - logging.error(err) |
171 | | - else: |
172 | | - # For each provided volume... |
173 | | - pbar = tqdm(total = len(args.files), desc=f"Overall progress") |
174 | | - for fp in args.files: |
175 | | - logging.debug(f"Processing '{fp}'") |
176 | | - # Extract slices for all volumes in provided folder |
177 | | - extract_slices(args, fp) |
178 | | - pbar.update() |
179 | | - pbar.close() |
180 | | - |
181 | | - logging.debug(f'Total execution time: {time() - start_time} seconds') |
| 147 | + start_time = time() |
| 148 | + |
| 149 | + # Collect all volumes and validate their metadata |
| 150 | + try: |
| 151 | + # Gather all files |
| 152 | + args.files = [] |
| 153 | + for p in args.path: |
| 154 | + for root, dirs, files in os.walk(p): |
| 155 | + for filename in files: |
| 156 | + args.files.append(os.path.join(root, filename)) |
| 157 | + |
| 158 | + # Append any loose, explicitly defined paths to .RAW files |
| 159 | + args.files.extend([f for f in args.path if f.endswith('.raw')]) |
| 160 | + |
| 161 | + # Get all RAW files |
| 162 | + args.files = [f for f in args.files if f.endswith('.raw')] |
| 163 | + logging.debug(f"All files: {args.files}") |
| 164 | + args.files = list(set(args.files)) # remove duplicates |
| 165 | + logging.info(f"Found {len(args.files)} volume(s).") |
| 166 | + logging.debug(f"Unique files: {args.files}") |
| 167 | + |
| 168 | + # Validate that a DAT file exists for each volume |
| 169 | + for fp in args.files: |
| 170 | + dat_fp = f"{os.path.splitext(fp)[0]}.dat" # .DAT filepath |
| 171 | + logging.debug(f"Validating DAT file: '{dat_fp}'") |
| 172 | + # Try to extract the dimensions to make sure that the file exists |
| 173 | + dat.read(dat_fp) |
| 174 | + except Exception as err: |
| 175 | + logging.error(err) |
| 176 | + else: |
| 177 | + # For each provided volume... |
| 178 | + pbar = tqdm(total=len(args.files), desc=f"Overall progress") |
| 179 | + for fp in args.files: |
| 180 | + logging.debug(f"Processing '{fp}'") |
| 181 | + # Extract slices for all volumes in provided folder |
| 182 | + extract_slices(args, fp) |
| 183 | + pbar.update() |
| 184 | + pbar.close() |
| 185 | + |
| 186 | + logging.debug(f'Total execution time: {time() - start_time} seconds') |
0 commit comments