ax-check.com
by Gauge
D
Pergola
docs.pergola.cloud
ax-check.com
Checked 25 September 2026
B
81/100
Agents need your login
Pergola docs get agents to a working quickstart fast
17 of 23 checklist items pass: llms.txt, CLI install, API spec and MCP docs are all in place. Pricing page itself wasn't crawled here.
2 fixes to start with
Concrete changes, backed by what we found.
Recorded coding sessions
Same task, 3 models, no docs.pergola.cloud account. Recorded 25 September 2026 on Pi.
3 recorded · 5m 29s
5m 29s · recorded
Session findings
Select a finding to see it in context.
›Thinking…
I need to understand what "Pergola" is. Let me look at the repository.
✓$ ls -la /sandbox/repo; cat /sandbox/repo/README* 2>/dev/null | head -100✓└{"content":[{"text":"total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 25 12:33 ..\ndrwxr-xr-x 8 root root 4096 Sep 24 20:05…ctrl+o
{"content":[{"text":"total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 25 12:33 ..\ndrwxr-xr-x 8 root root 4096 Sep 24 20:05 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\n","type":"text"}]}›Thinking…
I'm checking the git history and branches to see what's in the repository.
✓$ cd /sandbox/repo && git log --oneline | head -20; git branch -a; cat .gitignore✓└{"content":[{"text":"9ebf5fb Initial workspace\n* main\n# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# Jav…ctrl+o
{"content":[{"text":"9ebf5fb Initial workspace\n* main\n# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# JavaScript / TypeScript\nnode_modules/\n.next/\ndist/\nbuild/\ncoverage/\n\n# Python\n.venv/\nvenv/\n__pycache__/\n*.py[cod]\n.pytest_cache/\n*.egg-info/\n\n# Rust\ntarget/\n\n# Go / PHP / Ruby vendored deps\nvendor/\n.bundle/\n\n# Java / Kotlin / Scala\n.gradle/\n\n# .NET\nbin/\nobj/\n\n# Elixir\n_build/\ndeps/\n\n# Dart / Flutter\n.dart_tool/\n","type":"text"}]}›Thinking…
The repo is empty. The user is asking about "Pergola". I don't know what Pergola is. Possibly it's a fictional/unknown product. Let me search online? I have no network tool necessarily. Let me check if there's any network access, and search for "Pergola" — maybe it's a known product. There's "Pergola" ... I recall "Pergola" could be a data platform? Let me check pip/npm availability.
The instruction says: "If this product has no developer workflow you can act on, say so plainly and stop." So likely this is a test where Pergola is an unknown product with no docs. Let me check quickly whether there's any info: search npm, pip, network.
Let me do a quick check.
✓$ cd /tmp && (pip download pergola --no-deps -d /tmp/perg 2>&1 | tail -3); echo "---npm---"; npm view pergola 2>&1 | head -20; echo "---curl---"; curl -s -m 8 -o /dev/null -w "%{http_code}\n" https://py…✓└{"content":[{"text":"Downloading pergola-0.2.0-py2.py3-none-any.whl (57 kB)\nSaved ./perg/pergola-0.2.0-py2.py3-none-any.whl\nSuccessfully downloaded pergola\n-…ctrl+o
cd /tmp && (pip download pergola --no-deps -d /tmp/perg 2>&1 | tail -3); echo "---npm---"; npm view pergola 2>&1 | head -20; echo "---curl---"; curl -s -m 8 -o /dev/null -w "%{http_code}\n" https://pypi.org 2>&1
{"content":[{"text":"Downloading pergola-0.2.0-py2.py3-none-any.whl (57 kB)\nSaved ./perg/pergola-0.2.0-py2.py3-none-any.whl\nSuccessfully downloaded pergola\n---npm---\n\npergola@1.0.0 | MIT | deps: none | versions: 3\nFunctional utilities.\nhttps://github.com/Stassi/pergola#readme\n\nkeywords: arithmetic, comparison\n\ndist\n.tarball: https://registry.npmjs.org/pergola/-/pergola-1.0.0.tgz\n.shasum: abde3f01c1701b990d4dac4748bdcea8888726c4\n.integrity: sha512-UC8PIMZ+7qu2BqjrBcyKshYgcbTcihd8wtxViC+JJY/iP4wLZk8PM/0tMDAb1SNd9It47RgMAWDwrDHqkCBzug==\n.unpackedSize: 13.8 kB\n\nmaintainers:\n- stassi <andreas@stassi.net>\n\ndist-tags:\nlatest: 1.0.0\n\npublished over a year ago by stassi <andreas@stassi.net>\n---curl---\n200\n","type":"text"}]}›Thinking…
Interesting — there's a PyPI package "pergola" 0.2.0. Let me inspect it. That's probably the intended product (maybe a fictional product with docs about pricing?). Let's unzip and inspect.
✓$ cd /tmp/perg && unzip -o -q pergola-0.2.0-py2.py3-none-any.whl -d x && find x -type f | head -60 && echo "---" && cat x/*.dist-info/METADATA | head -120✓└{"content":[{"text":"x/scripts/pergola_rules.py\nx/scripts/pergola_isatab.py\nx/scripts/jaaba_to_pergola.py\nx/scripts/__init__.py\nx/pergola-0.2.0.dist-info/RE…ctrl+o
{"content":[{"text":"x/scripts/pergola_rules.py\nx/scripts/pergola_isatab.py\nx/scripts/jaaba_to_pergola.py\nx/scripts/__init__.py\nx/pergola-0.2.0.dist-info/RECORD\nx/pergola-0.2.0.dist-info/entry_points.txt\nx/pergola-0.2.0.dist-info/WHEEL\nx/pergola-0.2.0.dist-info/METADATA\nx/pergola-0.2.0.dist-info/top_level.txt\nx/pergola-0.2.0.dist-info/LICENSE\nx/pergola/intervals.py\nx/pergola/tracks.py\nx/pergola/jaaba_parsers.py\nx/pergola/__init__.py\nx/pergola/_version.py\nx/pergola/parsers.py\nx/pergola/mapping.py\nx/pergola/isatab_parser.py\n---\nMetadata-Version: 2.1\nName: pergola\nVersion: 0.2.0\nSummary: A library to analyze and visualize behavioral data by unlocking genomic tools\nHome-page: http://github.com/cbcrg/pergola\nAuthor: Jose Espinosa-Carrasco\nAuthor-email: espinosacarrascoj@gmail.com\nLicense: GPLv3\nPlatform: UNKNOWN\nClassifier: Development Status :: 2 - Pre-Alpha\nClassifier: Environment :: Console\nClassifier: Intended Audience :: Developers\nClassifier: Intended Audience :: Other Audience\nClassifier: Intended Audience :: Science/Research\nClassifier: License :: OSI Approved :: GNU General Public License (GPL)\nClassifier: Natural Language :: English\nClassifier: Operating System :: MacOS\nClassifier: Operating System :: POSIX :: Linux\nClassifier: Operating System :: Microsoft :: Windows\nClassifier: Programming Language :: Python\nClassifier: Topic :: Scientific/Engineering :: Bio-Informatics\nClassifier: Topic :: Scientific/Engineering :: Information Analysis\nClassifier: Topic :: Scientific/Engineering :: Visualization\nClassifier: Topic :: Software Development :: Libraries :: Python Modules\n\n|Pergola web server|\n====================\n\n.. figure:: https://circleci.com/gh/cbcrg/pergola.png?style=shield\n :alt: CircleCI status\n\n CircleCI status\n\nA python library for the conversion of behavioral data into genome\nbrowser compatible formats. Once behavioral data is formatted in genomic\nformats it is easy to use genomic software tools for its analysis. For\ninstance you can query your data using BEDTools or visualize your\nrecords using a genome browser (see snapshot below corresponding to a\nrendering of mice feeding behavior on the\n`IGV <http://software.broadinstitute.org/software/igv/>`__ genome\nbrowser).\n\nFor more information, instructions for installation and tutorials please\nvisit:\n\n`Pergola documentation <http://cbcrg.github.io/pergola/>`__\n\nYou can also use our web server still under development:\n\n`Pergola web server <http://pergola.crg.eu/>`__\n\n.. figure:: https://github.com/cbcrg/pergola/blob/master/doc/source/images/mouse_hf_igv.png\n :alt: Mouse feeding behavior-IGV snapshot\n\n Mouse feeding behavior-IGV snapshot\n\n.. |Pergola web server| image:: https://cloud.githubusercontent.com/assets/6224346/12887167/dcf80b24-ce72-11e5-8389-90122fd6c84e.png\n\n\n\n","type":"text"}]}›Thinking…
So Pergola is an open-source Python library (GPLv3) for converting behavioral data into genome-browser formats. It's not a hosted/SaaS product with pricing. The user asked "Tell me how pricing works" — there is no pricing; it's free open source. And "whether this product will be easy for you to manage" — it's a library, not a service; no API keys, no dashboard.
The user said: "If this product has no developer workflow you can act on, say so plainly and stop. Stay light: use the hosted product through its SDK or API." Pergola has no hosted API (web server was "under development" in 2016, likely dead). But there IS a developer workflow I can act on: the PyPI package is an SDK-ish Python library I can install and use locally without service stacks. That's light — install a wheel, run a conversion example. That's actionable and doesn't require local service stacks or long-running commands.
So I should build a simple example: use the pergola library to convert behavioral data (e.g., a timeline of mouse feeding events) into a genome-browser-compatible format (BED/GFF), and report honestly on pricing (none, GPLv3 open source, no hosted paid product) and manageability.
Let me check dependencies and whether it installs cleanly. Check the package contents: pergola/tracks.py, parsers, mapping, intervals. Let's look.
Also check whether it requires pybedtools etc. Let's inspect.
✓$ cd /tmp/perg/x && cat pergola-0.2.0.dist-info/entry_points.txt && head -40 pergola/__init__.py && echo "=== tracks" && head -80 pergola/tracks.py✓└{"content":[{"text":"[console_scripts]\njaaba_to_pergola = scripts.jaaba_to_pergola:main\npergola = scripts.pergola_rules:main\npergola_isatab.py = scripts.perg…ctrl+o
{"content":[{"text":"[console_scripts]\njaaba_to_pergola = scripts.jaaba_to_pergola:main\npergola = scripts.pergola_rules:main\npergola_isatab.py = scripts.pergola_isatab:main\npergola_rules.py = scripts.pergola_rules:main\n\n# Copyright (c) 2014-2019, Centre for Genomic Regulation (CRG).\n# Copyright (c) 2014-2019, Jose Espinosa-Carrasco and the respective authors.\n#\n# This file is part of Pergola.\n#\n# Pergola is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Pergola is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Pergola. If not, see <http://www.gnu.org/licenses/>.\n\n\"\"\"\n===========\nDescription\n===========\n\nPergola is a python library for the conversion of behavioral data into genome browser\ncompatible formats. The adoption of genomic formats enables the analysis and\nvisualization of longitudinal behavioral data by genomic software tools.\n\n============\nInstallation\n============\n\npergola requires:\n\n* Python >=2.6\n* numpy (http://numpy.scipy.org/)\n* argparse (http://code.google.com/p/argparse/)\n* csv (http://www.object-craft.com.au/projects/csv/)\n* biopy-isatab (https://github.com/ISA-tools/biopy-isatab)\n* scipy (https://www.scipy.org/)\n* pybedtools (https://daler.github.io/pybedtools/)\n=== tracks\n# Copyright (c) 2014-2019, Centre for Genomic Regulation (CRG).\n# Copyright (c) 2014-2019, Jose Espinosa-Carrasco and the respective authors.\n#\n# This file is part of Pergola.\n#\n# Pergola is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Pergola is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Pergola. If not, see <http://www.gnu.org/licenses/>.\n\n\"\"\"\n======================\nModule: pergola.tracks\n======================\n\n.. module:: tracks\n\nThis module provides the structures to keep the data in genomic format.\nIt provides a generic class :class:`~pergola.tracks.GenomicContainer` which has \nthe general attributes and methods shared by all the other subclasses. \n\nThese subclasses provide special features for each type of data and are:\n \n:py:class:`~pergola.tracks.Track` objects are generated by\n:func:`pergola.intervals.IntData.read` and hold the parsed and manipulated\ndata as an iterator and the attributes generated when reading file \n\n:py:class:`~pergola.tracks.Bed` :class:`~pergola.tracks.GenomicContainer` class \nfor Bed files.\n\n:py:class:`~pergola.tracks.BedGraph` :class:`~pergola.tracks.GenomicContainer` \nclass for BedGraph files.\n\n\"\"\"\nfrom __future__ import print_function\n\nfrom os import getcwd\nfrom sys import stderr, exit\nfrom os.path import join\nfrom operator import itemgetter\nfrom itertools import groupby\nfrom numpy import arange\nimport tempfile\nfrom pybedtools import BedTool\nfrom ntpath import split as path_split\n\n## Contains class and file extension\n_dict_file = {'bed' : ('Bed', 'track_convert2bed', '.bed'), \n 'bedGraph': ('BedGraph', 'track_convert2bedGraph', '.bedGraph'),\n 'gff': ('Gff', 'track_convert2gff', '.gff'),\n 'txt': ('Track', '', '.txt')}\n\n# From light to dark\n# n_interval = 9\n\n# _black_gradient = [\"226,226,226\", \"198,198,198\", \"170,170,170\", \"141,141,141\", \"113,113,113\", \"85,85,85\", \"56,56,56\", \"28,28,28\", \"0,0,0\"]\n# _blue_gradient = [\"224,255,255\", \"173,216,230\", \"135,206,250\", \"135,206,235\", \"65,105,225\", \"0,0,255\", \"0,0,205\", \"0,0,139\", \"0,0,128\"] \n# _red_gradient = [\"254,172,182\", \"254,153,162\", \"254,134,142\", \"254,115,121\", \"254,96,101\", \"254,77,81\", \"254,57,61\", \"254,38,40\", \"254,19,20\"]\n# _green_gradient = [\"203,254,203\", \"178,254,178\", \"152,254,152\", \"127,254,127\", \"102,254,102\", \"76,254,76\", \"51,254,51\", \"0,254,0\", \"25,115,25\"]\n# _orange_gradient = [\"255,223,0\", \"255,240,0\", \"255,220,0\", \"255,200,0\", \"255,180,0\", \"255,160,0\", \"255,140,0\", \"250,115,0\", \"255,100,0\"]\n\nn_interval = 4\n\n## short gradients\n_black_gradient = [\"170,170,170\", \"113,113,113\", \"85,85,85\", \"56,56,56\", \"28,28,28\", \"0,0,0\"]\n_blue_gradient = [\"135,206,250\", \"65,105,225\", \"0,0,255\", \"0,0,205\", \"0,0,139\", \"0,0,128\"] \n_red_gradient = [\"254,134,142\", \"254,96,101\", \"254,77,81\", \"254,57,61\", \"254,38,40\", \"254,19,20\"]\n_green_gradient = [\"152,254,152\", \"102,254,102\", \"76,254,76\", \"51,254,51\", \"0,254,0\", \"25,115,25\"]\n_orange_gradient = [\"255,220,0\", \"255,180,0\", \"255,160,0\", \"255,140,0\", \"250,115,0\", \"255,100,0\"]\n_yellow_gradient = [\"255,255,0\", \"255,255,31\", \"255,255,51\", \"255,255,102\", \"255,255,153\", \"255,255,204\"]\n_pink_gradient = [\"229,0,174\", \"234,38,190\", \"239,77,206\", \"244,116,222\", \"246,135,230\", \"254,194,254\"]\n_cyan_gradient = [\"0,186,229\", \"38,199,234\", \"58,205,236\", \"116,225,244\", \"135,231,246\", \"174,244,251\"]\n","type":"text"}]}›Thinking…
Requires pybedtools (needs bedtools binary), numpy, scipy. pybedtools needs the bedtools binary for some ops but basic BED file writing via pybedtools BedTool objects may work without bedtools binary for simple ops. Let's try installing and running a minimal example: IntData.read of a CSV/interval file, then track_convert2bed.
Let's check intervals.py and parsers.py to understand input format.
✓$ cd /tmp/perg/x && sed -n 1,140p pergola/parsers.py✓└{"content":[{"text":"# Copyright (c) 2014-2019, Centre for Genomic Regulation (CRG).\n# Copyright (c) 2014-2019, Jose Espinosa-Carrasco and the respective aut…ctrl+o
{"content":[{"text":"# Copyright (c) 2014-2019, Centre for Genomic Regulation (CRG).\n# Copyright (c) 2014-2019, Jose Espinosa-Carrasco and the respective authors.\n#\n# This file is part of Pergola.\n#\n# Pergola is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Pergola is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Pergola. If not, see <http://www.gnu.org/licenses/>.\n\n\"\"\"\n=========================\nModule: pergola.parsers\n=========================\n\n.. module:: parsers\n\nThis module provides the way to read scripts options provided by pergola library.\n\n\n\"\"\"\nfrom __future__ import print_function\nfrom __future__ import absolute_import\n\nfrom ._version import __version__\nfrom sys import stderr\nfrom argparse import ArgumentParser, ArgumentTypeError\nfrom re import match\nfrom os.path import abspath, split, realpath\nfrom .mapping import check_path\n\n_csv_file_ext = \".csv\"\n\n_dt_act_options = ['all', 'one_per_channel']\n_tr_act_options = ['split_all', 'join_all', 'join_odd', 'join_even']\n_starting_phase_options = ['light', 'dark']\n\nPATH = abspath(split(realpath(__file__))[0])\n\n\ndef parse_num_range(string):\n \"\"\" \n This function generate a numeric range from a string containing the boundaries\n From 1-4 generates a string \n \n :param tracks: :py:func:`set` of tracks to which track_action should be applied set([1,2])\n :param delimiter: :py:func:`str` option to join tracks (join_all, split_all, join_odd, join_evens)\n \n :return: :py:func:`set` with all the numbers in range as strings\n \n \"\"\"\n \n m = match(r'(\\d+)(?:-(\\d+))?$', string)\n\n if not m:\n raise ArgumentTypeError(\"'\" + string + \"' is not a range of number. Expected '0-5' or '2'.\")\n start = m.group(1)\n end = m.group(2) or start\n list_range=list(range(int(start,10), int(end,10)+1))\n set_range=set(['{0}'.format(t) for t in list_range]) #str because track can be set in the form of track_1 for instance\n \n return set_range\n\n\ndef read_track_actions (tracks, track_action = \"split_all\"):\n \"\"\" \n Read track actions and returns a set with the tracks to be joined\n \n :param tracks: :py:func:`set` of tracks to which track_action should be applied set([1,2])\n :param track_action: :py:func:`str` option to join tracks (join_all, split_all, join_odd, join_evens)\n \n :return: :py:func:`set` of tracks to be joined\n \n \"\"\"\n \n if track_action not in _tr_act_options:\n raise ValueError(\"Track_action \\'%s\\' not allowed. Possible values are %s\"%(track_action,', '.join(['{}'.format(m) for m in _tr_act_options])))\n \n tracks2merge = \"\"\n \n if track_action == \"join_all\":\n tracks2merge = tracks\n elif track_action == 'join_odd':\n tracks2merge = set([t for t in tracks if int(t) % 2])\n elif track_action == 'join_even':\n tracks2merge = set([t for t in tracks if not int(t) % 2])\n else:\n tracks2merge = \"\"\n \n print(\"Tracks to merge are: \", \",\".join(\"'{0}'\".format(t) for t in tracks2merge), file=stderr)\n \n if not tracks2merge:\n print((\"No track action applied as track actions \\'%s\\' can not be applied to list of tracks provided \\'%s\\'\"%(track_action, \" \".join(tracks))), file=stderr)\n \n return (tracks2merge)\n\n\ndef read_colors (path_color_file):\n \"\"\" \n Reads user colors for each data_type \n \n :param None path_color_file: :py:func:`str` path to read user color for data_types \n \n :returns: d_user_color dictionary {'data_type_1': 'orange', 'data_type_2':'blue'}\n \n \"\"\"\n\n check_path(path_color_file)\n comment_tag_t = \"#\" \n d_user_color = {} \n \n with open(path_color_file) as f: \n \n for row in f:\n \n if(row.startswith(comment_tag_t) or row.startswith('\\n')): \n continue\n \n row_split = row.rstrip('\\n').split('\\t') \n (data_type, color) = row_split\n \n # colors are checked inside tracks.assign_color\n d_user_color[data_type] = color\n \n return d_user_color\n\n\"\"\"\" \nParsers arguments of pergola_rules.py script (aka pergola)\n\n\"\"\"\n\nparent_parser = ArgumentParser(description = 'Script to transform behavioral data into GB readable data', add_help=False)\n","type":"text"}]}›Thinking…
Now look at intervals.py IntData.read signature and how the input CSV looks (probably the CRG format with columns). Let's read intervals.py briefly and check the CLI script pergola_rules.py to know the expected input format.
✓$ cd /tmp/perg/x && grep -n "def read" -A 60 pergola/intervals.py | head -120✓└{"content":[{"text":"393: def read(self, fields=None, relative_coord=False, intervals=False, int_step=None, fields2rel=None, multiply_t=None,\n394- …ctrl+o
{"content":[{"text":"393: def read(self, fields=None, relative_coord=False, intervals=False, int_step=None, fields2rel=None, multiply_t=None,\n394- **kwargs):\n395- \"\"\" \n396- Reads the data and converts it depending on selected options\n397- \n398- :param None fields: :py:func:`list` with data columns to read\n399- :param False relative_coord: If true all coordinates in start and end are\n400- make relative to the minimal value\n401- :param False intervals: if set to true intervals will be inferred from timepoints in\n402- start\n403- :param None int_step: :py:func:`int` :param None int_step: :py:func:`int` time step value to create \n404- the end of intervals\n405- :param None fields2rel: :py:func:`list` with data columns to make relative\n406- :param None multiply_t: :py:func:`int` multiplies the values of the field set as start and \n407- end \n408- \n409- :returns: Track object\n410- \n411- TODO: By the moment I make this function as a method of the class eventually I would make this a\n412- separated function\n413- Eventually do not change self.data but a list inside read and return the Track object with the modifications\n414- this way data is always the original one.\n415- \n416- \"\"\"\n417-\n418- _f_rel_mand = \"start\"\n419- _f_int_end = \"end\"\n420- _f2rel = [\"start\",\"end\"]\n421- _f2mult = [\"start\",\"end\"]\n422- i_time_f = [10000000000000]\n423-\n424- # If fields is not set then all the data columns are read\n425- if fields is None:\n426- fields = self.fieldsG\n427- indexL = range(len(self.fieldsG))\n428- else:\n429- try:\n430- indexL = [self.fieldsG.index(f) for f in fields]\n431-\n432- except ValueError:\n433- raise ValueError(\"Field '%s' not in file %s.\" % (f, self.path))\n434-\n435- # If start not present out \n436- try:\n437- idx_fields2int = self.fieldsG_dict[_f_rel_mand]\n438- except ValueError:\n439- raise ValueError(\"Parameter intervals=True needs that field '%s' is not missing in file %s.\"\n440- % (_f_rel_mand, self.path))\n441-\n442- ##################################\n443- # If there are several tracks we order by track\n444- # Control for interval change bw tracks \n445- _f_track = \"track\"\n446- i_track = None\n447-\n448- if _f_track in self.fieldsG_dict:\n449- i_track = self.fieldsG_dict[_f_track]\n450-\n451- if all(row[i_track].isdigit() for row in self.data):\n452- self.data = sorted(self.data, key=lambda x: (int(x[i_track]), x[idx_fields2int]))\n453- else:\n","type":"text"}]}›Thinking…
Now, plan: install pergola into a venv, create a small CSV of behavioral data, run pergola CLI or python API to convert to BED. Check dependencies install (numpy, scipy, pybedtools, biopy-isatab). That's heavy-ish but pip install should be fine (fast?). pybedtools requires bedtools binary only when calling operations; writing BED via pybedtools may work.
Note the user said don't start local service stacks; installing pip packages is fine. Let's try. Time it.
×$ cd /sandbox/repo && python3 -m venv .venv && . .venv/bin/activate && pip install -q --upgrade pip 2>&1 | tail -2 && timeout 600 pip install -q pergola 2>&1 | tail -20; python -c "import pergola, pybed…✓└{"content":[{"text":"Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\nModuleNotFoundError: No module named 'pybedtools'\n\n\nComman…ctrl+o
cd /sandbox/repo && python3 -m venv .venv && . .venv/bin/activate && pip install -q --upgrade pip 2>&1 | tail -2 && timeout 600 pip install -q pergola 2>&1 | tail -20; python -c "import pergola, pybedtools, numpy, scipy; print('ok', pergola.__file__)"
{"content":[{"text":"Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\nModuleNotFoundError: No module named 'pybedtools'\n\n\nCommand exited with code 1","type":"text"}],"details":{}}›Thinking…
pergola installed but pybedtools not declared as dependency in setup? Let's see what got installed and check whether pergola imports (it imports pybedtools at module load in tracks.py). So we need pybedtools too.
✓$ cd /sandbox/repo && . .venv/bin/activate && pip list 2>/dev/null | head -30 && timeout 600 pip install -q pybedtools 2>&1 | tail -5; python -c "✓└{"content":[{"text":"Package Version\n------- -------\npergola 0.2.0\npip 26.2.1\npergola import ok\n","type":"text"}]}ctrl+o
cd /sandbox/repo && . .venv/bin/activate && pip list 2>/dev/null | head -30 && timeout 600 pip install -q pybedtools 2>&1 | tail -5; python -c "
import pybedtools, pergola
from pergola import intervals
print('pergola import ok')
"
›Thinking…
Wait, pybedtools installed? pip list showed only pergola and pip before install. Then after install it printed 'pergola import ok' so pybedtools installed fine (numpy/scipy as deps). Check bedtools binary availability — not needed hopefully.
Now build the example. Look at the CLI pergola_rules.py main to see options, and intervals.IntData constructor.
✓$ cd /tmp/perg/x && sed -n 140,260p pergola/parsers.py✓└{"content":[{"text":"parent_parser = ArgumentParser(description = 'Script to transform behavioral data into GB readable data', add_help=False)\n\nparent_parser.…ctrl+o
{"content":[{"text":"parent_parser = ArgumentParser(description = 'Script to transform behavioral data into GB readable data', add_help=False)\n\nparent_parser.add_argument('-i', '--input', required=True, metavar=\"PATH\", nargs='+', help='Input file path')\nparent_parser.add_argument('-m', '--mapping_file', required=True, metavar=\"MAPPING_FILE\",\n help='File to set the reciprocity between fields in behavioral file and terms used by Pergola' + \\\n ' and genome browser grammar')\nparent_parser.add_argument('-t', '--tracks', required=False, metavar=\"TRACKS\", type=int, nargs='+', \n help='List of selected tracks')\nparent_parser.add_argument('-l','--list', required=False, metavar=\"LIST_OF_TRACKS\", type=str, nargs='+',\n help='Numeric list of tracks to be joined in a single genomic like file')### string allowed as some tracks could be named as: track_1, track2....\nparent_parser.add_argument('-r', '--range', required=False, type=parse_num_range,\n help='Numeric range of tracks to be joined in a single genomic like file')\nparent_parser.add_argument('-a', '-ta', '--track_actions', required=False, choices=_tr_act_options,\n help='Option of action with tracks selected, split_all, join_all,' + \\\n ' join_odd, join_even, join_range or join_list')\nparent_parser.add_argument('-dl', '--data_types_list', required=False, metavar=\"LIST_OF_DATA_TYPES\", type=str, nargs='+',\n help='List of data_types to be joined')\nparent_parser.add_argument('-d', '--data_types_actions', required=False, choices=_dt_act_options,\n help='Unique values of data_types field should be dumped on' + \\\n ' different data structures or not')\nparent_parser.add_argument('-f', '--format', required=False, type=str, default='bed',\n help='Write file output format (bed or bedGraph)')\nparent_parser.add_argument('-e', '-rel', '--relative_coord', required=False, action='store_true',\n default=False, help='Sets first timepoint' \\\n ' to 0 and make all the others relative to this timepoint')\nparent_parser.add_argument('-n', '-int', '--intervals_gen', required=False, action='store_true',\n default=None, help='Set startChrom and endChrom from just a timepoint in the file ' \\\n 'using field set as startChrom')\nparent_parser.add_argument('-ns', '-int_s', '--interval_step', required=False, metavar=\"INTERVAL_STEP\", type=int,\n default=False, help='Set step to generate intervals from a single time point')\nparent_parser.add_argument('-mi', '--multiply_intervals', metavar='N', type=int, required=False,\n help='Multiplies value in data_value by the given factor')\nparent_parser.add_argument('-nh', '--no_header', required=False, action='store_true', \n default=False, help='Data file contains no header')\nparent_parser.add_argument('-s', '-fr', '--fields_read', metavar='FIELDS2READ', type=str, nargs='+',\n help='List of fields to read from input file')\nparent_parser.add_argument('-w', '--window_size', required=False, metavar=\"WINDOW_SIZE\", type=int, \n help='Window size for bedGraph intervals, default value 300')\nparent_parser.add_argument('-nt', '--no_track_line', required=False, action='store_true',\n default=False, help='Track line no included in the bed file')\nparent_parser.add_argument('-fs', '--field_separator', required=False, type=str,\n default=False, help='Input file field separator')\nparent_parser.add_argument('-bl', '--bed_label', required=False, action='store_true',\n default=False, help='Show data_types as name field in bed file')\nparent_parser.add_argument('-c', '--color_file', required=False, metavar=\"PATH_COLOR_FILE\", \n help='Dictionary assigning colors of data_types path')\nparent_parser.add_argument('-wm', '--window_mean', required=False, action='store_true',\n default=False, help='Window values averaged by the window size')\nparent_parser.add_argument('-vm', '--value_mean', required=False, action='store_true',\n help='Window values averaged by number of items within window')\nparent_parser.add_argument('-min', '--min_time', type=int, required=False,\n help='Initial time point to extract')\nparent_parser.add_argument('-max', '--max_time', type=int, required=False,\n help='Last time point to extract')\nparent_parser.add_argument('-ng', '--no_genome', required=False, dest='genome', action='store_false',\n help='Avoinds the creation of a FASTA file which allows to render a longitudinal trajectory' \\\n ' in a genome browser')\nparent_parser.add_argument('-np', '--no_phases', required=False, dest='phases', action='store_false',\n help='Avoids the creation of a phases bed file')\nparent_parser.set_defaults(genome=True)\nparent_parser.set_defaults(phases=True)\nparent_parser.add_argument('-o', '--output_file_name', help='File name for output files')\nparent_parser.add_argument('-sp', '--starting_phase', type=str, required=False, choices=_starting_phase_options,\n help='Sets the first phase to appear in the phases and cytoband file: light or dark\\n')\nparent_parser.add_argument('-sh', '--shift', required=False, metavar=\"TIME_SHIFT\", type=int,\n help='Time shift to be set for the first phase')\nparent_parser.add_argument('-v', '--version', action='version', version='%(prog)s {version}'.format(version=__version__))\n\n\"\"\"\" \nParsers argument of jaaba_to_pergola.py script\n\n\"\"\"\n\njaaba_parser = ArgumentParser(description = 'Script to transform Jaaba annotations into Pergola readable formats', \n add_help=False)\njaaba_parser.add_argument('-v', '--version', action='version', version='%(prog)s {version}'.format(version=__version__))\n\nsubparsers = jaaba_parser.add_subparsers(help='Calls pergola_rules.py', dest='command')\njaaba_parser_sp = subparsers.add_parser('sp', help=\"Converts Jaaba data and process it using pergola\", parents=[parent_parser])\njaaba_parser_sc = subparsers.add_parser('sc', add_help='Converts scores Jaaba files into csv files')\njaaba_parser_fp = subparsers.add_parser('fp', add_help='Converts Jaaba features using pergola', parents=[parent_parser])\njaaba_parser_fc = subparsers.add_parser('fc', add_help='Converts Jaaba features into csv files')\n\njaaba_parser_sc.add_argument('-i', '--input', required=True, metavar=\"PATH\", help='Input file path')\n\njaaba_parser_fc.add_argument('-i', '--input', required=True, metavar=\"PATH\", help='Path to jaaba features')\njaaba_parser_fc.add_argument('-jf', '--feature', required=True, metavar=\"LIST_OF_FEATURES\", type=str, nargs='+',\n help='List of features to be extracted, e.g. velmag')\njaaba_parser_fc.add_argument('-dd', '--dumping_dir', required=False, metavar=\"DUMPING_DIR\", help='Input file path')\n# jaaba_parser_fp.add_argument('-i', '--input_jaaba_dir', required=True, metavar=\"PATH\", help='Input file path')\n# jaaba_parser_fp.add_argument('-jd', '--jaaba_dir', required=True, metavar=\"PATH\", help='Path to jaaba features')\njaaba_parser_fp.add_argument('-jf', '--feature', required=True, metavar=\"LIST_OF_FEATURES\", type=str, nargs='+',\n help='List of features to be extracted, e.g. velmag')\njaaba_parser_fp.add_argument('-dd', '--dumping_dir', required=False, metavar=\"DUMPING_DIR\", help='Input file path')\n","type":"text"}]}›Thinking…
Need mapping file format. Check mapping.py.
✓$ cd /tmp/perg/x && grep -n "def \|_mandatory\|term" pergola/mapping.py | head -50✓└{"content":[{"text":"7:# it under the terms of the GNU General Public License as published by\n47:_p_ontology_terms = [\"start\", \"data_value\", \"end\", \"da…ctrl+o
{"content":[{"text":"7:# it under the terms of the GNU General Public License as published by\n47:_p_ontology_terms = [\"start\", \"data_value\", \"end\", \"data_types\", \"track\", \"chrom\", \"dummy\"]\n68: def __init__(self, path, **kwargs):\n72: def _correspondence_from_config(self, path):\n101: def _tab_config(self, file_tab):\n121: file_term = row_split[0]\n122: pergola_term = row_split[1]\n124: # Validation of the ontology term\n125: if pergola_term not in _p_ontology_terms:\n126: raise ValueError(\"Term %s is not a valid pergola term.\" % (pergola_term))\n128: if pergola_term == \"dummy\": \n129: pergola_term = pergola_term + \"_\" + str(dummy_ctr)\n132: dict_correspondence[file_term] = pergola_term\n136: def _mapping_config(self, file_map):\n148: mapping_l_ex = \"tag_file:field_input_file > pergola:pergola_ontology_term\"\n160: file_term = l[0].split(\":\")[1].rstrip()\n161: pergola_term = l[1].split(\":\")[1].rstrip('\\t\\n\\r')\n163: # Validation of the ontology term\n164: if pergola_term not in _p_ontology_terms:\n165: raise ValueError(\"Term %s is not a valid pergola term.\" % (pergola_term))\n167: if pergola_term == \"dummy\": \n168: pergola_term = pergola_term + \"_\" + str(dummy_ctr)\n171: dict_correspondence[file_term] = pergola_term\n182: def write(self, indent=0):\n184: Writes correspondence between the pergola ontology terms and the behavioral data \n199:def check_path(path):\n219:def write_chr(self, mode=\"w\", path_w=None, min_c=None, max_c=None):\n256:def write_chr_sizes(self, mode=\"w\", path_w=None, file_n=None, min_c=None, max_c=None):\n302:def write_cytoband(end, start=0, delta=43200, start_phase=\"light\", mode=\"w\", path_w=None, lab_bed=True, track_line=True):\n426:def write_period_seq (end, start=0, delta=43200, tag=\"day\", mode=\"w\", path_w=None, name_file=\"period_seq\", lab_bed=True, track_line=True):\n","type":"text"}]}›Thinking…
Mapping file format (tab or mapping config). Let's see _tab_config lines 101-135 for format details (comments, header?).
✓$ cd /tmp/perg/x && sed -n 60,140p pergola/mapping.py✓└{"content":[{"text":" \n .. attribute:: correspondence \n Dictionary with keys fields in behavioral file and values correspondence in genomic gramm…ctrl+o
{"content":[{"text":" \n .. attribute:: correspondence \n Dictionary with keys fields in behavioral file and values correspondence in genomic grammar\n \n :returns: MappingInfo object\n \n \"\"\"\n \n def __init__(self, path, **kwargs):\n self.path = check_path(path)\n self.correspondence = self._correspondence_from_config(self.path)\n\n def _correspondence_from_config(self, path):\n \"\"\"\n Recognizes the type of mapping file tab separated or the External Mapping File Format\n from the Gene Ontology Consortium. Deletes comments \n\n :param path: :py:func:`str` path to the mapping file aka configuration file\n\n :returns: dictionary with mappings\n\n \"\"\"\n\n with open(path) as config_file:\n # Eliminates possible empty lines at the end\n config_file_list = list(filter(lambda x: not match(r'^\\s*$', x), config_file))\n \n file_list_no_comments = [l for l in config_file_list if l[0] != \"!\" if l[0] != \"#\"]\n\n for i, line in enumerate(file_list_no_comments):\n if not match(r\"^\\w+\\:[\\\"\\w\\\"]|[\\w]+\\s\\>\\s\\w+\\:\\w+] \", line) and not match(r\"(\\w+)\\s+(\\w+)\", line):\n raise TypeError(\"Mapping file format is not recognized: \\\"%s\\\".\" % (path))\n\n # Checks that files is a tsv and has the desired number of fields\n if match(r\"^\\w+\\:[\\\"\\w\\\"]|[\\w]+\\s\\>\\s\\w+\\:\\w+] \", file_list_no_comments[0]):\n return self._mapping_config(file_list_no_comments)\n elif match(r\"(\\w+)\\s+(\\w+)\", file_list_no_comments[0]):\n return self._tab_config(file_list_no_comments)\n else:\n raise TypeError(\"Mapping file format is not recognized: \\\"%s\\\".\" % (path))\n\n def _tab_config(self, file_tab):\n \"\"\"\n Reads mappings from a tab separated files \n \n :param file_tab: :py:func:`list` with the content of mapping file \n \n :returns: dictionary with mappings\n\n \"\"\"\n\n dict_correspondence = {}\n comment_tag_t = \"#\"\n \n dummy_ctr=0\n \n for row in file_tab:\n if row.startswith(comment_tag_t):\n continue\n row_split = split(r'\\s+', row.rstrip('\\n'))\n\n file_term = row_split[0]\n pergola_term = row_split[1]\n \n # Validation of the ontology term\n if pergola_term not in _p_ontology_terms:\n raise ValueError(\"Term %s is not a valid pergola term.\" % (pergola_term))\n \n if pergola_term == \"dummy\": \n pergola_term = pergola_term + \"_\" + str(dummy_ctr)\n dummy_ctr = dummy_ctr + 1\n \n dict_correspondence[file_term] = pergola_term\n\n return (dict_correspondence) \n\n def _mapping_config(self, file_map):\n \"\"\"\n Reads mappings from a file formatted following the External Mapping File Format\n from the Gene Ontology Consortium \n\n","type":"text"}]}›Thinking…
Now build example files in repo:
- data/behavior.csv: columns e.g.
timepoint,behavior,track— with mapping: timepoint -> start, behavior -> data_types, track -> track. Use-nintervals_gen with interval step to create end coordinates. - config/mapping.tsv
Then run pergola -i data/behavior.csv -m config/mapping.tsv -n -ns 1 -f bed -o out/feeding.
Let's check the main in scripts/pergola_rules.py to know output behavior (writes files in cwd?).
✓$ cd /tmp/perg/x && sed -n 1,200p scripts/pergola_rules.py | grep -n "def main" -A 80✓└{"content":[{"text":"37:def main(args=None):\n38- parser_pergola_rules = ArgumentParser(parents=[parsers.parent_parser]) \n39- \n40- args = parser_perg…ctrl+o
{"content":[{"text":"37:def main(args=None):\n38- parser_pergola_rules = ArgumentParser(parents=[parsers.parent_parser]) \n39- \n40- args = parser_pergola_rules.parse_args()\n41-\n42- multiple_files = False\n43-\n44- if len(args.input) > 1:\n45- print(\"@@@Pergola_rules.py: Multiple input files processed\")\n46- multiple_files = True\n47-\n48- for idx, input_file in enumerate(args.input):\n49-\n50- if multiple_files:\n51- if args.output_file_name is None:\n52- output_file_n = splitext(basename(input_file))[0]\n53- else:\n54- output_file_n = args.output_file_name + \"_\" + str(idx + 1)\n55- else:\n56- output_file_n = args.output_file_name\n57-\n58- pergola_rules(path=input_file, map_file_path=args.mapping_file, sel_tracks=args.tracks,\n59- list=args.list, range=args.range, track_actions=args.track_actions,\n60- data_types_actions=args.data_types_actions, data_types_list=args.data_types_list,\n61- write_format=args.format, relative_coord=args.relative_coord,\n62- intervals_gen=args.intervals_gen, interval_step=args.interval_step,\n63- multiply_f=args.multiply_intervals, no_header=args.no_header, fields2read=args.fields_read,\n64- window_size=args.window_size, no_track_line=args.no_track_line, separator=args.field_separator,\n65- bed_lab_sw=args.bed_label, color_dict=args.color_file, window_mean=args.window_mean,\n66- value_mean=args.value_mean, min_t=args.min_time, max_t=args.max_time, phases=args.phases,\n67- genome=args.genome, output_file_name=output_file_n, starting_phase=args.starting_phase,\n68- shift=args.shift)\n69-\n70-def pergola_rules(path, map_file_path, sel_tracks=None, list=None, range=None, track_actions=None, \n71- data_types_actions=None, data_types_list=None, write_format=None, relative_coord=False,\n72- intervals_gen=False, multiply_f=None, no_header=False, fields2read=None, window_size=None,\n73- no_track_line=False, separator=None, bed_lab_sw=False, color_dict=None, window_mean=False,\n74- value_mean=False, min_t=None, max_t=None, interval_step=None, phases=False, genome=False,\n75- output_file_name=None, starting_phase=False, shift=None):\n76- \n77- print(\"@@@Pergola_rules.py: Input file: %s\" % path, file=stderr) \n78- print(\"@@@Pergola_rules.py: Configuration file: %s\" % map_file_path, file=stderr)\n79- \n80- # Tracks selected by user\n81- print(\"@@@Pergola_rules.py: Selected tracks are: \", sel_tracks, file=stderr)\n82- \n83- # Configuration file\n84- map_file_dict = mapping.MappingInfo(map_file_path)\n85- \n86- # Reading color dictionary to set data_types\n87- if color_dict:\n88- print(\"@@@Pergola_rules.py: Color for data_types in file............ %s\" % color_dict, file=stderr)\n89- d_colors_data_types = parsers.read_colors (color_dict)\n90- else:\n91- d_colors_data_types = None \n92- \n93- # Handling list or range of tracks to join if set\n94- if list and range:\n95- raise ValueError(\"@@@Pergola_rules.py: Argument -l/--list and -r/--range are not compatible. \" \\\n96- \"As both arguments set tracks to be joined.\") \n97- elif (list):\n98- tracks2merge = list\n99- elif (range):\n100- tracks2merge = range\n101- else:\n102- tracks2merge = \"\"\n103- \n104- if tracks2merge: print(\"@@@Pergola_rules.py: Tracks to join are............ \", tracks2merge, file=stderr)\n105- \n106- # Handling argument track actions\n107- if tracks2merge and track_actions:\n108- raise ValueError (\"Options --list -l or --range -r are incompatible with \" \\\n109- \"--track_actions -a, please change your options\")\n110- \n111- track_act = track_actions\n112- print(\"@@@Pergola_rules.py: Track actions are.............................. \", track_act, file=stderr)\n113- \n114- data_types_list = data_types_list\n115- print(\"@@@Pergola_rules.py: data_types list is: \", data_types_list, file=stderr)\n116- \n117- # Handling argument data_types actions\n","type":"text"}]}›Thinking…
Fine. Now create the example in the repo: a small dataset + mapping + a Python script that uses the API directly (IntData.read → track_convert2bed) plus CLI usage in README. Let's write files and test.
Design example: mouse feeding behavior timeline. CSV:
timepoint,behavior,track 0,feeding,1 1,resting,1 ...
Mapping: timepoint start behavior data_types track track
Run: pergola -i data/mouse_feeding.csv -m config/mapping.tsv -n -ns 1 -f bed -o mouse_feeding
Also try gff format? -f supports bed or bedGraph per help; _dict_file includes gff. Keep bed.
Also genome/phases files generation — write_cytoband etc. Those default to true (genome=True, phases=True). Fine.
Let's create and run.
✓$ cd /sandbox/repo && mkdir -p data config examples out && python - <<'EOF'✓└{"content":[{"text":"timepoint,behavior,track\r\n0,resting,1\r\n3,feeding,1\r\n7,feeding,1\r\n8,feeding,1\r\n11,feeding,1\r\n50 data/mouse_feeding.csv\n","type"…ctrl+o
cd /sandbox/repo && mkdir -p data config examples out && python - <<'EOF'
import csv, random
random.seed(7)
behaviors = ["feeding", "resting", "grooming", "walking"]
rows = [("timepoint","behavior","track")]
t = 0
for track in (1,2):
t = 0
while t < 60:
dur = random.randint(1,4)
rows.append((t, random.choice(behaviors), track))
t += dur
with open("data/mouse_feeding.csv","w",newline="") as f:
csv.writer(f).writerows(rows)
EOF
cat > config/mapping.tsv <<'EOF'
# behavioral_file_field pergola_ontology_term
timepoint start
behavior data_types
track track
EOF
head -6 data/mouse_feeding.csv; wc -l data/mouse_feeding.csv
{"content":[{"text":"timepoint,behavior,track\r\n0,resting,1\r\n3,feeding,1\r\n7,feeding,1\r\n8,feeding,1\r\n11,feeding,1\r\n50 data/mouse_feeding.csv\n","type":"text"}]}›Thinking…
Hmm, the generated data has consecutive same-behavior rows; fine. Now write the Python API example and run the CLI.
Let's write examples/behavior_to_bed.py using the API.
First inspect IntData constructor signature.
✓$ cd /tmp/perg/x && grep -n "class IntData" -A 60 pergola/intervals.py | sed -n 1,80p✓└{"content":[{"text":"41:class IntData(object):\n42- \"\"\"\n43- Generic class for input data\n44- \n45- .. attribute:: path\n46- \n47- Name…ctrl+o
{"content":[{"text":"41:class IntData(object):\n42- \"\"\"\n43- Generic class for input data\n44- \n45- .. attribute:: path\n46- \n47- Name of path to a csv/tab or xlsx (excel) input file\n48- \n49- .. attribute:: delimiter\n50- \n51- Character use to separate values of the same record in file (default \"\\t\").\n52- \n53- .. attribute:: header\n54- \n55- Indicates the presence of a header.\n56- * `False` if there is no header. Fields should the be provided using fields param\n57- * `True` if the file have a header line with names. This names should match names in map_dict (default).\n58- \n59- .. attribute:: fieldsB\n60- \n61- List with the behavioral fields corresponding each column in the file\n62- \n63- .. attribute:: fieldsG_dict\n64- \n65- A dictionary with the equivalence to map fields in the input file and the pergola\n66- ontology to describe behavioral data \n67- \n68- .. attribute:: min\n69- \n70- First time value in the file. Read from field set as \"start\"\n71- \n72- .. attribute:: max\n73- \n74- Last timepoint in the file. Read from field set as \"end\"\n75- \n76- .. attribute:: range_values\n77- \n78- Range of values inside data_value field\n79- \n80- .. attribute:: data\n81- \n82- List of tuples containing the data read from the file\n83- \n84- .. attribute:: data_types\n85- \n86- All different data_types that appear in the data read from \"data_types\" field.\n87- If data_types field not in file, all intervals are set as belonging to data_types \"a\"\n88-\n89- .. attribute:: tracks\n90- \n91- Set of tracks in the file. Read from \"tracks\" field.\n92- If tracks field not in file, all intervals are set as belonging to track \"1\" \n93- \n94- :returns: IntData object\n95- \n96- \"\"\"\n97-\n98- def __init__(self, path, map_dict, header=True, **kwargs):\n99- self.path = check_path(path)\n100- self._in_file = open(self.path, \"r\")\n101- self.delimiter = self._check_delimiter(self.path, kwargs.get('delimiter', \"\\t\"))\n","type":"text"}]}›Thinking…
map_dict: dict of correspondence (from MappingInfo). Also delimiter auto-check? _check_delimiter(path, default "\t") — probably sniffs. Our CSV is comma separated; should detect. Let's write the example script and test.
✓write /sandbox/repo/examples/behavior_to_bed.py✓└{"content":[{"text":"Successfully wrote 1398 bytes to /sandbox/repo/examples/behavior_to_bed.py","type":"text"}]}
›Thinking…
Check Track.write signature to match params.
✓$ cd /tmp/perg/x && grep -n "def write\b" -A 40 pergola/tracks.py | head -80✓└{"content":[{"text":"(no output)","type":"text"}]}
›Thinking…
No write? Let's search for "def write" occurrences.
✓$ cd /tmp/perg/x && grep -rn "def write\|def track_convert\|def dump\|def save" pergola/*.py | head -30✓└{"content":[{"text":"pergola/mapping.py:182: def write(self, indent=0):\npergola/mapping.py:219:def write_chr(self, mode=\"w\", path_w=None, min_c=None, max_…ctrl+o
{"content":[{"text":"pergola/mapping.py:182: def write(self, indent=0):\npergola/mapping.py:219:def write_chr(self, mode=\"w\", path_w=None, min_c=None, max_c=None):\npergola/mapping.py:256:def write_chr_sizes(self, mode=\"w\", path_w=None, file_n=None, min_c=None, max_c=None):\npergola/mapping.py:302:def write_cytoband(end, start=0, delta=43200, start_phase=\"light\", mode=\"w\", path_w=None, lab_bed=True, track_line=True):\npergola/mapping.py:426:def write_period_seq (end, start=0, delta=43200, tag=\"day\", mode=\"w\", path_w=None, name_file=\"period_seq\", lab_bed=True, track_line=True):\npergola/tracks.py:158: def save_track(self, mode=\"w\", path=None, name_file=None, track_line=True, bed_label=False, gff_label=False):\npergola/tracks.py:625: def track_convert2bed(self, track, in_call=False, **kwargs):\npergola/tracks.py:698: def track_convert2gff(self, track, in_call=False, **kwargs):\npergola/tracks.py:808: def track_convert2bedGraph(self, track, in_call=False, window=300, mean_win=False, mean_value=False, **kwargs):\n","type":"text"}]}›Thinking…
The API path is convoluted: Track objects created by read; then track_convert2bed(track) returns Bed object; then bed.save_track(...). Easier: use the CLI pergola script (pergola_rules) which does the whole pipeline. Or call the library function pergola_rules from scripts. Let me simplify the example to use the CLI in a shell script + a small Python wrapper calling scripts.pergola_rules.pergola_rules. Let's check signature params needed and how outputs are named/where written (cwd).
Simplest robust approach: example script calls the console script pergola via subprocess in the out dir, or directly call pergola_rules with output_file_name. Let me check the tail of pergola_rules to see output writing (path_w).
✓$ cd /tmp/perg/x && sed -n 117,260p scripts/pergola_rules.py✓└{"content":[{"text":" # Handling argument data_types actions\n data_types_act = data_types_actions\n print(\"@@@Pergola_rules.py: data_types actions ar…ctrl+o
{"content":[{"text":" # Handling argument data_types actions\n data_types_act = data_types_actions\n print(\"@@@Pergola_rules.py: data_types actions are......................... \", data_types_act, file=stderr)\n \n # Handling argument format \n if write_format:\n print(\"@@@Pergola_rules.py format to write files....................... \", write_format, file=stderr)\n else:\n write_format = 'bed'\n print(\"@@@Pergola_rules.py format to write files has been set\" \\\n \" to default value:\", write_format, file=stderr)\n\n # Handling relative coordinates\n print(\"@@@Pergola_rules.py: Relative coordinates set to................. %s\" % relative_coord, file=stderr)\n \n # Handling intervals_gen\n print(\"@@@Pergola_rules.py: Intervals parameter set to.................. %s\" % intervals_gen, file=stderr)\n\n # Handling interval_step\n if interval_step:\n if intervals_gen:\n print(\"@@@Pergola_rules.py: Interval step set to........................ %s\" % interval_step, file=stderr)\n else:\n raise ValueError(\"Interval step needs intervals paramater to be set -n/--intervals_gen\")\n\n # Handling multiply_intervals\n if multiply_f:\n print(\"@@@Pergola_rules.py: Multiply intervals parameter set to...... %s\" % multiply_f, file=stderr) \n else:\n multiply_f = 1\n \n print(\"@@@Pergola_rules.py: Selected tracks are......................... \", sel_tracks, file=stderr)\n \n # Setting whether input file has header or not\n header_sw = True\n \n if no_header:\n header_sw = False\n print(\"@@@Pergola_rules.py: Data file has header set to............. \", header_sw, file=stderr)\n\n # Handling fields to read\n if fields2read:\n print(\"@@@Pergola_rules.py: Fields to read from the file are......... %s\" % fields2read, file=stderr) \n else:\n fields2read = None \n \n # When binning data setting the window of time used in seconds\n # if not size provided set to False\n if window_size:\n print(\"@@@Pergola_rules.py: Window size set to....................... %d\" % window_size, file=stderr)\n else:\n# window_size = 300\n window_size = False\n print(\"@@@Pergola_rules.py: Window size set by default to............ %d\" % window_size, file=stderr)\n \n if window_mean:\n print(\"@@@Pergola_rules.py: Window mean set to....................... %d\" % window_mean, file=stderr)\n else: \n window_mean = False\n\n if value_mean:\n print(\"@@@Pergola_rules.py: Value mean set to....................... %d\" % value_mean, file=stderr)\n else:\n value_mean = False\n\n if no_track_line:\n track_line=False\n else:\n track_line=True\n \n print(\"@@@Pergola_rules.py: track_line set to............................ %s\" % track_line, file=stderr)\n \n # Handling input file field delimiter \n if not separator:\n separator = \"\\t\"\n print(\"@@@Pergola_rules.py input file field separator set by default to...... \\\"\\\\t\\\".\", file=stderr)\n else: \n print(\"@@@Pergola_rules.py input file field separator set to..... \\\"%s\\\"\" % separator, file=stderr)\n \n if bed_lab_sw:\n bed_lab = True\n print(\"@@@Pergola_rules.py: bed_label set to......................... %s\" % bed_lab, file=stderr)\n else:\n bed_lab = False\n\n intData = intervals.IntData(path, map_dict=map_file_dict.correspondence,\n fields_names=fields2read,\n header=header_sw, delimiter=separator)\n\n start = intData.min\n end = intData.max\n\n if relative_coord:\n start = 0\n end = intData.max - intData.min\n\n print(\"@@@Pergola_rules.py: min time in trajectory......................... %d\" % start, file=stderr)\n print(\"@@@Pergola_rules.py: max time in trajectory......................... %d\" % end, file=stderr)\n\n if min_t or min_t == 0:\n min_time = min_t\n print(\"@@@Pergola_rules.py: min_time set by user to.............. %d\" % min_t, file=stderr)\n else:\n min_time = start\n\n if max_t:\n max_time = max_t\n print(\"@@@Pergola_rules.py: max_time set by user to............... %d\" % max_t, file=stderr)\n else:\n if interval_step:\n max_time = end + interval_step\n else:\n max_time = end + 1\n\n # Phases option\n print(\"@@@Pergola_rules.py: Phases file set to............................%s\" % phases, file=stderr)\n\n # Genome option\n print(\"@@@Pergola_rules.py: Genome option set to..........................%s\" % genome, file=stderr)\n\n # Output file name\n print(\"@@@Pergola_rules.py: Output file/s name set t......................%s\" % output_file_name, file=stderr)\n\n # Starting phase option\n if starting_phase:\n if phases:\n print(\"@@@Pergola_rules.py: Starting phase set to.............. %s\" % starting_phase, file=stderr)\n else:\n raise ValueError(\"Starting phase needs phases option to be set to true\")\n else:\n starting_phase = \"light\"\n if shift:\n time_shift = shift\n print(\"@@@Pergola_rules.py: Time shift set to....................... %d\" % shift, file=stderr)\n else:\n # window_size = 300\n time_shift = 0\n print(\"@@@Pergola_rules.py: Window size set by default to............ %d\" % window_size, file=stderr)\n\n if multiply_f:\n min_time = min_time * multiply_f\n max_time = max_time * multiply_f\n\n if track_act: tracks2merge = parsers.read_track_actions(tracks=intData.tracks, track_action=track_act)\n","type":"text"}]}›Thinking…
Let's just test the CLI directly in the repo, writing outputs to out/. CLI writes files in cwd presumably (getcwd). So run from out dir or specify -o name and check where files land. Let's test quickly.
×$ cd /sandbox/repo/out && ../.venv/bin/pergola -i ../data/mouse_feeding.csv -m ../config/mapping.tsv -n -ns 1 -f bed -bl -o mouse_feeding 2>&1 | tail -5; ls -la; echo "--- bed ---"; head -8 *.bed 2>/dev…✓└{"content":[{"text":" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/sandbox/repo/.venv/lib/python3.12/site-pack…ctrl+o
cd /sandbox/repo/out && ../.venv/bin/pergola -i ../data/mouse_feeding.csv -m ../config/mapping.tsv -n -ns 1 -f bed -bl -o mouse_feeding 2>&1 | tail -5; ls -la; echo "--- bed ---"; head -8 *.bed 2>/dev/null
{"content":[{"text":" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/sandbox/repo/.venv/lib/python3.12/site-packages/pergola/intervals.py\", line 139, in _check_delimiter\n else: raise ValueError(\"Input delimiter does not correspond to delimiter found in file \\'%s\\'\"%(delimiter))\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nValueError: Input delimiter does not correspond to delimiter found in file '\t'\ntotal 8\ndrwx------ 2 root root 4096 Sep 25 12:35 .\ndrwxr-xr-x 8 root root 4096 Sep 25 12:35 ..\n--- bed ---\n\n\nCommand exited with code 1","type":"text"}],"details":{}}›Thinking…
Default separator "\t" and it validates against file. So pass -fs ,. Let's retry.
×$ cd /sandbox/repo/out && ../.venv/bin/pergola -i ../data/mouse_feeding.csv -m ../config/mapping.tsv -n -ns 1 -f bed -bl -fs , -o mouse_feeding 2>&1 | grep -v "@@@" | tail -8; ls; echo "--- bed ---"; he…✓└{"content":[{"text":" File \"/sandbox/repo/.venv/lib/python3.12/site-packages/pergola/intervals.py\", line 105, in __init__\n self.fieldsG_dict = self._set_…ctrl+o
cd /sandbox/repo/out && ../.venv/bin/pergola -i ../data/mouse_feeding.csv -m ../config/mapping.tsv -n -ns 1 -f bed -bl -fs , -o mouse_feeding 2>&1 | grep -v "@@@" | tail -8; ls; echo "--- bed ---"; head -6 *.bed; echo "--- cytoband ---"; head -3 *cytoband* 2>/dev/null; echo "--- fasta ---"; head -c 200 *.fa* 2>/dev/null
{"content":[{"text":" File \"/sandbox/repo/.venv/lib/python3.12/site-packages/pergola/intervals.py\", line 105, in __init__\n self.fieldsG_dict = self._set_fields_g(map_dict)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/sandbox/repo/.venv/lib/python3.12/site-packages/pergola/intervals.py\", line 301, in _set_fields_g\n raise ValueError(\"Input file mandatory fields are \\\"start\\\" and \\\"data_value\\\" \\n\" \\\nValueError: Input file mandatory fields are \"start\" and \"data_value\" \nYour current assigned fields are \"start\",\"data_types\",\"track\"\nTIP: Check your ontology_file\n--- bed ---\nhead: cannot open '*.bed' for reading: No such file or directory\n--- cytoband ---\n--- fasta ---\n\n\nCommand exited with code 1","type":"text"}],"details":{}}›Thinking…
Needs a "data_value" field. Add an intensity column mapped to data_value. Let's regenerate CSV with data_value (e.g., speed or bout intensity).
✓$ cd /sandbox/repo && python - <<'EOF'✓└{"content":[{"text":"timepoint,behavior,intensity,track\r\n0,grooming,1,1\r\n4,feeding,2,1\r\n5,grooming,0,1\r\n7,feeding,2,1\r\nNo path selected, files dump in…ctrl+o
cd /sandbox/repo && python - <<'EOF'
import csv, random
random.seed(7)
behaviors = ["feeding", "resting", "grooming", "walking"]
rows = [("timepoint","behavior","intensity","track")]
for track in (1,2):
t = 0
while t < 60:
b = random.choice(behaviors)
intensity = random.randint(1,9) if b in ("feeding","walking") else random.randint(0,3)
rows.append((t, b, intensity, track))
t += random.randint(1,4)
with open("data/mouse_feeding.csv","w",newline="") as f:
csv.writer(f).writerows(rows)
EOF
cat > config/mapping.tsv <<'EOF'
# behavioral_file_field pergola_ontology_term
timepoint start
intensity data_value
behavior data_types
track track
EOF
head -5 data/mouse_feeding.csv
cd out && ../.venv/bin/pergola -i ../data/mouse_feeding.csv -m ../config/mapping.tsv -n -ns 1 -f bed -bl -fs , -o mouse_feeding 2>&1 | grep -v "@@@" | tail -6; ls; echo "--- bed ---"; head -6 *.bed
{"content":[{"text":"timepoint,behavior,intensity,track\r\n0,grooming,1,1\r\n4,feeding,2,1\r\n5,grooming,0,1\r\n7,feeding,2,1\r\nNo path selected, files dump into path: /sandbox/repo/out\nFile mouse_feeding.2_grooming.bed generated\nNo path selected, files dump into path: /sandbox/repo/out\nFile mouse_feeding.2_resting.bed generated\nNo path selected, files dump into path: /sandbox/repo/out\nFile mouse_feeding.2_walking.bed generated\nchr1.fa\nchrom.sizes\ncytoband_file.txt\nmouse_feeding.1_feeding.bed\nmouse_feeding.1_grooming.bed\nmouse_feeding.1_resting.bed\nmouse_feeding.1_walking.bed\nmouse_feeding.2_feeding.bed\nmouse_feeding.2_grooming.bed\nmouse_feeding.2_resting.bed\nmouse_feeding.2_walking.bed\nphases_dark.bed\nphases_light.bed\n--- bed ---\n==> mouse_feeding.1_feeding.bed <==\ntrack name=\"1_feeding\" description=\"1 feeding\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t4\t5\tfeeding\t2\t+\t4\t5\t254,96,101\nchr1\t7\t8\tfeeding\t2\t+\t7\t8\t254,96,101\nchr1\t13\t14\tfeeding\t9\t+\t13\t14\t254,57,61\nchr1\t17\t18\tfeeding\t2\t+\t17\t18\t254,96,101\nchr1\t19\t20\tfeeding\t7\t+\t19\t20\t254,57,61\n\n==> mouse_feeding.1_grooming.bed <==\ntrack name=\"1_grooming\" description=\"1 grooming\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t0\t1\tgrooming\t1\t+\t0\t1\t113,113,113\nchr1\t5\t6\tgrooming\t0\t+\t5\t6\t170,170,170\nchr1\t22\t23\tgrooming\t3\t+\t22\t23\t85,85,85\nchr1\t47\t48\tgrooming\t3\t+\t47\t48\t85,85,85\n\n==> mouse_feeding.1_resting.bed <==\ntrack name=\"1_resting\" description=\"1 resting\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t20\t21\tresting\t0\t+\t20\t21\t135,206,250\nchr1\t41\t42\tresting\t1\t+\t41\t42\t65,105,225\nchr1\t54\t55\tresting\t2\t+\t54\t55\t65,105,225\n\n==> mouse_feeding.1_walking.bed <==\ntrack name=\"1_walking\" description=\"1 walking\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t11\t12\twalking\t2\t+\t11\t12\t102,254,102\nchr1\t34\t35\twalking\t6\t+\t34\t35\t51,254,51\nchr1\t38\t39\twalking\t6\t+\t38\t39\t51,254,51\nchr1\t56\t57\twalking\t7\t+\t56\t57\t51,254,51\n\n==> mouse_feeding.2_feeding.bed <==\ntrack name=\"2_feeding\" description=\"2 feeding\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t6\t7\tfeeding\t5\t+\t6\t7\t254,57,61\nchr1\t13\t14\tfeeding\t8\t+\t13\t14\t254,57,61\nchr1\t20\t21\tfeeding\t4\t+\t20\t21\t254,77,81\nchr1\t41\t42\tfeeding\t3\t+\t41\t42\t254,77,81\n\n==> mouse_feeding.2_grooming.bed <==\ntrack name=\"2_grooming\" description=\"2 grooming\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t0\t1\tgrooming\t2\t+\t0\t1\t113,113,113\nchr1\t5\t6\tgrooming\t3\t+\t5\t6\t85,85,85\nchr1\t10\t11\tgrooming\t3\t+\t10\t11\t85,85,85\nchr1\t32\t33\tgrooming\t1\t+\t32\t33\t113,113,113\nchr1\t36\t37\tgrooming\t3\t+\t36\t37\t85,85,85\n\n==> mouse_feeding.2_resting.bed <==\ntrack name=\"2_resting\" description=\"2 resting\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t16\t17\tresting\t0\t+\t16\t17\t135,206,250\nchr1\t23\t24\tresting\t1\t+\t23\t24\t65,105,225\nchr1\t28\t29\tresting\t3\t+\t28\t29\t0,0,255\nchr1\t43\t44\tresting\t1\t+\t43\t44\t65,105,225\n\n==> mouse_feeding.2_walking.bed <==\ntrack name=\"2_walking\" description=\"2 walking\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t4\t5\twalking\t2\t+\t4\t5\t102,254,102\nchr1\t27\t28\twalking\t8\t+\t27\t28\t51,254,51\nchr1\t39\t40\twalking\t4\t+\t39\t40\t76,254,76\nchr1\t44\t45\twalking\t3\t+\t44\t45\t76,254,76\nchr1\t49\t50\twalking\t9\t+\t49\t50\t51,254,51\n\n==> phases_dark.bed <==\ntrack name=\"phases\" description=\"Track annotating phases of the experiment\" visibility=2 color=0,0,255 useScore=1 priority=user\n\n==> phases_light.bed <==\ntrack name=\"phases\" description=\"Track annotating phases of the experiment\" visibility=2 color=0,0,255 useScore=1 priority=user\n","type":"text"}]}›Thinking…
Works. Also try joining tracks with -a join_all and bedGraph? Let's also demo join_all in the shell script. And also a Python API example — update the example script to use the working API. Simpler: rewrite examples/behavior_to_bed.py to use the library's IntData + Bed conversion, or simply invoke the pipeline function. Let me test the library-level API to produce a single joined BED:
from scripts.pergola_rules import pergola_rules — but scripts is a top-level package installed. Let's test using pergola_rules function with track_actions='join_all'.
Actually simpler for the Python example: use IntData.read -> Track, then track.track_convert2bed? Let's check Track class and how pergola_rules writes.
✓$ cd /tmp/perg/x && sed -n 260,340p scripts/pergola_rules.py✓└{"content":[{"text":" if track_act: tracks2merge = parsers.read_track_actions(tracks=intData.tracks, track_action=track_act)\n\n data_read = intData.read(…ctrl+o
{"content":[{"text":" if track_act: tracks2merge = parsers.read_track_actions(tracks=intData.tracks, track_action=track_act)\n\n data_read = intData.read(relative_coord=relative_coord,\n intervals=intervals_gen,\n multiply_t=multiply_f,\n min_time=min_time, max_time=max_time,\n int_step=interval_step)\n\n if genome:\n # whole trajectory\n # mapping.write_chr(data_read, min_c=start, max_c=end)\n # mapping.write_chr_sizes(data_read, min_c=start, max_c=end)\n\n # using min and max set by user\n mapping.write_chr(data_read)\n mapping.write_chr_sizes(data_read)\n\n if phases:\n # writes cytoband and light, dark and light_dark bed files\n mapping.write_cytoband(end=end, start=time_shift, start_phase=starting_phase, track_line=track_line,\n lab_bed=bed_lab)\n # mapping.write_period_seq(start=0, end=intData.max, delta=43200, name_file=\"phases_dark\", track_line=False)\n\n ## all intervals not save, in should be an option if necessary to save it\n # data_read.save_track(name_file=\"all_intervals\")\n\n bed_str = data_read.convert(mode=write_format, tracks=sel_tracks,\n tracks_merge=tracks2merge, data_types=data_types_list,\n data_types_actions=data_types_act, window=window_size,\n mean_win=window_mean, mean_value=value_mean, color_restrictions=d_colors_data_types)\n #min_t_trim=min_t, max_t_trim=max_t)\n \n for key in bed_str:\n bedSingle = bed_str[key]\n\n output_file_n = None\n\n if output_file_name is not None:\n output_file_n = output_file_name + '.' + '_'.join(key)\n\n bedSingle.save_track(name_file=output_file_n, track_line=track_line, bed_label=bed_lab)\n\nif __name__ == '__main__':\n exit(main())","type":"text"}]}›Thinking…
So Python API: IntData.read -> Track; Track.convert(mode='bed', ...) -> dict of Bed; bed.save_track(path=..., name_file=...).
Let's rewrite examples/behavior_to_bed.py accordingly and test it, plus write outputs into out/api.
Note save_track signature: (mode="w", path=None, name_file=None, track_line=True, bed_label=False, gff_label=False). Also convert may need data_types_actions etc. Let's write it.
✓write /sandbox/repo/examples/behavior_to_bed.py✓└{"content":[{"text":"Successfully wrote 1502 bytes to /sandbox/repo/examples/behavior_to_bed.py","type":"text"}]}
›Thinking…
There's a typo I accidentally introduced: "mouse_feading.%s.bed" in print. Fix. Also run.
✓$ cd /sandbox/repo && sed -i 's/mouse_feading/mouse_feeding/' examples/behavior_to_bed.py && .venv/bin/python examples/behavior_to_bed.py 2>&1 | tail -20 && ls out/api && head -4 out/api/mouse_feeding.1…✓└{"content":[{"text":"Files dump into path: /sandbox/repo/out/api\nFile mouse_feeding.2_feeding.bed generated\nFiles dump into path: /sandbox/repo/out/api\nFil…ctrl+o
cd /sandbox/repo && sed -i 's/mouse_feading/mouse_feeding/' examples/behavior_to_bed.py && .venv/bin/python examples/behavior_to_bed.py 2>&1 | tail -20 && ls out/api && head -4 out/api/mouse_feeding.1_feeding.bed
{"content":[{"text":"Files dump into path: /sandbox/repo/out/api\nFile mouse_feeding.2_feeding.bed generated\nFiles dump into path: /sandbox/repo/out/api\nFile mouse_feeding.2_grooming.bed generated\nFiles dump into path: /sandbox/repo/out/api\nFile mouse_feeding.2_resting.bed generated\nFiles dump into path: /sandbox/repo/out/api\nFile mouse_feeding.2_walking.bed generated\nfield mapping: {'timepoint': 'start', 'intensity': 'data_value', 'behavior': 'data_types', 'track': 'track'}\ntracks: ['1', '2']\nbehaviors: ['feeding', 'grooming', 'resting', 'walking']\ntime range: 0 -> 57\nwrote /sandbox/repo/out/api/mouse_feeding.1_feeding.bed\nwrote /sandbox/repo/out/api/mouse_feeding.1_grooming.bed\nwrote /sandbox/repo/out/api/mouse_feeding.1_resting.bed\nwrote /sandbox/repo/out/api/mouse_feeding.1_walking.bed\nwrote /sandbox/repo/out/api/mouse_feeding.2_feeding.bed\nwrote /sandbox/repo/out/api/mouse_feeding.2_grooming.bed\nwrote /sandbox/repo/out/api/mouse_feeding.2_resting.bed\nwrote /sandbox/repo/out/api/mouse_feeding.2_walking.bed\nmouse_feeding.1_feeding.bed\nmouse_feeding.1_grooming.bed\nmouse_feeding.1_resting.bed\nmouse_feeding.1_walking.bed\nmouse_feeding.2_feeding.bed\nmouse_feeding.2_grooming.bed\nmouse_feeding.2_resting.bed\nmouse_feeding.2_walking.bed\ntrack name=\"1_feeding\" description=\"1 feeding\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t4\t5\tfeeding\t2\t+\t4\t5\t113,113,113\nchr1\t7\t8\tfeeding\t2\t+\t7\t8\t113,113,113\nchr1\t13\t14\tfeeding\t9\t+\t13\t14\t56,56,56\n","type":"text"}]}›Thinking…
Both CLI and API work. Now add a small shell script (examples/run_cli.sh) demonstrating the CLI with join_all, and a README explaining the example, plus notes on pricing/limitations.
Let's test join_all quickly to include in the script.
✓$ cd /sandbox/repo/out && rm -rf cli && mkdir cli && cd cli && ../../.venv/bin/pergola -i ../../data/mouse_feeding.csv -m ../../config/mapping.tsv -n -ns 1 -f bed -bl -fs , -a join_all -ng -np -o joined…✓└{"content":[{"text":"File joined.1_2_resting.bed generated\nNo path selected, files dump into path: /sandbox/repo/out/cli\nFile joined.1_2_walking.bed generate…ctrl+o
cd /sandbox/repo/out && rm -rf cli && mkdir cli && cd cli && ../../.venv/bin/pergola -i ../../data/mouse_feeding.csv -m ../../config/mapping.tsv -n -ns 1 -f bed -bl -fs , -a join_all -ng -np -o joined 2>&1 | grep -v "@@@" | tail -3; ls; head -5 joined*.bed | head -12
{"content":[{"text":"File joined.1_2_resting.bed generated\nNo path selected, files dump into path: /sandbox/repo/out/cli\nFile joined.1_2_walking.bed generated\njoined.1_2_feeding.bed\njoined.1_2_grooming.bed\njoined.1_2_resting.bed\njoined.1_2_walking.bed\n==> joined.1_2_feeding.bed <==\ntrack name=\"1_2_feeding\" description=\"1_2 feeding\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t4\t5\tfeeding\t2\t+\t4\t5\t113,113,113\nchr1\t6\t7\tfeeding\t5\t+\t6\t7\t56,56,56\nchr1\t7\t8\tfeeding\t2\t+\t7\t8\t113,113,113\nchr1\t13\t14\tfeeding\t9\t+\t13\t14\t56,56,56\n\n==> joined.1_2_grooming.bed <==\ntrack name=\"1_2_grooming\" description=\"1_2 grooming\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t0\t1\tgrooming\t1\t+\t0\t1\t102,254,102\nchr1\t0\t1\tgrooming\t2\t+\t0\t1\t102,254,102\nchr1\t5\t6\tgrooming\t0\t+\t5\t6\t152,254,152\n","type":"text"}]}›Thinking…
Note colors differ when joining (color depends on position in gradient list). Fine.
Now write examples/run_cli.sh and README.md. Also add requirements.txt and maybe a Makefile? Keep light. Add requirements.txt with pergola, pybedtools (since it's not declared as a dependency — important gotcha to document).
Check installed versions for pinning.
✓$ cd /sandbox/repo && .venv/bin/pip list 2>/dev/null✓└{"content":[{"text":"Package Version\n--------------- -----------\nnumpy 2.5.3\npandas 3.0.6\npergola 0.2.0\npip …ctrl+o
{"content":[{"text":"Package Version\n--------------- -----------\nnumpy 2.5.3\npandas 3.0.6\npergola 0.2.0\npip 26.2.1\npybedtools 0.12.1\npysam 0.24.1\npython-dateutil 2.9.0.post0\nsix 1.17.0\n","type":"text"}]}›Thinking…
Interesting: scipy/biopy-isatab not needed for the core path. pybedtools is required but not declared in the wheel metadata. Document that.
Now write files: requirements.txt, examples/run_cli.sh, README.md. Also add out/ to .gitignore? .gitignore doesn't include out/. Add "out/" entry.
✓write /sandbox/repo/examples/run_cli.sh✓└{"content":[{"text":"Successfully wrote 916 bytes to /sandbox/repo/examples/run_cli.sh","type":"text"}]}
›Thinking…
Note: backtick comments inside a line-continued command are fragile — the backtick comment works in bash but with line continuation \ followed by backtick... Actually \ continues the line, then `# ...` is a command substitution producing empty string — it works but risky (command substitution of "# ..." runs nothing; actually # inside backticks starts a comment, so substitution yields empty). It works, but cleaner to avoid. Let me rewrite with plain comments on separate lines.
Also $OLDPWD after cd out/cli: OLDPWD would be repo root since we cd'd from repo root. Yes, cd "$(dirname "$0")/.." sets OLDPWD to previous dir, then cd out/cli sets OLDPWD=repo root. OK but fragile; use ROOT variable.
✓write /sandbox/repo/examples/run_cli.sh✓└{"content":[{"text":"Successfully wrote 1043 bytes to /sandbox/repo/examples/run_cli.sh","type":"text"}]}
›Thinking…
There's a bug in my heredoc-ish content: the comment lines I wrote starting with "# -fs , input is..." were placed before the command, good. But I wrote them as lines starting with "#" — yes those are inside the script as comments. Wait, in the content I see lines "# -fs , input is comma separated..." — those appear after the command? Let me re-read the written file: the content shows comment lines then the "$PERGOLA" invocation. Good.
Test it.
✓$ cd /sandbox/repo && rm -rf out/cli && bash examples/run_cli.sh 2>&1 | grep -v "@@@" | tail -20✓└{"content":[{"text":"Relative coordinates set to: False\nIntervals will be inferred from timepoints\nTracks that will be merged are: 2 1\nNo path selected, file…ctrl+o
{"content":[{"text":"Relative coordinates set to: False\nIntervals will be inferred from timepoints\nTracks that will be merged are: 2 1\nNo path selected, files dump into path: /sandbox/repo/out/cli\nFile joined.2_1_feeding.bed generated\nNo path selected, files dump into path: /sandbox/repo/out/cli\nFile joined.2_1_grooming.bed generated\nNo path selected, files dump into path: /sandbox/repo/out/cli\nFile joined.2_1_resting.bed generated\nNo path selected, files dump into path: /sandbox/repo/out/cli\nFile joined.2_1_walking.bed generated\n\nGenerated:\njoined.2_1_feeding.bed\njoined.2_1_grooming.bed\njoined.2_1_resting.bed\njoined.2_1_walking.bed\n\nSample (joined feeding events; chrom = timeline, coords = timepoints):\nhead: cannot open 'joined.1_2_feeding.bed' for reading: No such file or directory\n","type":"text"}]}›Thinking…
Filename order depends on set ordering (nondeterministic-ish). Fix script to glob: head -4 joined.*_feeding.bed.
✓$ cd /sandbox/repo && sed -i 's/head -4 joined.1_2_feeding.bed/head -4 joined.*_feeding.bed/' examples/run_cli.sh && rm -rf out/cli && bash examples/run_cli.sh 2>&1 | tail -8✓└{"content":[{"text":"joined.2_1_resting.bed\njoined.2_1_walking.bed\n\nSample (joined feeding events; chrom = timeline, coords = timepoints):\ntrack name=\"2_1_…ctrl+o
{"content":[{"text":"joined.2_1_resting.bed\njoined.2_1_walking.bed\n\nSample (joined feeding events; chrom = timeline, coords = timepoints):\ntrack name=\"2_1_feeding\" description=\"2_1 feeding\" visibility=2 itemRgb=\"On\" priority=20\nchr1\t4\t5\tfeeding\t2\t+\t4\t5\t254,96,101\nchr1\t6\t7\tfeeding\t5\t+\t6\t7\t254,57,61\nchr1\t7\t8\tfeeding\t2\t+\t7\t8\t254,96,101\n","type":"text"}]}›Thinking…
There's a stray issue: the script had comment lines placed between? Fine.
Now write requirements.txt, README.md, and add out/ to .gitignore.
✓write /sandbox/repo/requirements.txt✓└{"content":[{"text":"Successfully wrote 207 bytes to /sandbox/repo/requirements.txt","type":"text"}]}
›Thinking…
Now README.md with pricing/manageability notes. Also .gitignore addition for out/ and .venv already there (.venv/). Add out/.
Then commit? Maybe leave committing optional; a git commit is nice. I'll commit.
✓write /sandbox/repo/README.md✓└{"content":[{"text":"Successfully wrote 5236 bytes to /sandbox/repo/README.md","type":"text"}]}
›Thinking…
There's an issue in README: I have some non-ASCII arrow "→" fine. Also the setup lines. Also fix: the README says examples write out/api. Fine.
Now .gitignore add out/. And commit. Also verify README claims: pergola --version? Let's double check the version/date claims quickly (last release date on PyPI). Quick check via pip index or PyPI JSON.
✓$ cd /sandbox/repo && printf '\n# Pergola example outputs\nout/\n' >> .gitignore && curl -s -m 10 https://pypi.org/pypi/pergola/json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['info'…✓└{"content":[{"text":"0.2.0 2019-12-23T10:38:26\n","type":"text"}]}ctrl+o
cd /sandbox/repo && printf '\n# Pergola example outputs\nout/\n' >> .gitignore && curl -s -m 10 https://pypi.org/pypi/pergola/json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['info']['version'], list(d['releases']['0.2.0'])[0]['upload_time'])"
›Thinking…
Confirms 2019 release. Good — README says ~2019.
Now clean the out dir? Keep outputs (gitignored) — fine, they demonstrate results. Maybe keep out/ present since generated. Commit source files.
Also verify the Python example runs from a clean out/api. Already ran. Let's do a final full run of both and commit.
✓$ cd /sandbox/repo && rm -rf out && .venv/bin/python examples/behavior_to_bed.py >/dev/null 2>&1 && bash examples/run_cli.sh >/dev/null 2>&1 && ls out out/api out/cli | head -30 && git add -A && git sta…✓└{"content":[{"text":"out:\napi\ncli\n\nout/api:\nmouse_feeding.1_feeding.bed\nmouse_feeding.1_grooming.bed\nmouse_feeding.1_resting.bed\nmouse_feeding.1_walking…ctrl+o
cd /sandbox/repo && rm -rf out && .venv/bin/python examples/behavior_to_bed.py >/dev/null 2>&1 && bash examples/run_cli.sh >/dev/null 2>&1 && ls out out/api out/cli | head -30 && git add -A && git status --short && git -c user.email=agent@pi -c user.name=agent commit -q -m "Add Pergola quickstart: behavioral CSV -> BED via CLI and Python API" && git log --oneline | head -3
{"content":[{"text":"out:\napi\ncli\n\nout/api:\nmouse_feeding.1_feeding.bed\nmouse_feeding.1_grooming.bed\nmouse_feeding.1_resting.bed\nmouse_feeding.1_walking.bed\nmouse_feeding.2_feeding.bed\nmouse_feeding.2_grooming.bed\nmouse_feeding.2_resting.bed\nmouse_feeding.2_walking.bed\n\nout/cli:\njoined.1_2_feeding.bed\njoined.1_2_grooming.bed\njoined.1_2_resting.bed\njoined.1_2_walking.bed\nM .gitignore\nA README.md\nA config/mapping.tsv\nA data/mouse_feeding.csv\nA examples/behavior_to_bed.py\nA examples/run_cli.sh\nA requirements.txt\n90df8c0 Add Pergola quickstart: behavioral CSV -> BED via CLI and Python API\n9ebf5fb Initial workspace\n","type":"text"}]}›Thinking…
I'm realizing I need to be upfront about a key limitation: Pergola doesn't actually offer a hosted product with an SDK or API as the user requested. What I did was use the open-source library from PyPI, which is the only practical developer path available—it's lightweight and doesn't require spinning up local services. I should clarify this constraint and mention how the pricing and manageability aspects work out.
Done — but with one important correction up front.
What Pergola actually is
There is no hosted Pergola product, no cloud API, and no SDK-as-a-service. Pergola is a GPLv3 open-source Python library on PyPI (pergola==0.2.0, Centre for Genomic Regulation, released 2019‑12‑23) that converts behavioral time-series into genome-browser formats (BED/bedGraph/GFF). The pergola.crg.eu web server mentioned in its README dates from 2016 and isn't a usable offering.
So the only developer workflow that exists is the local library one — which is light (a pip install plus file-in/file-out, ~2s runs, no services, no containers, no long-running commands). I used that rather than stopping, since it needs no service stack.
What I built (/sandbox/repo)
data/mouse_feeding.csv— tiny synthetic log (2 animals, 0–60 timepoints)config/mapping.tsv— CSV column → Pergola ontology (start,data_value,data_types,track)examples/run_cli.sh— CLI quickstart →out/cli/joined.*_*.bedexamples/behavior_to_bed.py— same via the Python API →out/api/mouse_feeding.*.bedrequirements.txt,README.md, committed as90df8c0
Output (loads directly into IGV/JBrowse/UCSC; chr1 = timeline, score = intensity, colour from intensity):
track name="2_1_feeding" description="2_1 feeding" visibility=2 itemRgb="On" priority=20
chr1 4 5 feeding 2 + 4 5 254,96,101
Pricing
None. Nothing to buy, subscribe to, or meter: no paid tiers, no usage billing, no API keys, no rate limits, no vendor account. Costs are only your own compute plus GPLv3 copyleft obligations if you redistribute derivatives (internal use unrestricted).
Will it be easy for me to manage?
Partly:
- Easy: deterministic CLI + library, fully scriptable/reproducible, no credentials or quotas to rotate, loud specific errors, trivially pinned.
- Friction I hit:
pergoladoesn't declarepybedtoolsbut imports it at load time (had to add it explicitly); mandatorystart+data_valuemapping terms; default separator is TAB and is validated against the file (-fs ,needed); output goes to the CWD; output filenames come from Pythonsetordering (joined.2_1_*vsjoined.1_2_*) so glob, don't hardcode; lots of@@@…stderr noise. - Risk: effectively dormant (0.2.0, "Pre-Alpha", ~2019) — no SLA, support, or security advisories. You own it.
Nothing blocked me; no local service stacks or long-running commands were started.
AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents