'''
main entry point to initialise the UI
'''
# Author: Matt Clifford <matt.clifford@bristol.ac.uk>
# License: BSD 3-Clause License
import os
import time
from functools import partial
from PyQt6.QtWidgets import QApplication, QMessageBox, QFileDialog
from PyQt6.QtCore import QCoreApplication
import IQM_Vis
from IQM_Vis.UI import layout, widgets, images, ProgressBar
[docs]class make_app(widgets, layout, images):
def __init__(self,
app,
data_stores: list,
transformations: dict,
metrics_info_format='graph', # graph or text
metrics_avg_graph=False,
metric_range_graph=True,
metric_params: dict={},
image_display_size=300,
default_save_dir=IQM_Vis.utils.save_utils.DEFAULT_SAVE_DIR,
default_dataset_name='dataset1',
restrict_options=None,
num_steps_range=11,
num_step_experiment=6,
num_images_scroll_show=7,
test=False
):
super().__init__()
self.app = app
self.data_stores = data_stores
self.transformations = transformations
self.num_steps_range = num_steps_range
self.num_step_experiment = num_step_experiment
self.num_images_scroll_show = num_images_scroll_show
self.preview_num = 0
self.test = test
self.metrics_info_format = metrics_info_format
self.metrics_avg_graph = metrics_avg_graph
self.metric_range_graph = metric_range_graph
self.metric_params = metric_params
self.default_save_dir = default_save_dir
self.default_dataset_name = default_dataset_name
self.restrict_options = restrict_options
self.data_lims = {'fixed': 1, 'range_data': 1}
self.plot_data_lim = 1
self.dataset = self._single_image_or_dataset()
self.setWindowTitle('IQM-Vis')
self.make_status_bar()
self.make_menu()
self.widget_settings = {}
self.image_display_size = image_display_size
self.graph_size = 35
self.rgb_brightness = 250 # max candela/m2 of rbg image
self.display_brightness = 250 # max candela/m2 of display
self.human_experiment_cache = {}
self.construct_UI()
[docs] def load_all_metrics(self):
if not hasattr(self.data_stores[0], 'add_metric'):
return
all_metrics_iqm_vis = IQM_Vis.metrics.get_all_metrics()
for metric, data in all_metrics_iqm_vis.items():
self.data_stores[0].add_metric(metric, data)
self._remake_menu()
self.construct_UI()
[docs] def load_all_metric_images(self):
if not hasattr(self.data_stores[0], 'add_metric_image'):
return
all_metric_images_iqm_vis = IQM_Vis.metrics.get_all_metric_images()
for metric, data in all_metric_images_iqm_vis.items():
self.data_stores[0].add_metric_image(metric, data)
self._remake_menu()
self.construct_UI()
[docs] def clear_all_cache_data(self):
for data_store in self.data_stores:
if hasattr(data_store, 'clear_all_cache'):
data_store.clear_all_cache()
self.status_bar.showMessage('Cleared all cache data', 8000)
[docs] def quit(self):
# QApplication.instance().quit()
self.app.quit()
def __del__(self):
# garbage collection
self.range_worker.stop()
[docs] def closeEvent(self, event):
# self.close_answer = None
# if self.test == False:
# Ask for confirmation
self.close_answer = QMessageBox.question(self,
"Confirm Exit...",
"Are you sure you want to exit?\nAll unsaved data will be lost.",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.Yes)
event.ignore()
# if self.test == True or self.close_answer == QMessageBox.StandardButton.Yes:
if self.close_answer == QMessageBox.StandardButton.Yes:
if hasattr(self, 'range_worker'):
self.range_worker.stop()
if hasattr(self, 'range_worker_thread'):
if self.range_worker_thread.isRunning():
self.range_worker_thread.quit()
self.range_worker_thread.wait()
event.accept()
# QCoreApplication.quit()
def _remake_menu(self):
self._save_checked_menu_items()
self.menu_bar.clear()
self.make_menu()
def _save_checked_menu_items(self):
self.restore_checked_menu_items = {}
for menu in ['transforms', 'metric_images', 'metrics']:
self.restore_checked_menu_items[menu] = {}
for item in self.menu_options[menu]:
self.restore_checked_menu_items[menu][item] = self.menu_options[menu][item].isChecked()
[docs] def make_status_bar(self):
self.status_bar = self.statusBar()
self.pbar = ProgressBar(self, minimum=0, maximum=100, textVisible=False,
objectName="GreenProgressBar")
self.pbar.setValue(0)
self.status_bar.addPermanentWidget(self.pbar)
[docs] def construct_UI(self):
if hasattr(self, 'range_worker'):
self.range_worker.stop() # stop any calculations on the old UI
# if hasattr(self, 'wait_until_safe_to_change_image'):
# self.wait_until_safe_to_change_image()
# get currently selected tranformations to use
self.checked_transformations = {}
for trans, item in self.transformations.items():
if self.menu_options['transforms'][trans].isChecked():
self.checked_transformations[trans] = item
# get currently selected metrics to use
self.checked_metrics = []
for metric in self.data_stores[0].metrics:
if self.menu_options['metrics'][metric].isChecked():
self.checked_metrics.append(metric)
# get currently selected metric_images to use
self.checked_metric_images = []
for metric_image in self.data_stores[0].metric_images:
if self.menu_options['metric_images'][metric_image].isChecked():
self.checked_metric_images.append(metric_image)
''' update these '''
# get any current tabs showing so we can keep them showing on a remake
tabs_index = {}
if hasattr(self, 'tabs'):
tabs_index['slider'] = self.tabs['slider'].currentIndex()
tabs_index['graph'] = self.tabs['graph'].currentIndex()
else:
tabs_index['slider'] = 0
tabs_index['graph'] = 1
# reset any range/correlation calc cache
self.metric_range_graph_num = 0
self.metric_correlation_graph_num = 0
self.reset_correlation_data()
# init the UI widgets and layouts
self.init_style() # layout.py
self.init_widgets() # widgets.py
self.change_data(0, _redo_plots=True) # images.py
if self.dataset:
self.set_preview_images(self.preview_num)
self.main = self.init_layout() # layout.py
self.tabs['slider'].setCurrentIndex(tabs_index['slider'])
self.tabs['graph'].setCurrentIndex(tabs_index['graph'])
# self.experiments_tab.setCurrentIndex(experi_tabs_index)
self.reset_sliders() # widgets.py
# self.setMinimumSize(self.main_widget.sizeHint())
self.resize(self.main_widget.sizeHint())
[docs] def reset_correlation_data(self):
self.correlation_data = {}
for i, _ in enumerate(self.data_stores):
self.correlation_data[i] = {}
def _single_image_or_dataset(self):
'''set whether dataset or single image used for data_store'''
for data_store in self.data_stores:
try:
data_store[1]
dataset_found = True
self.data_num = 0
data_store[self.data_num] # return to initial image
except:
return False
# calc what is the last data point in all the datasets
max_length = 0
for data_store in self.data_stores:
try:
length = len(data_store)
if length > max_length:
max_length = length
except:
obj_name = data_store.__class__.__name__
raise AttributeError(f'{obj_name} needs to have __len__ attribute to be a dataset')
self.max_data_ind = max_length - 1
return dataset_found
[docs] def change_save_folder(self):
''' change the save folder we are using '''
# get the file opener for the user
if os.path.exists(self.default_save_dir):
start_dir = self.default_save_dir
else:
start_dir = os.path.expanduser("~")
try:
dir = QFileDialog.getExistingDirectory(self,
'Choose Save folder',
start_dir)
except:
return
if dir == '':
return
self.default_save_dir = dir
self.status_bar.showMessage(f'Changed save dir to: {self.default_save_dir}', 8000)
self.widget_controls['label']['exp_change_save'].setText(f'Save Folder: {self.default_save_dir}')
self.widget_controls['label']['export_change_save'].setText(f'Save Folder: {self.default_save_dir}')