radnn 0.0.3__py3-none-any.whl → 0.0.5__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.
radnn/__init__.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # Version 0.0.3 [2025-01-25]
2
- __version__ = "0.0.3"
2
+ # Version 0.0.5 [2025-01-26]
3
+ __version__ = "0.0.5"
3
4
 
4
5
 
5
6
  from .system import FileStore, FileSystem
radnn/core.py CHANGED
@@ -16,19 +16,7 @@ def is_opencv_installed():
16
16
 
17
17
  # ----------------------------------------------------------------------------------------------------------------------
18
18
  def system_name() -> str:
19
- sPlatform = platform.system()
20
- sHostName = socket.gethostname()
21
- #sIPAddress = socket.gethostbyname(sHostName)
22
-
23
- bIsColab = "google.colab" in sys.modules
24
- if bIsColab:
25
- sResult = "(colab)" #+ sIPAddress
26
- else:
27
- if sPlatform == "Windows":
28
- sResult = "(windows)-" + sHostName
29
- else:
30
- sResult = "(linux)-" + sHostName
31
- return sResult
19
+ return MLInfrastructure.host_name(False)
32
20
  # ----------------------------------------------------------------------------------------------------------------------
33
21
  def now_iso():
34
22
  return datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
@@ -42,4 +30,45 @@ def shell_command_output(command_string):
42
30
  oResult.append(sLine)
43
31
 
44
32
  return oResult
45
- # ----------------------------------------------------------------------------------------------------------------------
33
+ # ----------------------------------------------------------------------------------------------------------------------
34
+
35
+
36
+
37
+
38
+
39
+
40
+ #TODO: macOS support
41
+
42
+ class MLInfrastructure(object):
43
+ # ----------------------------------------------------------------------------------------------------------------------
44
+ @classmethod
45
+ def is_linux(cls):
46
+ return not (cls.is_windows or cls.is_colab)
47
+ # ----------------------------------------------------------------------------------------------------------------------
48
+ @classmethod
49
+ def is_windows(cls):
50
+ sPlatform = platform.system()
51
+ return (sPlatform == "Windows")
52
+ # ----------------------------------------------------------------------------------------------------------------------
53
+ @classmethod
54
+ def is_colab(cls):
55
+ return "google.colab" in sys.modules
56
+ # ----------------------------------------------------------------------------------------------------------------------
57
+ @classmethod
58
+ def host_name(cls, is_using_ip_address=True) -> str:
59
+ sPlatform = platform.system()
60
+ sHostName = socket.gethostname()
61
+ sIPAddress = socket.gethostbyname(sHostName)
62
+
63
+ bIsColab = "google.colab" in sys.modules
64
+ if bIsColab:
65
+ sResult = "(colab)"
66
+ if is_using_ip_address:
67
+ sResult += "-" + sIPAddress
68
+ else:
69
+ if sPlatform == "Windows":
70
+ sResult = "(windows)-" + sHostName
71
+ else:
72
+ sResult = "(linux)-" + sHostName
73
+ return sResult
74
+ # ----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1 @@
1
+ from .evaluate_classification import EvaluateClassification
@@ -0,0 +1,113 @@
1
+ # ......................................................................................
2
+ # MIT License
3
+
4
+ # Copyright (c) 2020-2025 Pantelis I. Kaplanoglou
5
+
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+
24
+ # ......................................................................................
25
+
26
+ import numpy as np
27
+ from sklearn import metrics
28
+
29
+
30
+ # ==============================================================================================================================
31
+ class EvaluateClassification(object):
32
+ # --------------------------------------------------------------------------------------------------------------
33
+ def __init__(self, actual_classes, predicted_classes, probabilities=None):
34
+ self.actual_classes = actual_classes
35
+ self.predicted_classes = predicted_classes
36
+ self.confusion_matrix = np.asarray(metrics.confusion_matrix(self.actual_classes, self.predicted_classes))
37
+
38
+ self.accuracy = metrics.accuracy_score(self.actual_classes, self.predicted_classes)
39
+ self.precision, self.recall, self.f1score, self.support = metrics.precision_recall_fscore_support(
40
+ self.actual_classes, self.predicted_classes, average=None)
41
+ self.average_precision, self.average_recall, self.average_f1score, self.average_support = metrics.precision_recall_fscore_support(
42
+ self.actual_classes, self.predicted_classes, average='weighted')
43
+
44
+ if probabilities is not None:
45
+ self.auc = metrics.roc_auc_score(actual_classes, probabilities)
46
+ else:
47
+ self.auc = None
48
+
49
+
50
+ self.true_neg = self.confusion_matrix[0][0]
51
+ self.false_pos = self.confusion_matrix[0][1]
52
+ self.false_neg = self.confusion_matrix[1][0]
53
+ self.true_pos = self.confusion_matrix[1][1]
54
+
55
+ self.class_count = None
56
+ # --------------------------------------------------------------------------------------------------------------
57
+ def print_confusion_matrix(self):
58
+ nSize = len(self.confusion_matrix[0])
59
+ print(" Predicted ")
60
+ print(" --" + "-" * 5 * nSize)
61
+ sLabel = "Actual"
62
+ for nIndex, nRow in enumerate(self.confusion_matrix):
63
+ print(" %s | %s |" % (sLabel, " ".join(["%4d" % x for x in nRow])))
64
+ if nIndex == 0:
65
+ sLabel = " " * len(sLabel)
66
+ print(" --" + "-" * 5 * nSize)
67
+ print()
68
+ # --------------------------------------------------------------------------------------------------------------
69
+ def format_series_as_pc(self, metric_series):
70
+ oValStr = []
71
+ for x in metric_series:
72
+ sX = f"{x*100.0:.2f}"
73
+ oValStr.append(sX)
74
+
75
+ oValues = [f"{x:^7}" for x in oValStr]
76
+ return " |".join(oValues)
77
+ # --------------------------------------------------------------------------------------------------------------
78
+ def print_per_class(self, class_names=None):
79
+ if class_names is not None:
80
+ nClassCount = len(class_names.keys())
81
+ oClasses = [f"{class_names[x]:7}" for x in list(range(nClassCount))]
82
+ else:
83
+ oClasses = sorted(np.unique(self.actual_classes))
84
+ nClassCount = len(oClasses)
85
+ oClasses = [f"{x:^7}" for x in oClasses]
86
+ self.class_count = nClassCount
87
+
88
+ sClasses = " |".join(oClasses)
89
+ nRepeat = 28 + (7+2)*self.class_count
90
+ print(f" |{sClasses}|")
91
+ print("-"*nRepeat)
92
+ print(f"Per Class Recall % |{self.format_series_as_pc(self.recall[:])}|")
93
+ print(f"Per Class Precision % |{self.format_series_as_pc(self.precision[:])}|")
94
+ print("-" * nRepeat)
95
+ # --------------------------------------------------------------------------------------------------------------
96
+ def print_overall(self):
97
+ print(f"Weighted Average Recall % :{self.average_recall*100.0:.3f}")
98
+ print(f"Weighted Average Precision %:{self.average_precision*100.0:.3f}")
99
+ print(f"Accuracy % :{self.accuracy*100.0 :.3f}")
100
+ print(f"Average F1 Score % :{self.average_f1score*100.0:.3f}")
101
+ if (self.class_count == 2) and (self.auc is not None):
102
+ print(f"Area Under the Curve (AUC):{self.auc:.4f}")
103
+ print()
104
+ # --------------------------------------------------------------------------------------------------------------
105
+
106
+
107
+ # ==============================================================================================================================
108
+
109
+
110
+ # alias for compatibility
111
+ class CEvaluator(EvaluateClassification):
112
+ pass
113
+
@@ -1,3 +1,4 @@
1
- from .ml_experiment_config import experiment_code, MLExperimentConfig
2
-
1
+ from .ml_experiment_config import get_experiment_code, get_experiment_code_ex, experiment_number_and_variation, experiment_code_and_timestamp
2
+ from .ml_experiment_config import MLExperimentConfig
3
+ from .ml_experiment_env import MLExperimentEnv
3
4
 
@@ -25,31 +25,88 @@
25
25
 
26
26
  import os
27
27
  import json
28
+ import re
29
+ from datetime import datetime
28
30
 
31
+ from radnn.system import FileSystem
29
32
 
30
33
  # --------------------------------------------------------------------------------------
31
- def experiment_code(config_dict):
34
+ def legacy_model_code(config_dict):
35
+ if "ModelName" in config_dict:
36
+ sCode = config_dict["ModelName"]
37
+ if "ModelVariation" in config_dict:
38
+ sCode += "_" + config_dict["ModelVariation"]
39
+ if "ExperimentNumber" in config_dict:
40
+ sCode = sCode + "_%02d" % config_dict["ExperimentNumber"]
41
+ else:
42
+ raise Exception("Invalid experiment configuration. Needs at least the key 'ModelName'.")
43
+ return sCode
44
+ # --------------------------------------------------------------------------------------
45
+ def get_experiment_code(config_dict):
32
46
  if ("Experiment.BaseName" in config_dict) and ("Experiment.Number" in config_dict):
33
47
  sBaseName = config_dict["Experiment.BaseName"]
34
48
  nNumber = int(config_dict["Experiment.Number"])
35
49
  sVariation = None
36
50
  if "Experiment.Variation" in config_dict:
37
- sVariation = config_dict["Experiment.Variation"]
51
+ sVariation = str(config_dict["Experiment.Variation"])
38
52
  nFoldNumber = None
39
53
  if "Experiment.FoldNumber" in config_dict:
40
- nFoldNumber = config_dict["Experiment.FoldNumber"]
54
+ nFoldNumber = int(config_dict["Experiment.FoldNumber"])
41
55
 
42
- sCode = "%s_%02d" % (sBaseName, nNumber)
56
+ sCode = f"{sBaseName}_{nNumber:02d}"
43
57
  if sVariation is not None:
44
- sCode += ".%s" % str(sVariation)
58
+ sCode += "." + sVariation
45
59
  if nFoldNumber is not None:
46
- sCode += "-%02d" % int(nFoldNumber)
60
+ sCode += f"-{nFoldNumber:02d}"
47
61
  else:
48
62
  raise Exception("Invalid experiment configuration. Needs at least two keys 'Experiment.BaseName'\n"
49
63
  + "and `Experiment.Number`.")
50
64
 
51
65
  return sCode
52
66
  # --------------------------------------------------------------------------------------
67
+ def get_experiment_code_ex(base_name, number, variation=None, fold_number=None):
68
+ if (base_name is not None) and (number is not None):
69
+ nNumber = int(number)
70
+ sVariation = None
71
+ if variation is not None:
72
+ sVariation = str(variation)
73
+ nFoldNumber = None
74
+ if fold_number is not None:
75
+ nFoldNumber = int(fold_number)
76
+
77
+ sCode = f"{base_name}_{nNumber:02d}"
78
+ if variation is not None:
79
+ sCode += "." + sVariation
80
+ if nFoldNumber is not None:
81
+ sCode += f"-{nFoldNumber:02d}"
82
+ else:
83
+ raise Exception("Invalid experiment code parts. Needs a base name and a number.")
84
+
85
+ return sCode
86
+ # --------------------------------------------------------------------------------------
87
+ def experiment_number_and_variation(experiment_code):
88
+ if type(experiment_code) == int:
89
+ nNumber = int(experiment_code)
90
+ sVariation = None
91
+ else:
92
+ sParts = experiment_code.split(".")
93
+ nNumber = int(sParts[0])
94
+ if len(sParts) > 1:
95
+ sVariation = sParts[1]
96
+ else:
97
+ sVariation = None
98
+
99
+ return nNumber, sVariation
100
+ # --------------------------------------------------------------------------------------
101
+ def experiment_code_and_timestamp(filename):
102
+ sName, _ = os.path.splitext(os.path.split(filename)[1])
103
+ sParts = re.split(r"_", sName, 2)
104
+ sISODate = f"{sParts[0]}T{sParts[1][0:2]}:{sParts[1][2:4]}:{sParts[1][4:6]}"
105
+ sExperimentCode = sParts[2]
106
+ dRunTimestamp = datetime.fromisoformat(sISODate)
107
+ return sExperimentCode, dRunTimestamp
108
+ # --------------------------------------------------------------------------------------
109
+
53
110
 
54
111
 
55
112
 
@@ -58,27 +115,27 @@ def experiment_code(config_dict):
58
115
  # =========================================================================================================================
59
116
  class MLExperimentConfig(dict):
60
117
  # --------------------------------------------------------------------------------------
61
- def __init__(self, filename=None, expt_base_name=None, expt_number=None, expt_variation=None, expt_fold=None, hyperparams=None):
62
- self["Experiment.BaseName"] = expt_base_name
118
+ def __init__(self, filename=None, base_name=None, number=None, variation=None, fold_number=None, hyperparams=None):
119
+ self["Experiment.BaseName"] = base_name
63
120
  self.filename = filename
64
- if filename is not None:
121
+ if self.filename is not None:
65
122
  self.load()
66
123
 
67
- if expt_number is not None:
68
- self["Experiment.Number"] = expt_number
69
- if expt_variation is not None:
70
- self["Experiment.Variation"] = expt_variation
71
- if expt_fold is not None:
72
- self["Experiment.FoldNumber"] = expt_fold
124
+ if number is not None:
125
+ self["Experiment.Number"] = number
126
+ if variation is not None:
127
+ self["Experiment.Variation"] = variation
128
+ if fold_number is not None:
129
+ self["Experiment.FoldNumber"] = fold_number
73
130
 
74
131
  if hyperparams is not None:
75
132
  self.assign(hyperparams)
76
133
  # --------------------------------------------------------------------------------------
77
134
  @property
78
135
  def experiment_code(self):
79
- return experiment_code(self)
136
+ return get_experiment_code(self)
80
137
  # --------------------------------------------------------------------------------------
81
- def load(self, filename, must_exist=False):
138
+ def load(self, filename=None, must_exist=False):
82
139
  if filename is None:
83
140
  filename = self.filename
84
141
 
@@ -118,16 +175,22 @@ class MLExperimentConfig(dict):
118
175
  return self
119
176
 
120
177
  # --------------------------------------------------------------------------------------
121
- def save_config(self, filesystem, filename_only=None):
178
+ def save_config(self, fs, filename_only=None):
179
+ if isinstance(fs, FileSystem):
180
+ fs = fs.configs
181
+
122
182
  if filename_only is None:
123
- filename_only = experiment_code(self)
183
+ filename_only = get_experiment_code(self)
124
184
 
125
- sFileName = filesystem.configs.file(filename_only + ".json")
185
+ sFileName = fs.file(filename_only + ".json")
126
186
  return self.save(sFileName)
127
187
 
128
188
  # --------------------------------------------------------------------------------------
129
- def load_config(self, filesystem, filename_only):
130
- sFileName = filesystem.configs.file(filename_only + ".json")
189
+ def load_config(self, fs, filename_only):
190
+ if isinstance(fs, FileSystem):
191
+ fs = fs.configs
192
+
193
+ sFileName = fs.file(filename_only + ".json")
131
194
  return self.load(sFileName)
132
195
  # --------------------------------------------------------------------------------------
133
196
  def setDefaults(self):
@@ -0,0 +1,223 @@
1
+ # ......................................................................................
2
+ # MIT License
3
+
4
+ # Copyright (c) 2023-2025 Pantelis I. Kaplanoglou
5
+
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+
24
+ # ......................................................................................
25
+
26
+ import os
27
+ import shutil
28
+ import sys
29
+ import re
30
+
31
+ from radnn.experiment import MLExperimentConfig
32
+ from radnn.experiment import get_experiment_code_ex, experiment_number_and_variation, experiment_code_and_timestamp
33
+ from radnn.core import now_iso
34
+ from radnn.system import FileSystem, FileStore
35
+ from radnn.system.tee_logger import TeeLogger
36
+
37
+
38
+
39
+ class MLExperimentEnv(dict):
40
+
41
+ # --------------------------------------------------------------------------------------------------------------------
42
+ @classmethod
43
+ def experiment_filename_split(cls, filename):
44
+ sTryFileName, sTryExt = os.path.splitext(filename)
45
+ bIsVariationAndFold = "-" in sTryExt
46
+ if bIsVariationAndFold:
47
+ #LREXPLAINET22_MNIST_64.1-01
48
+ sMainParts = filename.split("-")
49
+ assert len(sMainParts) == 2, "Wrong experiment filename"
50
+ sFoldNumber, _ = os.path.splitext(sMainParts[1])
51
+ sParts = sMainParts[0].split("_")
52
+ sModelName = f"{sParts[0]}_{sParts[1]}"
53
+ sModelVar = sParts[2]
54
+ else:
55
+ sFileNameOnly, _ = os.path.splitext(filename)
56
+ sMainParts = sFileNameOnly.split("-")
57
+ if len(sMainParts) > 1:
58
+ sFoldNumber = sMainParts[1]
59
+ else:
60
+ sFoldNumber = None
61
+ sParts = sMainParts[0].split("_")
62
+ sModelName = f"{sParts[0]}_{sParts[1]}"
63
+ sModelVar = sParts[2]
64
+
65
+ return sModelName, sModelVar, sFoldNumber
66
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
67
+ @classmethod
68
+ def preload_config(cls, config_folder, experiment_group=None, experiment_base_name=None, experiment_variation=None, experiment_fold_number=None, experiment_filename=None):
69
+ oPrintOutput = []
70
+ oPrintOutput.append(f"[?] Experiment started at {now_iso()}")
71
+ oPrintOutput.append(f" |__ {'model':<24}: {experiment_base_name}")
72
+
73
+ dExperimentSpec = {"base_name": experiment_base_name, "variation": experiment_variation, "fold_number": experiment_fold_number}
74
+ if experiment_filename is not None:
75
+ # LREXPLAINET18_MNIST_08.1-01.json
76
+ _, sFileNameFull = os.path.split(experiment_filename)
77
+ experiment_base_name, experiment_variation, experiment_fold_number = cls.experiment_filename_split(sFileNameFull)
78
+ experiment_fold_number = int(experiment_fold_number)
79
+ dExperimentSpec = {"base_name": experiment_base_name, "variation": experiment_variation, "fold_number": experiment_fold_number}
80
+
81
+ if experiment_group is not None:
82
+ config_folder = os.path.join(config_folder, experiment_group)
83
+ oConfigFS = FileStore(config_folder, must_exist=True)
84
+
85
+ if "." in experiment_variation:
86
+ sParts = experiment_variation.split(".")
87
+ experiment_variation = f"{int(sParts[0]):02d}.{sParts[1]}"
88
+ else:
89
+ experiment_variation = f"{int(experiment_variation):02d}"
90
+ sMessage = f" |__ {'variation':<24}: {experiment_variation}"
91
+
92
+ if experiment_fold_number is not None:
93
+ experiment_variation = f"{experiment_variation}-{experiment_fold_number:02d}"
94
+ sMessage += f" fold: {experiment_fold_number}"
95
+ oPrintOutput.append(sMessage)
96
+
97
+ if experiment_filename is not None:
98
+ sExperimentFileName = experiment_filename
99
+ else:
100
+ sExperimentFileName = oConfigFS.file(f"{experiment_base_name}_{experiment_variation}.json")
101
+ oConfig = MLExperimentConfig(sExperimentFileName,p_nExperimentNumber=experiment_variation)
102
+ oPrintOutput.append(f" |__ {'configuration file':<24}: {sExperimentFileName}")
103
+
104
+ return oConfig, oPrintOutput, dExperimentSpec
105
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
106
+
107
+ # --------------------------------------------------------------------------------------------------------------------
108
+ def __init__(self, config_fs, model_fs=None, base_name=None, number=None, variation=None, fold_number=None,
109
+ experiment_filename=None, experiment_code=None, experiment_config=None, model_filestore=None):
110
+
111
+ if isinstance(config_fs, FileSystem):
112
+ oConfigFS = config_fs.configs
113
+ oModelFS = config_fs.models
114
+ else:
115
+ oConfigFS = config_fs
116
+ oModelFS = model_fs
117
+ if oModelFS is None:
118
+ oModelFS = config_fs
119
+
120
+ # ...................... | Fields | ......................
121
+ self.config_fs = oConfigFS
122
+ self.model_fs = oModelFS
123
+ self.base_name = base_name
124
+ self.number = number
125
+ self.variation = variation
126
+ self.fold_number = fold_number
127
+
128
+ if (experiment_filename is not None) or (experiment_code is not None):
129
+ self.base_name, self.number, self.variation, self.fold_number, _ = self.determine_code_parts(experiment_filename,
130
+ experiment_code, number, variation, fold_number)
131
+
132
+ self.experiment_filename = experiment_filename
133
+ if self.experiment_filename is None:
134
+ sExperimentCode = get_experiment_code_ex(self.base_name, self.number, self.variation, self.fold_number)
135
+ if experiment_code is not None:
136
+ assert sExperimentCode == experiment_code, "Re-created experiment code mismatch."
137
+ self.experiment_filename = self.config_fs.file(f"{sExperimentCode}.json")
138
+
139
+
140
+ self.is_debugable = False
141
+ self.is_retraining = False
142
+
143
+ self._config = experiment_config
144
+ if self._config is None:
145
+ self._config = MLExperimentConfig(self.experiment_filename, number=self.number)
146
+ self.experiment_fs = self.model_fs.subfs(self.experiment_code)
147
+ # ........................................................
148
+
149
+ # --------------------------------------------------------------------------------------------------------------------
150
+ def save_config(self):
151
+ self._config.save(self.experiment_filename)
152
+ # --------------------------------------------------------------------------------------------------------------------
153
+ def determine_code_parts(self, experiment_filename, experiment_code, number, variation, fold_number):
154
+ sExperimentCode = None
155
+ if experiment_code is not None:
156
+ sExperimentCode = experiment_code
157
+ elif experiment_filename is not None:
158
+ _, sFileNameFull = os.path.split(experiment_filename)
159
+ sExperimentCode, _ = os.path.splitext(sFileNameFull)
160
+
161
+ sISODate = None
162
+ if sExperimentCode is not None:
163
+ oMatch = re.match(r"^\d{4}-\d{2}-\d{2}_\d{6}_", sExperimentCode)
164
+ if oMatch is not None:
165
+ sISODate = oMatch.group()[:-1]
166
+ sExperimentCode = sExperimentCode[18:]
167
+
168
+ base_name, variation, sFoldNumber = MLExperimentEnv.experiment_filename_split(sExperimentCode)
169
+ if sFoldNumber is not None:
170
+ fold_number = int(sFoldNumber)
171
+
172
+ if number is None:
173
+ number, variation = experiment_number_and_variation(variation)
174
+
175
+ return base_name, number, variation, fold_number, sISODate
176
+ # --------------------------------------------------------------------------------------------------------------------
177
+ @property
178
+ def config(self):
179
+ return self._config
180
+ # --------------------------------------------------------------------------------------------------------------------
181
+ @property
182
+ def experiment_code(self):
183
+ return get_experiment_code_ex(self.base_name, self.number, self.variation, self.fold_number)
184
+ # --------------------------------------------------------------------------------------------------------------------
185
+ def copy_config(self, start_timestamp):
186
+ sOriginalFileName = self.experiment_filename
187
+ sNewFileName = self.experiment_fs.file(f"{start_timestamp}_{self.experiment_code}.json")
188
+ shutil.copy(sOriginalFileName, sNewFileName)
189
+ # --------------------------------------------------------------------------------------------------------------------
190
+ def move_log(self, start_timestamp, original_filename):
191
+ self.copy_config(start_timestamp)
192
+ _, original_filename_only = os.path.split(original_filename)
193
+ sNewFileName = f"{start_timestamp}_{self.experiment_code}.{original_filename_only}"
194
+ shutil.move(original_filename, self.experiment_fs.file(sNewFileName))
195
+ sys.stdout = TeeLogger(self.experiment_fs.file(sNewFileName))
196
+ # --------------------------------------------------------------------------------------------------------------------
197
+ '''
198
+ def AssignSystemParams(self, p_oParamsDict):
199
+ self.number = p_oParamsDict["ModelNumber"]
200
+ self.is_debugable = p_oParamsDict["IsDebuggable"]
201
+ self.is_retraining = p_oParamsDict["IsRetraining"]
202
+
203
+ self._config = MLExperimentConfig(self.model_fs.file(self.experiment_code + ".json"), number=self.number)
204
+ '''
205
+ # --------------------------------------------------------------------------------------------------------------------
206
+ def __str__(self)->str:
207
+ sResult = f"Experiment Code: {self.experiment_code} | Debugable: {self.is_debugable} | Retraining: {self.is_retraining}\n"
208
+ sResult += f" |___ base name: {self.base_name} , number:{self.number} , variation:{self.variation}, fold_number: {self.fold_number}\n"
209
+ sResult += f" |___ file name: {self.experiment_filename}\n"
210
+ sResult += f"Models FileStore : {self.model_fs}\n"
211
+ sResult += f"Configs FileStore : {self.config_fs}\n"
212
+ sResult += f"Experiment FileStore: {self.experiment_fs}\n"
213
+ sResult += f"Experiment Filename : {self.experiment_filename}\n"
214
+ sResult += f"Configuration\n"
215
+ sResult += "-"*40 + "\n"
216
+ sResult += str(self._config) + "\n"
217
+ return sResult
218
+ # --------------------------------------------------------------------------------------------------------------------
219
+ def __repr__(self)->str:
220
+ return self.__str__()
221
+ # --------------------------------------------------------------------------------------------------------------------
222
+
223
+
@@ -0,0 +1,4 @@
1
+ from .plot_confusion_matrix import PlotConfusionMatrix
2
+ from .plot_learning_curve import PlotLearningCurve
3
+ from .plot_roc import PlotROC
4
+ from .plot_voronoi_2d import PlotVoronoi2D
@@ -0,0 +1,54 @@
1
+ # ......................................................................................
2
+ # MIT License
3
+
4
+ # Copyright (c) 2020-2025 Pantelis I. Kaplanoglou
5
+
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+
24
+ # ......................................................................................
25
+
26
+
27
+ import matplotlib.pyplot as plt
28
+
29
+ class PlotConfusionMatrix(object):
30
+ # --------------------------------------------------------------------------------------
31
+ def __init__(self, confusion_matrix, title="Confusion Matrix"):
32
+ self.confusion_matrix = confusion_matrix
33
+ self.title = title
34
+ # --------------------------------------------------------------------------------------
35
+ def prepare(self):
36
+ fig, ax = plt.subplots(figsize=(7.5, 7.5))
37
+ ax.matshow(self.confusion_matrix, cmap=plt.cm.Blues, alpha=0.3)
38
+ for i in range(self.confusion_matrix.shape[0]):
39
+ for j in range(self.confusion_matrix.shape[1]):
40
+ ax.text(x=j, y=i, s=self.confusion_matrix[i, j], va='center', ha='center', size='xx-large')
41
+
42
+ plt.xlabel('Predicted Label', fontsize=18)
43
+ plt.ylabel('Actual Label', fontsize=18)
44
+ plt.title(self.title, fontsize=18)
45
+ return self
46
+ # --------------------------------------------------------------------------------------
47
+ def save(self, filename):
48
+ plt.savefig(filename, bbox_inches='tight')
49
+ return self
50
+ # --------------------------------------------------------------------------------------
51
+ def show(self):
52
+ plt.show()
53
+ # --------------------------------------------------------------------------------------
54
+
@@ -0,0 +1,73 @@
1
+ # ......................................................................................
2
+ # MIT License
3
+
4
+ # Copyright (c) 2020-2025 Pantelis I. Kaplanoglou
5
+
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+
24
+ # ......................................................................................
25
+
26
+ import matplotlib.pyplot as plt
27
+
28
+ class PlotLearningCurve(object):
29
+ # --------------------------------------------------------------------------------------
30
+ def __init__(self, metrics_dict, model_name):
31
+ self.metrics_dict = metrics_dict
32
+ self.model_name = model_name
33
+ print("Keys in training process log:", self.metrics_dict.keys())
34
+ # --------------------------------------------------------------------------------------
35
+ def prepare(self, metric_key="accuracy", custom_title=None, is_legend_right=False):
36
+ plt.clf()
37
+ plt.plot(self.metrics_dict[metric_key])
38
+ sValidationMetricName = "val_" + metric_key
39
+ if sValidationMetricName in self.metrics_dict:
40
+ plt.plot(self.metrics_dict[sValidationMetricName])
41
+ if custom_title is None:
42
+ plt.title(self.model_name + ' ' + metric_key)
43
+ else:
44
+ plt.title(self.model_name + ' ' + custom_title)
45
+ plt.ylabel(metric_key)
46
+ plt.xlabel("Epoch")
47
+ if is_legend_right:
48
+ plt.legend(["train", 'validation'], loc="upper right")
49
+ else:
50
+ plt.legend(["train", "validation"], loc="upper left")
51
+ return self
52
+ # --------------------------------------------------------------------------------------
53
+ def prepare_cost(self, cost_function=None):
54
+ if isinstance(cost_function, str):
55
+ sCostFunctionName = cost_function
56
+ else:
57
+ sClassName = str(cost_function.__class__)
58
+ if ("keras" in sClassName) and ("losses" in sClassName):
59
+ sCostFunctionNameParts = cost_function.name.split("_")
60
+ sCostFunctionNameParts = [x.capitalize() + " " for x in sCostFunctionNameParts]
61
+ sCostFunctionName = " ".join(sCostFunctionNameParts)
62
+
63
+ return self.prepare("loss", sCostFunctionName, True)
64
+ # --------------------------------------------------------------------------------------
65
+ def save(self, filename):
66
+ plt.savefig(filename, bbox_inches='tight')
67
+ return self
68
+ # --------------------------------------------------------------------------------------
69
+ def show(self):
70
+ plt.show()
71
+ # --------------------------------------------------------------------------------------
72
+
73
+
@@ -0,0 +1,75 @@
1
+ # ......................................................................................
2
+ # MIT License
3
+
4
+ # Copyright (c) 2024-2025 Pantelis I. Kaplanoglou
5
+
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+
24
+ # ......................................................................................
25
+
26
+ from sklearn import metrics
27
+ import matplotlib.pyplot as plt
28
+
29
+ '''
30
+ Receiver Operator Characteristics (ROC) Plot
31
+ '''
32
+ class PlotROC(object):
33
+ # --------------------------------------------------------------------------------------
34
+ def __init__(self, actual_labels, predicted_probs, title="Receiving Operator Characteristics Curve"):
35
+ self.actual_labels = actual_labels
36
+ self.predicted_probs = predicted_probs
37
+ self.title = title
38
+ # --------------------------------------------------------------------------------------
39
+ def prepare(self, true_threshold=0.5, figure_size=[6.00, 6.00], is_showing_grid=True):
40
+ plt.rcParams["figure.figsize"] = figure_size
41
+ plt.rcParams["figure.autolayout"] = True
42
+
43
+ nFPR, nTPR, nThresholds = metrics.roc_curve(self.actual_labels, self.predicted_probs)
44
+ nAUC = metrics.roc_auc_score(self.actual_labels, self.predicted_probs)
45
+ plt.xlim(0, 1.02)
46
+ plt.ylim(0, 1.02)
47
+ if is_showing_grid:
48
+ plt.grid()
49
+ plt.plot(nFPR, nTPR, label="ROC curve (AUC=%.2f)" % nAUC, linewidth=2)
50
+ plt.plot([0.0, 1.0], [0.0, 1.0], 'r--', label="Random prediction") # , color="yellow", linewidth=1)
51
+
52
+ plt.ylabel("TPR (True Positive Rate)")
53
+ plt.xlabel("FPR (False Positive Rate)")
54
+
55
+ nPointIndex = None
56
+ for nThresholdIndex, nThreshold in enumerate(nThresholds):
57
+ if nThreshold < true_threshold:
58
+ nPointIndex = nThresholdIndex
59
+ break
60
+
61
+ if nPointIndex is not None:
62
+ plt.plot(nFPR[nPointIndex], nTPR[nPointIndex]
63
+ , label="Threshold %.2f" % nThreshold
64
+ , marker="o", markersize=10, markeredgecolor="blue", markerfacecolor="yellow")
65
+ plt.title(self.title, fontsize=18)
66
+ plt.legend(loc=4)
67
+ return self
68
+ # --------------------------------------------------------------------------------------
69
+ def save(self, filename):
70
+ plt.savefig(filename, bbox_inches='tight')
71
+ return self
72
+ # --------------------------------------------------------------------------------------
73
+ def show(self):
74
+ plt.show()
75
+ # --------------------------------------------------------------------------------------
@@ -0,0 +1,95 @@
1
+ # ......................................................................................
2
+ # MIT License
3
+
4
+ # Copyright (c) 2024-2025 Pantelis I. Kaplanoglou
5
+
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+
24
+ # ......................................................................................
25
+
26
+
27
+ import matplotlib.pyplot as plt
28
+ import numpy as np
29
+ from matplotlib import cm
30
+
31
+ class PlotVoronoi2D(object):
32
+ # --------------------------------------------------------------------------------------
33
+ # Constructor
34
+ def __init__(self, samples_2d, labels, ground_truth_cluster_count=10, title="2D Voronoi Plot"):
35
+ # ................................................................
36
+ # // Fields \\
37
+ self.title = title
38
+ self.samples_2d = samples_2d
39
+ self.labels = labels
40
+ self.ground_truth_cluster_count = ground_truth_cluster_count
41
+
42
+ if (self.ground_truth_cluster_count <= 10):
43
+ self.color_map = cm.get_cmap("tab10")
44
+ elif (self.ground_truth_cluster_count <= 20):
45
+ self.color_map = cm.get_cmap("tab20")
46
+ else:
47
+ self.color_map = cm.get_cmap("prism")
48
+
49
+ self.point_size = 8
50
+ self.plot_dimensions = [14, 8]
51
+ # ................................................................
52
+ # --------------------------------------------------------------------------------------
53
+ def prepare_for_kmeans(self, kmeans_model):
54
+ reduced_data = self.samples_2d
55
+
56
+ # Step size of the mesh. Decrease to increase the quality of the VQ.
57
+ #h = .02 # point in the mesh [x_min, x_max]x[y_min, y_max].
58
+ h = .4 # point in the mesh [x_min, x_max]x[y_min, y_max].
59
+
60
+ # Plot the decision boundary. For that, we will assign a color to each
61
+ x_min, x_max = reduced_data[:, 0].min() - 1, reduced_data[:, 0].max() + 1
62
+ y_min, y_max = reduced_data[:, 1].min() - 1, reduced_data[:, 1].max() + 1
63
+ xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
64
+
65
+ # Obtain labels for each point in mesh. Use last trained model.
66
+ Z = kmeans_model.predict(np.c_[xx.ravel(), yy.ravel()])
67
+
68
+ # Put the result into a color plot
69
+ Z = Z.reshape(xx.shape)
70
+ plt.figure(1, figsize=self.plot_dimensions)
71
+ plt.clf()
72
+ plt.imshow(Z, interpolation="nearest",
73
+ extent=(xx.min(), xx.max(), yy.min(), yy.max()),
74
+ cmap=cm.get_cmap("tab20"), aspect="auto", origin="lower")
75
+
76
+ #plt.plot(reduced_data[:, 0], reduced_data[:, 1], 'k.', markersize=2)
77
+ plt.scatter(reduced_data[:, 0], reduced_data[:, 1], c=self.labels, s=self.point_size, cmap=self.color_map)
78
+
79
+ # Plot the centroids as a white X
80
+ centroids = kmeans_model.cluster_centers_
81
+ plt.scatter(centroids[:, 0], centroids[:, 1], marker="x", s=169, linewidths=3
82
+ ,color="w", zorder=10 )
83
+ plt.title(self.title)
84
+ plt.xlim(x_min, x_max)
85
+ plt.ylim(y_min, y_max)
86
+ plt.xticks(())
87
+ plt.yticks(())
88
+ # --------------------------------------------------------------------------------------
89
+ def save(self, filename):
90
+ plt.savefig(filename, bbox_inches='tight')
91
+ return self
92
+ # --------------------------------------------------------------------------------------
93
+ def show(self):
94
+ plt.show()
95
+ # --------------------------------------------------------------------------------------
radnn/system/__init__.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from .filestore import FileStore
2
2
  from .filesystem import FileSystem
3
+ from .tee_logger import TeeLogger
3
4
 
4
5
 
5
6
 
@@ -33,55 +33,77 @@ from radnn.system.files import JSONFile
33
33
  # =======================================================================================================================
34
34
  class FileSystem(object):
35
35
  # --------------------------------------------------------------------------------------------------------
36
- def __init__(self, config_folder=None, model_folder=None, dataset_folder=None, model_group=None, must_exist=False, setup_filename=None):
36
+ def __init__(self, config_folder="MLConfig", model_folder="MLModels", dataset_folder="MLData", must_exist=False, setup_filename="*auto*"):
37
37
  '''
38
38
  Initializes the file system settings for an experiment
39
39
  :param config_folder: The folder that contains the experiment hyperparameter files.
40
40
  :param model_folder: The main folder where the models are stored.
41
41
  :param dataset_folder: The main folder under which dataset folder are stores.
42
- :param model_group: A group of models under the main folder will be used instead.
43
42
  :param setup_filename: The filename of the setup file.
44
43
  :param must_exist: False: Auto-creates the directories on the file system | True: Raises an error if it does not exist.
45
44
  '''
46
45
 
47
46
  self.setup_filename = setup_filename
48
- if self.setup_filename is None:
47
+ if self.setup_filename == "*auto*":
49
48
  self.setup_filename = system_name() + ".fsys"
49
+ self._must_exist = must_exist
50
+ self._has_loaded_groups = False
51
+ self.model_groups = []
50
52
 
51
- if os.path.exists(self.setup_filename):
52
- oFile = JSONFile(self.setup_filename)
53
- dSetup = oFile.load()
54
- config_folder = dSetup["Configs"]
55
- model_folder = dSetup["Models"]
56
- dataset_folder = dSetup["Datasets"]
57
- if model_group is None:
58
- if "ModelGroup" in dSetup:
59
- model_group = dSetup["ModelGroup"]
60
- else:
53
+ dSetup = None
54
+ if self.setup_filename is not None:
55
+ if os.path.exists(self.setup_filename):
56
+ oFile = JSONFile(self.setup_filename)
57
+ dSetup = oFile.load()
58
+ config_folder = dSetup["Configs"]
59
+ model_folder = dSetup["Models"]
60
+ dataset_folder = dSetup["Datasets"]
61
+ if "ModelGroups" in dSetup:
62
+ self.model_groups = dSetup["ModelGroups"]
63
+ self._has_loaded_groups = True
64
+
65
+ if dSetup is None:
61
66
  dSetup = dict()
62
67
  dSetup["Configs"] = config_folder
63
68
  dSetup["Models"] = model_folder
64
69
  dSetup["Datasets"] = dataset_folder
65
70
 
66
- if model_group is not None:
67
- config_folder = os.path.join(config_folder, model_group)
68
- model_folder = os.path.join(model_folder, model_group)
71
+
72
+ if len(self.model_groups) > 0:
73
+ for model_group in self.model_groups:
74
+ config_folder = os.path.join(config_folder, model_group)
75
+ model_folder = os.path.join(model_folder, model_group)
69
76
 
70
77
  # ...................... | Fields | ......................
71
78
  self.setup = dSetup
72
- self.configs = FileStore(config_folder, must_exist=must_exist)
73
- self.models = FileStore(model_folder, must_exist=must_exist)
74
- self.datasets = FileStore(dataset_folder, must_exist=must_exist)
79
+ self.configs = FileStore(config_folder, must_exist=self._must_exist)
80
+ self.models = FileStore(model_folder, must_exist=self._must_exist)
81
+ self.datasets = FileStore(dataset_folder, must_exist=self._must_exist)
75
82
  # ........................................................
76
83
  # --------------------------------------------------------------------------------------------------------
84
+ def group(self, group_name):
85
+ if not self._has_loaded_groups:
86
+ if group_name is not None:
87
+ self.model_groups.append(group_name)
88
+ self.setup["ModelGroups"] = self.model_groups
89
+
90
+ self.configs = self.configs.subfs(group_name, must_exist=self._must_exist)
91
+ self.models = self.models.subfs(group_name, must_exist=self._must_exist)
92
+
93
+ return self
94
+ # --------------------------------------------------------------------------------------------------------
77
95
  def save_setup(self):
78
96
  oFile = JSONFile(self.setup_filename)
79
97
  oFile.save(self.setup)
80
98
  # --------------------------------------------------------------------------------------------------------
81
99
  def __str__(self)->str:
82
- sResult = f" ConfigsFolder: \"{self.configs.base_folder}\",\n"
83
- sResult += f" ModelsFolder: \"{self.models.base_folder}\",\n"
84
- sResult += f" DatasetsFolder: \"{self.datasets.base_folder}\"\n"
100
+ sResult = f" Configs: \"{self.configs.base_folder}\",\n"
101
+ sResult += f" Models: \"{self.models.base_folder}\",\n"
102
+ sResult += f" Datasets: \"{self.datasets.base_folder}\"\n"
103
+ if len(self.model_groups) > 0:
104
+ sModelGroups = str(self.model_groups)
105
+ sModelGroups = sModelGroups.replace("'", "\"")
106
+ sResult += f" ModelGroups: {sModelGroups}\n"
85
107
  sResult = "{\n" + sResult + "}"
86
108
  return sResult
87
109
  # --------------------------------------------------------------------------------------------------------
@@ -0,0 +1,10 @@
1
+ from radnn.core import MLInfrastructure
2
+
3
+ if MLInfrastructure.is_windows():
4
+ from .windows_host import WindowsHost
5
+
6
+ if MLInfrastructure.is_colab():
7
+ from .colab_host import ColabHost
8
+
9
+ if MLInfrastructure.is_linux():
10
+ from .linux_host import LinuxHost
@@ -0,0 +1,52 @@
1
+ import os
2
+ from google.colab import drive
3
+ from google.colab import files
4
+ from radnn.core import MLInfrastructure
5
+
6
+ COLAB_ROOT_FOLDER = "/content/gdrive/My Drive/Colab Notebooks"
7
+
8
+ class ColabHost(object):
9
+ # --------------------------------------------------------------------------------------------------------------------
10
+ def __init__(self, colab_root=COLAB_ROOT_FOLDER, workspace_folder=None):
11
+ self.colab_root = colab_root
12
+ self.workspace_prefixes = None
13
+ self.workspace_folder = workspace_folder
14
+ self.current_folder = None
15
+
16
+ drive.mount("/content/gdrive")
17
+ # --------------------------------------------------------------------------------------------------------------------
18
+ def detect_workspace(self, prefixes=None):
19
+ self.workspace_prefixes = prefixes
20
+
21
+ if self.workspace_prefixes is None:
22
+ self.workspace_folder = self.colab_root
23
+ else:
24
+ self.workspace_folder = None
25
+ for sProjectsFolder in os.listdir(self.colab_root):
26
+ for sPrefix in self.workspace_prefixes:
27
+ if sProjectsFolder.startswith(sPrefix):
28
+ self.workspace_folder = os.path.join(self.colab_root, sProjectsFolder)
29
+ break
30
+ return self
31
+ # --------------------------------------------------------------------------------------------------------------------
32
+ def change_dir(self, path):
33
+ self.current_folder = path
34
+ os.chdir(self.current_folder)
35
+ print("Current directory is: ", os.getcwd())
36
+ # --------------------------------------------------------------------------------------------------------------------
37
+ def change_to_project_dir(self, project_name):
38
+ if self.workspace_folder is not None:
39
+ self.current_folder = os.path.join(self.workspace_folder, project_name)
40
+ else:
41
+ self.current_folder = os.path.join(self.colab_root, project_name)
42
+ os.chdir(self.current_folder)
43
+ print("Current directory is: ", os.getcwd())
44
+
45
+ return self
46
+ # --------------------------------------------------------------------------------------------------------
47
+ def __str__(self)->str:
48
+ return f"Host: {MLInfrastructure.host_name()}"
49
+ # --------------------------------------------------------------------------------------------------------
50
+ def __repr__(self)->str:
51
+ return self.__str__()
52
+ # --------------------------------------------------------------------------------------------------------
@@ -0,0 +1,3 @@
1
+ class LinuxHost(object):
2
+ def __init__(self):
3
+ pass
@@ -0,0 +1,41 @@
1
+ import win32api
2
+
3
+ class WindowsHost(object):
4
+ # --------------------------------------------------------------------------------------------------------------------
5
+ @classmethod
6
+ def dll_info_root(self, dll_filename):
7
+ dInfo = dict()
8
+ ver_strings = ('Comments', 'InternalName', 'ProductName',
9
+ 'CompanyName', 'LegalCopyright', 'ProductVersion',
10
+ 'FileDescription', 'LegalTrademarks', 'PrivateBuild',
11
+ 'FileVersion', 'OriginalFilename', 'SpecialBuild')
12
+ # fname = os.environ["comspec"]
13
+ dFileVersionInfo = win32api.GetFileVersionInfo(dll_filename, '\\')
14
+ ## backslash as parm returns dictionary of numeric info corresponding to VS_FIXEDFILEINFO struc
15
+
16
+ for sKey, oValue in dFileVersionInfo.items():
17
+ dInfo[sKey] = oValue
18
+ return dInfo
19
+ # --------------------------------------------------------------------------------------------------------------------
20
+ @classmethod
21
+ def dll_info(cls, dll_filename):
22
+ dInfo = dict()
23
+
24
+ dFileVersionsInfo = win32api.GetFileVersionInfo(dll_filename, '\\VarFileInfo\\Translation')
25
+ ## \VarFileInfo\Translation returns list of available (language, codepage) pairs that can be used to retreive string info
26
+ ## any other must be of the form \StringfileInfo\%04X%04X\parm_name, middle two are language/codepage pair returned from above
27
+ dInfo = dict()
28
+ for sLanguageCode, sCodePage in dFileVersionsInfo:
29
+ dInfo["lang"] = sLanguageCode
30
+ dInfo["codepage"] = sCodePage
31
+
32
+ #print('lang: ', lang, 'codepage:', codepage)
33
+ oVersionStrings = ('Comments', 'InternalName', 'ProductName',
34
+ 'CompanyName', 'LegalCopyright', 'ProductVersion',
35
+ 'FileDescription', 'LegalTrademarks', 'PrivateBuild',
36
+ 'FileVersion', 'OriginalFilename', 'SpecialBuild')
37
+ for sVersionString in oVersionStrings:
38
+ str_info = u'\\StringFileInfo\\%04X%04X\\%s' % (sLanguageCode, sCodePage, sVersionString)
39
+ dInfo[sVersionString] = repr(win32api.GetFileVersionInfo(dll_filename, str_info))
40
+ return dInfo
41
+ # --------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,60 @@
1
+ # ......................................................................................
2
+ # MIT License
3
+
4
+ # Copyright (c) 2024-2025 Pantelis I. Kaplanoglou
5
+
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+
24
+ # ......................................................................................
25
+
26
+ import os
27
+ import sys
28
+ from datetime import datetime
29
+
30
+
31
+ class TeeLogger(object):
32
+ # --------------------------------------------------------------------------------------------------------
33
+ def __init__(self, file_name):
34
+ self.last_flush = datetime.now()
35
+ sMode = "w"
36
+ if os.path.exists(file_name):
37
+ sMode = "a"
38
+ self.file = open(file_name, sMode)
39
+ self.stdout = sys.stdout
40
+ self.isclosed = False
41
+ # --------------------------------------------------------------------------------------------------------
42
+ def write(self, message):
43
+ self.stdout.write(message)
44
+ self.file.write(message)
45
+ dNow = datetime.now()
46
+ oDelta = dNow - self.last_flush
47
+ if oDelta.total_seconds() >= 5:
48
+ self.flush()
49
+ self.last_flush = dNow
50
+ # --------------------------------------------------------------------------------------------------------
51
+ def flush(self):
52
+ if not self.isclosed:
53
+ self.stdout.flush()
54
+ self.file.flush()
55
+ # --------------------------------------------------------------------------------------------------------
56
+ def close(self):
57
+ self.isclosed = True
58
+ self.stdout.close()
59
+ self.file.close()
60
+ # --------------------------------------------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: radnn
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Rapid Deep Neural Networks
5
5
  Author-email: "Pantelis I. Kaplanoglou" <pikaplanoglou@ihu.gr>
6
6
  License: MIT License
@@ -0,0 +1,32 @@
1
+ radnn/__init__.py,sha256=FqalNi2naG2HW4WceCD7_Tnp8cHHUXCCY9HYsg9Buxk,140
2
+ radnn/core.py,sha256=cWGl_B3bHASEDdZHgDKN9CwGo7-TFjj3y2YD-Ei4bD4,2828
3
+ radnn/evaluation/__init__.py,sha256=7dXDyJfOpSAr7G8jfDofsW4YEHNElCTTyMXuLCtpoOI,59
4
+ radnn/evaluation/evaluate_classification.py,sha256=wacQ_02loI2ApOKrUIDTKqqrwi0_TpUZJQfTpJ6rtag,5497
5
+ radnn/experiment/__init__.py,sha256=8gxrFS4bG7rg2kgrDEhemJgDbO-5KhBYc4owJZ-S--k,247
6
+ radnn/experiment/ml_experiment_config.py,sha256=MuFb4sY4kgCwxtJGmpOace4eTopy7mVLg3IDCpy6evI,8547
7
+ radnn/experiment/ml_experiment_env.py,sha256=I54WILYjCJf9P133dFgOMsXGxkftlU8DcmwXDnBW930,11151
8
+ radnn/plots/__init__.py,sha256=sfhHqTDmYce5tg8jLJCN1IkELwbW3TCibm0PEYoomKY,181
9
+ radnn/plots/plot_confusion_matrix.py,sha256=KWW5k7-gaLPXO4oQBnzwAwgI-rvPOmqw8uiZbFlmU5o,2625
10
+ radnn/plots/plot_learning_curve.py,sha256=tgx3vAm4rg5qScqxLQ2T82BqrRWjiYaIKgLQp5Y19cQ,3480
11
+ radnn/plots/plot_roc.py,sha256=iyOnhux2qFEs7W-szEOCg2VCkcnBEaOJxZkWWYuSnCQ,3493
12
+ radnn/plots/plot_voronoi_2d.py,sha256=Kq3ZaLhLaFHQAurhFKNy7NNuIm3RArCk_WxPT8Ftxek,4409
13
+ radnn/system/__init__.py,sha256=uJLg56njcLtaRO0Kyudat055DGxqWsLMvrn9P8_Rt6A,119
14
+ radnn/system/filestore.py,sha256=gNESaiJNwdrFbkzcbkSv-O4V8vrBagOSR90WcFueNsA,8977
15
+ radnn/system/filesystem.py,sha256=jt37OP9UgdJihjKLRWpMmERt1L0EdS-uPRrdIduH_BQ,5523
16
+ radnn/system/tee_logger.py,sha256=WcyIJiW851PP6Jk7SyCHj63BEt7HnWa5-GJtw1CjGHE,2629
17
+ radnn/system/files/__init__.py,sha256=u2oGHNKM362JwqrDgwmWTAiisL067h6H9uu-d0iTBks,169
18
+ radnn/system/files/csvfile.py,sha256=9uKqmCdqLAKomANejcSruHle1r3gVdByQb1I2otLcMs,2246
19
+ radnn/system/files/fileobject.py,sha256=qT9qzpH6YFSIzaKfILxhpaQ1P2o2tqRh9J5J2L_Kp8Y,3851
20
+ radnn/system/files/imgfile.py,sha256=HxvvittOUG1mmIp-Bg8vHnLPtRYD-N4nE86G0Rcys6M,2242
21
+ radnn/system/files/jsonfile.py,sha256=CX0Y6kPEaF_eEUb8ah-5Wvp35JRVYcpvOXg-DCxRnf0,3172
22
+ radnn/system/files/picklefile.py,sha256=0BFiYzr6BbGWMXyQCMjYHa_GlW8nEZ95CQtn7lpH254,5526
23
+ radnn/system/files/textfile.py,sha256=r7FZ1pdFGYxKrQ_kgkEK35GIqRU8k_MjdB0OrDd5m0o,2891
24
+ radnn/system/hosts/__init__.py,sha256=k2gkMJhe96Nf-V2ex6jZqmCRX9vA_K6gFB8J8Ii9ahc,261
25
+ radnn/system/hosts/colab_host.py,sha256=tAuLn9gTkX9cCSEmm7sG1JmCtNIgFlASwBt2olHUSe4,2434
26
+ radnn/system/hosts/linux_host.py,sha256=TZ-5JEZV0WNEB5trTbRWM0REhpuK5oYxoLodKKqhm9s,57
27
+ radnn/system/hosts/windows_host.py,sha256=u4IvgfFoKWdrFy0hYG__AoxfEJYHF4-ISNXTCJ5uN8I,2257
28
+ radnn-0.0.5.dist-info/LICENSE.txt,sha256=vYtt_GDvm_yW65X9YMBOOu8Vqc9SAvqH94TbfBc2ckU,1106
29
+ radnn-0.0.5.dist-info/METADATA,sha256=_9T1n2CkCwcPhzFOtQcP4E8s6GkzQI4Q2TLEU2aeNF4,2861
30
+ radnn-0.0.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
31
+ radnn-0.0.5.dist-info/top_level.txt,sha256=FKlLIm6gRAeZlRzs-HCBJAB1q9ELJ7MgaL-qqFuPo6M,6
32
+ radnn-0.0.5.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- radnn/__init__.py,sha256=arFgYRlsbwGiNdsYNwxWkafpqvG4x9-7Xw4gwehj6Ds,109
2
- radnn/core.py,sha256=1CNl5Qejop4pCUIDfHEx97PO3bQVq1WrQ-a5IIRGaCE,1665
3
- radnn/experiment/__init__.py,sha256=GqESBzSW5fyoFENFOWLC4FB0xqqn0KleEiWRPZG6_Dc,75
4
- radnn/experiment/ml_experiment_config.py,sha256=6REP6lT4Rj6_WXu3kuPsOSX4DkCAC8ZpR-2Ua2fQffU,6280
5
- radnn/system/__init__.py,sha256=kFFqJ3SPucLmSoL4QP2TBO7IdFfjmgn3eGh6HGMFOM0,84
6
- radnn/system/filestore.py,sha256=gNESaiJNwdrFbkzcbkSv-O4V8vrBagOSR90WcFueNsA,8977
7
- radnn/system/filesystem.py,sha256=16m3-WDXGs5HoasbV7s7--bFSdfs9zYTg6nwTvqy22s,4642
8
- radnn/system/files/__init__.py,sha256=u2oGHNKM362JwqrDgwmWTAiisL067h6H9uu-d0iTBks,169
9
- radnn/system/files/csvfile.py,sha256=9uKqmCdqLAKomANejcSruHle1r3gVdByQb1I2otLcMs,2246
10
- radnn/system/files/fileobject.py,sha256=qT9qzpH6YFSIzaKfILxhpaQ1P2o2tqRh9J5J2L_Kp8Y,3851
11
- radnn/system/files/imgfile.py,sha256=HxvvittOUG1mmIp-Bg8vHnLPtRYD-N4nE86G0Rcys6M,2242
12
- radnn/system/files/jsonfile.py,sha256=CX0Y6kPEaF_eEUb8ah-5Wvp35JRVYcpvOXg-DCxRnf0,3172
13
- radnn/system/files/picklefile.py,sha256=0BFiYzr6BbGWMXyQCMjYHa_GlW8nEZ95CQtn7lpH254,5526
14
- radnn/system/files/textfile.py,sha256=r7FZ1pdFGYxKrQ_kgkEK35GIqRU8k_MjdB0OrDd5m0o,2891
15
- radnn-0.0.3.dist-info/LICENSE.txt,sha256=vYtt_GDvm_yW65X9YMBOOu8Vqc9SAvqH94TbfBc2ckU,1106
16
- radnn-0.0.3.dist-info/METADATA,sha256=fYW8JdhjMVYxbnseMrLVO_TEP7rqc50zrXLhd--jM1M,2861
17
- radnn-0.0.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
18
- radnn-0.0.3.dist-info/top_level.txt,sha256=FKlLIm6gRAeZlRzs-HCBJAB1q9ELJ7MgaL-qqFuPo6M,6
19
- radnn-0.0.3.dist-info/RECORD,,
File without changes