Skip to content

Commit f373a6b

Browse files
committed
Merge branch 'import_export_config'
2 parents 5e5e40a + d9b8635 commit f373a6b

File tree

6 files changed

+256
-44
lines changed

6 files changed

+256
-44
lines changed

MLC/GUI/Autogenerated/autogenerated.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ class Ui_ExperimentWindow(object):
362362
def setupUi(self, ExperimentWindow):
363363
ExperimentWindow.setObjectName("ExperimentWindow")
364364
ExperimentWindow.setWindowModality(QtCore.Qt.ApplicationModal)
365-
ExperimentWindow.resize(961, 552)
365+
ExperimentWindow.resize(961, 568)
366366
self.centralWidget = QtWidgets.QWidget(ExperimentWindow)
367367
self.centralWidget.setObjectName("centralWidget")
368368
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralWidget)
@@ -666,17 +666,20 @@ def setupUi(self, ExperimentWindow):
666666
self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
667667
self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
668668
self.frame_2.setObjectName("frame_2")
669-
self.horizontalLayout_7 = QtWidgets.QHBoxLayout(self.frame_2)
670-
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
669+
self.gridLayout_7 = QtWidgets.QGridLayout(self.frame_2)
670+
self.gridLayout_7.setObjectName("gridLayout_7")
671671
self.import_config_button = QtWidgets.QPushButton(self.frame_2)
672672
self.import_config_button.setObjectName("import_config_button")
673-
self.horizontalLayout_7.addWidget(self.import_config_button)
673+
self.gridLayout_7.addWidget(self.import_config_button, 0, 0, 1, 1)
674674
self.export_config_button = QtWidgets.QPushButton(self.frame_2)
675675
self.export_config_button.setObjectName("export_config_button")
676-
self.horizontalLayout_7.addWidget(self.export_config_button)
676+
self.gridLayout_7.addWidget(self.export_config_button, 0, 1, 1, 1)
677677
self.save_config_button = QtWidgets.QPushButton(self.frame_2)
678678
self.save_config_button.setObjectName("save_config_button")
679-
self.horizontalLayout_7.addWidget(self.save_config_button)
679+
self.gridLayout_7.addWidget(self.save_config_button, 1, 0, 1, 1)
680+
self.edit_config_button = QtWidgets.QPushButton(self.frame_2)
681+
self.edit_config_button.setObjectName("edit_config_button")
682+
self.gridLayout_7.addWidget(self.edit_config_button, 1, 1, 1, 1)
680683
self.verticalLayout_2.addWidget(self.frame_2)
681684
self.verticalLayout.addWidget(self.frame)
682685
self.tabWidget.addTab(self.config_tab, "")
@@ -709,6 +712,9 @@ def setupUi(self, ExperimentWindow):
709712
self.best_indiv_button.clicked.connect(ExperimentWindow.on_best_indiv_button_clicked)
710713
self.convergence_button.clicked.connect(ExperimentWindow.on_convergence_button_clicked)
711714
self.board_config_button.clicked.connect(ExperimentWindow.on_board_config_button_clicked)
715+
self.edit_config_button.clicked.connect(ExperimentWindow.on_edit_config_button_clicked)
716+
self.import_config_button.clicked.connect(ExperimentWindow.on_import_config_button_clicked)
717+
self.export_config_button.clicked.connect(ExperimentWindow.on_export_config_button_clicked)
712718
# QtCore.QMetaObject.connectSlotsByName(ExperimentWindow)
713719

714720
def retranslateUi(self, ExperimentWindow):
@@ -760,6 +766,7 @@ def retranslateUi(self, ExperimentWindow):
760766
self.import_config_button.setText(_translate("ExperimentWindow", "Import"))
761767
self.export_config_button.setText(_translate("ExperimentWindow", "Export"))
762768
self.save_config_button.setText(_translate("ExperimentWindow", "Save"))
769+
self.edit_config_button.setText(_translate("ExperimentWindow", "Edit"))
763770
self.tabWidget.setTabText(self.tabWidget.indexOf(self.config_tab), _translate("ExperimentWindow", "Configuration"))
764771

765772
# -*- coding: utf-8 -*-

MLC/GUI/Autogenerated/mlc_qtcreator/experiment.ui

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<x>0</x>
1111
<y>0</y>
1212
<width>961</width>
13-
<height>552</height>
13+
<height>568</height>
1414
</rect>
1515
</property>
1616
<property name="windowTitle">
@@ -591,28 +591,35 @@
591591
<property name="frameShadow">
592592
<enum>QFrame::Raised</enum>
593593
</property>
594-
<layout class="QHBoxLayout" name="horizontalLayout_7">
595-
<item>
594+
<layout class="QGridLayout" name="gridLayout_7">
595+
<item row="0" column="0">
596596
<widget class="QPushButton" name="import_config_button">
597597
<property name="text">
598598
<string>Import</string>
599599
</property>
600600
</widget>
601601
</item>
602-
<item>
602+
<item row="0" column="1">
603603
<widget class="QPushButton" name="export_config_button">
604604
<property name="text">
605605
<string>Export</string>
606606
</property>
607607
</widget>
608608
</item>
609-
<item>
609+
<item row="1" column="0">
610610
<widget class="QPushButton" name="save_config_button">
611611
<property name="text">
612612
<string>Save</string>
613613
</property>
614614
</widget>
615615
</item>
616+
<item row="1" column="1">
617+
<widget class="QPushButton" name="edit_config_button">
618+
<property name="text">
619+
<string>Edit</string>
620+
</property>
621+
</widget>
622+
</item>
616623
</layout>
617624
</widget>
618625
</item>
@@ -1012,6 +1019,54 @@
10121019
</hint>
10131020
</hints>
10141021
</connection>
1022+
<connection>
1023+
<sender>edit_config_button</sender>
1024+
<signal>clicked()</signal>
1025+
<receiver>ExperimentWindow</receiver>
1026+
<slot>on_edit_config_button_clicked()</slot>
1027+
<hints>
1028+
<hint type="sourcelabel">
1029+
<x>702</x>
1030+
<y>517</y>
1031+
</hint>
1032+
<hint type="destinationlabel">
1033+
<x>480</x>
1034+
<y>283</y>
1035+
</hint>
1036+
</hints>
1037+
</connection>
1038+
<connection>
1039+
<sender>import_config_button</sender>
1040+
<signal>clicked()</signal>
1041+
<receiver>ExperimentWindow</receiver>
1042+
<slot>on_import_config_button_clicked()</slot>
1043+
<hints>
1044+
<hint type="sourcelabel">
1045+
<x>258</x>
1046+
<y>489</y>
1047+
</hint>
1048+
<hint type="destinationlabel">
1049+
<x>480</x>
1050+
<y>283</y>
1051+
</hint>
1052+
</hints>
1053+
</connection>
1054+
<connection>
1055+
<sender>export_config_button</sender>
1056+
<signal>clicked()</signal>
1057+
<receiver>ExperimentWindow</receiver>
1058+
<slot>on_export_config_button_clicked()</slot>
1059+
<hints>
1060+
<hint type="sourcelabel">
1061+
<x>702</x>
1062+
<y>489</y>
1063+
</hint>
1064+
<hint type="destinationlabel">
1065+
<x>480</x>
1066+
<y>283</y>
1067+
</hint>
1068+
</hints>
1069+
</connection>
10151070
</connections>
10161071
<slots>
10171072
<slot>on_closed_dialog()</slot>
@@ -1035,5 +1090,6 @@
10351090
<slot>on_convergence_button_clicked()</slot>
10361091
<slot>on_genealogy_button_clicked()</slot>
10371092
<slot>on_board_config_button_clicked()</slot>
1093+
<slot>on_edit_config_button_clicked()</slot>
10381094
</slots>
10391095
</ui>

MLC/GUI/Experiment/ExperimentWindow.py

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import os
44
import numpy as np
5+
import shutil
56
import sys
67
import time
7-
sys.path.append(os.path.abspath(".") + "/../..")
88

99
from MLC.Log.log import get_gui_logger
1010
from MLC.GUI.Autogenerated.autogenerated import Ui_ExperimentWindow
@@ -21,10 +21,13 @@
2121
from MLC.Population.Evaluation.EvaluatorFactory import EvaluatorFactory
2222
from MLC.Population.Population import Population
2323
from PyQt5.QtCore import pyqtSignal
24+
from PyQt5.QtCore import QFileSystemWatcher
25+
# from PyQt5.QtCore import QHeaderView
2426
from PyQt5.QtCore import Qt
2527
from PyQt5.QtCore import QUrl
2628
from PyQt5.QtGui import QDesktopServices
2729
from PyQt5.QtWidgets import QAbstractItemView
30+
from PyQt5.QtWidgets import QFileDialog
2831
from PyQt5.QtWidgets import QMainWindow
2932
from PyQt5.QtWidgets import QMessageBox
3033
from PyQt5.QtWidgets import QInputDialog
@@ -73,6 +76,16 @@ def __init__(self, mlc_local,
7376
# Signal to be emitted when the experiment is closed
7477
self._experiment_closed_signal = experiment_closed_signal
7578

79+
# Watch changes in the experiment config file
80+
self._file_watcher = QFileSystemWatcher()
81+
conf_path_list = [self._mlc_local.get_working_dir(),
82+
self._experiment_name,
83+
self._experiment_name + ".conf"]
84+
# Use the splat operator to dearrange the list
85+
self._experiment_conf_path = os.path.join(*conf_path_list)
86+
self._file_watcher.addPath(self._experiment_conf_path)
87+
self._file_watcher.fileChanged.connect(self._reload_experiment_config)
88+
7689
# Experiment in progress chart configuration
7790
self._chart_conf = ChartConfiguration(self._autogenerated_object)
7891
indivs_per_gen = Config.get_instance().getint("POPULATION", "size")
@@ -83,6 +96,7 @@ def closeEvent(self, event):
8396
logger.debug('[EXPERIMENT {0}] [CLOSE_DIALOG] - Executing overriden closeEvent function'.format(self._experiment_name))
8497
self._ask_if_experiment_config_must_be_saved()
8598
# Close the experiment
99+
self._file_watcher.removePath(self._experiment_conf_path)
86100
self._mlc_local.close_experiment(self._experiment_name)
87101
self._experiment_closed_signal.emit(self._experiment_name)
88102

@@ -204,7 +218,12 @@ def on_dimension_check_clicked(self):
204218
def on_save_config_button_clicked(self):
205219
logger.debug('[EXPERIMENT {0}] [SAVE_CONFIG_BUTTON_CLICKED] - Executing on_save_config_button_clicked function'
206220
.format(self._experiment_name))
221+
222+
# Remove the config file from the Qt File watcher momentarily
223+
# to avoid rendering unnecesary warnings
224+
self._file_watcher.removePath(self._experiment_conf_path)
207225
self._persist_experiment_config()
226+
self._file_watcher.addPath(self._experiment_conf_path)
208227

209228
def on_tab_changed(self, tab_index):
210229
logger.debug('[EXPERIMENT {0}] [TAB_CHANGED] - Executing on_tab_changed function'
@@ -215,10 +234,52 @@ def on_import_config_button_clicked(self):
215234
logger.debug('[EXPERIMENT {0}] [IMPORT_CONFIG_BUTTON_CLICKED] - Executing on_import_config_button_clicked function'
216235
.format(self._experiment_name))
217236

237+
# Get the path of the experiment to import
238+
config_file_path = QFileDialog.getOpenFileName(self, "Import Experiment", ".",
239+
"Config File (*.conf)")[0]
240+
if not config_file_path:
241+
# User clicked 'Cancel' or simply closed the Dialog
242+
return
243+
244+
self._file_watcher.removePath(self._experiment_conf_path)
245+
self._mlc_local.set_experiment_configuration_from_file(self._experiment_name,
246+
config_file_path)
247+
self._load_experiment_config()
248+
QMessageBox.information(self, "Config File Imported",
249+
"New config file {0} was succesfully imported".format(config_file_path))
250+
logger.info("[EXPERIMENT {0}] [EXPORT_CONFIG] - New config file {1} was "
251+
"succesfully imported.".format(self._experiment_name, config_file_path))
252+
self._autogenerated_object.save_config_button.setDisabled(True)
253+
self._file_watcher.addPath(self._experiment_conf_path)
254+
218255
def on_export_config_button_clicked(self):
219256
logger.debug('[EXPERIMENT {0}] [EXPORT_CONFIG_BUTTON_CLICKED] - Executing on_export_config_button_clicked function'
220257
.format(self._experiment_name))
221258

259+
export_dir = QFileDialog.getExistingDirectory(self, 'Choose the directory where to export the configuration file',
260+
'.', QFileDialog.ShowDirsOnly)
261+
if not export_dir:
262+
# User clicked 'Cancel' or simply closed the Dialog
263+
return
264+
265+
export_file_name = os.path.join(export_dir, self._experiment_name + ".conf")
266+
try:
267+
shutil.copyfile(self._experiment_conf_path, export_file_name)
268+
except Exception, err:
269+
QMessageBox.critical(self, "Config File Not Exported",
270+
"Config File could not be exported. "
271+
"Error {0}".format(err))
272+
logger.error("[EXPERIMENT {0}] [EXPORT_CONFIG] - Config file could not "
273+
"be exported. Error: {0}".format(err))
274+
return
275+
276+
logger.info("[EXPERIMENT {0}] [EXPORT_CONFIG] - Config file was "
277+
"succesfully exported. Location: {1}"
278+
.format(self._experiment_name, export_file_name))
279+
QMessageBox.information(self, "Config File Exported",
280+
"Config File was succesfully exported. You can find it at {0}"
281+
.format(export_file_name))
282+
222283
def on_ev_edit_button_clicked(self):
223284
logger.debug('[EXPERIMENT {0}] [EV_EDIT_BUTTON_CLICKED] - Executing on_ev_edit_button_clicked function'
224285
.format(self._experiment_name))
@@ -313,6 +374,12 @@ def on_board_config_button_clicked(self):
313374
board_config_window = BoardConfigurationWindow(parent=self)
314375
board_config_window.show()
315376

377+
def on_edit_config_button_clicked(self):
378+
logger.debug('[EXPERIMENT {0}] [EDIT_CONFIG_BUTTON] - '
379+
'Executing on_edit_config_button_clicked function'
380+
.format(self._experiment_name))
381+
QDesktopServices.openUrl(QUrl(self._experiment_conf_path))
382+
316383
def _config_table_edited(self, left, right):
317384
config_table = self._autogenerated_object.config_table
318385
table_model = config_table.model()
@@ -472,15 +539,23 @@ def _update_experiment_info(self):
472539
number_of_gens = experiment_info["generations"]
473540
if number_of_gens == 0:
474541
from_gen_combo.addItems([str(1)])
475-
to_gen_combo.addItems([str(x) for x in xrange(2, ExperimentWindow.MAX_GENERATIONS)])
542+
to_gen_combo.addItems([str(x) for x in xrange(2, ExperimentWindow.MAX_GENERATIONS + 1)])
476543
else:
477544
from_gen_combo.addItems([str(x) for x in xrange(1, number_of_gens + 1)])
478-
to_gen_combo.addItems([str(x) for x in xrange(2, ExperimentWindow.MAX_GENERATIONS)])
545+
to_gen_combo.addItems([str(x) for x in xrange(2, ExperimentWindow.MAX_GENERATIONS + 1)])
479546

480547
# Set the from gen combo to the last generation evaluated
481548
index = from_gen_combo.findText(str(number_of_gens), Qt.MatchFixedString)
482549
from_gen_combo.setCurrentIndex(index)
483550

551+
# set the to_gen_combo to the lest generation evaluated + 1
552+
to_gen_index = number_of_gens + 1
553+
if number_of_gens == ExperimentWindow.MAX_GENERATIONS:
554+
to_gen_index = ExperimentWindow.MAX_GENERATIONS
555+
556+
index = to_gen_combo.findText(str(to_gen_index), Qt.MatchFixedString)
557+
to_gen_combo.setCurrentIndex(index)
558+
484559
# Fill the db_view
485560
gen_count_group = self._autogenerated_object.gen_count_group
486561
if number_of_gens != 0:
@@ -557,6 +632,23 @@ def _update_individuals_figure(self):
557632
# Add the Indiv Canvas
558633
chart_layout.addWidget(indiv_canvas)
559634

635+
def _reload_experiment_config(self, some_string):
636+
logger.debug('[EXPERIMENT {0}] [EXPERIMENT_CONF_FILE_CHANGED] - '
637+
'Executing _experiment_conf_file_changed function'
638+
.format(self._experiment_name))
639+
640+
self._mlc_local.reload_experiment_configuration(self._experiment_name)
641+
self._load_experiment_config()
642+
643+
QMessageBox.warning(self, "Experiment Config Changed", "The Experiment config file "
644+
"has changed. The configuration will be reloaded. "
645+
"The changes made will be discarded")
646+
logger.info('[EXPERIMENT {0}] [RELOAD_CONFIG] - Config file has changed. Configuration will be reloaded')
647+
self._autogenerated_object.save_config_button.setDisabled(True)
648+
# FIXME: Dunno why?, but I have to add the config file to the
649+
# watcher in order to work again the next time
650+
self._file_watcher.addPath(self._experiment_conf_path)
651+
560652
def _persist_experiment_config(self):
561653
try:
562654
self._mlc_local.set_experiment_configuration(self._experiment_name, self._experiment_config)
@@ -579,7 +671,9 @@ def _ask_if_experiment_config_must_be_saved(self):
579671
QMessageBox.Yes | QMessageBox.No,
580672
QMessageBox.Yes)
581673
if response == QMessageBox.Yes:
674+
self._file_watcher.removePath(self._experiment_conf_path)
582675
self._persist_experiment_config()
676+
self._file_watcher.addPath(self._experiment_conf_path)
583677
else:
584678
self._load_experiment_config()
585679

MLC/api/Experiment.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
logger = get_gui_logger()
2020

21+
2122
class Experiment:
2223
# FIXME: one simulation at a time
2324
__last_simulation = None
@@ -48,6 +49,15 @@ def get_simulation(self):
4849
def get_configuration(self):
4950
return Config.to_dictionary(self._configuration)
5051

52+
def reload_configuration(self):
53+
"""
54+
Load again the experiment configuration, reading the config file from disk
55+
"""
56+
self._configuration = ConfigParser.ConfigParser()
57+
self._configuration.read(self._config_file)
58+
Config.get_instance().read(self._config_file)
59+
return Config.to_dictionary(self._configuration)
60+
5161
def set_configuration(self, new_configuration):
5262
self._configuration = Config.from_dictionary(new_configuration, config_type=ConfigParser.ConfigParser)
5363
self._configuration.write(open(self._config_file, "wt"))

0 commit comments

Comments
 (0)