Ejemplo n.º 1
0
class PlayerMoveSignal(QObject):
    def __init__(self, parent):
        super(PlayerMoveSignal, self).__init__()
        self.game = parent
        self.sig = Signal(str)
        self.eventLoop = QEventLoop(self)
        self.data = None

    @Slot(str)
    def stop_waiting(self, data):
        self.data = data
        self.eventLoop.exit()
        return

    def wait_for_move(self):
        self.eventLoop.exec_()
        return self.data
class BatchProcessor():
    signals = signal.signalClass()
    GeneralEventLoop = None
    SnapshotEventLoop = None
    SnapshotTaken = False
    is_active = False
    well_positioner = None
    Well_Map = None
    Well_Targets = None
    ID = str
    info = str
    duration = 0
    interleave = 0
    start_time = 0
    end_time = 0
    logging = True

    ## @brief BatchProcessor()::__init__ sets the batch settings
    ## @param well_controller is the well positioning class instance used to position the wells under the camera.
    ## @param well_map is the list of target wells with their positions in mm of both row and column.
    ## @param well_targets is the list of the target wells with their column and row index positions and ID.
    ## @param ID is the batch ID.
    ## @param info is the batch information.
    ## @param dur is the batch duration.
    ## @param interl is the time between the photographing of each well.
    def __init__(self, well_controller, well_map, well_targets, ID, info, path,
                 dur, interl):
        #super().__init__()
        self.is_active = False
        self.well_positioner = well_controller
        self.Well_Map = well_map
        self.Well_Targets = well_targets
        self.batch_id = ID
        self.batch_info = info
        self.duration = dur
        self.interleave = interl
        self.path = os.path.sep.join([path, ID])
        if not os.path.exists(self.path):
            os.makedirs(self.path)

    ## @brief BatchProcessor()::msg emits the message signal. This emit will be catched by the logging slot function in main.py.
    ## @param message is the string message to be emitted.
    @Slot(str)
    def msg(self, message):
        if message:
            self.signals.mes.emit(self.__class__.__name__ + ": " +
                                  str(message))
        return

    ## @brief BatchProcessor()::updateBatchSettings(self, well_data, ID, info, dur, interl) can be called to update the batch settings during runtime.
    ## @todo well_data update in MainWindow.
    ## @depricated, BatchProcessor::updateBatchSettings not in use yet. Function might be usefull when updating the batch.ini via the GUI.
    ## @param well_data is the list of target wells with their positions/indexes.
    ## @param ID is the batch ID.
    ## @param info is the batch information.
    ## @param dur is the batch duration.
    ## @param interl is the time between the photographing of each well.
    def updateBatchSettings(self, well_map, well_targets, ID, info, dur,
                            interl):
        self.is_active = False
        self.Well_Map = well_map
        self.Well_Targets = well_targets
        self.batch_id = ID
        self.batch_info = info
        self.duration = dur
        self.interleave = interl
        return

    ## @brief BatchProcessor()::startBatch(self) starts the batch process by setting the current (start)time and the endtime.
    ## It disables the manual motor control and emits the batch_active signal
    ## @todo call updateBatchSettings function
    @Slot()
    def startBatch(self):
        self.start_time = current_milli_time()
        self.end_time = current_milli_time() + (self.duration * 1000)
        self.well_positioner.reset_current_well()
        self.signals.batch_active.emit()
        self.is_active = True
        self.msg("Batch process initialized and started with:\n\tDuration: " +
                 str(self.duration) + "\n\tInterleave: " +
                 str(self.interleave) + "\n\tStart_time: " +
                 str(self.start_time) + "\n\tEnd_time: " + str(self.end_time))
        print("Batch process initialized and started with:\n\tDuration: " +
              str(self.duration) + "\n\tInterleave: " + str(self.interleave) +
              "\n\tStart_time: " + str(self.start_time) + "\n\tEnd_time: " +
              str(self.end_time))
        self.runBatch()
        return

    ## @brief BatchProcessor()::runBatch(self) runs the batch after it is started.
    ## @note the interleave is actually the interleave + processingtime of the "for target in self.Well_Targets:" loop
    def runBatch(self):
        if self.logging:
            recording_file_name = os.path.sep.join(
                [self.path, 'batch_positioning_results.csv'])
            recording_file = open(recording_file_name, "w")

            # build csv file heading
            record_str = "run_start_time, run_time,"
            for target in self.Well_Targets:
                record_str += ',' + str(
                    self.Well_Map[target[0][1]][1][1]) + ',' + str(
                        self.Well_Map[1][target[0][0]][0])
            recording_file.write(record_str + "\n")
        else:
            recording_file = None

        first_run = True
        while True:
            print("BatchProcessor thread check: " +
                  str(QThread.currentThread()))
            ## Run start time
            run_start_time = current_milli_time()
            actual_postions = []

            # Home first on avery run
            self.well_positioner.stepper_control.homeXY()

            for target in self.Well_Targets:
                if not self.is_active:
                    self.signals.batch_inactive.emit()
                    self.msg("Batch stopped.")
                    print("Batch stopped.")
                    return

                else:
                    row = target[0][0]
                    column = target[0][1]
                    self.msg("Target: " + str(target[0][2]))
                    ##                    print("Target: at (" + str(self.Well_Map[column][1][1]) + ", " + str(self.Well_Map[1][row][0]) +")" + ", first run: " + str(first_run))
                    if (self.well_positioner.goto_well(
                            self.Well_Map[column][1][1],
                            self.Well_Map[1][row][0],
                            first_run)):  ## if found well
                        self.snapshot_request(
                            str(self.batch_id) + "/" + str(target[0][2]))
                        (self.Well_Map[1][row][0], self.Well_Map[column][1][1]
                         ) = self.well_positioner.get_current_well()
                        print("  Target adapted to (" +
                              str(self.Well_Map[column][1][1]) + ", " +
                              str(self.Well_Map[1][row][0]) + ")")
                        actual_postions.append(
                            self.well_positioner.get_current_well())

                    while self.SnapshotTaken is False:
                        if not self.is_active:
                            self.signals.batch_inactive.emit()
                            #self.msg("Batch stopped, breaking out of SnapshotTaken is False while loop.")
                            print(
                                "Batch stopped, breaking out of SnapshotTaken is False while loop."
                            )
                            return
                        print("Waiting in snapshot loop")
                        self.wait_ms(50)
                    self.msg(str(target) + " finished.")
                    print(str(target) + " finished.")

                if self.end_time < current_milli_time():
                    self.msg("batch completed")
                    print("batch completed")
                    self.signals.batch_inactive.emit()
                    self.stopBatch()
                    return
                else:
                    self.msg("Remaining time " +
                             str(self.end_time - current_milli_time()))
                    print("Remaining time " +
                          str(self.end_time - current_milli_time()) + " ms")

            # first run is completed, remove this statemant to adapt to well-position in stead of feedforward
            if actual_postions:
                first_run = False

            run_time = current_milli_time() - run_start_time
            self.msg("Run time: " + str(run_time))
            print("Run time: " + str(run_time) + "\nWaiting for " +
                  str(self.interleave * 1000 - run_time) + " ms")
            if self.interleave * 1000 - run_time < 0:
                self.msg(
                    "To short interleave, please increase the interleave to at least: "
                    + str(run_time))
                print(
                    "To short interleave, please increase the interleave to at least: "
                    + str(run_time))
            else:
                ## Wait for the specified interleave minus the run_time of one run
                self.msg("Waiting for: " +
                         str(self.interleave * 1000 - run_time) + " ms")
                print("Waiting for: " +
                      str(self.interleave * 1000 - run_time) + " ms")
                self.wait_ms(self.interleave * 1000 - run_time)

            if recording_file:
                record_str = str(run_start_time) + ',' + str(run_time)
                for actual_position in actual_postions:
                    record_str += ',' + str(actual_position[0]) + ',' + str(
                        actual_position[1])
                print(record_str)
                recording_file.write(record_str + "\n")

        if recording_file:
            recording_file.close()

        #self.msg("Breaking out batch process")
        print("Finishing batch process")
        return

    ## @brief BatchProcessor()::wait_ms(self, milliseconds) is a delay function.
    ## @param milliseconds is the number of milliseconds to wait.
    def wait_ms(self, milliseconds):
        self.GeneralEventLoop = QEventLoop()
        QTimer.singleShot(milliseconds, self.GeneralEventLoop.exit)
        self.GeneralEventLoop.exec_()
        return

    ## @brief BatchProcessor()::snapshot_request(self, message) emits the self.signals.snapshot_requested signal.
    ## @param message is the pathname of the desired snapshot
    def snapshot_request(self, message):
        self.msg("Requesting snapshot")
        self.signals.snapshot_requested.emit(message)
        self.snapshot_await()
        return

    ## @brief StepperWellPositioning()::snapshot_await(self) runs an eventloop while the new snapshot is not stored in self.image yet.
    def snapshot_await(self):
        self.SnapshotTaken = False
        while self.is_active and not self.SnapshotTaken:
            #self.msg("Waiting for snapshot")
            self.SnapshotEventLoop = QEventLoop()
            QTimer.singleShot(10, self.SnapshotEventLoop.exit)
            self.SnapshotEventLoop.exec_()
        return

    @Slot()
    def snapshot_confirmed(self):
        self.SnapshotTaken = True
        if not (self.SnapshotEventLoop is None):
            self.SnapshotEventLoop.exit()
        return

    ## @brief BatchProcessor()::stopBatch is a slot function which is called when the button stopBatch is pressed on the MainWindow GUI.
    ## It stops the batch process and resets the wait eventloop if running.
    @Slot()
    def stopBatch(self):
        #self.msg("Stopping Batch process")
        print("Stopping Batch process")
        self.signals.batch_inactive.emit()
        self.signals.process_inactive.emit(
        )  ## Used to stop positioning process of function StepperWellPositioning::goto_target
        self.is_active = False
        self.snapshot_confirmed()
        if not (self.GeneralEventLoop is None):
            self.GeneralEventLoop.exit()
            print("Exit BatchProcessor::GeneralEventloop")
        if not (self.SnapshotEventLoop is None):
            self.SnapshotEventLoop.exit()
            print("Exit BatchProcessor::SnapshotEventLoop")
        return

    @Slot()
    def close(self):
        self.stopBatch()
        return
Ejemplo n.º 3
0
class StepperWellPositioning():
    signals = signal.signalClass()
    process_activity = False
    stepper_control = None  ## object StepperControl instance
    current_well_row = None
    current_well_column = None
    Stopped = True
    GeneralEventLoop = None
    SnapshotEventLoop = None
    SnapshotTaken = False
    image = np.ndarray
    image_area = None
    WPE = None
    WPE_target = None
    WPE_targetRadius = None
    Well_Map = None
    diaphragm_diameter = 12.0  ## mm

    ## @brief StepperWellPositioning()::__init__ initialises the stepper objects for X and Y axis and initialises the gcodeSerial to the class member variable.
    ## @param steppers is the StepperControl object representing the X- and Y-axis
    ## @param Well_data contains the target well specified by the user in the batch.ini file
    def __init__(self, steppers, Well_data, record_path):
        self.stepper_control = steppers
        self.Well_Map = Well_data
        log_fine_tuning = False if record_path is None else True
        self.path = record_path
        return

    ## @brief StepperWellPositioning()::msg emits the message signal. This emit will be catched by the logging slot function in main.py.
    ## @param message is the string message to be emitted.
    @Slot(str)
    def msg(self, message):
        if message is not None:
            self.signals.mes.emit(self.__class__.__name__ + ": " +
                                  str(message))
        return

    ## @brief StepperWellPositioning()::reset_current_well(self) sets the current well position (XY) to None
    def reset_current_well(self):
        self.set_current_well(None, None)
        return

    ## @brief StepperWellPositioning()::set_current_well(self) sets the current well position.
    ## @param column is the column or x well coordinate
    ## @param row is the row or y well coordinate
    def set_current_well(self, column, row):
        self.current_well_column = column
        self.current_well_row = row
        return

    ## @brief StepperWellPositioning()::get_current_well(self) is the well position getter.
    ## @return current_well_column (x position in mm from home) and current_well_row (y position in mm from home).
    def get_current_well(self):
        if self.current_well_column == None or self.current_well_row == None:
            self.current_well_column = None
            self.current_well_row = None
        return self.current_well_column, self.current_well_row

    ## @brief StepperWellPositioning()::wait_ms(self, milliseconds) is a delay function.
    ## @param milliseconds is the number of milliseconds to wait.
    def wait_ms(self, milliseconds):
        GeneralEventLoop = QEventLoop()
        QTimer.singleShot(milliseconds, GeneralEventLoop.exit)
        GeneralEventLoop.exec_()
        return

    ## @brief StepperWellPositioning()::goto_well(self, row, column):
    ## @author Robin Meekers
    ## @author Gert van Lagen (ported to new prototype which makes use of the Wrecklab PrintHAT)
    @Slot()
    def goto_well(self, row, column, adapt_to_well=False):
        print("In goto_well function")
        print(" adapt to well is " + str(adapt_to_well))
        ##        self.stepper_control.enableMotors()
        self.signals.process_active.emit()
        self.Stopped = False
        self.stepper_control.setLightPWM(1.0)

        ## If the Well Position Evaluator is not initialized.
        if self.WPE is None:
            ## define the resolution of the received images to be entered into the evaluator.
            self.snapshot_request()
            self.snapshot_await()
            self.WPE_target = (int(self.image.shape[1] / 2),
                               int(self.image.shape[0] / 2))
            self.WPE = imageProcessor.WellPositionEvaluator(
                (self.image.shape[0], self.image.shape[1]))

        self.msg("Current well: " + str(self.get_current_well()))
        current_row, current_column = self.get_current_well()

        ## Position unknown
        if current_row is None or current_column is None:
            ## Need to go to home position first
            self.msg("Current position unknown. Moving to home...")
            self.stepper_control.homeXY()

            ## If homing is succeeded and confirmed by the STM of the Wrecklab PrintHAT
            if self.stepper_control.homing_confirmed:
                self.image = None
                self.snapshot_request()
                self.snapshot_await()
                self.wait_ms(
                    1000)  ## Wait for the snapshot to be stored in self.image

                ## Arrived at home, move to first well.
                ## The exact centre of the image can more easily be determined at the home position,
                ## as there is no distortion in the image by light refracting.
                self.msg("Looking for light source...")
                WPE_Error = self.WPE.evaluate(self.image, (int(
                    self.image.shape[1] / 2), int(self.image.shape[0] / 2)))
                self.msg("Found light-source at: (" + str(WPE_Error[0][0]) +
                         " | " + str(WPE_Error[0][1]) + "). Radius: " +
                         str(WPE_Error[2]))

                self.WPE_targetRadius = WPE_Error[2]
                self.WPE_target = (int(self.image.shape[1] / 2) +
                                   WPE_Error[0][0],
                                   int(self.image.shape[0] / 2) +
                                   WPE_Error[0][1])
                self.msg("WPE_target: " + str(self.WPE_target[0]) + " | " +
                         str(self.WPE_target[1]))

                self.signals.target_located.emit(
                    (self.WPE_target[0], self.WPE_target[1],
                     self.WPE_targetRadius))

                self.WPE.circle_minRadius = int(
                    self.WPE_targetRadius * 0.8
                )  ## Roughly the amount remaining if the target is on the edge between wells.
                self.WPE.circle_maxRadius = int(
                    self.WPE_targetRadius
                )  ## The well can never be larger than the diaphragm of the light source.

                ##                ## Homing succeeded, light source found, lets move to the first target of Well_map
                ##                self.set_current_well(column, row)
                ##                self.stepper_control.moveToWell(column, row)
                self.signals.first_move.emit()
            else:
                return False

        ## position known
        if self.process_activity:

            # JV: column and row is probably X and Y in Robin's functions, also in Gert's functions?
            self.stepper_control.moveToWell(column, row)

            ## Wait for ms depending on moving distance, JV:why?
            if self.current_well_column is None or self.current_well_row is None:
                dist = 60
            else:
                dist = math.sqrt(
                    float(
                        abs(float(column) - float(self.current_well_column)) *
                        abs(float(column) - float(self.current_well_column))) +
                    float(
                        abs(float(row) - float(self.current_well_row)) *
                        abs(float(row) - float(self.current_well_row))))
            self.msg("Moving distance: " + str(dist))
            if dist < 20:
                self.msg("Delay: 1999ms | dist: " + str(dist) + "mm")
                self.wait_ms(1999)
            elif dist < 50:
                self.msg("Delay: 3999ms | dist: " + str(dist) + "mm")
                self.wait_ms(3999)
            elif dist < 70:
                self.msg("Delay: 5999ms | dist: " + str(dist) + "mm")
                self.wait_ms(5999)
            elif dist < 85:
                self.msg("Delay: 8999ms | dist: " + str(dist) + "mm")
                self.wait_ms(8999)
            elif dist > 85:
                self.msg("Delay: 11999ms | dist: " + str(dist) + "mm")
                self.wait_ms(11999)
            else:
                self.msg("Delay: 4999ms | Unknown dist: " + str(dist) + "mm")
                self.wait_ms(4999)

            self.set_current_well(column, row)
            print(
                str(self.WPE_target[0]) + " | " + str(self.WPE_target[1]) +
                " first run " + str(adapt_to_well))

            if adapt_to_well:
                self.goto_target()
            else:
                self.wait_ms(
                    2000)  ## Wait for the snapshot to be stored in self.image
            self.msg("Well positioning succeeded.")
            ## Manual confirmation needed. STM is too fast for the software to catch the last confirmation.
            self.stepper_control.move_confirmed = True

##            if not self.goto_target():
##                self.msg("Well positioning not succeeded.")
##                self.set_current_well(None, None) ## Never reached reference point
##                self.Stopped = True
##                self.signals.process_inactive.emit()
##
##                ## Manual confirmation needed. STM is too fast for the software to catch the last confirmation.
##                self.stepper_control.move_confirmed = True
##                return False
##            else:
##                self.msg("Well positioning succeeded.")
##                ## Manual confirmation needed. STM is too fast for the software to catch the last confirmation.
##                self.stepper_control.move_confirmed = True

        else:
            print(
                "Positioning process inactive, cannot call goto_target positioning controller function."
            )
            self.msg(
                "Positioning process inactive, cannot call goto_target positioning controller function."
            )
        ## Different row?
        if row != self.current_well_row or column != self.current_well_column:
            self.msg("moving from: (" + str(self.current_well_row) + " | " +
                     str(self.current_well_column) + ") to: (" + str(row) +
                     " | " + str(column) + ")")

## why can we not re-enable motors during steps? Because M17 is not implemented by Klipper
##        self.stepper_control.disableMotors()
        self.stepper_control.setLightPWM(0.0)
        return True

    ## @brief StepperWellPositioning()::goto_target(self) evaluates the target circle using class Well_Position_Evaluator, updates the snapshot,
    ## and corrects the position of the well while not aligned with the light source.
    def goto_target(self):
        loops_ = 0
        error_count = 0
        WPE_Error = None
        new_column = 0
        new_row = 0
        error_threshold = 100  #50#120 # higher number means lower error...
        resolution = self.diaphragm_diameter / self.WPE_targetRadius  # [mm/px]

        run_start_time = current_milli_time()
        if self.path is not None:
            column, row = self.get_current_well()
            log_file_name = '_'.join([
                str(run_start_time), 'goto_target',
                str(round(column)),
                str(round(row))
            ])
            recording_file_name = os.path.sep.join([self.path, log_file_name])
            recording_file = open(recording_file_name, "w")
            record_str = "run_time, WPE_target[0], WPE_target[1], WPE_Error[0][0], WPE_Error[0][1]"
            recording_file.write(record_str + "\n")
        else:
            recording_file = None

        ## Do while the well is not aligned with the light source.
        while True:
            if (self.stepper_control.move_confirmed):
                if not self.process_activity:
                    #self.msg("!Returning from alignment controller loop in StepperWellPositioning::goto_target")
                    print(
                        "!Returning from alignment controller loop in StepperWellPositioning::goto_target"
                    )
                    return False
                ## Wait for image to stabilize and request new snapshot
                self.wait_ms(2000)
                self.snapshot_request()

                ## Wait for snapshot to be stored in self.image
                self.wait_ms(1000)

                ## Evaluate current wellposition relative to the light source.
                WPE_Error = self.WPE.evaluate(self.image, self.WPE_target)
                self.msg("WPE target at (" + str(self.WPE_target[0]) + " | " +
                         str(self.WPE_target[1]) + ")")

                ## Area error, surface smaller or equal to 0
                if WPE_Error[1] <= 0:
                    self.msg(
                        "Possibly something wrong with captured frame, retry with another snapshot"
                    )
                    error_count = error_count + 1
                    if (error_count >= 3):
                        self.msg("Error_count = " + str(error_count))
                        return False
                    continue
                else:
                    self.msg("Well found at offset (" + str(WPE_Error[0][0]) +
                             " | " + str(WPE_Error[0][1]) + ")")
                    self.signals.well_located.emit(
                        (self.WPE_target[0] + WPE_Error[0][0],
                         self.WPE_target[1] + WPE_Error[0][1], WPE_Error[2]))

#                     ## Some
#                     error_count = 0
#                     if (abs(WPE_Error[0][0]-30) < (self.image.shape[0] / error_threshold) and abs(WPE_Error[0][1]+30) < (self.image.shape[0] / error_threshold)) or (loops_ >20): ## No more adjustments to make or system is oscilating
#                         if loops_ > 20:loops_
#                             self.msg("More than 20 correction loops, return False")
#                             return False
#                         self.msg("Returning from positioner controller, loops: " + str(loops_))
#                         return True

## Define controller variables for column and row | x and y.

##                new_column = float(WPE_Error[0][0]) / ((loops_+1)*20.0)
##                new_row = float(WPE_Error[0][1]) / ((loops_+1)*15.0)
##                column, row = self.get_current_well()

                run_time = current_milli_time() - run_start_time
                if recording_file is not None:
                    record_str = str(run_time) + ',' + str(
                        self.WPE_target[0]) + ',' + str(
                            self.WPE_target[1]) + ',' + str(
                                WPE_Error[0][0]) + ',' + str(WPE_Error[0][1])
                    recording_file.write(record_str + "\n")

                error = np.sqrt(WPE_Error[0][0]**2 + WPE_Error[0][1]**2)
                threshold = min(self.image.shape[0:1]) / error_threshold
                print(" well placement error: {}, while acceptable error:{}".
                      format(error, threshold))

                if error > threshold:
                    # Decrease stepsize on each iteration for smooth convergence
                    new_column = float(
                        WPE_Error[0][0]) * resolution / (loops_ + 1)
                    new_row = float(
                        WPE_Error[0][1]) * resolution / (loops_ + 1)
                    column, row = self.get_current_well()
                    new_column += column
                    new_row += row
                    new_column = round(new_column, 1)
                    new_row = round(new_row, 1)
                    self.stepper_control.moveToWell(new_column, new_row)
                    self.set_current_well(new_column, new_row)
                else:
                    break
                loops_ += 1
                if loops_ > 5:
                    self.msg("Too many correction loops, giving up")
                    break
            if not self.process_activity:
                #self.msg("Returning from alignment controller loop in StepperWellPositioning::goto_target")
                print(
                    "Returning from alignment controller loop in StepperWellPositioning::goto_target"
                )
                return False

        if recording_file:
            recording_file.close()

        return True

    ## @brief StepperWellPositioning()::snapshot_request(self) emits the snapshot_request signal
    def snapshot_request(self):
        self.signals.snapshot_requested.emit(str((1, 1)))

    ## @brief StepperWellPositioning()::snapshot_awiat(self) runs an eventloop while the new snapshot is not stored in self.image yet.
    def snapshot_await(self):
        self.SnapshotTaken = False
        while not self.Stopped and not self.SnapshotTaken:
            self.SnapshotEventLoop = QEventLoop()
            QTimer.singleShot(10, self.SnapshotEventLoop.exit)
            self.SnapshotEventLoop.exec_()

    ## @brief StepperWellPositioning()::snapshot_confirmed(self, snapshot) exits the event loop of snapshot_await when the new image is arived.
    ## It updates the self.image variable with the new variable and defines the image area and WPE_target.
    @Slot(np.ndarray)
    def snapshot_confirmed(self, snapshot):
        self.image = snapshot
        #         self.WPE_target = (int(self.image.shape[1] / 2), int(self.image.shape[0] / 2))
        self.image_area = int((self.image.shape[0] * self.image.shape[1]))
        self.SnapshotTaken = True
        if not (self.SnapshotEventLoop is None):
            self.SnapshotEventLoop.exit()
        return

    ## @brief StepperWellPositioning()::setProcessInactive(self) disables the positionings process if the signal is emitted.
    @Slot()
    def setProcessInactive(self):
        #self.msg("Disabled positioning process activity")
        print("Disabled positioning process activity")
        self.process_activity = False
        self.Stopped = True
        if not (self.GeneralEventLoop is None):
            self.GeneralEventLoop.exit()
            #self.msg("Exit StepperWellPositioning::GeneralEventloop")
            print("Exit StepperWellPositioning::GeneralEventloop")
        if not (self.SnapshotEventLoop is None):
            self.SnapshotEventLoop.exit()
            #self.msg("Exit StepperWellPositioning::SnapshotEventLoop")
            print("Exit StepperWellPositioning::SnapshotEventLoop")
        return

    ## @brief StepperWellPositioning()::setProcessActive(self) enables the positionings process if the signal is emitted.
    @Slot()
    def setProcessActive(self):
        self.process_activity = True
        self.Stopped = False
        return

    @Slot()
    def close(self):
        self.stepper_control.setLightPWM(0.0)
        self.stepper_control.setFanPWM(0.0)
        self.process_activity = False
        self.Stopped = True
        return