diff --git a/docs/source/conf.py b/docs/source/conf.py index 461ac80a..f3eefb10 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -61,10 +61,12 @@ def __call__(self, *args, **kwargs): "matplotlib.pyplot", "matplotlib.dates", "matplotlib.figure", - "picamera2", - "picamera2", "jinja2", "seaborn", + "PyQt6", + "gpiod", + "picamera2", + "libcamera", ] # Add the module path to sys.path here. @@ -247,6 +249,4 @@ def __call__(self, *args, **kwargs): autosectionlabel_prefix_document = True -autodoc_mock_imports = ["PyQt5", "PyQt6", "gpiod", "picamera2", "libcamera"] - suppress_warnings = ["docutils"] diff --git a/village/calibration/sound_calibration.py b/village/calibration/sound_calibration.py index 8df3599c..b1138fee 100644 --- a/village/calibration/sound_calibration.py +++ b/village/calibration/sound_calibration.py @@ -60,4 +60,3 @@ def run_in_thread(self, daemon: bool = True) -> None: def close(self) -> None: """Closes the calibration instance and releases resources.""" return - diff --git a/village/classes/abstract_classes.py b/village/classes/abstract_classes.py index afbf3874..4b91f7fd 100644 --- a/village/classes/abstract_classes.py +++ b/village/classes/abstract_classes.py @@ -17,6 +17,7 @@ class PyBpodBase: session (Session | Any): Bpod session object. connected (bool): Connection status. """ + error: str = "Error connecting to the bpod " session: Session | Any = None connected: bool = False @@ -182,6 +183,7 @@ class TelegramBotBase: Attributes: error (str): Error message. """ + error: str = "Error connecting to the telegram_bot " def alarm(self, message: str) -> None: @@ -199,6 +201,7 @@ class ScaleBase: Attributes: error (str): Error message. """ + error: str = "Error connecting to the scale " def tare(self) -> None: @@ -228,6 +231,7 @@ class TempSensorBase: Attributes: error (str): Error message. """ + error: str = "Error connecting to the temp_sensor " def start(self) -> None: @@ -251,6 +255,7 @@ class MotorBase: open_angle (int): Angle for open position. close_angle (int): Angle for close position. """ + error: str = "Error connecting to the motor " open_angle: int = 0 close_angle: int = 0 @@ -271,6 +276,7 @@ class SoundDeviceBase: samplerate (int): Audio sample rate. error (str): Error message. """ + samplerate: int = 44100 error: str = ( "" @@ -353,6 +359,7 @@ class CameraBase: y_position (int): Y coordinate of tracked object. chrono (time_utils.Chrono): Timer utility. """ + area1: list[int] = [] area2: list[int] = [] area3: list[int] = [] @@ -477,6 +484,7 @@ class BehaviorWindowBase(QWidget): Attributes: background_color: The background color. """ + background_color = None def start_drawing(self) -> None: @@ -525,4 +533,3 @@ def get_video_frame(self) -> Optional[QImage]: Optional[QImage]: The current frame as a QImage. """ return None - diff --git a/village/classes/collection.py b/village/classes/collection.py index a8a14a5e..a2b4db6b 100644 --- a/village/classes/collection.py +++ b/village/classes/collection.py @@ -381,4 +381,3 @@ def convert_active(value) -> str: ) return new_df - diff --git a/village/classes/enums.py b/village/classes/enums.py index 5bdc032a..b5b6a61b 100644 --- a/village/classes/enums.py +++ b/village/classes/enums.py @@ -91,6 +91,7 @@ class AreaActive(SuperEnum): class State(SuperEnum): """Enum representing the state of the village system.""" + WAIT = "All subjects are at home, waiting for RFID detection" DETECTION = "Gathering subject data, checking requirements to enter" ACCESS = "Closing door1, opening door2" @@ -226,4 +227,3 @@ class Save(SuperEnum): NO = "NO" ZERO = "ZERO" ERROR = "ERROR" - diff --git a/village/classes/settings_class.py b/village/classes/settings_class.py index 223346e5..029c0605 100644 --- a/village/classes/settings_class.py +++ b/village/classes/settings_class.py @@ -193,12 +193,12 @@ def get(self, key: str) -> Any: val = self.saved_settings.value(key) if val is None: return setting.value - + # If QSettings returns a QVariant/PyQt object/string that is not the value we expect # but contains "QSettings", it's likely a Sphinx build artifact or mock issue. val_str = str(val) if "QSettings" in val_str or "PyQt5" in val_str: - return setting.value + return setting.value str_value = str(val) try: diff --git a/village/classes/subject.py b/village/classes/subject.py index a07a41c5..21d555c4 100644 --- a/village/classes/subject.py +++ b/village/classes/subject.py @@ -82,4 +82,3 @@ def minimum_time_ok(self) -> bool: subject=self.name, ) return False - diff --git a/village/custom_classes/after_session_base.py b/village/custom_classes/after_session_base.py index 374ee801..593624a9 100644 --- a/village/custom_classes/after_session_base.py +++ b/village/custom_classes/after_session_base.py @@ -16,7 +16,7 @@ class AfterSessionBase: """Base class for operations performed after a session ends. - + This class handles data synchronization (backup) to either a hard drive or a remote server based on current settings. """ @@ -62,4 +62,3 @@ def run(self) -> None: if __name__ == "__main__": after_session = AfterSessionBase() after_session.run() - diff --git a/village/custom_classes/camera_trigger_base.py b/village/custom_classes/camera_trigger_base.py index 9a9f6ca1..21c4b54c 100644 --- a/village/custom_classes/camera_trigger_base.py +++ b/village/custom_classes/camera_trigger_base.py @@ -3,21 +3,21 @@ class CameraTriggerBase: """Base class for defining custom camera trigger behavior. - + This class can be overridden to implement specific actions when detection areas are triggered or to monitor subject position. """ - + def __init__(self) -> None: """Initializes the CameraTriggerBase instance.""" self.name = "Camera Trigger" def trigger(self, cam: CameraBase) -> None: """Evaluates camera triggers and performs corresponding actions. - + This method is called to check if any defined areas in the camera view have been triggered (e.g., by the subject entering them). - + Args: cam (CameraBase): The camera instance providing the trigger status. """ @@ -37,4 +37,3 @@ def trigger(self, cam: CameraBase) -> None: cam.write_text("Area 4 triggered") # or you can check the position of the animal manually - diff --git a/village/custom_classes/change_cycle_base.py b/village/custom_classes/change_cycle_base.py index 721c3223..e6e88817 100644 --- a/village/custom_classes/change_cycle_base.py +++ b/village/custom_classes/change_cycle_base.py @@ -15,7 +15,7 @@ class ChangeCycleBase: """Base class for operations performed during the day/night cycle change. - + This class handles cleanup tasks such as deleting old video files and ensuring disk space is managed, potentially backing up data before deletion. """ @@ -64,4 +64,3 @@ def run(self) -> None: if __name__ == "__main__": change_cycle = ChangeCycleBase() change_cycle.run() - diff --git a/village/custom_classes/online_plot_base.py b/village/custom_classes/online_plot_base.py index df9c8b22..6836b066 100644 --- a/village/custom_classes/online_plot_base.py +++ b/village/custom_classes/online_plot_base.py @@ -78,4 +78,3 @@ def update_plot(self, df: pd.DataFrame) -> None: return df.plot(kind="scatter", x="TRIAL_START", y="trial", ax=self.ax) - diff --git a/village/custom_classes/session_plot_base.py b/village/custom_classes/session_plot_base.py index 17b2580a..d79ea62f 100644 --- a/village/custom_classes/session_plot_base.py +++ b/village/custom_classes/session_plot_base.py @@ -35,4 +35,3 @@ def create_plot( df.plot(kind="line", x="TRIAL_START", y="trial", ax=ax) ax.scatter(df["TRIAL_START"], df["trial"], color="red") return fig - diff --git a/village/custom_classes/subject_plot_base.py b/village/custom_classes/subject_plot_base.py index 60bb3c15..5697451d 100644 --- a/village/custom_classes/subject_plot_base.py +++ b/village/custom_classes/subject_plot_base.py @@ -36,4 +36,3 @@ def create_plot( ax.set_title("Subject Plot") ax.set_ylabel("Number of trials") return fig - diff --git a/village/custom_classes/task.py b/village/custom_classes/task.py index a6806429..2ca4cce3 100644 --- a/village/custom_classes/task.py +++ b/village/custom_classes/task.py @@ -29,11 +29,13 @@ def __init__(self, message) -> None: class Event(EventName): """Enumeration of Bpod event names.""" + pass class Output(OutputChannel): """Enumeration of Bpod output channels.""" + pass @@ -117,6 +119,7 @@ def run_in_thread(self, daemon: bool = True) -> None: Args: daemon (bool, optional): Whether to run as a daemon thread. Defaults to True. """ + def test_run(): self.create_paths() self.start() @@ -562,4 +565,3 @@ def create_paths(self) -> None: self.video_path = str(Path(self.video_directory, self.filename + ".mp4")) self.video_data_path = str(Path(self.video_directory, self.filename + ".csv")) self.subject_path = str(Path(self.sessions_directory, self.subject + ".csv")) - diff --git a/village/custom_classes/training_protocol_base.py b/village/custom_classes/training_protocol_base.py index 46448ccd..5dcdd9c9 100644 --- a/village/custom_classes/training_protocol_base.py +++ b/village/custom_classes/training_protocol_base.py @@ -248,4 +248,3 @@ def get_string(self) -> str: dict = self.get_dict() _, new_dict = self.correct_types_in_dict(dict) return json.dumps(new_dict) - diff --git a/village/devices/bpod.py b/village/devices/bpod.py index 7bb2c0cd..1bfdcf6b 100644 --- a/village/devices/bpod.py +++ b/village/devices/bpod.py @@ -394,4 +394,3 @@ def get_bpod() -> PyBpodBase: bpod = get_bpod() - diff --git a/village/devices/motor.py b/village/devices/motor.py index bdf7a1b9..e9ee8758 100644 --- a/village/devices/motor.py +++ b/village/devices/motor.py @@ -157,7 +157,6 @@ def get_motor(pin: int, angles: list[int]) -> MotorBase: return MotorBase() - import sys if "sphinx" in sys.modules: @@ -166,4 +165,3 @@ def get_motor(pin: int, angles: list[int]) -> MotorBase: else: motor1 = get_motor(settings.get("MOTOR1_PIN"), settings.get("MOTOR1_VALUES")) motor2 = get_motor(settings.get("MOTOR2_PIN"), settings.get("MOTOR2_VALUES")) - diff --git a/village/devices/rfid.py b/village/devices/rfid.py index 8fe297dc..b9a219c1 100644 --- a/village/devices/rfid.py +++ b/village/devices/rfid.py @@ -104,4 +104,3 @@ def get_id(self) -> tuple[str, bool]: rfid = Rfid() - diff --git a/village/devices/scale.py b/village/devices/scale.py index 408486e1..42ad299c 100644 --- a/village/devices/scale.py +++ b/village/devices/scale.py @@ -92,7 +92,7 @@ def get_value(self, samples: int = 1, interval_s: float = 0.005) -> int: if interval_s > 0 and i < samples - 1: time.sleep(interval_s) if not values: - return 0 + return 0 median = int(np.median(values)) if median == 0 and self.alarm_timer.has_elapsed(): log.alarm("Scale not responding, please check the connection.") @@ -158,4 +158,3 @@ def real_weight_inference(weight_array, threshold) -> tuple[bool, float]: return (True, median_weight) else: return (False, 0.0) - diff --git a/village/devices/sound_device.py b/village/devices/sound_device.py index 388e0cf4..6e95ca5d 100644 --- a/village/devices/sound_device.py +++ b/village/devices/sound_device.py @@ -232,4 +232,3 @@ def get_sound_device() -> SoundDeviceBase: sound_device = get_sound_device() - diff --git a/village/devices/telegram_bot.py b/village/devices/telegram_bot.py index af0288b7..e141880d 100644 --- a/village/devices/telegram_bot.py +++ b/village/devices/telegram_bot.py @@ -196,4 +196,3 @@ def get_telegram_bot() -> TelegramBotBase: telegram_bot = get_telegram_bot() - diff --git a/village/devices/temp_sensor.py b/village/devices/temp_sensor.py index 1a6a53cc..62f71edc 100644 --- a/village/devices/temp_sensor.py +++ b/village/devices/temp_sensor.py @@ -78,4 +78,3 @@ def get_temp_sensor(address: str) -> TempSensorBase: temp_sensor = get_temp_sensor(address=settings.get("TEMP_SENSOR_ADDRESS")) - diff --git a/village/gui/data_layout.py b/village/gui/data_layout.py index 0e441c22..ea884990 100644 --- a/village/gui/data_layout.py +++ b/village/gui/data_layout.py @@ -8,7 +8,6 @@ import cv2 import numpy as np import pandas as pd -from village.classes.enums import State from pandas import DataFrame from PyQt5.QtCore import ( QAbstractTableModel, @@ -40,7 +39,7 @@ QWidget, ) -from village.classes.enums import DataTable +from village.classes.enums import DataTable, State from village.gui.layout import Layout from village.manager import manager from village.plots.sound_calibration_plot import sound_calibration_plot diff --git a/village/gui/gui.py b/village/gui/gui.py index 56cbf2a0..3fe257af 100644 --- a/village/gui/gui.py +++ b/village/gui/gui.py @@ -78,4 +78,3 @@ def reload_app(self) -> None: self.q_app.quit() python = sys.executable os.execv(python, [python] + sys.argv) - diff --git a/village/gui/gui_window.py b/village/gui/gui_window.py index fb951b08..d0f3619c 100644 --- a/village/gui/gui_window.py +++ b/village/gui/gui_window.py @@ -139,4 +139,3 @@ def check_update_chrono(self) -> None: """ if self.update_chrono.get_seconds() > self.screensave_time: self.layout.main_button_clicked(auto=True) - diff --git a/village/gui/layout.py b/village/gui/layout.py index 9670e1c7..39a662de 100644 --- a/village/gui/layout.py +++ b/village/gui/layout.py @@ -5,7 +5,6 @@ from pathlib import Path from typing import TYPE_CHECKING, Callable -from village.classes.enums import State from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from PyQt5.QtCore import Qt, QTime from PyQt5.QtGui import QCloseEvent, QPixmap, QWheelEvent @@ -21,7 +20,7 @@ QVBoxLayout, ) -from village.classes.enums import DataTable +from village.classes.enums import DataTable, State from village.manager import manager from village.scripts.log import log from village.settings import settings @@ -32,6 +31,7 @@ class Label(QLabel): """Custom styled QLabel.""" + def __init__( self, text: str, @@ -60,6 +60,7 @@ def __init__( class LabelImage(QLabel): """QLabel for displaying an image from the resources directory.""" + def __init__(self, file) -> None: """Initializes a LabelImage.""" super().__init__() @@ -71,6 +72,7 @@ def __init__(self, file) -> None: class LineEdit(QLineEdit): """Custom QLineEdit that triggers an action on text change.""" + def __init__(self, text: str, action: Callable) -> None: """Initializes a LineEdit.""" super().__init__() @@ -80,6 +82,7 @@ def __init__(self, text: str, action: Callable) -> None: class TimeEdit(QTimeEdit): """Custom QTimeEdit with HH:mm format.""" + def __init__(self, text: str, action: Callable) -> None: """Initializes a TimeEdit.""" super().__init__() @@ -90,6 +93,7 @@ def __init__(self, text: str, action: Callable) -> None: class PushButton(QPushButton): """Custom styled QPushButton.""" + def __init__( self, text: str, color: str, action: Callable, description: str ) -> None: @@ -105,6 +109,7 @@ def __init__( class ToggleButton(QPushButton): """Button that cycles through a list of values when pressed.""" + def __init__( self, key: str, @@ -151,6 +156,7 @@ def on_pressed(self) -> None: class ComboBox(QComboBox): """Custom QComboBox linked to a setting or variable.""" + def __init__( self, key: str, possible_values: list[str], index: int, action: Callable ) -> None: @@ -188,7 +194,6 @@ def wheelEvent(self, event: QWheelEvent) -> None: event.ignore() - class Layout(QGridLayout): """Base class for all GUI layouts in the application. diff --git a/village/gui/main_layout.py b/village/gui/main_layout.py index b8cfd900..452dafdc 100644 --- a/village/gui/main_layout.py +++ b/village/gui/main_layout.py @@ -47,4 +47,3 @@ def draw(self) -> None: def update_gui(self) -> None: """Updates the status line and buttons.""" self.update_status_label_buttons() - diff --git a/village/gui/tasks_layout.py b/village/gui/tasks_layout.py index b6dc4661..fa47f4e6 100644 --- a/village/gui/tasks_layout.py +++ b/village/gui/tasks_layout.py @@ -33,6 +33,7 @@ class ExtraLayout(Layout): """Layout helper for checking task buttons and settings in scrollable areas.""" + def __init__(self, window: GuiWindow, rows: int, columns: int) -> None: """Initializes the ExtraLayout.""" super().__init__(window, stacked=True, rows=rows, columns=columns) @@ -545,4 +546,3 @@ def update_gui(self) -> None: """Updates the GUI status.""" self.update_status_label_buttons() self.check_buttons() - diff --git a/village/main.py b/village/main.py index 641234a3..befdbb9f 100644 --- a/village/main.py +++ b/village/main.py @@ -60,7 +60,8 @@ print("No permission to change nice value.") print("Write this in the terminal:") print("sudo setcap cap_sys_nice=eip /usr/bin/python3.11") - raise + # When running in restricted environments (e.g., documentation builds), + # we can't change the nice value. Continue without raising to allow import. os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "0" os.environ["QT_SCALE_FACTOR"] = "1" diff --git a/village/plots/corridor_plot.py b/village/plots/corridor_plot.py index 08f729dd..3a0fab4e 100644 --- a/village/plots/corridor_plot.py +++ b/village/plots/corridor_plot.py @@ -161,4 +161,3 @@ def corridor_plot( fig.subplots_adjust(left=0.03, right=0.97, top=0.97, bottom=0.1) return fig - diff --git a/village/plots/sound_calibration_plot.py b/village/plots/sound_calibration_plot.py index e57565ae..92dbf95e 100644 --- a/village/plots/sound_calibration_plot.py +++ b/village/plots/sound_calibration_plot.py @@ -83,4 +83,3 @@ def sound_calibration_plot( ax.grid(True) return fig - diff --git a/village/plots/temperatures_plot.py b/village/plots/temperatures_plot.py index 82365048..334ccb92 100644 --- a/village/plots/temperatures_plot.py +++ b/village/plots/temperatures_plot.py @@ -41,4 +41,3 @@ def temperatures_plot(df: pd.DataFrame, width: float, height: float) -> Figure: ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d %H:%M")) return fig - diff --git a/village/plots/water_calibration_plot.py b/village/plots/water_calibration_plot.py index 21001a46..3263d741 100644 --- a/village/plots/water_calibration_plot.py +++ b/village/plots/water_calibration_plot.py @@ -66,4 +66,3 @@ def water_calibration_plot( ax.grid(True) return fig - diff --git a/village/plots/weights_plot.py b/village/plots/weights_plot.py index f23fa010..8abf4b8d 100644 --- a/village/plots/weights_plot.py +++ b/village/plots/weights_plot.py @@ -112,4 +112,3 @@ def weights_plot(df: pd.DataFrame, width: float, height: float) -> Figure: fig.suptitle(f"Weights by Subject (N subjects = {n_subjects})", y=0.995) fig.tight_layout(rect=[0, 0, 1, 0.97]) return fig - diff --git a/village/screen/behavior_window.py b/village/screen/behavior_window.py index e4f58ec3..e3675898 100644 --- a/village/screen/behavior_window.py +++ b/village/screen/behavior_window.py @@ -253,4 +253,3 @@ def clear_function(self) -> None: with QPainter(self) as painter: # clean the window painter.fillRect(manager.behavior_window.rect(), self.background_color) - diff --git a/village/screen/video_worker.py b/village/screen/video_worker.py index ca0bcedc..fca30c11 100644 --- a/village/screen/video_worker.py +++ b/village/screen/video_worker.py @@ -14,11 +14,11 @@ class VideoWorker(QObject): """Worker class for decoding video frames in a separate thread. - + Uses OpenCV to read frames and serves them as QImages for display. Maintains synchronization with real-time based on the video's FPS. """ - + finished = pyqtSignal() def __init__(self, path: str) -> None: @@ -136,4 +136,3 @@ def get_latest_qimage(self) -> Optional[QImage]: def stop(self) -> None: """Stops the video decoding loop.""" self._running = False - diff --git a/village/scripts/global_csv_for_subject.py b/village/scripts/global_csv_for_subject.py index b12a23c6..1e8b846d 100644 --- a/village/scripts/global_csv_for_subject.py +++ b/village/scripts/global_csv_for_subject.py @@ -86,4 +86,3 @@ def extract_datetime(filename) -> str: if __name__ == "__main__": fire.Fire(main) - diff --git a/village/scripts/log.py b/village/scripts/log.py index f3eaf241..f7b27034 100644 --- a/village/scripts/log.py +++ b/village/scripts/log.py @@ -161,4 +161,3 @@ def clean_text(self, exception: str | None, description: str) -> str: log = Log() - diff --git a/village/scripts/parse_bpod_messages.py b/village/scripts/parse_bpod_messages.py index 93746b89..66fd586d 100644 --- a/village/scripts/parse_bpod_messages.py +++ b/village/scripts/parse_bpod_messages.py @@ -156,4 +156,3 @@ def parse_output_to_tuple(val: str | tuple[str, int]) -> tuple: return (base, suffix) raise ValueError(f"Bad output_state: {val}") - diff --git a/village/scripts/playground.py b/village/scripts/playground.py index c4b99df7..106ea00b 100644 --- a/village/scripts/playground.py +++ b/village/scripts/playground.py @@ -85,6 +85,6 @@ def measure_softcode_latency(bpod: BpodWithLatencyTest, n_trials: int = 100): print(f"Mediana = {statistics.median(latencies):.3f} ms") -mybpod = BpodWithLatencyTest() -measure_softcode_latency(mybpod, 200) - +if __name__ == "__main__": + mybpod = BpodWithLatencyTest() + measure_softcode_latency(mybpod, 200) diff --git a/village/scripts/rsync_to_server.py b/village/scripts/rsync_to_server.py index 8d061935..af2662e1 100644 --- a/village/scripts/rsync_to_server.py +++ b/village/scripts/rsync_to_server.py @@ -322,4 +322,3 @@ def main( if __name__ == "__main__": fire.Fire(main) - diff --git a/village/scripts/time_utils.py b/village/scripts/time_utils.py index 06bcc22c..f5241e27 100644 --- a/village/scripts/time_utils.py +++ b/village/scripts/time_utils.py @@ -284,6 +284,7 @@ def measure_time(self, func) -> Callable[..., Any]: Returns: Callable: Wrapped function. """ + def wrapper(*args, **kwargs) -> Any: start = time.perf_counter() result = func(*args, **kwargs)