diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index 38ec2fe94..8c8320c2e 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -7,6 +7,7 @@ on: paths: - '.github/workflows/documentation.yaml' - 'docs/**' + - 'iblrig/__init__.py' - 'CHANGELOG.md' - 'README.md' - 'pyproject.toml' @@ -20,13 +21,12 @@ jobs: - name: Set up PDM uses: pdm-project/setup-pdm@v4 with: - python-version: ${{ matrix.python-version }} cache: true + python-version-file: pyproject.toml - name: Install requirements run: pdm sync -dG doc - name: Sphinx build - run: | - pdm run sphinx-build docs/source docs/build/html + run: pdm run sphinx-build docs/source docs/build/html - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 000000000..4d0e20139 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,44 @@ +name: Release + +on: + push: + branches: + - iblrigv8 + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +jobs: + pdf: + name: Build PDF + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up PDM + uses: pdm-project/setup-pdm@v4 + with: + cache: true + python-version-file: pyproject.toml + - name: Install requirements + run: pdm sync -dG doc + - name: Build PDF + run: pdm run make -C docs/ simplepdf + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: documentation + path: docs/build/simplepdf/*.pdf + retention-days: 1 + + release: + name: Publish GitHub Release + needs: pdf + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: documentation + - uses: softprops/action-gh-release@v2 + with: + files: documentation/*.pdf \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index df785488a..20fc9c08a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +8.23.1 +------ +* feature: post hardware information to alyx +* generate PDF documentation +* increase verbosity of error handling in base task +* remove dead code + 8.23.0 ------ * hardware validation: check for unexpected events on Bpod's digital input ports diff --git a/docs/source/conf.py b/docs/source/conf.py index 1e94dc5bd..4b87d1fc4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,16 +1,18 @@ from datetime import date from iblrig import __version__ +from iblrig.constants import BASE_PATH project = 'iblrig' copyright = f'2018 – {date.today().year} International Brain Laboratory' author = 'International Brain Laboratory' version = '.'.join(__version__.split('.')[:3]) +release = '.'.join(__version__.split('.')[:3]) # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ['sphinx_lesson', 'sphinx.ext.autosectionlabel'] +extensions = ['sphinx_lesson', 'sphinx.ext.autosectionlabel', 'sphinx_simplepdf'] autosectionlabel_prefix_document = True source_suffix = ['.rst', '.md'] @@ -23,3 +25,17 @@ html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] + + +simplepdf_vars = { + 'primary': '#004f8c', + 'secondary': '#004f8c', + 'cover': 'white', + 'cover-bg': 'linear-gradient(180deg, #004f8c 0%, #00a1d9 50%, #cc3399 100%)', +} +simplepdf_file_name = f'iblrig_{__version__}_reference.pdf' +simplepdf_weasyprint_flags = ['-j70', '-D150', '--hinting'] +html_context = { + 'docs_scope': 'external', + 'cover_meta_data': 'International Brain Laboratory', +} diff --git a/docs/source/index.rst b/docs/source/index.rst index 86dbf53a0..c043419f9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,8 +1,10 @@ -.. include:: ../../README.md - :parser: myst_parser.sphinx_ +.. if-builder:: html + + .. include:: ../../README.md + :parser: myst_parser.sphinx_ .. toctree:: - :caption: Contents: + :maxdepth: 3 :hidden: installation @@ -12,14 +14,16 @@ reference_developer_guide faq -.. toctree:: - :hidden: +.. if-builder:: html - changelog + .. toctree:: + :hidden: -.. toctree:: - :caption: Links - :hidden: + changelog + + .. toctree:: + :caption: Links + :hidden: - IBLRIG on GitHub - Appendix 3: IBL protocol for setting up the behavioral training rig \ No newline at end of file + IBLRIG on GitHub + Appendix 3: IBL protocol for setting up the behavioral training rig diff --git a/docs/source/installation.rst b/docs/source/installation.rst index acb8be90d..efba77d19 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -49,8 +49,8 @@ Now, run the following command at the prompt of Windows PowerShell: and usability. -Installing MS Visual C++ Redistributable ----------------------------------------- +Installing Visual C++ Redistributable +------------------------------------- With the Administrator PowerShell still open, run the following commands: @@ -67,7 +67,7 @@ With the Administrator PowerShell still open, run the following commands: .. admonition:: Background :class: seealso - These commands will create a temporary directory, download and silently install the Visual C++ Redistributable package for + These commands will create a temporary directory, download and install the Visual C++ Redistributable package for 64-bit Windows systems. The installer is retrieved from a Microsoft server and executed with parameters to ensure a seamless and unobtrusive installation process. diff --git a/docs/source/reference_developer_guide.rst b/docs/source/reference_developer_guide.rst index 0d6667cec..a5a6e24db 100644 --- a/docs/source/reference_developer_guide.rst +++ b/docs/source/reference_developer_guide.rst @@ -5,7 +5,7 @@ Developer Guide Versioning Scheme ----------------- -IBLRIG v8 uses `Semantic Versioning 2.0.0 `_. +IBLRIG v8 uses `Semantic Versioning `_. Its version string (currently "|version|") is a combination of three fields, separated by dots: .. centered:: ``MAJOR`` . ``MINOR`` . ``PATCH`` @@ -32,18 +32,21 @@ Here, * ``post3`` indicates the third unversioned commit after the latest versioned release, and * ``dirty`` indicates the presence of uncommited changes in your local repository of IBLRIG. -Both of these fields are inferred by means of git describe and do not require manual interaction from the developer. +Both of these fields are automatically inferred (by means of ``git describe``) and do not require manual interaction from the +developer. -PDM ---- +Package Management and Development Workflows with PDM +----------------------------------------------------- We use `PDM `_ to manage dependencies of IBLRIG. -See `PDM's documentation ` for help with installing PDM. +PDM can also be used to run various commands with relevance to the development process without having to activate a virtual +environment first. +Please refer to `PDM's documentation `_ for help with installing PDM. Installing Developer Dependencies ---------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To install additional dependencies needed for working on IBLRIG's code-base, run: @@ -52,8 +55,8 @@ To install additional dependencies needed for working on IBLRIG's code-base, run pdm sync -d -Running Unit Tests Locally --------------------------- +Running Unit Tests +^^^^^^^^^^^^^^^^^^ To run unit tests locally, run: @@ -61,36 +64,30 @@ To run unit tests locally, run: pdm run pytest -This will also generate a coverage report which can be found in the ``htmlcov`` directory. +This will also generate a HTML based coverage report which can be found in the ``htmlcov`` directory. Linting & Formatting --------------------- - -To lint your code, run the: - -.. code-block:: console +^^^^^^^^^^^^^^^^^^^^ - pdm run ruff check +We use `Ruff `_ for linting and formatting our code-base in close accordance with `the Black code +style `_. -Adding the commandline flag ``--fix`` will automatically fix issues that are deemed safe to handle: +To lint your code, run: .. code-block:: console - pdm run ruff check --fix - -To *check* if your code conforms to the `Black code style `_, run: - -.. code-block:: console + pdm run ruff check - pdm run ruff format --check +Appending the flag ``--fix`` to the above command will automatically fix issues that are deemed safe to handle. -To reformat your code according to the `Black code style `_, run: +To reformat your code according to the `Black code style `_ run: .. code-block:: console pdm run ruff format +Appending the flag ``--check`` to the above command will check your code for formatting issues without applying any changes. Refer to `Ruff Formater's documentation `_ for further details. @@ -115,18 +112,29 @@ Release Checklist Building the documentation -------------------------- +To build the documentation, run: + .. code-block:: console pdm run sphinx-autobuild ./docs/source ./docs/build +You can also export the documentation to a PDF file: + +.. code-block:: console + + pdm run make -C docs/ simplepdf + +Find the exported PDF file in ``docs/build/simplepdf``. + Contribute to the documentation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + To write the documentation: * Write the documentation in the ``iblrig/docs/source`` folder * If you are writing in a new file, add it to the ``index.rst`` so it appears in the table of content -* Push all your changes to the ``iblrigv8dev`` branch ; if this branch does not exist, create it first +* Push all your changes to the ``iblrigv8dev`` branch; if this branch does not exist, create it first To release the documentation onto the `website `_: diff --git a/iblrig/__init__.py b/iblrig/__init__.py index 4978b8721..45e724ad2 100644 --- a/iblrig/__init__.py +++ b/iblrig/__init__.py @@ -6,7 +6,7 @@ # 5) git tag the release in accordance to the version number below (after merge!) # >>> git tag 8.15.6 # >>> git push origin --tags -__version__ = '8.23.0' +__version__ = '8.23.1' from iblrig.version_management import get_detailed_version_string diff --git a/iblrig/base_choice_world.py b/iblrig/base_choice_world.py index 12ebfc76b..fbc772a26 100644 --- a/iblrig/base_choice_world.py +++ b/iblrig/base_choice_world.py @@ -676,21 +676,32 @@ def trial_completed(self, bpod_data): # get the trial outcome state_names = ['correct', 'error', 'no_go', 'omit_correct', 'omit_error', 'omit_no_go'] raw_outcome = {sn: ~np.isnan(bpod_data['States timestamps'].get(sn, [[np.NaN]])[0][0]) for sn in state_names} - outcome = next(k for k in raw_outcome if raw_outcome[k]) - # Update response buffer -1 for left, 0 for nogo, and 1 for rightward - position = self.trials_table.at[self.trial_num, 'position'] - if 'correct' in outcome: - self.trials_table.at[self.trial_num, 'trial_correct'] = True - self.session_info.NTRIALS_CORRECT += 1 - self.trials_table.at[self.trial_num, 'response_side'] = -np.sign(position) - elif 'error' in outcome: - self.trials_table.at[self.trial_num, 'response_side'] = np.sign(position) - elif 'no_go' in outcome: - self.trials_table.at[self.trial_num, 'response_side'] = 0 - super().trial_completed(bpod_data) - # here we throw potential errors after having written the trial to disk - assert np.sum(list(raw_outcome.values())) == 1 - assert position != 0, 'the position value should be either 35 or -35' + try: + outcome = next(k for k in raw_outcome if raw_outcome[k]) + # Update response buffer -1 for left, 0 for nogo, and 1 for rightward + position = self.trials_table.at[self.trial_num, 'position'] + if 'correct' in outcome: + self.trials_table.at[self.trial_num, 'trial_correct'] = True + self.session_info.NTRIALS_CORRECT += 1 + self.trials_table.at[self.trial_num, 'response_side'] = -np.sign(position) + elif 'error' in outcome: + self.trials_table.at[self.trial_num, 'response_side'] = np.sign(position) + elif 'no_go' in outcome: + self.trials_table.at[self.trial_num, 'response_side'] = 0 + super().trial_completed(bpod_data) + # here we throw potential errors after having written the trial to disk + assert np.sum(list(raw_outcome.values())) == 1 + assert position != 0, 'the position value should be either 35 or -35' + except StopIteration as e: + log.error(f'No outcome detected for trial {self.trial_num}.') + log.error(f'raw_outcome: {raw_outcome}') + log.error('State names: ' + ', '.join(bpod_data['States timestamps'].keys())) + raise e + except AssertionError as e: + log.error(f'Assertion Error in trial {self.trial_num}.') + log.error(f'raw_outcome: {raw_outcome}') + log.error('State names: ' + ', '.join(bpod_data['States timestamps'].keys())) + raise e class BiasedChoiceWorldSession(ActiveChoiceWorldSession): diff --git a/iblrig/gui/wizard.py b/iblrig/gui/wizard.py index 76ff8dab9..395086136 100644 --- a/iblrig/gui/wizard.py +++ b/iblrig/gui/wizard.py @@ -47,7 +47,7 @@ from iblrig.path_helper import load_pydantic_yaml from iblrig.pydantic_definitions import HardwareSettings, RigSettings from iblrig.raw_data_loaders import load_task_jsonable -from iblrig.tools import alyx_reachable, internet_available +from iblrig.tools import alyx_reachable, get_lab_location_dict, internet_available from iblrig.valve import Valve from iblrig.version_management import check_for_updates, get_changelog from iblutil.util import Bunch, setup_logger @@ -229,7 +229,12 @@ def login( # validate connection and some parameters now that we're connected try: - self.alyx.rest('locations', 'read', id=self.hardware_settings.RIG_NAME) + self.alyx.rest( + 'locations', + 'partial_update', + id=self.hardware_settings.RIG_NAME, + data={'json': get_lab_location_dict(self.hardware_settings, self.iblrig_settings)}, + ) except HTTPError as ex: if ex.response.status_code not in (404, 400): # file not found; auth error # Likely Alyx is down or server-side issue diff --git a/iblrig/tools.py b/iblrig/tools.py index e939eefdd..99ab066f4 100644 --- a/iblrig/tools.py +++ b/iblrig/tools.py @@ -1,18 +1,23 @@ import asyncio import logging import os +import platform import re import shutil import socket import subprocess from collections.abc import Callable from dataclasses import dataclass +from datetime import date +from functools import cache from pathlib import Path from typing import Any, TypeVar -from iblrig.constants import BONSAI_EXE +from iblrig import version_management +from iblrig.constants import BONSAI_EXE, IS_GIT from iblrig.path_helper import create_bonsai_layout_from_template, load_pydantic_yaml -from iblrig.pydantic_definitions import RigSettings +from iblrig.pydantic_definitions import HardwareSettings, RigSettings +from iblutil.util import get_mac log = logging.getLogger(__name__) @@ -50,12 +55,15 @@ def ask_user(prompt: str, default: bool = False) -> bool: return False -def get_anydesk_id(silent: bool = False) -> str | None: +def get_anydesk_id(format_id: bool = True, silent: bool = False) -> str | None: """ Retrieve the AnyDesk ID of the current machine. Parameters ---------- + format_id : bool, optional + If True (default), format the ID in blocks separated by spaces. + If False, return the ID as one continuous block. silent : bool, optional If True, suppresses exceptions and logs them instead. If False (default), raises exceptions. @@ -94,7 +102,7 @@ def get_anydesk_id(silent: bool = False) -> str | None: proc = subprocess.Popen([cmd, '--get-id'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if proc.stdout and re.match(r'^\d{10}$', id_string := next(proc.stdout).decode()): - anydesk_id = f'{int(id_string):,}'.replace(',', ' ') + anydesk_id = f'{int(id_string):,}'.replace(',', ' ' if format_id else '') except (FileNotFoundError, subprocess.CalledProcessError, StopIteration, UnicodeDecodeError) as e: if silent: log.debug(e, exc_info=True) @@ -375,3 +383,34 @@ class ANSI: DIM = '\033[2m' UNDERLINE = '\033[4m' END = '\033[0m' + + +cached_check_output = cache(subprocess.check_output) + + +def get_lab_location_dict(hardware_settings: HardwareSettings, iblrig_settings: RigSettings) -> dict[str, Any]: + lab_location = dict() + lab_location['rig_name'] = hardware_settings.RIG_NAME + lab_location['iblrig_version'] = str(version_management.get_local_version()) + lab_location['last_seen'] = date.today().isoformat() + + machine = dict() + machine['platform'] = platform.platform() + machine['hostname'] = socket.gethostname() + machine['fqdn'] = socket.getfqdn() + machine['ip'] = socket.gethostbyname(machine['hostname']) + machine['mac'] = get_mac() + machine['anydesk'] = get_anydesk_id(format_id=False, silent=True) + lab_location['machine'] = machine + + git = dict() + git['is_git'] = IS_GIT + git['branch'] = version_management.get_branch() + git['commit_id'] = version_management.get_commit_hash() + git['is_dirty'] = version_management.is_dirty() + lab_location['git'] = git + + # TODO: add hardware/firmware versions of bpod, soundcard, rotary encoder, frame2ttl, ambient module, etc + # TODO: add validation errors/warnings + + return lab_location diff --git a/iblrig/version_management.py b/iblrig/version_management.py index b6677e814..1003f95cd 100644 --- a/iblrig/version_management.py +++ b/iblrig/version_management.py @@ -1,15 +1,17 @@ import logging import re from collections.abc import Callable +from functools import cache from pathlib import Path from subprocess import STDOUT, CalledProcessError, SubprocessError, check_call, check_output +from typing import Any, Literal import requests from packaging import version from iblrig import __version__ from iblrig.constants import BASE_DIR, IS_GIT, IS_VENV -from iblrig.tools import internet_available, static_vars +from iblrig.tools import cached_check_output, internet_available log = logging.getLogger(__name__) @@ -125,38 +127,89 @@ def get_detailed_version_string(v_basic: str) -> str: return v_detailed -@static_vars(branch=None) -def get_branch() -> str | None: +OnErrorLiteral = Literal['raise', 'log', 'silence'] + + +def call_git(*args: str, cache_output: bool = True, on_error: OnErrorLiteral = 'raise') -> str | None: """ - Get the Git branch of the iblrig installation. + Call a git command with the specified arguments. - This function retrieves and caches the Git branch of the iblrig installation. - If the branch is already cached, it returns the cached value. If not, it - attempts to obtain the branch from the Git repository. + This function executes a git command with the provided arguments. It can cache the output of the command + and handle errors based on the specified behavior. + + Parameters + ---------- + *args : str + The arguments to pass to the git command. + cache_output : bool, optional + Whether to cache the output of the command. Default is True. + on_error : str, optional + The behavior when an error occurs. Either + - 'raise': raise the exception (default), + - 'log': log the exception, or + - 'silence': suppress the exception. Returns ------- - Union[str, None] - The Git branch of the iblrig installation, or None if it cannot be determined. + str or None + The output of the git command as a string, or None if an error occurred. - Notes - ----- - This method will only work with installations managed through Git. + Raises + ------ + RuntimeError + If the installation is not managed through git and on_error is set to 'raise'. + SubprocessError + If the command fails and on_error is set to 'raise'. """ - if get_branch.branch is not None: - return get_branch.branch + kwargs: dict[str, Any] = {'args': ('git', *args), 'cwd': BASE_DIR, 'timeout': 5, 'text': True} if not IS_GIT: - log.error('This installation of iblrig is not managed through git') + message = 'This installation of iblrig is not managed through git' + if on_error == 'raise': + raise RuntimeError(message) + elif on_error == 'log': + log.error(message) + return None try: - get_branch.branch = check_output( - ['git', 'rev-parse', '--abbrev-ref', 'HEAD'], cwd=BASE_DIR, timeout=5, text=True - ).removesuffix('\n') - return get_branch.branch - except (SubprocessError, CalledProcessError): + output = cached_check_output(**kwargs) if cache_output else check_output(**kwargs) + return str(output).strip() + except SubprocessError as e: + if on_error == 'raise': + raise e + elif on_error == 'log': + log.exception(e) return None -@static_vars(is_fetched_already=False) +def get_branch(): + """ + Get the Git branch of the iblrig installation. + + Returns + ------- + str or None + The Git branch of the iblrig installation, or None if it cannot be determined. + """ + return call_git('rev-parse', '--abbrev-ref', 'HEAD', on_error='log') + + +def get_commit_hash(short: bool = True): + """ + Get the hash of the currently checked out commit of the iblrig installation. + + Parameters + ---------- + short : bool, optional + Whether to return the short hash of the commit hash. Default is True. + + Returns + ------- + str or None + Hash of the currently checked out commit, or None if it cannot be determined. + """ + args = ['rev-parse', '--short', 'HEAD'] if short else ['rev-parse', 'HEAD'] + return call_git(*args, on_error='log') + + def get_remote_tags() -> None: """ Fetch remote Git tags if not already fetched. @@ -173,18 +226,14 @@ def get_remote_tags() -> None: ----- This method will only work with installations managed through Git. """ - if get_remote_tags.is_fetched_already or not internet_available(): + if not internet_available(): return - if not IS_GIT: - log.error('This installation of iblrig is not managed through git') - try: - check_call(['git', 'fetch', 'origin', get_branch(), '-t', '-q', '-f'], cwd=BASE_DIR, timeout=5) - except (SubprocessError, CalledProcessError): + if (branch := get_branch()) is None: return - get_remote_tags.is_fetched_already = True + call_git('fetch', 'origin', branch, '-t', '-q', '-f', on_error='log') -@static_vars(changelog=None) +@cache def get_changelog() -> str: """ Retrieve the changelog for the iblrig installation. @@ -204,20 +253,18 @@ def get_changelog() -> str: This method relies on the presence of a CHANGELOG.md file either in the repository or locally. """ - if get_changelog.changelog is not None: - return get_changelog.changelog try: + if (branch := get_branch()) is None: + raise RuntimeError() changelog = requests.get( - f'https://raw.githubusercontent.com/int-brain-lab/iblrig/{get_branch()}/CHANGELOG.md', allow_redirects=True + f'https://raw.githubusercontent.com/int-brain-lab/iblrig/{branch}/CHANGELOG.md', allow_redirects=True ).text - except requests.RequestException: + except (requests.RequestException, RuntimeError): with open(Path(BASE_DIR).joinpath('CHANGELOG.md')) as f: changelog = f.read() - get_changelog.changelog = changelog - return get_changelog.changelog + return changelog -@static_vars(remote_version=None) def get_remote_version() -> version.Version | None: """ Retrieve the remote version of iblrig from the Git repository. @@ -235,31 +282,11 @@ def get_remote_version() -> version.Version | None: ----- This method will only work with installations managed through Git. """ - if get_remote_version.remote_version is not None: - log.debug(f'Using cached remote version: {get_remote_version.remote_version}') - return get_remote_version.remote_version - - if not IS_GIT: - log.error('Cannot obtain remote version: This installation of iblrig is not managed through git') - return None - if not internet_available(): log.error('Cannot obtain remote version: Not connected to internet') return None - try: - log.debug('Obtaining remote version from github') - get_remote_tags() - references = check_output( - ['git', 'ls-remote', '-t', '-q', '--exit-code', '--refs', 'origin', 'tags', '*'], - cwd=BASE_DIR, - timeout=5, - encoding='UTF-8', - ) - - except (SubprocessError, CalledProcessError, FileNotFoundError): - log.error('Could not obtain remote version string') - return None + references = call_git('ls-remote', '-t', '-q', '--exit-code', '--refs', 'origin', 'tags', '*', on_error='log') try: log.debug('Parsing local version string') diff --git a/pdm.lock b/pdm.lock index 95d257af3..a5b08782c 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "ci", "dev", "doc", "project-extraction", "test", "typing"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:5997e4ffdd76310675bcace841dbb38af89e7dc8da116600cabe1f5bf79e18e6" +content_hash = "sha256:02c08779c288dce1bc517fc41e2c3d385fd24fc15a0e2fa491f87e5f94863e0b" [[metadata.targets]] requires_python = "==3.10.*" @@ -78,7 +78,7 @@ files = [ name = "asttokens" version = "2.4.1" summary = "Annotate AST trees with source code positions" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] dependencies = [ "six>=1.12.0", "typing; python_version < \"3.5\"", @@ -116,6 +116,20 @@ files = [ {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +requires_python = ">=3.6.0" +summary = "Screen-scraping library" +groups = ["dev", "doc"] +dependencies = [ + "soupsieve>1.2", +] +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + [[package]] name = "boto3" version = "1.34.162" @@ -149,6 +163,53 @@ files = [ {file = "botocore-1.34.162.tar.gz", hash = "sha256:adc23be4fb99ad31961236342b7cbf3c0bfc62532cd02852196032e8c0d682f3"}, ] +[[package]] +name = "brotli" +version = "1.1.0" +summary = "Python bindings for the Brotli compression library" +groups = ["dev", "doc"] +marker = "platform_python_implementation == \"CPython\"" +files = [ + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, + {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, + {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, + {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, +] + +[[package]] +name = "brotlicffi" +version = "1.1.0.0" +requires_python = ">=3.7" +summary = "Python CFFI bindings to the Brotli library" +groups = ["dev", "doc"] +marker = "platform_python_implementation != \"CPython\"" +dependencies = [ + "cffi>=1.0.0", +] +files = [ + {file = "brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851"}, + {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b"}, + {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814"}, + {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820"}, + {file = "brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb"}, + {file = "brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613"}, + {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca"}, + {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391"}, + {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8"}, + {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35"}, + {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d"}, + {file = "brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13"}, +] + [[package]] name = "certifi" version = "2024.7.4" @@ -402,6 +463,21 @@ files = [ {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"}, ] +[[package]] +name = "cssselect2" +version = "0.7.0" +requires_python = ">=3.7" +summary = "CSS selectors for Python ElementTree" +groups = ["dev", "doc"] +dependencies = [ + "tinycss2", + "webencodings", +] +files = [ + {file = "cssselect2-0.7.0-py3-none-any.whl", hash = "sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969"}, + {file = "cssselect2-0.7.0.tar.gz", hash = "sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a"}, +] + [[package]] name = "cycler" version = "0.12.1" @@ -468,7 +544,7 @@ name = "decorator" version = "5.1.1" requires_python = ">=3.5" summary = "Decorators for Humans" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] files = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, @@ -491,7 +567,6 @@ version = "1.2.2" requires_python = ">=3.7" summary = "Backport of PEP 654 (exception groups)" groups = ["default", "ci", "dev", "doc", "test"] -marker = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -502,7 +577,7 @@ name = "executing" version = "2.0.1" requires_python = ">=3.5" summary = "Get the currently executing AST node of a frame, and other information" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] files = [ {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, @@ -539,7 +614,33 @@ name = "fonttools" version = "4.53.1" requires_python = ">=3.8" summary = "Tools to manipulate font files" -groups = ["default"] +groups = ["default", "dev", "doc"] +files = [ + {file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"}, + {file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"}, + {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"}, + {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"}, + {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"}, + {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"}, + {file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"}, + {file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"}, + {file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"}, + {file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"}, +] + +[[package]] +name = "fonttools" +version = "4.53.1" +extras = ["woff"] +requires_python = ">=3.8" +summary = "Tools to manipulate font files" +groups = ["dev", "doc"] +dependencies = [ + "brotli>=1.0.1; platform_python_implementation == \"CPython\"", + "brotlicffi>=0.8.0; platform_python_implementation != \"CPython\"", + "fonttools==4.53.1", + "zopfli>=0.1.4", +] files = [ {file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"}, {file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"}, @@ -599,7 +700,7 @@ version = "3.0.3" requires_python = ">=3.7" summary = "Lightweight in-process concurrent programming" groups = ["dev", "doc"] -marker = "(platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\") and python_version < \"3.13\"" +marker = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\"" files = [ {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, @@ -627,6 +728,21 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "html5lib" +version = "1.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "HTML parser based on the WHATWG HTML specification" +groups = ["dev", "doc"] +dependencies = [ + "six>=1.9", + "webencodings", +] +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] + [[package]] name = "ibl-neuropixel" version = "1.2.0" @@ -866,7 +982,7 @@ name = "ipython" version = "8.26.0" requires_python = ">=3.10" summary = "IPython: Productive Interactive Computing" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] dependencies = [ "colorama; sys_platform == \"win32\"", "decorator", @@ -890,7 +1006,7 @@ name = "jedi" version = "0.19.1" requires_python = ">=3.6" summary = "An autocompletion tool for Python that can be used for text editors." -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] dependencies = [ "parso<0.9.0,>=0.8.3", ] @@ -1068,6 +1184,21 @@ files = [ {file = "lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1"}, ] +[[package]] +name = "libsass" +version = "0.23.0" +requires_python = ">=3.8" +summary = "Sass for Python: A straightforward binding of libsass for Python." +groups = ["dev", "doc"] +files = [ + {file = "libsass-0.23.0-cp38-abi3-macosx_11_0_x86_64.whl", hash = "sha256:34cae047cbbfc4ffa832a61cbb110f3c95f5471c6170c842d3fed161e40814dc"}, + {file = "libsass-0.23.0-cp38-abi3-macosx_14_0_arm64.whl", hash = "sha256:ea97d1b45cdc2fc3590cb9d7b60f1d8915d3ce17a98c1f2d4dd47ee0d9c68ce6"}, + {file = "libsass-0.23.0-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4a218406d605f325d234e4678bd57126a66a88841cb95bee2caeafdc6f138306"}, + {file = "libsass-0.23.0-cp38-abi3-win32.whl", hash = "sha256:31e86d92a5c7a551df844b72d83fc2b5e50abc6fbbb31e296f7bebd6489ed1b4"}, + {file = "libsass-0.23.0-cp38-abi3-win_amd64.whl", hash = "sha256:a2ec85d819f353cbe807432d7275d653710d12b08ec7ef61c124a580a8352f3c"}, + {file = "libsass-0.23.0.tar.gz", hash = "sha256:6f209955ede26684e76912caf329f4ccb57e4a043fd77fe0e7348dd9574f1880"}, +] + [[package]] name = "llvmlite" version = "0.43.0" @@ -1161,7 +1292,7 @@ name = "matplotlib-inline" version = "0.1.7" requires_python = ">=3.8" summary = "Inline Matplotlib backend for Jupyter" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] dependencies = [ "traitlets", ] @@ -1502,7 +1633,7 @@ name = "parso" version = "0.8.4" requires_python = ">=3.6" summary = "A Python Parser" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] files = [ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, @@ -1541,7 +1672,7 @@ files = [ name = "pexpect" version = "4.9.0" summary = "Pexpect allows easy control of interactive console applications." -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] marker = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" dependencies = [ "ptyprocess>=0.5", @@ -1576,7 +1707,7 @@ name = "pillow" version = "10.4.0" requires_python = ">=3.8" summary = "Python Imaging Library (Fork)" -groups = ["default"] +groups = ["default", "dev", "doc"] files = [ {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, @@ -1635,7 +1766,7 @@ name = "prompt-toolkit" version = "3.0.47" requires_python = ">=3.7.0" summary = "Library for building powerful interactive command lines in Python" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] dependencies = [ "wcwidth", ] @@ -1680,7 +1811,7 @@ files = [ name = "ptyprocess" version = "0.7.0" summary = "Run a subprocess in a pseudo terminal" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] marker = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, @@ -1691,7 +1822,7 @@ files = [ name = "pure-eval" version = "0.2.3" summary = "Safely evaluate AST nodes without side effects" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, @@ -1789,6 +1920,17 @@ files = [ {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, ] +[[package]] +name = "pydyf" +version = "0.11.0" +requires_python = ">=3.8" +summary = "A low-level PDF generator." +groups = ["dev", "doc"] +files = [ + {file = "pydyf-0.11.0-py3-none-any.whl", hash = "sha256:0aaf9e2ebbe786ec7a78ec3fbffa4cdcecde53fd6f563221d53c6bc1328848a3"}, + {file = "pydyf-0.11.0.tar.gz", hash = "sha256:394dddf619cca9d0c55715e3c55ea121a9bf9cbc780cdc1201a2427917b86b64"}, +] + [[package]] name = "pyflakes" version = "3.2.0" @@ -1805,7 +1947,7 @@ name = "pygments" version = "2.18.0" requires_python = ">=3.8" summary = "Pygments is a syntax highlighting package written in Python." -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -1865,6 +2007,17 @@ files = [ {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, ] +[[package]] +name = "pyphen" +version = "0.16.0" +requires_python = ">=3.8" +summary = "Pure Python module to hyphenate text" +groups = ["dev", "doc"] +files = [ + {file = "pyphen-0.16.0-py3-none-any.whl", hash = "sha256:b4a4c6d7d5654b698b5fc68123148bb799b3debe0175d1d5dc3edfe93066fc4c"}, + {file = "pyphen-0.16.0.tar.gz", hash = "sha256:2c006b3ddf072c9571ab97606d9ab3c26a92eaced4c0d59fd1d26988f308f413"}, +] + [[package]] name = "pyqt5" version = "5.15.10" @@ -2400,6 +2553,17 @@ files = [ {file = "sounddevice-0.5.0.tar.gz", hash = "sha256:0de95277654b3d403d9c15ded3c6cedf307e9b27cc9ce7bd995a2891d0c955af"}, ] +[[package]] +name = "soupsieve" +version = "2.6" +requires_python = ">=3.8" +summary = "A modern CSS selector implementation for Beautiful Soup." +groups = ["dev", "doc"] +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + [[package]] name = "sparse" version = "0.15.4" @@ -2541,6 +2705,22 @@ files = [ {file = "sphinx_rtd_theme_ext_color_contrast-0.3.2.tar.gz", hash = "sha256:464a579ac030821a328ef485d712f9b0a4b57dcb8e3aab44515dedc1944662e3"}, ] +[[package]] +name = "sphinx-simplepdf" +version = "1.6.0" +summary = "An easy to use PDF Builder for Sphinx with a modern PDF-Theme." +groups = ["dev", "doc"] +dependencies = [ + "beautifulsoup4", + "libsass", + "sphinx", + "weasyprint", +] +files = [ + {file = "sphinx-simplepdf-1.6.0.tar.gz", hash = "sha256:bc8412c6b029886ae2e9241612dfc59c4cd35fa8cf2e7eb987c14126d422a939"}, + {file = "sphinx_simplepdf-1.6.0-py3-none-any.whl", hash = "sha256:466a2b7e2000997ebf4dae62d88cd37b27d38c436ca23e81caf939e1d0e611f1"}, +] + [[package]] name = "sphinx-tabs" version = "3.4.5" @@ -2681,7 +2861,7 @@ files = [ name = "stack-data" version = "0.6.3" summary = "Extract data from python stack frames and tracebacks for informative displays" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] dependencies = [ "asttokens>=2.1.0", "executing>=1.2.0", @@ -2766,6 +2946,20 @@ files = [ {file = "tifffile-2024.8.10.tar.gz", hash = "sha256:fdc12124f1478a07b1524641dc6b50cf6bde0483011a63fd2a773094090c3dcf"}, ] +[[package]] +name = "tinycss2" +version = "1.3.0" +requires_python = ">=3.8" +summary = "A tiny CSS parser" +groups = ["dev", "doc"] +dependencies = [ + "webencodings>=0.4", +] +files = [ + {file = "tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"}, + {file = "tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d"}, +] + [[package]] name = "tomli" version = "2.0.1" @@ -2827,7 +3021,7 @@ name = "traitlets" version = "5.14.3" requires_python = ">=3.8" summary = "Traitlets Python configuration system" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, @@ -2963,7 +3157,7 @@ files = [ name = "wcwidth" version = "0.2.13" summary = "Measures the displayed width of unicode strings in a terminal" -groups = ["dev", "doc"] +groups = ["default", "dev", "doc"] dependencies = [ "backports-functools-lru-cache>=1.2.1; python_version < \"3.2\"", ] @@ -2972,6 +3166,37 @@ files = [ {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] +[[package]] +name = "weasyprint" +version = "62.3" +requires_python = ">=3.9" +summary = "The Awesome Document Factory" +groups = ["dev", "doc"] +dependencies = [ + "Pillow>=9.1.0", + "Pyphen>=0.9.1", + "cffi>=0.6", + "cssselect2>=0.1", + "fonttools[woff]>=4.0.0", + "html5lib>=1.1", + "pydyf>=0.10.0", + "tinycss2>=1.3.0", +] +files = [ + {file = "weasyprint-62.3-py3-none-any.whl", hash = "sha256:d31048646ce15084e135b33e334a61f526aa68d2f679fcc109ed0e0f5edaed21"}, + {file = "weasyprint-62.3.tar.gz", hash = "sha256:8d8680d732f7fa0fcbc587692a5a5cb095c3525627066918d6e203cbf42b7fcd"}, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +summary = "Character encoding aliases for legacy web content" +groups = ["dev", "doc"] +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + [[package]] name = "websockets" version = "12.0" @@ -3020,3 +3245,27 @@ files = [ {file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"}, {file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"}, ] + +[[package]] +name = "zopfli" +version = "0.2.3" +requires_python = ">=3.8" +summary = "Zopfli module for python" +groups = ["dev", "doc"] +files = [ + {file = "zopfli-0.2.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:52438999888715a378fc6fe1477ab7813e9e9b58a27a38d2ad7be0e396b1ab2e"}, + {file = "zopfli-0.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6020a3533c6c7be09db9e59c2a8f3f894bf5d8e95cc01890d82114c923317c57"}, + {file = "zopfli-0.2.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:72349c78da402e6784bd9c5f4aff5cc7017bd969016ec07b656722f7f29fc975"}, + {file = "zopfli-0.2.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:711d4fde9cb99e1a9158978e9d1624a37cdd170ff057f6340059514fcf38e808"}, + {file = "zopfli-0.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae890df6e5f1e8fa0697cafd848826decce0ac53e54e5a018fd97775e3a354c0"}, + {file = "zopfli-0.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40b830244e6458ef982b4a5ebb0f228986d481408bae557a95eeece2c5ede4e6"}, + {file = "zopfli-0.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bc89b71d1c4677f708cc162f40a4560f78f5f4c6aa6d884b423df7d38e8ba0b"}, + {file = "zopfli-0.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f07997453e7777e19ef0a2445cc1b90e1bb90c623dd77554325932dea6350fee"}, + {file = "zopfli-0.2.3-cp310-cp310-win32.whl", hash = "sha256:978395a4ce5cc46db29a36cdb80549b564dc7706237abaca5aac328dd5842f65"}, + {file = "zopfli-0.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:61a2fcc624e8b038d4fca84ba927dc3f31df53a7284692d46aa44d16fb3f47b2"}, + {file = "zopfli-0.2.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:09ad5f8d7e0fe1975ca6d9fd5ad61c74233ae277982d3bc8814b599bbeb92f44"}, + {file = "zopfli-0.2.3-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78022777139ac973286219e9e085d9496fb6c935502d93a52bd1bed01dfc2002"}, + {file = "zopfli-0.2.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13d151d5c83980f384439c87a5511853890182c05d93444f3cb05e5ceed37d82"}, + {file = "zopfli-0.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c1afe5ba0d957e462afbd3da116ac1a2a6d23e8a94436a95b692c5c324694a16"}, + {file = "zopfli-0.2.3.zip", hash = "sha256:dbc9841bedd736041eb5e6982cd92da93bee145745f5422f3795f6f258cdc6ef"}, +] diff --git a/pyproject.toml b/pyproject.toml index a4b595bc0..4ec7a74f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ # Everything else "annotated-types>=0.7.0", "graphviz>=0.20.3", + "ipython>=8.26.0", "matplotlib>=3.9.2", "numpy>=1.26.4", "packaging>=24.1", @@ -86,6 +87,7 @@ doc = [ "sphinx>=7.4.7", "sphinx-autobuild>=2024.4.16", "sphinx-lesson>=0.8.18", + "sphinx-simplepdf>=1.6.0", "myst-parser>=4.0.0", ] typing = [ @@ -104,7 +106,7 @@ files = [ "iblrig/[!test]**/*.py", "iblrig_tasks/**/*.py" ] ignore_missing_imports = true [tool.pytest.ini_options] -addopts = "-ra --showlocals --cov=iblrig --cov=iblrig_tasks --cov-report=html --cov-report=xml --tb=short" +addopts = "-ra --showlocals --cov --cov-report=html --cov-report=xml --tb=short" minversion = "6.0" testpaths = [ "iblrig/test" ] python_files = [ "test_*.py" ] @@ -170,10 +172,15 @@ quote-style = "single" convention = "numpy" [tool.ruff.lint.isort] -known-first-party = [ "ibl*", "one*", "pybpod*" ] +known-first-party = [ "ibl*", "one*", "pybpod*", "tycmd" ] [tool.deadcode] -exclude = [ "docs", "venv", "iblrig/test" ] +exclude = [ "docs", "venv", ".venv" ] [tool.coverage.run] -omit = [ "iblrig/test/**" ] +source_pkgs = [ "iblrig", "iblrig_tasks" ] +omit = [ "iblrig/test/*" ] +relative_files = true + +[tool.coverage.report] +skip_empty = true diff --git a/scripts/__init__.py b/scripts/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/scripts/ibllib/__init__.py b/scripts/ibllib/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/scripts/ibllib/purge_rig_data.py b/scripts/ibllib/purge_rig_data.py deleted file mode 100644 index 21bed86cb..000000000 --- a/scripts/ibllib/purge_rig_data.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -Purge data from RIG. - -- looks for datasets matching filename pattern -- datasets that exist in ONE cache are removed -""" - -import argparse -import logging -from fnmatch import fnmatch -from pathlib import Path - -from one.alf.files import get_session_path -from one.alf.io import iter_datasets, iter_sessions -from one.api import ONE - -log = logging.getLogger('iblrig') - - -def session_name(path: str | Path, lab: str | None = None) -> str: - """ - Return the session name (`subject/date/number`) string for a given session path. - - If lab is given return `lab/Subjects/subject/date/number`. - - Parameters - ---------- - path : str or Path - Session path. - lab : str, optional - Lab name - """ - lab = f'{lab}/Subjects/' if lab else '' - return lab + '/'.join(get_session_path(path).parts[-3:]) - - -def local_alf_paths(root_dir: str | Path, filename: str): - """ - Yield session path and relative paths of ALFs that match filename pattern. - - Parameters - ---------- - root_dir : str or Path - The folder to look for sessions. - filename : str - Session filename. - - Yields - ------ - session_path : Path - Session path. - dataset : Path - Relative paths of ALFs. - """ - for session_path in iter_sessions(root_dir): - for dataset in iter_datasets(session_path): - if fnmatch(dataset, filename): - yield session_path, dataset - - -def purge_local_data(local_folder, filename='*', lab=None, dry=False, one=None): - # Figure out datasetType from filename or file path - local_folder = Path(local_folder) - - # Get matching files that exist in ONE cache - to_remove = [] - one = one or ONE() - for session_path, dataset in local_alf_paths(local_folder, filename): - session = session_name(session_path, lab=lab) - eid = one.to_eid(session) - if not eid: - continue - matching = one.list_datasets(eid, dataset.as_posix()) - if not matching: - continue - assert len(matching) == 1 - to_remove.append(local_folder.joinpath(session_path, dataset)) - - log.info(f'Local files to remove: {len(to_remove)}') - for f in to_remove: - log.info(f'DELETE: {f}') - f.unlink() if not dry else None - return to_remove - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Delete files from rig') - parser.add_argument('folder', help='Local iblrig_data folder') - parser.add_argument('file', help='File name to search and destroy for every session') - parser.add_argument( - '-lab', required=False, default=None, help='Lab name, in case sessions conflict between labs. default: None' - ) - parser.add_argument('--dry', required=False, default=False, action='store_true', help='Dry run? default: False') - args = parser.parse_args() - purge_local_data(args.folder, args.file, lab=args.lab, dry=args.dry) - print('purge_rig_data script done\n') diff --git a/scripts/ibllib/register_session.py b/scripts/ibllib/register_session.py deleted file mode 100644 index 4fdd4f352..000000000 --- a/scripts/ibllib/register_session.py +++ /dev/null @@ -1,18 +0,0 @@ -import logging -import sys -import traceback - -from ibllib.oneibl.registration import IBLRegistrationClient - -log = logging.getLogger('iblrig') - - -if __name__ == '__main__': - IBLRIG_DATA = sys.argv[1] - try: - log.info('Trying to register session in Alyx...') - IBLRegistrationClient(one=None).create_sessions(IBLRIG_DATA, dry=False) - log.info('Done') - except Exception: - log.error(traceback.format_exc()) - log.warning('Failed to register session on Alyx, will try again from local server after transfer') diff --git a/scripts/ibllib/screen_stimulus_from_wheel.py b/scripts/ibllib/screen_stimulus_from_wheel.py deleted file mode 100644 index 7b23ecb87..000000000 --- a/scripts/ibllib/screen_stimulus_from_wheel.py +++ /dev/null @@ -1,81 +0,0 @@ -import math - -import numpy as np - -from one.api import ONE - -WHEEL_RADIUS = 31 -USER_DEFINED_GAIN = 4.0 -MM_PER_DEG = (2 * math.pi * WHEEL_RADIUS) / 360 -GAIN_FACTOR = 1 / (MM_PER_DEG * USER_DEFINED_GAIN) - - -def pos_on_screen(pos, init_pos): - try: - iter(pos) - except TypeError: - pos = [pos] - - for p in pos: - yield (((p / GAIN_FACTOR) + init_pos) + 180) % 360 - 180 - - -def find_nearest(array, value): - array = np.asarray(array) - idx = (np.abs(array - value)).argmin() - return idx - - -def get_stim_from_wheel(eid: str, tr: int): - """ - Get position of the stimulus on the screen. - - For a given session (eid) and trial (tr) - return the position of the stimulus on the screen, - where the one screen side is at 35 and the other at -35. - - If the mouse wheels wrongly away from 0, the stimulus - remains at the edge of the screen - """ - # eid = '83e77b4b-dfa0-4af9-968b-7ea0c7a0c7e4' - # tr = 0 - # For a given trial tr, the stim on screen responds to the wheel between - # trials['goCue_times'][tr] and trials['feedback_times'][tr] - - one = ONE() - dataset_types = [ - 'trials.goCue_times', - 'trials.feedback_times', - 'trials.feedbackType', - 'trials.contrastLeft', - 'trials.contrastRight', - 'trials.choice', - ] - one.load(eid, dataset_types=dataset_types, dclass_output=True) - alf_path = one.path_from_eid(eid) / 'alf' - trials = one.load_object(alf_path, 'trials') - wheel = one.load_object(eid, 'wheel') - - # check where stimulus started for initial shift - init_pos = -35 if np.isnan(trials['contrastLeft'][tr]) else 35 - - # the screen stim is only coupled to the wheel in this time - wheel_start_idx = find_nearest(wheel.timestamps, trials['goCue_times'][tr]) - wheel_end_idx = find_nearest(wheel.timestamps, trials['feedback_times'][tr]) - wheel_pos = wheel.position[wheel_start_idx:wheel_end_idx] - wheel_times = wheel.timestamps[wheel_start_idx:wheel_end_idx] - - wheel_pos = wheel_pos * 180 / np.pi - wheel_pos = wheel_pos - wheel_pos[0] # starting at 0 - - screen_deg = np.array(list(pos_on_screen(wheel_pos, init_pos))) - # set screen degrees to initial value if larger than 35 - # as then the stimulus stays at the edge of the screen - idc = np.where(abs(screen_deg) > 35)[0] - - screen_deg[idc] = screen_deg[0] - - # f = interp1d(wheel_times, absolute_screen_deg) - # as you might want to get values as shown on screen, i.e. at 60 Hz - - return wheel_pos, screen_deg, trials['feedbackType'][tr], wheel_times diff --git a/visual_stim/Fredericksen_etal_97.pdf b/visual_stim/Fredericksen_etal_97.pdf deleted file mode 100644 index a34996ead..000000000 Binary files a/visual_stim/Fredericksen_etal_97.pdf and /dev/null differ diff --git a/visual_stim/literature.md b/visual_stim/literature.md new file mode 100644 index 000000000..458ebd761 --- /dev/null +++ b/visual_stim/literature.md @@ -0,0 +1,4 @@ +Literature +========== + +* Fredericksen RE, Bex PJ, Verstraten FA. How big is a Gabor patch, and why should we care? J Opt Soc Am A Opt Image Sci Vis. 1997 Jan;14(1):1-12. doi: [10.1364/josaa.14.000001](https://doi.org/10.1364/josaa.14.000001). PMID: 8988615. \ No newline at end of file