dragon-ml-toolbox 3.10.2__py3-none-any.whl → 3.11.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.

Potentially problematic release.


This version of dragon-ml-toolbox might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dragon-ml-toolbox
3
- Version: 3.10.2
3
+ Version: 3.11.0
4
4
  Summary: A collection of tools for data science and machine learning projects.
5
5
  Author-email: Karl Loza <luigiloza@gmail.com>
6
6
  License-Expression: MIT
@@ -1,7 +1,7 @@
1
- dragon_ml_toolbox-3.10.2.dist-info/licenses/LICENSE,sha256=2uUFNy7D0TLgHim1K5s3DIJ4q_KvxEXVilnU20cWliY,1066
2
- dragon_ml_toolbox-3.10.2.dist-info/licenses/LICENSE-THIRD-PARTY.md,sha256=lY4_rJPnLnMu7YBQaY-_iz1JRDcLdQzNCyeLAF1glJY,1837
1
+ dragon_ml_toolbox-3.11.0.dist-info/licenses/LICENSE,sha256=2uUFNy7D0TLgHim1K5s3DIJ4q_KvxEXVilnU20cWliY,1066
2
+ dragon_ml_toolbox-3.11.0.dist-info/licenses/LICENSE-THIRD-PARTY.md,sha256=lY4_rJPnLnMu7YBQaY-_iz1JRDcLdQzNCyeLAF1glJY,1837
3
3
  ml_tools/ETL_engineering.py,sha256=yeZsW_7zRvEcuMZbM4E2GV1dxwBoWIeJAcFFk2AK0fY,39502
4
- ml_tools/GUI_tools.py,sha256=bsav1gBo8Pj6bnGo72Bd5RmlZljzVyZrQLLHf6ZZdjM,21500
4
+ ml_tools/GUI_tools.py,sha256=g3zZGpGG0IR1lll5ngn3TDE10xy9kdZJ3RBrV3tQBUU,36135
5
5
  ml_tools/MICE_imputation.py,sha256=rYqvwQDVtoAJJ0agXWoGzoZEHedWiA6QzcEKEIkiZ08,11388
6
6
  ml_tools/ML_callbacks.py,sha256=g_9nSzoA22UJOQZCPKeDz-Ayh0ECFZLzRd6rZ8SokrE,13080
7
7
  ml_tools/ML_evaluation.py,sha256=oiDV6HItQloUUKCUpltV-2pogubWLBieGpc-VUwosAQ,10106
@@ -14,13 +14,13 @@ ml_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  ml_tools/_pytorch_models.py,sha256=bpWZsrSwCvHJQkR6UfoPpElsMv9AvmiNErNHC8NYB_I,10132
15
15
  ml_tools/data_exploration.py,sha256=M7bn2q5XN9zJZJGAmMMFSFFZh8LGzC2arFelrXw3N6Q,25241
16
16
  ml_tools/datasetmaster.py,sha256=S3PKHNQZ9cyAOck8xQltVLZhaD1gFLfgHFL-aRjz4JU,30077
17
- ml_tools/ensemble_learning.py,sha256=0Ld6jwVRthG-IgtEKw68Hh1K5G-Jx1Sk5MdXnmvKL9M,45663
17
+ ml_tools/ensemble_learning.py,sha256=D-9IbOKtCvyAB-LbPu3sdSRtdp0RZIcQEZcyMnarHmQ,45758
18
18
  ml_tools/handle_excel.py,sha256=lwds7rDLlGSCWiWGI7xNg-Z7kxAepogp0lstSFa0590,12949
19
- ml_tools/keys.py,sha256=pwn7IkqQ00r1rspq7vV2fHJVSg5TTOQ_cPTqM-PewGE,536
19
+ ml_tools/keys.py,sha256=A3mLrtLZrxL27whAs2F1GPqZ1KzJpxBp6QbhxY5ioPI,636
20
20
  ml_tools/logger.py,sha256=UkbiU9ihBhw9VKyn3rZzisdClWV94EBV6B09_D0iUU0,6026
21
21
  ml_tools/path_manager.py,sha256=OCpESgdftbi6mOxetDMIaHhazt4N-W8pJx11X3-yNOs,8305
22
22
  ml_tools/utilities.py,sha256=FW97hMTLLxjDR1so-C-_yDm_iz2z_YfirRXjG_IwSLo,22843
23
- dragon_ml_toolbox-3.10.2.dist-info/METADATA,sha256=kBiUOEZa1iZ9jl-VzV71G5vhr-qg4-WcmRoyH_fTFgA,3274
24
- dragon_ml_toolbox-3.10.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
- dragon_ml_toolbox-3.10.2.dist-info/top_level.txt,sha256=wm-oxax3ciyez6VoO4zsFd-gSok2VipYXnbg3TH9PtU,9
26
- dragon_ml_toolbox-3.10.2.dist-info/RECORD,,
23
+ dragon_ml_toolbox-3.11.0.dist-info/METADATA,sha256=6Iaz06zWgkGQAQL4w0AdDrDSqDSNEJPnPjv3IZbyc9A,3274
24
+ dragon_ml_toolbox-3.11.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ dragon_ml_toolbox-3.11.0.dist-info/top_level.txt,sha256=wm-oxax3ciyez6VoO4zsFd-gSok2VipYXnbg3TH9PtU,9
26
+ dragon_ml_toolbox-3.11.0.dist-info/RECORD,,
ml_tools/GUI_tools.py CHANGED
@@ -3,19 +3,18 @@ from pathlib import Path
3
3
  import traceback
4
4
  import FreeSimpleGUI as sg
5
5
  from functools import wraps
6
- from typing import Any, Dict, Tuple, List, Literal, Union, Any, Optional
6
+ from typing import Any, Dict, Tuple, List, Literal, Union, Any, Optional, Callable
7
7
  from .utilities import _script_info
8
8
  import numpy as np
9
9
  from .logger import _LOGGER
10
- from abc import ABC, abstractmethod
11
10
 
12
11
 
13
12
  __all__ = [
14
13
  "ConfigManager",
15
14
  "GUIFactory",
16
15
  "catch_exceptions",
17
- "BaseFeatureHandler",
18
- "update_target_fields"
16
+ "FeatureMaster",
17
+ "GUIHandler"
19
18
  ]
20
19
 
21
20
  # --- Configuration Management ---
@@ -208,7 +207,7 @@ class GUIFactory:
208
207
  # --- General-Purpose Layout Generators ---
209
208
  def generate_continuous_layout(
210
209
  self,
211
- data_dict: Dict[str, Optional[Tuple[Union[int,float], Union[int,float]]]],
210
+ data_dict: Dict[str, Union[Tuple[Union[int,float,None], Union[int,float,None]],List[Union[int,float,None]]]],
212
211
  is_target: bool = False,
213
212
  layout_mode: Literal["grid", "row"] = 'grid',
214
213
  features_per_column: int = 4
@@ -232,9 +231,9 @@ class GUIFactory:
232
231
  columns = []
233
232
  for name, value in data_dict.items():
234
233
  if value is None:
235
- val_min, val_max = None, None
236
- if not is_target:
237
- raise ValueError(f"Feature '{name}' was assigned a 'None' value. It is not defined as a target.")
234
+ raise ValueError(f"Feature '{name}' was assigned a 'None' value.")
235
+ elif len(value) != 2:
236
+ raise ValueError(f"Feature '{name}' must provide exactly 2 values.")
238
237
  else:
239
238
  val_min, val_max = value
240
239
  key = name
@@ -268,7 +267,7 @@ class GUIFactory:
268
267
 
269
268
  def generate_combo_layout(
270
269
  self,
271
- data_dict: Dict[str, List[Any]],
270
+ data_dict: Dict[str, Union[List[Any],Tuple[Any,...]]],
272
271
  layout_mode: Literal["grid", "row"] = 'grid',
273
272
  features_per_column: int = 4
274
273
  ) -> List[List[sg.Column]]:
@@ -357,175 +356,487 @@ def catch_exceptions(show_popup: bool = True):
357
356
  return decorator
358
357
 
359
358
 
360
- # --- Inference Helper ---
361
- class BaseFeatureHandler(ABC):
359
+ # --- Feature Handler ---
360
+ class FeatureMaster:
362
361
  """
363
- An abstract base class that defines the template for preparing a model input feature vector to perform inference, from GUI inputs.
364
-
365
- A subclass must implement the `gui_input_map` property and the `process_categorical` method.
362
+ Manages and organizes feature definitions for a machine learning model.
363
+
364
+ This class serves as a centralized registry for all features and targets
365
+ used by a model. It is designed to bridge the gap between a user-facing
366
+ application (like a GUI) and the underlying model's data representation.
367
+
368
+ It takes various types of features (continuous, binary, one-hot encoded,
369
+ categorical) and targets, processing them into two key formats:
370
+ 1. A mapping from a user-friendly "GUI name" to the corresponding "model name"
371
+ used in the dataset or model training.
372
+ 2. A structure containing the acceptable values or ranges for each feature,
373
+ suitable for populating GUI elements like sliders, dropdowns, or checkboxes.
374
+
375
+ By separating the GUI representation from the model's internal logic, this
376
+ class simplifies the process of building user interfaces for model interaction
377
+ and ensures that user input is correctly formatted. At least one type of
378
+ feature must be provided upon initialization.
379
+
380
+ Properties are available to access the processed mappings and GUI-ready values
381
+ for each feature type.
366
382
  """
367
- def __init__(self, expected_columns_in_order: list[str]):
383
+ def __init__(self,
384
+ targets: Dict[str, str],
385
+ continuous_features: Optional[Dict[str, Tuple[str, float, float]]] = None,
386
+ binary_features: Optional[Dict[str, str]] = None,
387
+ one_hot_features: Optional[Dict[str, Dict[str, str]]] = None,
388
+ categorical_features: Optional[List[Tuple[str, str, Dict[str, int]]]] = None) -> None:
368
389
  """
369
- Validates and stores the feature names in the order the model expects.
370
-
390
+ Initializes the FeatureMaster instance by processing feature and target definitions.
391
+
392
+ This constructor creates internal mappings to translate between GUI-friendly names and model-specific feature names. It also
393
+ prepares data structures needed to populate UI components.
394
+
371
395
  Args:
372
- expected_columns_in_order (List[str]): A list of strings with the feature names in the correct order.
396
+ targets (Dict[str, str]):
397
+ A dictionary defining the model's target variables.
398
+ - **key** (str): The name to be displayed in the GUI.
399
+ - **value** (str): The corresponding column name in the model's dataset.
400
+
401
+ continuous_features (Dict[str, Tuple[str, float, float]]):
402
+ A dictionary for continuous numerical features.
403
+ - **key** (str): The name to be displayed in the GUI (e.g., for a slider).
404
+ - **value** (Tuple[str, float, float]): A tuple containing:
405
+ - `[0]` (str): The model's internal feature name.
406
+ - `[1]` (float): The minimum allowed value (inclusive).
407
+ - `[2]` (float): The maximum allowed value (inclusive).
408
+
409
+ binary_features (Dict[str, str]):
410
+ A dictionary for binary (True/False) features.
411
+ - **key** (str): The name to be displayed in the GUI (e.g., for a checkbox).
412
+ - **value** (str): The model's internal feature name.
413
+
414
+ one_hot_features (Dict[str, Dict[str, str]]):
415
+ A dictionary for features that will be one-hot encoded from a single
416
+ categorical input.
417
+ - **key** (str): The name for the group to be displayed in the GUI (e.g.,
418
+ for a dropdown menu).
419
+ - **value** (Dict[str, str]): A nested dictionary where:
420
+ - key (str): The user-selectable option (e.g., 'Category A').
421
+ - value (str): The corresponding model column name that will be
422
+ set to 1.
423
+
424
+ categorical_features (List[Tuple[str, str, Dict[str, int]]]):
425
+ A list for ordinal or label-encoded categorical features.
426
+ - **Each element is a tuple** containing:
427
+ - `[0]` (str): The name to be displayed in the GUI (e.g., for a
428
+ dropdown menu).
429
+ - `[1]` (str): The model's internal feature name.
430
+ - `[2]` (Dict[str, int]): A dictionary mapping the user-selectable
431
+ options to their corresponding integer values.
373
432
  """
374
- # --- Validation Logic ---
375
- if not isinstance(expected_columns_in_order, list):
376
- raise TypeError("Input 'expected_columns_in_order' must be a list.")
433
+ # Validation
434
+ if continuous_features is None and binary_features is None and one_hot_features is None and categorical_features is None:
435
+ raise ValueError("No features provided.")
436
+
437
+ # Targets
438
+ self._targets_values = self._handle_targets(targets)
439
+ self._targets_mapping = targets
440
+
441
+ # continuous features
442
+ if continuous_features is not None:
443
+ self._continuous_values, self._continuous_mapping = self._handle_continuous_features(continuous_features)
444
+ self.has_continuous = True
445
+ else:
446
+ self._continuous_values, self._continuous_mapping = None, None
447
+ self.has_continuous = False
448
+
449
+ # binary features
450
+ if binary_features is not None:
451
+ self._binary_values = self._handle_binary_features(binary_features)
452
+ self._binary_mapping = binary_features
453
+ self.has_binary = True
454
+ else:
455
+ self._binary_values, self._binary_mapping = None, None
456
+ self.has_binary = False
457
+
458
+ # one-hot features
459
+ if one_hot_features is not None:
460
+ self._one_hot_values = self._handle_one_hot_features(one_hot_features)
461
+ self._one_hot_mapping = one_hot_features
462
+ self.has_one_hot = True
463
+ else:
464
+ self._one_hot_values, self._one_hot_mapping = None, None
465
+ self.has_one_hot = False
466
+
467
+ # categorical features
468
+ if categorical_features is not None:
469
+ self._categorical_values, self._categorical_mapping = self._handle_categorical_features(categorical_features)
470
+ self.has_categorical = True
471
+ else:
472
+ self._categorical_values, self._categorical_mapping = None, None
473
+ self.has_categorical = False
377
474
 
378
- if not all(isinstance(col, str) for col in expected_columns_in_order):
379
- raise TypeError("All elements in the 'expected_columns_in_order' list must be strings.")
380
- # -----------------------
475
+ # all features attribute
476
+ self._all_features = self._get_all_gui_features()
381
477
 
382
- self._model_feature_order = expected_columns_in_order
478
+ def _handle_targets(self, targets: Dict[str, str]):
479
+ # Make dictionary GUI name: range values
480
+ gui_values: dict[str, tuple[None,None]] = {gui_key: (None, None) for gui_key in targets.keys()}
481
+ # Map GUI name to Model name (same as input)
482
+ return gui_values
383
483
 
484
+ def _handle_continuous_features(self, continuous_features: Dict[str, Tuple[str, float, float]]):
485
+ # Make dictionary GUI name: range values
486
+ gui_values: dict[str, tuple[float,float]] = {gui_key: (tuple_values[1], tuple_values[2]) for gui_key, tuple_values in continuous_features.items()}
487
+ # Map GUI name to Model name
488
+ gui_to_model: dict[str,str] = {gui_key: tuple_values[0] for gui_key, tuple_values in continuous_features.items()}
489
+ return gui_values, gui_to_model
490
+
491
+ def _handle_binary_features(self, binary_features: Dict[str, str]):
492
+ # Make dictionary GUI name: range values
493
+ gui_values: dict[str, tuple[Literal["False"],Literal["True"]]] = {gui_key: ("False", "True") for gui_key in binary_features.keys()}
494
+ # Map GUI name to Model name (same as input)
495
+ return gui_values
496
+
497
+ def _handle_one_hot_features(self, one_hot_features: Dict[str, Dict[str,str]]):
498
+ # Make dictionary GUI name: range values
499
+ gui_values: dict[str, tuple[str,...]] = {gui_key: tuple(nested_dict.keys()) for gui_key, nested_dict in one_hot_features.items()}
500
+ # Map GUI name to Model name and preserve internal mapping (same as input)
501
+ return gui_values
502
+
503
+ def _handle_categorical_features(self, categorical_features: List[Tuple[str, str, Dict[str, int]]]):
504
+ # Make dictionary GUI name: range values
505
+ gui_values: dict[str, tuple[str,...]] = {gui_key: tuple(gui_options.keys()) for gui_key, _, gui_options in categorical_features}
506
+ # Map GUI name to Model name and preserve internal mapping
507
+ gui_to_model: dict[str, tuple[str, dict[str, int]]] = {gui_key: (model_key, gui_options) for gui_key, model_key, gui_options in categorical_features}
508
+ return gui_values, gui_to_model
509
+
510
+ def _get_all_gui_features(self) -> dict[str,Any]:
511
+ all_dict: dict[str,Any] = dict()
512
+ # Add all feature GUI keys
513
+ if self._continuous_mapping is not None:
514
+ all_dict.update(self._continuous_mapping)
515
+ if self._binary_mapping is not None:
516
+ all_dict.update(self._binary_mapping)
517
+ if self._one_hot_mapping is not None:
518
+ all_dict.update(self._one_hot_mapping)
519
+ if self._categorical_mapping is not None:
520
+ all_dict.update(self._categorical_mapping)
521
+ return all_dict
522
+
384
523
  @property
385
- @abstractmethod
386
- def gui_input_map(self) -> Dict[str, Literal["continuous","categorical"]]:
524
+ def all_features(self):
387
525
  """
388
- Must be implemented by the subclass.
389
-
390
- Should return a dictionary mapping each GUI input name to its type ('continuous' or 'categorical').
526
+ A merged dictionary of all feature mappings.
391
527
 
392
- _Example:_
393
- ```python
394
- {
395
- 'Temperature': 'continuous',
396
- 'Material Type': 'categorical'
397
- }
398
- ```
528
+ The value type varies based on the feature type (str, dict, or tuple).
529
+
530
+ Structure:
531
+ Dict[str, Any]
399
532
  """
400
- pass
533
+ return self._all_features
401
534
 
402
535
  @property
403
- @abstractmethod
404
- def map_gui_to_real(self) -> Dict[str,str]:
536
+ def targets(self):
405
537
  """
406
- Must be implemented by the subclass.
407
-
408
- Should return a dictionary mapping each GUI continuous feature name to its expected model feature name.
538
+ The mapping for target variables from GUI name to model name.
409
539
 
410
- _Example:_
411
- ```python
412
- {
413
- 'Temperature (K)': 'temperature_k',
414
- 'Pressure (Pa)': 'pressure_pa'
415
- }
416
- ```
540
+ Structure:
541
+ Dict[str, str]
542
+ """
543
+ return self._targets_mapping
544
+
545
+ @property
546
+ def targets_gui(self):
547
+ """
548
+ The GUI value structure for targets.
549
+
550
+ Structure:
551
+ Dict[str, Tuple[None, None]]
552
+ """
553
+ return self._targets_values
554
+
555
+ @property
556
+ def continuous(self):
557
+ """
558
+ The mapping for continuous features from GUI name to model name.
559
+
560
+ Structure:
561
+ Dict[str, str]
562
+ """
563
+ if self._continuous_mapping is not None:
564
+ return self._continuous_mapping
565
+
566
+ @property
567
+ def continuous_gui(self):
568
+ """
569
+ The GUI value ranges (min, max) for continuous features.
570
+
571
+ Structure:
572
+ Dict[str, Tuple[float, float]]
573
+ """
574
+ if self._continuous_values is not None:
575
+ return self._continuous_values
576
+
577
+ @property
578
+ def binary(self):
579
+ """
580
+ The mapping for binary features from GUI name to model name.
581
+
582
+ Structure:
583
+ Dict[str, str]
584
+ """
585
+ if self._binary_mapping is not None:
586
+ return self._binary_mapping
587
+
588
+ @property
589
+ def binary_gui(self):
417
590
  """
418
- pass
591
+ The GUI options ('False', 'True') for binary features.
592
+
593
+ Structure:
594
+ Dict[str, Tuple['False', 'True']]
595
+ """
596
+ if self._binary_values is not None:
597
+ return self._binary_values
419
598
 
420
- @abstractmethod
421
- def process_categorical(self, gui_feature_name: str, chosen_value: Any) -> Dict[str, float]:
599
+ @property
600
+ def one_hot(self):
601
+ """
602
+ The mapping for one-hot encoded features.
603
+
604
+ {"GUI NAME": {"GUI OPTION 1": "model_column"}}
605
+
606
+ Structure:
607
+ Dict[str, Dict[str, str]]
608
+ """
609
+ if self._one_hot_mapping is not None:
610
+ return self._one_hot_mapping
611
+
612
+ @property
613
+ def one_hot_gui(self):
614
+ """
615
+ The GUI options for one-hot encoded feature groups.
616
+
617
+ Structure:
618
+ Dict[str, Tuple[str, ...]]
422
619
  """
423
- Must be implemented by the subclass.
620
+ if self._one_hot_values is not None:
621
+ return self._one_hot_values
424
622
 
425
- Should take a GUI categorical feature name and its chosen value, and return a dictionary mapping the one-hot-encoded/binary real feature names to their
426
- float values (as expected by the inference model).
623
+ @property
624
+ def categorical(self):
625
+ """
626
+ The mapping for categorical features.
427
627
 
428
- _Example:_
429
- ```python
430
- # GUI input: "Material Type"
431
- # GUI values: "Steel", "Aluminum", "Titanium"
432
- {
433
- "is_steel": 0,
434
- "is_aluminum": 1,
435
- "is_titanium": 0,
436
- }
437
- ```
628
+ {"GUI NAME": ("model_column", {"GUI OPTION 1": column_value})}
629
+
630
+ Structure:
631
+ Dict[str, Tuple[str, Dict[str, int]]]
438
632
  """
439
- pass
633
+ if self._categorical_mapping is not None:
634
+ return self._categorical_mapping
635
+
636
+ @property
637
+ def categorical_gui(self):
638
+ """
639
+ The GUI options for categorical features.
640
+
641
+ Structure:
642
+ Dict[str, Tuple[str, ...]]
643
+ """
644
+ if self._categorical_values is not None:
645
+ return self._categorical_values
646
+
647
+
648
+ # --- GUI-Model API ---
649
+ class GUIHandler:
650
+ """
651
+ Translates data between a GUI and a machine learning model.
440
652
 
441
- def _process_continuous(self, gui_feature_name: str, chosen_value: Any) -> Tuple[str, float]:
653
+ This class acts as the primary interface between a user-facing application
654
+ (FreeSimpleGUI) and the model's expected data format. It uses a `FeatureMaster` instance to correctly process
655
+ and encode user inputs.
656
+
657
+ Its main responsibilities are:
658
+ 1. To take raw values from GUI elements and, using the definitions from
659
+ `FeatureMaster`, convert them into a single, ordered `numpy.ndarray`
660
+ that can be fed directly into a model for inference.
661
+ 2. To take the results of a model's inference and update the
662
+ corresponding target fields in the GUI to display the prediction.
663
+
664
+ This handler ensures a clean separation of concerns, where the GUI is
665
+ only responsible for presentation, and the model sees correctly formatted numerical data.
666
+ """
667
+ def __init__(self, feature_handler: FeatureMaster, model_expected_features: list[str]) -> None:
442
668
  """
443
- Maps GUI names to model expected names and casts the value to float.
669
+ Initializes the GUIHandler.
670
+
671
+ Args:
672
+ feature_handler (FeatureMaster):
673
+ An initialized instance of the `FeatureMaster` class. This object
674
+ contains all the necessary mappings and definitions for the model's
675
+ features and targets.
676
+ model_expected_features (list[str]):
677
+ A list of strings specifying the exact names of the features the
678
+ machine learning model expects in its input vector. The **order**
679
+ of features in this list is critical, as it dictates the final
680
+ column order of the output numpy array.
681
+
682
+ Raises:
683
+ TypeError: If `model_expected_features` is not a list or if any of its elements are not strings.
684
+ """
685
+ if not isinstance(model_expected_features, list):
686
+ raise TypeError("Input 'model_expected_features' must be a list.")
687
+ if not all(isinstance(col, str) for col in model_expected_features):
688
+ raise TypeError("All elements in the 'model_expected_features' must be strings.")
444
689
 
445
- Should not be overridden by subclasses.
690
+ # Model expected features
691
+ self.model_expected_features = tuple(model_expected_features)
692
+ # Feature master instance
693
+ self.master = feature_handler
694
+
695
+ def _process_continuous(self, gui_feature: str, chosen_value: Any) -> Tuple[str,float]:
696
+ """
697
+ Maps GUI name to model expected name and casts the value to float.
446
698
  """
447
699
  try:
448
- real_name = self.map_gui_to_real[gui_feature_name]
700
+ model_name = self.master.continuous[gui_feature]
449
701
  float_value = float(chosen_value)
450
702
  except KeyError as e:
451
- _LOGGER.error(f"No matching name for '{gui_feature_name}'. Check the 'map_gui_to_real' implementation.")
703
+ _LOGGER.error(f"No matching name for '{gui_feature}' defined as continuous.")
452
704
  raise e
453
705
  except (ValueError, TypeError) as e2:
454
- _LOGGER.error(f"Invalid number conversion for '{chosen_value}' of '{gui_feature_name}'.")
706
+ _LOGGER.error(f"Invalid number conversion for '{chosen_value}' of '{gui_feature}'.")
455
707
  raise e2
456
708
  else:
457
- return real_name, float_value
458
-
459
- def __call__(self, window_values: Dict[str, Any]) -> np.ndarray:
709
+ return model_name, float_value
710
+
711
+ def _process_binary(self, gui_feature: str, chosen_value: str) -> Tuple[str,int]:
712
+ """
713
+ Maps GUI name to model expected name and casts the value to binary (0,1).
460
714
  """
461
- Performs the full vector preparation, returning a 1D numpy array.
715
+ try:
716
+ model_name = self.master.binary[gui_feature]
717
+ binary_mapping_keys = self.master.binary_gui[gui_feature]
718
+ except KeyError as e:
719
+ _LOGGER.error(f"No matching name for '{gui_feature}' defined as binary.")
720
+ raise e
721
+ else:
722
+ mapping_dict = {
723
+ binary_mapping_keys[0]: 0,
724
+ binary_mapping_keys[1]: 1
725
+ }
726
+ result = mapping_dict[chosen_value]
727
+ return model_name, result
462
728
 
463
- Should not be overridden by subclasses.
729
+ def _process_one_hot(self, gui_feature: str, chosen_value: str) -> Dict[str,int]:
464
730
  """
465
- # Stage 1: Process GUI inputs into a dictionary
466
- processed_features: Dict[str, float] = {}
467
- for gui_name, feature_type in self.gui_input_map.items():
468
- chosen_value = window_values.get(gui_name)
731
+ Maps GUI names to model expected names and casts values to one-hot encoding.
732
+ """
733
+ try:
734
+ one_hot_mapping = self.master.one_hot[gui_feature]
735
+ except KeyError as e:
736
+ _LOGGER.error(f"No matching name for '{gui_feature}' defined as one-hot.")
737
+ raise e
738
+ else:
739
+ mapped_chosen_value = one_hot_mapping[chosen_value]
740
+ # base results mapped to 0
741
+ results = {model_key: 0 for model_key in one_hot_mapping.values()}
742
+ # update chosen value
743
+ results[mapped_chosen_value] = 1
744
+ return results
745
+
746
+ def _process_categorical(self, gui_feature: str, chosen_value: str) -> Tuple[str,int]:
747
+ """
748
+ Maps GUI name to model expected name and casts the value to a categorical number.
749
+ """
750
+ try:
751
+ categorical_tuple = self.master.categorical[gui_feature]
752
+ except KeyError as e:
753
+ _LOGGER.error(f"No matching name for '{gui_feature}' defined as categorical.")
754
+ raise e
755
+ else:
756
+ model_name = categorical_tuple[0]
757
+ categorical_mapping = categorical_tuple[1]
758
+ result = categorical_mapping[chosen_value]
759
+ return model_name, result
760
+
761
+ def update_target_fields(self, window: sg.Window, inference_results: Dict[str, Any]):
762
+ """
763
+ Updates the GUI's target fields with inference results.
764
+
765
+ Args:
766
+ window (sg.Window): The application's window object.
767
+ inference_results (dict): A dictionary where keys are target names (as used by the model) and values are the predicted results to update.
768
+ """
769
+ # Target values to update
770
+ gui_targets_values = {gui_key: inference_results[model_key] for gui_key, model_key in self.master.targets.items()}
771
+
772
+ # Update window
773
+ for gui_key, result in gui_targets_values.items():
774
+ # Format numbers to 2 decimal places, leave other types as-is
775
+ display_value = f"{result:.2f}" if isinstance(result, (int, float)) else result
776
+ window[gui_key].update(display_value) # type: ignore
469
777
 
778
+ def _call_subprocess(self, window_values: Dict[str,Any], master_feature: Dict[str,str], processor: Callable) -> Dict[str, Union[float,int]]:
779
+ processed_features_subset: Dict[str, Union[float,int]] = dict()
780
+
781
+ for gui_name in master_feature.keys():
782
+ chosen_value = window_values.get(gui_name)
470
783
  # value validation
471
784
  if chosen_value is None or str(chosen_value) == '':
472
785
  raise ValueError(f"GUI input '{gui_name}' is missing a value.")
786
+ # process value
787
+ raw_result = processor(gui_name, chosen_value)
788
+ if isinstance(raw_result, tuple):
789
+ model_name, result = raw_result
790
+ processed_features_subset[model_name] = result
791
+ elif isinstance(raw_result, dict):
792
+ processed_features_subset.update(raw_result)
793
+ else:
794
+ raise TypeError(f"Processor returned an unrecognized type: {type(raw_result)}")
795
+
796
+ return processed_features_subset
473
797
 
474
- # process continuous
475
- if feature_type == 'continuous':
476
- mapped_name, float_value = self._process_continuous(gui_name, chosen_value)
477
- processed_features[mapped_name] = float_value
798
+ def process_features(self, window_values: Dict[str, Any]) -> np.ndarray:
799
+ """
800
+ Translates GUI values to a model-expected input array, returning a 1D numpy array.
801
+ """
802
+ # Stage 1: Process GUI inputs into a dictionary
803
+ processed_features: Dict[str, Union[float,int]] = {}
804
+
805
+ if self.master.has_continuous:
806
+ processed_subset = self._call_subprocess(window_values=window_values,
807
+ master_feature=self.master.continuous,
808
+ processor=self._process_continuous)
809
+ processed_features.update(processed_subset)
810
+
811
+ if self.master.has_binary:
812
+ processed_subset = self._call_subprocess(window_values=window_values,
813
+ master_feature=self.master.binary,
814
+ processor=self._process_binary)
815
+ processed_features.update(processed_subset)
816
+
817
+ if self.master.has_one_hot:
818
+ processed_subset = self._call_subprocess(window_values=window_values,
819
+ master_feature=self.master.one_hot,
820
+ processor=self._process_one_hot)
821
+ processed_features.update(processed_subset)
478
822
 
479
- # process categorical
480
- elif feature_type == 'categorical':
481
- feature_dict = self.process_categorical(gui_name, chosen_value)
482
- processed_features.update(feature_dict)
823
+ if self.master.has_categorical:
824
+ processed_subset = self._call_subprocess(window_values=window_values,
825
+ master_feature=self.master.categorical,
826
+ processor=self._process_categorical)
827
+ processed_features.update(processed_subset)
483
828
 
484
829
  # Stage 2: Assemble the final vector using the model's required order
485
- final_vector: List[float] = []
830
+ final_vector: List[float] = list()
486
831
 
487
832
  try:
488
- for feature_name in self._model_feature_order:
833
+ for feature_name in self.model_expected_features:
489
834
  final_vector.append(processed_features[feature_name])
490
835
  except KeyError as e:
491
- raise RuntimeError(
492
- f"Configuration Error: Implemented methods failed to generate "
493
- f"the required model feature: '{e}'"
494
- f"Check the gui_input_map and process_categorical logic."
495
- )
496
-
836
+ raise RuntimeError(f"Configuration Error: Implemented methods failed to generate the required model feature: '{e}'")
837
+
497
838
  return np.array(final_vector, dtype=np.float32)
498
-
499
-
500
- def update_target_fields(window: sg.Window, results_dict: Dict[str, Any], map_model_to_gui: Optional[Dict[str,str]]):
501
- """
502
- Updates the GUI's target fields with inference results.
503
-
504
- Args:
505
- window (sg.Window): The application's window object.
506
- results_dict (dict): A dictionary where keys are target names (as expected by the GUI) and values are the predicted results to update.
507
- map_model_to_gui (dict | None): Map `results_dict.keys()` from model target names to GUI target names, if gui names were customized.
508
- """
509
- if map_model_to_gui is not None:
510
- # Validation
511
- if len(map_model_to_gui) != len(results_dict):
512
- _LOGGER.error(f"Expected a mapping for {len(results_dict)} targets, but received {len(map_model_to_gui)} target map names.")
513
- raise ValueError
514
-
515
- # new dictionary with GUI keys and corresponding result values
516
- display_dict = {
517
- gui_key: results_dict[model_key]
518
- for model_key, gui_key in map_model_to_gui.items()
519
- }
520
- else:
521
- # If no map is provided, use given result keys
522
- display_dict = results_dict
523
-
524
- for key, result in display_dict.items():
525
- # Format numbers to 2 decimal places, leave other types as-is
526
- display_value = f"{result:.2f}" if isinstance(result, (int, float)) else result
527
- window[key].update(display_value) # type: ignore
528
-
839
+
529
840
 
530
841
  def info():
531
842
  _script_info(__all__)
@@ -1026,7 +1026,8 @@ class InferenceHandler:
1026
1026
  else: # Classification
1027
1027
  label = model.predict(features)[0]
1028
1028
  probabilities = model.predict_proba(features)[0]
1029
- results[target_name] = {"label": label, "probabilities": probabilities}
1029
+ results[target_name] = {ModelSaveKeys.CLASSIFICATION_LABEL: label,
1030
+ ModelSaveKeys.CLASSIFICATION_PROBABILITIES: probabilities}
1030
1031
 
1031
1032
  if self.verbose:
1032
1033
  _LOGGER.info("✅ Inference process complete.")
ml_tools/keys.py CHANGED
@@ -17,9 +17,12 @@ class LogKeys:
17
17
  class ModelSaveKeys:
18
18
  """
19
19
  Used internally for ensemble_learning module.
20
-
21
- Keys used for serializing a trained model metadata.
22
20
  """
21
+ # Serializing a trained model metadata.
23
22
  MODEL = "model"
24
23
  FEATURES = "feature_names"
25
24
  TARGET = "target_name"
25
+
26
+ # Classification keys
27
+ CLASSIFICATION_LABEL = "label"
28
+ CLASSIFICATION_PROBABILITIES = "probabilities"