class DAQ_2DViewer_FLIM(DAQ_1DViewer_TH260):
    """
        ==================== ==================
        **Atrributes**        **Type**
        *params*              dictionnary list
        *hardware_averaging*  boolean
        *x_axis*              1D numpy array      
        *ind_data*            int
        ==================== ==================

        See Also
        --------

        utility_classes.DAQ_Viewer_base
    """
    params = DAQ_1DViewer_TH260.params + stage_params
    stop_scanner = pyqtSignal()
    start_tttr_scan = pyqtSignal()

    def __init__(self, parent=None, params_state=None):

        super(DAQ_2DViewer_FLIM, self).__init__(
            parent, params_state
        )  #initialize base class with commom attributes and methods
        self.settings.child(
            'acquisition',
            'acq_type').setOpts(limits=['Counting', 'Histo', 'T3', 'FLIM'])
        self.settings.child('acquisition', 'acq_type').setValue('Histo')

        self.stage = None
        self.scan_parameters = None
        self.x_axis = None
        self.y_axis = None
        self.Nx = 1
        self.Ny = 1
        self.signal_axis = None

    def commit_settings(self, param):

        if param.name() not in custom_tree.iter_children(
                self.settings.child(('stage_settings'))):
            super(DAQ_2DViewer_FLIM, self).commit_settings(param)

        else:
            if param.name() == 'time_interval':
                self.stage._get_read()
                self.stage.set_time_interval(
                    Time(param.value(),
                         unit='m'))  # set time interval between pixels

            elif param.name() == 'show_navigator':
                self.emit_status(ThreadCommand('show_navigator'))
                self.emit_status(ThreadCommand('show_scanner'))
                param.setValue(False)

            elif param.name() in custom_tree.iter_children(
                    self.settings.child('stage_settings', 'move_at'), []):
                pos_x = self.settings.child('stage_settings', 'move_at',
                                            'move_at_x').value()
                pos_y = self.settings.child('stage_settings', 'move_at',
                                            'move_at_y').value()
                self.move_at_navigator(pos_x, pos_y)

    def emit_data(self):
        """
        """
        try:
            mode = self.settings.child('acquisition', 'acq_type').value()
            if mode == 'Counting' or mode == 'Histo' or mode == 'T3':
                super(DAQ_2DViewer_FLIM, self).emit_data()

            elif mode == 'FLIM':
                self.stop_scanner.emit()
                self.h5saver.h5_file.flush()
                datas = self.process_histo_from_h5_and_correct_shifts(
                    self.Nx, self.Ny, channel=0, marker=65)
                self.data_grabed_signal.emit([
                    DataFromPlugins(name='TH260',
                                    data=datas,
                                    dim='DataND',
                                    nav_axes=(0, 1),
                                    nav_x_axis=self.get_nav_xaxis(),
                                    nav_y_axis=self.get_nav_yaxis(),
                                    xaxis=self.get_xaxis(),
                                    external_h5=self.h5saver.h5_file)
                ])
                self.stop()

        except Exception as e:
            self.emit_status(
                ThreadCommand('Update_Status',
                              [getLineInfo() + str(e), 'log']))

    def emit_data_tmp(self):
        """
        """
        try:
            mode = self.settings.child('acquisition', 'acq_type').value()
            if mode == 'Counting' or mode == 'Histo' or mode == 'T3':
                super(DAQ_2DViewer_FLIM, self).emit_data_tmp()

            elif mode == 'FLIM':

                self.data_grabed_signal_temp.emit([
                    DataFromPlugins(name='TH260',
                                    data=self.datas,
                                    dim='DataND',
                                    nav_axes=(0, 1),
                                    nav_x_axis=self.get_nav_xaxis(),
                                    nav_y_axis=self.get_nav_yaxis(),
                                    xaxis=self.get_xaxis())
                ])

        except Exception as e:
            self.emit_status(
                ThreadCommand('Update_Status',
                              [getLineInfo() + str(e), 'log']))

    def process_histo_from_h5_and_correct_shifts(self,
                                                 Nx=1,
                                                 Ny=1,
                                                 channel=0,
                                                 marker=65):
        """
        Specific method to correct for drifts between various scans performed using the quick scans feature
        Parameters
        ----------
        Nx
        Ny
        channel
        marker

        Returns
        -------

        """
        Nbins = self.settings.child('acquisition', 'timings', 'nbins').value()
        time_window = Nbins

        markers_array = self.h5saver.h5_file.get_node('/markers')
        nanotime_array = self.h5saver.h5_file.get_node('/nanotimes')
        datas = np.zeros((Nx, Ny, Nbins))
        intensity_map_ref = np.zeros((Nx, Ny), dtype=np.int64)
        ind_lines = np.where(markers_array.read() == marker)[0]
        indexes_reading = ind_lines[::Nx * Ny][1:]
        Nreadings = len(indexes_reading)

        ind_reading = 0
        ind_offset = 0

        for ind in range(Nreadings):

            if len(ind_lines) > 2:
                ind_last_line = ind_lines[-1]
                markers_tmp = markers_array[ind_reading:ind_reading +
                                            ind_last_line]
                nanotimes_tmp = nanotime_array[ind_reading:ind_reading +
                                               ind_last_line]

                # datas array is updated within this method
                datas_tmp = self.extract_TTTR_histo_every_pixels(
                    nanotimes_tmp,
                    markers_tmp,
                    marker=marker,
                    Nx=Nx,
                    Ny=Ny,
                    Ntime=Nbins,
                    ind_line_offset=ind_offset,
                    channel=channel,
                    time_window=time_window)
                intensity_map = np.squeeze(np.sum(datas_tmp, axis=2))
                if ind == 0:
                    intensity_map_ref = intensity_map
                ind_offset += len(ind_lines) - 2
                ind_reading += ind_lines[-2]

            #correct for shifts in x or y during collections and multiple scans of the same area
            shift, error, diffphase = register_translation(
                intensity_map_ref, intensity_map, 1)
            datas += np.roll(datas_tmp, [int(s) for s in shift], (0, 1))

        return datas

    def ini_detector(self, controller=None):
        """
            See Also
            --------
            DAQ_utils.ThreadCommand, hardware1D.DAQ_1DViewer_Picoscope.update_pico_settings
        """
        self.status.update(
            edict(initialized=False,
                  info="",
                  x_axis=None,
                  y_axis=None,
                  controller=None))
        try:
            self.status = super(DAQ_2DViewer_FLIM,
                                self).ini_detector(controller)

            self.ini_stage()
            self.status.x_axis = self.x_axis
            self.status.initialized = True
            self.status.controller = self.controller

            return self.status

        except Exception as e:
            self.status.info = getLineInfo() + str(e)
            self.status.initialized = False
            return self.status

    @pyqtSlot(ScanParameters)
    def update_scanner(self, scan_parameters):
        self.scan_parameters = scan_parameters
        self.x_axis = self.scan_parameters.axes_unique[0]
        self.Nx = self.x_axis.size
        self.y_axis = self.scan_parameters.axes_unique[1]
        self.Ny = self.y_axis.size

    def get_nav_xaxis(self):
        """
        """
        return self.scan_parameters.axis_2D_1

    def get_nav_yaxis(self):
        """
        """
        return self.scan_parameters.axis_2D_2

    def ini_stage(self):
        if self.settings.child('stage_settings',
                               'stage_type').value() == 'PiezoConcept':
            self.stage = PiezoConcept()
            self.controller.stage = self.stage
            self.stage.init_communication(
                self.settings.child('stage_settings', 'com_port').value())

            controller_id = self.stage.get_controller_infos()
            self.settings.child('stage_settings',
                                'controller_id').setValue(controller_id)
            self.stage.set_time_interval(
                Time(self.settings.child('stage_settings',
                                         'time_interval').value(),
                     unit='m'))  #set time interval between pixels
            #set the TTL outputs for each displacement of first axis
            self.stage.set_TTL_state(
                1,
                self.settings.child('stage_settings', 'stage_x',
                                    'stage_x_axis').value(), 'output',
                dict(type='start'))

            self.move_abs(0, 'X')
            self.move_abs(0, 'Y')

    @pyqtSlot(float, float)
    def move_at_navigator(self, posx, posy):
        self.move_abs(posx, 'X')
        self.move_abs(posy, 'Y')

    def move_abs(self, position, axis='X'):
        stage = f'stage_{axis}'.lower()
        stage_axis = f'stage_{axis}_axis'.lower()
        offset_stage = f'offset_{axis}'.lower()
        stage_dir = f'stage_{axis}_direction'.lower()

        offset = self.settings.child('stage_settings', stage,
                                     offset_stage).value()
        if self.settings.child('stage_settings', stage,
                               stage_dir).value() == 'Normal':
            posi = position + offset
        else:
            posi = -position + offset

        ax = self.settings.child('stage_settings', stage, stage_axis).value()
        pos = Position(ax, int(posi * 1000), unit='n')
        out = self.stage.move_axis('ABS', pos)

    def close(self):
        """

        """
        super(DAQ_2DViewer_FLIM, self).close()
        self.stage.close_communication()

    def set_acq_mode(self, mode='FLIM', update=False):
        """
        herited method

        Change the acquisition mode (histogram for mode=='Counting' and 'Histo' or T3 for mode == 'FLIM')
        Parameters
        ----------
        mode

        Returns
        -------

        """

        # check enabled channels
        labels = [
            k for k in self.channels_enabled.keys()
            if self.channels_enabled[k]['enabled']
        ]
        N = len(labels)

        if mode != self.actual_mode or update:
            if mode == 'Counting' or mode == 'Histo' or mode == 'T3':
                super(DAQ_2DViewer_FLIM, self).set_acq_mode(mode, update)

            elif mode == 'FLIM':

                self.emit_status(ThreadCommand('show_scanner'))
                QtWidgets.QApplication.processEvents()
                self.emit_status(ThreadCommand('show_navigator'))

                self.controller.TH260_Initialize(self.device,
                                                 mode=3)  # mode T3
                self.controller.TH260_SetMarkerEnable(self.device, 1)
                self.datas = np.zeros((10, 10, 1024))
                self.data_grabed_signal_temp.emit([
                    DataFromPlugins(name='TH260',
                                    data=self.datas,
                                    nav_axes=[0, 1],
                                    dim='DataND')
                ])

                self.data_pointers = self.datas.ctypes.data_as(
                    ctypes.POINTER(ctypes.c_uint32))
                self.actual_mode = mode

            self.actual_mode = mode

    def grab_data(self, Naverage=1, **kwargs):
        """
            Start new acquisition in two steps :
                * Initialize data: self.datas for the memory to store new data and self.data_average to store the average data
                * Start acquisition with the given exposure in ms, in "1d" or "2d" mode

            =============== =========== =============================
            **Parameters**   **Type**    **Description**
            Naverage         int         Number of images to average
            =============== =========== =============================

            See Also
            --------
            DAQ_utils.ThreadCommand
        """
        try:
            self.acq_done = False
            mode = self.settings.child('acquisition', 'acq_type').value()
            if mode == 'Counting' or mode == 'Histo' or mode == 'T3':
                super(DAQ_2DViewer_FLIM, self).grab_data(Naverage, **kwargs)

            elif mode == 'FLIM':
                self.ind_reading = 0
                self.ind_offset = 0
                self.do_process_tttr = False

                self.init_h5file()
                self.datas = np.zeros((
                    self.Nx,
                    self.Ny,
                    self.settings.child('acquisition', 'timings',
                                        'nbins').value(),
                ),
                                      dtype=np.float64)

                time_acq = int(
                    self.settings.child('acquisition', 'acq_time').value() *
                    1000)  # in ms
                self.general_timer.stop()

                self.prepare_moves()

                # prepare asynchronous tttr time event reading
                t3_reader = T3Reader_scan(self.device, self.controller,
                                          time_acq, self.stage, self.Nchannels)
                self.detector_thread = QThread()
                t3_reader.moveToThread(self.detector_thread)
                t3_reader.data_signal[dict].connect(self.populate_h5)
                self.stop_tttr.connect(t3_reader.stop_TTTR)
                self.start_tttr_scan.connect(t3_reader.start_TTTR)
                self.detector_thread.t3_reader = t3_reader
                self.detector_thread.start()
                self.detector_thread.setPriority(QThread.HighestPriority)

                #start acquisition and scanner
                self.time_t3 = time.perf_counter()
                self.time_t3_rate = time.perf_counter()

                self.start_tttr_scan.emit()

        except Exception as e:
            self.emit_status(
                ThreadCommand('Update_Status',
                              [getLineInfo() + str(e), "log"]))

    def prepare_moves(self):
        """
        prepare given actuators with positions from scan_parameters
        Returns
        -------

        """
        self.x_axis = self.scan_parameters.axis_2D_1
        self.Nx = self.x_axis.size
        self.y_axis = self.scan_parameters.axis_2D_2
        self.Ny = self.y_axis.size
        positions_real = self.transform_scan_coord(
            self.scan_parameters.positions)
        # interval_time = self.settings.child('stage_settings', 'time_interval').value()/1000
        # total_time = self.settings.child('acquisition', 'acq_time').value()
        # Ncycles = int(total_time/(interval_time*len(positions_real)))
        # total_time = Ncycles * interval_time*len(positions_real)
        # self.settings.child('acquisition', 'acq_time').setValue(total_time)
        # positions = []
        # for ind in range(Ncycles):
        #     positions.extend(positions_real)

        self.stage.set_positions_arbitrary(positions_real)

        self.move_at_navigator(*self.scan_parameters.positions[0][0:2])

    def transform_scan_coord(self, positions):

        offset = self.settings.child('stage_settings', 'stage_x',
                                     'offset_x').value()
        if self.settings.child('stage_settings', 'stage_x',
                               'stage_x_direction').value() == 'Normal':
            scaling_x = -1
        else:
            scaling_x = 1

        if self.settings.child('stage_settings', 'stage_y',
                               'stage_y_direction').value() == 'Normal':
            scaling_y = -1
        else:
            scaling_y = +1
        if self.settings.child('stage_settings', 'stage_x',
                               'stage_x_axis').value() == 'X':
            ind_x = 0
        else:
            ind_x = 1

        if self.settings.child('stage_settings', 'stage_y',
                               'stage_y_axis').value() == 'Y':
            ind_y = 1
        else:
            ind_y = 0

        positions_out = []
        for pos in positions:
            pos_tmp = [(scaling_x * pos[ind_x] + offset) * 1000,
                       (scaling_y * pos[ind_y] + offset) * 1000]
            positions_out.append(pos_tmp)

        return positions_out

    def stop(self):
        super(DAQ_2DViewer_FLIM, self).stop()
        self.stop_scanner.emit()
        try:
            self.move_at_navigator(0, 0)
        except:
            pass
class DAQ_1DViewer_TH260(DAQ_Viewer_base):
    """
        See Also
        --------
        utility_classes.DAQ_Viewer_base
    """

    params = comon_parameters+[
            {'title': 'Device index:', 'name': 'device', 'type': 'int', 'value': 0, 'max': 3, 'min': 0},
            {'title': 'Infos:', 'name': 'infos', 'type': 'str', 'value': "", 'readonly': True},
            {'title': 'Line Settings:', 'name': 'line_settings', 'type': 'group', 'expanded': False, 'children': [
                {'title': 'Sync Settings:', 'name': 'sync_settings', 'type': 'group', 'expanded': True, 'children': [
                    {'title': 'ZeroX (mV):', 'name': 'zerox', 'type': 'int', 'value': -10, 'max': 0, 'min': -40},
                    {'title': 'Level (mV):', 'name': 'level', 'type': 'int', 'value': -50, 'max': 0, 'min': -1200},
                    {'title': 'Offset (ps):', 'name': 'offset', 'type': 'int', 'value': 0, 'max': 99999, 'min': -99999},
                    {'title': 'Divider:', 'name': 'divider', 'type': 'list', 'value': 1, 'values': [1, 2, 4, 8]},
                ]},
                {'title': 'CH1 Settings:', 'name': 'ch1_settings', 'type': 'group', 'expanded': True, 'children': [
                    {'title': 'Enabled?:', 'name': 'enabled', 'type': 'bool', 'value': True},
                    {'title': 'ZeroX (mV):', 'name': 'zerox', 'type': 'int', 'value': -10, 'max': 0, 'min': -40},
                    {'title': 'Level (mV):', 'name': 'level', 'type': 'int', 'value': -150, 'max': 0, 'min': -1200},
                    {'title': 'Offset (ps):', 'name': 'offset', 'type': 'int', 'value': 0, 'max': 99999, 'min': -99999},
                    {'title': 'Deadtime (ns):', 'name': 'deadtime', 'type': 'list', 'value': 24, 'values': [24, 44, 66, 88, 112, 135, 160, 180]},

                ]},
                {'title': 'CH2 Settings:', 'name': 'ch2_settings', 'type': 'group', 'expanded': True, 'children': [
                    {'title': 'Enabled?:', 'name': 'enabled', 'type': 'bool', 'value': False},
                    {'title': 'ZeroX (mV):', 'name': 'zerox', 'type': 'int', 'value': -10, 'max': 0, 'min': -40},
                    {'title': 'Level (mV):', 'name': 'level', 'type': 'int', 'value': -150, 'max': 0, 'min': -1200},
                    {'title': 'Offset (ps):', 'name': 'offset', 'type': 'int', 'value': 0, 'max': 99999, 'min': -99999},
                    {'title': 'Deadtime (ns):', 'name': 'deadtime', 'type': 'list', 'value': 24, 'values': [24, 44, 66, 88, 112, 135, 160, 180]},
                ]},
             ]},
            {'title': 'Acquisition:', 'name': 'acquisition', 'type': 'group', 'expanded': True, 'children': [
                 {'title': 'Acq. type:', 'name': 'acq_type', 'type': 'list',
                                'value': 'Histo', 'values': ['Counting', 'Histo', 'T3']},
                 {'title': 'Base path:', 'name': 'base_path', 'type': 'browsepath', 'value': 'E:\Data',
                 'filetype': False, 'readonly': True, 'visible': False },
                 {'title': 'Temp. File:', 'name': 'temp_file', 'type': 'str', 'value': '', 'visible': False},
                 {'title': 'Acq. time (s):', 'name': 'acq_time', 'type': 'float', 'value': 1, 'min': 0.1,
                                    'max': 360000},
                 {'title': 'Elapsed time (s):', 'name': 'elapsed_time', 'type': 'float', 'value': 0, 'min': 0,
                                    'readonly': True},

                 {'title': 'Timings:', 'name': 'timings', 'type': 'group', 'expanded': True, 'children': [
                     {'title': 'Mode:', 'name': 'timing_mode', 'type': 'list', 'value': 'Hires', 'values': ['Hires', 'Lowres']},
                     {'title': 'Base Resolution (ps):', 'name': 'base_resolution', 'type': 'float', 'value': 25, 'min': 0, 'readonly': True},
                     {'title': 'Resolution (ns):', 'name': 'resolution', 'type': 'float', 'value': 0.2, 'min': 0},
                     {'title': 'Time window (s):', 'name': 'window', 'type': 'float', 'value': 100, 'min': 0, 'readonly': True, 'enabled': False, 'siPrefix': True},
                     {'title': 'Nbins:', 'name': 'nbins', 'type': 'list', 'value': 1024, 'values': [1024*(2**lencode) for lencode in range(6)]},
                     {'title': 'Offset (ns):', 'name': 'offset', 'type': 'int', 'value': 0, 'max': 100000000, 'min': 0},
                 ]},

                 {'title': 'Rates:', 'name': 'rates', 'type': 'group', 'expanded': True, 'children': [
                     {'title': 'Show large display?', 'name': 'large_display', 'type': 'bool', 'value': True},
                     {'title': 'Sync rate (cts/s):', 'name': 'syncrate', 'type': 'int', 'value': 0, 'min': 0, 'readonly': True, 'siPrefix': True},
                     {'title': 'CH1 rate (cts/s):', 'name': 'ch1_rate', 'type': 'int', 'value': 0, 'min': 0, 'readonly': True, 'siPrefix': True},
                     {'title': 'CH2 rate (cts/s):', 'name': 'ch2_rate', 'type': 'int', 'value': 0, 'min': 0, 'readonly': True, 'siPrefix': True},
                     {'title': 'Nrecords:', 'name': 'records', 'type': 'int', 'value': 0, 'min': 0, 'readonly': True,  'siPrefix': True},
                 ]},
             ]},

            ]

    hardware_averaging = False
    stop_tttr = pyqtSignal()

    def __init__(self, parent=None, params_state=None):

        super(DAQ_1DViewer_TH260, self).__init__(parent, params_state) #initialize base class with commom attributes and methods

        self.device = None
        self.x_axis = None
        self.controller = None
        self.datas = None #list of numpy arrays, see set_acq_mode
        self.data_pointers = None #list of ctypes pointers pointing to self.datas array elements, see set_acq_mode
        self.acq_done = False
        self.Nchannels = 0
        self.channels_enabled = {'CH1': {'enabled': True, 'index': 0}, 'CH2': {'enabled': False, 'index': 1}}
        self.modes = ['Histo', 'T2', 'T3']
        self.actual_mode = 'Counting'
        self.h5file = None
        self.detector_thread = None
        self.time_t3 = 0
        self.time_t3_rate = 0
        self.ind_reading = 0
        self.histo_array = None

    def emit_log(self,string):
        self.emit_status(ThreadCommand('Update_Status', [string, 'log']))

    def commit_settings(self, param):
        """
            | Activate parameters changes on the hardware from parameter's name.
            |

            =============== ================================    =========================
            **Parameters**   **Type**                           **Description**
            *param*          instance of pyqtgraph parameter    The parameter to activate
            =============== ================================    =========================

            Three profile of parameter :
                * **bin_x** : set binning camera from bin_x parameter's value
                * **bin_y** : set binning camera from bin_y parameter's value
                * **set_point** : Set the camera's temperature from parameter's value.

        """
        try:
            if param.name() == 'acq_type':
                self.set_acq_mode(param.value())
                self.set_get_resolution(wintype='both')
                if param.value() == 'Counting' or param.value() == 'Histo':
                    self.settings.child('acquisition', 'temp_file').hide()
                    self.settings.child('acquisition', 'base_path').hide()
                else:
                    self.settings.child('acquisition', 'temp_file').show()
                    self.settings.child('acquisition', 'base_path').show()
                #     self.settings.child('acquisition', 'timings', 'nbins').setOpts(
                #         limits=[128 * (2 ** lencode) for lencode in range(6)])
                #     self.settings.child('acquisition', 'timings', 'nbins').setValue(128)
                #
                # else:
                #     self.settings.child('acquisition', 'timings', 'nbins').setOpts(
                #         limits=[1024 * (2 ** lencode) for lencode in range(6)])
                #     self.settings.child('acquisition', 'timings', 'nbins').setValue(1024)

            elif param.name() == 'nbins' or param.name() == 'resolution':
                self.set_get_resolution(param.name())

            elif param.name() == 'timing_mode':
                self.set_get_resolution('resolution')

            elif param.parent().name() == 'ch1_settings' or param.parent().name() == 'ch2_settings' or param.parent().name() == 'sync_settings':
                self.set_sync_channel(param)

            elif param.name() == 'offset' and param.parent().name() == 'timings':
                self.controller.TH260_SetOffset(self.device, param.value())

            elif param.name() == 'large_display' and param.value():
                self.emit_status(ThreadCommand('init_lcd', [dict(labels=['Syn. Rate (kcts/s)',
                                  'CH1 rate (kcts/s)', 'CH2 Rate (kcts/s)'], Nvals=3, digits=6)]))

        except Exception as e:
            self.emit_status(ThreadCommand('Update_Status', [getLineInfo() + str(e), 'log']))


    def emit_data(self):
        """
        """
        try:
            mode = self.settings.child('acquisition', 'acq_type').value()
            if mode == 'Counting':
                rates = [np.array(rate) for rate in self.get_rates()[1:]]
                self.data_grabed_signal.emit([OrderedDict(name='TH260', data=rates, type='Data0D')])
            elif mode == 'Histo':
                channels_index = [self.channels_enabled[k]['index'] for k in self.channels_enabled.keys() if self.channels_enabled[k]['enabled']]
                for ind, channel in enumerate(channels_index):
                    self.controller.TH260_GetHistogram(self.device, self.data_pointers[ind], channel=channel, clear=True)
                records = np.sum(np.array([np.sum(data) for data in self.datas]))
                self.settings.child('acquisition', 'rates', 'records').setValue(records)
                self.data_grabed_signal.emit([OrderedDict(name='TH260', data=self.datas, type='Data1D',)])
                self.general_timer.start()

            elif mode == 'T3':
                self.data_grabed_signal.emit([OrderedDict(name='TH260', data=[self.datas], type='Data1D')])
                self.general_timer.start()



        except Exception as e:
            self.emit_status(ThreadCommand('Update_Status', [getLineInfo()+ str(e), 'log']))

    def emit_data_tmp(self):
        """
        """
        try:
            mode = self.settings.child('acquisition', 'acq_type').value()
            if mode == 'Counting':
                rates = [np.array(rate) for rate in self.get_rates()[1:]]
                self.data_grabed_signal_temp.emit([OrderedDict(name='TH260', data=rates, type='Data0D')])
            elif mode == 'Histo':
                channels_index = [self.channels_enabled[k]['index'] for k in self.channels_enabled.keys() if self.channels_enabled[k]['enabled']]
                for ind, channel in enumerate(channels_index):
                    self.controller.TH260_GetHistogram(self.device, self.data_pointers[ind], channel=channel, clear=False)
                records = np.sum(np.array([np.sum(data) for data in self.datas]))
                self.settings.child('acquisition', 'rates', 'records').setValue(records)
                self.data_grabed_signal_temp.emit([OrderedDict(name='TH260', data=self.datas, type='Data1D',)])
            elif mode == 'T3':
                self.data_grabed_signal_temp.emit([OrderedDict(name='TH260', data=[self.datas], type='Data1D')])


        except Exception as e:
            self.emit_status(ThreadCommand('Update_Status', [getLineInfo()+ str(e), 'log']))

    def process_histo_from_h5(self, Nx=1, Ny=1, channel=0, marker=65):
        marker = 65
        markers = self.h5file.get_node('/markers')[self.ind_reading:]
        nanotimes = self.h5file.get_node('/nanotimes')[self.ind_reading:]

        nbins = self.settings.child('acquisition', 'timings', 'nbins').value()
        datas = extract_TTTR_histo_every_pixels(nanotimes, markers, marker=marker, Nx=Nx, Ny=Ny,
                                        Ntime=nbins, ind_line_offset=self.ind_reading, channel=channel)
        self.ind_reading += nanotimes.size
        return datas

    def set_acq_mode(self, mode, update=False):
        """
        Change the acquisition mode (histogram for mode=='Counting' and 'Histo' or T3 for mode == 'FLIM')
        Parameters
        ----------
        mode

        Returns
        -------

        """
        #check enabled channels
        labels = [k for k in self.channels_enabled.keys() if self.channels_enabled[k]['enabled']]
        N = len(labels)

        if mode != self.actual_mode or update:

            if mode == 'Counting':
                self.controller.TH260_Initialize(self.device, mode=0)  # histogram
                self.datas = [np.zeros((1,), dtype=np.uint32) for ind in range(N)]
                self.data_grabed_signal_temp.emit([OrderedDict(name='TH260', data=self.datas, type='Data0D', labels=labels)])
                self.data_pointers = [data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32)) for data in self.datas]
            elif mode == 'Histo':
                self.controller.TH260_Initialize(self.device, mode=0)  # histogram
                self.datas = [np.zeros((self.settings.child('acquisition', 'timings', 'nbins').value(),), dtype=np.uint32) for ind in range(N)]
                self.data_grabed_signal_temp.emit([OrderedDict(name='TH260', data=self.datas, type='Data1D',
                                                                x_axis=self.get_xaxis(), labels=labels)])
                self.data_pointers = [data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32)) for data in self.datas]
            elif mode == 'T3':
                self.controller.TH260_Initialize(self.device, mode=3)  # T3 mode
                self.datas = [np.zeros((self.settings.child('acquisition', 'timings', 'nbins').value(),), dtype=np.uint32) for ind in range(N)]
                self.data_grabed_signal_temp.emit([OrderedDict(name='TH260', data=self.datas, type='Data1D',
                                                                x_axis=self.get_xaxis(), labels=labels)])
                self.data_pointers = [data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32)) for data in self.datas]

            self.actual_mode = mode

    def ini_channels(self):
        self.controller.TH260_SetSyncDiv(self.device,
                                         self.settings.child('line_settings', 'sync_settings', 'divider').value())

        self.controller.TH260_SetSyncCFD(self.device,
                                         self.settings.child('line_settings', 'sync_settings', 'level').value(),
                                         self.settings.child('line_settings', 'sync_settings', 'zerox').value())

        self.controller.TH260_SetSyncChannelOffset(self.device, self.settings.child('line_settings', 'sync_settings',
                                                                                    'offset').value())

        self.controller.TH260_SetInputCFD(self.device, 0,
                                          self.settings.child('line_settings', 'ch1_settings', 'level').value(),
                                          self.settings.child('line_settings', 'ch1_settings', 'zerox').value())
        self.controller.TH260_SetInputCFD(self.device, 1,
                                          self.settings.child('line_settings', 'ch2_settings', 'level').value(),
                                          self.settings.child('line_settings', 'ch2_settings', 'zerox').value())

        self.controller.TH260_SetInputChannelOffset(self.device, 0,
                                          self.settings.child('line_settings', 'ch1_settings', 'offset').value())
        self.controller.TH260_SetInputChannelOffset(self.device, 1,
                                          self.settings.child('line_settings', 'ch2_settings', 'offset').value())

        param = self.settings.child('line_settings', 'ch1_settings', 'deadtime')
        code = param.opts['limits'].index(param.value())
        self.controller.TH260_SetInputDeadTime(self.device, 0, code)
        param = self.settings.child('line_settings', 'ch2_settings', 'deadtime')
        code = param.opts['limits'].index(param.value())
        self.controller.TH260_SetInputDeadTime(self.device, 1, code)

        self.Nchannels = self.controller.TH260_GetNumOfInputChannels(self.device)
        if self.Nchannels >= 1:
            self.settings.child('line_settings', 'ch2_settings').hide()
            self.controller.TH260_SetInputChannelEnable(self.device, channel=0,
                                                        enable=self.settings.child('line_settings', 'ch1_settings',
                                                                                   'enabled').value())
            self.channels_enabled['CH2']['enabled'] = False
            self.channels_enabled['CH1']['enabled'] = self.settings.child('line_settings', 'ch1_settings',
                                                                          'enabled').value()

        if self.Nchannels >= 2:
            self.settings.child('line_settings', 'ch2_settings').show()
            self.channels_enabled['CH2']['enabled'] = self.settings.child('line_settings', 'ch2_settings',
                                                                          'enabled').value()
            self.controller.TH260_SetInputChannelEnable(self.device, channel=1,
                                                        enable=self.settings.child('line_settings', 'ch2_settings',
                                                                                   'enabled').value())



    def ini_detector(self, controller=None):
        """
            See Also
            --------
            DAQ_utils.ThreadCommand, hardware1D.DAQ_1DViewer_Picoscope.update_pico_settings
        """
        self.status.update(edict(initialized=False,info="",x_axis=None,y_axis=None,controller=None))
        try:

            if self.settings.child(('controller_status')).value()=="Slave":
                if controller is None:
                    raise Exception('no controller has been defined externally while this detector is a slave one')
                else:
                    self.controller=controller
            else:
                self.device = self.settings.child(('device')).value()
                self.settings.child(('device')).setOpts(readonly=True) #not possible to change it once initialized
                self.controller = timeharp260.Th260()

                # open device and initialize it
                self.controller.TH260_OpenDevice(self.device)

            #set timer to update info from controller
            self.general_timer = QTimer()
            self.general_timer.setInterval(200)
            self.general_timer.timeout.connect(self.update_timer)

            #set timer to check acquisition state
            self.acq_timer = QTimer()
            self.acq_timer.setInterval(500)
            self.acq_timer.timeout.connect(self.check_acquisition)

            #init the device and memory in the selected mode
            self.set_acq_mode(self.settings.child('acquisition', 'acq_type').value(), update=True)

            model, partn, version = self.controller.TH260_GetHardwareInfo(self.device)
            serial = self.controller.TH260_GetSerialNumber(self.device)
            self.settings.child(('infos')).setValue('serial: {}, model: {}, pn: {}, version: {}'.format(serial, model, partn, version))

            self.ini_channels()

            self.set_get_resolution(wintype='both')

            self.emit_status(ThreadCommand('init_lcd', [dict(labels=['CH1 rate (kcts/s)', 'CH2 Rate (kcts/s)'], Nvals=2,
                                                             digits=6)]))

            self.general_timer.start()  # Timer event fired every 200ms

            #%%%%%%% init axes from image
            self.x_axis = self.get_xaxis()
            self.status.x_axis = self.x_axis
            self.status.initialized = True
            self.status.controller = self.controller

            return self.status

        except Exception as e:
            self.status.info = getLineInfo()+ str(e)
            self.status.initialized = False
            return self.status

    def poll_acquisition(self):
        """
        valid only for histogramming mode
        Returns
        -------

        """
        while not self.controller.TH260_CTCStatus(self.device):
            # elapsed_time = self.controller.TH260_GetElapsedMeasTime(self.device)  # in ms
            # self.settings.child('acquisition', 'elapsed_time').setValue(elapsed_time / 1000)  # in s
            QtWidgets.QApplication.processEvents()
            QThread.msleep(100)
            #self.emit_data_tmp()

        self.controller.TH260_StopMeas(self.device)
        self.emit_data()

    @pyqtSlot(int)
    def set_elapsed_time(self, elapsed_time):
        self.settings.child('acquisition', 'elapsed_time').setValue(elapsed_time/1000)  # in s

    def check_acquisition(self):
        if not self.controller.TH260_CTCStatus(self.device):
            elapsed_time = self.controller.TH260_GetElapsedMeasTime(self.device)  # in ms
            self.set_elapsed_time(elapsed_time)
            self.emit_data_tmp()
        else:
            self.acq_timer.stop()
            QtWidgets.QApplication.processEvents()  # this to be sure the timer is not fired while emitting data
            self.controller.TH260_StopMeas(self.device)
            QtWidgets.QApplication.processEvents()  #this to be sure the timer is not fired while emitting data
            self.emit_data()

    def get_rates(self):
        vals = []
        sync_rate = self.controller.TH260_GetSyncRate(self.device)

        vals.append([sync_rate/1000])
        for ind_channel in range(self.Nchannels):
            if self.settings.child('line_settings',  'ch{:d}_settings'.format(ind_channel+1), 'enabled').value():
                rate = self.controller.TH260_GetCountRate(self.device, ind_channel)
                vals.append([rate/1000])
            else:
                vals.append([0])

        self.emit_rates(vals)
        return vals

    def emit_rates(self,vals):
        self.settings.child('acquisition', 'rates', 'syncrate').setValue(vals[0][0]*1000)
        for ind_channel in range(self.Nchannels):
            self.settings.child('acquisition', 'rates', 'ch{:d}_rate'.format(ind_channel+1)).setValue(vals[ind_channel+1][0]*1000)

        if self.settings.child('acquisition', 'rates', 'large_display').value():
            self.emit_status(ThreadCommand('lcd', [vals[1:]]))
        return vals

    def set_sync_channel(self, param):
        """
        Set the channel or sync settings (level, zerox, ...)
        Parameters
        ----------
        param: (Parameter) either ch1_settings children, ch2_settings children or sync_settings children
        """
        if param.parent().name() == 'sync_settings':
            source = 'sync'
            source_str = 'sync'
        elif param.parent().name() == 'ch1_settings':
            source = 0
            source_str = 'CH1'
        elif param.parent().name() == 'ch2_settings':
            source = 1
            source_str = 'CH2'

        if param.name() == 'divider':
            self.controller.TH260_SetSyncDiv(self.device, param.value())

        elif param.name() == 'zerox' or param.name() == 'level':
            level = param.parent().child(('level')).value()
            zerox = param.parent().child(('zerox')).value()
            if source == 'sync':
                self.controller.TH260_SetSyncCFD(self.device, level, zerox)
            else:
                self.controller.TH260_SetInputCFD(self.device, source, level, zerox)

        elif param.name() == 'offset':
            if source == 'sync':
                self.controller.TH260_SetSyncChannelOffset(self.device,param.value())
            else:
                self.controller.TH260_SetInputChannelOffset(self.device, source, param.value())

        elif param.name() == 'enabled':
            self.controller.TH260_SetInputChannelEnable(self.device, source, enable=param.value())
            self.channels_enabled[source_str]['enabled'] = param.value()
            for par in param.parent().children():
                if par != param:
                    par.setOpts(enabled=param.value())

        elif param.name() == 'deadtime':
            code = param.opts['limits'].index(param.value())
            self.controller.TH260_SetInputDeadTime(self.device, source, code)


    def set_get_resolution(self, wintype='resolution'):
        """
        Set and get right values of bin time resolution number of bins and gloabl time window
        Parameters
        ----------
        wintype: (str) either 'nbins' or 'resolution' or 'both'

        Returns
        -------

        """

        base_res, max_bin_size_code = self.controller.TH260_GetBaseResolution(self.device)  # bas res in ps
        self.settings.child('acquisition', 'timings', 'base_resolution').setValue(base_res)
        resolution = self.settings.child('acquisition', 'timings', 'resolution').value()  # in ns
        Nbins = self.settings.child('acquisition', 'timings', 'nbins').value()

        bin_size_code = int(np.log(resolution * 1000 / base_res)/np.log(2))
        if bin_size_code < 0:
            bin_size_code = 0

        if wintype =='resolution' or wintype =='both':
            if bin_size_code >= max_bin_size_code:
                bin_size_code = max_bin_size_code-1 #see SetBinning documentation
            self.controller.TH260_SetBinning(self.device, bin_size_code)
            resolution = 2**bin_size_code * base_res / 1000
            resolution=self.controller.TH260_GetResolution(self.device)/1000
            self.settings.child('acquisition', 'timings', 'resolution').setValue(resolution)
        if wintype =='nbins' or wintype =='both':
            mode = self.settings.child('acquisition', 'acq_type').value()

            if mode == 'Counting' or mode == 'Histo':
                Nbins = self.controller.TH260_SetHistoLen(self.device, int(np.log(Nbins/1024)/np.log(2)))
            self.settings.child('acquisition', 'timings', 'nbins').setValue(Nbins)

            N = len([k for k in self.channels_enabled.keys() if self.channels_enabled[k]['enabled']])
            if mode == 'Counting':
                self.datas = [np.zeros((1,), dtype=np.uint32) for ind in range(N)]
            elif mode == 'Histo' or mode == 'T3':
                self.datas = [np.zeros((Nbins,), dtype=np.uint32) for ind in range(N)]
                self.get_xaxis()
                self.emit_x_axis()
            self.data_pointers = [data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32)) for data in self.datas]


        self.settings.child('acquisition', 'timings', 'window').setValue(Nbins*resolution/1e6)  # in ms
        self.set_acq_mode(self.settings.child('acquisition', 'acq_type').value())



    def update_timer(self):
        """

        """
        self.get_rates()
        warn = self.controller.TH260_GetWarnings(self.device)
        if warn != '':
            self.emit_status(ThreadCommand('Update_Status', [warn, '']))


    def close(self):
        """

        """
        self.stop()
        QtWidgets.QApplication.processEvents()
        self.datas = None
        self.data_pointers = None
        self.general_timer.stop()
        QtWidgets.QApplication.processEvents()
        #QThread.msleep(1000)
        self.controller.TH260_CloseDevice(self.device)
        if self.h5file is not None:
            if self.h5file.isopen:
                self.h5file.flush()
                self.h5file.close()

    def get_xaxis(self):
        """
            Obtain the horizontal axis of the data.

            Returns
            -------
            1D numpy array
                Contains a vector of integer corresponding to the horizontal camera pixels.
        """
        if self.controller is not None:
            res = self.settings.child('acquisition', 'timings', 'resolution').value()
            Nbins = self.settings.child('acquisition', 'timings', 'nbins').value()
            self.x_axis = dict(data=np.linspace(0, (Nbins-1)*res, Nbins), label='Time', units='ns')
        else:
            raise(Exception('Controller not defined'))
        return self.x_axis

    def get_yaxis(self):
        """
            Obtain the vertical axis of the image.

            Returns
            -------
            1D numpy array
                Contains a vector of integer corresponding to the vertical camera pixels.
        """
        if self.controller is not None:
            pass
        else: raise(Exception('Controller not defined'))
        return self.y_axis



    def grab_data(self, Naverage=1, **kwargs):
        """
            Start new acquisition in two steps :
                * Initialize data: self.datas for the memory to store new data and self.data_average to store the average data
                * Start acquisition with the given exposure in ms, in "1d" or "2d" mode

            =============== =========== =============================
            **Parameters**   **Type**    **Description**
            Naverage         int         Number of images to average
            =============== =========== =============================

            See Also
            --------
            DAQ_utils.ThreadCommand
        """
        try:
            self.acq_done = False
            mode = self.settings.child('acquisition', 'acq_type').value()
            if mode == 'Counting':
                QThread.msleep(100) #sleeps 100ms otherwise the loop is too fast
                self.emit_data()

            elif mode == 'Histo':
                time_acq = int(self.settings.child('acquisition', 'acq_time').value()*1000)  # in ms
                self.controller.TH260_ClearHistMem(self.device)
                self.controller.TH260_StartMeas(self.device, time_acq)
                self.acq_timer.start()
                #self.poll_acquisition()

            elif mode == 'T3':
                self.ind_reading = 0
                self.Nx = 1
                self.Ny = 1
                self.init_h5file()
                self.datas = np.zeros((self.settings.child('acquisition', 'timings', 'nbins').value(),), dtype=np.float64)
                self.init_histo_group()

                time_acq = int(self.settings.child('acquisition', 'acq_time').value() * 1000)  # in ms
                self.general_timer.stop()

                t3_reader = T3Reader(self.device, self.controller, time_acq, self.Nchannels)
                self.detector_thread = QThread()
                t3_reader.moveToThread(self.detector_thread)

                t3_reader.data_signal[dict].connect(self.populate_h5)
                self.stop_tttr.connect(t3_reader.stop_TTTR)

                self.detector_thread.t3_reader = t3_reader
                self.detector_thread.start()
                self.detector_thread.setPriority(QThread.HighestPriority)
                self.time_t3 = time.perf_counter()
                self.time_t3_rate = time.perf_counter()
                t3_reader.start_TTTR()


        except Exception as e:
            self.emit_status(ThreadCommand('Update_Status', [getLineInfo() + str(e), "log"]))

    def init_histo_group(self):
        histo_group = self.h5file.create_group(self.h5file.root, 'histograms')
        self.histo_array = self.h5file.create_carray(histo_group, 'histogram', obj=self.datas,
                                                     title='histogram')
        x_axis = self.get_xaxis()
        xarray = self.h5file.create_carray(histo_group, "x_axis", obj=x_axis['data'], title='x_axis')
        xarray.attrs['shape'] = xarray.shape
        xarray.attrs['type'] = 'signal_axis'
        xarray.attrs['data_type'] = '1D'

        self.histo_array._v_attrs['data_type'] = '1D'
        self.histo_array._v_attrs['type'] = 'histo_data'
        self.histo_array._v_attrs['shape'] = self.datas.shape


    def get_new_file_name(self):
        today = datetime.datetime.now()

        date = today.strftime('%Y%m%d')
        year = today.strftime('%Y')
        curr_dir = os.path.join(self.settings.child('acquisition', 'base_path').value(), year, date)
        if not os.path.isdir(curr_dir):
            os.mkdir(curr_dir)

        with os.scandir(curr_dir) as it:
            files = []
            for entry in it:
                if entry.name.startswith('tttr_data') and entry.is_file():
                    files.append(entry.name)
            files.sort()
            if files == []:
                index = 0
            else:
                index = int(os.path.splitext(files[-1])[0][-3:])+1

            file = 'tttr_data_{:03d}'.format(index)
        return file, curr_dir

    def init_h5file(self):

        file, curr_dir = self.get_new_file_name()

        self.settings.child('acquisition', 'temp_file').setValue(file+'.h5')
        self.h5file = tables.open_file(os.path.join(curr_dir, file+'.h5'), mode='w')
        h5group = self.h5file.root
        h5group._v_attrs['settings'] = customparameter.parameter_to_xml_string(self.settings)
        h5group._v_attrs.type = 'detector'
        h5group._v_attrs['format_name'] = 'timestamps'

        channels_index = [self.channels_enabled[k]['index'] for k in self.channels_enabled.keys() if
                          self.channels_enabled[k]['enabled']]
        self.marker_array = self.h5file.create_earray(self.h5file.root, 'markers', tables.UInt8Atom(), (0,),
                                                      title='markers')
        self.marker_array._v_attrs['data_type'] = '1D'
        self.marker_array._v_attrs['type'] = 'tttr_data'

        self.nanotimes_array = self.h5file.create_earray(self.h5file.root, 'nanotimes', tables.UInt16Atom(), (0,),
                                                         title='nanotimes')
        self.nanotimes_array._v_attrs['data_type'] = '1D'
        self.nanotimes_array._v_attrs['type'] = 'tttr_data'

        self.timestamp_array = self.h5file.create_earray(self.h5file.root, 'timestamps', tables.UInt64Atom(), (0,),
                                                   title='timestamps')
        self.timestamp_array._v_attrs['data_type'] = '1D'
        self.timestamp_array._v_attrs['type'] = 'tttr_data'

        # self.raw_datas_array = self.h5file.create_earray(self.h5file.root, 'raw_data', tables.UInt64Atom(), (0,),
        #                                           title='raw_data')
        # self.raw_datas_array._v_attrs['data_type'] = '1D'
        # self.raw_datas_array._v_attrs['type'] = 'tttr_data'



    @pyqtSlot(dict)
    def populate_h5(self, datas):
        """

        Parameters
        ----------
        datas: (dict) dict(data=self.buffer[0:nrecords], rates=rates, elapsed_time=elapsed_time)

        Returns
        -------

        """
        if datas['data'] != []:
            # self.raw_datas_array.append(datas['data'])
            # self.raw_datas_array._v_attrs['shape'] = self.raw_datas_array.shape
            detectors, timestamps, nanotimes = pqreader.process_t3records(
                datas['data'], time_bit=10, dtime_bit=15, ch_bit=6, special_bit=True,
                ovcfunc=pqreader._correct_overflow_nsync)

            self.timestamp_array.append(timestamps)
            self.timestamp_array._v_attrs['shape'] = self.timestamp_array.shape
            self.nanotimes_array.append(nanotimes)
            self.nanotimes_array._v_attrs['shape'] = self.nanotimes_array.shape
            self.marker_array.append(detectors)
            self.marker_array._v_attrs['shape'] = self.marker_array.shape
            self.h5file.flush()

        if time.perf_counter() - self.time_t3_rate > 0.5:
            self.emit_rates(datas['rates'])
            self.set_elapsed_time(datas['elapsed_time'])
            self.settings.child('acquisition', 'rates', 'records').setValue(self.nanotimes_array.shape[0])
            self.time_t3_rate = time.perf_counter()

        elif time.perf_counter() - self.time_t3 > 5:
            self.datas += np.squeeze(self.process_histo_from_h5(Nx=self.Nx, Ny=self.Ny))
            self.histo_array[:] = self.datas
            self.emit_data_tmp()
            self.time_t3 = time.perf_counter()

        if datas['acquisition_done']:
            self.datas += np.squeeze(self.process_histo_from_h5(Nx=self.Nx, Ny=self.Ny))
            self.histo_array[:] = self.datas
            self.emit_data()





    def stop(self):
        """
            stop the camera's actions.
        """
        try:
            self.acq_timer.stop()
            QtWidgets.QApplication.processEvents()
            self.controller.TH260_StopMeas(self.device)
            QtWidgets.QApplication.processEvents()
            self.general_timer.start()
        except Exception as e:
            self.emit_status(ThreadCommand('Update_Status', [getLineInfo()+ str(e), "log"]))

        return ""
Ejemplo n.º 3
0
class PrettyWidget(QOpenGLWidget):
    started = False
    finished = False
    timer = None
    port_trigger = None
    current_index = None
    presenting = None
    paused = False
    delay = 0  # used when pausing
    cross_delay = 2
    cross_color = 'green'
    sound = {'start': None, 'end': None}
    fast_tsv = None
    fast_i = None
    fast_t = None

    def __init__(self, parameters):
        super().__init__()
        self.P = parameters

        if self.P['SOUND']['PLAY']:
            self.sound = {
                'start': QSound(str(SOUNDS_DIR / self.P['SOUND']['START'])),
                'end': QSound(str(SOUNDS_DIR / self.P['SOUND']['END'])),
            }
        self.open_serial()

        try:
            port_input = Serial(self.P['COM']['INPUT']['PORT'],
                                baudrate=self.P['COM']['INPUT']['BAUDRATE'])
        except SerialException:
            port_input = None
            lg.warning('could not open serial port to read input')
            _warn_about_ports()
        self.port_input = port_input
        self.start_serial_input()

        lg.info('Reading images')
        self.stimuli = read_stimuli(self.P)
        lg.info('Reading images: finished')

        # background color
        self.bg_color = QColor(self.P['BACKGROUND'])

        self.show()

        self.time = QTime()
        self.timer = QTimer()
        self.timer.setTimerType(Qt.PreciseTimer)
        self.timer.timeout.connect(self.check_time)

        self.setCursor(Qt.BlankCursor)
        self.setGeometry(200, 200, 1000, 1000)
        if self.P['FULLSCREEN']:
            self.showFullScreen()
        else:
            self.showNormal()
        self.serial(250)
        if self.P['DATAGLOVE']:
            self.open_dataglove()

    def open_dataglove(self):

        lg.info('Opening dataglove')
        self.glove = []
        if FiveDTGlove.gloveDLL is None:  # could not initialize DLL
            return

        for i in range(2):  # TODO: we should use scan_USB but I get error
            logname = self.P['logname']
            DATAGLOVE_LOG = logname.parent / (logname.stem +
                                              f'_dataglove{i}.txt')
            new_glove = FiveDTGlove(DATAGLOVE_LOG)
            try:
                new_glove.open(f'USB{i}'.encode())
            except IOError:
                pass
            else:
                self.glove.append(new_glove)

    def open_serial(self):
        try:
            self.port_trigger = Serial(
                self.P['COM']['TRIGGER']['PORT'],
                baudrate=self.P['COM']['TRIGGER']['BAUDRATE'])
        except SerialException:
            lg.warning('could not open serial port for triggers')
            _warn_about_ports()

    def serial(self, trigger):
        """trigger needs to be between 0 and 255. If none, then it closes the
        serial port"""
        if trigger is None:
            if self.port_trigger is not None:
                self.port_trigger.close()
        else:
            lg.debug(f'Sending trigger {trigger:03d}')
            if self.port_trigger is not None:
                try:
                    self.port_trigger.write(pack('>B', trigger))
                except Exception:
                    lg.warning('could not write to serial port')
                    self.open_serial()

    def paintGL(self):

        window_rect = self.rect()
        rect_x = window_rect.center().x() + self.P['SCREEN']['RIGHTWARDS']
        rect_y = window_rect.center().y() + self.P['SCREEN']['DOWNWARDS']

        qp = QPainter()
        qp.begin(self)

        qp.fillRect(window_rect, self.bg_color)

        if self.paused:
            self.draw_text(qp, 'PAUSED')

        elif self.current_index is None:
            self.draw_text(qp, 'READY')

        else:

            if self.fast_tsv is not None:
                i_pixmap = where(self.fast_tsv['onset'] <= self.fast_i)[0][-1]

                if self.fast_i == self.fast_tsv['onset'][-1]:
                    self.fast_tsv = None
                    self.fast_i = None
                    self.frameSwapped.disconnect()

                else:
                    if self.fast_tsv['stim_file'][i_pixmap] is not None:
                        current_pixmap = self.fast_tsv['stim_file'][i_pixmap]
                        image_rect = current_pixmap.rect()
                        size = image_rect.size().scaled(
                            window_rect.size(), Qt.KeepAspectRatio)
                        img_origin_x = rect_x - int(size.width() / 2)
                        img_origin_y = rect_y - int(size.height() / 2)

                        qp.beginNativePainting()
                        qp.drawPixmap(img_origin_x, img_origin_y, size.width(),
                                      size.height(), current_pixmap)
                        qp.endNativePainting()

                    lg.debug(f'FAST IMAGE #{self.fast_i}')
                    self.fast_i += 1

            else:
                current_pixmap = self.stimuli['stim_file'][self.current_index]
                if isinstance(current_pixmap, Path):
                    self.fast_tsv = read_fast_stimuli(current_pixmap)
                    self.fast_i = 0

                elif isinstance(current_pixmap, str):
                    self.draw_text(qp, current_pixmap)
                    if current_pixmap == 'END':
                        if not self.finished:
                            self.draw_text(qp, 'END')
                            self.finished = True
                            self.serial(None)
                            if self.sound['end'] is not None:
                                self.sound['end'].play()

                else:
                    image_rect = current_pixmap.rect()

                    size = image_rect.size().scaled(window_rect.size(),
                                                    Qt.KeepAspectRatio)
                    img_origin_x = rect_x - int(size.width() / 2)
                    img_origin_y = rect_y - int(size.height() / 2)
                    qp.drawPixmap(img_origin_x, img_origin_y, size.width(),
                                  size.height(), current_pixmap)

                self.drawText(qp)

        qp.end()

        if self.presenting is not None:  # send triggers and log info right after the image was presented
            trial = self.stimuli[self.presenting]
            lg.info('Presenting ' + str(trial['trial_name']))
            self.serial(trial['trial_type'])
            self.presenting = None

        if self.fast_i == 0:
            self.input_thread.msleep(1000)
            self.frameSwapped.connect(self.update)
            self.update()

    def drawText(self, qp):

        if not self.P['FIXATION']['ACTIVE']:
            return

        elapsed = self.time.elapsed() + self.delay
        if elapsed > self.cross_delay:
            if self.cross_color == 'green':
                self.cross_color = 'red'
                self.serial(240)
            else:
                self.cross_color = 'green'
                self.serial(241)
            self.cross_delay += random() * 5000 + 2000

        color = QColor(self.cross_color)
        qp.setPen(color)
        qp.setFont(QFont('SansSerif', 50))
        qp.drawText(*self.center_rect(), Qt.AlignCenter, '+')

    def draw_text(self, qp, text):

        qp.setPen(QColor(40, 40, 255))
        qp.setFont(QFont('Decorative', 50))
        qp.drawText(*self.center_rect(), Qt.AlignCenter, text)

    def center_rect(self):
        window_rect = self.rect()
        width = window_rect.width()
        height = window_rect.height()
        img_origin_x = window_rect.center().x() - int(
            width / 2) + self.P['SCREEN']['RIGHTWARDS']
        img_origin_y = window_rect.center().y() - int(
            height / 2) + self.P['SCREEN']['DOWNWARDS']

        return (img_origin_x, img_origin_y, width, height)

    def check_time(self):

        elapsed = self.time.elapsed() + self.delay

        if self.P['DATAGLOVE']:
            for glove in self.glove:
                if glove.new_data:
                    glove_data = glove.get_sensor_raw_all()
                    glove.f.write(datetime.now().strftime('%H:%M:%S.%f') +
                                  '\t' +
                                  '\t'.join([f'{x}'
                                             for x in glove_data]) + '\n')

        index_image = where((self.stimuli['onset'] * 1e3) <= elapsed)[0]
        if len(index_image) == len(self.stimuli):
            self.stop()

        elif len(index_image) > 0:
            index_image = index_image[-1]

            if index_image != self.current_index:
                self.current_index = index_image

                if index_image is not None:
                    self.presenting = index_image

        self.update()

    def start(self):
        if self.started:
            return

        lg.warning('Starting')
        self.started = True
        self.current_index = -1
        self.time.start()
        self.timer.start(self.P['QTIMER_INTERVAL'])
        if self.sound['start'] is not None:
            self.sound['start'].play()

    def start_serial_input(self):
        self.input_worker = SerialInputWorker()
        self.input_worker.port_input = self.port_input
        self.input_thread = QThread(parent=self)
        self.input_thread.started.connect(self.input_worker.start_reading)
        self.input_worker.signal_to_main.connect(self.read_serial_input)
        self.input_worker.moveToThread(self.input_thread)
        self.input_thread.start()
        self.input_thread.setPriority(QThread.LowestPriority)

    def read_serial_input(self, number):
        lg.info(f'Received input trigger {number}')

        if self.P['COM']['INPUT']['START_TRIGGER'] == number:
            self.start()

        self.serial(254)

    def stop(self):
        lg.info('Stopping task')

        if self.timer is not None:
            self.timer.stop()

        # nice wayt to stop the worker
        self.input_worker.running = False
        self.input_thread.terminate()

        sleep(1)
        app.exit(0)

    def pause(self):
        if not self.paused:
            self.paused = True
            self.delay += self.time.elapsed()
            self.timer.stop()
            self.serial(253)
            lg.info('Pausing the task')

        else:
            self.paused = False
            self.time.restart()
            self.serial(254)
            lg.info('Pause finished: restarting the task')
            self.timer.start(self.P['QTIMER_INTERVAL'])
        self.update()

    def keyPressEvent(self, event):
        if isinstance(event, QKeyEvent):
            if event.key() in (Qt.Key_Enter, Qt.Key_Return):
                self.start()

            elif event.key() == Qt.Key_Space:
                self.pause()

            elif event.key() == Qt.Key_Escape:
                lg.info('Pressed Escape')
                self.serial(255)
                self.stop()

            elif event.key() == Qt.Key_PageUp:
                self.showFullScreen()

            elif event.key() == Qt.Key_PageDown:
                self.showNormal()

            else:
                super().keyPressEvent(event)
        else:
            super().keyPressEvent(event)

    def mouseDoubleClickEvent(self, event):
        if isinstance(event, QMouseEvent):
            if event.pos().x() > self.rect().center().x():

                if not self.started:
                    self.start()
                else:
                    self.pause()

            else:
                if self.isFullScreen():
                    self.showNormal()
                else:
                    self.showFullScreen()

        else:
            super().mouseDoubleClickEvent(event)

    def closeEvent(self, event):
        self.stop()
        event.accept()
Ejemplo n.º 4
0
class MainWindowController(QMainWindow):
    __storyData: ActionBundle
    # Type hints for .ui objects - same name, do not change here!
    storyStatementList: QTableWidget
    comboLogLevel: QComboBox
    btnPlay: QPushButton
    btnLoadStory: QPushButton
    btnSaveStory: QPushButton
    # Signals
    sigStoryDataChanged = pyqtSignal()  # Must NOT be in constructor !

    @property
    def storyData(self):
        return self.__storyData

    @storyData.setter
    def storyData(self, bundle: ActionBundle):
        self.__storyData = bundle
        self.sigStoryDataChanged.emit()

    def __init__(self, *args, **kwargs):
        logger.debug("Initializing class %s", __class__.__name__)

        # Order of steps is important from here.

        # 1. Load UI file
        super(MainWindowController, self).__init__(*args, **kwargs)
        uic.loadUi("mainWindow.ui",
                   self)  # Loads all widgets of .ui into my instance

        # 2. Init priority properties
        # Set StoryLogHandler which is responsible for logging into a GUI-Widget
        self.storyLogHandler = StoryLogHandler()
        # Thread handling. We later need to access the worker while running,
        # to be able to force stop it. So preserve window lifecycle.
        self.storyWorker = None
        # Keep thread lifecycle, so we don't need to create any new as long as this window lives
        self.workerThread = QThread()

        # Bind GUI elements.
        self.defineConnections()

        # 3. Init general properties which may depend on step 2.
        self.storyData = ActionBundle.createNew()

        # 4. Init UI
        self.initView()

    def initView(self):
        self.setStoryStatementListLayout()
        self.initProgressbar(isVisible=False)
        self.defineComboLogLevel()
        self.btnPlay.setShortcut(QKeySequence("Ctrl+R"))
        self.btnLoadStory.setShortcut(QKeySequence("Ctrl+L"))
        self.btnSaveStory.setShortcut(QKeySequence("Ctrl+S"))

    def defineConnections(self):
        self.storyLogHandler.sigLoggerEmitted.connect(self.addLogText)
        self.sigStoryDataChanged.connect(self.setRunButtonState)
        self.sigStoryDataChanged.connect(self.setStoryTitle)
        self.sigStoryDataChanged.connect(self.setStoryStatementListData)
        self.btnLoadStory.clicked.connect(self.onClickedLoadStory)
        self.btnPlay.clicked.connect(self.onClickedRun)
        self.btnClearLog.clicked.connect(self.onClickedClearLog)
        self.comboLogLevel.currentIndexChanged.connect(
            self.onComboLogLevelChanged)

    def addStoryLogHandlerToLogger(self, level):
        """Add dedicated log handler to some package loggers (hence all its
        children, too) for routing logs into the GUI widget.
        NOTE: We MUST NOT change log levels of the loggers themselves here!

        :param level: The log level
        :return: None
        """
        packagesToChange = (__name__, "storage", "story", "network",
                            "selenium")
        self.storyLogHandler.setLevel(level)

        for package in packagesToChange:
            tmpLogger = clog.getLogger(
                package)  # Side effect: sets new logger if not exists!
            tmpLogger.removeHandler(
                self.storyLogHandler)  # removing only works by reference!
            tmpLogger.addHandler(self.storyLogHandler)

    def setStoryTitle(self):
        self.lblStoryTitle.setText(f"Loaded Story: {self.storyData.name}")

    def setStoryStatementListLayout(self):
        # Set style of story list
        self.storyStatementList.setShowGrid(False)
        self.storyStatementList.setStyleSheet(
            'QTableView::item {border-right: 1px solid #d6d9dc;}')
        # Set the story headers
        headerLabels = [
            "Action", "Property", "Value", "Search Data", "Search Strategy",
            "Result"
        ]
        self.storyStatementList.setColumnCount(len(headerLabels))
        self.storyStatementList.setHorizontalHeaderLabels(headerLabels)

        # Fonts
        headerFont = QFont()
        headerFont.setPointSize(15)
        self.storyStatementList.horizontalHeader().setFont(headerFont)
        self.storyStatementList.verticalHeader().setFont(headerFont)
        # Fit horizontal sizes
        headerH: QHeaderView = self.storyStatementList.horizontalHeader()
        headerH.setSectionResizeMode(0, QHeaderView.ResizeToContents)
        headerH.setSectionResizeMode(1, QHeaderView.ResizeToContents)
        headerH.setSectionResizeMode(2, QHeaderView.Interactive)
        headerH.setSectionResizeMode(3, QHeaderView.Interactive)
        headerH.setSectionResizeMode(4, QHeaderView.ResizeToContents)
        headerH.setSectionResizeMode(5, QHeaderView.Stretch)

    def setStoryStatementListData(self):
        numberOfStatements = len(self.storyData.actions)
        self.storyStatementList.setRowCount(numberOfStatements)
        # Fill
        for row, statement in enumerate(self.storyData.actions):
            # Set cells
            # col id 0
            content = QTableWidgetItem(statement.command.readable)
            content.setTextAlignment(QtCore.Qt.AlignLeft
                                     | QtCore.Qt.AlignVCenter)
            self.storyStatementList.setItem(row, 0, content)
            # col id 1, 2
            content = ("", "")
            if isinstance(statement, TextSendable):
                content = (ActionKey.TEXT_TO_SEND.readable,
                           statement.textToSend)
            elif isinstance(statement, Waitable):
                content = (ActionKey.MAX_WAIT.readable, str(statement.maxWait))
            self.storyStatementList.setItem(row, 1,
                                            QTableWidgetItem(content[0]))
            self.storyStatementList.setItem(row, 2,
                                            QTableWidgetItem(content[1]))
            # col id 3, 4
            content = ("", "")
            if isinstance(statement, Searchable):
                content = (statement.searchConditions.identifier,
                           statement.searchConditions.searchStrategy.readable)
            self.storyStatementList.setItem(row, 3,
                                            QTableWidgetItem(content[0]))
            self.storyStatementList.setItem(row, 4,
                                            QTableWidgetItem(content[1]))
            # col id 5 is set by setStoryStatementIndicatorsToDefault()
            self.setStoryStatementIndicatorsToDefault()

    def setStoryStatementIndicatorsToDefault(self):
        for row, statement in enumerate(self.storyData.actions):
            # Vertical headers
            self.setStoryStatementVerticalHeader(row=row)
            # Results
            self.setStoryStatementResult(row=row, text="")

    def setStoryStatementVerticalHeader(self,
                                        row: int,
                                        rgb: (int, int, int) = None):
        if row < 0:
            return
        counterStr = str(row + 1)
        headerItem = QTableWidgetItem(counterStr)
        if rgb:
            r, g, b = rgb[0], rgb[1], rgb[2]
            brush = QBrush(QColor(r, g, b))
            headerItem.setBackground(brush)
        self.storyStatementList.setVerticalHeaderItem(row, headerItem)

    def setStoryStatementResult(self, row, text: str):
        if row < 0:
            return
        COLUMN = 5
        content = QTableWidgetItem(text)
        content.setTextAlignment(QtCore.Qt.AlignCenter)
        content.setForeground(QBrush(QColor(94, 94, 94)))
        self.storyStatementList.setItem(row, COLUMN, content)

    def defineComboLogLevel(self):
        self.comboLogLevel.disconnect(
        )  # Avoid multiple triggers while setting up

        self.comboLogLevel.clear()
        self.comboLogLevel.addItem("Debug", clog.DEBUG)
        self.comboLogLevel.addItem("Info", clog.INFO)
        self.comboLogLevel.addItem("Warning", clog.WARN)
        self.comboLogLevel.addItem("Error", clog.ERROR)
        self.comboLogLevel.addItem("Critical", clog.CRITICAL)

        self.comboLogLevel.currentIndexChanged.connect(
            self.onComboLogLevelChanged)
        self.comboLogLevel.setCurrentIndex(1)

    def addLogText(self, logText):
        self.logList.appendPlainText(logText)
        self.logList.moveCursor(QtGui.QTextCursor.End)  # implies scrolling

    def initProgressbar(self,
                        isVisible: bool,
                        maximum: int = 100,
                        showText: bool = False):
        if isVisible:
            self.progressBar.setMaximum(maximum)
            self.progressBar.show()
        else:
            self.progressBar.hide()
            self.setProgressbar(0)
        self.progressBar.setTextVisible(showText)

    def setProgressbar(self, val):
        self.progressBar.setValue(val)

    def setRunButtonState(self):
        savedShortcut = self.btnPlay.shortcut()  # We must restore that below
        isEnabled = len(self.storyData.actions) > 0
        self.btnPlay.setEnabled(isEnabled)
        if self.workerThread.isRunning():
            self.btnPlay.setText("Stop")
        else:
            self.btnPlay.setText("Run")
        # Documentation says: Shortcut gets deleted after setText is called. Restore:
        self.btnPlay.setShortcut(savedShortcut)

    def runStory(self):
        actionCount = len(self.storyData.actions)
        if actionCount == 0:
            return
        self.initProgressbar(isVisible=True,
                             maximum=actionCount,
                             showText=True)
        # Setup storyWorker & thread
        self.storyWorker = StoryWorker(self.storyData, doHeadless=True)
        self.workerThread.started.connect(self.storyWorker.work)
        self.storyWorker.sigStarted.connect(self.setRunButtonState)
        self.storyWorker.sigStarted.connect(
            self.setStoryStatementIndicatorsToDefault)
        self.storyWorker.sigCurrentAction.connect(self.onStoryRunNextStatement)
        self.storyWorker.sigCompleted.connect(self.onStoryRunCompleted)
        # Only delete worker object inside the thread after it's been finished work.
        # Notice: We do NOT mark the workerThread for deletion as we will reuse it.
        self.storyWorker.sigCompleted.connect(self.storyWorker.deleteLater)
        # Move worker to thread and start working:
        self.storyWorker.moveToThread(
            self.workerThread)  # Move Worker object to Thread object
        # Start work
        self.workerThread.start()
        self.workerThread.setPriority(QThread.HighPriority)

    def openFileDialog(self, dirPath: pl.Path) -> pl.Path:
        dirStr = ""
        if dirPath is not None and dirPath.is_dir():
            dirStr = str(dirPath)
        # Setup and show dialog
        dlg = QFileDialog()
        options = dlg.HideNameFilterDetails
        pathStr, what = dlg.getOpenFileName(
            parent=self,
            caption="Open a story",
            directory=dirStr,
            filter="Story Files (*.json);; All Files (*)",
            options=options)
        # Transform path string of selected file
        if pathStr:
            return pl.Path(pathStr)

    # ---------------------------------------------------------------------------------
    # Callbacks
    # ---------------------------------------------------------------------------------

    def onClickedLoadStory(self, _, dao=JSONActionBundleDao()):
        path = self.openFileDialog(dao.connection.path.parent)
        if path:
            try:
                dao.connection.path = path
                with dao as connectedDao:
                    self.storyData = connectedDao.loadAll()

            except Exception as e:
                logger.error("Failed to load story. %s", e, exc_info=True)

    def onClickedRun(self, _):
        if self.workerThread.isRunning():
            logger.info("User stopped story execution.")
            self.storyWorker.stop()
        else:
            self.runStory()

    def onClickedClearLog(self, _):
        self.logList.clear()

    def onComboLogLevelChanged(self):
        currentLevel = self.comboLogLevel.currentData()
        self.addStoryLogHandlerToLogger(currentLevel)
        logger.debug("Log level changed - new level: %s", currentLevel)

    def onStoryRunNextStatement(self, actionProgress: int):
        row = actionProgress - 1
        self.setStoryStatementResult(row, text="ok")
        self.setStoryStatementVerticalHeader(row,
                                             rgb=(26, 173, 102))  # mark green
        self.setProgressbar(actionProgress)

    def onStoryRunCompleted(self):
        if not self.workerThread.isRunning():
            return
        # Stop storyWorker's thread and do cleanups
        logger.debug(
            "Work completed. Waiting for storyWorker thread to quit...")
        self.workerThread.quit()
        self.workerThread.wait()
        if self.workerThread.isFinished():
            self.initProgressbar(isVisible=False)
            self.setRunButtonState()
            logger.debug("Worker thread did quit.\n")
class DAQ_2DViewer_FLIM(DAQ_1DViewer_TH260):
    """
        ==================== ==================
        **Atrributes**        **Type**
        *params*              dictionnary list
        *hardware_averaging*  boolean
        *x_axis*              1D numpy array      
        *ind_data*            int
        ==================== ==================

        See Also
        --------

        utility_classes.DAQ_Viewer_base
    """
    params = DAQ_1DViewer_TH260.params + stage_params
    stop_scanner = pyqtSignal()
    start_tttr_scan = pyqtSignal()

    def __init__(self, parent=None, params_state=None):

        super(DAQ_2DViewer_FLIM, self).__init__(parent, params_state) #initialize base class with commom attributes and methods
        self.settings.child('acquisition', 'acq_type').setOpts(limits=['Counting', 'Histo', 'T3', 'FLIM'])
        self.settings.child('acquisition', 'acq_type').setValue('Histo')

        self.stage = None
        self.scan_parameters = None
        self.x_axis = None
        self.y_axis = None
        self.Nx = 1
        self.Ny = 1
        self.signal_axis = None

    def commit_settings(self, param):

        if param.name() not in custom_tree.iter_children(self.settings.child(('stage_settings'))):
            super(DAQ_2DViewer_FLIM, self).commit_settings(param)

        else:
            if param.name() == 'time_interval':
                self.stage._get_read()
                self.stage.set_time_interval(Time(param.value(),
                                                  unit='m'))  # set time interval between pixels

            elif param.name() == 'show_navigator':
                self.emit_status(ThreadCommand('show_navigator'))
                param.setValue(False)

    def emit_data(self):
        """
        """
        try:
            mode = self.settings.child('acquisition', 'acq_type').value()
            if mode == 'Counting' or mode == 'Histo' or mode == 'T3':
                super(DAQ_2DViewer_FLIM, self).emit_data()

            elif mode == 'FLIM':
                self.stop_scanner.emit()
                self.data_grabed_signal.emit([OrderedDict(name='TH260', data=self.datas, type='DataND', nav_axes=(0, 1),
                                                          nav_x_axis=self.get_nav_xaxis(),
                                                          nav_y_axis=self.get_nav_yaxis(),
                                                          xaxis=self.get_xaxis())])
                self.stop()

        except Exception as e:
            self.emit_status(ThreadCommand('Update_Status', [getLineInfo() + str(e), 'log']))

    def emit_data_tmp(self):
        """
        """
        try:
            mode = self.settings.child('acquisition', 'acq_type').value()
            if mode == 'Counting' or mode == 'Histo' or mode == 'T3':
                super(DAQ_2DViewer_FLIM, self).emit_data_tmp()

            elif mode == 'FLIM':

                self.data_grabed_signal_temp.emit([OrderedDict(name='TH260', data=self.datas, type='DataND', nav_axes=(0, 1),
                                                          nav_x_axis=self.get_nav_xaxis(),
                                                          nav_y_axis=self.get_nav_yaxis(),
                                                          xaxis=self.get_xaxis())])

        except Exception as e:
            self.emit_status(ThreadCommand('Update_Status', [getLineInfo() + str(e), 'log']))


    def process_histo_from_h5(self, Nx=1, Ny=1, channel=0, marker=65):
        mode = self.settings.child('acquisition', 'acq_type').value()
        if mode == 'Counting' or mode == 'Histo' or mode == 'T3':
            datas = super(DAQ_2DViewer_FLIM, self).process_histo_from_h5(Nx, Ny, channel)
            return datas
        else:
            markers_array = self.h5file.get_node('/markers')
            ind_lines = np.squeeze(np.where(self.h5file.get_node('/markers')[self.ind_reading:] == marker))
            if ind_lines.size == 0:
                return np.zeros_like(self.datas)  # basically do nothing
            else:
                ind_last_line = ind_lines[-1]
            print(self.ind_reading, ind_last_line)
            markers = markers_array[self.ind_reading:self.ind_reading+ind_last_line]
            nanotimes = self.h5file.get_node('/nanotimes')[self.ind_reading:self.ind_reading+ind_last_line]

            nbins = self.settings.child('acquisition', 'timings', 'nbins').value()
            datas = extract_TTTR_histo_every_pixels(nanotimes, markers, marker=marker, Nx=Nx, Ny=Ny,
                                            Ntime=nbins, ind_line_offset=self.ind_reading, channel=channel)
            self.ind_reading = ind_last_line
            return datas

    def ini_detector(self, controller=None):
        """
            See Also
            --------
            DAQ_utils.ThreadCommand, hardware1D.DAQ_1DViewer_Picoscope.update_pico_settings
        """
        self.status.update(edict(initialized=False, info="", x_axis=None, y_axis=None, controller=None))
        try:
            self.status = super(DAQ_2DViewer_FLIM, self).ini_detector(controller)

            self.ini_stage()
            self.status.x_axis = self.x_axis
            self.status.initialized = True
            self.status.controller = self.controller

            return self.status

        except Exception as e:
            self.status.info = getLineInfo() + str(e)
            self.status.initialized = False
            return self.status

    @pyqtSlot(ScanParameters)
    def update_scanner(self, scan_parameters):
        self.scan_parameters = scan_parameters
        self.x_axis = self.scan_parameters.axis_2D_1
        self.Nx = self.x_axis.size
        self.y_axis = self.scan_parameters.axis_2D_2
        self.Ny = self.y_axis.size




    def get_nav_xaxis(self):
        """
        """
        return self.scan_parameters.axis_2D_1

    def get_nav_yaxis(self):
        """
        """
        return self.scan_parameters.axis_2D_2

    def ini_stage(self):
        if self.settings.child('stage_settings', 'stage_type').value() == 'PiezoConcept':
            self.stage = PiezoConcept()
            self.controller.stage = self.stage
            self.stage.init_communication(self.settings.child('stage_settings', 'com_port').value())

            controller_id = self.stage.get_controller_infos()
            self.settings.child('stage_settings', 'controller_id').setValue(controller_id)
            self.stage.set_time_interval(Time(self.settings.child('stage_settings', 'time_interval').value(), unit='m'))  #set time interval between pixels
            #set the TTL outputs for each displacement of first axis
            self.stage.set_TTL_state(1, self.settings.child('stage_settings', 'stage_x', 'stage_x_axis').value(),
                                     'output', dict(type='start'))

            self.move_abs(0, 'X')
            self.move_abs(0, 'Y')

    @pyqtSlot(float, float)
    def move_at_navigator(self, posx, posy):
        self.move_abs(posx, 'X')
        self.move_abs(posy, 'Y')

    def move_abs(self, position, axis='X'):
        offset = self.settings.child('stage_settings', 'stage_x', 'offset_x').value()
        if self.settings.child('stage_settings', 'stage_x', 'stage_x_direction').value() == 'Normal':
            posi = position + offset
        else:
            posi = -position + offset
        if axis == 'X':
            ax = self.settings.child('stage_settings', 'stage_x', 'stage_x_axis').value()
        else:
            ax = self.settings.child('stage_settings', 'stage_y', 'stage_y_axis').value()
        pos = Position(ax, int(posi * 1000), unit='n')
        out = self.stage.move_axis('ABS', pos)

    def close(self):
        """

        """
        super(DAQ_2DViewer_FLIM, self).close()
        self.stage.close_communication()

    def set_acq_mode(self, mode='FLIM', update=False):
        """
        herited method

        Change the acquisition mode (histogram for mode=='Counting' and 'Histo' or T3 for mode == 'FLIM')
        Parameters
        ----------
        mode

        Returns
        -------

        """


        # check enabled channels
        labels = [k for k in self.channels_enabled.keys() if self.channels_enabled[k]['enabled']]
        N = len(labels)

        if mode != self.actual_mode or update:
            if mode == 'Counting' or mode == 'Histo' or mode == 'T3':
                super(DAQ_2DViewer_FLIM, self).set_acq_mode(mode, update)

            elif mode == 'FLIM':

                self.emit_status(ThreadCommand('show_scanner'))
                QtWidgets.QApplication.processEvents()
                self.emit_status(ThreadCommand('show_navigator'))

                self.controller.TH260_Initialize(self.device, mode=3)  # mode T3
                self.controller.TH260_SetMarkerEnable(self.device, 1)
                self.datas = np.zeros((10, 10, 1024))
                self.data_grabed_signal_temp.emit([OrderedDict(name='TH260', data=self.datas, nav_axes=[0, 1], type='DataND')])

                self.data_pointers = self.datas.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32))
                self.actual_mode = mode

            self.actual_mode = mode

    def grab_data(self, Naverage=1, **kwargs):
        """
            Start new acquisition in two steps :
                * Initialize data: self.datas for the memory to store new data and self.data_average to store the average data
                * Start acquisition with the given exposure in ms, in "1d" or "2d" mode

            =============== =========== =============================
            **Parameters**   **Type**    **Description**
            Naverage         int         Number of images to average
            =============== =========== =============================

            See Also
            --------
            DAQ_utils.ThreadCommand
        """
        try:
            self.acq_done = False
            mode = self.settings.child('acquisition', 'acq_type').value()
            if mode == 'Counting' or mode == 'Histo' or mode == 'T3':
                super(DAQ_2DViewer_FLIM, self).grab_data(Naverage, **kwargs)

            elif mode == 'FLIM':
                self.ind_reading = 0
                self.do_process_tttr = False

                self.init_h5file()
                self.datas = np.zeros((self.Nx, self.Ny, self.settings.child('acquisition', 'timings', 'nbins').value(),),
                                       dtype=np.float64)
                self.init_histo_group()

                time_acq = int(self.settings.child('acquisition', 'acq_time').value() * 1000)  # in ms
                self.general_timer.stop()

                self.prepare_moves()

                # prepare asynchronous tttr time event reading
                t3_reader = T3Reader_scan(self.device, self.controller, time_acq, self.stage, self.Nchannels)
                self.detector_thread = QThread()
                t3_reader.moveToThread(self.detector_thread)
                t3_reader.data_signal[dict].connect(self.populate_h5)
                self.stop_tttr.connect(t3_reader.stop_TTTR)
                self.start_tttr_scan.connect(t3_reader.start_TTTR)
                self.detector_thread.t3_reader = t3_reader
                self.detector_thread.start()
                self.detector_thread.setPriority(QThread.HighestPriority)



                #start acquisition and scanner
                self.time_t3 = time.perf_counter()
                self.time_t3_rate = time.perf_counter()

                self.start_tttr_scan.emit()



        except Exception as e:
            self.emit_status(ThreadCommand('Update_Status', [getLineInfo() + str(e), "log"]))

    def init_histo_group(self):

        mode = self.settings.child('acquisition', 'acq_type').value()
        if mode == 'Counting' or mode == 'Histo' or mode == 'T3':
            super(DAQ_2DViewer_FLIM, self).init_histo_group()
        else:

            histo_group = self.h5file.create_group(self.h5file.root, 'histograms')
            self.histo_array = self.h5file.create_carray(histo_group, 'histogram', obj=self.datas,
                                                         title='histogram')
            x_axis = self.get_xaxis()
            xarray = self.h5file.create_carray(histo_group, "x_axis", obj=x_axis['data'], title='x_axis')
            xarray.attrs['shape'] = xarray.shape
            xarray.attrs['type'] = 'signal_axis'
            xarray.attrs['data_type'] = '1D'

            navxarray = self.h5file.create_carray(self.h5file.root, 'scan_x_axis_unique', obj=self.get_nav_xaxis(),
                                                  title='data')
            navxarray.set_attr('shape', navxarray.shape)
            navxarray.attrs['type'] = 'navigation_axis'
            navxarray.attrs['data_type'] = '1D'

            navyarray = self.h5file.create_carray(self.h5file.root, 'scan_y_axis_unique', obj=self.get_nav_yaxis(),
                                                  title='data')
            navyarray.set_attr('shape', navyarray.shape)
            navyarray.attrs['type'] = 'navigation_axis'
            navyarray.attrs['data_type'] = '1D'

            self.histo_array._v_attrs['data_type'] = '1D'
            self.histo_array._v_attrs['type'] = 'histo_data'
            self.histo_array._v_attrs['shape'] = self.datas.shape
            self.histo_array._v_attrs['scan_type'] = 'Scan2D'

    def prepare_moves(self):
        """
        prepare given actuators with positions from scan_parameters
        Returns
        -------

        """
        self.x_axis = self.scan_parameters.axis_2D_1
        self.Nx = self.x_axis.size
        self.y_axis = self.scan_parameters.axis_2D_2
        self.Ny = self.y_axis.size
        positions_real = self.transform_scan_coord(self.scan_parameters.positions)
        # interval_time = self.settings.child('stage_settings', 'time_interval').value()/1000
        # total_time = self.settings.child('acquisition', 'acq_time').value()
        # Ncycles = int(total_time/(interval_time*len(positions_real)))
        # total_time = Ncycles * interval_time*len(positions_real)
        # self.settings.child('acquisition', 'acq_time').setValue(total_time)
        # positions = []
        # for ind in range(Ncycles):
        #     positions.extend(positions_real)

        self.stage.set_positions_arbitrary(positions_real)

        self.move_at_navigator(*self.scan_parameters.positions[0][0:2])

    def transform_scan_coord(self, positions):

        offset = self.settings.child('stage_settings', 'stage_x', 'offset_x').value()
        if self.settings.child('stage_settings', 'stage_x', 'stage_x_direction').value() == 'Normal':
            scaling_x = 1
        else:
            scaling_x = -1

        if self.settings.child('stage_settings', 'stage_y', 'stage_y_direction').value() == 'Normal':
            scaling_y = 1
        else:
            scaling_y = -1
        if self.settings.child('stage_settings', 'stage_x', 'stage_x_axis').value() == 'X':
            ind_x = 0
        else:
            ind_x = 1

        if self.settings.child('stage_settings', 'stage_y', 'stage_y_axis').value() == 'Y':
            ind_y = 1
        else:
            ind_y = 0

        positions_out = []
        for pos in positions:
            pos_tmp = [(scaling_x*pos[ind_x]+offset)*1000, (scaling_y*pos[ind_y]+offset)*1000]
            positions_out.append(pos_tmp)

        return positions_out


    def stop(self):
        super(DAQ_2DViewer_FLIM, self).stop()
        self.stop_scanner.emit()
        try:
            self.move_at_navigator(*self.scan_parameters.positions[0][0:2])
        except:
            pass