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')
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 14 14:54:55 2019

@author: flim-users
"""
import time
import numpy as np
from pymodaq_plugins.hardware.piezoconcept.piezoconcept import PiezoConcept, Position, Time
from pymodaq.daq_utils.daq_utils import set_scan_linear
#%%

stage = PiezoConcept()

stage.init_communication('COM6')
print(stage.get_controller_infos())
stage._write_command('CHAIO 1o2s')
stage._get_read()

#%%
stage.set_time_interval(Time(20, 'm'))
stage.get_time_interval()
#%%
stage.get_position('X')
#%%
stage.get_position('Y')
#%%
offset = 100000

start_axis1 = -10000 + offset
start_axis2 = -10000 + offset
Exemple #3
0
from pymodaq_plugins.hardware.piezoconcept.piezoconcept import PiezoConcept, Position, Time
import numpy as np

if __name__ == '__main__':

    controller = PiezoConcept()
    controller.init_communication('COM6')
    print(controller.get_controller_infos())
    controller.get_time_interval()
    xaxis = np.linspace(100000, 120000, 21)
    yaxis = np.linspace(100000, 110000, 11)
    zaxis = np.linspace(0, 0, 11)

    #controller.set_positions_simple(xaxis, yaxis, [])
    #controller.run_simple()
    #controller._get_read()
    xaxis = np.linspace(100000, 120000, 21)
    yaxis = np.linspace(100000, 110000, 21)
    #controller.set_positions_arbitrary([xaxis, yaxis])
    #controller.run_arbitrary()
    #controller._get_read()
    controller.get_TTL_state(1)
    controller._get_read()
    pass
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
    def ini_stage(self, controller=None):
        """
            Initialize the controller and stages (axes) with given parameters.

            =============== =========================================== ===========================================================================================
            **Parameters**   **Type**                                     **Description**

            *controller*     instance of the specific controller object   If defined this hardware will use it and will not initialize its own controller instance
            =============== =========================================== ===========================================================================================

            Returns
            -------
            Easydict
                dictionnary containing keys:
                  * *info* : string displaying various info
                  * *controller*: instance of the controller object in order to control other axes without the need to init the same controller twice
                  * *stage*: instance of the stage (axis or whatever) object
                  * *initialized*: boolean indicating if initialization has been done corretly

            See Also
            --------
            daq_utils.ThreadCommand
        """

        # initialize the stage and its controller status
        # controller is an object that may be passed to other instances of DAQ_Move_Mock in case
        # of one controller controlling multiaxes
        try:
            self.status.update(
                edict(info="", controller=None, initialized=False))

            #check whether this stage is controlled by a multiaxe controller (to be defined for each plugin)

            # if mutliaxes then init the controller here if Master state otherwise use external controller
            if self.settings.child(
                    'multiaxes',
                    'ismultiaxes').value() and self.settings.child(
                        'multiaxes', 'multi_status').value() == "Slave":
                if controller is None:
                    raise Exception(
                        'no controller has been defined externally while this stage is a slave one'
                    )
                else:
                    self.controller = controller
            else:  #Master stage
                try:
                    self.close()
                except:
                    pass
                self.controller = PiezoConcept()
                self.controller.init_communication(
                    self.settings.child(('com_port')).value())

            controller_id = self.controller.get_controller_infos()
            self.settings.child(('controller_id')).setValue(controller_id)

            self.settings.child('bounds', 'is_bounds').setValue(True)
            self.settings.child('bounds', 'min_bound').setValue(self.min_bound)
            self.settings.child('bounds', 'max_bound').setValue(self.max_bound)
            self.settings.child('scaling', 'use_scaling').setValue(True)
            self.settings.child('scaling', 'offset').setValue(self.offset)

            self.move_Abs(0)

            self.settings.child(('epsilon')).setValue(2)
            self.status.info = controller_id
            self.status.controller = self.controller
            self.status.initialized = True
            return self.status

        except Exception as e:
            self.emit_status(
                ThreadCommand('Update_Status',
                              [getLineInfo() + str(e), 'log']))
            self.status.info = getLineInfo() + str(e)
            self.status.initialized = False
            return self.status
class DAQ_Move_PiezoConcept(DAQ_Move_base):
    """
    Plugin to drive piezoconcpet XY (Z) stages. There is a string nonlinear offset between the set position and the read
    position. It seems to bnot be problem in the sens where a given displacement is maintained. But because the read
    position is not "accurate", I've decided to ignore it and just trust the set position. So the return will be always
    strictly equal to the set position. However, if there is more that 10% difference raise a warning
    """

    _controller_units = 'µm'

    #find available COM ports
    import serial.tools.list_ports
    ports = [
        str(port)[0:4] for port in list(serial.tools.list_ports.comports())
    ]
    port = 'COM6' if 'COM6' in ports else ports[0] if len(ports) > 0 else ''
    #if ports==[]:
    #    ports.append('')

    is_multiaxes = True
    stage_names = ['X', 'Y', 'Z']
    min_bound = -95  #*µm
    max_bound = 95  #µm
    offset = 100  #µm

    params = [{
        'title': 'Time interval (ms):',
        'name': 'time_interval',
        'type': 'int',
        'value': 200
    }, {
        'title': 'Controller Info:',
        'name': 'controller_id',
        'type': 'text',
        'value': '',
        'readonly': True
    }, {
        'title': 'COM Port:',
        'name': 'com_port',
        'type': 'list',
        'values': ports,
        'value': port
    }, {
        'title':
        'MultiAxes:',
        'name':
        'multiaxes',
        'type':
        'group',
        'visible':
        is_multiaxes,
        'children': [
            {
                'title': 'is Multiaxes:',
                'name': 'ismultiaxes',
                'type': 'bool',
                'value': is_multiaxes,
                'default': False
            },
            {
                'title': 'Status:',
                'name': 'multi_status',
                'type': 'list',
                'value': 'Master',
                'values': ['Master', 'Slave']
            },
            {
                'title': 'Axis:',
                'name': 'axis',
                'type': 'list',
                'values': stage_names
            },
        ]
    }] + comon_parameters

    def __init__(self, parent=None, params_state=None):
        super(DAQ_Move_PiezoConcept, self).__init__(parent, params_state)

        self.controller = None
        self.settings.child(('epsilon')).setValue(1)

    def ini_stage(self, controller=None):
        """
            Initialize the controller and stages (axes) with given parameters.

            =============== =========================================== ===========================================================================================
            **Parameters**   **Type**                                     **Description**

            *controller*     instance of the specific controller object   If defined this hardware will use it and will not initialize its own controller instance
            =============== =========================================== ===========================================================================================

            Returns
            -------
            Easydict
                dictionnary containing keys:
                  * *info* : string displaying various info
                  * *controller*: instance of the controller object in order to control other axes without the need to init the same controller twice
                  * *stage*: instance of the stage (axis or whatever) object
                  * *initialized*: boolean indicating if initialization has been done corretly

            See Also
            --------
            daq_utils.ThreadCommand
        """

        # initialize the stage and its controller status
        # controller is an object that may be passed to other instances of DAQ_Move_Mock in case
        # of one controller controlling multiaxes
        try:
            self.status.update(
                edict(info="", controller=None, initialized=False))

            #check whether this stage is controlled by a multiaxe controller (to be defined for each plugin)

            # if mutliaxes then init the controller here if Master state otherwise use external controller
            if self.settings.child(
                    'multiaxes',
                    'ismultiaxes').value() and self.settings.child(
                        'multiaxes', 'multi_status').value() == "Slave":
                if controller is None:
                    raise Exception(
                        'no controller has been defined externally while this stage is a slave one'
                    )
                else:
                    self.controller = controller
            else:  #Master stage
                try:
                    self.close()
                except:
                    pass
                self.controller = PiezoConcept()
                self.controller.init_communication(
                    self.settings.child(('com_port')).value())

            controller_id = self.controller.get_controller_infos()
            self.settings.child(('controller_id')).setValue(controller_id)

            self.settings.child('bounds', 'is_bounds').setValue(True)
            self.settings.child('bounds', 'min_bound').setValue(self.min_bound)
            self.settings.child('bounds', 'max_bound').setValue(self.max_bound)
            self.settings.child('scaling', 'use_scaling').setValue(True)
            self.settings.child('scaling', 'offset').setValue(self.offset)

            self.move_Abs(0)

            self.settings.child(('epsilon')).setValue(2)
            self.status.info = controller_id
            self.status.controller = self.controller
            self.status.initialized = True
            return self.status

        except Exception as e:
            self.emit_status(
                ThreadCommand('Update_Status',
                              [getLineInfo() + str(e), 'log']))
            self.status.info = getLineInfo() + str(e)
            self.status.initialized = False
            return self.status

    def close(self):
        """
            close the current instance of Piezo instrument.
        """
        try:
            self.move_Abs(0)
            QThread.msleep(1000)
        except:
            pass

        self.controller.close_communication()
        self.controller = None

    def check_position(self):
        """
            Check the current position from the hardware.

            Returns
            -------
            float
                The position of the hardware.

            See Also
            --------
            DAQ_Move_base.get_position_with_scaling, daq_utils.ThreadCommand
        """
        position = self.controller.get_position(
            self.settings.child('multiaxes', 'axis').value())  #in mm
        pos = position.pos / 1000  # in um
        pos = self.get_position_with_scaling(pos)
        self.current_position = self.target_position  #should be pos but not precise enough conpared to set position
        self.emit_status(
            ThreadCommand('check_position', [self.target_position]))
        #print(pos)
        return self.target_position

    def move_Abs(self, position):
        """

        Parameters
        ----------
        position: (float) target position of the given axis in um (or scaled units)

        Returns
        -------

        """
        position = self.check_bound(
            position
        )  #limits the position within the specified bounds (-100,100)
        self.target_position = position

        #get positions in controller units
        position = self.set_position_with_scaling(position)
        pos = Position(self.settings.child('multiaxes', 'axis').value(),
                       int(position * 1000),
                       unit='n')
        out = self.controller.move_axis('ABS', pos)
        #self.move_is_done = True
        QThread.msleep(50)  #to make sure the closed loop converged
        self.poll_moving()

    def move_Rel(self, position):
        """
            Make the hardware relative move of the Piezo instrument from the given position after thread command signal was received in DAQ_Move_main.

            =============== ========= =======================
            **Parameters**  **Type**   **Description**

            *position*       float     The absolute position
            =============== ========= =======================

            See Also
            --------
            DAQ_Move_base.set_position_with_scaling, DAQ_Move_base.poll_moving

        """
        position = self.check_bound(self.current_position +
                                    position) - self.current_position
        self.target_position = position + self.current_position

        position = self.set_position_relative_with_scaling(position)

        pos = Position(self.settings.child('multiaxes', 'axis').value(),
                       position * 1000,
                       unit='n')  # always use microns for simplicity
        out = self.controller.move_axis('REL', pos)
        QThread.msleep(50)  # to make sure the closed loop converged
        self.poll_moving()

    def move_Home(self):
        """
            Move to the absolute vlue 100 corresponding the default point of the Piezo instrument.

            See Also
            --------
            DAQ_Move_base.move_Abs
        """
        self.move_Abs(100)  #put the axis on the middle position so 100µm

    def stop_motion(self):
        """
        Call the specific move_done function (depending on the hardware).

        See Also
        --------
        move_done
      """
        self.move_done()
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