Commit 3a33998f authored by Martin Drechsler's avatar Martin Drechsler

Tested with zyla connected, it works. Also, saving and loading added to controlGui.

parent 75027243
......@@ -17,9 +17,6 @@ from zylaCameraWorker import CameraWorker
from subclasses.cameraParameterTree import CameraParameterTree
from subclasses.controllayouts import LaserControlLayout, LaserScanLayout
from resources.scanFunctions import create_measurement_array
#from drivers.andorzyla import AndorZyla
from drivers.dummyAndor import AndorZyla
from measurement import MeasurementFrame
import sys
import numpy as np
......@@ -71,6 +68,7 @@ class CameraGuiMainWindow(QMainWindow):
self.measurementFrame = MeasurementFrame()
self.dMeasurement.addWidget(self.measurementFrame)
# button spinbox and label
self.cameraButton = QPushButton("Start Acquisition")
self.isCameraAcquiring = False
......@@ -136,7 +134,8 @@ class CameraGuiMainWindow(QMainWindow):
self.cameraButton.clicked.connect(self.camera_button_pressed)
self.console = console.ConsoleWidget(
namespace = {'np': np, 'cam': backend.cam, 'roi': self.rois[0]}
namespace = {'np': np, 'cam': backend.cam, 'roi': self.rois[0], 'storage': backend.storage
}
)
self.dConsole.addWidget(self.console)
......@@ -146,9 +145,10 @@ class CameraGuiMainWindow(QMainWindow):
self.measurementFrame.startValue.valueChanged.connect(self.measurementFrame.update_tree)
self.measurementFrame.endValue.valueChanged.connect(self.measurementFrame.update_tree)
self.measurementFrame.stepsNum.valueChanged.connect(self.measurementFrame.update_tree)
self.measurementFrame.saveMeasureButton.clicked.connect(
lambda: self.measurementFrame.saveMeasureButton.setEnabled(False)
)
def camera_button_pressed(self):
if self.isCameraAcquiring:
......@@ -208,7 +208,9 @@ class CameraGuiMainWindow(QMainWindow):
new_sx, new_sy = new_size/old_size * sx, new_size/old_size * sy
roi.setPos([new_x, new_y])
roi.setSize([new_sx, new_sy])
self.current_image_size = new_size
@QtCore.pyqtSlot(np.ndarray, int)
def updateImage(self, image, acq_index):
if self.frame_index is not 0 and not self.frame_index == acq_index:
......@@ -220,7 +222,7 @@ class CameraGuiMainWindow(QMainWindow):
self.img.setImage(image, autoDownsample=True)
self.updatePlots()
self.frame_index = self.frame_index + 1
self.current_image_size = image.shape[0]
def frame_counter(self):
self.counter = self.counter + 1
......@@ -257,6 +259,7 @@ class CameraGuiMainWindow(QMainWindow):
def measurement_ending(self):
self.cameraButton.setEnabled(True)
self.measurementFrame.startMeasureButton.setEnabled(True)
self.measurementFrame.saveMeasureButton.setEnabled(True)
self.isMeasuring = False
for l in LaserControlLayout._registry + LaserScanLayout._registry :
l.unfreeze()
......
This diff is collapsed.
......@@ -792,7 +792,7 @@ class AndorBase(SDK3Camera):
self.acq_index_i = self.acq_index_i + 1
print('stopping acq loop thread')
def _convert_buffer_12(self, buf):
import itertools
xs, ys = self.GetPicWidth(), self.GetPicHeight()
......@@ -843,6 +843,10 @@ class AndorZyla(AndorBase):
AndorBase.__init__(self,camNum)
def trigger(self):
self.SoftwareTrigger()
class AndorSim(AndorBase):
def __init__(self, camNum):
#define properties
......
from PyQt5 import QtGui, QtCore
#from drivers.andorzyla import AndorZyla
#from drivers.andorzyla import AndorZyla
from drivers.dummyAndor import AndorZyla
from cameraGui import CameraGuiMainWindow
......
......@@ -13,127 +13,126 @@ from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import QAction, QFileDialog, QInputDialog, QWidget
from measurement_ui import Ui_Frame
from MCDAQcontrolsignals import anal_control_signal
from subclasses.controllayouts import LaserControlLayout, LaserScanLayout
from resources.scanFunctions import create_measurement_array, yield_array
import time
class MeasurementFrame(QtWidgets.QFrame, Ui_Frame):
def __init__(self):
super().__init__()
self.setupUi(self)
for ao in anal_control_signal._registry:
self.selectScanSignal.addItem(ao.name)
try:
self.configure_spinBoxes(anal_control_signal._registry[0].name)
except IndexError:
print('No analog control signals defined. ')
self.treeModel = QtGui.QStandardItemModel()
self.treeView.setAlternatingRowColors(True)
self.treeView.setSortingEnabled(True)
self.treeView.setHeaderHidden(False)
self.treeView.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
self.treeView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.treeModel.setHorizontalHeaderLabels(['Parameter', 'Value'])
self.treeView.setModel(self.treeModel)
self.treeModel.insertRow(0)
self.treeModel.setData(self.treeModel.index(0, 0), r'dV')
self.treeModel.setData(self.treeModel.index(0, 1), '?')
def select_folder(self):
directory = QFileDialog.getExistingDirectory(self, 'Choose directory')
self.folderBrowser.setText(directory)
@QtCore.pyqtSlot(str)
def configure_spinBoxes(self, ao_name):
ao = self.get_selected_ao()
self.startValue.setMinimum(ao.min)
self.startValue.setMaximum(ao.max)
self.endValue.setMinimum(ao.min)
self.endValue.setMaximum(ao.max)
def get_selected_ao(self):
try:
return [ao for ao in anal_control_signal._registry if ao.name == self.selectScanSignal.currentText()][0]
except IndexError:
print('No analog control signals defined. ')
def update_tree(self):
try:
self.treeModel.setData(self.treeModel.index(0, 1), (self.endValue.value()-self.startValue.value())/self.stepsNum.value() )
except:
self.treeModel.setData(self.treeModel.index(0, 1), '?')
def __init__(self):
super().__init__()
self.setupUi(self)
for ao in anal_control_signal._registry:
self.selectScanSignal.addItem(ao.name)
try:
self.configure_spinBoxes(anal_control_signal._registry[0].name)
except IndexError:
print('No analog control signals defined. ')
self.treeModel = QtGui.QStandardItemModel()
self.treeView.setAlternatingRowColors(True)
self.treeView.setSortingEnabled(True)
self.treeView.setHeaderHidden(False)
self.treeView.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
self.treeView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.treeModel.setHorizontalHeaderLabels(['Parameter', 'Value'])
self.treeView.setModel(self.treeModel)
self.treeModel.insertRow(0)
self.treeModel.setData(self.treeModel.index(0, 0), r'dV')
self.treeModel.setData(self.treeModel.index(0, 1), '?')
self.saveMeasureButton.setEnabled(False)
def select_folder(self):
directory = QFileDialog.getExistingDirectory(self, 'Choose directory')
self.folderBrowser.setText(directory)
@QtCore.pyqtSlot(str)
def configure_spinBoxes(self, ao_name):
ao = self.get_selected_ao()
self.startValue.setMinimum(ao.min)
self.startValue.setMaximum(ao.max)
self.startValue.setSingleStep(0.1)
self.endValue.setMinimum(ao.min)
self.endValue.setMaximum(ao.max)
self.endValue.setSingleStep(0.1)
self.stepsNum.setMaximum(1000000)
def get_selected_ao(self):
try:
return [ao for ao in anal_control_signal._registry if ao.name == self.selectScanSignal.currentText()][0]
except IndexError:
print('No analog control signals defined. ')
def update_tree(self):
try:
self.treeModel.setData(self.treeModel.index(0, 1), (self.endValue.value()-self.startValue.value())/self.stepsNum.value() )
except:
self.treeModel.setData(self.treeModel.index(0, 1), '?')
"""
class MeasurementWorker(QtCore.QObject):
Backend for the measurement frame.
Simple scan measurement: for scanning a spinbox.
External scan measurement: scans an external object. For example, the rigol function generator.
Backend for the measurement frame.
Simple scan measurement: for scanning a spinbox.
External scan measurement: scans an external object. For example, the rigol function generator.
def __init__(self):
super().__init__()
def __init__(self):
super().__init__()
def make_connections(self, frontend, camera):
frontend.startMeasureButton.clicked.connect(self.simple_scan_measurement)
def make_connections(self, frontend, camera):
frontend.startMeasureButton.clicked.connect(self.simple_scan_measurement)
def simple_scan_measurement(self):
def simple_scan_measurement(self):
for l in LaserControlLayout._registry + LaserScanLayout._registry :
l.freeze()
scanSignal = self.get_scan_signal()
scan_array = create_measurement_array() # aca necesito parametros de los spinboxes de la measurementGUi
prepare_camara_to_triggered_acq() #includes stopping acquisition if it is in acq live
create_temporary_data_files
measurement loop:
set_scan_signal
wait_predefined_time
take_and_recibe_image
process_image_to_get_data
save_data
update_camera_gui_with_image_and_data
update_measurement_gui
for l in LaserControlLayout._registry + LaserScanLayout._registry :
l.unfreeze()
for l in LaserControlLayout._registry + LaserScanLayout._registry :
l.freeze()
scanSignal = self.get_scan_signal()
scan_array = create_measurement_array() # aca necesito parametros de los spinboxes de la measurementGUi
prepare_camara_to_triggered_acq() #includes stopping acquisition if it is in acq live
create_temporary_data_files
measurement loop:
set_scan_signal
wait_predefined_time
take_and_recibe_image
process_image_to_get_data
save_data
update_camera_gui_with_image_and_data
update_measurement_gui
for l in LaserControlLayout._registry + LaserScanLayout._registry :
l.unfreeze()
"""
if __name__ == '__main__':
import sys
from PyQt5 import QtWidgets
from measurement_ui import Ui_Frame
piezoA397 = anal_control_signal('piezoA397', channel=0, ao_type='cavity_piezo', out_minimum = 0, out_maximum = 4)
piezoB397 = anal_control_signal('piezoB397', channel=1, ao_type='cavity_piezo')
piezoA866 = anal_control_signal('piezoA866', channel=2, ao_type='cavity_piezo', out_minimum = 0, out_maximum = 4)
piezoB866 = anal_control_signal('piezoB866', channel=3, ao_type='cavity_piezo')
piezo423 = anal_control_signal('piezo423', channel=4, ao_type='laser_piezo')
trapDCA = anal_control_signal('dcA', channel = 5, ao_type='electrode')
trapDCB = anal_control_signal('dcB', channel = 6, ao_type='electrode')
trapCOMPC = anal_control_signal('compC', channel = 7, ao_type='electrode')
trapCOMPD = anal_control_signal('compD', channel = 8, ao_type='electrode')
trapOven = anal_control_signal('compOven', channel = 9, ao_type='electrode')
trapExYb = anal_control_signal('compExYb', channel = 10, ao_type='electrode')
app = QtWidgets.QApplication(sys.argv)
gui = MeasurementGui()
measurementWorker = MeasurementWorker()
measurementWorker.make_connections(gui, 0)
gui.make_connections(measurementWorker)
gui.show()
sys.exit(app.exec_())
import sys
from PyQt5 import QtWidgets
from measurement_ui import Ui_Frame
piezoA397 = anal_control_signal('piezoA397', channel=0, ao_type='cavity_piezo', out_minimum = 0, out_maximum = 4)
piezoB397 = anal_control_signal('piezoB397', channel=1, ao_type='cavity_piezo')
piezoA866 = anal_control_signal('piezoA866', channel=2, ao_type='cavity_piezo', out_minimum = 0, out_maximum = 4)
piezoB866 = anal_control_signal('piezoB866', channel=3, ao_type='cavity_piezo')
piezo423 = anal_control_signal('piezo423', channel=4, ao_type='laser_piezo')
trapDCA = anal_control_signal('dcA', channel = 5, ao_type='electrode')
trapDCB = anal_control_signal('dcB', channel = 6, ao_type='electrode')
trapCOMPC = anal_control_signal('compC', channel = 7, ao_type='electrode')
trapCOMPD = anal_control_signal('compD', channel = 8, ao_type='electrode')
trapOven = anal_control_signal('compOven', channel = 9, ao_type='electrode')
trapExYb = anal_control_signal('compExYb', channel = 10, ao_type='electrode')
app = QtWidgets.QApplication(sys.argv)
gui = MeasurementFrame()
gui.show()
sys.exit(app.exec_())
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon May 6 12:35:37 2019
@author: martindrech
"""
import numpy as np
import pylab as plt
data = np.loadtxt('./20190506/s02.dat', skiprows=1)
t = data[:, 0]
v = data[:, 1]
signal = data[:, 2]
#%%
plt.figure('some data')
plt.clf()
plt.plot(np.diff(t), 'o')
print(np.mean(np.diff(t)))
\ No newline at end of file
......@@ -7,9 +7,10 @@ Created on Thu Jul 5 12:17:30 2018
from PyQt5.QtCore import QSettings
from PyQt5 import QtGui
from PyQt5.QtWidgets import QAction, QFileDialog, QInputDialog, QWidget
from threading import Thread
#from webcamROI import roiWindow
from subclasses.controllayouts import LaserControlLayout
#%%
class myToolbarMenu(QWidget):
......@@ -17,38 +18,47 @@ class myToolbarMenu(QWidget):
This will create a toolbar menu
"""
def __init__(self, win, analog_control_signals):
def __init__(self, win):
super().__init__()
self.MainGuiWindow = win
self.aos = analog_control_signals
self.sbsDicc = dict()
self.first_load(self.aos)
self.incorporate_toolbar(self.MainGuiWindow, self.aos)
self.first_load()
self.incorporate_toolbar(self.MainGuiWindow)
self.createAosDict()
self.fillSpinBoxesDict()
def createAosDict(self):
analogs = self.aos
self.aosDicc = {}
for i in range(len(analogs)):
self.aosDicc[analogs[i].name] = analogs[i]
def fillSpinBoxesDict(self):
def incorporate_toolbar(self, win, analog_control_signals):
for l in LaserControlLayout._registry:
for sb_name in l.spinboxes:
self.sbsDicc[sb_name] = l.spinboxes[sb_name]
def createElectrodesDict(self):
d = dict()
for l in LaserControlLayout._registry:
if l.name == 'electrodes':
for sb_name in l.spinboxes:
d[sb_name] = l.spinboxes[sb_name]
return d
def incorporate_toolbar(self, win):
menubar = win.menuBar()
menuBar = win.menuBar
#statusBar = QtGui.QStatusBar(self)
# saveAct = QAction('Save', win)
saveAct = QAction('Save', win)
loadAct = QAction('Load', win)
saveAsAct = QAction('Save as' , win)
openFromAct = QAction('open from', win)
loadElectrodesAct = QAction('Load electrodes', win)
win.statusBar()
menubar = win.menuBar()
menubar = menuBar
fileMenu = menubar.addMenu('&File')
#fileMenu.addAction(saveAct)
fileMenu.addAction(saveAct)
fileMenu.addAction(loadAct)
fileMenu.addAction(saveAsAct)
fileMenu.addAction(openFromAct)
......@@ -61,94 +71,95 @@ class myToolbarMenu(QWidget):
viewWebcams.setChecked(False)
viewMenu.addAction(viewWebcams)
roiMenu = menubar.addMenu('Roi')
plotRoi = QAction('Open roi plot', win)
roiMenu.addAction(plotRoi)
#☺saveAct.triggered.connect(lambda: self.save(analog_control_signals))
loadAct.triggered.connect(lambda: self.load(analog_control_signals))
loadElectrodesAct.triggered.connect(lambda: self.open_from([ao for ao in analog_control_signals if ao.ao_type == 'electrode'], win))
saveAsAct.triggered.connect(lambda: self.save_as(analog_control_signals, win))
openFromAct.triggered.connect(lambda: self.open_from(analog_control_signals, win))
saveAct.triggered.connect(lambda: self.save('config.ini'))
loadAct.triggered.connect(lambda: self.load('config.ini'))
loadElectrodesAct.triggered.connect(lambda: self.load_electrodes(win))
saveAsAct.triggered.connect(lambda: self.save_as(win))
openFromAct.triggered.connect(lambda: self.open_from(win))
viewWebcams.toggled.connect(lambda: self.putWebcams(viewWebcams.isChecked()) )
plotRoi.triggered.connect(self.openRoi)
def save(self, aos):
settings = QSettings('C:\\Users\\Usuario\\Documents\\control_app\\config.ini', QSettings.IniFormat)
def save(self, location):
settings = QSettings(str(location), QSettings.IniFormat)
settings.beginGroup('Ventana')
for ao in aos:
spin_boxes = ao.spin_boxes
for text, spinb in spin_boxes:
settings.setValue(str(ao.name) + '_' + text, spinb.value())
#print(spinb.value() )
for text, spinb in self.sbsDicc.items():
settings.setValue(text, spinb.value())
settings.endGroup()
#print('saving')
def load(self, aos):
settings = QSettings('C:\\Users\\Usuario\\Documents\\control_app\\config.ini', QSettings.IniFormat)
def load(self, location):
settings = QSettings(location, QSettings.IniFormat)
settings.beginGroup('Ventana')
for ao in aos:
spin_boxes = ao.spin_boxes
for text, spinb in spin_boxes:
spin = settings.value(str(ao.name) + '_' + text)
try:
spinb.setValue( float(spin) )
except TypeError:
spinb.setValue( float(ao.initial_value) )
for text, spinb in self.sbsDicc.items():
spinbox_saved_value = settings.value(text)
try:
spinb.setValue( float(spinbox_saved_value) )
except TypeError:
spinb.setValue( float(spinb.value()) )
settings.endGroup()
def first_load(self, aos):
def first_load(self):
settings = QSettings('C:\\Users\\Usuario\\Documents\\control_app\\config.ini', QSettings.IniFormat)
settings = QSettings('.\config.ini', QSettings.IniFormat)
if not settings:
print('Warning: no config.ini file in control_app folder. Pleace, be kindful and create one')
return 0
settings.beginGroup('Ventana')
for ao in aos:
spin_boxes = ao.spin_boxes
for text, spinb in spin_boxes:
spin = settings.value(str(ao.name) + '_' + text)
spinb.setValue( float(spin) )
for text, spinb in self.sbsDicc.items():
spinbox_saved_value = settings.value(text)
try:
spinb.setValue( float(spinbox_saved_value) )
except TypeError:
spinb.setValue( float(spinb.initial_value) )
settings.endGroup()
def save_as(self, aos, window):
def save_as(self, window):
name = QFileDialog.getSaveFileName(window, 'Save File')
if not name[0]:
print('Saving was canceled')
return 0
file = open(name[0], 'w+')
filename = name[0]+'.ini'
file = open(filename, 'w+')
file.close()
settings = QSettings(name[0], QSettings.IniFormat)
settings.beginGroup('Ventana')
for ao in aos:
spin_boxes = ao.spin_boxes
for text, spinb in spin_boxes:
settings.setValue(str(ao.name) + '_' + text, spinb.value())
#print(spinb.value() )
settings.endGroup()
print('saving to %s' % name[0])
self.save( filename )
print('saving to %s' % filename)
def open_from(self, aos, window):
name = QFileDialog.getOpenFileName(window, 'Save File')
def open_from(self, window):
name = QFileDialog.getOpenFileName(window, 'Open File')
if not name[0]:
print('Opening was canceled')
return 0
self.load(name[0])
def load_electrodes(self, window):
name = QFileDialog.getOpenFileName(window, 'Open File')
if not name[0]:
print('Opening was canceled')
return 0
settings = QSettings(name[0], QSettings.IniFormat)
settings.beginGroup('Ventana')
for ao in aos:
spin_boxes = ao.spin_boxes
for text, spinb in spin_boxes:
spin = settings.value(str(ao.name) + '_' + text)
spinb.setValue( float(spin) )
for text, spinb in self.createElectrodesDict().items():
spinbox_saved_value = settings.value(text)
try:
spinb.setValue( float(spinbox_saved_value) )
except TypeError:
spinb.setValue( float(spinb.value()) )
settings.endGroup()
def startWebcamsThread(self):
exec(open('C:\\Users\\Usuario\\Documents\\control_app\\webcams.py').read())
......@@ -160,25 +171,5 @@ class myToolbarMenu(QWidget):
else:
pass
def openRoi(self):
print('opening roi window')
items = self.aosDicc.keys()
item, okPressed = QInputDialog.getItem(self, "Get item","Color:", items, 0, False)
if okPressed and item:
ao = self.aosDicc[item]
self.roiW = roiWindow(ao)
self.roiW.w.show()
else:
print('Roi canceled')
def startRoiThread(self, ao):
self.roiW = roiWindow(ao)
self.roiW.w.show()
def getChoiceForRoi(self):
items = self.aosDicc.keys()
item, okPressed = QInputDialog.getItem(self, "Get item","Color:", items, 0, False)
if okPressed and item:
self.startRoiThread(item)
return self.aosDicc[item]
\ No newline at end of file
......@@ -23,7 +23,7 @@ class Storage(object):
"""
def __init__(self):
self.directory = ''
self.directory = '.'
def set_directory(self, directory):
self.directory = directory
......@@ -41,13 +41,13 @@ class Storage(object):
with open(filename, 'wb') as f:
f.write(header.encode('utf-8'))
self.delete_temporary()
def append_data_to_current_file(self, *args):
def append_data_to_current_file(self, row):
with open(self.get_current_filename(), 'ab') as f:
#data = np.column_stack(args)
data = [arg for arg in args]
np.savetxt(f, data)
np.savetxt(f, [row])
f.flush()
def get_date_string(self):
......@@ -72,20 +72,30 @@ class Storage(object):
if len(savings_number_list) == 0:
return 1
if len(savings_number_list) == 3 and not permanent_saving: #removes the last of the last 10 files
return max(savings_number_list)+1
def delete_temporary(self):
"""
Deletes old temporary files.
"""
savings_number_list = []
suffix = '.temp'
saving_directory = "/".join([self.directory, self.get_date_string(), 'dump'])
for s in [dat_file for dat_file in os.listdir(saving_directory) if dat_file.endswith(suffix)]:
savings_number_list.append(int(s[1:].split('.')[0]))
if len(savings_number_list) > 3: #removes the last of the last 10 files
file_to_remove = "/".join([saving_directory, 's' + str(min(savings_number_list)).zfill(2)])+'.temp'
os.remove(file_to_remove)
return max(savings_number_list)+1
def get_current_filename(self):
s_num = self.get_saving_number(True)-1
s_num = self.get_saving_number()-1
saving_directory = "/".join([self.directory, self.get_date_string()])
filename = "/".join([saving_directory, 'dump', 's' + str(s_num).zfill(2)]) + '.temp'
return filename
def permanent_save_current_data(self):
oldfile = self.get_current_filename()
saving_directory = "/".join([self.directory, self.get_date_string()])
......@@ -95,13 +105,13 @@ class Storage(object):
#%%
import time
if __name__ == '__main__':
storage = Storage()
storage.set_directory('./')
storage.create_data_file('col1', 'col2')
storage.create_data_file('col1', 'col2')
storage.create_data_file('col1', 'col2')
storage.create_data_file('col1', 'col2')
storage.create_data_file('col1', 'col2')
storage.create_data_file('col1', 'col2', 'col3')
storage.append_data_to_current_file([1.0,2.5,time.time()])
storage.permanent_save_current_data()
......@@ -41,6 +41,8 @@ class CameraWorker(QtCore.QObject):
frontend.cameraButton.clicked.connect(lambda: self.run(live_acq_params))
frontend.signalWithMeasurementParameters.connect(self.simple_scan_measurement_start)
frontend.roiDataReadySignal.connect(self.save_data_from_scan_step)
frontend.measurementFrame.saveMeasureButton.clicked.connect(
self.storage.permanent_save_current_data)
# internal connections
self.cam.helper.imageAquiredSignal.connect(self.new_image_acquired)
......@@ -171,9 +173,8 @@ class CameraWorker(QtCore.QObject):
@QtCore.pyqtSlot(float)
def save_data_from_scan_step(self, roiData):
self.storage.append_data_to_current_file([
time.time(), self.ao_to_scan.AO.current_value, roiData
])
row = [time.time(), self.ao_to_scan.AO.current_value, roiData]
self.storage.append_data_to_current_file(row)
self.simple_scan_measurement_step()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment