spacr 0.2.3__py3-none-any.whl → 0.2.31__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.
- spacr/app_annotate.py +3 -4
- spacr/core.py +58 -229
- spacr/gui_core.py +26 -20
- spacr/gui_elements.py +93 -11
- spacr/gui_utils.py +32 -0
- spacr/io.py +121 -37
- spacr/measure.py +6 -8
- spacr/settings.py +1 -4
- spacr/utils.py +55 -0
- {spacr-0.2.3.dist-info → spacr-0.2.31.dist-info}/METADATA +1 -1
- {spacr-0.2.3.dist-info → spacr-0.2.31.dist-info}/RECORD +15 -15
- {spacr-0.2.3.dist-info → spacr-0.2.31.dist-info}/LICENSE +0 -0
- {spacr-0.2.3.dist-info → spacr-0.2.31.dist-info}/WHEEL +0 -0
- {spacr-0.2.3.dist-info → spacr-0.2.31.dist-info}/entry_points.txt +0 -0
- {spacr-0.2.3.dist-info → spacr-0.2.31.dist-info}/top_level.txt +0 -0
spacr/gui_elements.py
CHANGED
@@ -3,7 +3,7 @@ import tkinter as tk
|
|
3
3
|
from tkinter import ttk
|
4
4
|
import tkinter.font as tkFont
|
5
5
|
from queue import Queue
|
6
|
-
from tkinter import Label
|
6
|
+
from tkinter import Label, Frame, Button
|
7
7
|
import numpy as np
|
8
8
|
from PIL import Image, ImageOps, ImageTk
|
9
9
|
from concurrent.futures import ThreadPoolExecutor
|
@@ -368,9 +368,11 @@ class spacrProgressBar(ttk.Progressbar):
|
|
368
368
|
|
369
369
|
# Get the style colors
|
370
370
|
style_out = set_dark_style(ttk.Style())
|
371
|
-
|
371
|
+
|
372
|
+
self.fg_color = style_out['fg_color']
|
372
373
|
self.bg_color = style_out['bg_color']
|
373
374
|
self.active_color = style_out['active_color']
|
375
|
+
self.inactive_color = style_out['inactive_color']
|
374
376
|
|
375
377
|
# Configure the style for the progress bar
|
376
378
|
self.style = ttk.Style()
|
@@ -387,6 +389,29 @@ class spacrProgressBar(ttk.Progressbar):
|
|
387
389
|
# Set initial value to 0
|
388
390
|
self['value'] = 0
|
389
391
|
|
392
|
+
# Create the progress label
|
393
|
+
self.progress_label = tk.Label(parent, text="Processing: 0/0", anchor='w', justify='left', bg=self.inactive_color, fg=self.fg_color)
|
394
|
+
self.progress_label.grid(row=1, column=0, columnspan=2, pady=5, padx=5, sticky='ew')
|
395
|
+
|
396
|
+
# Initialize attributes for time and operation
|
397
|
+
self.operation_type = None
|
398
|
+
self.time_image = None
|
399
|
+
self.time_batch = None
|
400
|
+
self.time_left = None
|
401
|
+
|
402
|
+
def update_label(self):
|
403
|
+
# Update the progress label with current progress and additional info
|
404
|
+
label_text = f"Processing: {self['value']}/{self['maximum']}"
|
405
|
+
if self.operation_type:
|
406
|
+
label_text += f", {self.operation_type}"
|
407
|
+
if self.time_image:
|
408
|
+
label_text += f", Time/image: {self.time_image:.3f} sec"
|
409
|
+
if self.time_batch:
|
410
|
+
label_text += f", Time/batch: {self.time_batch:.3f} sec"
|
411
|
+
if self.time_left:
|
412
|
+
label_text += f", Time_left: {self.time_left:.3f} min"
|
413
|
+
self.progress_label.config(text=label_text)
|
414
|
+
|
390
415
|
class spacrFrame(ttk.Frame):
|
391
416
|
def __init__(self, container, width=None, *args, bg='black', **kwargs):
|
392
417
|
super().__init__(container, *args, **kwargs)
|
@@ -1561,14 +1586,19 @@ class ModifyMaskApp:
|
|
1561
1586
|
self.update_display()
|
1562
1587
|
|
1563
1588
|
class AnnotateApp:
|
1564
|
-
def __init__(self, root, db_path, src, image_type=None, channels=None,
|
1589
|
+
def __init__(self, root, db_path, src, image_type=None, channels=None, image_size=200, annotation_column='annotate', normalize=False, percentiles=(1, 99), measurement=None, threshold=None):
|
1565
1590
|
self.root = root
|
1566
1591
|
self.db_path = db_path
|
1567
1592
|
self.src = src
|
1568
1593
|
self.index = 0
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1594
|
+
|
1595
|
+
if isinstance(image_size, list):
|
1596
|
+
self.image_size = (int(image_size[0]), int(image_size[0]))
|
1597
|
+
elif isinstance(image_size, int):
|
1598
|
+
self.image_size = (image_size, image_size)
|
1599
|
+
else:
|
1600
|
+
raise ValueError("Invalid image size")
|
1601
|
+
|
1572
1602
|
self.annotation_column = annotation_column
|
1573
1603
|
self.image_type = image_type
|
1574
1604
|
self.channels = channels
|
@@ -1580,22 +1610,72 @@ class AnnotateApp:
|
|
1580
1610
|
self.adjusted_to_original_paths = {}
|
1581
1611
|
self.terminate = False
|
1582
1612
|
self.update_queue = Queue()
|
1583
|
-
self.status_label = Label(self.root, text="", font=("Arial", 12))
|
1584
|
-
self.status_label.grid(row=self.grid_rows + 1, column=0, columnspan=self.grid_cols)
|
1585
1613
|
self.measurement = measurement
|
1586
1614
|
self.threshold = threshold
|
1587
1615
|
|
1616
|
+
print('self.image_size', self.image_size)
|
1617
|
+
|
1618
|
+
style_out = set_dark_style(ttk.Style())
|
1619
|
+
self.root.configure(bg=style_out['inactive_color'])
|
1620
|
+
|
1588
1621
|
self.filtered_paths_annotations = []
|
1589
1622
|
self.prefilter_paths_annotations()
|
1590
1623
|
|
1591
1624
|
self.db_update_thread = threading.Thread(target=self.update_database_worker)
|
1592
1625
|
self.db_update_thread.start()
|
1593
1626
|
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1627
|
+
# Set the initial window size and make it fit the screen size
|
1628
|
+
self.root.geometry(f"{self.root.winfo_screenwidth()}x{self.root.winfo_screenheight()}")
|
1629
|
+
self.root.update_idletasks()
|
1630
|
+
|
1631
|
+
# Create the status label
|
1632
|
+
self.status_label = Label(root, text="", font=("Arial", 12), bg=self.root.cget('bg'))
|
1633
|
+
self.status_label.grid(row=2, column=0, padx=10, pady=10, sticky="w")
|
1634
|
+
|
1635
|
+
# Place the buttons at the bottom right
|
1636
|
+
self.button_frame = Frame(root, bg=self.root.cget('bg'))
|
1637
|
+
self.button_frame.grid(row=2, column=1, padx=10, pady=10, sticky="se")
|
1638
|
+
|
1639
|
+
self.next_button = Button(self.button_frame, text="Next", command=self.next_page, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
|
1640
|
+
self.next_button.pack(side="right", padx=5)
|
1641
|
+
|
1642
|
+
self.previous_button = Button(self.button_frame, text="Back", command=self.previous_page, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
|
1643
|
+
self.previous_button.pack(side="right", padx=5)
|
1644
|
+
|
1645
|
+
self.exit_button = Button(self.button_frame, text="Exit", command=self.shutdown, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
|
1646
|
+
self.exit_button.pack(side="right", padx=5)
|
1647
|
+
|
1648
|
+
# Calculate grid rows and columns based on the root window size and image size
|
1649
|
+
self.calculate_grid_dimensions()
|
1650
|
+
|
1651
|
+
# Create a frame to hold the image grid
|
1652
|
+
self.grid_frame = Frame(root, bg=self.root.cget('bg'))
|
1653
|
+
self.grid_frame.grid(row=0, column=0, columnspan=2, padx=0, pady=0, sticky="nsew")
|
1654
|
+
|
1655
|
+
for i in range(self.grid_rows * self.grid_cols):
|
1656
|
+
label = Label(self.grid_frame, bg=self.root.cget('bg'))
|
1657
|
+
label.grid(row=i // self.grid_cols, column=i % self.grid_cols, padx=2, pady=2, sticky="nsew")
|
1597
1658
|
self.labels.append(label)
|
1598
1659
|
|
1660
|
+
# Make the grid frame resize with the window
|
1661
|
+
self.root.grid_rowconfigure(0, weight=1)
|
1662
|
+
self.root.grid_columnconfigure(0, weight=1)
|
1663
|
+
self.root.grid_columnconfigure(1, weight=1)
|
1664
|
+
|
1665
|
+
self.grid_frame.grid_rowconfigure(0, weight=1)
|
1666
|
+
self.grid_frame.grid_columnconfigure(0, weight=1)
|
1667
|
+
|
1668
|
+
def calculate_grid_dimensions(self):
|
1669
|
+
window_width = self.root.winfo_width()
|
1670
|
+
window_height = self.root.winfo_height()
|
1671
|
+
|
1672
|
+
self.grid_cols = window_width // (self.image_size[0] + 4)
|
1673
|
+
self.grid_rows = (window_height - self.button_frame.winfo_height() - 4) // (self.image_size[1] + 4)
|
1674
|
+
|
1675
|
+
# Update to make sure grid_rows and grid_cols are at least 1
|
1676
|
+
self.grid_cols = max(1, self.grid_cols)
|
1677
|
+
self.grid_rows = max(1, self.grid_rows)
|
1678
|
+
|
1599
1679
|
def prefilter_paths_annotations(self):
|
1600
1680
|
from .io import _read_and_join_tables
|
1601
1681
|
from .utils import is_list_of_lists
|
@@ -1849,6 +1929,7 @@ class AnnotateApp:
|
|
1849
1929
|
self.update_queue.put(self.pending_updates.copy())
|
1850
1930
|
self.pending_updates.clear()
|
1851
1931
|
self.index += self.grid_rows * self.grid_cols
|
1932
|
+
self.prefilter_paths_annotations() # Re-fetch annotations from the database
|
1852
1933
|
self.load_images()
|
1853
1934
|
|
1854
1935
|
def previous_page(self):
|
@@ -1858,6 +1939,7 @@ class AnnotateApp:
|
|
1858
1939
|
self.index -= self.grid_rows * self.grid_cols
|
1859
1940
|
if self.index < 0:
|
1860
1941
|
self.index = 0
|
1942
|
+
self.prefilter_paths_annotations() # Re-fetch annotations from the database
|
1861
1943
|
self.load_images()
|
1862
1944
|
|
1863
1945
|
def shutdown(self):
|
spacr/gui_utils.py
CHANGED
@@ -318,3 +318,35 @@ def annotate_with_image_refs(settings, root, shutdown_callback):
|
|
318
318
|
# Call load_images after setting up the root window
|
319
319
|
app.load_images()
|
320
320
|
|
321
|
+
def annotate_with_image_refs(settings, root, shutdown_callback):
|
322
|
+
from .settings import set_annotate_default_settings
|
323
|
+
|
324
|
+
settings = set_annotate_default_settings(settings)
|
325
|
+
src = settings['src']
|
326
|
+
|
327
|
+
db = os.path.join(src, 'measurements/measurements.db')
|
328
|
+
conn = sqlite3.connect(db)
|
329
|
+
c = conn.cursor()
|
330
|
+
c.execute('PRAGMA table_info(png_list)')
|
331
|
+
cols = c.fetchall()
|
332
|
+
if settings['annotation_column'] not in [col[1] for col in cols]:
|
333
|
+
c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
|
334
|
+
conn.commit()
|
335
|
+
conn.close()
|
336
|
+
|
337
|
+
screen_width = root.winfo_screenwidth()
|
338
|
+
screen_height = root.winfo_screenheight()
|
339
|
+
root.geometry(f"{screen_width}x{screen_height}")
|
340
|
+
|
341
|
+
app = AnnotateApp(root, db, src, image_type=settings['image_type'], channels=settings['channels'], image_size=settings['img_size'], annotation_column=settings['annotation_column'], normalize=settings['normalize'], percentiles=settings['percentiles'], measurement=settings['measurement'], threshold=settings['threshold'])
|
342
|
+
|
343
|
+
# Set the canvas background to black
|
344
|
+
root.configure(bg='black')
|
345
|
+
|
346
|
+
# Store the shutdown function and next app details in the root
|
347
|
+
root.current_app_exit_func = lambda: [app.shutdown(), shutdown_callback()]
|
348
|
+
|
349
|
+
# Call load_images after setting up the root window
|
350
|
+
app.load_images()
|
351
|
+
|
352
|
+
|
spacr/io.py
CHANGED
@@ -583,7 +583,7 @@ def _rename_and_organize_image_files(src, regex, batch_size=100, pick_slice=Fals
|
|
583
583
|
None
|
584
584
|
"""
|
585
585
|
|
586
|
-
from .utils import _extract_filename_metadata
|
586
|
+
from .utils import _extract_filename_metadata, print_progress
|
587
587
|
|
588
588
|
regular_expression = re.compile(regex)
|
589
589
|
images_by_key = defaultdict(list)
|
@@ -591,10 +591,15 @@ def _rename_and_organize_image_files(src, regex, batch_size=100, pick_slice=Fals
|
|
591
591
|
if not os.path.exists(stack_path) or (os.path.isdir(stack_path) and len(os.listdir(stack_path)) == 0):
|
592
592
|
all_filenames = [filename for filename in os.listdir(src) if filename.endswith(img_format)]
|
593
593
|
print(f'All_files:{len(all_filenames)} in {src}')
|
594
|
+
time_ls = []
|
595
|
+
processed = 0
|
594
596
|
for i in range(0, len(all_filenames), batch_size):
|
597
|
+
start = time.time()
|
595
598
|
batch_filenames = all_filenames[i:i+batch_size]
|
599
|
+
processed += len(batch_filenames)
|
596
600
|
for filename in batch_filenames:
|
597
601
|
images_by_key = _extract_filename_metadata(batch_filenames, src, images_by_key, regular_expression, metadata_type, pick_slice, skip_mode)
|
602
|
+
|
598
603
|
if pick_slice:
|
599
604
|
for key in images_by_key:
|
600
605
|
plate, well, field, channel, mode = key
|
@@ -609,6 +614,14 @@ def _rename_and_organize_image_files(src, regex, batch_size=100, pick_slice=Fals
|
|
609
614
|
print(f'WARNING: A file with the same name already exists at location {output_filename}')
|
610
615
|
else:
|
611
616
|
mip_image.save(output_path)
|
617
|
+
|
618
|
+
stop = time.time()
|
619
|
+
duration = stop - start
|
620
|
+
time_ls.append(duration)
|
621
|
+
files_processed = processed
|
622
|
+
files_to_process = len(all_filenames)
|
623
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=batch_size, operation_type='Preprocessing filenames')
|
624
|
+
|
612
625
|
else:
|
613
626
|
for key, images in images_by_key.items():
|
614
627
|
mip = np.max(np.stack(images), axis=0)
|
@@ -623,6 +636,12 @@ def _rename_and_organize_image_files(src, regex, batch_size=100, pick_slice=Fals
|
|
623
636
|
print(f'WARNING: A file with the same name already exists at location {output_filename}')
|
624
637
|
else:
|
625
638
|
mip_image.save(output_path)
|
639
|
+
stop = time.time()
|
640
|
+
duration = stop - start
|
641
|
+
time_ls.append(duration)
|
642
|
+
files_processed = processed
|
643
|
+
files_to_process = len(all_filenames)
|
644
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=batch_size, operation_type='Preprocessing filenames')
|
626
645
|
|
627
646
|
images_by_key.clear()
|
628
647
|
|
@@ -826,6 +845,7 @@ def _merge_channels(src, plot=False):
|
|
826
845
|
"""
|
827
846
|
|
828
847
|
from .plot import plot_arrays
|
848
|
+
from .utils import print_progress
|
829
849
|
|
830
850
|
stack_dir = os.path.join(src, 'stack')
|
831
851
|
allowed_names = ['01', '02', '03', '04', '00', '1', '2', '3', '4', '0']
|
@@ -835,8 +855,7 @@ def _merge_channels(src, plot=False):
|
|
835
855
|
chan_dirs.sort()
|
836
856
|
|
837
857
|
print(f'List of folders in src: {chan_dirs}. Single channel folders.')
|
838
|
-
|
839
|
-
|
858
|
+
|
840
859
|
# Assuming chan_dirs[0] is not empty and exists, adjust according to your logic
|
841
860
|
first_dir_path = os.path.join(src, chan_dirs[0])
|
842
861
|
dir_files = os.listdir(first_dir_path)
|
@@ -847,14 +866,18 @@ def _merge_channels(src, plot=False):
|
|
847
866
|
print(f'Generated folder with merged arrays: {stack_dir}')
|
848
867
|
|
849
868
|
if _is_dir_empty(stack_dir):
|
850
|
-
|
869
|
+
time_ls = []
|
870
|
+
files_to_process = len(dir_files)
|
871
|
+
for i, file_name in enumerate(dir_files):
|
872
|
+
start_time = time.time()
|
851
873
|
full_file_path = os.path.join(first_dir_path, file_name)
|
852
874
|
if os.path.isfile(full_file_path):
|
853
875
|
_merge_file([os.path.join(src, d) for d in chan_dirs], stack_dir, file_name)
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
876
|
+
stop_time = time.time()
|
877
|
+
duration = stop_time - start_time
|
878
|
+
time_ls.append(duration)
|
879
|
+
files_processed = i + 1
|
880
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type='Merging channels into npy stacks')
|
858
881
|
|
859
882
|
if plot:
|
860
883
|
plot_arrays(os.path.join(src, 'stack'))
|
@@ -911,6 +934,7 @@ def _mip_all(src, include_first_chan=True):
|
|
911
934
|
|
912
935
|
#@log_function_call
|
913
936
|
def _concatenate_channel(src, channels, randomize=True, timelapse=False, batch_size=100):
|
937
|
+
from .utils import print_progress
|
914
938
|
"""
|
915
939
|
Concatenates channel data from multiple files and saves the concatenated data as numpy arrays.
|
916
940
|
|
@@ -926,6 +950,7 @@ def _concatenate_channel(src, channels, randomize=True, timelapse=False, batch_s
|
|
926
950
|
"""
|
927
951
|
channels = [item for item in channels if item is not None]
|
928
952
|
paths = []
|
953
|
+
time_ls = []
|
929
954
|
index = 0
|
930
955
|
channel_stack_loc = os.path.join(os.path.dirname(src), 'channel_stack')
|
931
956
|
os.makedirs(channel_stack_loc, exist_ok=True)
|
@@ -944,8 +969,13 @@ def _concatenate_channel(src, channels, randomize=True, timelapse=False, batch_s
|
|
944
969
|
array = np.take(array, channels, axis=2)
|
945
970
|
stack_region.append(array)
|
946
971
|
filenames_region.append(os.path.basename(path))
|
947
|
-
|
948
|
-
|
972
|
+
|
973
|
+
stop = time.time()
|
974
|
+
duration = stop - start
|
975
|
+
time_ls.append(duration)
|
976
|
+
files_processed = i+1
|
977
|
+
files_to_process = time_stack_path_lists
|
978
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=None, batch_size=None, operation_type="Concatinating")
|
949
979
|
stack = np.stack(stack_region)
|
950
980
|
save_loc = os.path.join(channel_stack_loc, f'{name}.npz')
|
951
981
|
np.savez(save_loc, data=stack, filenames=filenames_region)
|
@@ -964,23 +994,24 @@ def _concatenate_channel(src, channels, randomize=True, timelapse=False, batch_s
|
|
964
994
|
nr_files = len(paths)
|
965
995
|
batch_index = 0 # Added this to name the output files
|
966
996
|
stack_ls = []
|
967
|
-
filenames_batch = []
|
997
|
+
filenames_batch = []
|
968
998
|
for i, path in enumerate(paths):
|
999
|
+
start = time.time()
|
969
1000
|
array = np.load(path)
|
970
1001
|
array = np.take(array, channels, axis=2)
|
971
1002
|
stack_ls.append(array)
|
972
1003
|
filenames_batch.append(os.path.basename(path)) # store the filename
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
1004
|
+
stop = time.time()
|
1005
|
+
duration = stop - start
|
1006
|
+
time_ls.append(duration)
|
1007
|
+
files_processed = i+1
|
1008
|
+
files_to_process = nr_files
|
1009
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=None, batch_size=None, operation_type="Concatinating")
|
977
1010
|
if (i+1) % batch_size == 0 or i+1 == nr_files:
|
978
1011
|
unique_shapes = {arr.shape[:-1] for arr in stack_ls}
|
979
1012
|
if len(unique_shapes) > 1:
|
980
1013
|
max_dims = np.max(np.array(list(unique_shapes)), axis=0)
|
981
|
-
#clear_output(wait=True)
|
982
1014
|
print(f'Warning: arrays with multiple shapes found in batch {i+1}. Padding arrays to max X,Y dimentions {max_dims}')
|
983
|
-
#print(f'Warning: arrays with multiple shapes found in batch {i+1}. Padding arrays to max X,Y dimentions {max_dims}', end='\r', flush=True)
|
984
1015
|
padded_stack_ls = []
|
985
1016
|
for arr in stack_ls:
|
986
1017
|
pad_width = [(0, max_dim - dim) for max_dim, dim in zip(max_dims, arr.shape[:-1])]
|
@@ -1002,6 +1033,7 @@ def _concatenate_channel(src, channels, randomize=True, timelapse=False, batch_s
|
|
1002
1033
|
|
1003
1034
|
def _normalize_img_batch(stack, channels, save_dtype, settings):
|
1004
1035
|
|
1036
|
+
from .utils import print_progress
|
1005
1037
|
"""
|
1006
1038
|
Normalize the stack of images.
|
1007
1039
|
|
@@ -1018,7 +1050,9 @@ def _normalize_img_batch(stack, channels, save_dtype, settings):
|
|
1018
1050
|
normalized_stack = np.zeros_like(stack, dtype=np.float32)
|
1019
1051
|
|
1020
1052
|
#for channel in range(stack.shape[-1]):
|
1021
|
-
|
1053
|
+
time_ls = []
|
1054
|
+
for i, channel in enumerate(channels):
|
1055
|
+
start = time.time()
|
1022
1056
|
if channel == settings['nucleus_channel']:
|
1023
1057
|
background = settings['nucleus_background']
|
1024
1058
|
signal_threshold = settings['nucleus_Signal_to_noise']*settings['nucleus_background']
|
@@ -1065,10 +1099,18 @@ def _normalize_img_batch(stack, channels, save_dtype, settings):
|
|
1065
1099
|
arr_2d_normalized = exposure.rescale_intensity(arr_2d, in_range=(global_lower, global_upper), out_range=(0, 1))
|
1066
1100
|
normalized_stack[array_index, :, :, channel] = arr_2d_normalized
|
1067
1101
|
|
1102
|
+
stop = time.time()
|
1103
|
+
duration = stop - start
|
1104
|
+
time_ls.append(duration)
|
1105
|
+
files_processed = i+1
|
1106
|
+
files_to_process = len(channels)
|
1107
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=None, batch_size=None, operation_type=f"Normalizing: Channel: {channel}")
|
1108
|
+
|
1068
1109
|
return normalized_stack.astype(save_dtype)
|
1069
1110
|
|
1070
1111
|
def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={}):
|
1071
|
-
|
1112
|
+
from .utils import print_progress
|
1113
|
+
|
1072
1114
|
"""
|
1073
1115
|
Concatenates and normalizes channel data from multiple files and saves the normalized data.
|
1074
1116
|
|
@@ -1088,10 +1130,11 @@ def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={})
|
|
1088
1130
|
Returns:
|
1089
1131
|
str: The directory path where the concatenated and normalized channel data is saved.
|
1090
1132
|
"""
|
1091
|
-
|
1133
|
+
|
1092
1134
|
channels = [item for item in channels if item is not None]
|
1093
1135
|
|
1094
1136
|
paths = []
|
1137
|
+
time_ls = []
|
1095
1138
|
output_fldr = os.path.join(os.path.dirname(src), 'norm_channel_stack')
|
1096
1139
|
os.makedirs(output_fldr, exist_ok=True)
|
1097
1140
|
|
@@ -1099,6 +1142,7 @@ def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={})
|
|
1099
1142
|
try:
|
1100
1143
|
time_stack_path_lists = _generate_time_lists(os.listdir(src))
|
1101
1144
|
for i, time_stack_list in enumerate(time_stack_path_lists):
|
1145
|
+
start = time.time()
|
1102
1146
|
stack_region = []
|
1103
1147
|
filenames_region = []
|
1104
1148
|
for idx, file in enumerate(time_stack_list):
|
@@ -1110,7 +1154,12 @@ def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={})
|
|
1110
1154
|
#array = np.take(array, channels, axis=2)
|
1111
1155
|
stack_region.append(array)
|
1112
1156
|
filenames_region.append(os.path.basename(path))
|
1113
|
-
|
1157
|
+
stop = time.time()
|
1158
|
+
duration = stop - start
|
1159
|
+
time_ls.append(duration)
|
1160
|
+
files_processed = i+1
|
1161
|
+
files_to_process = len(time_stack_path_lists)
|
1162
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=None, batch_size=None, operation_type="Concatinating")
|
1114
1163
|
stack = np.stack(stack_region)
|
1115
1164
|
|
1116
1165
|
normalized_stack = _normalize_img_batch(stack=stack,
|
@@ -1138,13 +1187,19 @@ def concatenate_and_normalize(src, channels, save_dtype=np.float32, settings={})
|
|
1138
1187
|
batch_index = 0
|
1139
1188
|
stack_ls = []
|
1140
1189
|
filenames_batch = []
|
1141
|
-
|
1190
|
+
time_ls = []
|
1142
1191
|
for i, path in enumerate(paths):
|
1192
|
+
start = time.time()
|
1143
1193
|
array = np.load(path)
|
1144
1194
|
#array = np.take(array, channels, axis=2)
|
1145
1195
|
stack_ls.append(array)
|
1146
1196
|
filenames_batch.append(os.path.basename(path))
|
1147
|
-
|
1197
|
+
stop = time.time()
|
1198
|
+
duration = stop - start
|
1199
|
+
time_ls.append(duration)
|
1200
|
+
files_processed = i+1
|
1201
|
+
files_to_process = nr_files
|
1202
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=None, batch_size=None, operation_type="Concatinating")
|
1148
1203
|
|
1149
1204
|
if (i + 1) % settings['batch_size'] == 0 or i + 1 == nr_files:
|
1150
1205
|
unique_shapes = {arr.shape[:-1] for arr in stack_ls}
|
@@ -1219,6 +1274,7 @@ def _get_lists_for_normalization(settings):
|
|
1219
1274
|
return backgrounds, signal_to_noise, signal_thresholds, remove_background
|
1220
1275
|
|
1221
1276
|
def _normalize_stack(src, backgrounds=[100, 100, 100], remove_backgrounds=[False, False, False], lower_percentile=2, save_dtype=np.float32, signal_to_noise=[5, 5, 5], signal_thresholds=[1000, 1000, 1000]):
|
1277
|
+
from .utils import print_progress
|
1222
1278
|
"""
|
1223
1279
|
Normalize the stack of images.
|
1224
1280
|
|
@@ -1266,10 +1322,11 @@ def _normalize_stack(src, backgrounds=[100, 100, 100], remove_backgrounds=[False
|
|
1266
1322
|
global_upper = np.percentile(non_zero_single_channel, upper_p)
|
1267
1323
|
if global_upper >= signal_threshold:
|
1268
1324
|
break
|
1269
|
-
|
1325
|
+
|
1270
1326
|
# Normalize the pixels in each image to the global percentiles and then dtype.
|
1271
1327
|
arr_2d_normalized = np.zeros_like(single_channel, dtype=single_channel.dtype)
|
1272
1328
|
signal_to_noise_ratio_ls = []
|
1329
|
+
time_ls = []
|
1273
1330
|
for array_index in range(single_channel.shape[0]):
|
1274
1331
|
start = time.time()
|
1275
1332
|
arr_2d = single_channel[array_index, :, :]
|
@@ -1291,8 +1348,15 @@ def _normalize_stack(src, backgrounds=[100, 100, 100], remove_backgrounds=[False
|
|
1291
1348
|
duration = (stop - start) * single_channel.shape[0]
|
1292
1349
|
time_ls.append(duration)
|
1293
1350
|
average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
|
1294
|
-
print(f'
|
1351
|
+
print(f'channels:{chan_index}/{stack.shape[-1] - 1}, arrays:{array_index + 1}/{single_channel.shape[0]}, Signal:{upper:.1f}, noise:{lower:.1f}, Signal-to-noise:{average_stnr:.1f}, Time/channel:{average_time:.2f}sec')
|
1295
1352
|
|
1353
|
+
stop = time.time()
|
1354
|
+
duration = stop - start
|
1355
|
+
time_ls.append(duration)
|
1356
|
+
files_processed = file_index + 1
|
1357
|
+
files_to_process = len(paths)
|
1358
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type="Normalizing")
|
1359
|
+
|
1296
1360
|
normalized_stack[:, :, :, channel] = arr_2d_normalized
|
1297
1361
|
|
1298
1362
|
save_loc = os.path.join(output_fldr, f'{name}_norm_stack.npz')
|
@@ -1303,6 +1367,7 @@ def _normalize_stack(src, backgrounds=[100, 100, 100], remove_backgrounds=[False
|
|
1303
1367
|
return print(f'Saved stacks: {output_fldr}')
|
1304
1368
|
|
1305
1369
|
def _normalize_timelapse(src, lower_percentile=2, save_dtype=np.float32):
|
1370
|
+
from .utils import print_progress
|
1306
1371
|
"""
|
1307
1372
|
Normalize the timelapse data by rescaling the intensity values based on percentiles.
|
1308
1373
|
|
@@ -1326,8 +1391,9 @@ def _normalize_timelapse(src, lower_percentile=2, save_dtype=np.float32):
|
|
1326
1391
|
|
1327
1392
|
for chan_index in range(stack.shape[-1]):
|
1328
1393
|
single_channel = stack[:, :, :, chan_index]
|
1329
|
-
|
1394
|
+
time_ls = []
|
1330
1395
|
for array_index in range(single_channel.shape[0]):
|
1396
|
+
start = time.time()
|
1331
1397
|
arr_2d = single_channel[array_index]
|
1332
1398
|
# Calculate the 1% and 98% percentiles for this specific image
|
1333
1399
|
q_low = np.percentile(arr_2d[arr_2d != 0], lower_percentile)
|
@@ -1337,7 +1403,14 @@ def _normalize_timelapse(src, lower_percentile=2, save_dtype=np.float32):
|
|
1337
1403
|
arr_2d_rescaled = exposure.rescale_intensity(arr_2d, in_range=(q_low, q_high), out_range='dtype')
|
1338
1404
|
normalized_stack[array_index, :, :, chan_index] = arr_2d_rescaled
|
1339
1405
|
|
1340
|
-
print(f'
|
1406
|
+
print(f'channels:{chan_index+1}/{stack.shape[-1]}, arrays:{array_index+1}/{single_channel.shape[0]}', end='\r')
|
1407
|
+
|
1408
|
+
stop = time.time()
|
1409
|
+
duration = stop - start
|
1410
|
+
time_ls.append(duration)
|
1411
|
+
files_processed = file_index+1
|
1412
|
+
files_to_process = len(paths)
|
1413
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type="Normalizing")
|
1341
1414
|
|
1342
1415
|
save_loc = os.path.join(output_fldr, f'{name}_norm_timelapse.npz')
|
1343
1416
|
np.savez(save_loc, data=normalized_stack, filenames=filenames)
|
@@ -1602,6 +1675,7 @@ def _get_avg_object_size(masks):
|
|
1602
1675
|
return 0 # Return 0 if no objects are found
|
1603
1676
|
|
1604
1677
|
def _save_figure(fig, src, text, dpi=300, i=1, all_folders=1):
|
1678
|
+
from .utils import print_progress
|
1605
1679
|
"""
|
1606
1680
|
Save a figure to a specified location.
|
1607
1681
|
|
@@ -1611,6 +1685,7 @@ def _save_figure(fig, src, text, dpi=300, i=1, all_folders=1):
|
|
1611
1685
|
text (str): The text to be included in the figure name.
|
1612
1686
|
dpi (int, optional): The resolution of the saved figure. Defaults to 300.
|
1613
1687
|
"""
|
1688
|
+
|
1614
1689
|
save_folder = os.path.dirname(src)
|
1615
1690
|
obj_type = os.path.basename(src)
|
1616
1691
|
name = os.path.basename(save_folder)
|
@@ -1619,10 +1694,11 @@ def _save_figure(fig, src, text, dpi=300, i=1, all_folders=1):
|
|
1619
1694
|
fig_name = f'{obj_type}_{name}_{text}.pdf'
|
1620
1695
|
save_location = os.path.join(save_folder, fig_name)
|
1621
1696
|
fig.savefig(save_location, bbox_inches='tight', dpi=dpi)
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1697
|
+
|
1698
|
+
files_processed = i
|
1699
|
+
files_to_process = all_folders
|
1700
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=None, batch_size=None, operation_type="Saving Figures")
|
1701
|
+
print(f'Saved single cell figure: {os.path.basename(save_location)}')
|
1626
1702
|
plt.close(fig)
|
1627
1703
|
del fig
|
1628
1704
|
gc.collect()
|
@@ -1842,6 +1918,9 @@ def _create_database(db_path):
|
|
1842
1918
|
conn.close()
|
1843
1919
|
|
1844
1920
|
def _load_and_concatenate_arrays(src, channels, cell_chann_dim, nucleus_chann_dim, pathogen_chann_dim):
|
1921
|
+
|
1922
|
+
from .utils import print_progress
|
1923
|
+
|
1845
1924
|
"""
|
1846
1925
|
Load and concatenate arrays from multiple folders.
|
1847
1926
|
|
@@ -1870,9 +1949,10 @@ def _load_and_concatenate_arrays(src, channels, cell_chann_dim, nucleus_chann_di
|
|
1870
1949
|
|
1871
1950
|
count=0
|
1872
1951
|
all_imgs = len(os.listdir(reference_folder))
|
1873
|
-
|
1952
|
+
time_ls = []
|
1874
1953
|
# Iterate through each file in the reference folder
|
1875
1954
|
for filename in os.listdir(reference_folder):
|
1955
|
+
start = time.time()
|
1876
1956
|
stack_ls = []
|
1877
1957
|
if filename.endswith('.npy'):
|
1878
1958
|
count += 1
|
@@ -1929,9 +2009,13 @@ def _load_and_concatenate_arrays(src, channels, cell_chann_dim, nucleus_chann_di
|
|
1929
2009
|
output_path = os.path.join(output_folder, filename)
|
1930
2010
|
np.save(output_path, stack)
|
1931
2011
|
|
1932
|
-
|
1933
|
-
|
1934
|
-
|
2012
|
+
stop = time.time()
|
2013
|
+
duration = stop - start
|
2014
|
+
time_ls.append(duration)
|
2015
|
+
files_processed = count
|
2016
|
+
files_to_process = all_imgs
|
2017
|
+
print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type="Merging Arrays")
|
2018
|
+
|
1935
2019
|
return
|
1936
2020
|
|
1937
2021
|
def _read_db(db_loc, tables):
|
@@ -2211,7 +2295,7 @@ def _save_model(model, model_type, results_df, dst, epoch, epochs, intermedeate_
|
|
2211
2295
|
|
2212
2296
|
def save_model_at_threshold(threshold, epoch, suffix=""):
|
2213
2297
|
percentile = str(threshold * 100)
|
2214
|
-
print(f'\rfound: {percentile}% accurate model'
|
2298
|
+
print(f'\rfound: {percentile}% accurate model')#, end='\r', flush=True)
|
2215
2299
|
torch.save(model, f'{dst}/{model_type}_epoch_{str(epoch)}{suffix}_acc_{percentile}_channels_{channels_str}.pth')
|
2216
2300
|
|
2217
2301
|
if epoch % 100 == 0 or epoch == epochs:
|
@@ -2545,5 +2629,5 @@ def generate_cellpose_train_test(src, test_split=0.1):
|
|
2545
2629
|
new_mask_path = os.path.join(dst_mask, filename)
|
2546
2630
|
shutil.copy(img_path, new_img_path)
|
2547
2631
|
shutil.copy(mask_path, new_mask_path)
|
2548
|
-
print(f'Copied {idx+1}/{len(ls)} images to {_type} set'
|
2632
|
+
print(f'Copied {idx+1}/{len(ls)} images to {_type} set')#, end='\r', flush=True)
|
2549
2633
|
|
spacr/measure.py
CHANGED
@@ -935,7 +935,7 @@ def measure_crop(settings):
|
|
935
935
|
from .io import _save_settings_to_db
|
936
936
|
from .timelapse import _timelapse_masks_to_gif, _scmovie
|
937
937
|
from .plot import _save_scimg_plot
|
938
|
-
from .utils import _list_endpoint_subdirectories, _generate_representative_images, measure_test_mode
|
938
|
+
from .utils import _list_endpoint_subdirectories, _generate_representative_images, measure_test_mode, print_progress
|
939
939
|
from .settings import get_measure_crop_settings
|
940
940
|
|
941
941
|
settings = get_measure_crop_settings(settings)
|
@@ -996,24 +996,20 @@ def measure_crop(settings):
|
|
996
996
|
return
|
997
997
|
|
998
998
|
_save_settings_to_db(settings)
|
999
|
-
|
1000
999
|
files = [f for f in os.listdir(settings['src']) if f.endswith('.npy')]
|
1001
1000
|
n_jobs = settings['n_jobs'] or mp.cpu_count()-4
|
1002
1001
|
print(f'using {n_jobs} cpu cores')
|
1003
|
-
|
1004
1002
|
with mp.Manager() as manager:
|
1005
1003
|
time_ls = manager.list()
|
1006
1004
|
with mp.Pool(n_jobs) as pool:
|
1007
1005
|
result = pool.starmap_async(_measure_crop_core, [(index, time_ls, file, settings) for index, file in enumerate(files)])
|
1008
1006
|
|
1009
1007
|
# Track progress in the main process
|
1010
|
-
while not result.ready():
|
1011
|
-
time.sleep(1)
|
1008
|
+
while not result.ready():
|
1009
|
+
time.sleep(1)
|
1012
1010
|
files_processed = len(time_ls)
|
1013
1011
|
files_to_process = len(files)
|
1014
|
-
|
1015
|
-
time_left = (((files_to_process-files_processed)*average_time)/n_jobs)/60
|
1016
|
-
print(f'Progress: {files_processed}/{files_to_process} Time/img {average_time:.3f}sec, Time Remaining {time_left:.3f} min.', end='\r', flush=True)
|
1012
|
+
print_progress(files_processed, files_to_process, n_jobs, time_ls=None)
|
1017
1013
|
result.get()
|
1018
1014
|
|
1019
1015
|
if settings['representative_images']:
|
@@ -1114,3 +1110,5 @@ def get_object_counts(src):
|
|
1114
1110
|
# Close the database connection
|
1115
1111
|
conn.close()
|
1116
1112
|
return grouped_df
|
1113
|
+
|
1114
|
+
|
spacr/settings.py
CHANGED
@@ -1143,10 +1143,7 @@ def set_annotate_default_settings(settings):
|
|
1143
1143
|
settings.setdefault('src', 'path')
|
1144
1144
|
settings.setdefault('image_type', 'cell_png')
|
1145
1145
|
settings.setdefault('channels', 'r,g,b')
|
1146
|
-
settings.setdefault('
|
1147
|
-
settings.setdefault('img_size', [200, 200])
|
1148
|
-
settings.setdefault('rows', 10)
|
1149
|
-
settings.setdefault('columns', 18)
|
1146
|
+
settings.setdefault('img_size', 200)
|
1150
1147
|
settings.setdefault('annotation_column', 'test')
|
1151
1148
|
settings.setdefault('normalize', 'False')
|
1152
1149
|
settings.setdefault('percentiles', [2, 98])
|