Repository: aeye-lab/pymovements
Pull request #46 (2022-10-26 10:02:36)

Add event base classes
## Description

This PR adds the first event base classes.
This will help in unifying the output of the event detection functions.
Proposed in #16

## Implemented changes
- [x] Added `Event` class with `duration` property
- [x] Added child classes `Fixation` and `Saccade`
- [x] Added basic tests

## Type of change

- [x] New feature (non-breaking change which adds functionality)
- [x] This change requires a documentation update

## How Has This Been Tested?

- [x] Tests correct duration property for three different cases
- [x] Tests correct name attribute

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream modules
- [x] I have checked my code and corrected any misspellings



Patch:
diff --git a/.gitignore b/.gitignore
index da57d9f..b165914 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,10 +41,13 @@ pip-delete-this-directory.txt
 \#*\#
 .\#*
 
+# PyCharm files
+.idea
+
 # Docs
 docs/build
 docs/doctree*
-
+docs/source/reference/pymovements.*.rst
 # Testing
 .tox
 .coverage
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..6b20cb9
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,15 @@
+version: 2
+
+build:
+  os: ubuntu-20.04
+  tools:
+    python: "3.9"
+
+sphinx:
+  configuration: docs/source/conf.py
+
+python:
+  install:
+    - method: pip
+      path: .
+      extra_requirements: ["docs"]
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index d0c3cbf..0000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,20 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line, and also
-# from the environment for the first two.
-SPHINXOPTS    ?=
-SPHINXBUILD   ?= sphinx-build
-SOURCEDIR     = source
-BUILDDIR      = build
-
-# Put it first so that "make" without argument is like "make help".
-help:
-	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
-	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/make.bat b/docs/make.bat
deleted file mode 100644
index 6fcf05b..0000000
--- a/docs/make.bat
+++ /dev/null
@@ -1,35 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
-	set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=source
-set BUILDDIR=build
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
-	echo.
-	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
-	echo.installed, then set the SPHINXBUILD environment variable to point
-	echo.to the full path of the 'sphinx-build' executable. Alternatively you
-	echo.may add the Sphinx directory to PATH.
-	echo.
-	echo.If you don't have Sphinx installed, grab it from
-	echo.https://www.sphinx-doc.org/
-	exit /b 1
-)
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-
-:end
-popd
diff --git a/docs/source/_templates/modules.rst b/docs/source/_templates/modules.rst
new file mode 100644
index 0000000..5525a58
--- /dev/null
+++ b/docs/source/_templates/modules.rst
@@ -0,0 +1,70 @@
+{{ fullname | escape | underline}}
+
+.. automodule:: {{ fullname }}
+   :members:
+   :show-inheritance:
+
+   {% block attributes %}
+   {% if attributes %}
+   .. rubric:: {{ _('Module Attributes') }}
+
+   .. autosummary::
+      :nosignatures:
+
+   {% for item in attributes %}
+      {{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+   {% block functions %}
+   {% if functions %}
+   .. rubric:: {{ _('Functions') }}
+
+   .. autosummary::
+      :nosignatures:
+
+   {% for item in functions %}
+      {{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+   {% block classes %}
+   {% if classes %}
+   .. rubric:: {{ _('Classes') }}
+
+   .. autosummary::
+      :nosignatures:
+
+   {% for item in classes %}
+      {{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+   {% block exceptions %}
+   {% if exceptions %}
+   .. rubric:: {{ _('Exceptions') }}
+
+   .. autosummary::
+      :nosignatures:
+
+   {% for item in exceptions %}
+      {{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+{% block modules %}
+{% if modules %}
+.. rubric:: Modules
+
+.. autosummary::
+   :toctree:
+   :recursive:
+{% for item in modules %}
+   {{ item }}
+{%- endfor %}
+{% endif %}
+{% endblock %}
diff --git a/docs/source/bibliography.rst b/docs/source/bibliography.rst
new file mode 100644
index 0000000..73b0782
--- /dev/null
+++ b/docs/source/bibliography.rst
@@ -0,0 +1,5 @@
+============
+Bibliography
+============
+
+.. bibliography::
diff --git a/docs/source/getting-started.rst b/docs/source/getting-started.rst
new file mode 100644
index 0000000..6f034ae
--- /dev/null
+++ b/docs/source/getting-started.rst
@@ -0,0 +1,25 @@
+================
+ Getting started
+================
+
+
+Install
+-------
+
+pymovements can be installed directly from PyPI:
+
+.. code-block:: console
+
+   pip install pymovements
+
+To  use the latest development version or to try out examples, pymovements may be
+alternatively cloned and installed with
+
+.. code-block:: console
+
+   git clone https://github.com/aeye-lab/pymovements.git
+   pip install -e ./pymovements
+
+Basic Usage
+-----------
+
diff --git a/docs/source/how-to/index.rst b/docs/source/how-to/index.rst
new file mode 100644
index 0000000..611900b
--- /dev/null
+++ b/docs/source/how-to/index.rst
@@ -0,0 +1,10 @@
+================
+ How-Tos
+================
+
+
+These How-Tos provide detailed information on the usage of specific parts of pymovements.
+
+.. toctree::
+    :maxdepth: 1
+
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 89821b4..2c0a53e 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -1,22 +1,36 @@
-.. pymovements documentation master file, created by
-   sphinx-quickstart on Tue Jun 14 16:02:58 2022.
-   You can adapt this file completely to your liking, but it should at least
-   contain the root `toctree` directive.
+=========================================
+Welcome to the pymovements documentation!
+=========================================
 
-Welcome to pymovements's documentation!
-=======================================
+**pymovements** is a Python library for processing eye tracking data.
 
-**pymovement** is a Python library for processing eye tracking data.
+This package is available on PyPI and may be installed using:
+
+.. code-block:: console
+
+   $ pip install pymovements
+
+
+Contents
+--------
 
 .. toctree::
    :maxdepth: 2
-   :caption: Contents:
 
-   API <pymovements.rst>
+   getting-started
+   how-to/index
+   tutorial/index
+   reference/index
+   bibliography
+
 
 Indices and tables
-==================
+------------------
 
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
+
+
+Citing
+------
diff --git a/docs/source/pymovements.rst b/docs/source/pymovements.rst
deleted file mode 100644
index cef6a49..0000000
--- a/docs/source/pymovements.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-pymovements package
-===================
-
-pymovements.base module
-----------------------------------
-
-.. automodule:: pymovements.base
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-pymovements.transformations module
-----------------------------------
-
-.. automodule:: pymovements.transforms
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-Module contents
----------------
-
-.. automodule:: pymovements
-   :members:
-   :undoc-members:
-   :show-inheritance:
diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst
new file mode 100644
index 0000000..2d0e73a
--- /dev/null
+++ b/docs/source/reference/index.rst
@@ -0,0 +1,15 @@
+================
+ API Reference
+================
+
+.. autosummary::
+    :toctree:
+    :nosignatures:
+    :recursive:
+    :template: modules.rst
+
+    pymovements.base
+    pymovements.checks
+    pymovements.paths
+    pymovements.plot
+    pymovements.transforms
diff --git a/docs/source/tutorial/index.rst b/docs/source/tutorial/index.rst
new file mode 100644
index 0000000..e990fca
--- /dev/null
+++ b/docs/source/tutorial/index.rst
@@ -0,0 +1,11 @@
+================
+ Tutorials
+================
+
+
+These How-Tos provide detailed guides to implement the most important use cases of pymovements.
+
+
+.. toctree::
+    :maxdepth: 1
+
diff --git a/requirements.txt b/requirements.txt
index 4b8917a..014c679 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,2 @@
-numpy>=1.10.0
-scipy>=1.7
-matplotlib~=3.6.1
+# installs dependencies from ./setup.cfg, and the package itself, in editable mode
+-e .[test,docs]
\ No newline at end of file
diff --git a/requirements_doc.txt b/requirements_doc.txt
deleted file mode 100644
index a58ab6a..0000000
--- a/requirements_doc.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-ipykernel>=6.13.0
-nbsphinx>=0.8.8
-numpy>=1.10.0
-scipy>=1.7
-sphinx-copybutton>=0.4.0
-sphinx-rtd-theme>=1.0.0
-sphinxcontrib.bibtex>=2.4.1
-sphinxcontrib.datatemplates>=0.9.0
diff --git a/setup.cfg b/setup.cfg
index 465a2f8..6d12017 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = pymovements
-version = 0.0.6
+version = 0.0.8
 author = Daniel Krakowczyk
 author_email = daniel.krakowczyk@uni-potsdam.de
 description = Package for processing eye tracking data
@@ -40,6 +40,7 @@ docs =
      sphinxcontrib.bibtex>=2.4.1
      nbsphinx>=0.8.8
      ipykernel>=6.13.0
+     pybtex
 
 [options.packages.find]
 where = src
diff --git a/src/pymovements/events/__init__.py b/src/pymovements/events/__init__.py
new file mode 100644
index 0000000..7ad69f4
--- /dev/null
+++ b/src/pymovements/events/__init__.py
@@ -0,0 +1,8 @@
+"""
+This module holds all event related functionality.
+"""
+
+
+from pymovements.events.base import Event  # noqa: F401
+from pymovements.events.base import Fixation  # noqa: F401
+from pymovements.events.base import Saccade  # noqa: F401
diff --git a/src/pymovements/events/base.py b/src/pymovements/events/base.py
new file mode 100644
index 0000000..7ebc625
--- /dev/null
+++ b/src/pymovements/events/base.py
@@ -0,0 +1,78 @@
+"""
+This module holds all the main Event classes used for event detection.
+"""
+
+
+class Event:
+    """
+    Base Event class.
+
+    Parameters
+    ----------
+    name: str
+        Name of event.
+    onset: int
+        Starting index of event (included).
+    offset: int
+        Ending index of event (excluded).
+
+    Attributes
+    ----------
+    name: str
+        Name of event.
+    onset: int
+        Starting index of event (included).
+    offset: int
+        Ending index of event (excluded).
+
+    """
+    def __init__(self, name: str, onset: int, offset: int):
+        self.name = name
+        self.onset = onset
+        self.offset = offset
+
+    @property
+    def duration(self) -> int:
+        """
+        Get sample duration of event.
+
+        Returns
+        -------
+        int
+            duration in samples.
+        """
+        return self.offset - self.onset
+
+
+class Fixation(Event):
+    """
+    Fixation class.
+
+    Parameters
+    ----------
+    onset: int
+        Starting index of event (included).
+    offset: int
+        Ending index of event (excluded).
+    """
+    _name = 'fixation'
+
+    def __init__(self, onset: int, offset: int):
+        super().__init__(name=self._name, onset=onset, offset=offset)
+
+
+class Saccade(Event):
+    """
+    Saccade class.
+
+    Parameters
+    ----------
+    onset: int
+        Starting index of event (included).
+    offset: int
+        Ending index of event (excluded).
+    """
+    _name = 'saccade'
+
+    def __init__(self, onset: int, offset: int):
+        super().__init__(name=self._name, onset=onset, offset=offset)
diff --git a/src/pymovements/plot.py b/src/pymovements/plot.py
index df14458..564b852 100644
--- a/src/pymovements/plot.py
+++ b/src/pymovements/plot.py
@@ -29,7 +29,7 @@ default_segmentdata = {
     ],
 }
 
-# pylint: disable=consider-using-namedtuple-or-dataclass
+
 default_segmentdata_twoslope = {
     'red':   [
         [0.0,  0.0, 0.0],


Test patch:
diff --git a/requirements_test.txt b/requirements_test.txt
deleted file mode 100644
index 336e2e0..0000000
--- a/requirements_test.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-numpy>=1.10.0
-pytest>=6.0.0
-pytest-cov>=3.0.0
-scipy>=1.7
diff --git a/tests/events/test_events_base.py b/tests/events/test_events_base.py
new file mode 100644
index 0000000..e042535
--- /dev/null
+++ b/tests/events/test_events_base.py
@@ -0,0 +1,53 @@
+"""
+This module tests functionality of base event classes.
+"""
+from __future__ import annotations
+from typing import Any
+
+import pytest
+
+from pymovements.events import Event
+from pymovements.events import Fixation
+from pymovements.events import Saccade
+
+
+# pylint: disable=protected-access
+@pytest.mark.parametrize(
+    'event_class, init_params, expected',
+    [
+        pytest.param(
+            Event,
+            {'name': 'custom_event', 'onset': 10, 'offset': 11},
+            {'name': 'custom_event', 'duration': 1},
+            id='custom_event_short',
+        ),
+        pytest.param(
+            Event,
+            {'name': 'custom_event', 'onset': 0, 'offset': 999},
+            {'name': 'custom_event', 'duration': 999},
+            id='custom_event_long',
+        ),
+        pytest.param(
+            Fixation,
+            {'onset': 10, 'offset': 100},
+            {'name': Fixation._name, 'duration': 90},
+            id='fixation',
+        ),
+        pytest.param(
+            Saccade,
+            {'onset': 10, 'offset': 100},
+            {'name': Saccade._name, 'duration': 90},
+            id='saccade',
+        ),
+    ]
+)
+def test_event_class(event_class: Event, init_params: dict[str, Any], expected: dict[str, Any]):
+    """Test if instantiated event attributes fit expected values."""
+    event = event_class(**init_params)
+
+    assert event.name == expected['name'], (
+        f'event name does not match expected value ({event.name} != {expected["name"]})'
+    )
+    assert event.duration == expected['duration'], (
+        f'event duration does not match expected value ({event.duration} != {expected["duration"]})'
+    )