Source code for gcode_reader.emulate.statistics

import re
import pandas as pd
import numpy as np
from typing import Iterable

from ..syntax import flavors, ABS_POSITION

from .tag_literals import MOVE_EXTRUDE
from ._dataframe import (
    _check_loc_dist_time_gcode_dataframe,
)


[docs] def gcode_statistics( gcode: pd.DataFrame, flavor: dict = flavors["default"], words: Iterable = None, ) -> dict: """Quick statistics for any G-code DataFrame. For AM-specific stats (layer count, deposition volume, etc.), use ``AdditivePart`` instead. Args: gcode (pd.DataFrame): Cleaned and tagged G-code DataFrame. flavor (dict, optional): Syntax definition for G-code. Defaults to flavors["default"]. words (Iterable, optional): Words (e.g. ``"G1"``, ``("G", 1)``) to count occurrences of. Defaults to ``[]``. Returns: dict: Statistics including line/word/command counts, positioning mode, units, bounding box, and elapsed time. """ # Cleans the gcode by inserting location and distance columns if missing # Forward fills missing values gcode = _check_loc_dist_time_gcode_dataframe(gcode, flavor, True) n_rows = gcode.shape[0] # We can estimate the number of words as shape[0] * word_map present in the dataframe column list word_map = [] for value in flavor["word_map"].values(): if isinstance(value, list): word_map.extend(value) else: word_map.append(value) word_map = set(word_map).intersection(gcode.columns) # # Count the number of G and M commands with values to estimate the number of commands # cmd_columns = [] if "G" in gcode.columns: cmd_columns.append("G") if "M" in gcode.columns: cmd_columns.append("M") n_irrelevant_rows = gcode[cmd_columns].isnull().any(axis=1).sum() stats = { "n_lines": n_rows, "n_words": len(word_map) * n_rows, "n_commands": n_rows - n_irrelevant_rows, "n_word_use": _word_appearance_gcode_df(gcode, flavor, words), } # Absolute or relative positioning # TODO: Relative as the default? Probably syntax specific # for now omitting without a G command if gcode[gcode["G"] == 90].shape[0] != 0: stats["positioning"] = "absolute" elif gcode[gcode["G"] == 91].shape[0] != 0: stats["positioning"] = "relative" # # Unit system # TODO: This isn't going to work if the system switches back and forth between units # user will receive whichever unit system was last in flavor["units"]["commands"] # if flavor["units"]["commands"]: for key, val in flavor["units"]["commands"].items(): if key in gcode.columns and (gcode[key] == val).any(): stats["units"] = flavor["units"]["commands"][key] # # Contruct a bounding box around the entire dataset and try bounding the deposition # stats["bounds"] = bounds_of_gcode_df(gcode) # # Time estimate, same as event series # stats["elapsed_time_s"] = gcode["time"].sum() return stats
[docs] def bounds_of_gcode_df(gcode: pd.DataFrame, tag=None) -> tuple | None: """Compute the XYZ bounding box of a G-code DataFrame. For AM-specific bounds, use ``AdditivePart`` instead. Args: gcode (pd.DataFrame): G-code DataFrame with absolute position columns. tag (str, optional): If provided, filter to rows whose ``tags`` string contains this tag before computing bounds. Returns: tuple: ``((x_min, y_min, z_min), (x_max, y_max, z_max))``, or ``None`` if position columns are missing. """ X, Y, Z = ABS_POSITION try: assert all(col in gcode.columns for col in ABS_POSITION) except: print( f"\nFailed to calculate bounds.\n\tColumns {ABS_POSITION} missing from DataFrame with columns {gcode.columns}" ) return if tag: gcode = gcode[gcode["tags"].str.contains(tag)] return ( (gcode[X].min(), gcode[Y].min(), gcode[Z].min()), (gcode[X].max(), gcode[Y].max(), gcode[Z].max()), )
def _word_appearance_gcode_df( gcode: pd.DataFrame, flavor: dict = flavors["default"], words: Iterable = None ) -> dict: """Count occurrences of specific G-code words in a DataFrame. Args: gcode (pd.DataFrame): G-code DataFrame to search. flavor (dict, optional): Syntax definition used to parse word strings. Defaults to flavors["default"]. words (Iterable, optional): Words to count (strings like ``"G1"`` or tuples like ``("G", 1)``). Defaults to ``[]``. Returns: dict: Mapping of word to occurrence count. """ if words is None: words = [] # if a word is a string, we assume that the user only wanted 1 word elif isinstance(words, str): words = [words] # words variable allows us to check number of times that a specific word was used n_word_use = dict() for word in words: n_word_use[word] = 0 # Simple case, where word is whole. i.e. "G1" if isinstance(word, str): split_word = re.findall(flavor["word_re"], word) if split_word: key, val = split_word[0] val = float(val) if key not in gcode.columns: continue n_word_use[word] = gcode[gcode[key] == val].shape[0] # Assuming that the word has been split. i.e. ("G", 1), ["G", 1] # Here we assume that the type of word[1] is correct elif isinstance(word, (tuple, list, np.ndarray)) and len(word) == 2: key, val = word if key not in gcode.columns: continue n_word_use[f"{key}{val}"] = gcode[gcode[key] == val].shape[0] return n_word_use