FeatureRank 0.1.1__tar.gz → 0.1.4__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.1 → featurerank-0.1.4}/FeatureRank/GUI.py +223 -72
- {featurerank-0.1.1 → featurerank-0.1.4}/FeatureRank/__init__.py +1 -1
- {featurerank-0.1.1 → featurerank-0.1.4/FeatureRank.egg-info}/PKG-INFO +76 -30
- featurerank-0.1.4/FeatureRank.egg-info/requires.txt +11 -0
- featurerank-0.1.1/Readme.md → featurerank-0.1.4/PKG-INFO +87 -27
- featurerank-0.1.1/FeatureRank.egg-info/PKG-INFO → featurerank-0.1.4/README.md +71 -41
- {featurerank-0.1.1 → featurerank-0.1.4}/pyproject.toml +5 -3
- featurerank-0.1.1/FeatureRank.egg-info/requires.txt +0 -5
- {featurerank-0.1.1 → featurerank-0.1.4}/FeatureRank/__main__.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/FeatureRank.egg-info/SOURCES.txt +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/FeatureRank.egg-info/dependency_links.txt +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/FeatureRank.egg-info/entry_points.txt +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/FeatureRank.egg-info/top_level.txt +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/EvaluatePaperDimensionReduction.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/FeatureBlockDatasetTools.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/FeatureRank.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateAccuracyBoxplotFromList.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateClassificationFigure.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateClusterClassificationSummaryFigure.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateClusterFigure1.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateConfusionMatrixPanel.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/Figure_Scripts/CreatePrecisionRecallPanel.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateRegressionFigure2.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateRocPanel.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/GeneratePaperDimensionReduction.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/PaperDimensionReductionCommon.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/RunAutoencoder.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/RunBlockFeatureSelection.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/RunDimensionReduction.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/RunFeatureRankCV.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/Simple.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/scripts/__init__.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/setup.cfg +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/AutoencoderFeatureSelection.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/Classification.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/Clustering.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/Config.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/DataLoader.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/DivideCombine.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/Experiment.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/Models.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/OutputPaths.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/Preprocessing.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/Regression.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/Reporting.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/Runtime.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/Utils.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/Workflow.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/src/__init__.py +0 -0
- {featurerank-0.1.1 → featurerank-0.1.4}/tests/test_feature_rank_refactor.py +0 -0
|
@@ -6,8 +6,6 @@ still performed by ``scripts.FeatureRank`` and the existing ``src`` modules.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
import contextlib
|
|
10
|
-
import io
|
|
11
9
|
import os
|
|
12
10
|
from pathlib import Path
|
|
13
11
|
import queue
|
|
@@ -21,6 +19,7 @@ from tkinter import filedialog, messagebox, ttk
|
|
|
21
19
|
|
|
22
20
|
PERCENT_VALUES = tuple(str(value) for value in range(10, 101, 10))
|
|
23
21
|
BLOCK_VALUES = ("5", "10", "20")
|
|
22
|
+
NO_DATASET_LABEL = "No Dataset"
|
|
24
23
|
MODE_LABELS = {
|
|
25
24
|
"GL": "GL — Global Feature Ranking",
|
|
26
25
|
"DC": "DC — Divide & Combine Feature Ranking",
|
|
@@ -33,22 +32,6 @@ TASK_LABELS = {
|
|
|
33
32
|
_WINDOW_OPENED = False
|
|
34
33
|
|
|
35
34
|
|
|
36
|
-
class _QueueWriter(io.TextIOBase):
|
|
37
|
-
"""Forward workflow output to the GUI queue without changing the workflow."""
|
|
38
|
-
|
|
39
|
-
def __init__(self, messages: queue.Queue[tuple[str, object]]) -> None:
|
|
40
|
-
super().__init__()
|
|
41
|
-
self.messages = messages
|
|
42
|
-
|
|
43
|
-
def write(self, text: str) -> int:
|
|
44
|
-
if text:
|
|
45
|
-
self.messages.put(("log", text))
|
|
46
|
-
return len(text)
|
|
47
|
-
|
|
48
|
-
def flush(self) -> None:
|
|
49
|
-
return None
|
|
50
|
-
|
|
51
|
-
|
|
52
35
|
def _dataset_choices() -> list[str]:
|
|
53
36
|
"""Find the repository's paired raw datasets for the dropdown."""
|
|
54
37
|
project_root = Path(__file__).resolve().parent.parent
|
|
@@ -58,9 +41,42 @@ def _dataset_choices() -> list[str]:
|
|
|
58
41
|
if not directory.exists():
|
|
59
42
|
continue
|
|
60
43
|
for data_file in sorted(directory.glob("*_data.csv")):
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
44
|
+
absolute_path = str(data_file.resolve())
|
|
45
|
+
if data_file.is_file() and absolute_path not in choices:
|
|
46
|
+
choices.append(absolute_path)
|
|
47
|
+
return choices or [NO_DATASET_LABEL]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _resolve_dataset_path(dataset: str) -> str:
|
|
51
|
+
"""Use an absolute path when the selected file exists locally."""
|
|
52
|
+
requested = Path(dataset).expanduser()
|
|
53
|
+
if requested.is_file():
|
|
54
|
+
return str(requested.resolve())
|
|
55
|
+
|
|
56
|
+
project_root = Path(__file__).resolve().parent.parent
|
|
57
|
+
candidates = (
|
|
58
|
+
Path.cwd() / requested,
|
|
59
|
+
Path.cwd() / "data" / "raw" / requested,
|
|
60
|
+
project_root / requested,
|
|
61
|
+
project_root / "data" / "raw" / requested,
|
|
62
|
+
)
|
|
63
|
+
for candidate in candidates:
|
|
64
|
+
if candidate.is_file():
|
|
65
|
+
return str(candidate.resolve())
|
|
66
|
+
return dataset
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _worker_python() -> Path:
|
|
70
|
+
"""Find a project virtual-environment interpreter before the system one."""
|
|
71
|
+
project_root = Path(__file__).resolve().parent.parent
|
|
72
|
+
executable_name = "python.exe" if os.name == "nt" else "python"
|
|
73
|
+
candidates = (
|
|
74
|
+
Path.cwd() / ".venv" / ("Scripts" if os.name == "nt" else "bin") / executable_name,
|
|
75
|
+
project_root / ".venv" / ("Scripts" if os.name == "nt" else "bin") / executable_name,
|
|
76
|
+
Path(sys.executable).with_name("python"),
|
|
77
|
+
Path(sys.executable),
|
|
78
|
+
)
|
|
79
|
+
return next((candidate for candidate in candidates if candidate.is_file()), candidates[-1])
|
|
64
80
|
|
|
65
81
|
|
|
66
82
|
def _open_folder(path: Path) -> None:
|
|
@@ -97,16 +113,27 @@ class FeatureRankGUI:
|
|
|
97
113
|
def __init__(self, root: tk.Tk) -> None:
|
|
98
114
|
self.root = root
|
|
99
115
|
self.root.title("FeatureRank")
|
|
100
|
-
|
|
101
|
-
self.root.
|
|
116
|
+
# Keep the first view compact; the log can still grow when resized.
|
|
117
|
+
self.root.geometry("760x680")
|
|
118
|
+
self.root.minsize(680, 600)
|
|
102
119
|
|
|
103
120
|
self.messages: queue.Queue[tuple[str, object]] = queue.Queue()
|
|
104
121
|
self.worker: threading.Thread | None = None
|
|
105
122
|
self.last_output_dir: Path | None = None
|
|
106
123
|
self.block_count = 10
|
|
107
124
|
self.blocks_seen = 0
|
|
125
|
+
self.progress_value = 0.0
|
|
126
|
+
self.progress_started_at: float | None = None
|
|
127
|
+
self.progress_job: str | None = None
|
|
128
|
+
self.progress_running = False
|
|
129
|
+
|
|
130
|
+
dataset_paths = _dataset_choices()
|
|
131
|
+
self.dataset_paths = {
|
|
132
|
+
Path(path).name: path for path in dataset_paths if path != NO_DATASET_LABEL
|
|
133
|
+
}
|
|
134
|
+
self.dataset_labels = [NO_DATASET_LABEL, *self.dataset_paths]
|
|
108
135
|
|
|
109
|
-
self.dataset_var = tk.StringVar(value=
|
|
136
|
+
self.dataset_var = tk.StringVar(value=NO_DATASET_LABEL)
|
|
110
137
|
self.percent_var = tk.StringVar(value="20")
|
|
111
138
|
self.task_var = tk.StringVar(value="Classification")
|
|
112
139
|
self.mode_var = tk.StringVar(value=MODE_LABELS["GL"])
|
|
@@ -139,16 +166,56 @@ class FeatureRankGUI:
|
|
|
139
166
|
style.theme_use("clam")
|
|
140
167
|
except tk.TclError:
|
|
141
168
|
pass
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
style.configure("
|
|
145
|
-
style.configure("
|
|
169
|
+
# A restrained palette gives the form a clean surface on all platforms.
|
|
170
|
+
self.root.configure(background="#f4f6f8")
|
|
171
|
+
style.configure("TFrame", background="#f4f6f8")
|
|
172
|
+
style.configure("TLabel", background="#ffffff", foreground="#1f2937")
|
|
173
|
+
style.configure("TLabelframe", background="#ffffff")
|
|
174
|
+
style.configure("TLabelframe.Label", background="#ffffff", foreground="#1f2937")
|
|
175
|
+
style.configure(
|
|
176
|
+
"Title.TLabel",
|
|
177
|
+
background="#f4f6f8",
|
|
178
|
+
font=("Helvetica", 20, "bold"),
|
|
179
|
+
foreground="#1f2937",
|
|
180
|
+
)
|
|
181
|
+
style.configure(
|
|
182
|
+
"Subtitle.TLabel",
|
|
183
|
+
background="#f4f6f8",
|
|
184
|
+
font=("Helvetica", 10),
|
|
185
|
+
foreground="#4b5563",
|
|
186
|
+
)
|
|
187
|
+
style.configure(
|
|
188
|
+
"Stage.TLabel",
|
|
189
|
+
background="#f4f6f8",
|
|
190
|
+
font=("Helvetica", 11, "bold"),
|
|
191
|
+
foreground="#1f4e79",
|
|
192
|
+
)
|
|
193
|
+
style.configure(
|
|
194
|
+
"Run.TButton",
|
|
195
|
+
background="#1f4e79",
|
|
196
|
+
foreground="#ffffff",
|
|
197
|
+
font=("Helvetica", 11, "bold"),
|
|
198
|
+
padding=(16, 6),
|
|
199
|
+
)
|
|
200
|
+
style.map(
|
|
201
|
+
"Run.TButton",
|
|
202
|
+
background=[("active", "#173a5c"), ("disabled", "#b9c3cf")],
|
|
203
|
+
foreground=[("disabled", "#eef2f6")],
|
|
204
|
+
)
|
|
205
|
+
style.configure(
|
|
206
|
+
"Horizontal.TProgressbar",
|
|
207
|
+
background="#1f4e79",
|
|
208
|
+
troughcolor="#e5e7eb",
|
|
209
|
+
bordercolor="#d1d5db",
|
|
210
|
+
lightcolor="#1f4e79",
|
|
211
|
+
darkcolor="#1f4e79",
|
|
212
|
+
)
|
|
146
213
|
|
|
147
214
|
def _build_window(self) -> None:
|
|
148
215
|
self.root.columnconfigure(0, weight=1)
|
|
149
216
|
self.root.rowconfigure(3, weight=1)
|
|
150
217
|
|
|
151
|
-
header = ttk.Frame(self.root, padding=(
|
|
218
|
+
header = ttk.Frame(self.root, padding=(24, 18, 24, 8))
|
|
152
219
|
header.grid(row=0, column=0, sticky="ew")
|
|
153
220
|
ttk.Label(header, text="FeatureRank", style="Title.TLabel").pack(anchor="w")
|
|
154
221
|
ttk.Label(
|
|
@@ -157,53 +224,55 @@ class FeatureRankGUI:
|
|
|
157
224
|
style="Subtitle.TLabel",
|
|
158
225
|
).pack(anchor="w", pady=(4, 0))
|
|
159
226
|
|
|
160
|
-
options = ttk.LabelFrame(self.root, text="Experiment", padding=
|
|
161
|
-
options.grid(row=1, column=0, padx=
|
|
227
|
+
options = ttk.LabelFrame(self.root, text="Experiment", padding=14)
|
|
228
|
+
options.grid(row=1, column=0, padx=24, pady=6, sticky="ew")
|
|
162
229
|
options.columnconfigure(1, weight=1)
|
|
163
230
|
|
|
164
|
-
ttk.Label(options, text="Dataset").grid(row=0, column=0, sticky="w", pady=
|
|
231
|
+
ttk.Label(options, text="Dataset").grid(row=0, column=0, sticky="w", pady=5)
|
|
165
232
|
self.dataset_combo = ttk.Combobox(
|
|
166
|
-
options, textvariable=self.dataset_var, values=
|
|
233
|
+
options, textvariable=self.dataset_var, values=self.dataset_labels, state="readonly"
|
|
167
234
|
)
|
|
168
|
-
self.dataset_combo.grid(row=0, column=1, sticky="ew", padx=(
|
|
235
|
+
self.dataset_combo.grid(row=0, column=1, sticky="ew", padx=(10, 6), pady=5)
|
|
169
236
|
ttk.Button(options, text="Browse…", command=self._browse_dataset).grid(
|
|
170
|
-
row=0, column=2, pady=
|
|
237
|
+
row=0, column=2, pady=5
|
|
171
238
|
)
|
|
172
239
|
|
|
173
|
-
ttk.Label(options, text="Task").grid(row=1, column=0, sticky="w", pady=
|
|
240
|
+
ttk.Label(options, text="Task").grid(row=1, column=0, sticky="w", pady=5)
|
|
174
241
|
ttk.Combobox(
|
|
175
242
|
options,
|
|
176
243
|
textvariable=self.task_var,
|
|
177
244
|
values=tuple(TASK_LABELS),
|
|
178
245
|
state="readonly",
|
|
179
|
-
).grid(row=1, column=1, sticky="ew", padx=(
|
|
246
|
+
).grid(row=1, column=1, sticky="ew", padx=(10, 6), pady=5)
|
|
180
247
|
|
|
181
|
-
ttk.Label(options, text="Feature Percent").grid(row=2, column=0, sticky="w", pady=
|
|
248
|
+
ttk.Label(options, text="Feature Percent").grid(row=2, column=0, sticky="w", pady=5)
|
|
182
249
|
ttk.Combobox(
|
|
183
250
|
options,
|
|
184
251
|
textvariable=self.percent_var,
|
|
185
252
|
values=PERCENT_VALUES,
|
|
186
253
|
state="readonly",
|
|
187
254
|
width=12,
|
|
188
|
-
).grid(row=2, column=1, sticky="w", padx=(
|
|
255
|
+
).grid(row=2, column=1, sticky="w", padx=(10, 6), pady=5)
|
|
189
256
|
|
|
190
|
-
ttk.Label(options, text="Mode").grid(row=3, column=0, sticky="w", pady=
|
|
257
|
+
ttk.Label(options, text="Mode").grid(row=3, column=0, sticky="w", pady=5)
|
|
191
258
|
self.mode_combo = ttk.Combobox(
|
|
192
259
|
options,
|
|
193
260
|
textvariable=self.mode_var,
|
|
194
261
|
values=tuple(MODE_LABELS.values()),
|
|
195
262
|
state="readonly",
|
|
196
263
|
)
|
|
197
|
-
self.mode_combo.grid(row=3, column=1, sticky="ew", padx=(
|
|
264
|
+
self.mode_combo.grid(row=3, column=1, sticky="ew", padx=(10, 6), pady=5)
|
|
198
265
|
self.mode_combo.bind("<<ComboboxSelected>>", self._mode_changed)
|
|
199
266
|
|
|
200
|
-
ttk.Label(options, text="Block Count").grid(row=4, column=0, sticky="w", pady=
|
|
267
|
+
ttk.Label(options, text="Block Count").grid(row=4, column=0, sticky="w", pady=5)
|
|
201
268
|
self.block_combo = ttk.Combobox(
|
|
202
269
|
options, textvariable=self.block_var, values=BLOCK_VALUES, state="disabled", width=12
|
|
203
270
|
)
|
|
204
|
-
self.block_combo.grid(row=4, column=1, sticky="w", padx=(
|
|
271
|
+
self.block_combo.grid(row=4, column=1, sticky="w", padx=(10, 6), pady=5)
|
|
205
272
|
|
|
206
|
-
|
|
273
|
+
# Keep the run controls in a shallow toolbar so START is not surrounded
|
|
274
|
+
# by a large empty band.
|
|
275
|
+
action_row = ttk.Frame(self.root, padding=(24, 2, 24, 2))
|
|
207
276
|
action_row.grid(row=2, column=0, sticky="ew")
|
|
208
277
|
action_row.columnconfigure(1, weight=1)
|
|
209
278
|
self.start_button = ttk.Button(
|
|
@@ -214,14 +283,14 @@ class FeatureRankGUI:
|
|
|
214
283
|
row=0, column=1, sticky="e", padx=(12, 0)
|
|
215
284
|
)
|
|
216
285
|
|
|
217
|
-
progress_frame = ttk.Frame(self.root, padding=(
|
|
286
|
+
progress_frame = ttk.Frame(self.root, padding=(24, 0, 24, 8))
|
|
218
287
|
progress_frame.grid(row=3, column=0, sticky="nsew")
|
|
219
288
|
progress_frame.columnconfigure(0, weight=1)
|
|
220
289
|
progress_frame.rowconfigure(2, weight=1)
|
|
221
290
|
self.progress = ttk.Progressbar(progress_frame, mode="determinate", maximum=100)
|
|
222
291
|
self.progress.grid(row=0, column=0, sticky="ew")
|
|
223
292
|
ttk.Label(progress_frame, textvariable=self.status_var, style="Subtitle.TLabel").grid(
|
|
224
|
-
row=1, column=0, sticky="w", pady=(
|
|
293
|
+
row=1, column=0, sticky="w", pady=(5, 6)
|
|
225
294
|
)
|
|
226
295
|
|
|
227
296
|
log_frame = ttk.LabelFrame(progress_frame, text="Log / Output", padding=8)
|
|
@@ -230,7 +299,7 @@ class FeatureRankGUI:
|
|
|
230
299
|
log_frame.rowconfigure(0, weight=1)
|
|
231
300
|
self.log_text = tk.Text(
|
|
232
301
|
log_frame,
|
|
233
|
-
height=
|
|
302
|
+
height=8,
|
|
234
303
|
wrap="word",
|
|
235
304
|
state="disabled",
|
|
236
305
|
background="#f8fafc",
|
|
@@ -244,19 +313,19 @@ class FeatureRankGUI:
|
|
|
244
313
|
scrollbar.grid(row=0, column=1, sticky="ns")
|
|
245
314
|
self.log_text.configure(yscrollcommand=scrollbar.set)
|
|
246
315
|
|
|
247
|
-
summary = ttk.LabelFrame(self.root, text="Summary", padding=
|
|
248
|
-
summary.grid(row=4, column=0, padx=
|
|
316
|
+
summary = ttk.LabelFrame(self.root, text="Summary", padding=12)
|
|
317
|
+
summary.grid(row=4, column=0, padx=24, pady=(0, 8), sticky="ew")
|
|
249
318
|
summary.columnconfigure(1, weight=1)
|
|
250
319
|
for row, (key, value) in enumerate(self.summary_vars.items()):
|
|
251
320
|
ttk.Label(summary, text=key).grid(row=row // 2, column=(row % 2) * 2, sticky="w")
|
|
252
321
|
ttk.Label(summary, textvariable=value, foreground="#1f4e79").grid(
|
|
253
|
-
row=row // 2, column=(row % 2) * 2 + 1, sticky="w", padx=(8,
|
|
322
|
+
row=row // 2, column=(row % 2) * 2 + 1, sticky="w", padx=(8, 22), pady=2
|
|
254
323
|
)
|
|
255
324
|
|
|
256
325
|
self.open_button = ttk.Button(
|
|
257
326
|
self.root, text="Open Results Folder", command=self._open_results, state="disabled"
|
|
258
327
|
)
|
|
259
|
-
self.open_button.grid(row=5, column=0, padx=
|
|
328
|
+
self.open_button.grid(row=5, column=0, padx=24, pady=(0, 14), sticky="w")
|
|
260
329
|
|
|
261
330
|
def _browse_dataset(self) -> None:
|
|
262
331
|
selected = filedialog.askopenfilename(
|
|
@@ -264,14 +333,20 @@ class FeatureRankGUI:
|
|
|
264
333
|
filetypes=[("CSV files", "*.csv"), ("Text files", "*.txt"), ("All files", "*.*")],
|
|
265
334
|
)
|
|
266
335
|
if selected:
|
|
267
|
-
|
|
336
|
+
# Keep the full path internally, but show only the file name in
|
|
337
|
+
# the form so the selected dataset remains readable.
|
|
338
|
+
self.dataset_paths[Path(selected).name] = selected
|
|
339
|
+
self.dataset_labels = [NO_DATASET_LABEL, *self.dataset_paths]
|
|
340
|
+
self.dataset_combo.configure(values=self.dataset_labels)
|
|
341
|
+
self.dataset_var.set(Path(selected).name)
|
|
268
342
|
|
|
269
343
|
def _mode_changed(self, _event: object = None) -> None:
|
|
270
344
|
is_dc = self.mode_var.get().startswith("DC")
|
|
271
345
|
self.block_combo.configure(state="readonly" if is_dc else "disabled")
|
|
272
346
|
|
|
273
347
|
def _start(self) -> None:
|
|
274
|
-
|
|
348
|
+
dataset_label = self.dataset_var.get().strip()
|
|
349
|
+
dataset = self.dataset_paths.get(dataset_label, "")
|
|
275
350
|
if not dataset:
|
|
276
351
|
messagebox.showerror("FeatureRank", "Please select a dataset.")
|
|
277
352
|
return
|
|
@@ -291,10 +366,13 @@ class FeatureRankGUI:
|
|
|
291
366
|
self.last_output_dir = None
|
|
292
367
|
self.open_button.configure(state="disabled")
|
|
293
368
|
self.start_button.configure(state="disabled")
|
|
294
|
-
self.
|
|
369
|
+
self.progress_value = 2.0
|
|
370
|
+
self.progress.configure(value=self.progress_value)
|
|
295
371
|
self.stage_var.set("Loading Dataset")
|
|
296
372
|
self.status_var.set("The experiment is running. You can follow progress in the log.")
|
|
297
373
|
self._clear_log()
|
|
374
|
+
self.progress_started_at = time.perf_counter()
|
|
375
|
+
self.progress_running = True
|
|
298
376
|
|
|
299
377
|
self.worker = threading.Thread(
|
|
300
378
|
target=self._run_experiment,
|
|
@@ -302,23 +380,24 @@ class FeatureRankGUI:
|
|
|
302
380
|
daemon=True,
|
|
303
381
|
)
|
|
304
382
|
self.worker.start()
|
|
383
|
+
self._animate_progress()
|
|
305
384
|
self.root.after(100, self._poll_messages)
|
|
306
385
|
|
|
307
386
|
def _run_experiment(
|
|
308
387
|
self, dataset: str, percent: float, task: str, mode: str, block_count: int
|
|
309
388
|
) -> None:
|
|
310
389
|
started_at = time.perf_counter()
|
|
311
|
-
|
|
390
|
+
output_lines: list[str] = []
|
|
312
391
|
try:
|
|
313
|
-
#
|
|
314
|
-
#
|
|
315
|
-
#
|
|
316
|
-
|
|
392
|
+
# Keep TensorFlow out of the Tk process. On macOS, loading a model
|
|
393
|
+
# from a GUI thread can terminate the native Python process. The
|
|
394
|
+
# existing FeatureRank CLI runs in a separate worker instead; its
|
|
395
|
+
# stdout is streamed back into this window.
|
|
317
396
|
from src.Config import ExperimentConfig
|
|
318
|
-
from src.OutputPaths import metric_runs_path
|
|
319
397
|
|
|
398
|
+
dataset_path = _resolve_dataset_path(dataset)
|
|
320
399
|
config = ExperimentConfig(
|
|
321
|
-
dataset_name=
|
|
400
|
+
dataset_name=dataset_path,
|
|
322
401
|
task=task,
|
|
323
402
|
feature_percent=percent,
|
|
324
403
|
random_state=42,
|
|
@@ -328,16 +407,59 @@ class FeatureRankGUI:
|
|
|
328
407
|
cluster_k=None,
|
|
329
408
|
save_details=False,
|
|
330
409
|
)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
410
|
+
python_executable = _worker_python()
|
|
411
|
+
command = [
|
|
412
|
+
str(python_executable),
|
|
413
|
+
"-m",
|
|
414
|
+
"scripts.FeatureRank",
|
|
415
|
+
"--dataset-name",
|
|
416
|
+
dataset_path,
|
|
417
|
+
"--task",
|
|
418
|
+
task,
|
|
419
|
+
"--feature-percent",
|
|
420
|
+
str(percent),
|
|
421
|
+
"--random-state",
|
|
422
|
+
"42",
|
|
423
|
+
"--dc" if mode == "dc" else "--global",
|
|
424
|
+
]
|
|
425
|
+
if mode == "dc":
|
|
426
|
+
command.extend(["--block-count", str(block_count)])
|
|
427
|
+
|
|
428
|
+
worker_environment = os.environ.copy()
|
|
429
|
+
worker_environment["PYTHONUNBUFFERED"] = "1"
|
|
430
|
+
package_root = str(Path(__file__).resolve().parent.parent)
|
|
431
|
+
existing_python_path = worker_environment.get("PYTHONPATH")
|
|
432
|
+
worker_environment["PYTHONPATH"] = os.pathsep.join(
|
|
433
|
+
path for path in (package_root, existing_python_path) if path
|
|
434
|
+
)
|
|
435
|
+
self.messages.put(("log", f"Worker: {python_executable}"))
|
|
436
|
+
process = subprocess.Popen(
|
|
437
|
+
command,
|
|
438
|
+
cwd=Path.cwd(),
|
|
439
|
+
env=worker_environment,
|
|
440
|
+
stdout=subprocess.PIPE,
|
|
441
|
+
stderr=subprocess.STDOUT,
|
|
442
|
+
text=True,
|
|
443
|
+
bufsize=1,
|
|
444
|
+
)
|
|
445
|
+
assert process.stdout is not None
|
|
446
|
+
for line in process.stdout:
|
|
447
|
+
output_lines.append(line.rstrip())
|
|
448
|
+
self.messages.put(("log", line))
|
|
449
|
+
return_code = process.wait()
|
|
450
|
+
if return_code != 0:
|
|
451
|
+
if return_code in {139, -11}:
|
|
452
|
+
raise RuntimeError(
|
|
453
|
+
"TensorFlow Anaconda ortaminda baslatilamadi (native crash). "
|
|
454
|
+
"Anaconda TensorFlow paketini guncelleyin veya uyumlu bir "
|
|
455
|
+
"Python yorumlayicisi kullanin."
|
|
456
|
+
)
|
|
457
|
+
recent_output = "\n".join(line for line in output_lines[-12:] if line.strip())
|
|
458
|
+
details = f"\n\nSon worker çıktısı:\n{recent_output}" if recent_output else ""
|
|
459
|
+
raise RuntimeError(f"FeatureRank worker stopped with code {return_code}.{details}")
|
|
460
|
+
|
|
461
|
+
average = 0.0
|
|
462
|
+
values: list[float] = []
|
|
341
463
|
summary = self._read_summary(
|
|
342
464
|
config,
|
|
343
465
|
mode,
|
|
@@ -476,11 +598,31 @@ class FeatureRankGUI:
|
|
|
476
598
|
elif "metrik" in lower or "metric" in lower or "tamamlandi" in lower:
|
|
477
599
|
self._set_progress("Calculating Results", 95)
|
|
478
600
|
|
|
601
|
+
def _animate_progress(self) -> None:
|
|
602
|
+
"""Advance the bar smoothly while the worker is still running.
|
|
603
|
+
|
|
604
|
+
Workflow messages move the bar to meaningful stages. This timer fills
|
|
605
|
+
the gaps between those messages based on elapsed time, then waits near
|
|
606
|
+
90% until the worker reports completion instead of pretending to know
|
|
607
|
+
the exact duration of every dataset.
|
|
608
|
+
"""
|
|
609
|
+
if not self.progress_running or self.progress_started_at is None:
|
|
610
|
+
return
|
|
611
|
+
elapsed = time.perf_counter() - self.progress_started_at
|
|
612
|
+
time_based_value = min(90.0, 2.0 + elapsed * 1.2)
|
|
613
|
+
if time_based_value > self.progress_value:
|
|
614
|
+
self.progress_value = time_based_value
|
|
615
|
+
self.progress.configure(value=self.progress_value)
|
|
616
|
+
self.status_var.set(f"The experiment is running. Elapsed time: {elapsed:.0f} s.")
|
|
617
|
+
self.progress_job = self.root.after(200, self._animate_progress)
|
|
618
|
+
|
|
479
619
|
def _set_progress(self, stage: str, value: int) -> None:
|
|
480
620
|
self.stage_var.set(stage)
|
|
481
|
-
self.
|
|
621
|
+
self.progress_value = max(self.progress_value, float(min(value, 99)))
|
|
622
|
+
self.progress.configure(value=self.progress_value)
|
|
482
623
|
|
|
483
624
|
def _completed(self, summary: dict[str, str]) -> None:
|
|
625
|
+
self._stop_progress_animation()
|
|
484
626
|
for key, value in summary.items():
|
|
485
627
|
self.summary_vars[key].set(value)
|
|
486
628
|
output = summary.get("Output Directory", "N/A")
|
|
@@ -493,13 +635,22 @@ class FeatureRankGUI:
|
|
|
493
635
|
self.start_button.configure(state="normal")
|
|
494
636
|
|
|
495
637
|
def _failed(self, error: str) -> None:
|
|
638
|
+
self._stop_progress_animation()
|
|
496
639
|
self.progress.configure(value=0)
|
|
640
|
+
self.progress_value = 0.0
|
|
497
641
|
self.stage_var.set("Failed")
|
|
498
642
|
self.status_var.set("The experiment could not be completed.")
|
|
499
643
|
self._append_log(f"ERROR: {error}")
|
|
500
644
|
self.start_button.configure(state="normal")
|
|
501
645
|
messagebox.showerror("FeatureRank", error)
|
|
502
646
|
|
|
647
|
+
def _stop_progress_animation(self) -> None:
|
|
648
|
+
self.progress_running = False
|
|
649
|
+
self.progress_started_at = None
|
|
650
|
+
if self.progress_job is not None:
|
|
651
|
+
self.root.after_cancel(self.progress_job)
|
|
652
|
+
self.progress_job = None
|
|
653
|
+
|
|
503
654
|
def _clear_log(self) -> None:
|
|
504
655
|
self.log_text.configure(state="normal")
|
|
505
656
|
self.log_text.delete("1.0", tk.END)
|