teareduce 0.6.5__py3-none-any.whl → 0.6.7__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.
@@ -9,7 +9,6 @@
9
9
 
10
10
  """Parameter editor dialog for L.A.Cosmic parameters."""
11
11
 
12
- from astropy.io import fits
13
12
  from pathlib import Path
14
13
  import tkinter as tk
15
14
  from tkinter import ttk
@@ -19,9 +18,10 @@ from tkinter import messagebox
19
18
  from .askextension import ask_extension_input_image
20
19
  from .centerchildparent import center_on_parent
21
20
  from .definitions import lacosmic_default_dict
21
+ from .definitions import pycosmic_default_dict
22
22
 
23
23
 
24
- class ParameterEditor:
24
+ class ParameterEditorLACosmic:
25
25
  """A dialog to edit L.A.Cosmic parameters."""
26
26
 
27
27
  def __init__(
@@ -76,6 +76,10 @@ class ParameterEditor:
76
76
  -------
77
77
  create_widgets()
78
78
  Create the widgets for the dialog.
79
+ define_inbkg()
80
+ Define the input background image.
81
+ define_invar()
82
+ Define the input variance image.
79
83
  on_ok()
80
84
  Validate and save the updated values.
81
85
  on_cancel()
@@ -84,6 +88,8 @@ class ParameterEditor:
84
88
  Reset all fields to original values.
85
89
  get_result()
86
90
  Return the updated dictionary.
91
+ update_colour_param_run1_run2()
92
+ Update the color of run2 entries if they differ from run1.
87
93
 
88
94
  Attributes
89
95
  ----------
@@ -167,7 +173,7 @@ class ParameterEditor:
167
173
  row += 1
168
174
 
169
175
  # Note: here we are using entry_vars to trace changes in the entries
170
- # so that we can update the color of run2 entries if they differ from run1.
176
+ # so that we can update the color of run2 entries if they differ from run1
171
177
  self.entry_vars = {}
172
178
  row_subtable = 0
173
179
  coloff = 0
@@ -215,7 +221,7 @@ class ParameterEditor:
215
221
  entry = tk.Entry(main_frame, textvariable=self.entry_vars[key2], width=10)
216
222
  entry.insert(0, str(self.param_dict[key2]["value"]))
217
223
  entry.grid(row=row, column=2 + coloff, padx=10, pady=5)
218
- self.entries["run2_" + key[5:]] = entry # dictionary to hold entry widgets
224
+ self.entries[key2] = entry # dictionary to hold entry widgets
219
225
  # Type label
220
226
  infotext = info["type"].__name__
221
227
  if infotext == "int":
@@ -266,7 +272,11 @@ class ParameterEditor:
266
272
  # Vertical separator between splitted table
267
273
  separatorv1 = ttk.Separator(main_frame, orient="vertical")
268
274
  separatorv1.grid(
269
- row=row - max_num_params_in_columns, column=4, rowspan=max_num_params_in_columns, sticky="ns", padx=10
275
+ row=row - max_num_params_in_columns - 1,
276
+ column=4,
277
+ rowspan=max_num_params_in_columns + 1,
278
+ sticky="ns",
279
+ padx=10,
270
280
  )
271
281
 
272
282
  # Separator
@@ -480,6 +490,14 @@ class ParameterEditor:
480
490
 
481
491
  # Additional validation for region limits
482
492
  try:
493
+ if updated_dict["xmin"]["value"] < 1 or updated_dict["xmin"]["value"] > self.imgshape[1]:
494
+ raise ValueError(f"xmin must be in the range [1, {self.imgshape[1]}]")
495
+ if updated_dict["xmax"]["value"] < 1 or updated_dict["xmax"]["value"] > self.imgshape[1]:
496
+ raise ValueError(f"xmax must be in the range [1, {self.imgshape[1]}]")
497
+ if updated_dict["ymin"]["value"] < 1 or updated_dict["ymin"]["value"] > self.imgshape[0]:
498
+ raise ValueError(f"ymin must be in the range [1, {self.imgshape[0]}]")
499
+ if updated_dict["ymax"]["value"] < 1 or updated_dict["ymax"]["value"] > self.imgshape[0]:
500
+ raise ValueError(f"ymax must be in the range [1, {self.imgshape[0]}]")
483
501
  if updated_dict["xmax"]["value"] <= updated_dict["xmin"]["value"]:
484
502
  raise ValueError("xmax must be greater than xmin")
485
503
  if updated_dict["ymax"]["value"] <= updated_dict["ymin"]["value"]:
@@ -555,3 +573,366 @@ class ParameterEditor:
555
573
  self.entries["run1_" + parname].selection_clear()
556
574
  if "run2_" + parname in self.entries:
557
575
  self.entries["run2_" + parname].selection_clear()
576
+
577
+
578
+ class ParameterEditorPyCosmic:
579
+ """A dialog to edit PyCosmic parameters."""
580
+
581
+ def __init__(
582
+ self,
583
+ root,
584
+ param_dict,
585
+ window_title,
586
+ xmin,
587
+ xmax,
588
+ ymin,
589
+ ymax,
590
+ imgshape,
591
+ ):
592
+ """Initialize the parameter editor dialog.
593
+
594
+ Parameters
595
+ ----------
596
+ root : tk.Tk
597
+ The root Tkinter window.
598
+ param_dict : dict
599
+ Dictionary with L.A.Cosmic parameters.
600
+ window_title : str
601
+ Title of the dialog window.
602
+ xmin : int
603
+ Minimum x-coordinate of the region to be examined.
604
+ From 1 to NAXIS1.
605
+ xmax : int
606
+ Maximum x-coordinate of the region to be examined.
607
+ From 1 to NAXIS1.
608
+ ymin : int
609
+ Minimum y-coordinate of the region to be examined.
610
+ From 1 to NAXIS2.
611
+ ymax : int
612
+ Maximum y-coordinate of the region to be examined.
613
+ From 1 to NAXIS2.
614
+ imgshape : tuple
615
+ Shape of the image (height, width).
616
+
617
+ Methods
618
+ -------
619
+ create_widgets()
620
+ Create the widgets for the dialog.
621
+ on_ok()
622
+ Validate and save the updated values.
623
+ on_cancel()
624
+ Close the dialog without saving.
625
+ get_result()
626
+ Return the updated dictionary.
627
+
628
+ Attributes
629
+ ----------
630
+ root : tk.Tk
631
+ The root Tkinter window.
632
+ param_dict : dict
633
+ Dictionary with L.A.Cosmic parameters.
634
+ imgshape : tuple
635
+ Shape of the image (height, width).
636
+ entries : dict
637
+ Dictionary to hold entry widgets.
638
+ result_dict : dict or None
639
+ The updated dictionary of parameters or None if cancelled.
640
+ """
641
+ self.root = root
642
+ self.root.title(window_title)
643
+ self.param_dict = param_dict
644
+ # Set default region values
645
+ self.param_dict["xmin"]["value"] = xmin
646
+ self.param_dict["xmax"]["value"] = xmax
647
+ self.param_dict["ymin"]["value"] = ymin
648
+ self.param_dict["ymax"]["value"] = ymax
649
+ self.imgshape = imgshape
650
+ self.entries = {"run1": {}, "run2": {}} # dictionary to hold entry widgets
651
+ self.result_dict = {}
652
+
653
+ # Create the form
654
+ self.create_widgets()
655
+ center_on_parent(child=self.root, parent=self.root.master)
656
+
657
+ def create_widgets(self):
658
+ """Create the widgets for the dialog."""
659
+ # Define different styles for different conditions
660
+ style = ttk.Style()
661
+ style.configure("Normal.TCombobox", foreground="black", background="white")
662
+ style.configure("Changed.TCombobox", foreground="red", background="white")
663
+
664
+ # Main frame
665
+ main_frame = tk.Frame(self.root, padx=10, pady=10)
666
+ main_frame.pack()
667
+
668
+ row = 0
669
+
670
+ # Subtitle for PyCosmic parameters
671
+ default_font = tk.font.nametofont("TkDefaultFont")
672
+ bold_font = default_font.copy()
673
+ bold_font.configure(weight="bold", size=default_font.cget("size") + 2)
674
+ subtitle_label = tk.Label(main_frame, text="PyCosmic Parameters", font=bold_font)
675
+ subtitle_label.grid(row=row, column=0, columnspan=9, pady=(0, 10))
676
+ row += 1
677
+
678
+ # Count number of parameters for run1 and run2
679
+ nparams_run1 = sum(1 for key in self.param_dict.keys() if key.startswith("run1_"))
680
+ nparams_run2 = sum(1 for key in self.param_dict.keys() if key.startswith("run2_"))
681
+ if nparams_run1 != nparams_run2:
682
+ raise ValueError("Number of parameters for run1 and run2 do not match.")
683
+ else:
684
+ nparams_total = nparams_run1
685
+ max_num_params_in_columns = nparams_total // 2 + nparams_total % 2
686
+
687
+ # Create labels and entry fields for each parameter.
688
+ bold_font_subheader = default_font.copy()
689
+ bold_font_subheader.configure(weight="bold", size=default_font.cget("size") + 1)
690
+ for subtable in range(2):
691
+ if subtable == 0:
692
+ coloff = 0
693
+ else:
694
+ coloff = 5
695
+ label = tk.Label(main_frame, text="Parameter", font=bold_font_subheader, anchor="w", fg="gray")
696
+ label.grid(row=row, column=0 + coloff, sticky="e", pady=0)
697
+ label = tk.Label(main_frame, text="Run 1", font=bold_font_subheader, anchor="w", fg="gray", width=10)
698
+ label.grid(row=row, column=1 + coloff, sticky="w", padx=10, pady=0)
699
+ label = tk.Label(main_frame, text="Run 2", font=bold_font_subheader, anchor="w", fg="gray", width=10)
700
+ label.grid(row=row, column=2 + coloff, sticky="w", padx=10, pady=0)
701
+ label = tk.Label(main_frame, text="Type", font=bold_font_subheader, anchor="w", fg="gray", width=10)
702
+ label.grid(row=row, column=3 + coloff, sticky="w", pady=0)
703
+ row += 1
704
+
705
+ # Note: here we are using entry_vars to trace changes in the entries
706
+ # so that we can update the color of run2 entries if they differ from run1
707
+ self.entry_vars = {}
708
+ row_subtable = 0
709
+ coloff = 0
710
+ for key, info in self.param_dict.items():
711
+ if not key.startswith("run1_"):
712
+ continue
713
+ # Parameter name label
714
+ label = tk.Label(main_frame, text=f"{key[5:]}:", anchor="e", width=15)
715
+ label.grid(row=row, column=coloff, sticky="w", pady=5)
716
+ # Entry field for run1
717
+ self.entry_vars[key] = tk.StringVar()
718
+ self.entry_vars[key].trace_add("write", lambda *args: self.update_colour_param_run1_run2())
719
+ entry = tk.Entry(main_frame, textvariable=self.entry_vars[key], width=10)
720
+ entry.insert(0, str(info["value"]))
721
+ entry.grid(row=row, column=1 + coloff, padx=10, pady=5)
722
+ self.entries[key] = entry # dictionary to hold entry widgets
723
+ # Entry field for run2
724
+ key2 = "run2_" + key[5:]
725
+ self.entry_vars[key2] = tk.StringVar()
726
+ self.entry_vars[key2].trace_add("write", lambda *args: self.update_colour_param_run1_run2())
727
+ entry = tk.Entry(main_frame, textvariable=self.entry_vars[key2], width=10)
728
+ entry.insert(0, str(self.param_dict[key2]["value"]))
729
+ entry.grid(row=row, column=2 + coloff, padx=10, pady=5)
730
+ self.entries[key2] = entry # dictionary to
731
+ # Type label
732
+ infotext = info["type"].__name__
733
+ if infotext == "int":
734
+ if "intmode" in info:
735
+ if info["intmode"] == "odd":
736
+ infotext += ", odd"
737
+ elif info["intmode"] == "even":
738
+ infotext += ", even"
739
+ type_label = tk.Label(main_frame, text=f"({infotext})", fg="gray", anchor="w", width=10)
740
+ type_label.grid(row=row, column=3 + coloff, sticky="w", pady=5)
741
+ row_subtable += 1
742
+ if row_subtable == max_num_params_in_columns:
743
+ coloff = 5
744
+ row -= max_num_params_in_columns
745
+ row += 1
746
+
747
+ # Adjust row if odd number of parameters
748
+ if nparams_total % 2 != 0:
749
+ row += nparams_total % 2
750
+
751
+ # Vertical separator
752
+ separatorv1 = ttk.Separator(main_frame, orient="vertical")
753
+ separatorv1.grid(
754
+ row=row - max_num_params_in_columns - 1,
755
+ column=4,
756
+ rowspan=max_num_params_in_columns + 1,
757
+ sticky="ns",
758
+ padx=10,
759
+ )
760
+
761
+ # Separator
762
+ separator1 = ttk.Separator(main_frame, orient="horizontal")
763
+ separator1.grid(row=row, column=0, columnspan=9, sticky="ew", pady=(10, 10))
764
+ row += 1
765
+
766
+ # Subtitle for region to be examined
767
+ subtitle_label = tk.Label(main_frame, text="Region to be Examined", font=bold_font)
768
+ subtitle_label.grid(row=row, column=0, columnspan=9, pady=(0, 10))
769
+ row += 1
770
+
771
+ # Region to be examined label and entries
772
+ for key, info in self.param_dict.items():
773
+ if key.lower() in ["xmin", "xmax", "ymin", "ymax"]:
774
+ # Parameter name label
775
+ label = tk.Label(main_frame, text=f"{key}:", anchor="e", width=15)
776
+ if key.lower() in ["xmin", "xmax"]:
777
+ coloff = 0
778
+ else:
779
+ coloff = 5
780
+ label.grid(row=row, column=coloff, sticky="w", pady=5)
781
+ # Entry field
782
+ entry = tk.Entry(main_frame, width=10)
783
+ entry.insert(0, str(info["value"]))
784
+ entry.grid(row=row, column=coloff + 1, padx=10, pady=5)
785
+ self.entries[key] = entry # dictionary to hold entry widgets
786
+ # Type label
787
+ dumtext = f"({info['type'].__name__})"
788
+ if key.lower() in ["xmin", "xmax"]:
789
+ dumtext += f" --> [1, {self.imgshape[1]}]"
790
+ else:
791
+ dumtext += f" --> [1, {self.imgshape[0]}]"
792
+ type_label = tk.Label(main_frame, text=dumtext, fg="gray", anchor="w", width=15)
793
+ type_label.grid(row=row, column=coloff + 2, sticky="w", pady=5)
794
+ if key.lower() == "xmax":
795
+ row -= 1
796
+ else:
797
+ row += 1
798
+
799
+ # Vertical separator
800
+ separatorv2 = ttk.Separator(main_frame, orient="vertical")
801
+ separatorv2.grid(row=row - 2, column=4, rowspan=2, sticky="ns", padx=10)
802
+
803
+ # Separator
804
+ separator2 = ttk.Separator(main_frame, orient="horizontal")
805
+ separator2.grid(row=row, column=0, columnspan=9, sticky="ew", pady=(10, 10))
806
+ row += 1
807
+
808
+ # Button frame
809
+ button_frame = tk.Frame(main_frame)
810
+ button_frame.grid(row=row, column=0, columnspan=9, pady=(5, 0))
811
+
812
+ # OK button
813
+ self.ok_button = ttk.Button(button_frame, text="OK", takefocus=True, command=self.on_ok)
814
+ self.ok_button.pack(side="left", padx=5)
815
+
816
+ # Cancel button
817
+ self.cancel_button = ttk.Button(button_frame, text="Cancel", command=self.on_cancel)
818
+ self.cancel_button.pack(side="left", padx=5)
819
+
820
+ # Reset button
821
+ self.reset_button = ttk.Button(button_frame, text="Reset", command=self.on_reset)
822
+ self.reset_button.pack(side="left", padx=5)
823
+
824
+ # Set focus to OK button
825
+ self.ok_button.focus_set()
826
+
827
+ def on_ok(self):
828
+ """Validate and save the updated values"""
829
+ try:
830
+ updated_dict = {}
831
+ for key, info in self.param_dict.items():
832
+ if key in ["nruns"]:
833
+ continue
834
+ entry_value = self.entries[key].get()
835
+ value_type = info["type"]
836
+
837
+ # Convert string to appropriate type
838
+ if value_type == bool:
839
+ # Handle boolean conversion
840
+ if entry_value.lower() in ["true", "1", "yes"]:
841
+ converted_value = True
842
+ elif entry_value.lower() in ["false", "0", "no"]:
843
+ converted_value = False
844
+ else:
845
+ raise ValueError(f"Invalid boolean value for {key}")
846
+ elif value_type == str:
847
+ converted_value = entry_value
848
+ if "valid_values" in info and entry_value not in info["valid_values"]:
849
+ raise ValueError(f"Invalid value for {key}. Valid values are: {info['valid_values']}")
850
+ else:
851
+ converted_value = value_type(entry_value)
852
+ if "positive" in info and info["positive"] and converted_value < 0:
853
+ raise ValueError(f"Value for {key} must be positive")
854
+ if "intmode" in info:
855
+ if info["intmode"] == "odd" and converted_value % 2 == 0:
856
+ raise ValueError(f"Value for {key} must be an odd integer")
857
+ elif info["intmode"] == "even" and converted_value % 2 != 0:
858
+ raise ValueError(f"Value for {key} must be an even integer")
859
+
860
+ # Duplicate the parameter info and update only the value
861
+ # (preserving other metadata)
862
+ updated_dict[key] = self.param_dict[key].copy()
863
+ updated_dict[key]["value"] = converted_value
864
+
865
+ # Check whether any run1 and run2 parameters differ
866
+ nruns = 1
867
+ for key in self.param_dict.keys():
868
+ if key.startswith("run1_"):
869
+ parname = key[5:]
870
+ key2 = "run2_" + parname
871
+ if updated_dict[key]["value"] != updated_dict[key2]["value"]:
872
+ nruns = 2
873
+ print(
874
+ f"Parameter '{parname}' differs between run1 and run2: "
875
+ f"{updated_dict[key]['value']} (run1) vs {updated_dict[key2]['value']} (run2)"
876
+ )
877
+
878
+ # Additional validation for region limits
879
+ try:
880
+ if updated_dict["xmin"]["value"] < 1 or updated_dict["xmin"]["value"] > self.imgshape[1]:
881
+ raise ValueError(f"xmin must be in the range [1, {self.imgshape[1]}]")
882
+ if updated_dict["xmax"]["value"] < 1 or updated_dict["xmax"]["value"] > self.imgshape[1]:
883
+ raise ValueError(f"xmax must be in the range [1, {self.imgshape[1]}]")
884
+ if updated_dict["ymin"]["value"] < 1 or updated_dict["ymin"]["value"] > self.imgshape[0]:
885
+ raise ValueError(f"ymin must be in the range [1, {self.imgshape[0]}]")
886
+ if updated_dict["ymax"]["value"] < 1 or updated_dict["ymax"]["value"] > self.imgshape[0]:
887
+ raise ValueError(f"ymax must be in the range [1, {self.imgshape[0]}]")
888
+ if updated_dict["xmax"]["value"] <= updated_dict["xmin"]["value"]:
889
+ raise ValueError("xmax must be greater than xmin")
890
+ if updated_dict["ymax"]["value"] <= updated_dict["ymin"]["value"]:
891
+ raise ValueError("ymax must be greater than ymin")
892
+ self.result_dict = updated_dict
893
+ self.result_dict["nruns"] = {"value": nruns, "type": int, "positive": True}
894
+ if nruns not in [1, 2]:
895
+ raise ValueError("nruns must be 1 or 2")
896
+ self.root.destroy()
897
+ except ValueError as e:
898
+ messagebox.showerror(
899
+ "Invalid Inputs", "Error in region limits:\n" f"{str(e)}\n\nPlease check your inputs."
900
+ )
901
+
902
+ except ValueError as e:
903
+ messagebox.showerror(
904
+ "Invalid Inputs", f"Error converting value for {key}:\n{str(e)}\n\n" "Please check your inputs."
905
+ )
906
+
907
+ def on_cancel(self):
908
+ """Close without saving"""
909
+ self.result_dict = None
910
+ self.root.destroy()
911
+
912
+ def on_reset(self):
913
+ """Reset all fields to original values"""
914
+ self.param_dict = pycosmic_default_dict.copy()
915
+ self.param_dict["xmin"]["value"] = 1
916
+ self.param_dict["xmax"]["value"] = self.imgshape[1]
917
+ self.param_dict["ymin"]["value"] = 1
918
+ self.param_dict["ymax"]["value"] = self.imgshape[0]
919
+ for key, info in self.param_dict.items():
920
+ parname = key[5:]
921
+ self.entries[key].delete(0, tk.END)
922
+ self.entries[key].insert(0, str(info["value"]))
923
+
924
+ def get_result(self):
925
+ """Return the updated dictionary"""
926
+ return self.result_dict
927
+
928
+ def update_colour_param_run1_run2(self):
929
+ """Update the foreground color of run1 and run2 entries."""
930
+ # Highlight run2 parameter if different from run1
931
+ for key in self.param_dict.keys():
932
+ if key.startswith("run1_"):
933
+ parname = key[5:]
934
+ if key in self.entries and "run2_" + parname in self.entries:
935
+ if self.entries[key].get() != self.entries["run2_" + parname].get():
936
+ self.entries["run2_" + parname].config(fg="red")
937
+ else:
938
+ self.entries["run2_" + parname].config(fg="black")