spacr 0.2.68__py3-none-any.whl → 0.3.0__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 (77) hide show
  1. spacr/__init__.py +2 -1
  2. spacr/core.py +107 -12
  3. spacr/gui.py +3 -2
  4. spacr/gui_core.py +160 -109
  5. spacr/gui_elements.py +190 -18
  6. spacr/gui_utils.py +4 -1
  7. spacr/io.py +1 -1
  8. spacr/measure.py +4 -4
  9. spacr/mediar.py +366 -0
  10. spacr/plot.py +4 -1
  11. spacr/resources/MEDIAR/.git +1 -0
  12. spacr/resources/MEDIAR/.gitignore +18 -0
  13. spacr/resources/MEDIAR/LICENSE +21 -0
  14. spacr/resources/MEDIAR/README.md +189 -0
  15. spacr/resources/MEDIAR/SetupDict.py +39 -0
  16. spacr/resources/MEDIAR/config/baseline.json +60 -0
  17. spacr/resources/MEDIAR/config/mediar_example.json +72 -0
  18. spacr/resources/MEDIAR/config/pred/pred_mediar.json +17 -0
  19. spacr/resources/MEDIAR/config/step1_pretraining/phase1.json +55 -0
  20. spacr/resources/MEDIAR/config/step1_pretraining/phase2.json +58 -0
  21. spacr/resources/MEDIAR/config/step2_finetuning/finetuning1.json +66 -0
  22. spacr/resources/MEDIAR/config/step2_finetuning/finetuning2.json +66 -0
  23. spacr/resources/MEDIAR/config/step3_prediction/base_prediction.json +16 -0
  24. spacr/resources/MEDIAR/config/step3_prediction/ensemble_tta.json +23 -0
  25. spacr/resources/MEDIAR/core/BasePredictor.py +120 -0
  26. spacr/resources/MEDIAR/core/BaseTrainer.py +240 -0
  27. spacr/resources/MEDIAR/core/Baseline/Predictor.py +59 -0
  28. spacr/resources/MEDIAR/core/Baseline/Trainer.py +113 -0
  29. spacr/resources/MEDIAR/core/Baseline/__init__.py +2 -0
  30. spacr/resources/MEDIAR/core/Baseline/utils.py +80 -0
  31. spacr/resources/MEDIAR/core/MEDIAR/EnsemblePredictor.py +105 -0
  32. spacr/resources/MEDIAR/core/MEDIAR/Predictor.py +234 -0
  33. spacr/resources/MEDIAR/core/MEDIAR/Trainer.py +172 -0
  34. spacr/resources/MEDIAR/core/MEDIAR/__init__.py +3 -0
  35. spacr/resources/MEDIAR/core/MEDIAR/utils.py +429 -0
  36. spacr/resources/MEDIAR/core/__init__.py +2 -0
  37. spacr/resources/MEDIAR/core/utils.py +40 -0
  38. spacr/resources/MEDIAR/evaluate.py +71 -0
  39. spacr/resources/MEDIAR/generate_mapping.py +121 -0
  40. spacr/resources/MEDIAR/image/examples/img1.tiff +0 -0
  41. spacr/resources/MEDIAR/image/examples/img2.tif +0 -0
  42. spacr/resources/MEDIAR/image/failure_cases.png +0 -0
  43. spacr/resources/MEDIAR/image/mediar_framework.png +0 -0
  44. spacr/resources/MEDIAR/image/mediar_model.PNG +0 -0
  45. spacr/resources/MEDIAR/image/mediar_results.png +0 -0
  46. spacr/resources/MEDIAR/main.py +125 -0
  47. spacr/resources/MEDIAR/predict.py +70 -0
  48. spacr/resources/MEDIAR/requirements.txt +14 -0
  49. spacr/resources/MEDIAR/train_tools/__init__.py +3 -0
  50. spacr/resources/MEDIAR/train_tools/data_utils/__init__.py +1 -0
  51. spacr/resources/MEDIAR/train_tools/data_utils/custom/CellAware.py +88 -0
  52. spacr/resources/MEDIAR/train_tools/data_utils/custom/LoadImage.py +161 -0
  53. spacr/resources/MEDIAR/train_tools/data_utils/custom/NormalizeImage.py +77 -0
  54. spacr/resources/MEDIAR/train_tools/data_utils/custom/__init__.py +3 -0
  55. spacr/resources/MEDIAR/train_tools/data_utils/custom/modalities.pkl +0 -0
  56. spacr/resources/MEDIAR/train_tools/data_utils/datasetter.py +208 -0
  57. spacr/resources/MEDIAR/train_tools/data_utils/transforms.py +148 -0
  58. spacr/resources/MEDIAR/train_tools/data_utils/utils.py +84 -0
  59. spacr/resources/MEDIAR/train_tools/measures.py +200 -0
  60. spacr/resources/MEDIAR/train_tools/models/MEDIARFormer.py +102 -0
  61. spacr/resources/MEDIAR/train_tools/models/__init__.py +1 -0
  62. spacr/resources/MEDIAR/train_tools/utils.py +70 -0
  63. spacr/resources/MEDIAR_weights/.DS_Store +0 -0
  64. spacr/resources/icons/.DS_Store +0 -0
  65. spacr/resources/icons/plaque.png +0 -0
  66. spacr/resources/images/plate1_E01_T0001F001L01A01Z01C02.tif +0 -0
  67. spacr/resources/images/plate1_E01_T0001F001L01A02Z01C01.tif +0 -0
  68. spacr/resources/images/plate1_E01_T0001F001L01A03Z01C03.tif +0 -0
  69. spacr/sequencing.py +234 -422
  70. spacr/settings.py +16 -10
  71. spacr/utils.py +14 -11
  72. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/METADATA +10 -2
  73. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/RECORD +77 -18
  74. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/LICENSE +0 -0
  75. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/WHEEL +0 -0
  76. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/entry_points.txt +0 -0
  77. {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/top_level.txt +0 -0
spacr/gui_elements.py CHANGED
@@ -670,25 +670,189 @@ class spacrProgressBar(ttk.Progressbar):
670
670
 
671
671
  def update_label(self):
672
672
  if self.label and self.progress_label:
673
- # Update the progress label with current progress and additional info
673
+ # Start with the base progress information
674
674
  label_text = f"Processing: {self['value']}/{self['maximum']}"
675
+
676
+ # Include the operation type if it exists
675
677
  if self.operation_type:
676
678
  label_text += f", {self.operation_type}"
679
+
680
+ # Handle additional info without adding newlines
677
681
  if hasattr(self, 'additional_info') and self.additional_info:
678
- # Add a space between progress information and additional information
679
- label_text += "\n\n"
680
- # Split the additional_info into a list of items
682
+ # Join all additional info items with a space and ensure they're on the same line
681
683
  items = self.additional_info.split(", ")
682
- formatted_additional_info = ""
683
- # Group the items in pairs, adding them to formatted_additional_info
684
- for i in range(0, len(items), 2):
685
- if i + 1 < len(items):
686
- formatted_additional_info += f"{items[i]}, {items[i + 1]}\n\n"
687
- else:
688
- formatted_additional_info += f"{items[i]}\n\n" # If there's an odd item out, add it alone
689
- label_text += formatted_additional_info.strip()
684
+ formatted_additional_info = " ".join(items)
685
+
686
+ # Append the additional info to the label_text, ensuring it's all in one line
687
+ label_text += f" {formatted_additional_info.strip()}"
688
+
689
+ # Update the progress label
690
690
  self.progress_label.config(text=label_text)
691
691
 
692
+ class spacrSlider(tk.Frame):
693
+ def __init__(self, master=None, length=None, thickness=2, knob_radius=10, position="center", from_=0, to=100, value=None, show_index=False, command=None, **kwargs):
694
+ super().__init__(master, **kwargs)
695
+
696
+ self.specified_length = length # Store the specified length, if any
697
+ self.knob_radius = knob_radius
698
+ self.thickness = thickness
699
+ self.knob_position = knob_radius # Start at the beginning of the slider
700
+ self.slider_line = None
701
+ self.knob = None
702
+ self.position = position.lower() # Store the position option
703
+ self.offset = 0 # Initialize offset
704
+ self.from_ = from_ # Minimum value of the slider
705
+ self.to = to # Maximum value of the slider
706
+ self.value = value if value is not None else from_ # Initial value of the slider
707
+ self.show_index = show_index # Whether to show the index Entry widget
708
+ self.command = command # Callback function to handle value changes
709
+
710
+ # Initialize the style and colors
711
+ style_out = set_dark_style(ttk.Style())
712
+ self.fg_color = style_out['fg_color']
713
+ self.bg_color = style_out['bg_color']
714
+ self.active_color = style_out['active_color']
715
+ self.inactive_color = style_out['inactive_color']
716
+
717
+ # Configure the frame's background color
718
+ self.configure(bg=self.bg_color)
719
+
720
+ # Create a frame for the slider and entry if needed
721
+ self.grid_columnconfigure(1, weight=1)
722
+
723
+ # Entry widget for showing and editing index, if enabled
724
+ if self.show_index:
725
+ self.index_var = tk.StringVar(value=str(int(self.value)))
726
+ self.index_entry = tk.Entry(self, textvariable=self.index_var, width=5, bg=self.bg_color, fg=self.fg_color, insertbackground=self.fg_color)
727
+ self.index_entry.grid(row=0, column=0, padx=5)
728
+ # Bind the entry to update the slider on change
729
+ self.index_entry.bind("<Return>", self.update_slider_from_entry)
730
+
731
+ # Create the slider canvas
732
+ self.canvas = tk.Canvas(self, height=knob_radius * 2, bg=self.bg_color, highlightthickness=0)
733
+ self.canvas.grid(row=0, column=1, sticky="ew")
734
+
735
+ # Set initial length to specified length or default value
736
+ self.length = self.specified_length if self.specified_length is not None else self.canvas.winfo_reqwidth()
737
+
738
+ # Calculate initial knob position based on the initial value
739
+ self.knob_position = self.value_to_position(self.value)
740
+
741
+ # Bind resize event to dynamically adjust the slider length if no length is specified
742
+ self.canvas.bind("<Configure>", self.resize_slider)
743
+
744
+ # Draw the slider components
745
+ self.draw_slider(inactive=True)
746
+
747
+ # Bind mouse events to the knob and slider
748
+ self.canvas.bind("<B1-Motion>", self.move_knob)
749
+ self.canvas.bind("<Button-1>", self.activate_knob) # Activate knob on click
750
+ self.canvas.bind("<ButtonRelease-1>", self.release_knob) # Trigger command on release
751
+
752
+ def resize_slider(self, event):
753
+ if self.specified_length is not None:
754
+ self.length = self.specified_length
755
+ else:
756
+ self.length = int(event.width * 0.9) # 90% of the container width
757
+
758
+ # Calculate the horizontal offset based on the position
759
+ if self.position == "center":
760
+ self.offset = (event.width - self.length) // 2
761
+ elif self.position == "right":
762
+ self.offset = event.width - self.length
763
+ else: # position is "left"
764
+ self.offset = 0
765
+
766
+ # Update the knob position after resizing
767
+ self.knob_position = self.value_to_position(self.value)
768
+ self.draw_slider(inactive=True)
769
+
770
+ def value_to_position(self, value):
771
+ if self.to == self.from_:
772
+ return self.knob_radius
773
+ relative_value = (value - self.from_) / (self.to - self.from_)
774
+ return self.knob_radius + relative_value * (self.length - 2 * self.knob_radius)
775
+
776
+ def position_to_value(self, position):
777
+ if self.to == self.from_:
778
+ return self.from_
779
+ relative_position = (position - self.knob_radius) / (self.length - 2 * self.knob_radius)
780
+ return self.from_ + relative_position * (self.to - self.from_)
781
+
782
+ def draw_slider(self, inactive=False):
783
+ self.canvas.delete("all")
784
+
785
+ self.slider_line = self.canvas.create_line(
786
+ self.offset + self.knob_radius,
787
+ self.knob_radius,
788
+ self.offset + self.length - self.knob_radius,
789
+ self.knob_radius,
790
+ fill=self.fg_color,
791
+ width=self.thickness
792
+ )
793
+
794
+ knob_color = self.inactive_color if inactive else self.active_color
795
+ self.knob = self.canvas.create_oval(
796
+ self.offset + self.knob_position - self.knob_radius,
797
+ self.knob_radius - self.knob_radius,
798
+ self.offset + self.knob_position + self.knob_radius,
799
+ self.knob_radius + self.knob_radius,
800
+ fill=knob_color,
801
+ outline=""
802
+ )
803
+
804
+ def move_knob(self, event):
805
+ new_position = min(max(event.x - self.offset, self.knob_radius), self.length - self.knob_radius)
806
+ self.knob_position = new_position
807
+ self.value = self.position_to_value(self.knob_position)
808
+ self.canvas.coords(
809
+ self.knob,
810
+ self.offset + self.knob_position - self.knob_radius,
811
+ self.knob_radius - self.knob_radius,
812
+ self.offset + self.knob_position + self.knob_radius,
813
+ self.knob_radius + self.knob_radius
814
+ )
815
+ if self.show_index:
816
+ self.index_var.set(str(int(self.value)))
817
+
818
+ def activate_knob(self, event):
819
+ self.draw_slider(inactive=False)
820
+ self.move_knob(event)
821
+
822
+ def release_knob(self, event):
823
+ self.draw_slider(inactive=True)
824
+ if self.command:
825
+ self.command(self.value) # Call the command with the final value when the knob is released
826
+
827
+ def set_to(self, new_to):
828
+ self.to = new_to
829
+ self.knob_position = self.value_to_position(self.value)
830
+ self.draw_slider(inactive=False)
831
+
832
+ def get(self):
833
+ return self.value
834
+
835
+ def set(self, value):
836
+ """Set the slider's value and update the knob position."""
837
+ self.value = max(self.from_, min(value, self.to)) # Ensure the value is within bounds
838
+ self.knob_position = self.value_to_position(self.value)
839
+ self.draw_slider(inactive=False)
840
+ if self.show_index:
841
+ self.index_var.set(str(int(self.value)))
842
+
843
+ def jump_to_click(self, event):
844
+ self.activate_knob(event)
845
+
846
+ def update_slider_from_entry(self, event):
847
+ """Update the slider's value from the entry."""
848
+ try:
849
+ index = int(self.index_var.get())
850
+ self.set(index)
851
+ if self.command:
852
+ self.command(self.value)
853
+ except ValueError:
854
+ pass
855
+
692
856
  def spacrScrollbarStyle(style, inactive_color, active_color):
693
857
  # Check if custom elements already exist to avoid duplication
694
858
  if not style.element_names().count('custom.Vertical.Scrollbar.trough'):
@@ -1987,6 +2151,11 @@ class AnnotateApp:
1987
2151
  style_out = set_dark_style(ttk.Style())
1988
2152
  self.font_loader = style_out['font_loader']
1989
2153
  self.font_size = style_out['font_size']
2154
+ self.bg_color = style_out['bg_color']
2155
+ self.fg_color = style_out['fg_color']
2156
+ self.active_color = style_out['active_color']
2157
+ self.inactive_color = style_out['inactive_color']
2158
+
1990
2159
 
1991
2160
  if self.font_loader:
1992
2161
  self.font_style = self.font_loader.get_font(size=self.font_size)
@@ -2013,13 +2182,13 @@ class AnnotateApp:
2013
2182
  self.button_frame = Frame(root, bg=self.root.cget('bg'))
2014
2183
  self.button_frame.grid(row=2, column=1, padx=10, pady=10, sticky="se")
2015
2184
 
2016
- self.next_button = Button(self.button_frame, text="Next", command=self.next_page, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
2185
+ self.next_button = Button(self.button_frame, text="Next", command=self.next_page, bg=self.bg_color, fg=self.fg_color, highlightbackground=self.fg_color, highlightcolor=self.fg_color, highlightthickness=1)
2017
2186
  self.next_button.pack(side="right", padx=5)
2018
2187
 
2019
- self.previous_button = Button(self.button_frame, text="Back", command=self.previous_page, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
2188
+ self.previous_button = Button(self.button_frame, text="Back", command=self.previous_page, bg=self.bg_color, fg=self.fg_color, highlightbackground=self.fg_color, highlightcolor=self.fg_color, highlightthickness=1)
2020
2189
  self.previous_button.pack(side="right", padx=5)
2021
2190
 
2022
- self.exit_button = Button(self.button_frame, text="Exit", command=self.shutdown, bg='black', fg='white', highlightbackground='white', highlightcolor='white', highlightthickness=1)
2191
+ self.exit_button = Button(self.button_frame, text="Exit", command=self.shutdown, bg=self.bg_color, fg=self.fg_color, highlightbackground=self.fg_color, highlightcolor=self.fg_color, highlightthickness=1)
2023
2192
  self.exit_button.pack(side="right", padx=5)
2024
2193
 
2025
2194
  # Calculate grid rows and columns based on the root window size and image size
@@ -2169,7 +2338,7 @@ class AnnotateApp:
2169
2338
 
2170
2339
  for i, (img, annotation) in enumerate(loaded_images):
2171
2340
  if annotation:
2172
- border_color = 'teal' if annotation == 1 else 'red'
2341
+ border_color = self.active_color if annotation == 1 else 'red'
2173
2342
  img = self.add_colored_border(img, border_width=5, border_color=border_color)
2174
2343
 
2175
2344
  photo = ImageTk.PhotoImage(img)
@@ -2215,7 +2384,7 @@ class AnnotateApp:
2215
2384
  left_border = Image.new('RGB', (border_width, img.height), color=border_color)
2216
2385
  right_border = Image.new('RGB', (border_width, img.height), color=border_color)
2217
2386
 
2218
- bordered_img = Image.new('RGB', (img.width + 2 * border_width, img.height + 2 * border_width), color='white')
2387
+ bordered_img = Image.new('RGB', (img.width + 2 * border_width, img.height + 2 * border_width), color=self.fg_color)
2219
2388
  bordered_img.paste(top_border, (border_width, 0))
2220
2389
  bordered_img.paste(bottom_border, (border_width, img.height + border_width))
2221
2390
  bordered_img.paste(left_border, (0, border_width))
@@ -2255,7 +2424,7 @@ class AnnotateApp:
2255
2424
  print(f"Image {os.path.split(path)[1]} annotated: {new_annotation}")
2256
2425
 
2257
2426
  img_ = img.crop((5, 5, img.width-5, img.height-5))
2258
- border_fill = 'teal' if new_annotation == 1 else ('red' if new_annotation == 2 else None)
2427
+ border_fill = self.active_color if new_annotation == 1 else ('red' if new_annotation == 2 else None)
2259
2428
  img_ = ImageOps.expand(img_, border=5, fill=border_fill) if border_fill else img_
2260
2429
 
2261
2430
  photo = ImageTk.PhotoImage(img_)
@@ -2400,6 +2569,7 @@ def standardize_figure(fig):
2400
2569
  - Line width: 1
2401
2570
  - Line color: from style
2402
2571
  """
2572
+
2403
2573
 
2404
2574
  for ax in fig.get_axes():
2405
2575
  # Set font properties for title and labels
@@ -2448,6 +2618,8 @@ def standardize_figure(fig):
2448
2618
 
2449
2619
  fig.canvas.draw_idle()
2450
2620
 
2621
+ return fig
2622
+
2451
2623
  def modify_figure_properties(fig, scale_x=None, scale_y=None, line_width=None, font_size=None, x_lim=None, y_lim=None, grid=False, legend=None, title=None, x_label_rotation=None, remove_axes=False, bg_color=None, text_color=None, line_color=None):
2452
2624
  """
2453
2625
  Modifies the properties of the figure, including scaling, line widths, font sizes, axis limits, x-axis label rotation, background color, text color, line color, and other common options.
spacr/gui_utils.py CHANGED
@@ -486,7 +486,7 @@ def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imp
486
486
  def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
487
487
 
488
488
  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, compare_cellpose_masks, analyze_plaques, generate_dataset, apply_model_to_tar
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
490
  from .io import generate_cellpose_train_test
491
491
  from .measure import measure_crop
492
492
  from .sim import run_multiple_simulations
@@ -532,6 +532,9 @@ def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
532
532
  elif settings_type == 'umap':
533
533
  function = generate_image_umap
534
534
  imports = 1
535
+ elif settings_type == 'analyze_plaques':
536
+ function = analyze_plaques
537
+ imports = 1
535
538
  else:
536
539
  raise ValueError(f"Invalid settings type: {settings_type}")
537
540
  try:
spacr/io.py CHANGED
@@ -26,7 +26,7 @@ import atexit
26
26
 
27
27
  from .logger import log_function_call
28
28
 
29
- def _load_images_and_labels(image_files, label_files, circular=False, invert=False, image_extension="*.tif", label_extension="*.tif"):
29
+ def _load_images_and_labels(image_files, label_files, circular=False, invert=False):
30
30
 
31
31
  from .utils import invert_image, apply_mask
32
32
 
spacr/measure.py CHANGED
@@ -998,10 +998,10 @@ def measure_crop(settings):
998
998
  src_fldr = os.path.join(src_fldr, 'merged')
999
999
  print(f"Changed source folder to: {src_fldr}")
1000
1000
 
1001
- if settings['save_measurements']:
1002
- source_folder = os.path.dirname(settings['src'])
1003
- os.makedirs(source_folder+'/measurements', exist_ok=True)
1004
- _create_database(source_folder+'/measurements/measurements.db')
1001
+ #if settings['save_measurements']:
1002
+ #source_folder = os.path.dirname(settings['src'])
1003
+ #os.makedirs(source_folder+'/measurements', exist_ok=True)
1004
+ #_create_database(source_folder+'/measurements/measurements.db')
1005
1005
 
1006
1006
  if settings['cell_mask_dim'] is None:
1007
1007
  settings['include_uninfected'] = True