{"id":45942729,"url":"https://github.com/hvalev/rcpyci","last_synced_at":"2026-02-28T10:52:22.224Z","repository":{"id":303382657,"uuid":"795065661","full_name":"hvalev/rcpyci","owner":"hvalev","description":"Reverse correlation classification images toolbox for creating and analyzing data from 2afc experiments","archived":false,"fork":false,"pushed_at":"2026-02-01T21:26:38.000Z","size":1247,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-02T05:57:41.193Z","etag":null,"topics":["classification-images","reverse-correlation"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hvalev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-05-02T14:12:25.000Z","updated_at":"2026-02-01T21:26:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"e1e0290d-0bc5-4a7b-9339-2390493157aa","html_url":"https://github.com/hvalev/rcpyci","commit_stats":null,"previous_names":["hvalev/rcpyci"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/hvalev/rcpyci","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hvalev%2Frcpyci","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hvalev%2Frcpyci/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hvalev%2Frcpyci/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hvalev%2Frcpyci/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hvalev","download_url":"https://codeload.github.com/hvalev/rcpyci/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hvalev%2Frcpyci/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29930692,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-28T09:58:13.507Z","status":"ssl_error","status_checked_at":"2026-02-28T09:57:57.047Z","response_time":90,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["classification-images","reverse-correlation"],"created_at":"2026-02-28T10:52:21.727Z","updated_at":"2026-02-28T10:52:22.216Z","avatar_url":"https://github.com/hvalev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rcpyci 🌶️\n[![build](https://github.com/hvalev/rcpyci/actions/workflows/build.yml/badge.svg)](https://github.com/hvalev/rcpyci/actions/workflows/build.yml)\n[![Downloads](https://static.pepy.tech/badge/rcpyci)](https://pepy.tech/project/rcpyci)\n[![Downloads](https://static.pepy.tech/badge/rcpyci/month)](https://pepy.tech/project/rcpyci)\n[![Downloads](https://static.pepy.tech/badge/rcpyci/week)](https://pepy.tech/project/rcpyci)\n[![DOI](https://zenodo.org/badge/795065661.svg)](https://doi.org/10.5281/zenodo.15827936)\n\nrcpyci (/ɑr ˈspaɪsi/) is a reverse correlation classification images toolbox for generating classification images for 2AFC experiments and analyzing experiment results. rcpyci supports the following tasks:\n- generate stimulus images for 2AFC tasks\n- create classification images from 2AFC tasks split by participant or condition\n- compute zmaps on classification images or parameter space\n- generic way to add additional pipelines for computing anything on the ci- or parameter space\n- caching of intermediary results\n- generating the parameter space using a seed\n\n## How does this compare to the [rcicr](https://github.com/rdotsch/rcicr/) R package\nThe implementation of the core functions closely mirrors that of `rcicr`, but `rcpyci` is much faster, easy to use, and future-proof as it depends on a few regularly maintained python packages. Additionally, `rcpyci` exposes a way for the user to implement their own custom processing pipelines without needing to modify the source code. And it has caching and seeding.\n#TODO\n\n## What is in this package\nThe main components in this package are split in 5 namespaces:\n- `core`: Functions for creating stimulus images and computing classification images using pure numpy arrays.\n- `interface`: User-friendly interface for setting up experiments and analyzing data, wrapping the functionality of `core`.\n- `pipelines`: A collection of functions for computing cis, zmaps, and others, executed in sequence on 2AFC task results. More on that in the [Default processing pipelines](#default-processing-pipelines) section.\n- `im_ops`: Operations on image arrays.\n- `utils`: Helper functions.\n\n__NOTE__: `infoval` is a port of the `infoval` functionality from the original `rcicr` package, but it is untested.\n\n## How to generate stimuli for a 2AFC task\nHere is how you can generate 2AFC task stimuli:\n```python\nfrom rcpyci.interface import setup_experiment\nbase_face_path = \"./base_face.jpg\"\nsetup_experiment(base_face_path)\n```\n__NOTE__: The input face image needs to be square-shaped.\n\n`setup_experiment` further exposes the parameters `n_trials`, `n_scales`, `gabor_sigma`, `noise_type` to control the noise generation and `seed` for reproducibility. Check the docstrings of `setup_experiment` for more information.\n\n## How to analyze data from an experiment\nThe following snippet shows how experiment data can be analyzed. Here we are using some generated sample data.\n```python\nfrom rcpyci.interface import analyze_data\nfrom rcpyci.utils import create_test_data, verify_data\n\nsample_data = create_test_data(n_trials=500)\nverify_data(sample_data)\nbase_face_path = \"./base_face.jpg\"\nanalyze_data(sample_data, base_face_path, n_trials=500)\n```\n__NOTE__: `analyze_data` takes a dataframe with 5 columns: `idx`, `condition`, `participant_id`, `stimulus_id` and `responses`. \n`idx` is the row identifier, `condition` is the condition that was used for each trial, (e.g. the question variation asked to a participant). If a single condition is tested, the column will have a single value. `participant_id` is an identifier for each participant. `stimulus_id` is an identifier for each stimulus (pair of original and inverted images generated by setup_experiment). `responses` is the response that was given by each participant for each stimulus image pair. If a participant has selected the original response image, then the value should be coded as `1`, if the inverted response image is selected -- as `-1`. There is no need to sort by stimulus ids as that is taken care of in the method.\n\n__NOTE__: To speed-up computation, you can allocate multiple cores using `n_jobs`. It's a balancing game between number of cpu cores and available RAM. If you have around 20GB RAM, you could use around 6 concurrent jobs. The default parameter configuration uses caching, so you could stop and resume the computation at a later point without losing any progress.\n\n## Default processing pipelines\nThe default processing pipelines are defined in `pipelines.full_pipeline`. A quick look: \n```\nfull_pipeline = [\n    (compute_ci, compute_anti_ci_kwargs),\n    (combine_ci, combine_anti_ci_kwargs),\n    (compute_ci, compute_ci_kwargs),\n    (combine_ci, combine_ci_kwargs),\n    (compute_zmap_ci, compute_zmap_ci_kwargs),\n    (compute_zmap_stimulus_params, compute_zmap_stimulus_params_kwargs)\n]\n```\nUsing the default pipelines, we compute in-order the ci, anti-ci, zmap on the ci and zmap on the parameter space for a given input, typically on a participant or a condition split with some sensible default parameters. This pipeline can be modified by adding or removing individual steps or modifying the `..._kwargs` parameters assigned to each of the steps.\n\n### Custom processing pipelines\nThe pipelines can also be extended by adding your own functions with their respective parameters. Here is a simple example where we modify the seed and cache the modified seed as a numpy array in two steps.\n```python\nfrom rcpyci.interface import analyze_data\nfrom rcpyci.utils import create_test_data, verify_data, cache_as_numpy\n\nsample_data = create_test_data(n_trials=500)\nverify_data(sample_data)\nbase_face_path = \"./base_face.jpg\"\n\nsample_pipe_generator_kwargs = {\n    'addition': 1000\n}\n\ndef sample_pipe_generator(seed, addition):\n    return {'modified_seed': seed + addition}\n\nsample_pipe_receiver_kwargs = {\n    'use_cache': True,\n    'save_folder': 'sample'\n}\n\n@cache_as_numpy\ndef sample_pipe_receiver(modified_seed, cache_path=None):\n    return {'modified_seed': modified_seed}\n\npipelines = [\n    (sample_pipe_generator, sample_pipe_generator_kwargs),\n    (sample_pipe_receiver, sample_pipe_receiver_kwargs)\n]\n\nanalyze_data(sample_data, base_face_path, pipelines=pipelines, n_trials=500)\n```\n__NOTE__: If you want to leverage caching, use the `@cache_as_numpy` decorator and add `cache_path=None` to the function signature.\n\n### Parameters always exposed to pipeline functions\nBy default, the following parameters can be used within all pipeline functions:\n`base_image` - base image used in the experiment\n`responses` - sorted responses to the stimulus images in the current split. E.g, 1 or -1 mapping the choice to original or inverted noise images \n`pipeline_id` - string combining the label and participant/codnition split. Used for caching.\n`experiment_path`- relative path where data will be stored\n`stimuli_params` - the parameter space used to generate the stimulus images\n`patches` - noise patches used to generate the original and inverted stimulus images \n`patch_idx` - patch indices \n`n_trials` - number of trials\n`n_scales` - number of scales used when generating the noise\n`gabor_sigma` - sigma parameter when using gabor noise\n`noise_type` - method used for generating noise. Either `sinusoid` or `gabor`\n`seed` - seed value\nTo use either of them, just add the variable to the method signature. Be mindful that changing those values directly will persist for the rest of the pipeline run.\n\n# Compatibility with R's rcicr\nThe implementation should produce the same results between this one and R's implementations with a few caveats. First, there are differences in how pythons' `numpy` and R's `random` packages generate random numbers. Even though `rcpyci` and `rcicr` can both be seeded, the output will differ. Second, there differences in how certain operations in the underlying libraries are implemented, which results in small numerical differences, the biggest being at computing the cis with roughly `~0.0005` difference. A comparison between the analogous functions can be ran using the `run_tests.sh` script from the `ref` folder in this repository. More info on that [here](ref/README.md).\n\n## How to use rcicr stimuli in rcpyci\nIf you have experiment data created with the R `rcicr` package, you can still use `rcpyci` to process your data. As the random number generation approaches between python and R are not interchangeable, the parameter space generated by `rcicr` needs to be exported. After that, you can process your data `rcpyci`. \n\nThe easiest way is to use the docker container which has a functional R environment as well as the necessary `rpy2` python package. It can be started as follows:\n```bash\ndocker run -it -w / -v ./data:/data hvalev/rcpyci  /bin/bash\nexport R_HOME=/usr/local/lib/R \u0026\u0026 export LD_LIBRARY_PATH=/usr/local/lib/R/lib:/usr/local/lib/R/\n/pyrcicr/bin/python3\n```\n__NOTE__: This will create a data folder in the current working dir on your host, where the exported parameter space will be stored.\n\nMake sure that you have your RData file created by `rcicr` in the `./data` folder. Afterwards you need to load and convert to to a numpy array like this:\n```python\nimport rpy2.robjects as robjects\nimport numpy as np\nrobjects.r['load'](\"/data/test.RData\")\nz = np.array(robjects.conversion.rpy2py(robjects.r['stimuli_params']))\nz.shape\n```\nIn my case, the number of trials were 500, hence the shape of the reshaped array below. Feel free to adjust the number below to the number of trials used in your own experiment. Additionally, you can double-check that the max and min values in the converted array are within the (-1,1) interval as one would expect.\n```python\nt = z.reshape(500,4092)\nnp.save('/data/stimulus.npy', t)\nt.max()\nt.min()\n```\nWith the stimulus.npy file in place you can analyze your data with `rcpyci` by loading and passing the numpy array as `stimulus_param` in the `analyze_data` function in `rcpyci.interface`. This would disable re-generating the parameter space using the provided seed value and use this sideloaded parameter space instead. You must make sure that you provide matching parameters for `n_scales` and `n_trials` so that the patches and patch indices are generated with the correct array dimensions.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhvalev%2Frcpyci","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhvalev%2Frcpyci","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhvalev%2Frcpyci/lists"}