Example #1
0
    def __init__(self, measurement_file, project, tab_id):
        """Inits measurement.
        
        Args:
            measurement_file: String representing path to measurement file.
            project: Project class object.
            tab_id: Integer representing tab identifier for measurement.
        """
        measurement_folder, measurement_name = os.path.split(measurement_file)
        self.measurement_file = measurement_name
        # With extension
        self.measurement_name = os.path.splitext(measurement_name)[0]

        self.directory = os.path.join(measurement_folder, self.measurement_name)
        self.directory_cuts = os.path.join(self.directory, "cuts")
        self.directory_elemloss = os.path.join(self.directory_cuts, "elemloss")

        self.project = project  # To which project be belong to
        self.data = []
        self.tab_id = tab_id

        self.__make_directories(self.directory)
        self.__make_directories(self.directory_cuts)
        self.__make_directories(self.directory_elemloss)

        self.set_loggers()

        # The settings that come from the project
        self.__project_settings = self.project.settings
        # The settings that are individually set for this measurement
        self.measurement_settings = Settings(self.directory, self.__project_settings)

        # Main window's statusbar TODO: Remove GUI stuff.
        self.statusbar = self.project.statusbar

        element_colors = self.project.global_settings.get_element_colors()
        self.selector = Selector(
            self.directory,
            self.measurement_name,
            self.project.masses,
            element_colors,
            settings=self.measurement_settings,
        )

        # Which color scheme is selected by default
        self.color_scheme = "Default color"
Example #2
0
class Measurement:
    """Measurement class to handle one measurement data.
    """

    def __init__(self, measurement_file, project, tab_id):
        """Inits measurement.
        
        Args:
            measurement_file: String representing path to measurement file.
            project: Project class object.
            tab_id: Integer representing tab identifier for measurement.
        """
        measurement_folder, measurement_name = os.path.split(measurement_file)
        self.measurement_file = measurement_name
        # With extension
        self.measurement_name = os.path.splitext(measurement_name)[0]

        self.directory = os.path.join(measurement_folder, self.measurement_name)
        self.directory_cuts = os.path.join(self.directory, "cuts")
        self.directory_elemloss = os.path.join(self.directory_cuts, "elemloss")

        self.project = project  # To which project be belong to
        self.data = []
        self.tab_id = tab_id

        self.__make_directories(self.directory)
        self.__make_directories(self.directory_cuts)
        self.__make_directories(self.directory_elemloss)

        self.set_loggers()

        # The settings that come from the project
        self.__project_settings = self.project.settings
        # The settings that are individually set for this measurement
        self.measurement_settings = Settings(self.directory, self.__project_settings)

        # Main window's statusbar TODO: Remove GUI stuff.
        self.statusbar = self.project.statusbar

        element_colors = self.project.global_settings.get_element_colors()
        self.selector = Selector(
            self.directory,
            self.measurement_name,
            self.project.masses,
            element_colors,
            settings=self.measurement_settings,
        )

        # Which color scheme is selected by default
        self.color_scheme = "Default color"

    def __make_directories(self, directory):
        if not os.path.exists(directory):
            os.makedirs(directory)
            log = "Created a directory {0}.".format(directory)
            logging.getLogger("project").info(log)

    def load_data(self):
        """Loads measurement data from filepath
        """
        # import cProfile, pstats
        # pr = cProfile.Profile()
        # pr.enable()
        n = 0
        try:
            extension = os.path.splitext(self.measurement_file)[1]
            if extension == ".asc":
                with open("{0}{1}".format(self.directory, extension)) as fp:
                    for line in fp:
                        n += 1  # Event number
                        # TODO: Figure good way to split into columns. REGEX too slow.
                        split = line.split()
                        split_len = len(split)
                        if split_len == 2:  # At least two columns
                            self.data.append([int(split[0]), int(split[1]), n])
                        if split_len == 3:
                            self.data.append([int(split[0]), int(split[1]), int(split[2]), n])
            self.selector.measurement = self
        except IOError as e:
            error_log = "Error while loading the {0} {1}. {2}".format(
                "measurement date for the measurement", self.measurement_name, "The error was:"
            )
            error_log_2 = "I/O error ({0}): {1}".format(e.errno, e.strerror)
            logging.getLogger("project").error(error_log)
            logging.getLogger("project").error(error_log_2)
        except Exception as e:
            error_log = "Unexpected error: [{0}] {1}".format(e.errno, e.strerror)
            logging.getLogger("project").error(error_log)
        # pr.disable()
        # ps = pstats.Stats(pr)
        # ps.sort_stats("time")
        # ps.print_stats(10)

    def set_loggers(self):
        """Sets the loggers for this specified measurement. 
        
        The logs will be displayed in the measurements folder.
        After this, the measurement logger can be called from anywhere of the 
        program, using logging.getLogger([measurement_name]).
        """

        # Initializes the logger for this measurement.
        logger = logging.getLogger(self.measurement_name)
        logger.setLevel(logging.DEBUG)

        # Adds two loghandlers. The other one will be used to log info (and up)
        # messages to a default.log file. The other one will log errors and
        # criticals to the errors.log file.
        self.defaultlog = logging.FileHandler(os.path.join(self.directory, "default.log"))
        self.defaultlog.setLevel(logging.INFO)
        self.errorlog = logging.FileHandler(os.path.join(self.directory, "errors.log"))
        self.errorlog.setLevel(logging.ERROR)

        # Set the formatter which will be used to log messages. Here you can edit
        # the format so it will be deprived to all log messages.
        defaultformat = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S")

        projectlog = logging.FileHandler(os.path.join(self.project.directory, "project.log"))
        projectlogformat = logging.Formatter(
            "%(asctime)s - %(levelname)s - [Measurement : %(name)s] - %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
        )

        # Set the formatters to the logs.
        projectlog.setFormatter(projectlogformat)
        self.defaultlog.setFormatter(defaultformat)
        self.errorlog.setFormatter(defaultformat)

        # Add handlers to this measurement's logger.
        logger.addHandler(self.defaultlog)
        logger.addHandler(self.errorlog)
        logger.addHandler(projectlog)

    def remove_and_close_log(self, log_filehandler):
        """Closes the log file and removes it from the logger.
        
        Args:
            log_filehandler: Log's filehandler.
        """
        logging.getLogger(self.measurement_name).removeHandler(log_filehandler)
        log_filehandler.flush()
        log_filehandler.close()

    def set_axes(self, axes):
        """Set axes information to selector within measurement.
        
        Sets axes information to selector to add selection points. Since 
        previously when creating measurement old selection could not be checked. 
        Now is time to check for it, while data is still "loading".
        
        Args:
            axes: Matplotlib FigureCanvas's subplot
        """
        self.selector.axes = axes
        # We've set axes information, check for old selection.
        self.__check_for_old_selection()

    def __check_for_old_selection(self):
        """Use old selection file_path if exists.
        """
        try:
            selection_file = os.path.join(self.directory, "{0}.sel".format(self.measurement_name))
            with open(selection_file):
                self.load_selection(selection_file)
        except:
            # TODO: Is it necessary to inform user with this?
            log_msg = "There was no old selection file to add to this project."
            logging.getLogger(self.measurement_name).info(log_msg)

    def add_point(self, point, canvas):
        """Add point into selection or create new selection if first or all closed.
        
        Args:
            point: Point (x, y) to be added to selection.
            canvas: matplotlib's FigureCanvas where selections are drawn.
            
        Return:
            1: When point closes open selection and allows new selection to 
                be made.
            0: When point was added to open selection.
            -1: When new selection is not allowed and there are no selections.
        """
        flag = self.selector.add_point(point, canvas)
        if flag >= 0:
            self.selector.update_axes_limits()
        return flag

    def undo_point(self):
        """Undo last point in open selection.
             
        Undo last point in open (last) selection. If there are no selections, 
        do nothing.
        """
        return self.selector.undo_point()

    def purge_selection(self):
        """Purges (removes) all open selections and allows new selection to be made.
        """
        self.selector.purge()

    def remove_all(self):
        """Remove all selections in selector.
        """
        self.selector.remove_all()

    def draw_selection(self):
        """Draw all selections in measurement.
        """
        self.selector.draw()

    def end_open_selection(self, canvas):
        """End last open selection.
        
        Ends last open selection. If selection is open, it will show dialog to 
        select element information and draws into canvas before opening the dialog.
        
        Args:
            canvas: Matplotlib's FigureCanvas

        Return:
            1: If selection closed
            0: Otherwise
        """
        return self.selector.end_open_selection(canvas)

    def selection_select(self, cursorpoint, highlight=True):
        """Select a selection based on point.
        
        Args:
            point: Point (x, y) which is clicked on the graph to select selection.
            highlight: Boolean to determine whether to highlight just this 
                       selection.
            
        Return:
            1: If point is within selection.
            0: If point is not within selection.
        """
        return self.selector.select(cursorpoint, highlight)

    def selection_count(self):
        """Get count of selections.
        
        Return:
            Returns the count of selections in selector object.
        """
        return self.selector.count()

    def reset_select(self):
        """Reset selection to None.
        
        Resets current selection to None and resets colors of all selections
        to their default values. 
        """
        self.selector.reset_select()

    def remove_selected(self):
        """Remove selection
        
        Removes currently selected selection.
        """
        self.selector.remove_selected()

    # TODO: UI stuff here. Something should be in the widgets...?
    def save_cuts(self):
        """Save cut files
        
        Saves data points within selections into cut files.
        """
        if self.selector.is_empty():
            return 0
        if not os.path.exists(self.directory_cuts):
            os.makedirs(self.directory_cuts)

        progress_bar = QtGui.QProgressBar()
        self.statusbar.addWidget(progress_bar, 1)
        progress_bar.show()
        QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
        # Mac requires event processing to show progress bar and its
        # process.

        starttime = time.time()

        self.__remove_old_cut_files()

        # Initializes the list size to match the number of selections.
        points_in_selection = [[] for unused_i in range(self.selector.count())]

        # Go through all points in measurement data
        data_count = len(self.data)
        for n in range(data_count):  # while n < data_count:
            if n % 5000 == 0:
                # Do not always update UI to make it faster.
                progress_bar.setValue((n / data_count) * 90)
                QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
                # Mac requires event processing to show progress bar and its
                # process.
            point = self.data[n]
            # Check if point is within selectors' limits for faster processing.
            if not self.selector.axes_limits.is_inside(point):
                continue

            dirtyinteger = 0  # Lazyway
            for selection in self.selector.selections:
                if selection.point_inside(point):
                    points_in_selection[dirtyinteger].append(point)
                dirtyinteger += 1

        # Save all found data points into appropriate element cut files
        # Firstly clear old cut files so those won't be accidentally
        # left there.

        dirtyinteger = 0  # Increases with for, for each selection
        content_length = len(points_in_selection)
        for points in points_in_selection:
            if points:  # If not empty selection -> save
                selection = self.selector.get_at(dirtyinteger)
                cut_file = CutFile(self.directory)
                cut_file.set_info(selection, points)
                cut_file.save()
            dirtyinteger += 1
            progress_bar.setValue(90 + (dirtyinteger / content_length) * 10)
            QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
            # Mac requires event processing to show progress bar and its
            # process.

        self.statusbar.removeWidget(progress_bar)
        progress_bar.hide()

        log_msg = "Saving finished in {0} seconds.".format(time.time() - starttime)
        logging.getLogger(self.measurement_name).info(log_msg)

    def __remove_old_cut_files(self):
        self.__unlink_files(self.directory_cuts)
        if not os.path.exists(self.directory_elemloss):
            os.makedirs(self.directory_elemloss)
        self.__unlink_files(self.directory_elemloss)

    def __unlink_files(self, directory):
        for the_file in os.listdir(directory):
            file_path = os.path.join(directory, the_file)
            try:
                if os.path.isfile(file_path):
                    os.unlink(file_path)
            except Exception:
                log_msg = "Failed to remove the old cut files."
                logging.getLogger(self.measurement_name).error(log_msg)

    def get_cut_files(self):
        """Get cut files from a measurement.
        
        Return:
            Returns a list of cut files in measurement.
        """
        cuts = [f for f in os.listdir(self.directory_cuts) if os.path.isfile(os.path.join(self.directory_cuts, f))]
        elemloss = [
            f for f in os.listdir(self.directory_elemloss) if os.path.isfile(os.path.join(self.directory_elemloss, f))
        ]
        return cuts, elemloss

    def fill_cuts_treewidget(self, treewidget, use_elemloss=False, checked_files=[]):
        """Fill QTreeWidget with cut files.
        
        Args:
            treewidget: A QtGui.QTreeWidget, where cut files are added to.
            use_elemloss: A boolean representing whether to add elemental losses.
            checked_files: A list of previously checked files.
        """
        treewidget.clear()
        cuts, cuts_elemloss = self.get_cut_files()
        for cut in cuts:
            item = QtGui.QTreeWidgetItem([cut])
            item.directory = self.directory_cuts
            item.file_name = cut
            if not checked_files or item.file_name in checked_files:
                item.setCheckState(0, QtCore.Qt.Checked)
            else:
                item.setCheckState(0, QtCore.Qt.Unchecked)
            treewidget.addTopLevelItem(item)
        if use_elemloss and cuts_elemloss:
            elem_root = QtGui.QTreeWidgetItem(["Elemental Losses"])
            for elemloss in cuts_elemloss:
                item = QtGui.QTreeWidgetItem([elemloss])
                item.directory = self.directory_elemloss
                item.file_name = elemloss
                if item.file_name in checked_files:
                    item.setCheckState(0, QtCore.Qt.Checked)
                else:
                    item.setCheckState(0, QtCore.Qt.Unchecked)
                elem_root.addChild(item)
            treewidget.addTopLevelItem(elem_root)

    def load_selection(self, filename):
        """Load selections from a file_path.
        
        Removes all current selections and loads selections from given filename.
        
        Args:
            filename: String representing (full) directory to selection file_path.
        """
        self.selector.load(filename)

    def generate_tof_in(self):
        """Generate tof.in file for external programs.
        
        Generates tof.in file for measurement to be used in external programs 
        (tof_list, erd_depth).
        """
        tof_in_directory = os.path.join(os.path.realpath(os.path.curdir), "external", "Potku-bin")
        tof_in_file = os.path.join(tof_in_directory, "tof.in")

        # Get settings
        use_settings = self.measurement_settings.get_measurement_settings()
        global_settings = self.project.global_settings

        # Measurement settings
        str_beam = "Beam: {0}\n".format(use_settings.measuring_unit_settings.element)
        str_energy = "Energy: {0}\n".format(use_settings.measuring_unit_settings.energy)
        str_detector = "Detector angle: {0}\n".format(use_settings.measuring_unit_settings.detector_angle)
        str_target = "Target angle: {0}\n".format(use_settings.measuring_unit_settings.target_angle)
        str_toflen = "Toflen: {0}\n".format(use_settings.measuring_unit_settings.time_of_flight_length)
        str_carbon = "Carbon foil thickness: {0}\n".format(use_settings.measuring_unit_settings.carbon_foil_thickness)
        str_density = "Target density: {0}\n".format(use_settings.measuring_unit_settings.target_density)

        # Depth Profile settings
        str_depthnumber = "Number of depth steps: {0}\n".format(
            use_settings.depth_profile_settings.number_of_depth_steps
        )
        str_depthstop = "Depth step for stopping: {0}\n".format(
            use_settings.depth_profile_settings.depth_step_for_stopping
        )
        str_depthout = "Depth step for output: {0}\n".format(use_settings.depth_profile_settings.depth_step_for_output)
        str_depthscale = "Depths for concentration scaling: {0} {1}\n".format(
            use_settings.depth_profile_settings.depths_for_concentration_from,
            use_settings.depth_profile_settings.depths_for_concentration_to,
        )

        # Cross section
        flag_cross = global_settings.get_cross_sections()
        str_cross = "Cross section: {0}\n".format(flag_cross)
        # Cross Sections: 1=Rutherford, 2=L'Ecuyer, 3=Andersen

        str_num_iterations = "Number of iterations: {0}\n".format(global_settings.get_num_iterations())

        # Efficiency directory
        eff_directory = global_settings.get_efficiency_directory()
        str_eff_dir = "Efficiency directory: {0}".format(eff_directory)

        # Combine strings
        measurement = str_beam + str_energy + str_detector + str_target + str_toflen + str_carbon + str_density
        calibration = "TOF calibration: {0} {1}\n".format(
            use_settings.calibration_settings.slope, use_settings.calibration_settings.offset
        )
        anglecalibration = "Angle calibration: {0} {1}\n".format(
            use_settings.calibration_settings.angleslope, use_settings.calibration_settings.angleoffset
        )
        depthprofile = str_depthnumber + str_depthstop + str_depthout + str_depthscale

        tof_in = (
            measurement + calibration + anglecalibration + depthprofile + str_cross + str_num_iterations + str_eff_dir
        )

        # Get md5 of file and new settings
        md5 = hashlib.md5()
        md5.update(tof_in.encode("utf8"))
        digest = md5.digest()
        digest_file = None
        if os.path.isfile(tof_in_file):
            f = open(tof_in_file, "r")
            digest_file = md5_for_file(f)
            f.close()

        # If different back up old tof.in and generate a new one.
        if digest_file != digest:
            # Try to back up old file.
            try:
                new_file = "{0}_{1}.bak".format(tof_in_file, time.strftime("%Y-%m-%d_%H.%M.%S"))
                shutil.copyfile(tof_in_file, new_file)
                back_up_msg = "Backed up old tof.in file to {0}".format(os.path.realpath(new_file))
                logging.getLogger(self.measurement_name).info(back_up_msg)
            except:
                import traceback

                err_file = sys.exc_info()[2].tb_frame.f_code.co_filename
                str_err = ", ".join(
                    [
                        sys.exc_info()[0].__name__ + ": " + traceback._some_str(sys.exc_info()[1]),
                        err_file,
                        str(sys.exc_info()[2].tb_lineno),
                    ]
                )
                error_msg = "Unexpected error when generating tof.in: {0}".format(str_err)
                logging.getLogger(self.measurement_name).error(error_msg)
            # Write new settings to the file.
            with open(tof_in_file, "wt+") as fp:
                fp.write(tof_in)
            str_logmsg = "Generated tof.in with params> {0}".format(tof_in.replace("\n", "; "))
            logging.getLogger(self.measurement_name).info(str_logmsg)