| import os |
| from typing import Any, Literal |
|
|
| from pandas import DataFrame |
|
|
| try: |
| from trackio.media.media import TrackioMedia |
| from trackio.utils import MEDIA_DIR |
| except ImportError: |
| from media.media import TrackioMedia |
| from utils import MEDIA_DIR |
|
|
|
|
| class Table: |
| """ |
| Initializes a Table object. Tables can be used to log tabular data including images, numbers, and text. |
| |
| Args: |
| columns (`list[str]`, *optional*): |
| Names of the columns in the table. Optional if `data` is provided. Not |
| expected if `dataframe` is provided. Currently ignored. |
| data (`list[list[Any]]`, *optional*): |
| 2D row-oriented array of values. Each value can be: a number, a string (treated as Markdown and truncated if too long), |
| or a `Trackio.Image` or list of `Trackio.Image` objects. |
| dataframe (`pandas.`DataFrame``, *optional*): |
| DataFrame object used to create the table. When set, `data` and `columns` |
| arguments are ignored. |
| rows (`list[list[any]]`, *optional*): |
| Currently ignored. |
| optional (`bool` or `list[bool]`, *optional*, defaults to `True`): |
| Currently ignored. |
| allow_mixed_types (`bool`, *optional*, defaults to `False`): |
| Currently ignored. |
| log_mode: (`Literal["IMMUTABLE", "MUTABLE", "INCREMENTAL"]` or `None`, *optional*, defaults to `"IMMUTABLE"`): |
| Currently ignored. |
| """ |
|
|
| TYPE = "trackio.table" |
|
|
| def __init__( |
| self, |
| columns: list[str] | None = None, |
| data: list[list[Any]] | None = None, |
| dataframe: DataFrame | None = None, |
| rows: list[list[Any]] | None = None, |
| optional: bool | list[bool] = True, |
| allow_mixed_types: bool = False, |
| log_mode: Literal["IMMUTABLE", "MUTABLE", "INCREMENTAL"] | None = "IMMUTABLE", |
| ): |
| |
| |
| if dataframe is None: |
| self.data = DataFrame(data) if data is not None else DataFrame() |
| else: |
| self.data = dataframe |
|
|
| def _has_media_objects(self, dataframe: DataFrame) -> bool: |
| """Check if dataframe contains any TrackioMedia objects or lists of TrackioMedia objects.""" |
| for col in dataframe.columns: |
| if dataframe[col].apply(lambda x: isinstance(x, TrackioMedia)).any(): |
| return True |
| if ( |
| dataframe[col] |
| .apply( |
| lambda x: isinstance(x, list) |
| and len(x) > 0 |
| and isinstance(x[0], TrackioMedia) |
| ) |
| .any() |
| ): |
| return True |
| return False |
|
|
| def _process_data(self, project: str, run: str, step: int = 0): |
| """Convert dataframe to dict format, processing any TrackioMedia objects if present.""" |
| df = self.data |
| if not self._has_media_objects(df): |
| return df.to_dict(orient="records") |
|
|
| processed_df = df.copy() |
| for col in processed_df.columns: |
| for idx in processed_df.index: |
| value = processed_df.at[idx, col] |
| if isinstance(value, TrackioMedia): |
| value._save(project, run, step) |
| processed_df.at[idx, col] = value._to_dict() |
| if ( |
| isinstance(value, list) |
| and len(value) > 0 |
| and isinstance(value[0], TrackioMedia) |
| ): |
| [v._save(project, run, step) for v in value] |
| processed_df.at[idx, col] = [v._to_dict() for v in value] |
|
|
| return processed_df.to_dict(orient="records") |
|
|
| @staticmethod |
| def to_display_format(table_data: list[dict]) -> list[dict]: |
| """Convert stored table data to display format for UI rendering. Note |
| that this does not use the self.data attribute, but instead uses the |
| table_data parameter, which is is what the UI receives. |
| |
| Args: |
| table_data: List of dictionaries representing table rows (from stored _value) |
| |
| Returns: |
| Table data with images converted to markdown syntax and long text truncated. |
| """ |
| truncate_length = int(os.getenv("TRACKIO_TABLE_TRUNCATE_LENGTH", "250")) |
|
|
| def convert_image_to_markdown(image_data: dict) -> str: |
| relative_path = image_data.get("file_path", "") |
| caption = image_data.get("caption", "") |
| absolute_path = MEDIA_DIR / relative_path |
| return f'<img src="/gradio_api/file={absolute_path}" alt="{caption}" />' |
|
|
| processed_data = [] |
| for row in table_data: |
| processed_row = {} |
| for key, value in row.items(): |
| if isinstance(value, dict) and value.get("_type") == "trackio.image": |
| processed_row[key] = convert_image_to_markdown(value) |
| elif ( |
| isinstance(value, list) |
| and len(value) > 0 |
| and isinstance(value[0], dict) |
| and value[0].get("_type") == "trackio.image" |
| ): |
| |
| processed_row[key] = ( |
| '<div style="display: flex; gap: 10px;">' |
| + "".join([convert_image_to_markdown(item) for item in value]) |
| + "</div>" |
| ) |
| elif isinstance(value, str) and len(value) > truncate_length: |
| truncated = value[:truncate_length] |
| full_text = value.replace("<", "<").replace(">", ">") |
| processed_row[key] = ( |
| f'<details style="display: inline;">' |
| f'<summary style="display: inline; cursor: pointer;">{truncated}…<span><em>(truncated, click to expand)</em></span></summary>' |
| f'<div style="margin-top: 10px; padding: 10px; background: #f5f5f5; border-radius: 4px; max-height: 400px; overflow: auto;">' |
| f'<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;">{full_text}</pre>' |
| f"</div>" |
| f"</details>" |
| ) |
| else: |
| processed_row[key] = value |
| processed_data.append(processed_row) |
| return processed_data |
|
|
| def _to_dict(self, project: str, run: str, step: int = 0): |
| """Convert table to dictionary representation. |
| |
| Args: |
| project: Project name for saving media files |
| run: Run name for saving media files |
| step: Step number for saving media files |
| """ |
| data = self._process_data(project, run, step) |
| return { |
| "_type": self.TYPE, |
| "_value": data, |
| } |
|
|