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.
- spacr/__init__.py +2 -1
- spacr/core.py +107 -12
- spacr/gui.py +3 -2
- spacr/gui_core.py +160 -109
- spacr/gui_elements.py +190 -18
- spacr/gui_utils.py +4 -1
- spacr/io.py +1 -1
- spacr/measure.py +4 -4
- spacr/mediar.py +366 -0
- spacr/plot.py +4 -1
- spacr/resources/MEDIAR/.git +1 -0
- spacr/resources/MEDIAR/.gitignore +18 -0
- spacr/resources/MEDIAR/LICENSE +21 -0
- spacr/resources/MEDIAR/README.md +189 -0
- spacr/resources/MEDIAR/SetupDict.py +39 -0
- spacr/resources/MEDIAR/config/baseline.json +60 -0
- spacr/resources/MEDIAR/config/mediar_example.json +72 -0
- spacr/resources/MEDIAR/config/pred/pred_mediar.json +17 -0
- spacr/resources/MEDIAR/config/step1_pretraining/phase1.json +55 -0
- spacr/resources/MEDIAR/config/step1_pretraining/phase2.json +58 -0
- spacr/resources/MEDIAR/config/step2_finetuning/finetuning1.json +66 -0
- spacr/resources/MEDIAR/config/step2_finetuning/finetuning2.json +66 -0
- spacr/resources/MEDIAR/config/step3_prediction/base_prediction.json +16 -0
- spacr/resources/MEDIAR/config/step3_prediction/ensemble_tta.json +23 -0
- spacr/resources/MEDIAR/core/BasePredictor.py +120 -0
- spacr/resources/MEDIAR/core/BaseTrainer.py +240 -0
- spacr/resources/MEDIAR/core/Baseline/Predictor.py +59 -0
- spacr/resources/MEDIAR/core/Baseline/Trainer.py +113 -0
- spacr/resources/MEDIAR/core/Baseline/__init__.py +2 -0
- spacr/resources/MEDIAR/core/Baseline/utils.py +80 -0
- spacr/resources/MEDIAR/core/MEDIAR/EnsemblePredictor.py +105 -0
- spacr/resources/MEDIAR/core/MEDIAR/Predictor.py +234 -0
- spacr/resources/MEDIAR/core/MEDIAR/Trainer.py +172 -0
- spacr/resources/MEDIAR/core/MEDIAR/__init__.py +3 -0
- spacr/resources/MEDIAR/core/MEDIAR/utils.py +429 -0
- spacr/resources/MEDIAR/core/__init__.py +2 -0
- spacr/resources/MEDIAR/core/utils.py +40 -0
- spacr/resources/MEDIAR/evaluate.py +71 -0
- spacr/resources/MEDIAR/generate_mapping.py +121 -0
- spacr/resources/MEDIAR/image/examples/img1.tiff +0 -0
- spacr/resources/MEDIAR/image/examples/img2.tif +0 -0
- spacr/resources/MEDIAR/image/failure_cases.png +0 -0
- spacr/resources/MEDIAR/image/mediar_framework.png +0 -0
- spacr/resources/MEDIAR/image/mediar_model.PNG +0 -0
- spacr/resources/MEDIAR/image/mediar_results.png +0 -0
- spacr/resources/MEDIAR/main.py +125 -0
- spacr/resources/MEDIAR/predict.py +70 -0
- spacr/resources/MEDIAR/requirements.txt +14 -0
- spacr/resources/MEDIAR/train_tools/__init__.py +3 -0
- spacr/resources/MEDIAR/train_tools/data_utils/__init__.py +1 -0
- spacr/resources/MEDIAR/train_tools/data_utils/custom/CellAware.py +88 -0
- spacr/resources/MEDIAR/train_tools/data_utils/custom/LoadImage.py +161 -0
- spacr/resources/MEDIAR/train_tools/data_utils/custom/NormalizeImage.py +77 -0
- spacr/resources/MEDIAR/train_tools/data_utils/custom/__init__.py +3 -0
- spacr/resources/MEDIAR/train_tools/data_utils/custom/modalities.pkl +0 -0
- spacr/resources/MEDIAR/train_tools/data_utils/datasetter.py +208 -0
- spacr/resources/MEDIAR/train_tools/data_utils/transforms.py +148 -0
- spacr/resources/MEDIAR/train_tools/data_utils/utils.py +84 -0
- spacr/resources/MEDIAR/train_tools/measures.py +200 -0
- spacr/resources/MEDIAR/train_tools/models/MEDIARFormer.py +102 -0
- spacr/resources/MEDIAR/train_tools/models/__init__.py +1 -0
- spacr/resources/MEDIAR/train_tools/utils.py +70 -0
- spacr/resources/MEDIAR_weights/.DS_Store +0 -0
- spacr/resources/icons/.DS_Store +0 -0
- spacr/resources/icons/plaque.png +0 -0
- spacr/resources/images/plate1_E01_T0001F001L01A01Z01C02.tif +0 -0
- spacr/resources/images/plate1_E01_T0001F001L01A02Z01C01.tif +0 -0
- spacr/resources/images/plate1_E01_T0001F001L01A03Z01C03.tif +0 -0
- spacr/sequencing.py +234 -422
- spacr/settings.py +16 -10
- spacr/utils.py +14 -11
- {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/METADATA +10 -2
- {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/RECORD +77 -18
- {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/LICENSE +0 -0
- {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/WHEEL +0 -0
- {spacr-0.2.68.dist-info → spacr-0.3.0.dist-info}/entry_points.txt +0 -0
- {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
|
-
#
|
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
|
-
#
|
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
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
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=
|
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=
|
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=
|
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 =
|
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=
|
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 =
|
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,
|
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
|
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
|