spacr 0.3.1__py3-none-any.whl → 0.3.22__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.
Files changed (41) hide show
  1. spacr/__init__.py +19 -3
  2. spacr/cellpose.py +311 -0
  3. spacr/core.py +245 -2494
  4. spacr/deep_spacr.py +316 -48
  5. spacr/gui.py +1 -0
  6. spacr/gui_core.py +74 -63
  7. spacr/gui_elements.py +110 -5
  8. spacr/gui_utils.py +346 -6
  9. spacr/io.py +680 -141
  10. spacr/logger.py +28 -9
  11. spacr/measure.py +107 -95
  12. spacr/mediar.py +0 -3
  13. spacr/ml.py +1051 -0
  14. spacr/openai.py +37 -0
  15. spacr/plot.py +707 -20
  16. spacr/resources/data/lopit.csv +3833 -0
  17. spacr/resources/data/toxoplasma_metadata.csv +8843 -0
  18. spacr/resources/icons/convert.png +0 -0
  19. spacr/resources/{models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model → icons/dna_matrix.mp4} +0 -0
  20. spacr/sequencing.py +241 -1311
  21. spacr/settings.py +134 -47
  22. spacr/sim.py +0 -2
  23. spacr/submodules.py +349 -0
  24. spacr/timelapse.py +0 -2
  25. spacr/toxo.py +238 -0
  26. spacr/utils.py +419 -180
  27. {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/METADATA +31 -22
  28. {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/RECORD +32 -33
  29. spacr/chris.py +0 -50
  30. spacr/graph_learning.py +0 -340
  31. spacr/resources/MEDIAR/.git +0 -1
  32. spacr/resources/MEDIAR_weights/.DS_Store +0 -0
  33. spacr/resources/icons/.DS_Store +0 -0
  34. spacr/resources/icons/spacr_logo_rotation.gif +0 -0
  35. spacr/resources/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model_settings.csv +0 -23
  36. spacr/resources/models/cp/toxo_pv_lumen.CP_model +0 -0
  37. spacr/sim_app.py +0 -0
  38. {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/LICENSE +0 -0
  39. {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/WHEEL +0 -0
  40. {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/entry_points.txt +0 -0
  41. {spacr-0.3.1.dist-info → spacr-0.3.22.dist-info}/top_level.txt +0 -0
spacr/gui_utils.py CHANGED
@@ -1,4 +1,4 @@
1
- import os, io, sys, ast, ctypes, ast, sqlite3, requests, time, traceback, torch
1
+ import os, io, sys, ast, ctypes, ast, sqlite3, requests, time, traceback, torch, cv2
2
2
  import tkinter as tk
3
3
  from tkinter import ttk
4
4
  import matplotlib
@@ -6,6 +6,8 @@ import matplotlib.pyplot as plt
6
6
  matplotlib.use('Agg')
7
7
  from huggingface_hub import list_repo_files
8
8
  import psutil
9
+ from PIL import Image, ImageTk
10
+ from screeninfo import get_monitors
9
11
 
10
12
  from .gui_elements import AnnotateApp, spacrEntry, spacrCheck, spacrCombo
11
13
 
@@ -394,6 +396,7 @@ def convert_settings_dict_for_gui(settings):
394
396
  'train_channels': ('combo', ["['r','g','b']", "['r','g']", "['r','b']", "['g','b']", "['r']", "['g']", "['b']"], "['r','g','b']"),
395
397
  'channel_dims': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
396
398
  'dataset_mode': ('combo', ['annotation', 'metadata', 'recruitment'], 'metadata'),
399
+ 'cov_type': ('combo', ['HC0', 'HC1', 'HC2', 'HC3', None], None),
397
400
  'cell_mask_dim': ('combo', chans, None),
398
401
  'cell_chann_dim': ('combo', chans, None),
399
402
  'nucleus_mask_dim': ('combo', chans, None),
@@ -486,12 +489,16 @@ def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imp
486
489
  def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
487
490
 
488
491
  from .gui_utils import process_stdout_stderr
489
- from .core import generate_image_umap, preprocess_generate_masks, generate_ml_scores, identify_masks_finetune, check_cellpose_models, analyze_recruitment, train_cellpose, analyze_plaques, compare_cellpose_masks, generate_dataset, apply_model_to_tar
490
- from .io import generate_cellpose_train_test
492
+ from .core import generate_image_umap, preprocess_generate_masks
493
+ from .cellpose import identify_masks_finetune, check_cellpose_models, compare_cellpose_masks
494
+ from .submodules import analyze_recruitment
495
+ from .ml import generate_ml_scores, perform_regression
496
+ from .submodules import train_cellpose, analyze_plaques
497
+ from .io import process_non_tif_non_2D_images, generate_cellpose_train_test, generate_dataset
491
498
  from .measure import measure_crop
492
499
  from .sim import run_multiple_simulations
493
- from .deep_spacr import deep_spacr
494
- from .sequencing import generate_barecode_mapping, perform_regression
500
+ from .deep_spacr import deep_spacr, apply_model_to_tar
501
+ from .sequencing import generate_barecode_mapping
495
502
  process_stdout_stderr(q)
496
503
 
497
504
  print(f'run_function_gui settings_type: {settings_type}')
@@ -535,6 +542,9 @@ def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
535
542
  elif settings_type == 'analyze_plaques':
536
543
  function = analyze_plaques
537
544
  imports = 1
545
+ elif settings_type == 'convert':
546
+ function = process_non_tif_non_2D_images
547
+ imports = 1
538
548
  else:
539
549
  raise ValueError(f"Invalid settings type: {settings_type}")
540
550
  try:
@@ -697,4 +707,334 @@ def download_dataset(q, repo_id, subfolder, local_dir=None, retries=5, delay=5):
697
707
 
698
708
  def ensure_after_tasks(frame):
699
709
  if not hasattr(frame, 'after_tasks'):
700
- frame.after_tasks = []
710
+ frame.after_tasks = []
711
+
712
+ def display_gif_in_plot_frame_v1(gif_path, parent_frame):
713
+ """Display and zoom a GIF to fill the entire parent_frame, maintaining aspect ratio, with lazy resizing and caching."""
714
+ # Clear parent_frame if it contains any previous widgets
715
+ for widget in parent_frame.winfo_children():
716
+ widget.destroy()
717
+
718
+ # Load the GIF
719
+ gif = Image.open(gif_path)
720
+
721
+ # Get the aspect ratio of the GIF
722
+ gif_width, gif_height = gif.size
723
+ gif_aspect_ratio = gif_width / gif_height
724
+
725
+ # Create a label to display the GIF and configure it to fill the parent_frame
726
+ label = tk.Label(parent_frame, bg="black")
727
+ label.grid(row=0, column=0, sticky="nsew") # Expands in all directions (north, south, east, west)
728
+
729
+ # Configure parent_frame to stretch the label to fill available space
730
+ parent_frame.grid_rowconfigure(0, weight=1)
731
+ parent_frame.grid_columnconfigure(0, weight=1)
732
+
733
+ # Cache for storing resized frames (lazily filled)
734
+ resized_frames_cache = {}
735
+
736
+ # Last frame dimensions
737
+ last_frame_width = 0
738
+ last_frame_height = 0
739
+
740
+ def resize_and_crop_frame(frame_idx, frame_width, frame_height):
741
+ """Resize and crop the current frame of the GIF to fit the parent_frame while maintaining the aspect ratio."""
742
+ # If the frame is already cached at the current size, return it
743
+ if (frame_idx, frame_width, frame_height) in resized_frames_cache:
744
+ return resized_frames_cache[(frame_idx, frame_width, frame_height)]
745
+
746
+ # Calculate the scaling factor to zoom in on the GIF
747
+ scale_factor = max(frame_width / gif_width, frame_height / gif_height)
748
+
749
+ # Calculate new dimensions while maintaining the aspect ratio
750
+ new_width = int(gif_width * scale_factor)
751
+ new_height = int(gif_height * scale_factor)
752
+
753
+ # Resize the GIF to fit the frame
754
+ gif.seek(frame_idx)
755
+ resized_gif = gif.copy().resize((new_width, new_height), Image.Resampling.LANCZOS)
756
+
757
+ # Calculate the cropping box to center the resized GIF in the frame
758
+ crop_left = (new_width - frame_width) // 2
759
+ crop_top = (new_height - frame_height) // 2
760
+ crop_right = crop_left + frame_width
761
+ crop_bottom = crop_top + frame_height
762
+
763
+ # Crop the resized GIF to exactly fit the frame
764
+ cropped_gif = resized_gif.crop((crop_left, crop_top, crop_right, crop_bottom))
765
+
766
+ # Convert the cropped frame to a Tkinter-compatible format
767
+ frame_image = ImageTk.PhotoImage(cropped_gif)
768
+
769
+ # Cache the resized frame
770
+ resized_frames_cache[(frame_idx, frame_width, frame_height)] = frame_image
771
+
772
+ return frame_image
773
+
774
+ def update_frame(frame_idx):
775
+ """Update the GIF frame using lazy resizing and caching."""
776
+ # Get the current size of the parent_frame
777
+ frame_width = parent_frame.winfo_width()
778
+ frame_height = parent_frame.winfo_height()
779
+
780
+ # Only resize if the frame size has changed
781
+ nonlocal last_frame_width, last_frame_height
782
+ if frame_width != last_frame_width or frame_height != last_frame_height:
783
+ last_frame_width, last_frame_height = frame_width, frame_height
784
+
785
+ # Get the resized and cropped frame image
786
+ frame_image = resize_and_crop_frame(frame_idx, frame_width, frame_height)
787
+ label.config(image=frame_image)
788
+ label.image = frame_image # Keep a reference to avoid garbage collection
789
+
790
+ # Move to the next frame, or loop back to the beginning
791
+ next_frame_idx = (frame_idx + 1) % gif.n_frames
792
+ parent_frame.after(gif.info['duration'], update_frame, next_frame_idx)
793
+
794
+ # Start the GIF animation from frame 0
795
+ update_frame(0)
796
+
797
+ def display_gif_in_plot_frame(gif_path, parent_frame):
798
+ """Display and zoom a GIF to fill the entire parent_frame, maintaining aspect ratio, with lazy resizing and caching."""
799
+ # Clear parent_frame if it contains any previous widgets
800
+ for widget in parent_frame.winfo_children():
801
+ widget.destroy()
802
+
803
+ # Load the GIF
804
+ gif = Image.open(gif_path)
805
+
806
+ # Get the aspect ratio of the GIF
807
+ gif_width, gif_height = gif.size
808
+ gif_aspect_ratio = gif_width / gif_height
809
+
810
+ # Create a label to display the GIF and configure it to fill the parent_frame
811
+ label = tk.Label(parent_frame, bg="black")
812
+ label.grid(row=0, column=0, sticky="nsew") # Expands in all directions (north, south, east, west)
813
+
814
+ # Configure parent_frame to stretch the label to fill available space
815
+ parent_frame.grid_rowconfigure(0, weight=1)
816
+ parent_frame.grid_columnconfigure(0, weight=1)
817
+
818
+ # Cache for storing resized frames (lazily filled)
819
+ resized_frames_cache = {}
820
+
821
+ # Store last frame size and aspect ratio
822
+ last_frame_width = 0
823
+ last_frame_height = 0
824
+
825
+ def resize_and_crop_frame(frame_idx, frame_width, frame_height):
826
+ """Resize and crop the current frame of the GIF to fit the parent_frame while maintaining the aspect ratio."""
827
+ # If the frame is already cached at the current size, return it
828
+ if (frame_idx, frame_width, frame_height) in resized_frames_cache:
829
+ return resized_frames_cache[(frame_idx, frame_width, frame_height)]
830
+
831
+ # Calculate the scaling factor to zoom in on the GIF
832
+ scale_factor = max(frame_width / gif_width, frame_height / gif_height)
833
+
834
+ # Calculate new dimensions while maintaining the aspect ratio
835
+ new_width = int(gif_width * scale_factor)
836
+ new_height = int(gif_height * scale_factor)
837
+
838
+ # Resize the GIF to fit the frame using NEAREST for faster resizing
839
+ gif.seek(frame_idx)
840
+ resized_gif = gif.copy().resize((new_width, new_height), Image.Resampling.NEAREST if scale_factor > 2 else Image.Resampling.LANCZOS)
841
+
842
+ # Calculate the cropping box to center the resized GIF in the frame
843
+ crop_left = (new_width - frame_width) // 2
844
+ crop_top = (new_height - frame_height) // 2
845
+ crop_right = crop_left + frame_width
846
+ crop_bottom = crop_top + frame_height
847
+
848
+ # Crop the resized GIF to exactly fit the frame
849
+ cropped_gif = resized_gif.crop((crop_left, crop_top, crop_right, crop_bottom))
850
+
851
+ # Convert the cropped frame to a Tkinter-compatible format
852
+ frame_image = ImageTk.PhotoImage(cropped_gif)
853
+
854
+ # Cache the resized frame
855
+ resized_frames_cache[(frame_idx, frame_width, frame_height)] = frame_image
856
+
857
+ return frame_image
858
+
859
+ def update_frame(frame_idx):
860
+ """Update the GIF frame using lazy resizing and caching."""
861
+ # Get the current size of the parent_frame
862
+ frame_width = parent_frame.winfo_width()
863
+ frame_height = parent_frame.winfo_height()
864
+
865
+ # Only resize if the frame size has changed
866
+ nonlocal last_frame_width, last_frame_height
867
+ if frame_width != last_frame_width or frame_height != last_frame_height:
868
+ last_frame_width, last_frame_height = frame_width, frame_height
869
+
870
+ # Get the resized and cropped frame image
871
+ frame_image = resize_and_crop_frame(frame_idx, frame_width, frame_height)
872
+ label.config(image=frame_image)
873
+ label.image = frame_image # Keep a reference to avoid garbage collection
874
+
875
+ # Move to the next frame, or loop back to the beginning
876
+ next_frame_idx = (frame_idx + 1) % gif.n_frames
877
+ parent_frame.after(gif.info['duration'], update_frame, next_frame_idx)
878
+
879
+ # Start the GIF animation from frame 0
880
+ update_frame(0)
881
+
882
+ def display_media_in_plot_frame(media_path, parent_frame):
883
+ """Display an MP4, AVI, or GIF and play it on repeat in the parent_frame, fully filling the frame while maintaining aspect ratio."""
884
+ # Clear parent_frame if it contains any previous widgets
885
+ for widget in parent_frame.winfo_children():
886
+ widget.destroy()
887
+
888
+ # Check file extension to decide between video (mp4/avi) or gif
889
+ file_extension = os.path.splitext(media_path)[1].lower()
890
+
891
+ if file_extension in ['.mp4', '.avi']:
892
+ # Handle video formats (mp4, avi) using OpenCV
893
+ video = cv2.VideoCapture(media_path)
894
+
895
+ # Create a label to display the video
896
+ label = tk.Label(parent_frame, bg="black")
897
+ label.grid(row=0, column=0, sticky="nsew")
898
+
899
+ # Configure the parent_frame to expand
900
+ parent_frame.grid_rowconfigure(0, weight=1)
901
+ parent_frame.grid_columnconfigure(0, weight=1)
902
+
903
+ def update_frame():
904
+ """Update function for playing video."""
905
+ ret, frame = video.read()
906
+ if ret:
907
+ # Get the frame dimensions
908
+ frame_height, frame_width, _ = frame.shape
909
+
910
+ # Get parent frame dimensions
911
+ parent_width = parent_frame.winfo_width()
912
+ parent_height = parent_frame.winfo_height()
913
+
914
+ # Ensure dimensions are greater than 0
915
+ if parent_width > 0 and parent_height > 0:
916
+ # Calculate the aspect ratio of the media
917
+ frame_aspect_ratio = frame_width / frame_height
918
+ parent_aspect_ratio = parent_width / parent_height
919
+
920
+ # Determine whether to scale based on width or height to cover the parent frame
921
+ if parent_aspect_ratio > frame_aspect_ratio:
922
+ # The parent frame is wider than the video aspect ratio
923
+ # Fit to width, crop height
924
+ new_width = parent_width
925
+ new_height = int(parent_width / frame_aspect_ratio)
926
+ else:
927
+ # The parent frame is taller than the video aspect ratio
928
+ # Fit to height, crop width
929
+ new_width = int(parent_height * frame_aspect_ratio)
930
+ new_height = parent_height
931
+
932
+ # Resize the frame to the new dimensions (cover the parent frame)
933
+ resized_frame = cv2.resize(frame, (new_width, new_height))
934
+
935
+ # Crop the frame to fit exactly within the parent frame
936
+ x_offset = (new_width - parent_width) // 2
937
+ y_offset = (new_height - parent_height) // 2
938
+ cropped_frame = resized_frame[y_offset:y_offset + parent_height, x_offset:x_offset + parent_width]
939
+
940
+ # Convert the frame to RGB (OpenCV uses BGR by default)
941
+ cropped_frame = cv2.cvtColor(cropped_frame, cv2.COLOR_BGR2RGB)
942
+
943
+ # Convert the frame to a Tkinter-compatible format
944
+ frame_image = ImageTk.PhotoImage(Image.fromarray(cropped_frame))
945
+
946
+ # Update the label with the new frame
947
+ label.config(image=frame_image)
948
+ label.image = frame_image # Keep a reference to avoid garbage collection
949
+
950
+ # Call update_frame again after a delay to match the video's frame rate
951
+ parent_frame.after(int(1000 / video.get(cv2.CAP_PROP_FPS)), update_frame)
952
+ else:
953
+ # Restart the video if it reaches the end
954
+ video.set(cv2.CAP_PROP_POS_FRAMES, 0)
955
+ update_frame()
956
+
957
+ # Start the video playback
958
+ update_frame()
959
+
960
+ elif file_extension == '.gif':
961
+ # Handle GIF format using PIL
962
+ gif = Image.open(media_path)
963
+
964
+ # Create a label to display the GIF
965
+ label = tk.Label(parent_frame, bg="black")
966
+ label.grid(row=0, column=0, sticky="nsew")
967
+
968
+ # Configure the parent_frame to expand
969
+ parent_frame.grid_rowconfigure(0, weight=1)
970
+ parent_frame.grid_columnconfigure(0, weight=1)
971
+
972
+ def update_gif_frame(frame_idx):
973
+ """Update function for playing GIF."""
974
+ try:
975
+ gif.seek(frame_idx) # Move to the next frame
976
+
977
+ # Get the frame dimensions
978
+ gif_width, gif_height = gif.size
979
+
980
+ # Get parent frame dimensions
981
+ parent_width = parent_frame.winfo_width()
982
+ parent_height = parent_frame.winfo_height()
983
+
984
+ # Ensure dimensions are greater than 0
985
+ if parent_width > 0 and parent_height > 0:
986
+ # Calculate the aspect ratio of the GIF
987
+ gif_aspect_ratio = gif_width / gif_height
988
+ parent_aspect_ratio = parent_width / parent_height
989
+
990
+ # Determine whether to scale based on width or height to cover the parent frame
991
+ if parent_aspect_ratio > gif_aspect_ratio:
992
+ # Fit to width, crop height
993
+ new_width = parent_width
994
+ new_height = int(parent_width / gif_aspect_ratio)
995
+ else:
996
+ # Fit to height, crop width
997
+ new_width = int(parent_height * gif_aspect_ratio)
998
+ new_height = parent_height
999
+
1000
+ # Resize the GIF frame to cover the parent frame
1001
+ resized_gif = gif.copy().resize((new_width, new_height), Image.Resampling.LANCZOS)
1002
+
1003
+ # Crop the resized GIF to fit the exact parent frame dimensions
1004
+ x_offset = (new_width - parent_width) // 2
1005
+ y_offset = (new_height - parent_height) // 2
1006
+ cropped_gif = resized_gif.crop((x_offset, y_offset, x_offset + parent_width, y_offset + parent_height))
1007
+
1008
+ # Convert the frame to a Tkinter-compatible format
1009
+ frame_image = ImageTk.PhotoImage(cropped_gif)
1010
+
1011
+ # Update the label with the new frame
1012
+ label.config(image=frame_image)
1013
+ label.image = frame_image # Keep a reference to avoid garbage collection
1014
+ frame_idx += 1
1015
+ except EOFError:
1016
+ frame_idx = 0 # Restart the GIF if at the end
1017
+
1018
+ # Schedule the next frame update
1019
+ parent_frame.after(gif.info['duration'], update_gif_frame, frame_idx)
1020
+
1021
+ # Start the GIF animation from frame 0
1022
+ update_gif_frame(0)
1023
+
1024
+ else:
1025
+ raise ValueError("Unsupported file format. Only .mp4, .avi, and .gif are supported.")
1026
+
1027
+ def print_widget_structure(widget, indent=0):
1028
+ """Recursively print the widget structure."""
1029
+ # Print the widget's name and class
1030
+ print(" " * indent + f"{widget}: {widget.winfo_class()}")
1031
+
1032
+ # Recursively print all child widgets
1033
+ for child_name, child_widget in widget.children.items():
1034
+ print_widget_structure(child_widget, indent + 2)
1035
+
1036
+ def get_screen_dimensions():
1037
+ monitor = get_monitors()[0] # Get the primary monitor
1038
+ screen_width = monitor.width
1039
+ screen_height = monitor.height
1040
+ return screen_width, screen_height