Skip to content

Commit

Permalink
Merge branch 'exec' into 'main'
Browse files Browse the repository at this point in the history
Bundle as executable

See merge request acubesat/thermal/utilities!1
  • Loading branch information
xlxs4 committed Oct 30, 2023
2 parents 0fddf05 + 4f3b202 commit aa80d2e
Show file tree
Hide file tree
Showing 14 changed files with 974 additions and 422 deletions.
14 changes: 14 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true

# 4 space indentation
[*.py]
indent_style = space
indent_size = 4
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: CI
run-name: ${{ github.actor }} is running 🚀
on: [push]

jobs:
ci:
strategy:
fail-fast: false # Don't fail all jobs if a single job fails.
matrix:
python-version: ["3.11"]
poetry-version: ["1.2.2"] # Poetry is used for project/dependency management.
os: [ubuntu-latest, macos-latest, windows-latest]
include: # Where pip stores its cache is OS-dependent.
- pip-cache-path: ~/.cache
os: ubuntu-latest
- pip-cache-path: ~/.cache
os: macos-latest
- pip-cache-path: ~\appdata\local\pip\cache
os: windows-latest
defaults:
run:
shell: bash # For sane consistent scripting throughout.
runs-on: ${{ matrix.os }}
steps:
- name: Check out repository
uses: actions/checkout@v3
- name: Setup Python
id: setup-python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: ${{ matrix.poetry-version }}
virtualenvs-create: true
virtualenvs-in-project: true # Otherwise the venv will be the same across all OSes.
installer-parallel: true
- name: Load cached venv
id: cached-pip-wheels
uses: actions/cache@v3
with:
path: ${{ matrix.pip-cache-path }}
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
run: poetry install --no-interaction --no-root -E build -E format # https://github.com/python-poetry/poetry/issues/1227
- name: Check formatting
run: |
source $VENV
yapf -drp --no-local-style --style "facebook" src/
- name: Build for ${{ matrix.os }}
run: | # https://stackoverflow.com/questions/19456518/error-when-using-sed-with-find-command-on-os-x-invalid-command-code
source $VENV
pyi-makespec src/main.py
if [ "$RUNNER_OS" == "macOS" ]; then
sed -i '' -e '2 r add-files-to-spec' main.spec
sed -i '' -e 's/datas=\[]/datas=added_files/' main.spec
else
sed -i '2 r add-files-to-spec' main.spec
sed -i 's/datas=\[]/datas=added_files/' main.spec
fi
pyinstaller main.spec
- name: Archive binary artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.os }}-bundle
path: dist
12 changes: 8 additions & 4 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ stages:
- build_and_run

build_and_run:
variables:
DEBIAN_FRONTEND: "noninteractive"
stage: build_and_run
script:
- pip install poetry
- poetry --version
- poetry install
- poetry run python src/main.py
- |
apt-get update && apt-get install -y --no-install-recommends freeglut3-dev libxkbcommon-x11-0 libdbus-1-3
pip install poetry
poetry install --no-interaction --no-root -E build -E format # https://github.com/python-poetry/poetry/issues/1227
source $(poetry env info --path)/bin/activate
yapf -drp --no-local-style --style "facebook" src/
3 changes: 3 additions & 0 deletions add-files-to-spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
added_files = [
('src/config.toml', 'src/')
]
1,074 changes: 695 additions & 379 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions poetry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[virtualenvs]
in-project = true
14 changes: 11 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,20 @@ license = "MIT"
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.11"
seaborn = "^0.12.2"
python = ">=3.11,<3.13"
pyside6 = "^6.6.0"

seaborn = "^0.13.0"
matplotlib = "^3.7.2"
pydantic = "^2.3.0"
pyprojroot = "^0.3.0"
pyinstaller = { version = "^6.1.0", optional = true }
yapf = { version = "^0.40.2", optional = true }
toml = { version = "^0.10.2", optional = true }
isort = { version = "^5.12.0", optional = true }

[tool.poetry.extras]
build = ["pyinstaller"]
format = ["yapf", "toml", "isort"]

[build-system]
requires = ["poetry-core"]
Expand Down
45 changes: 25 additions & 20 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
annotated-types==0.5.0 ; python_version >= "3.11" and python_version < "4.0"
contourpy==1.1.0 ; python_version >= "3.11" and python_version < "4.0"
cycler==0.11.0 ; python_version >= "3.11" and python_version < "4.0"
fonttools==4.42.1 ; python_version >= "3.11" and python_version < "4.0"
kiwisolver==1.4.5 ; python_version >= "3.11" and python_version < "4.0"
matplotlib==3.7.2 ; python_version >= "3.11" and python_version < "4.0"
numpy==1.25.2 ; python_version >= "3.11" and python_version < "4.0"
packaging==23.1 ; python_version >= "3.11" and python_version < "4.0"
pandas==2.1.0 ; python_version >= "3.11" and python_version < "4.0"
pillow==10.0.0 ; python_version >= "3.11" and python_version < "4.0"
pydantic-core==2.6.3 ; python_version >= "3.11" and python_version < "4.0"
pydantic==2.3.0 ; python_version >= "3.11" and python_version < "4.0"
pyparsing==3.0.9 ; python_version >= "3.11" and python_version < "4.0"
pyprojroot==0.3.0 ; python_version >= "3.11" and python_version < "4.0"
python-dateutil==2.8.2 ; python_version >= "3.11" and python_version < "4.0"
pytz==2023.3 ; python_version >= "3.11" and python_version < "4.0"
seaborn==0.12.2 ; python_version >= "3.11" and python_version < "4.0"
six==1.16.0 ; python_version >= "3.11" and python_version < "4.0"
typing-extensions==4.7.1 ; python_version >= "3.11" and python_version < "4.0"
tzdata==2023.3 ; python_version >= "3.11" and python_version < "4.0"
annotated-types==0.6.0 ; python_version >= "3.11" and python_version < "3.13"
contourpy==1.1.1 ; python_version >= "3.11" and python_version < "3.13"
cycler==0.12.1 ; python_version >= "3.11" and python_version < "3.13"
fonttools==4.43.1 ; python_version >= "3.11" and python_version < "3.13"
kiwisolver==1.4.5 ; python_version >= "3.11" and python_version < "3.13"
matplotlib==3.8.0 ; python_version >= "3.11" and python_version < "3.13"
numpy==1.26.1 ; python_version >= "3.11" and python_version < "3.13"
packaging==23.2 ; python_version >= "3.11" and python_version < "3.13"
pandas==2.1.2 ; python_version >= "3.11" and python_version < "3.13"
pillow==10.1.0 ; python_version >= "3.11" and python_version < "3.13"
pydantic-core==2.10.1 ; python_version >= "3.11" and python_version < "3.13"
pydantic==2.4.2 ; python_version >= "3.11" and python_version < "3.13"
pyparsing==3.1.1 ; python_version >= "3.11" and python_version < "3.13"
pyside6-addons==6.6.0 ; python_version >= "3.11" and python_version < "3.13"
pyside6-essentials==6.6.0 ; python_version >= "3.11" and python_version < "3.13"
pyside6==6.6.0 ; python_version >= "3.11" and python_version < "3.13"
python-dateutil==2.8.2 ; python_version >= "3.11" and python_version < "3.13"
pytz==2023.3.post1 ; python_version >= "3.11" and python_version < "3.13"
seaborn==0.13.0 ; python_version >= "3.11" and python_version < "3.13"
setuptools-scm==8.0.4 ; python_version >= "3.11" and python_version < "3.13"
setuptools==68.2.2 ; python_version >= "3.11" and python_version < "3.13"
shiboken6==6.6.0 ; python_version >= "3.11" and python_version < "3.13"
six==1.16.0 ; python_version >= "3.11" and python_version < "3.13"
typing-extensions==4.8.0 ; python_version >= "3.11" and python_version < "3.13"
tzdata==2023.3 ; python_version >= "3.11" and python_version < "3.13"
92 changes: 92 additions & 0 deletions src/GUI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import logging
from pathlib import Path

from PySide6.QtCore import QEvent
from PySide6.QtWidgets import (
QFileDialog, QGroupBox, QHBoxLayout, QLabel, QMainWindow, QPushButton,
QVBoxLayout, QWidget
)

from parse import read_csv, read_csv_meta
from plot import lineplot, setup_plots


class UtilsGUI(QMainWindow):
def __init__(self):
super().__init__()
self.csv = None
setup_plots()
self._init_ui()

def _init_ui(self) -> None:
self._create_io_group_box()
self._create_plot_group_box()

self.selected_csv_path = QLabel(self.tr("Selected CSV: "))

main_layout = QVBoxLayout()
main_layout.addWidget(self._io_group_box)
main_layout.addWidget(self._plot_group_box)
self.setLayout(main_layout)

self.setWindowTitle(self.tr("ESATAN Utilities"))

# To have widgets appear.
dummy_widget = QWidget()
dummy_widget.setLayout(main_layout)
self.setCentralWidget(dummy_widget)

def closeEvent(self, event: QEvent) -> None:
logging.info("Application closed nominally.")
super().closeEvent(event)

def _create_io_group_box(self) -> None:
self._io_group_box = QGroupBox(self.tr("IO"))
layout = QHBoxLayout()

browse_button = QPushButton(self.tr("Browse"))
browse_button.clicked.connect(self._browse_csv)

layout.addWidget(browse_button)

self._io_group_box.setLayout(layout)

def _create_plot_group_box(self) -> None:
self._plot_group_box = QGroupBox(self.tr("Plot"))
layout = QHBoxLayout()

plot_button = QPushButton(self.tr("Plot"))
plot_button.clicked.connect(self._plot_csv)

layout.addWidget(plot_button)

self._plot_group_box.setLayout(layout)

def _browse_csv(self) -> None:
dialog = QFileDialog(self)
dialog.setFileMode(QFileDialog.ExistingFile)
dialog.setViewMode(QFileDialog.List)
dialog.setNameFilter(self.tr("CSV (*.csv)"))

if dialog.exec():
csv_filename = dialog.selectedFiles()[0]
csv_filename = Path(csv_filename)

self.selected_csv_path.setText(
self.tr(f"Selected CSV: {csv_filename.name}")
)

meta_row_idx, meta_map = read_csv_meta(csv_filename)
self.meta_map = meta_map
self.csv = read_csv(csv_filename, meta_row_idx)

logging.info(f"Loaded file {csv_filename.name} successfully.")

def _plot_csv(self) -> None:
if self.csv is None:
logging.info("You have to load a CSV file first!")
else:
lineplot(
self.csv, self.meta_map["Data Source"],
self.meta_map["Element"], self.meta_map["Measurement Kind"]
)
7 changes: 5 additions & 2 deletions src/config.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Example
filename = "example.csv"
[window]

[window.dimension]
width = 190
height = 100
2 changes: 1 addition & 1 deletion src/config_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@


class Config(BaseModel):
filename: str
window: dict[str, dict[str, int]]
39 changes: 31 additions & 8 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
import logging
import sys

from PySide6.QtWidgets import QApplication

from GUI import UtilsGUI
from IOutils import read_config
from parse import read_csv, read_csv_meta
from paths import get_path
from plot import lineplot, setup_plots

if __name__ == "__main__":
RELATIVE_PATHS = True
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%d-%b-%y %H:%M:%S'
)
logging.info("Logger initialized.")

RELATIVE_PATHS = False
CONFIG = read_config(get_path("config", RELATIVE_PATHS))
meta_row_idx, meta_map = read_csv_meta(CONFIG.filename)
df = read_csv(CONFIG.filename, meta_row_idx)
setup_plots()
lineplot(
df, meta_map["Data Source"], meta_map["Element"], meta_map["Measurement Kind"]
if not CONFIG:
logging.error("Loading the configuration file failed.")
exit(1)
logging.info("Configuration file loaded successfully.")

WINDOW_CONFIG = CONFIG.window
app = QApplication([])

logging.info("Starting main window.")
window = UtilsGUI()
window.resize(
WINDOW_CONFIG["dimension"]["width"],
WINDOW_CONFIG["dimension"]["height"]
)
window.show()
logging.info("Window rendered successfully.")

sys.exit(app.exec())
14 changes: 11 additions & 3 deletions src/paths.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import sys
from pathlib import Path

from pyprojroot import here

_PATHS = {"config": "src/config.toml"}


def get_path(name: str, relative: bool) -> Path:
return here(_PATHS[name]) if not relative else _PATHS[name]
# https://www.pyinstaller.org/en/stable/CHANGES.html?highlight=_internal#incompatible-changes
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
base_path = Path(sys._MEIPASS)
resource_path = base_path / _PATHS[name]
else:
script_dir = Path(__file__).parent
resource_path = script_dir / _PATHS[name] if relative else Path(
_PATHS[name]
).resolve()
return resource_path
11 changes: 9 additions & 2 deletions src/plot.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from typing import Tuple

import matplotlib

# Backend for interactive plots to work when app is bundled.
# Needs to be before import matplotlib.pyplot
matplotlib.use('qtagg')
import matplotlib.pyplot as plt
import seaborn as sns

Expand All @@ -16,7 +21,7 @@ def lineplot(
element_ids: list[int],
measurement_kinds: list[str],
dims: Tuple[int, int] = (15, 8),
):
) -> None:
same = lambda xs: all(x == xs[0] for x in xs)
same_sources = same(data_sources)
title = f"{data_sources[0]}" if same_sources else f"{', '.join(data_sources)}"
Expand All @@ -26,7 +31,9 @@ def lineplot(
plt.title(title)

dedup_label = lambda s: s.split(".")[0]
for idx, (t_col, val_col) in enumerate(zip(df.columns[::2], df.columns[1::2])):
for idx, (t_col, val_col) in enumerate(
zip(df.columns[::2], df.columns[1::2])
):
if not same_kinds:
plt.figure(figsize=dims)

Expand Down

0 comments on commit aa80d2e

Please sign in to comment.