from PyQt5 import QtWidgets, QtGui, QtCore, QtDesigner
from .acquisitions import Acquisition, AcquisitionList
#from ..mesoSPIM_State import mesoSPIM_StateSingleton
import copy
import pickle
[docs]
class AcquisitionModel(QtCore.QAbstractTableModel):
'''
Model class containing a AcquisitionList
The headers are derived from the keys of the first acquisition
dictionary.
TODO: Typecheck in __init__ for AcquisitionList as table
'''
def __init__(self, table=None, parent=None):
super().__init__(parent)
if parent is not None:
self.parent = parent
else:
raise ValueError('Parent is None')
if table is None:
self._table = AcquisitionList()
else:
self._table = table
''' Get the headers as the capitalized keys from the first acquisition '''
self._headers = self._table.get_capitalized_keylist()
self.state = self.parent.state # the mesoSPIM_StateSingleton() instance
self.dataChanged.connect(self.updatePlanes)
[docs]
def rowCount(self, parent = QtCore.QModelIndex()):
''' Tells the view how many items this model contains '''
return len(self._table)
[docs]
def columnCount(self, parent = QtCore.QModelIndex()):
return len(self._table[0])
[docs]
def flags(self, index):
''' Return Editability for arbitrary elements
Drag and drop flags are also set here
'''
# What does | actually mean?
'''
Attention: Here, it is hardcoded that column 5 (# Planes is not editable) If acquistions.py changes, this will
require a change here as well
'''
if index.column() == self._headers.index('Planes'):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
else:
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
[docs]
def data(self, index, role = QtCore.Qt.DisplayRole):
''' Data allows to fetch one item'''
row = index.row()
column = index.column()
if role == QtCore.Qt.EditRole:
''' What is shown when the editing (double click)
is selected: A line edit is transiently created
'''
return self._table[row](column)
if role == QtCore.Qt.DisplayRole:
''' What is displayed '''
return self._table[row](column)
if role == QtCore.Qt.ToolTipRole:
''' Tooltip: Text that is display when mouse hovers '''
return "Table entry: " + str(self._table[row](column))
[docs]
def setData(self, index, value, role=QtCore.Qt.EditRole):
''' Method used to write data
Here, a single table entry is set.
Has to return True when the change was successful
'''
if role == QtCore.Qt.EditRole:
_ = self.columnCount()
row = index.row()
column = index.column()
try:
'''
self._table[row] is the Acquisition object
out of which a column entry needs to be accessed
- this can only happen via the key
- if d is the indexed dict, then
- d[d.keys()[column]] = new_value allow that
'''
self._table[row][self._table[row].keys()[column]] = value
self.dataChanged.emit(index, index)
#print('Data changed')
return True
except:
#print('Data NOT changed')
return False
[docs]
def setDataFromState(self, row, state_parameter):
column = self._table.get_keylist().index(state_parameter)
if state_parameter in ('x_pos','y_pos','z_pos','f_pos'):
new_value = round(self.state['position'][state_parameter],2)
elif state_parameter == 'rot':
new_value = round(self.state['position']['theta_pos'],1)
else:
new_value = self.state[state_parameter]
index = self.createIndex(row, column)
self.setData(index, new_value)
[docs]
def updatePlanes(self, index, index2):
''' Checks if z_planes need to be updated and updates them'''
column = index.column()
keylist = self._table.get_keylist()
z_start_column = keylist.index('z_start')
z_end_column = keylist.index('z_end')
z_step_column = keylist.index('z_step')
if column in (z_start_column ,z_end_column, z_step_column):
row = index.row()
planes = self._table[row].get_image_count()
planes_column = keylist.index('planes')
index = self.createIndex(row, planes_column)
self.setData(index, planes)
[docs]
def insertRows(self, position, rows, parent=QtCore.QModelIndex()):
''' Method to add entries to the model
Rows: how many rows are inserted at once.
The views expect a zero-based index
self.beginInsertRows(index, first, last) to send signals
'''
self.beginInsertRows(parent, position, position + rows - 1)
for i in range(rows):
# defaultValues = ['Default' for i in range(self.columnCount(parent))]
# defaultValues = ['Default',1,'One',50,56]
self._table.insert(position, Acquisition())
''' Sending the required signal '''
self.endInsertRows()
return True
[docs]
def removeRows(self, position, rows, parent = QtCore.QModelIndex()):
self.beginRemoveRows(parent, position, position + rows - 1)
for i in range(rows):
del self._table[position]
self.endRemoveRows()
return True
[docs]
def copyRow(self, row):
''' Copies a row '''
old_row = copy.deepcopy(self._table[row])
self.insertRow(row)
self._table[row] = old_row
self.send_data_changed()
# def supportedDropActions(self):
# pass
# return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction
[docs]
def mimeData(self, indices):
'''
Here, the model needs to provide a serialization of the entries
in a QMimeData object
'''
#print('MimeData called')
mimeData = super().mimeData(indices)
# print(indices[0].row())
#
# mimeData = QtCore.QMimeData()
mimeData.setText(str(self._table[indices[0].row()](0)))
return mimeData
[docs]
def dropMimeData(self, data, action, row, col, parent):
'''
Here, the model needs to deserialize the entries
in a QMimeData object
Always move the entire row, and don't allow column "shifting"
'''
#print('MimeData dropped')
if action == QtCore.Qt.MoveAction:
pass
#print('Row: ', row)
#print('Col: ', col)
#print('Data:', data.text())
#print('HTML:', data.html())
# print()
# self.insertRow(row)
# print('Data methods: ', dir(data))
# return super().dropMimeData(data, action, row, col, parent)
if action == QtCore.Qt.CopyAction:
#print('Copy action detected.')
pass
return True
[docs]
def moveRows(self, source_parent, source_row, count, destination_parent, destination_row):
self.rowsAboutToBeMoved.emit(source_parent, source_row, source_row+count-1, destination_parent, destination_row)
#print('Moving rows: ', source_row, ' #Rows ', count, ' to destination ', destination_row)
extracted_list = []
try:
''' Remove the sublist from the list '''
for i in range(count):
extracted_list.append(self._table.pop(source_row))
''' Go through the sublist and add its items '''
for i in range(count):
''' As the _table was shortened during modification, the destination_row has to
be adapted for insertion as well. '''
if destination_row > source_row:
self._table.insert(destination_row-count+1,extracted_list.pop(len(extracted_list)-1))
else:
self._table.insert(destination_row,extracted_list.pop(len(extracted_list)-1))
self.send_data_changed()
return True
except:
#print('moveRows failed')
return False
self.rowsMoved.emit(source_parent, source_row, source_row+count-1, destination_parent, destination_row)
[docs]
def send_data_changed(self):
''' Helper method that allows to send a dataChanged Signal for the whole table'''
top_left_index = self.createIndex(0,0)
bottom_right_index = self.createIndex(self.rowCount(),self.columnCount())
self.dataChanged.emit(top_left_index,bottom_right_index)
[docs]
def getName(self, row):
''' Here, I assume that the filename is the name of the process'''
return self._table[row]['filename']
[docs]
def getFilenameColumn(self):
return self._headers.index('Filename')
[docs]
def getFilename(self, row):
return self._table[row]['filename']
[docs]
def getStartFocusColumn(self):
return self._headers.index('F_start')
[docs]
def getEndFocusColumn(self):
return self._headers.index('F_end')
[docs]
def getColumnByName(self, name):
return self._headers.index(name)
[docs]
def getTime(self, row):
return int(self._table[row].get_acquisition_time())
[docs]
def getFilter(self, row):
return self._table[row]['filter']
[docs]
def getLaser(self, row):
return self._table[row]['laser']
[docs]
def getLaserList(self):
return self._table.get_unique_attr_list('laser')
[docs]
def getShutterconfig(self, row):
return self._table[row]['shutterconfig']
[docs]
def getNShutterConfigs(self):
return self._table.get_n_shutter_configs()
[docs]
def getNAngles(self):
return self._table.get_n_angles()
[docs]
def getZoom(self, row):
return self._table[row]['zoom']
[docs]
def getImageCount(self, row):
return self._table[row].get_image_count()
[docs]
def getXPosition(self, row):
return self._table[row]['x_pos']
[docs]
def getYPosition(self, row):
return self._table[row]['y_pos']
[docs]
def getZStartPosition(self, row):
return self._table[row]['z_start']
[docs]
def getZEndPosition(self, row):
return self._table[row]['z_end']
[docs]
def getRotationPosition(self, row):
return self._table[row]['rot']
[docs]
def getTotalImageCount(self):
''' gets the total number of planes from the model '''
return self._table.get_image_count()
[docs]
def getTileIndex(self, row):
return self._table.get_tile_index(self._table[row])
[docs]
def get_acquisition_list(self, row=None):
if row is None:
return self._table
else:
return AcquisitionList([self._table[row]])
[docs]
def saveModel(self, filename):
''' Allows to serialize a model via pickle '''
with open(filename, "wb" ) as file:
pickle.dump(self._table, file)
[docs]
def setTable(self, table):
self.modelAboutToBeReset.emit()
self._table = table
self.modelReset.emit()
[docs]
def setShutterconfig(self, row, shutterconfig):
self._table[row]['shutterconfig'] = shutterconfig
[docs]
def loadModel(self, filename):
self.modelAboutToBeReset.emit()
with open(filename, "rb" ) as file:
self._table = pickle.load(file)
self.modelReset.emit()
[docs]
def deleteTable(self):
self.modelAboutToBeReset.emit()
self._table = AcquisitionList()
self.modelReset.emit()