FeatureRank 0.1.2__tar.gz → 0.1.5__tar.gz
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.
- {featurerank-0.1.2 → featurerank-0.1.5}/FeatureRank/GUI.py +245 -43
- {featurerank-0.1.2 → featurerank-0.1.5}/FeatureRank/__init__.py +1 -1
- {featurerank-0.1.2 → featurerank-0.1.5/FeatureRank.egg-info}/PKG-INFO +77 -30
- featurerank-0.1.5/FeatureRank.egg-info/requires.txt +11 -0
- featurerank-0.1.2/README.md → featurerank-0.1.5/PKG-INFO +89 -27
- featurerank-0.1.2/FeatureRank.egg-info/PKG-INFO → featurerank-0.1.5/README.md +73 -42
- {featurerank-0.1.2 → featurerank-0.1.5}/pyproject.toml +4 -3
- featurerank-0.1.2/FeatureRank.egg-info/requires.txt +0 -6
- {featurerank-0.1.2 → featurerank-0.1.5}/FeatureRank/__main__.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/FeatureRank.egg-info/SOURCES.txt +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/FeatureRank.egg-info/dependency_links.txt +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/FeatureRank.egg-info/entry_points.txt +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/FeatureRank.egg-info/top_level.txt +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/EvaluatePaperDimensionReduction.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/FeatureBlockDatasetTools.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/FeatureRank.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/Figure_Scripts/CreateAccuracyBoxplotFromList.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/Figure_Scripts/CreateClassificationFigure.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/Figure_Scripts/CreateClusterClassificationSummaryFigure.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/Figure_Scripts/CreateClusterFigure1.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/Figure_Scripts/CreateConfusionMatrixPanel.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/Figure_Scripts/CreatePrecisionRecallPanel.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/Figure_Scripts/CreateRegressionFigure2.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/Figure_Scripts/CreateRocPanel.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/GeneratePaperDimensionReduction.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/PaperDimensionReductionCommon.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/RunAutoencoder.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/RunBlockFeatureSelection.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/RunDimensionReduction.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/RunFeatureRankCV.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/Simple.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/scripts/__init__.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/setup.cfg +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/AutoencoderFeatureSelection.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/Classification.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/Clustering.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/Config.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/DataLoader.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/DivideCombine.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/Experiment.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/Models.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/OutputPaths.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/Preprocessing.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/Regression.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/Reporting.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/Runtime.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/Utils.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/Workflow.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/src/__init__.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.5}/tests/test_feature_rank_refactor.py +0 -0
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
import os
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
import queue
|
|
12
|
+
import shutil
|
|
12
13
|
import subprocess
|
|
13
14
|
import sys
|
|
14
15
|
import threading
|
|
@@ -19,6 +20,7 @@ from tkinter import filedialog, messagebox, ttk
|
|
|
19
20
|
|
|
20
21
|
PERCENT_VALUES = tuple(str(value) for value in range(10, 101, 10))
|
|
21
22
|
BLOCK_VALUES = ("5", "10", "20")
|
|
23
|
+
NO_DATASET_LABEL = "No Dataset"
|
|
22
24
|
MODE_LABELS = {
|
|
23
25
|
"GL": "GL — Global Feature Ranking",
|
|
24
26
|
"DC": "DC — Divide & Combine Feature Ranking",
|
|
@@ -40,9 +42,99 @@ def _dataset_choices() -> list[str]:
|
|
|
40
42
|
if not directory.exists():
|
|
41
43
|
continue
|
|
42
44
|
for data_file in sorted(directory.glob("*_data.csv")):
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
absolute_path = str(data_file.resolve())
|
|
46
|
+
if data_file.is_file() and absolute_path not in choices:
|
|
47
|
+
choices.append(absolute_path)
|
|
48
|
+
return choices or [NO_DATASET_LABEL]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _resolve_dataset_path(dataset: str) -> str:
|
|
52
|
+
"""Use an absolute path when the selected file exists locally."""
|
|
53
|
+
requested = Path(dataset).expanduser()
|
|
54
|
+
if requested.is_file():
|
|
55
|
+
return str(requested.resolve())
|
|
56
|
+
|
|
57
|
+
project_root = Path(__file__).resolve().parent.parent
|
|
58
|
+
candidates = (
|
|
59
|
+
Path.cwd() / requested,
|
|
60
|
+
Path.cwd() / "data" / "raw" / requested,
|
|
61
|
+
project_root / requested,
|
|
62
|
+
project_root / "data" / "raw" / requested,
|
|
63
|
+
)
|
|
64
|
+
for candidate in candidates:
|
|
65
|
+
if candidate.is_file():
|
|
66
|
+
return str(candidate.resolve())
|
|
67
|
+
return dataset
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _supported_python(path: Path) -> bool:
|
|
71
|
+
"""Return whether an interpreter can run the TensorFlow worker."""
|
|
72
|
+
try:
|
|
73
|
+
result = subprocess.run(
|
|
74
|
+
[str(path), "-c", "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')"],
|
|
75
|
+
stdin=subprocess.DEVNULL,
|
|
76
|
+
stdout=subprocess.PIPE,
|
|
77
|
+
stderr=subprocess.DEVNULL,
|
|
78
|
+
text=True,
|
|
79
|
+
timeout=5,
|
|
80
|
+
check=False,
|
|
81
|
+
)
|
|
82
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
83
|
+
return False
|
|
84
|
+
try:
|
|
85
|
+
major, minor = (int(part) for part in result.stdout.strip().split(".", maxsplit=1))
|
|
86
|
+
except (ValueError, TypeError):
|
|
87
|
+
return False
|
|
88
|
+
return (3, 10) <= (major, minor) < (3, 14)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _discover_compatible_python() -> list[Path]:
|
|
92
|
+
"""Find an installed Python 3.13–3.10 for a Python 3.14 GUI process."""
|
|
93
|
+
candidates: list[Path] = []
|
|
94
|
+
versions = ("3.13", "3.12", "3.11", "3.10")
|
|
95
|
+
if os.name == "nt":
|
|
96
|
+
launcher = shutil.which("py")
|
|
97
|
+
if launcher:
|
|
98
|
+
for version in versions:
|
|
99
|
+
try:
|
|
100
|
+
result = subprocess.run(
|
|
101
|
+
[launcher, f"-{version}", "-c", "import sys; print(sys.executable)"],
|
|
102
|
+
stdin=subprocess.DEVNULL,
|
|
103
|
+
stdout=subprocess.PIPE,
|
|
104
|
+
stderr=subprocess.DEVNULL,
|
|
105
|
+
text=True,
|
|
106
|
+
timeout=5,
|
|
107
|
+
check=False,
|
|
108
|
+
)
|
|
109
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
110
|
+
continue
|
|
111
|
+
executable = result.stdout.strip()
|
|
112
|
+
if executable:
|
|
113
|
+
candidates.append(Path(executable))
|
|
114
|
+
else:
|
|
115
|
+
for version in versions:
|
|
116
|
+
executable = shutil.which(f"python{version}")
|
|
117
|
+
if executable:
|
|
118
|
+
candidates.append(Path(executable))
|
|
119
|
+
return candidates
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _worker_python() -> Path | None:
|
|
123
|
+
"""Find a compatible interpreter before falling back to the current one."""
|
|
124
|
+
project_root = Path(__file__).resolve().parent.parent
|
|
125
|
+
executable_name = "python.exe" if os.name == "nt" else "python"
|
|
126
|
+
candidates = [
|
|
127
|
+
Path.cwd() / ".venv" / ("Scripts" if os.name == "nt" else "bin") / executable_name,
|
|
128
|
+
project_root / ".venv" / ("Scripts" if os.name == "nt" else "bin") / executable_name,
|
|
129
|
+
Path(sys.executable).with_name("python"),
|
|
130
|
+
Path(sys.executable),
|
|
131
|
+
]
|
|
132
|
+
if sys.version_info >= (3, 14):
|
|
133
|
+
candidates.extend(_discover_compatible_python())
|
|
134
|
+
for candidate in candidates:
|
|
135
|
+
if candidate.is_file() and _supported_python(candidate):
|
|
136
|
+
return candidate
|
|
137
|
+
return None
|
|
46
138
|
|
|
47
139
|
|
|
48
140
|
def _open_folder(path: Path) -> None:
|
|
@@ -79,16 +171,27 @@ class FeatureRankGUI:
|
|
|
79
171
|
def __init__(self, root: tk.Tk) -> None:
|
|
80
172
|
self.root = root
|
|
81
173
|
self.root.title("FeatureRank")
|
|
82
|
-
|
|
83
|
-
self.root.
|
|
174
|
+
# Keep the first view compact; the log can still grow when resized.
|
|
175
|
+
self.root.geometry("760x680")
|
|
176
|
+
self.root.minsize(680, 600)
|
|
84
177
|
|
|
85
178
|
self.messages: queue.Queue[tuple[str, object]] = queue.Queue()
|
|
86
179
|
self.worker: threading.Thread | None = None
|
|
87
180
|
self.last_output_dir: Path | None = None
|
|
88
181
|
self.block_count = 10
|
|
89
182
|
self.blocks_seen = 0
|
|
183
|
+
self.progress_value = 0.0
|
|
184
|
+
self.progress_started_at: float | None = None
|
|
185
|
+
self.progress_job: str | None = None
|
|
186
|
+
self.progress_running = False
|
|
187
|
+
|
|
188
|
+
dataset_paths = _dataset_choices()
|
|
189
|
+
self.dataset_paths = {
|
|
190
|
+
Path(path).name: path for path in dataset_paths if path != NO_DATASET_LABEL
|
|
191
|
+
}
|
|
192
|
+
self.dataset_labels = [NO_DATASET_LABEL, *self.dataset_paths]
|
|
90
193
|
|
|
91
|
-
self.dataset_var = tk.StringVar(value=
|
|
194
|
+
self.dataset_var = tk.StringVar(value=NO_DATASET_LABEL)
|
|
92
195
|
self.percent_var = tk.StringVar(value="20")
|
|
93
196
|
self.task_var = tk.StringVar(value="Classification")
|
|
94
197
|
self.mode_var = tk.StringVar(value=MODE_LABELS["GL"])
|
|
@@ -121,16 +224,56 @@ class FeatureRankGUI:
|
|
|
121
224
|
style.theme_use("clam")
|
|
122
225
|
except tk.TclError:
|
|
123
226
|
pass
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
style.configure("
|
|
127
|
-
style.configure("
|
|
227
|
+
# A restrained palette gives the form a clean surface on all platforms.
|
|
228
|
+
self.root.configure(background="#f4f6f8")
|
|
229
|
+
style.configure("TFrame", background="#f4f6f8")
|
|
230
|
+
style.configure("TLabel", background="#ffffff", foreground="#1f2937")
|
|
231
|
+
style.configure("TLabelframe", background="#ffffff")
|
|
232
|
+
style.configure("TLabelframe.Label", background="#ffffff", foreground="#1f2937")
|
|
233
|
+
style.configure(
|
|
234
|
+
"Title.TLabel",
|
|
235
|
+
background="#f4f6f8",
|
|
236
|
+
font=("Helvetica", 20, "bold"),
|
|
237
|
+
foreground="#1f2937",
|
|
238
|
+
)
|
|
239
|
+
style.configure(
|
|
240
|
+
"Subtitle.TLabel",
|
|
241
|
+
background="#f4f6f8",
|
|
242
|
+
font=("Helvetica", 10),
|
|
243
|
+
foreground="#4b5563",
|
|
244
|
+
)
|
|
245
|
+
style.configure(
|
|
246
|
+
"Stage.TLabel",
|
|
247
|
+
background="#f4f6f8",
|
|
248
|
+
font=("Helvetica", 11, "bold"),
|
|
249
|
+
foreground="#1f4e79",
|
|
250
|
+
)
|
|
251
|
+
style.configure(
|
|
252
|
+
"Run.TButton",
|
|
253
|
+
background="#1f4e79",
|
|
254
|
+
foreground="#ffffff",
|
|
255
|
+
font=("Helvetica", 11, "bold"),
|
|
256
|
+
padding=(16, 6),
|
|
257
|
+
)
|
|
258
|
+
style.map(
|
|
259
|
+
"Run.TButton",
|
|
260
|
+
background=[("active", "#173a5c"), ("disabled", "#b9c3cf")],
|
|
261
|
+
foreground=[("disabled", "#eef2f6")],
|
|
262
|
+
)
|
|
263
|
+
style.configure(
|
|
264
|
+
"Horizontal.TProgressbar",
|
|
265
|
+
background="#1f4e79",
|
|
266
|
+
troughcolor="#e5e7eb",
|
|
267
|
+
bordercolor="#d1d5db",
|
|
268
|
+
lightcolor="#1f4e79",
|
|
269
|
+
darkcolor="#1f4e79",
|
|
270
|
+
)
|
|
128
271
|
|
|
129
272
|
def _build_window(self) -> None:
|
|
130
273
|
self.root.columnconfigure(0, weight=1)
|
|
131
274
|
self.root.rowconfigure(3, weight=1)
|
|
132
275
|
|
|
133
|
-
header = ttk.Frame(self.root, padding=(
|
|
276
|
+
header = ttk.Frame(self.root, padding=(24, 18, 24, 8))
|
|
134
277
|
header.grid(row=0, column=0, sticky="ew")
|
|
135
278
|
ttk.Label(header, text="FeatureRank", style="Title.TLabel").pack(anchor="w")
|
|
136
279
|
ttk.Label(
|
|
@@ -139,53 +282,55 @@ class FeatureRankGUI:
|
|
|
139
282
|
style="Subtitle.TLabel",
|
|
140
283
|
).pack(anchor="w", pady=(4, 0))
|
|
141
284
|
|
|
142
|
-
options = ttk.LabelFrame(self.root, text="Experiment", padding=
|
|
143
|
-
options.grid(row=1, column=0, padx=
|
|
285
|
+
options = ttk.LabelFrame(self.root, text="Experiment", padding=14)
|
|
286
|
+
options.grid(row=1, column=0, padx=24, pady=6, sticky="ew")
|
|
144
287
|
options.columnconfigure(1, weight=1)
|
|
145
288
|
|
|
146
|
-
ttk.Label(options, text="Dataset").grid(row=0, column=0, sticky="w", pady=
|
|
289
|
+
ttk.Label(options, text="Dataset").grid(row=0, column=0, sticky="w", pady=5)
|
|
147
290
|
self.dataset_combo = ttk.Combobox(
|
|
148
|
-
options, textvariable=self.dataset_var, values=
|
|
291
|
+
options, textvariable=self.dataset_var, values=self.dataset_labels, state="readonly"
|
|
149
292
|
)
|
|
150
|
-
self.dataset_combo.grid(row=0, column=1, sticky="ew", padx=(
|
|
293
|
+
self.dataset_combo.grid(row=0, column=1, sticky="ew", padx=(10, 6), pady=5)
|
|
151
294
|
ttk.Button(options, text="Browse…", command=self._browse_dataset).grid(
|
|
152
|
-
row=0, column=2, pady=
|
|
295
|
+
row=0, column=2, pady=5
|
|
153
296
|
)
|
|
154
297
|
|
|
155
|
-
ttk.Label(options, text="Task").grid(row=1, column=0, sticky="w", pady=
|
|
298
|
+
ttk.Label(options, text="Task").grid(row=1, column=0, sticky="w", pady=5)
|
|
156
299
|
ttk.Combobox(
|
|
157
300
|
options,
|
|
158
301
|
textvariable=self.task_var,
|
|
159
302
|
values=tuple(TASK_LABELS),
|
|
160
303
|
state="readonly",
|
|
161
|
-
).grid(row=1, column=1, sticky="ew", padx=(
|
|
304
|
+
).grid(row=1, column=1, sticky="ew", padx=(10, 6), pady=5)
|
|
162
305
|
|
|
163
|
-
ttk.Label(options, text="Feature Percent").grid(row=2, column=0, sticky="w", pady=
|
|
306
|
+
ttk.Label(options, text="Feature Percent").grid(row=2, column=0, sticky="w", pady=5)
|
|
164
307
|
ttk.Combobox(
|
|
165
308
|
options,
|
|
166
309
|
textvariable=self.percent_var,
|
|
167
310
|
values=PERCENT_VALUES,
|
|
168
311
|
state="readonly",
|
|
169
312
|
width=12,
|
|
170
|
-
).grid(row=2, column=1, sticky="w", padx=(
|
|
313
|
+
).grid(row=2, column=1, sticky="w", padx=(10, 6), pady=5)
|
|
171
314
|
|
|
172
|
-
ttk.Label(options, text="Mode").grid(row=3, column=0, sticky="w", pady=
|
|
315
|
+
ttk.Label(options, text="Mode").grid(row=3, column=0, sticky="w", pady=5)
|
|
173
316
|
self.mode_combo = ttk.Combobox(
|
|
174
317
|
options,
|
|
175
318
|
textvariable=self.mode_var,
|
|
176
319
|
values=tuple(MODE_LABELS.values()),
|
|
177
320
|
state="readonly",
|
|
178
321
|
)
|
|
179
|
-
self.mode_combo.grid(row=3, column=1, sticky="ew", padx=(
|
|
322
|
+
self.mode_combo.grid(row=3, column=1, sticky="ew", padx=(10, 6), pady=5)
|
|
180
323
|
self.mode_combo.bind("<<ComboboxSelected>>", self._mode_changed)
|
|
181
324
|
|
|
182
|
-
ttk.Label(options, text="Block Count").grid(row=4, column=0, sticky="w", pady=
|
|
325
|
+
ttk.Label(options, text="Block Count").grid(row=4, column=0, sticky="w", pady=5)
|
|
183
326
|
self.block_combo = ttk.Combobox(
|
|
184
327
|
options, textvariable=self.block_var, values=BLOCK_VALUES, state="disabled", width=12
|
|
185
328
|
)
|
|
186
|
-
self.block_combo.grid(row=4, column=1, sticky="w", padx=(
|
|
329
|
+
self.block_combo.grid(row=4, column=1, sticky="w", padx=(10, 6), pady=5)
|
|
187
330
|
|
|
188
|
-
|
|
331
|
+
# Keep the run controls in a shallow toolbar so START is not surrounded
|
|
332
|
+
# by a large empty band.
|
|
333
|
+
action_row = ttk.Frame(self.root, padding=(24, 2, 24, 2))
|
|
189
334
|
action_row.grid(row=2, column=0, sticky="ew")
|
|
190
335
|
action_row.columnconfigure(1, weight=1)
|
|
191
336
|
self.start_button = ttk.Button(
|
|
@@ -196,14 +341,14 @@ class FeatureRankGUI:
|
|
|
196
341
|
row=0, column=1, sticky="e", padx=(12, 0)
|
|
197
342
|
)
|
|
198
343
|
|
|
199
|
-
progress_frame = ttk.Frame(self.root, padding=(
|
|
344
|
+
progress_frame = ttk.Frame(self.root, padding=(24, 0, 24, 8))
|
|
200
345
|
progress_frame.grid(row=3, column=0, sticky="nsew")
|
|
201
346
|
progress_frame.columnconfigure(0, weight=1)
|
|
202
347
|
progress_frame.rowconfigure(2, weight=1)
|
|
203
348
|
self.progress = ttk.Progressbar(progress_frame, mode="determinate", maximum=100)
|
|
204
349
|
self.progress.grid(row=0, column=0, sticky="ew")
|
|
205
350
|
ttk.Label(progress_frame, textvariable=self.status_var, style="Subtitle.TLabel").grid(
|
|
206
|
-
row=1, column=0, sticky="w", pady=(
|
|
351
|
+
row=1, column=0, sticky="w", pady=(5, 6)
|
|
207
352
|
)
|
|
208
353
|
|
|
209
354
|
log_frame = ttk.LabelFrame(progress_frame, text="Log / Output", padding=8)
|
|
@@ -212,7 +357,7 @@ class FeatureRankGUI:
|
|
|
212
357
|
log_frame.rowconfigure(0, weight=1)
|
|
213
358
|
self.log_text = tk.Text(
|
|
214
359
|
log_frame,
|
|
215
|
-
height=
|
|
360
|
+
height=8,
|
|
216
361
|
wrap="word",
|
|
217
362
|
state="disabled",
|
|
218
363
|
background="#f8fafc",
|
|
@@ -226,19 +371,19 @@ class FeatureRankGUI:
|
|
|
226
371
|
scrollbar.grid(row=0, column=1, sticky="ns")
|
|
227
372
|
self.log_text.configure(yscrollcommand=scrollbar.set)
|
|
228
373
|
|
|
229
|
-
summary = ttk.LabelFrame(self.root, text="Summary", padding=
|
|
230
|
-
summary.grid(row=4, column=0, padx=
|
|
374
|
+
summary = ttk.LabelFrame(self.root, text="Summary", padding=12)
|
|
375
|
+
summary.grid(row=4, column=0, padx=24, pady=(0, 8), sticky="ew")
|
|
231
376
|
summary.columnconfigure(1, weight=1)
|
|
232
377
|
for row, (key, value) in enumerate(self.summary_vars.items()):
|
|
233
378
|
ttk.Label(summary, text=key).grid(row=row // 2, column=(row % 2) * 2, sticky="w")
|
|
234
379
|
ttk.Label(summary, textvariable=value, foreground="#1f4e79").grid(
|
|
235
|
-
row=row // 2, column=(row % 2) * 2 + 1, sticky="w", padx=(8,
|
|
380
|
+
row=row // 2, column=(row % 2) * 2 + 1, sticky="w", padx=(8, 22), pady=2
|
|
236
381
|
)
|
|
237
382
|
|
|
238
383
|
self.open_button = ttk.Button(
|
|
239
384
|
self.root, text="Open Results Folder", command=self._open_results, state="disabled"
|
|
240
385
|
)
|
|
241
|
-
self.open_button.grid(row=5, column=0, padx=
|
|
386
|
+
self.open_button.grid(row=5, column=0, padx=24, pady=(0, 14), sticky="w")
|
|
242
387
|
|
|
243
388
|
def _browse_dataset(self) -> None:
|
|
244
389
|
selected = filedialog.askopenfilename(
|
|
@@ -246,14 +391,20 @@ class FeatureRankGUI:
|
|
|
246
391
|
filetypes=[("CSV files", "*.csv"), ("Text files", "*.txt"), ("All files", "*.*")],
|
|
247
392
|
)
|
|
248
393
|
if selected:
|
|
249
|
-
|
|
394
|
+
# Keep the full path internally, but show only the file name in
|
|
395
|
+
# the form so the selected dataset remains readable.
|
|
396
|
+
self.dataset_paths[Path(selected).name] = selected
|
|
397
|
+
self.dataset_labels = [NO_DATASET_LABEL, *self.dataset_paths]
|
|
398
|
+
self.dataset_combo.configure(values=self.dataset_labels)
|
|
399
|
+
self.dataset_var.set(Path(selected).name)
|
|
250
400
|
|
|
251
401
|
def _mode_changed(self, _event: object = None) -> None:
|
|
252
402
|
is_dc = self.mode_var.get().startswith("DC")
|
|
253
403
|
self.block_combo.configure(state="readonly" if is_dc else "disabled")
|
|
254
404
|
|
|
255
405
|
def _start(self) -> None:
|
|
256
|
-
|
|
406
|
+
dataset_label = self.dataset_var.get().strip()
|
|
407
|
+
dataset = self.dataset_paths.get(dataset_label, "")
|
|
257
408
|
if not dataset:
|
|
258
409
|
messagebox.showerror("FeatureRank", "Please select a dataset.")
|
|
259
410
|
return
|
|
@@ -273,10 +424,13 @@ class FeatureRankGUI:
|
|
|
273
424
|
self.last_output_dir = None
|
|
274
425
|
self.open_button.configure(state="disabled")
|
|
275
426
|
self.start_button.configure(state="disabled")
|
|
276
|
-
self.
|
|
427
|
+
self.progress_value = 2.0
|
|
428
|
+
self.progress.configure(value=self.progress_value)
|
|
277
429
|
self.stage_var.set("Loading Dataset")
|
|
278
430
|
self.status_var.set("The experiment is running. You can follow progress in the log.")
|
|
279
431
|
self._clear_log()
|
|
432
|
+
self.progress_started_at = time.perf_counter()
|
|
433
|
+
self.progress_running = True
|
|
280
434
|
|
|
281
435
|
self.worker = threading.Thread(
|
|
282
436
|
target=self._run_experiment,
|
|
@@ -284,12 +438,14 @@ class FeatureRankGUI:
|
|
|
284
438
|
daemon=True,
|
|
285
439
|
)
|
|
286
440
|
self.worker.start()
|
|
441
|
+
self._animate_progress()
|
|
287
442
|
self.root.after(100, self._poll_messages)
|
|
288
443
|
|
|
289
444
|
def _run_experiment(
|
|
290
445
|
self, dataset: str, percent: float, task: str, mode: str, block_count: int
|
|
291
446
|
) -> None:
|
|
292
447
|
started_at = time.perf_counter()
|
|
448
|
+
output_lines: list[str] = []
|
|
293
449
|
try:
|
|
294
450
|
# Keep TensorFlow out of the Tk process. On macOS, loading a model
|
|
295
451
|
# from a GUI thread can terminate the native Python process. The
|
|
@@ -297,8 +453,9 @@ class FeatureRankGUI:
|
|
|
297
453
|
# stdout is streamed back into this window.
|
|
298
454
|
from src.Config import ExperimentConfig
|
|
299
455
|
|
|
456
|
+
dataset_path = _resolve_dataset_path(dataset)
|
|
300
457
|
config = ExperimentConfig(
|
|
301
|
-
dataset_name=
|
|
458
|
+
dataset_name=dataset_path,
|
|
302
459
|
task=task,
|
|
303
460
|
feature_percent=percent,
|
|
304
461
|
random_state=42,
|
|
@@ -308,15 +465,19 @@ class FeatureRankGUI:
|
|
|
308
465
|
cluster_k=None,
|
|
309
466
|
save_details=False,
|
|
310
467
|
)
|
|
311
|
-
python_executable =
|
|
312
|
-
if
|
|
313
|
-
|
|
468
|
+
python_executable = _worker_python()
|
|
469
|
+
if python_executable is None:
|
|
470
|
+
raise RuntimeError(
|
|
471
|
+
"Python 3.14 GUI acildi, ancak TensorFlow worker'i icin Python 3.13, "
|
|
472
|
+
"3.12, 3.11 veya 3.10 bulunamadi. FeatureRank'i calistirmak icin "
|
|
473
|
+
"desteklenen bir Python surumu kurun."
|
|
474
|
+
)
|
|
314
475
|
command = [
|
|
315
476
|
str(python_executable),
|
|
316
477
|
"-m",
|
|
317
478
|
"scripts.FeatureRank",
|
|
318
479
|
"--dataset-name",
|
|
319
|
-
|
|
480
|
+
dataset_path,
|
|
320
481
|
"--task",
|
|
321
482
|
task,
|
|
322
483
|
"--feature-percent",
|
|
@@ -328,9 +489,18 @@ class FeatureRankGUI:
|
|
|
328
489
|
if mode == "dc":
|
|
329
490
|
command.extend(["--block-count", str(block_count)])
|
|
330
491
|
|
|
492
|
+
worker_environment = os.environ.copy()
|
|
493
|
+
worker_environment["PYTHONUNBUFFERED"] = "1"
|
|
494
|
+
package_root = str(Path(__file__).resolve().parent.parent)
|
|
495
|
+
existing_python_path = worker_environment.get("PYTHONPATH")
|
|
496
|
+
worker_environment["PYTHONPATH"] = os.pathsep.join(
|
|
497
|
+
path for path in (package_root, existing_python_path) if path
|
|
498
|
+
)
|
|
499
|
+
self.messages.put(("log", f"Worker: {python_executable}"))
|
|
331
500
|
process = subprocess.Popen(
|
|
332
501
|
command,
|
|
333
502
|
cwd=Path.cwd(),
|
|
503
|
+
env=worker_environment,
|
|
334
504
|
stdout=subprocess.PIPE,
|
|
335
505
|
stderr=subprocess.STDOUT,
|
|
336
506
|
text=True,
|
|
@@ -338,6 +508,7 @@ class FeatureRankGUI:
|
|
|
338
508
|
)
|
|
339
509
|
assert process.stdout is not None
|
|
340
510
|
for line in process.stdout:
|
|
511
|
+
output_lines.append(line.rstrip())
|
|
341
512
|
self.messages.put(("log", line))
|
|
342
513
|
return_code = process.wait()
|
|
343
514
|
if return_code != 0:
|
|
@@ -347,7 +518,9 @@ class FeatureRankGUI:
|
|
|
347
518
|
"Anaconda TensorFlow paketini guncelleyin veya uyumlu bir "
|
|
348
519
|
"Python yorumlayicisi kullanin."
|
|
349
520
|
)
|
|
350
|
-
|
|
521
|
+
recent_output = "\n".join(line for line in output_lines[-12:] if line.strip())
|
|
522
|
+
details = f"\n\nSon worker çıktısı:\n{recent_output}" if recent_output else ""
|
|
523
|
+
raise RuntimeError(f"FeatureRank worker stopped with code {return_code}.{details}")
|
|
351
524
|
|
|
352
525
|
average = 0.0
|
|
353
526
|
values: list[float] = []
|
|
@@ -489,11 +662,31 @@ class FeatureRankGUI:
|
|
|
489
662
|
elif "metrik" in lower or "metric" in lower or "tamamlandi" in lower:
|
|
490
663
|
self._set_progress("Calculating Results", 95)
|
|
491
664
|
|
|
665
|
+
def _animate_progress(self) -> None:
|
|
666
|
+
"""Advance the bar smoothly while the worker is still running.
|
|
667
|
+
|
|
668
|
+
Workflow messages move the bar to meaningful stages. This timer fills
|
|
669
|
+
the gaps between those messages based on elapsed time, then waits near
|
|
670
|
+
90% until the worker reports completion instead of pretending to know
|
|
671
|
+
the exact duration of every dataset.
|
|
672
|
+
"""
|
|
673
|
+
if not self.progress_running or self.progress_started_at is None:
|
|
674
|
+
return
|
|
675
|
+
elapsed = time.perf_counter() - self.progress_started_at
|
|
676
|
+
time_based_value = min(90.0, 2.0 + elapsed * 1.2)
|
|
677
|
+
if time_based_value > self.progress_value:
|
|
678
|
+
self.progress_value = time_based_value
|
|
679
|
+
self.progress.configure(value=self.progress_value)
|
|
680
|
+
self.status_var.set(f"The experiment is running. Elapsed time: {elapsed:.0f} s.")
|
|
681
|
+
self.progress_job = self.root.after(200, self._animate_progress)
|
|
682
|
+
|
|
492
683
|
def _set_progress(self, stage: str, value: int) -> None:
|
|
493
684
|
self.stage_var.set(stage)
|
|
494
|
-
self.
|
|
685
|
+
self.progress_value = max(self.progress_value, float(min(value, 99)))
|
|
686
|
+
self.progress.configure(value=self.progress_value)
|
|
495
687
|
|
|
496
688
|
def _completed(self, summary: dict[str, str]) -> None:
|
|
689
|
+
self._stop_progress_animation()
|
|
497
690
|
for key, value in summary.items():
|
|
498
691
|
self.summary_vars[key].set(value)
|
|
499
692
|
output = summary.get("Output Directory", "N/A")
|
|
@@ -506,13 +699,22 @@ class FeatureRankGUI:
|
|
|
506
699
|
self.start_button.configure(state="normal")
|
|
507
700
|
|
|
508
701
|
def _failed(self, error: str) -> None:
|
|
702
|
+
self._stop_progress_animation()
|
|
509
703
|
self.progress.configure(value=0)
|
|
704
|
+
self.progress_value = 0.0
|
|
510
705
|
self.stage_var.set("Failed")
|
|
511
706
|
self.status_var.set("The experiment could not be completed.")
|
|
512
707
|
self._append_log(f"ERROR: {error}")
|
|
513
708
|
self.start_button.configure(state="normal")
|
|
514
709
|
messagebox.showerror("FeatureRank", error)
|
|
515
710
|
|
|
711
|
+
def _stop_progress_animation(self) -> None:
|
|
712
|
+
self.progress_running = False
|
|
713
|
+
self.progress_started_at = None
|
|
714
|
+
if self.progress_job is not None:
|
|
715
|
+
self.root.after_cancel(self.progress_job)
|
|
716
|
+
self.progress_job = None
|
|
717
|
+
|
|
516
718
|
def _clear_log(self) -> None:
|
|
517
719
|
self.log_text.configure(state="normal")
|
|
518
720
|
self.log_text.delete("1.0", tk.END)
|