Full Code of matthias-k/pysaliency for AI

dev b93d86a5fe0d cached
190 files
5.2 MB
1.4M tokens
1253 symbols
1 requests
Download .txt
Showing preview only (5,459K chars total). Download the full file or copy to clipboard to get everything.
Repository: matthias-k/pysaliency
Branch: dev
Commit: b93d86a5fe0d
Files: 190
Total size: 5.2 MB

Directory structure:
gitextract_9eg4n1my/

├── .github/
│   └── workflows/
│       └── test-package-conda.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.md
├── MANIFEST.in
├── Makefile
├── README.md
├── docs/
│   └── superpowers/
│       ├── plans/
│       │   ├── 2026-03-21-crossvalidated-baseline-model-hdf5.md
│       │   └── 2026-03-23-compact-hdf5-export.md
│       └── specs/
│           ├── 2026-03-21-model-hdf5-save-reload-design.md
│           └── 2026-03-23-hdf5-compact-export-design.md
├── examples/
│   └── docker_submission/
│       ├── README.md
│       ├── docker_deepgaze3/
│       │   ├── Dockerfile
│       │   ├── model_server.py
│       │   └── requirements.txt
│       ├── docker_pysaliency_model/
│       │   ├── Dockerfile
│       │   ├── model_server.py
│       │   ├── requirements.txt
│       │   └── sample_submission.py
│       └── sample_evaluation.py
├── notebooks/
│   ├── LSUN.ipynb
│   └── Tutorial.ipynb
├── pyproject.toml
├── pysaliency/
│   ├── __init__.py
│   ├── baseline_utils.py
│   ├── dataset_config.py
│   ├── datasets/
│   │   ├── __init__.py
│   │   ├── fixations.py
│   │   ├── scanpaths.py
│   │   ├── stimuli.py
│   │   └── utils.py
│   ├── external_datasets/
│   │   ├── __init__.py
│   │   ├── cat2000.py
│   │   ├── coco_freeview.py
│   │   ├── coco_search18.py
│   │   ├── dut_omrom.py
│   │   ├── figrim.py
│   │   ├── isun.py
│   │   ├── koehler.py
│   │   ├── mit.py
│   │   ├── nusef.py
│   │   ├── osie.py
│   │   ├── pascal_s.py
│   │   ├── salicon.py
│   │   ├── scripts/
│   │   │   ├── extract_fixations.m
│   │   │   └── load_cat2000.m
│   │   ├── toronto.py
│   │   └── utils.py
│   ├── external_models/
│   │   ├── __init__.py
│   │   ├── deepgaze.py
│   │   ├── matlab_models.py
│   │   ├── models.py
│   │   ├── scripts/
│   │   │   ├── AIM_wrapper.m
│   │   │   ├── BMS/
│   │   │   │   ├── BMS_wrapper.m
│   │   │   │   └── patches/
│   │   │   │       ├── adapt_opencv_paths.diff
│   │   │   │       ├── correct_add_path.diff
│   │   │   │       ├── fix_FileGettor.diff
│   │   │   │       └── series
│   │   │   ├── ContextAwareSaliency_wrapper.m
│   │   │   ├── CovSal_wrapper.m
│   │   │   ├── GBVS/
│   │   │   │   ├── GBVSIttiKoch_wrapper.m
│   │   │   │   ├── GBVS_wrapper.m
│   │   │   │   └── patches/
│   │   │   │       ├── get_path
│   │   │   │       ├── make_mex_files_octave_compatible
│   │   │   │       └── series
│   │   │   ├── IttiKoch_wrapper.m
│   │   │   ├── Judd/
│   │   │   │   ├── FaceDetect_patches/
│   │   │   │   │   ├── change_opencv_include
│   │   │   │   │   └── series
│   │   │   │   ├── JuddSaliencyModel_patches/
│   │   │   │   │   ├── find_cascade_file
│   │   │   │   │   ├── locate_FelzenszwalbDetector_files
│   │   │   │   │   └── series
│   │   │   │   ├── Judd_wrapper.m
│   │   │   │   ├── SaliencyToolbox_patches/
│   │   │   │   │   ├── enable_unit16
│   │   │   │   │   └── series
│   │   │   │   └── voc_patches/
│   │   │   │       ├── change_fconv
│   │   │   │       ├── matlabR2014a_compatible
│   │   │   │       ├── matlabR2021a_compatible
│   │   │   │       └── series
│   │   │   ├── RARE2012_wrapper.m
│   │   │   ├── SUN_wrapper.m
│   │   │   └── ensure_image_is_color_image.m
│   │   └── utils.py
│   ├── filter_datasets.py
│   ├── hdf5.py
│   ├── http_models.py
│   ├── metric_optimization.py
│   ├── metric_optimization_tf.py
│   ├── metric_optimization_torch.py
│   ├── metrics.py
│   ├── models.py
│   ├── numba_utils.py
│   ├── optpy/
│   │   ├── README.md
│   │   ├── __init__.py
│   │   ├── jacobian.py
│   │   └── optimization.py
│   ├── plotting.py
│   ├── precomputed_models.py
│   ├── quilt.py
│   ├── roc.py
│   ├── roc_cython.pyx
│   ├── saliency_map_conversion.py
│   ├── saliency_map_conversion_theano.py
│   ├── saliency_map_conversion_torch.py
│   ├── saliency_map_models.py
│   ├── sampling_models.py
│   ├── tf_utils.py
│   ├── theano_utils.py
│   ├── torch_datasets.py
│   ├── torch_utils.py
│   └── utils/
│       ├── __init__.py
│       └── variable_length_array.py
├── pytest.ini
├── requirements.txt
├── setup.py
└── tests/
    ├── conftest.py
    ├── datasets/
    │   ├── test_datasets.py
    │   ├── test_fixations.py
    │   ├── test_scanpaths.py
    │   ├── test_stimuli.py
    │   └── utils.py
    ├── external_datasets/
    │   ├── test_COCO_Search18.py
    │   ├── test_NUSEF.py
    │   ├── test_PASCAL_S.py
    │   ├── test_SALICON.py
    │   └── test_coco_freeview.py
    ├── external_models/
    │   ├── AIM_color_stimulus.npy
    │   ├── AIM_grayscale_stimulus.npy
    │   ├── ContextAwareSaliency_color_stimulus.npy
    │   ├── ContextAwareSaliency_grayscale_stimulus.npy
    │   ├── CovSal_color_stimulus.npy
    │   ├── CovSal_grayscale_stimulus.npy
    │   ├── GBVSIttiKoch_color_stimulus.npy
    │   ├── GBVSIttiKoch_grayscale_stimulus.npy
    │   ├── GBVS_color_stimulus.npy
    │   ├── GBVS_grayscale_stimulus.npy
    │   ├── IttiKoch_color_stimulus.npy
    │   ├── IttiKoch_grayscale_stimulus.npy
    │   ├── Judd_color_stimulus.npy
    │   ├── Judd_grayscale_stimulus.npy
    │   ├── RARE2007_color_stimulus.npy
    │   ├── RARE2007_grayscale_stimulus.npy
    │   ├── RARE2012_color_stimulus.npy
    │   ├── RARE2012_grayscale_stimulus.npy
    │   ├── SUN_color_stimulus.npy
    │   ├── SUN_grayscale_stimulus.npy
    │   ├── color_stimulus.npy
    │   ├── grayscale_stimulus.npy
    │   └── test_deepgaze.py
    ├── skippedtest_theano_utils.py
    ├── test_baseline_utils.py
    ├── test_crossvalidation.py
    ├── test_dataset_config.py
    ├── test_external_datasets.py
    ├── test_external_models.py
    ├── test_filter_datasets.py
    ├── test_hdf5_io.py
    ├── test_helpers.py
    ├── test_http_models.py
    ├── test_metric_optimization.py
    ├── test_metric_optimization_tf.py
    ├── test_metric_optimization_torch.py
    ├── test_models.py
    ├── test_numba_utils.py
    ├── test_precomputed_models.py
    ├── test_quilt/
    │   ├── .pc/
    │   │   ├── .quilt_patches
    │   │   ├── .quilt_series
    │   │   ├── .version
    │   │   ├── add_numbers.diff/
    │   │   │   ├── .timestamp
    │   │   │   └── source.txt
    │   │   └── applied-patches
    │   ├── patches/
    │   │   ├── add_numbers.diff
    │   │   └── series
    │   ├── source/
    │   │   ├── .pc/
    │   │   │   ├── .quilt_patches
    │   │   │   ├── .quilt_series
    │   │   │   └── .version
    │   │   └── source.txt
    │   ├── source.txt
    │   └── target/
    │       └── source.txt
    ├── test_quilt.py
    ├── test_saliency_map_conversion.py
    ├── test_saliency_map_conversion_theano.py
    ├── test_saliency_map_conversion_torch.py
    ├── test_saliency_map_conversion_torch_extended.py
    ├── test_saliency_map_models.py
    ├── test_sampling.py
    ├── test_torch_datasets.py
    ├── test_torch_utils.py
    ├── test_utils.py
    └── utils/
        └── test_variable_length_array.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/test-package-conda.yml
================================================
name: Tests

on: [push, pull_request]

jobs:
  build-linux:
    runs-on: ubuntu-latest
    strategy:
      max-parallel: 5
      matrix:
        python-version:
        - "3.9"
        - "3.10"
        - "3.11"
        - "3.12"
        - "3.13"
    steps:
    - uses: actions/checkout@v4
    - uses: conda-incubator/setup-miniconda@v3
      with:
        python-version: ${{ matrix.python-version }}
        channels: conda-forge
    - name: Conda info
      # the shell setting is necessary for loading profile etc which activates the conda environment
      shell: bash -el {0}
      run: conda info
    - name: Install dependencies
      shell: bash -el {0}
      run: |
        conda install \
          boltons \
          cython \
          deprecation \
          dill \
          diskcache \
          h5py \
          imageio \
          natsort \
          numba \
          numpy \
          numpydoc \
          orjson \
          pandas \
          piexif \
          pillow \
          pip \
          pkg-config \
          pytorch \
          requests \
          schema \
          scikit-learn \
          scipy \
          setuptools \
          sphinx \
          torchvision \
          tqdm
    - name: Conda list
      shell: bash -el {0}
      run: conda list
    - name: Test with pytest
      shell: bash -el {0}
      run: |
        conda install pytest hypothesis
        pip install -e .
        python -m pytest --nomatlab --notheano --nodownload tests
    - name: test build and install
      shell: bash -el {0}
      run: |
        pip install build
        python -m build --sdist
        pip install dist/*.tar.gz
        mkdir tmp && cd tmp && python -c "import pysaliency"


================================================
FILE: .gitignore
================================================
.hypothesis
.mypy_cache
/tmp/
/build/
/pysaliency_datasets/
/test_datasets/
/test_models/
*.pyc
*.swp
*.c
*.so
*.egg-info
.vscode
.DS_Store


================================================
FILE: .travis.yml
================================================
language: python
python:
#  - 2.7
  - 3.6
  - 3.7
before_install:
  - sudo apt-get update
  - sudo apt-get install g++
  - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
      wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh;
    else
      wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
    fi
  - bash miniconda.sh -b -p $HOME/miniconda
  - source "$HOME/miniconda/etc/profile.d/conda.sh"
  - hash -r
  - conda config --set always_yes yes --set changeps1 no
  - conda config --add channels conda-forge
  - conda update -q conda
  # Useful for debugging any issues with conda
  - conda info -a
#  - wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh
#  - chmod +x miniconda.sh
#  - ./miniconda.sh -b -p $HOME/miniconda
#  - export PATH=/home/travis/miniconda/bin:$PATH
#  - conda config --set always_yes yes --set changeps1 no
#  - conda config --add channels conda-forge
#  - conda update -q conda
#  - pip install tqdm
install:
  - conda create -q -n test-env python=$TRAVIS_PYTHON_VERSION numpy scipy cython  six setuptools sphinx numpydoc pkg-config pillow tqdm boltons natsort requests dill pytest theano imageio scikit-learn pandas pytorch hypothesis
#  - conda install -q -n test-env tensorflow=1.13.2
  - conda activate test-env
before_script:
  - conda info
  - conda list
  - pip --version
  - pip freeze
script:
  # Make sure the library installs.
  - python setup.py install
  - python setup.py build_ext --inplace
  - python -c "import pysaliency"  # make sure can be installed without theano, pytorch, and tensorflow
  - conda install theano pytorch
  - pip install tensorflow==1.13.2 schema
  - python -m pytest --nomatlab tests
  - python setup.py sdist
  - pip install dist/*.tar.gz
  - mkdir tmp && cd tmp && python -c "import pysaliency"


================================================
FILE: CHANGELOG.md
================================================
# Changelog

* 0.3 (dev):
  This is a major release, refactoring most of `pysaliency.datasets` including some breaking changes.
  * Feature: `Scanpaths` is a new class for storing scanpaths. It has a similar API to the old `FixationTrains`,
    but exclusively cares about scanpaths. For example, the length of a Scanpaths instance is the number of scanpaths,
    not the number of fixations. It is intended to make iterating over and working with scanpaths more convenient.
  * Feature: `ScanpathFixations` is a new subclass of `Fixations` intended for handling fixations that come from scanpaths.
    It is intended to replace the old `FixationTrains` class. `ScanpathFixations` has a `scanpaths: Scanpaths`
    attribute storing the source scanpaths (what used to be stored manually as `train_xs` etc in `FixationTrains`).
    Unlike `FixationTrains`, `ScanpathFixations` does not have any attributes that are not derived from the scanpaths.
    `FixationTrains` is now a deprecated subclass of `ScanpathFixations` which adds the old properties and constructor.
    and allows for attributes which are neither scanpath attributes nor scanpath fixation attributes.
  * Feature: `VariableLengthArray` for inuititively handling data like scanpaths where each row can have a different length.
    `Fixations.x_Hist`, `Fixations.y_hist`, `Scanpaths.xs` etc are now instances of `VariableLengthArray`.
  * `Fixations.lengths` has been renamed to `Fixations.scanpath_history_length` to make it clear that it is the length of the scanpath history.
    `Fixations.lengths` is now a deprecated alias.
  * In general, naming convention for attriutes has been changed to use the plural form if the attribute is a list of values for each
    element (i.e., `Scanpaths.xs`) and the singular form if the attribute is a single value (i.e., `Scanpaths.length`,  `Fixations.x`). This resulted in
    renaming `Fixations.subjects` to `Fixations.subject`. The old name is now a deprecated alias.
  * Bugfix: Compatibility with torch 2.2 and numpy 2.0
  * Bugfix!: The download location of the RARE2012 model changed. The new source code results in slightly different predictions.
  * Feature: The RARE2007 model is now available as `pysaliency.external_models.RARE2007`. It's execution requires MATLAB.
  * matlab scripts are now called with the `-batch` option instead of `-nodisplay -nosplash -r`, which should behave better.
  * Enhancement: preloaded stimulus ids are passed on to subsets of Stimuli and FileStimuli.
  * Feature: `pysaliency.read_hdf5` now takes additional keyword arguments which are passed to the respective class methods. This allows, e.g., to load `FileStimuli` with caching disabled.
  * Enhancement: `pysaliency.HDF5Model` and `pysaliency.HDF5SaliencyMapModel` now better handle the case of loading a model for a subset of entries in the HDF5 file which might be saved under a certain common prefix.
  * Feature: `pysaliency.saliency_map_conversation_torch` now specifies constraints, where applicable, as linear. To that end, `pysaliency.optpy` now allows specifying linear constraints. This results in better optimization performance, especially since scipy 1.15.0 fixed a bug that in our case actually helped.
  * Feature: `pysaliency.baseline_utils.BaselineModel` now supports `to_hdf5` / `read_hdf5` model persistence using HDF5 type `pysaliency.baseline_utils.BaselineModel`.
  * Enhancement: Introduced unified HDF5 I/O dispatch in `pysaliency.hdf5` (`read_hdf5`) and routed `pysaliency.datasets.read_hdf5` through the dataset dispatcher for compatibility.
  * Enhancement: `BaselineModel` no longer stores `stimuli` and `fixations` after initialization; it keeps only the normalized fixation representation used for predictions.


* 0.2.22:
  * Enhancement: New [Tutorial](notebooks/Tutorial.ipynb).
  * Bugfix: `SaliencyMapModel.AUC` failed if some images didn't have any fixations.
  * Feature: `StimulusDependentSaliencyMapModel`
  * Bugfix: The NUSEF dataset scaled some fixations not correctly to image coordinates. Also, we now account for some typos in the
    dataset source data.
  * Feature: CrossvalMultipleRegularizations and GeneralMixtureKernelDensityEstimator in baseline utils (names might change!)
  * Feature: DVAAwareScanpathModel
  * Feature: ShuffledBaselineModel is now much more efficient and able to handle large numbers of stimuli.
    hence, ShuffledSimpleBaselineModel is not necessary anymore and a deprecated alias to ShuffledBaselineModel
  * Feature: ShuffledBaselineModel can now compute predictions for very large numbers of stimuli without needing
    to have all individual predictions in memory due to a recursive reduce logsumexp implementation.
  * Feature: `plotting.plot_scanpath` to visualize scanpaths and saccades. WIP, expect the API to change!
  * Feature: DeepGaze I and DeepGazeIIE models
  * Feature: COCO Freeview dataset
  * Feature: `optimize_for_information_gain(framework='torch', ...) now supports a `cache_directory`,
    where intermediate steps are cached. This supports resuming crashed optimization runs.
  * Bugfix: fixed some edge cases in `optimize_for_information_gain(framework='torch')`
  * Feature: COCO Seach18 dataset
  * Feature: `FixationTrains.train_lengths`
  * Feature: `FixationTrains.scanpath_fixation_attributes` allows handling of per-fixation attributes on scanpath level,
    e.g. fixation durations. According attributes as in a Fixations instance are automatically created,
    e.g. for durations there will be an attribute `durations` and an attribute `duration_hist`. Also
    for scanpath_attributes (e.g., attributes applying to a whole scanpath, such as task) will also generate
    an attribute for each fixation to make this information available in Fixations instance.
  * Feature: `scanpaths_from_fixations` reconstructs a FixationTrains object from a Fixations instance
  * Bugfix: `t_hist` got replaced with `y_hist` in Fixations instances (but luckily not in FixationTrains instances)
  * Bugfix: torch code was broken due to changes in torch 1.11
  * Bugfix: SALICON dataset download did not work anymore
  * Bugfix: NUSEF datast links changed

* 0.2.21:
  * Added new datasets: PASCAL-S and DUT-OMRON
  * Feature: FixedStimulusSizeModel and DVAAwareModel
  * Feature: Fixations finally support len()
  * Experimental feature: conditional_log_densities(stimuli, fixations) and conditional_saliency_maps(...).
    This is WIP to enable batch processing in models.
  * Fallback models for stimulus dependent models
  * MixtureScanpathModel
  * Reimplemented AUC for special case of only one positive sample, leading to substantial speedup
  * There is a new version of the CAT2000 train dataset which fixes some details in the processing.
    Since it changes the dataset, by default the old processing is used.
  * Feature: ShuffledSimpleBaselineModel. Baseline model to be used with ShuffledAUCSaliencyMapModel
    in cases where using ShuffledBaselineModel is not feasible.
  * `pysaliency.get_toronto` now returns a `Fixations` instance instead of `FixationTrains` since
    we don not have scanpath information.
  * `pysaliency.baseline_utils.KDEGoldModel` now supports a keyword argument `grid_spacing` which
    controls how densly the log density of the KDEModel is computed before it is linearly interpolated.
    This can substantially speed up computations on high resolution images.
  * Feature: `pysaliency.precomputed_models.SaliencyMapModelFromArchive` and `ModelFromArchive`
    for loading model predictions from ZIP, TAR and RAR files.
  * Bugfix: all matlab scripts where missing in the pip installation since the change
    to setuptools.
* 0.2.20:
  * Stimuli now support attributes, just like Fixations. The CAT2000 train and test
    datasets now have the stimulus categories as attribute.
  * failure to download and setup a dataset will no longer result in leftover
    dataset files that keep pysaliency from trying again.
  * crossvalidation splits now support stratifying stimulus attributes
  * the MIT1003 dataset now also contains the history of fixation durations
  * FixationIndexDependentModel
  * Bugfix: The CC of a constant saliency map wrt to a nonconstant one
    now returns zero (instead of nan as previously).
  * Feature: Added keyword argument `attributes` to `Fixations` constructor
  * Feature: Provide KLDiv and SIM as functions that can be applied to saliency maps without need for a model.
* 0.2.19:
  * added pytorch implementation for optimization of similarity metric as alternative
    to tensorflow implementation which still uses tensorflow 1.x
  * added pytorch implementation for saliency map processing as alternative
    to theano implementation.
  * removed obsolete dependency on openmp
  * made import of pytorch, theano and tensorflow optional
  * bugfixes in precomputed models for stimuli sets with nested directories


================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) 2015 Matthias Kümmerer

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: MANIFEST.in
================================================
include README.md
include LICENSE.md
include pysaliency/*.pyx
include pysaliency/scripts/*.m
include pysaliency/scripts/models/*.m
include pysaliency/scripts/models/*/*.m
include pysaliency/scripts/models/*/*/*
include pysaliency/scripts/models/BMS/patches/*
include pysaliency/scripts/models/GBVS/patches/*
include pysaliency/scripts/models/Judd/patches/*


================================================
FILE: Makefile
================================================
cython:
	#python setup.py build_ext --inplace
	python3 setup.py build_ext --inplace

test: cython
	python3 -m pytest --nomatlab tests

prepublish:
	./run-docker.sh rm -rf dist
	./run-docker.sh bash build.sh
	twine upload --repository=pysaliency-test dist/pysaliency*.tar.gz  # assumes that ~/.pypirc defines a pysaliency-test entry, see https://test.pypi.org/manage/account/token/
#	twine upload dist/pysaliency*.tar.gz -r testpypi


publish:
	./run-docker.sh rm -rf dist
	./run-docker.sh bash build.sh
	twine upload --repository=pysaliency dist/pysaliency*.tar.gz



================================================
FILE: README.md
================================================
Pysaliency
==========

![test](https://github.com/matthias-k/pysaliency/actions/workflows/test-package-conda.yml/badge.svg)

Pysaliency is a python package for saliency modelling. It aims at providing a unified interface
to both the traditional saliency maps used in saliency modeling as well as probabilistic saliency
models.

Pysaliency can evaluate most commonly used saliency metrics, including AUC, sAUC, NSS, CC
image-based KL divergence, fixation based KL divergence and SIM for saliency map models and
log likelihoods and information gain for probabilistic models.

Installation
------------

You can install pysaliency from pypi via

    pip install pysaliency


Quickstart
----------

    import pysaliency

    dataset_location = 'datasets'
    model_location = 'models'

    mit_stimuli, mit_fixations = pysaliency.external_datasets.get_mit1003(location=dataset_location)
    aim = pysaliency.AIM(location=model_location)
    saliency_map = aim.saliency_map(mit_stimuli.stimuli[0])

    plt.imshow(saliency_map)


    auc = aim.AUC(mit_stimuli, mit_fixations)

If you already have saliency maps for some dataset, you can import them into pysaliency easily:

    my_model = pysaliency.SaliencyMapModelFromDirectory(mit_stimuli, '/path/to/my/saliency_maps')
    auc = my_model.AUC(mit_stimuli, mit_fixations)

Check out the [Tutorial](notebooks/Tutorial.ipynb) for a more detailed introduction!

Included datasets and models
----------------------------

Pysaliency provides several important datasets:

* MIT1003
* MIT300
* CAT2000
* Toronto
* Koehler
* iSUN
* SALICON (both the 2015 and the 2017 edition and each with both the original mouse traces and the inferred fixations)
* FIGRIM
* OSIE
* NUSEF (the part with public images)

and some influential models:
* AIM
* SUN
* ContextAwareSaliency
* BMS
* GBVS
* GBVSIttiKoch
* Judd
* IttiKoch
* RARE2012
* CovSal

These models are using the original code which is often matlab.
Therefore, a matlab licence is required to make use of these models, although quite some of them
work with octave, too (see below).


Using Octave
------------

pysaliency will fall back to octave if no matlab is installed.
Some models might work with octave, e.g. AIM and GBVSIttiKoch. In Debian/Ubuntu you need to install
`octave`, `octave-image`, `octave-statistics`, `liboctave-dev`.

These models and dataset seem to work with octave:

- models
  - AIM
  - GBVSIttiKoch
- datasets
  - Toronto
  - MIT1003
  - MIT300
  - SALICON

Dependencies
-----------

The Judd Model needs some libraries to work. In ubuntu/debian you need to install these packages:
`libopencv-core-dev, libopencv-flann-dev, libopencv-imgproc-dev, libopencv-photo-dev, libopencv-video-dev, libopencv-features2d-dev, libopencv-objdetect-dev, libopencv-calib3d-dev, libopencv-ml-dev, opencv2/contrib/contrib.hpp`


================================================
FILE: docs/superpowers/plans/2026-03-21-crossvalidated-baseline-model-hdf5.md
================================================
# CrossvalidatedBaselineModel HDF5 Save/Reload Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add `to_hdf5`/`read_hdf5` to `CrossvalidatedBaselineModel` so it can be serialized by parameters (not predictions), wired into the `pysaliency.read_hdf5` dispatcher.

**Architecture:** Refactor `CrossvalidatedBaselineModel` to store only `fixations_n` (not the full fixations object), then add `to_hdf5`/`read_hdf5` following the exact same pattern as the already-complete `BaselineModel`. Register in the `_MODEL_READERS` dispatch table in `pysaliency/hdf5.py`.

**Tech Stack:** Python, h5py, numpy, pytest. Build Cython extensions with `make cython` before running tests.

---

## File Map

| File | Change |
|------|--------|
| `pysaliency/baseline_utils.py` | Refactor `CrossvalidatedBaselineModel.__init__` and `_log_density`; add `to_hdf5` and `read_hdf5` |
| `pysaliency/hdf5.py` | Add `_read_crossvalidated_baseline_model` helper; extend existing `_MODEL_READERS` dict |
| `tests/test_baseline_utils.py` | Extend top-level import; add 6 new tests |
| `tests/test_hdf5_io.py` | Extend top-level import; add 3 new tests for dispatcher, kwarg override, and cache bypass |

---

## Task 1: Refactor `CrossvalidatedBaselineModel` internals

Replace `self.fixations` + `self.shape_cache` with `self.fixations_n`. Public constructor signature is unchanged.

**Files:**
- Modify: `pysaliency/baseline_utils.py:559-591`
- Modify: `tests/test_baseline_utils.py`

- [ ] **Step 1: Update the top-level import in `tests/test_baseline_utils.py`**

The existing import block (lines 9–17) imports from `pysaliency.baseline_utils`. Add `CrossvalidatedBaselineModel` to it:

```python
from pysaliency.baseline_utils import (
    BaselineModel,
    CrossvalidatedBaselineModel,
    CrossvalMultipleRegularizations,
    GeneralMixtureKernelDensityEstimator,
    KDEGoldModel,
    MixtureKernelDensityEstimator,
    ScikitLearnImageCrossValidationGenerator,
    fill_fixation_map,
)
```

- [ ] **Step 2: Write a failing test that asserts the new internal state**

Add to `tests/test_baseline_utils.py`:

```python
def test_crossvalidated_baseline_model_stores_fixations_n(stimuli, scanpath_fixations):
    model = CrossvalidatedBaselineModel(stimuli, scanpath_fixations, bandwidth=0.1)
    assert hasattr(model, 'fixations_n')
    assert not hasattr(model, 'fixations')
    assert not hasattr(model, 'shape_cache')
    np.testing.assert_array_equal(model.fixations_n, scanpath_fixations.n)
```

- [ ] **Step 3: Run the test to confirm it fails**

```bash
python -m pytest --nomatlab tests/test_baseline_utils.py::test_crossvalidated_baseline_model_stores_fixations_n -v
```

Expected: FAIL — `model` has no `fixations_n`, still has `fixations` and `shape_cache`.

- [ ] **Step 4: Implement the refactor**

In `pysaliency/baseline_utils.py`, replace the `CrossvalidatedBaselineModel` class body (lines 559–591) with:

```python
class CrossvalidatedBaselineModel(Model):
    def __init__(self, stimuli, fixations, bandwidth, eps=1e-20, **kwargs):
        super(CrossvalidatedBaselineModel, self).__init__(**kwargs)
        self.stimuli = stimuli
        self.bandwidth = bandwidth
        self.eps = eps
        self.xs, self.ys = normalize_fixations(stimuli, fixations)
        self.fixations_n = fixations.n.copy()

    def _log_density(self, stimulus):
        shape = stimulus.shape[0], stimulus.shape[1]

        stimulus_id = get_image_hash(stimulus)
        stimulus_index = self.stimuli.stimulus_ids.index(stimulus_id)

        inds = self.fixations_n != stimulus_index

        ZZ = np.zeros(shape)

        _fixations = np.array([self.ys[inds]*shape[0], self.xs[inds]*shape[1]]).T
        fill_fixation_map(ZZ, _fixations)
        ZZ = gaussian_filter(ZZ, [self.bandwidth*shape[0], self.bandwidth*shape[1]])
        ZZ *= (1-self.eps)
        ZZ += self.eps * 1.0/(shape[0]*shape[1])
        ZZ = np.log(ZZ)

        ZZ -= logsumexp(ZZ)

        return ZZ
```

- [ ] **Step 5: Run the new test plus the full baseline_utils suite**

```bash
python -m pytest --nomatlab tests/test_baseline_utils.py -v
```

Expected: all pass. If any existing test relied on `model.fixations` or `model.shape_cache`, it will fail here — fix it in the same commit.

- [ ] **Step 6: Commit**

```bash
git add pysaliency/baseline_utils.py tests/test_baseline_utils.py
git commit -m "refactor: CrossvalidatedBaselineModel stores fixations_n, removes shape_cache"
```

---

## Task 2: Add `to_hdf5` to `CrossvalidatedBaselineModel`

**Files:**
- Modify: `pysaliency/baseline_utils.py`
- Modify: `tests/test_baseline_utils.py`

- [ ] **Step 1: Write the failing test**

Add to `tests/test_baseline_utils.py`. Note: `import h5py` goes inside the test function body, matching the style of the existing `test_baseline_model_hdf5_type` test:

```python
def test_crossvalidated_baseline_model_hdf5_type(tmp_path, stimuli, scanpath_fixations):
    model = CrossvalidatedBaselineModel(stimuli, scanpath_fixations, bandwidth=0.1)
    path = tmp_path / 'cv_baseline_type.hdf5'
    model.to_hdf5(path)

    import h5py
    with h5py.File(path, 'r') as f:
        value = f.attrs['type']
        if not isinstance(value, str):
            value = value.decode('utf8')
        assert value == 'pysaliency.baseline_utils.CrossvalidatedBaselineModel'
```

- [ ] **Step 2: Run to confirm failure**

```bash
python -m pytest --nomatlab tests/test_baseline_utils.py::test_crossvalidated_baseline_model_hdf5_type -v
```

Expected: FAIL — `CrossvalidatedBaselineModel` has no `to_hdf5`.

- [ ] **Step 3: Implement `to_hdf5`**

Add the method to `CrossvalidatedBaselineModel` in `pysaliency/baseline_utils.py`, immediately after `_log_density`. `hdf5_wrapper` is already imported from `.datasets.utils` at the top of the file:

```python
    @hdf5_wrapper(mode='w')
    def to_hdf5(self, target, include_stimuli=True):
        target.attrs['type'] = np.bytes_('pysaliency.baseline_utils.CrossvalidatedBaselineModel')
        target.attrs['version'] = np.bytes_('1.0')
        target.attrs['bandwidth'] = self.bandwidth
        target.attrs['eps'] = self.eps
        target.create_dataset('xs', data=self.xs)
        target.create_dataset('ys', data=self.ys)
        target.create_dataset('fixations_n', data=self.fixations_n)

        if include_stimuli:
            stimuli_group = target.create_group('stimuli')
            self.stimuli.to_hdf5(stimuli_group)
```

- [ ] **Step 4: Run the type test**

```bash
python -m pytest --nomatlab tests/test_baseline_utils.py::test_crossvalidated_baseline_model_hdf5_type -v
```

Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add pysaliency/baseline_utils.py tests/test_baseline_utils.py
git commit -m "feat: add CrossvalidatedBaselineModel.to_hdf5"
```

---

## Task 3: Add `read_hdf5` to `CrossvalidatedBaselineModel` with full roundtrip tests

**Files:**
- Modify: `pysaliency/baseline_utils.py`
- Modify: `tests/test_baseline_utils.py`

- [ ] **Step 1: Write all four failing tests at once**

Add to `tests/test_baseline_utils.py`:

```python
def test_crossvalidated_baseline_model_hdf5_roundtrip_with_stimuli(tmp_path, stimuli, scanpath_fixations):
    model = CrossvalidatedBaselineModel(stimuli, scanpath_fixations, bandwidth=0.1)
    first_prediction = model.log_density(stimuli[0]).copy()

    path = tmp_path / 'cv_baseline.hdf5'
    model.to_hdf5(path)

    reloaded = CrossvalidatedBaselineModel.read_hdf5(path)
    np.testing.assert_allclose(reloaded.log_density(stimuli[0]), first_prediction)


def test_crossvalidated_baseline_model_hdf5_roundtrip_without_stimuli(tmp_path, stimuli, scanpath_fixations):
    model = CrossvalidatedBaselineModel(stimuli, scanpath_fixations, bandwidth=0.1)
    first_prediction = model.log_density(stimuli[1]).copy()

    path = tmp_path / 'cv_baseline_no_stimuli.hdf5'
    model.to_hdf5(path, include_stimuli=False)

    reloaded = CrossvalidatedBaselineModel.read_hdf5(path, stimuli=stimuli)
    np.testing.assert_allclose(reloaded.log_density(stimuli[1]), first_prediction)


def test_crossvalidated_baseline_model_hdf5_error_without_stimuli(tmp_path, stimuli, scanpath_fixations):
    model = CrossvalidatedBaselineModel(stimuli, scanpath_fixations, bandwidth=0.1)
    path = tmp_path / 'cv_baseline_no_stimuli.hdf5'
    model.to_hdf5(path, include_stimuli=False)

    with pytest.raises(ValueError, match='stimuli'):
        CrossvalidatedBaselineModel.read_hdf5(path)


def test_crossvalidated_baseline_model_hdf5_stimuli_kwarg_overrides_embedded(tmp_path, stimuli, scanpath_fixations):
    """stimuli= kwarg takes precedence over the embedded stimuli group."""
    model = CrossvalidatedBaselineModel(stimuli, scanpath_fixations, bandwidth=0.1)
    first_prediction = model.log_density(stimuli[0]).copy()

    path = tmp_path / 'cv_baseline_with_stimuli.hdf5'
    model.to_hdf5(path, include_stimuli=True)

    # Pass the same stimuli explicitly — should still work (kwarg wins)
    reloaded = CrossvalidatedBaselineModel.read_hdf5(path, stimuli=stimuli)
    assert reloaded.stimuli is stimuli
    np.testing.assert_allclose(reloaded.log_density(stimuli[0]), first_prediction)
```

- [ ] **Step 2: Run to confirm all four fail**

```bash
python -m pytest --nomatlab \
  tests/test_baseline_utils.py::test_crossvalidated_baseline_model_hdf5_roundtrip_with_stimuli \
  tests/test_baseline_utils.py::test_crossvalidated_baseline_model_hdf5_roundtrip_without_stimuli \
  tests/test_baseline_utils.py::test_crossvalidated_baseline_model_hdf5_error_without_stimuli \
  tests/test_baseline_utils.py::test_crossvalidated_baseline_model_hdf5_stimuli_kwarg_overrides_embedded \
  -v
```

Expected: all FAIL — `CrossvalidatedBaselineModel` has no `read_hdf5`.

- [ ] **Step 3: Implement `read_hdf5`**

Add to `CrossvalidatedBaselineModel` in `pysaliency/baseline_utils.py`. Decorator stack: `@classmethod` outermost, `@hdf5_wrapper(mode='r')` inner — this matches `BaselineModel.read_hdf5` exactly. `Model` is already available at module level via `from . import Model`.

```python
    @classmethod
    @hdf5_wrapper(mode='r')
    def read_hdf5(
        cls,
        source,
        *,
        stimuli=None,
        caching=True,
        memory_cache_size=None,
        cache_location=None,
    ):
        from .hdf5 import read_hdf5 as _read_hdf5

        data_type = decode_string(source.attrs['type'])
        data_version = decode_string(source.attrs['version'])

        if data_type != 'pysaliency.baseline_utils.CrossvalidatedBaselineModel':
            raise ValueError("Invalid type! Expected 'pysaliency.baseline_utils.CrossvalidatedBaselineModel', got", data_type)
        if data_version != '1.0':
            raise ValueError("Invalid version! Expected '1.0', got", data_version)

        if stimuli is None:
            if 'stimuli' not in source:
                raise ValueError(
                    "No stimuli found in HDF5 file. Pass stimuli= explicitly."
                )
            stimuli = _read_hdf5(source['stimuli'])

        model = cls.__new__(cls)
        Model.__init__(model, cache_location=cache_location, caching=caching, memory_cache_size=memory_cache_size)
        model.bandwidth = source.attrs['bandwidth']
        model.eps = source.attrs['eps']
        model.xs = source['xs'][...]
        model.ys = source['ys'][...]
        model.fixations_n = source['fixations_n'][...]
        model.stimuli = stimuli

        return model
```

- [ ] **Step 4: Run all five tests (type + 4 roundtrip/override)**

```bash
python -m pytest --nomatlab \
  tests/test_baseline_utils.py::test_crossvalidated_baseline_model_hdf5_type \
  tests/test_baseline_utils.py::test_crossvalidated_baseline_model_hdf5_roundtrip_with_stimuli \
  tests/test_baseline_utils.py::test_crossvalidated_baseline_model_hdf5_roundtrip_without_stimuli \
  tests/test_baseline_utils.py::test_crossvalidated_baseline_model_hdf5_error_without_stimuli \
  tests/test_baseline_utils.py::test_crossvalidated_baseline_model_hdf5_stimuli_kwarg_overrides_embedded \
  -v
```

Expected: all PASS.

- [ ] **Step 5: Run the full test suite to catch regressions**

```bash
python -m pytest --nomatlab tests/test_baseline_utils.py -v
```

Expected: all pass.

- [ ] **Step 6: Commit**

```bash
git add pysaliency/baseline_utils.py tests/test_baseline_utils.py
git commit -m "feat: add CrossvalidatedBaselineModel.read_hdf5 with optional stimuli embedding"
```

---

## Task 4: Register in `pysaliency.read_hdf5` dispatcher and add integration tests

**Files:**
- Modify: `pysaliency/hdf5.py`
- Modify: `tests/test_hdf5_io.py`

- [ ] **Step 1: Update the import in `tests/test_hdf5_io.py`**

The existing file imports `BaselineModel` from `pysaliency.baseline_utils`. Extend it to also import `CrossvalidatedBaselineModel`:

```python
from pysaliency.baseline_utils import BaselineModel, CrossvalidatedBaselineModel
```

- [ ] **Step 2: Write the three failing dispatcher tests**

Add to `tests/test_hdf5_io.py`:

```python
def test_unified_read_hdf5_reads_crossvalidated_baseline_model(tmp_path):
    stimuli = pysaliency.Stimuli([np.random.randn(20, 20, 3), np.random.randn(20, 20, 3)])
    fixations = pysaliency.FixationTrains.from_fixation_trains(
        [[1, 2, 3], [4, 8]],
        [[5, 6, 7], [9, 2]],
        [[0, 1, 2], [0, 1]],
        [0, 1],
        [0, 1],
    )
    model = CrossvalidatedBaselineModel(stimuli, fixations, bandwidth=0.1)
    path = tmp_path / 'cv_baseline_model.hdf5'
    model.to_hdf5(path)

    loaded = pysaliency.read_hdf5(path)
    assert isinstance(loaded, CrossvalidatedBaselineModel)
    np.testing.assert_allclose(loaded.log_density(stimuli[0]), model.log_density(stimuli[0]))


def test_unified_read_hdf5_crossvalidated_baseline_model_stimuli_kwarg(tmp_path):
    """stimuli= kwarg is forwarded correctly through the dispatcher."""
    stimuli = pysaliency.Stimuli([np.random.randn(20, 20, 3), np.random.randn(20, 20, 3)])
    fixations = pysaliency.FixationTrains.from_fixation_trains(
        [[1, 2, 3], [4, 8]],
        [[5, 6, 7], [9, 2]],
        [[0, 1, 2], [0, 1]],
        [0, 1],
        [0, 1],
    )
    model = CrossvalidatedBaselineModel(stimuli, fixations, bandwidth=0.1)
    path = tmp_path / 'cv_baseline_no_stimuli.hdf5'
    model.to_hdf5(path, include_stimuli=False)

    loaded = pysaliency.read_hdf5(str(path), stimuli=stimuli)
    assert isinstance(loaded, CrossvalidatedBaselineModel)
    assert loaded.stimuli is stimuli
    np.testing.assert_allclose(loaded.log_density(stimuli[0]), model.log_density(stimuli[0]))


def test_unified_read_hdf5_crossvalidated_baseline_model_stimuli_kwarg_bypasses_cache(tmp_path):
    """Regression: stimuli= kwarg must bypass WeakValueDictionary cache in _read_hdf5_from_file."""
    stimuli = pysaliency.Stimuli([np.random.randn(20, 20, 3), np.random.randn(20, 20, 3)])
    fixations = pysaliency.FixationTrains.from_fixation_trains(
        [[1, 2, 3], [4, 8]],
        [[5, 6, 7], [9, 2]],
        [[0, 1, 2], [0, 1]],
        [0, 1],
        [0, 1],
    )
    model = CrossvalidatedBaselineModel(stimuli, fixations, bandwidth=0.1)
    path = tmp_path / 'cv_baseline_no_stimuli.hdf5'
    model.to_hdf5(path, include_stimuli=False)

    loaded = pysaliency.read_hdf5(str(path), stimuli=stimuli)
    assert loaded.stimuli is stimuli

    # Second call with a different stimuli object — must not return the cached first result
    stimuli2 = pysaliency.Stimuli([np.random.randn(20, 20, 3), np.random.randn(20, 20, 3)])
    loaded2 = pysaliency.read_hdf5(str(path), stimuli=stimuli2)
    assert loaded2.stimuli is stimuli2
    np.testing.assert_allclose(loaded2.log_density(stimuli2[0]), CrossvalidatedBaselineModel(stimuli2, fixations, bandwidth=0.1).log_density(stimuli2[0]))
```

- [ ] **Step 3: Run to confirm all three fail**

```bash
python -m pytest --nomatlab \
  tests/test_hdf5_io.py::test_unified_read_hdf5_reads_crossvalidated_baseline_model \
  tests/test_hdf5_io.py::test_unified_read_hdf5_crossvalidated_baseline_model_stimuli_kwarg \
  tests/test_hdf5_io.py::test_unified_read_hdf5_crossvalidated_baseline_model_stimuli_kwarg_bypasses_cache \
  -v
```

Expected: all FAIL — `CrossvalidatedBaselineModel` not in `_MODEL_READERS`.

- [ ] **Step 4: Update `pysaliency/hdf5.py`**

Add `_read_crossvalidated_baseline_model` after `_read_baseline_model`, then **replace** the existing `_MODEL_READERS` dict definition with the extended version:

```python
def _read_crossvalidated_baseline_model(source, **kwargs):
    from .baseline_utils import CrossvalidatedBaselineModel
    return CrossvalidatedBaselineModel.read_hdf5(source, **kwargs)


_MODEL_READERS = {
    'pysaliency.baseline_utils.BaselineModel': _read_baseline_model,
    'pysaliency.baseline_utils.CrossvalidatedBaselineModel': _read_crossvalidated_baseline_model,
}
```

- [ ] **Step 5: Run all three dispatcher tests**

```bash
python -m pytest --nomatlab \
  tests/test_hdf5_io.py::test_unified_read_hdf5_reads_crossvalidated_baseline_model \
  tests/test_hdf5_io.py::test_unified_read_hdf5_crossvalidated_baseline_model_stimuli_kwarg \
  tests/test_hdf5_io.py::test_unified_read_hdf5_crossvalidated_baseline_model_stimuli_kwarg_bypasses_cache \
  -v
```

Expected: all PASS.

- [ ] **Step 6: Run the full test suite**

```bash
python -m pytest --nomatlab tests/ -v
```

Expected: all pass. Fix any regressions before committing.

- [ ] **Step 7: Commit**

```bash
git add pysaliency/hdf5.py tests/test_hdf5_io.py
git commit -m "feat: register CrossvalidatedBaselineModel in pysaliency.read_hdf5 dispatcher"
```


================================================
FILE: docs/superpowers/plans/2026-03-23-compact-hdf5-export.md
================================================
# Compact HDF5 Export Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add `dtype` and `downscale_factor` parameters to `export_model_to_hdf5` so large prediction files can be stored more compactly, with transparent upsampling/upcasting on load.

**Architecture:** All changes live in one file (`pysaliency/precomputed_models.py`). Four private helper functions handle the new export logic. `HDF5SaliencyMapModel._saliency_map` gains transparent upsampling. `HDF5Model._log_density` gains a two-path normalization check (strict for legacy/float32/float64, configurable+renorm for downsampled/float16).

**Tech Stack:** numpy, h5py, scipy.ndimage (already used in project), pytest with parametrize. No new dependencies.

**Spec:** `docs/superpowers/specs/2026-03-23-hdf5-compact-export-design.md`

---

## File Map

| File | Change |
|------|--------|
| `pysaliency/precomputed_models.py` | Add 4 helpers, update `export_model_to_hdf5`, `HDF5SaliencyMapModel`, `HDF5Model` |
| `tests/test_precomputed_models.py` | Add all new tests (append to existing file) |

---

## Task 1: Export — dtype parameter and root attrs

Add the `dtype` parameter and write versioned root attrs. No downsampling yet.

**Files:**
- Modify: `pysaliency/precomputed_models.py:129-168`
- Test: `tests/test_precomputed_models.py`

- [ ] **Step 1: Write failing tests**

Add to `tests/test_precomputed_models.py`:

```python
import h5py
import warnings


def test_export_root_attrs_written(file_stimuli, tmpdir):
    """New-format files always have type/version/downscale_factor/dtype root attrs."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename)
    with h5py.File(filename, 'r') as f:
        assert f.attrs['type'] == 'pysaliency.precomputed_models.predictions'
        assert f.attrs['version'] == '1.0'
        assert int(f.attrs['downscale_factor']) == 1
        assert f.attrs['dtype'] == 'float64'


def test_export_dtype_float32(file_stimuli, tmpdir):
    """dtype=np.float32 stores float32 datasets."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename, dtype=np.float32)
    with h5py.File(filename, 'r') as f:
        assert f.attrs['dtype'] == 'float32'
        # check one dataset
        keys = list(f.keys())
        assert f[keys[0]].dtype == np.float32


def test_export_dtype_float16(file_stimuli, tmpdir):
    """dtype=np.float16 stores float16 datasets."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename, dtype=np.float16)
    with h5py.File(filename, 'r') as f:
        keys = list(f.keys())
        assert f[keys[0]].dtype == np.float16


def test_export_dtype_none_preserves_native(file_stimuli, tmpdir):
    """dtype=None (default) preserves the model's native output dtype."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename, dtype=None)
    with h5py.File(filename, 'r') as f:
        keys = list(f.keys())
        # GaussianSaliencyMapModel returns float64
        assert f[keys[0]].dtype == np.float64
```

- [ ] **Step 2: Run tests to verify they fail**

```bash
cd /home/matthias/Documents/Uni/Bethge/Saliency/pysaliency
python -m pytest --nomatlab tests/test_precomputed_models.py::test_export_root_attrs_written tests/test_precomputed_models.py::test_export_dtype_float32 tests/test_precomputed_models.py::test_export_dtype_float16 tests/test_precomputed_models.py::test_export_dtype_none_preserves_native -v
```

Expected: FAIL — `export_model_to_hdf5` doesn't write root attrs yet.

- [ ] **Step 3: Add `_effective_dtype` helper and update `export_model_to_hdf5`**

In `pysaliency/precomputed_models.py`, add before `export_model_to_hdf5`:

```python
def _effective_dtype(smap, dtype, downscale_factor):
    """Determine the dtype that will actually be stored in the HDF5 file."""
    if dtype is not None:
        return np.dtype(dtype)
    if downscale_factor > 1:
        # numpy .mean() returns float64 for integer inputs, preserves float types
        if np.issubdtype(smap.dtype, np.integer):
            return np.dtype(np.float64)
        else:
            return smap.dtype  # float32 stays float32, float64 stays float64
    return smap.dtype
```

Replace the `export_model_to_hdf5` signature and body:

```python
def export_model_to_hdf5(model, stimuli, filename, compression=9, overwrite=True, flush=False,
                          dtype=None, downscale_factor=1):
    """Export pysaliency model predictions for stimuli into hdf5 file

    model: Model or SaliencyMapModel
    stimuli: instance of FileStimuli or Stimuli with filenames attribute
    filename: where to save hdf5 file to
    compression: how much to compress the data
    overwrite: if False, an existing file will be appended to (for resuming
      interrupted exports). Stimuli already present in the file are skipped.
    flush: whether the hdf5 file should be flushed after each stimulus
    dtype: numpy dtype for stored predictions (e.g. np.float32, np.float16).
      None (default) preserves the model's native output dtype.
    downscale_factor: integer >= 1. Spatially downsample predictions by this
      factor before storing. 1 = no downsampling (default).
    """
    filenames = get_stimuli_filenames(stimuli)
    names = get_minimal_unique_filenames(filenames)

    import h5py

    mode = 'w' if overwrite else 'a'
    # Record whether the file existed before opening, to decide whether to write root attrs
    file_existed = os.path.isfile(filename)

    with h5py.File(filename, mode=mode) as f:
        # Determine which stimuli to process
        if overwrite:
            indices = list(range(len(stimuli)))
        else:
            # append mode is for resuming an interrupted export of the same job
            if 'type' in f.attrs:
                _validate_append_consistency(f, downscale_factor)
            indices = [i for i in range(len(stimuli)) if names[i] not in f]
            logging.debug(f"Skipping {len(stimuli) - len(indices)} already existing entries")

        if not indices:
            return

        # Compute first smap to determine effective dtype (needed for root attrs)
        first_stimulus = stimuli[indices[0]]
        if isinstance(model, SaliencyMapModel):
            first_smap = model.saliency_map(first_stimulus)
        elif isinstance(model, Model):
            first_smap = model.log_density(first_stimulus)
        else:
            raise TypeError(type(model))

        effective_stored_dtype = _effective_dtype(first_smap, dtype, downscale_factor)
        _check_size_guard(first_smap, effective_stored_dtype, downscale_factor, dtype)

        # Write root attrs for new files only (overwrite=True always creates fresh;
        # overwrite=False writes attrs only if the file is brand new, not for legacy files)
        if overwrite or not file_existed:
            f.attrs['type'] = 'pysaliency.precomputed_models.predictions'
            f.attrs['version'] = '1.0'
            f.attrs['downscale_factor'] = downscale_factor
            f.attrs['dtype'] = str(effective_stored_dtype)

        for i, k in tqdm(list(enumerate(indices))):
            if i == 0:
                smap = first_smap
            else:
                stimulus = stimuli[k]
                if isinstance(model, SaliencyMapModel):
                    smap = model.saliency_map(stimulus)
                elif isinstance(model, Model):
                    smap = model.log_density(stimulus)

            H, W = smap.shape[0], smap.shape[1]
            smap = _downsample_smap(smap, downscale_factor)
            if dtype is not None:
                smap = smap.astype(dtype)

            ds = f.create_dataset(names[k], data=smap, compression=compression)
            if downscale_factor > 1:
                ds.attrs['original_shape'] = np.array([H, W], dtype=np.int64)
            if flush:
                f.flush()
```

Add stub helpers (full implementation in later tasks) before `export_model_to_hdf5`:

```python
def _downsample_smap(smap, k):
    """Downsample a 2D map by integer factor k using area averaging."""
    if k == 1:
        return smap
    H, W = smap.shape
    H_pad = int(np.ceil(H / k)) * k
    W_pad = int(np.ceil(W / k)) * k
    smap = np.pad(np.ascontiguousarray(smap),
                  ((0, H_pad - H), (0, W_pad - W)),
                  mode='edge')
    return smap.reshape(H_pad // k, k, W_pad // k, k).mean(axis=(1, 3))


def _check_size_guard(smap, effective_stored_dtype, downscale_factor, dtype):
    """Warn if compact settings will produce a larger-per-element file than the native dtype."""
    native_dtype = np.dtype(smap.dtype)
    if np.dtype(effective_stored_dtype).itemsize > native_dtype.itemsize:
        msg = (
            f"Model produces {native_dtype} predictions "
            f"({native_dtype.itemsize} byte/element) but will be stored as "
            f"{effective_stored_dtype} ({np.dtype(effective_stored_dtype).itemsize} byte/element). "
            f"Compact export may result in a larger file than the original."
        )
        if np.issubdtype(native_dtype, np.integer):
            msg += " Consider passing dtype=np.uint8 to preserve the original dtype."
        if np.issubdtype(native_dtype, np.integer) and downscale_factor > 1 and dtype is None:
            msg += " Note: casting area-averaging float results back to integer is lossy."
        warnings.warn(msg)


def _validate_append_consistency(f, downscale_factor):
    """Check that an existing compact HDF5 file is compatible with the current export settings."""
    existing = int(f.attrs['downscale_factor'])
    if existing != downscale_factor:
        raise ValueError(
            f"Cannot append to HDF5 file: existing downscale_factor={existing} "
            f"does not match requested downscale_factor={downscale_factor}."
        )
```

- [ ] **Step 4: Run tests to verify they pass**

```bash
python -m pytest --nomatlab tests/test_precomputed_models.py::test_export_root_attrs_written tests/test_precomputed_models.py::test_export_dtype_float32 tests/test_precomputed_models.py::test_export_dtype_float16 tests/test_precomputed_models.py::test_export_dtype_none_preserves_native -v
```

Expected: PASS

- [ ] **Step 5: Verify existing tests still pass**

```bash
python -m pytest --nomatlab tests/test_precomputed_models.py -v
```

Expected: all previously passing tests still pass.

- [ ] **Step 6: Commit**

```bash
git add pysaliency/precomputed_models.py tests/test_precomputed_models.py
git commit -m "feat: add dtype parameter and versioned root attrs to export_model_to_hdf5"
```

---

## Task 2: Export — downsampling parameter

Add and test the `downscale_factor` parameter behaviour (stored shape, `original_shape` attr, non-divisible sizes).

**Files:**
- Modify: `tests/test_precomputed_models.py`

(All implementation code was already added in Task 1 — `_downsample_smap` and the `ds.attrs['original_shape']` write are in place. This task adds the tests.)

- [ ] **Step 1: Write failing tests**

```python
def test_export_downscale_stored_shape(file_stimuli, tmpdir):
    """Stored shape is ceil(H/k) x ceil(W/k) for divisible dimensions."""
    # file_stimuli uses 100x100 images, which is divisible by 2 and 4
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename, downscale_factor=2)
    with h5py.File(filename, 'r') as f:
        keys = list(f.keys())
        assert f[keys[0]].shape == (50, 50)


def test_export_downscale_original_shape_attr(file_stimuli, tmpdir):
    """original_shape attr is present and correct when downscale_factor > 1."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename, downscale_factor=2)
    with h5py.File(filename, 'r') as f:
        keys = list(f.keys())
        ds = f[keys[0]]
        assert 'original_shape' in ds.attrs
        np.testing.assert_array_equal(ds.attrs['original_shape'], [100, 100])
        assert ds.attrs['original_shape'].dtype == np.int64


def test_export_no_downscale_no_original_shape_attr(file_stimuli, tmpdir):
    """original_shape attr is absent when downscale_factor == 1."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename)
    with h5py.File(filename, 'r') as f:
        keys = list(f.keys())
        assert 'original_shape' not in f[keys[0]].attrs


@pytest.fixture
def file_stimuli_nondivisible(tmpdir):
    """Stimuli with 101x97 images — not divisible by 2 or 4."""
    filenames = []
    for i in range(3):
        filename = tmpdir.join(f'stim_{i:04d}.png')
        imsave(str(filename), np.random.randint(0, 255, (101, 97, 3), dtype=np.uint8))
        filenames.append(str(filename))
    return pysaliency.FileStimuli(filenames=filenames)


def test_export_downscale_nondivisible_stored_shape(file_stimuli_nondivisible, tmpdir):
    """Non-divisible shapes are padded: stored shape is ceil(H/k) x ceil(W/k)."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli_nondivisible, filename, downscale_factor=2)
    with h5py.File(filename, 'r') as f:
        keys = list(f.keys())
        # ceil(101/2)=51, ceil(97/2)=49
        assert f[keys[0]].shape == (51, 49)


def test_export_downscale_nondivisible_original_shape_attr(file_stimuli_nondivisible, tmpdir):
    """original_shape stores the pre-padding shape, not the padded or stored shape."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli_nondivisible, filename, downscale_factor=2)
    with h5py.File(filename, 'r') as f:
        keys = list(f.keys())
        np.testing.assert_array_equal(f[keys[0]].attrs['original_shape'], [101, 97])


def test_export_float32_downscale_dtype_is_float32(file_stimuli, tmpdir):
    """float32 model output + downscale_factor > 1 stores float32, not float64."""
    # GaussianSaliencyMapModel returns float64, so we need a float32-producing model
    class Float32SaliencyMapModel(pysaliency.SaliencyMapModel):
        def _saliency_map(self, stimulus):
            return np.ones((stimulus.shape[0], stimulus.shape[1]), dtype=np.float32)

    model = Float32SaliencyMapModel()
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename, downscale_factor=2)
    with h5py.File(filename, 'r') as f:
        keys = list(f.keys())
        assert f[keys[0]].dtype == np.float32
        assert f.attrs['dtype'] == 'float32'
```

- [ ] **Step 2: Run tests to verify they fail**

```bash
python -m pytest --nomatlab tests/test_precomputed_models.py::test_export_downscale_stored_shape tests/test_precomputed_models.py::test_export_downscale_original_shape_attr tests/test_precomputed_models.py::test_export_no_downscale_no_original_shape_attr tests/test_precomputed_models.py::test_export_downscale_nondivisible_stored_shape tests/test_precomputed_models.py::test_export_downscale_nondivisible_original_shape_attr tests/test_precomputed_models.py::test_export_float32_downscale_dtype_is_float32 -v
```

Expected: FAIL (downscale_factor parameter not wired up yet — actually already implemented in Task 1. These may already pass.)

- [ ] **Step 3: Verify all pass and run full suite**

```bash
python -m pytest --nomatlab tests/test_precomputed_models.py -v
```

Expected: all pass.

- [ ] **Step 4: Commit**

```bash
git add tests/test_precomputed_models.py
git commit -m "test: add downsampling export tests"
```

---

## Task 3: Export — append mode and size guard

Test append consistency validation and the uint8/size warning.

**Files:**
- Test: `tests/test_precomputed_models.py`

- [ ] **Step 1: Write failing tests**

```python
# NOTE: The spec test table lists "Append mode — mismatch (dtype only)" but the spec design
# section intentionally excludes dtype from the consistency check (it is purely informational
# and cannot be determined before the first stimulus is computed). There is therefore no
# ValueError raised on dtype mismatch — this is correct behavior, not a gap.


def test_export_append_consistency_mismatch_downscale(file_stimuli, tmpdir):
    """Appending with a different downscale_factor raises ValueError."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    partial = pysaliency.FileStimuli(filenames=file_stimuli.filenames[:3])
    export_model_to_hdf5(model, partial, filename, downscale_factor=2)
    with pytest.raises(ValueError, match='downscale_factor'):
        export_model_to_hdf5(model, file_stimuli, filename, overwrite=False, downscale_factor=1)


def test_export_append_new_file_root_attrs(file_stimuli, tmpdir):
    """overwrite=False on a new file still writes root attrs."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename, overwrite=False)
    with h5py.File(filename, 'r') as f:
        assert 'type' in f.attrs
        assert f.attrs['version'] == '1.0'


def test_export_append_legacy_file_no_root_attrs(file_stimuli, tmpdir):
    """Appending to a legacy file (no root attrs) succeeds without adding root attrs."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    partial = pysaliency.FileStimuli(filenames=file_stimuli.filenames[:3])
    remaining = pysaliency.FileStimuli(filenames=file_stimuli.filenames[3:])

    # Write a legacy-format file manually (no root attrs)
    import h5py
    names = pysaliency.utils.get_minimal_unique_filenames(partial.filenames)
    with h5py.File(filename, 'w') as f:
        for k, s in enumerate(partial):
            f.create_dataset(names[k], data=model.saliency_map(s))

    export_model_to_hdf5(model, remaining, filename, overwrite=False)

    with h5py.File(filename, 'r') as f:
        assert 'type' not in f.attrs  # no root attrs written into legacy file


def test_export_uint8_model_downscale_warns(tmpdir):
    """Uint8 model output + downscale_factor > 1 emits a UserWarning (stored as float64 > uint8)."""
    class Uint8SaliencyMapModel(pysaliency.SaliencyMapModel):
        def _saliency_map(self, stimulus):
            return np.ones((stimulus.shape[0], stimulus.shape[1]), dtype=np.uint8)

    filenames = []
    for i in range(2):
        fname = str(tmpdir.join(f'stim_{i}.png'))
        imsave(fname, np.random.randint(0, 255, (20, 20, 3), dtype=np.uint8))
        filenames.append(fname)
    stimuli = pysaliency.FileStimuli(filenames=filenames)

    model = Uint8SaliencyMapModel()
    filename = str(tmpdir.join('model.hdf5'))
    with pytest.warns(UserWarning, match='larger'):
        export_model_to_hdf5(model, stimuli, filename, downscale_factor=2)


def test_export_uint8_model_downscale_stored_as_float64(tmpdir):
    """Uint8 model output + downscale_factor > 1 is stored as float64 (area avg result)."""
    class Uint8SaliencyMapModel(pysaliency.SaliencyMapModel):
        def _saliency_map(self, stimulus):
            return np.ones((stimulus.shape[0], stimulus.shape[1]), dtype=np.uint8)

    filenames = []
    for i in range(2):
        fname = str(tmpdir.join(f'stim_{i}.png'))
        imsave(fname, np.random.randint(0, 255, (20, 20, 3), dtype=np.uint8))
        filenames.append(fname)
    stimuli = pysaliency.FileStimuli(filenames=filenames)

    model = Uint8SaliencyMapModel()
    filename = str(tmpdir.join('model.hdf5'))
    with warnings.catch_warnings():
        warnings.simplefilter('ignore')
        export_model_to_hdf5(model, stimuli, filename, downscale_factor=2)
    with h5py.File(filename, 'r') as f:
        keys = list(f.keys())
        assert f[keys[0]].dtype == np.float64


def test_export_uint8_model_float32_dtype_warns(tmpdir):
    """Uint8 model + dtype=np.float32 warns (float32 > uint8)."""
    class Uint8SaliencyMapModel(pysaliency.SaliencyMapModel):
        def _saliency_map(self, stimulus):
            return np.ones((stimulus.shape[0], stimulus.shape[1]), dtype=np.uint8)

    filenames = [str(tmpdir.join(f'stim_{i}.png')) for i in range(2)]
    for f in filenames:
        imsave(f, np.random.randint(0, 255, (20, 20, 3), dtype=np.uint8))
    stimuli = pysaliency.FileStimuli(filenames=filenames)

    model = Uint8SaliencyMapModel()
    filename = str(tmpdir.join('model.hdf5'))
    with pytest.warns(UserWarning, match='larger'):
        export_model_to_hdf5(model, stimuli, filename, dtype=np.float32)


def test_export_uint8_model_uint8_dtype_no_warn(tmpdir):
    """Uint8 model + dtype=np.uint8 does not warn."""
    class Uint8SaliencyMapModel(pysaliency.SaliencyMapModel):
        def _saliency_map(self, stimulus):
            return np.ones((stimulus.shape[0], stimulus.shape[1]), dtype=np.uint8)

    filenames = [str(tmpdir.join(f'stim_{i}.png')) for i in range(2)]
    for f in filenames:
        imsave(f, np.random.randint(0, 255, (20, 20, 3), dtype=np.uint8))
    stimuli = pysaliency.FileStimuli(filenames=filenames)

    model = Uint8SaliencyMapModel()
    filename = str(tmpdir.join('model.hdf5'))
    with warnings.catch_warnings():
        warnings.simplefilter('error')  # any warning becomes an error
        export_model_to_hdf5(model, stimuli, filename, dtype=np.uint8)
```

- [ ] **Step 2: Run tests**

```bash
python -m pytest --nomatlab tests/test_precomputed_models.py::test_export_append_consistency_mismatch_downscale tests/test_precomputed_models.py::test_export_append_new_file_root_attrs tests/test_precomputed_models.py::test_export_append_legacy_file_no_root_attrs tests/test_precomputed_models.py::test_export_uint8_model_downscale_warns tests/test_precomputed_models.py::test_export_uint8_model_downscale_stored_as_float64 tests/test_precomputed_models.py::test_export_uint8_model_float32_dtype_warns tests/test_precomputed_models.py::test_export_uint8_model_uint8_dtype_no_warn -v
```

Note: all implementation for these tests is already in place from Task 1. Most will pass immediately; the legacy-file test (`test_export_append_legacy_file_no_root_attrs`) verifies the `file_existed` flag logic. If any fail, fix in `precomputed_models.py`.

- [ ] **Step 3: Run full suite.**

```bash
python -m pytest --nomatlab tests/test_precomputed_models.py -v
```

If any new tests fail, debug and fix in `precomputed_models.py`.

- [ ] **Step 4: Commit**

```bash
git add tests/test_precomputed_models.py pysaliency/precomputed_models.py
git commit -m "test: add append-mode consistency and size-guard tests"
```

---

## Task 4: Load side — `HDF5SaliencyMapModel` upsampling

Add `_key_for_stimulus` helper and transparent upsampling in `_saliency_map`.

**Files:**
- Modify: `pysaliency/precomputed_models.py:303-334`
- Test: `tests/test_precomputed_models.py`

- [ ] **Step 1: Write failing tests**

```python
@pytest.mark.parametrize('dtype,downscale_factor', [
    (None, 1),
    (np.float32, 1),
    (np.float16, 1),
    (np.float32, 2),
    (np.float16, 4),
])
def test_hdf5_saliency_map_model_roundtrip(file_stimuli, tmpdir, dtype, downscale_factor):
    """HDF5SaliencyMapModel returns correct shape and values after compact export."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename,
                          dtype=dtype, downscale_factor=downscale_factor)

    loaded = pysaliency.HDF5SaliencyMapModel(file_stimuli, filename)
    for s in file_stimuli:
        result = loaded.saliency_map(s)
        assert result.shape == (s.shape[0], s.shape[1]), \
            f"Shape mismatch: {result.shape} != {(s.shape[0], s.shape[1])}"


def test_hdf5_saliency_map_model_dtype_reduced_returns_native(file_stimuli, tmpdir):
    """dtype-reduced-only files return the stored native dtype (not upcast)."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename, dtype=np.float32)

    loaded = pysaliency.HDF5SaliencyMapModel(file_stimuli, filename)
    result = loaded.saliency_map(file_stimuli[0])
    assert result.dtype == np.float32


def test_hdf5_saliency_map_model_downsampled_returns_float64(file_stimuli, tmpdir):
    """Downsampled files upsample and return float64."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli, filename, downscale_factor=2)

    loaded = pysaliency.HDF5SaliencyMapModel(file_stimuli, filename)
    result = loaded.saliency_map(file_stimuli[0])
    assert result.dtype == np.float64


def test_hdf5_saliency_map_model_nondivisible_loaded_shape(file_stimuli_nondivisible, tmpdir):
    """Upsampled output shape matches original_shape (not padded shape)."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, file_stimuli_nondivisible, filename, downscale_factor=2)

    loaded = pysaliency.HDF5SaliencyMapModel(file_stimuli_nondivisible, filename)
    result = loaded.saliency_map(file_stimuli_nondivisible[0])
    assert result.shape == (101, 97)  # original shape, not (51, 49) or (102, 98)


def test_hdf5_saliency_map_model_resized_stimuli(tmpdir):
    """With check_shape=False and resized stimuli, upsampling targets original_shape."""
    # Export at full size, then load with resized (smaller) stimuli
    from imageio import imsave as _imsave
    filenames = []
    for i in range(2):
        fname = str(tmpdir.join(f'stim_{i}.png'))
        _imsave(fname, np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8))
        filenames.append(fname)
    full_stimuli = pysaliency.FileStimuli(filenames=filenames)

    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(model, full_stimuli, filename, downscale_factor=2)

    # Load with the same filenames but we'll check that the loaded shape is 100x100
    # (original_shape), not 50x50 (stored) nor whatever the stimulus reports
    loaded = pysaliency.HDF5SaliencyMapModel(full_stimuli, filename, check_shape=False)
    result = loaded.saliency_map(full_stimuli[0])
    assert result.shape == (100, 100)


def test_hdf5_saliency_map_model_legacy_file_unchanged(file_stimuli, tmpdir):
    """Legacy files (no root attrs) still work exactly as before."""
    model = pysaliency.GaussianSaliencyMapModel(width=0.1)
    filename = str(tmpdir.join('model.hdf5'))
    # Write a legacy file manually
    names = pysaliency.utils.get_minimal_unique_filenames(file_stimuli.filenames)
    with h5py.File(filename, 'w') as f:
        for k, s in enumerate(file_stimuli):
            f.create_dataset(names[k], data=model.saliency_map(s))

    loaded = pysaliency.HDF5SaliencyMapModel(file_stimuli, filename)
    for s in file_stimuli:
        expected = model.saliency_map(s)
        np.testing.assert_array_equal(loaded.saliency_map(s), expected)
```

- [ ] **Step 2: Run tests to verify they fail**

```bash
python -m pytest --nomatlab -k "roundtrip or downsampled_returns_float64 or nondivisible_loaded_shape" tests/test_precomputed_models.py -v
```

Expected: FAIL — `_saliency_map` doesn't upsample yet.

- [ ] **Step 3: Update `HDF5SaliencyMapModel` in `pysaliency/precomputed_models.py`**

Replace lines 310–334:

```python
class HDF5SaliencyMapModel(SaliencyMapModel):
    """ exposes a HDF5 file with saliency maps as pysaliency model

        The stimuli have to be of type `FileStimuli`. For each
        stimulus file, the model expects a dataset with the same
        name in the dataset.
        If the file was created with downscale_factor > 1, predictions are
        transparently upsampled to their original resolution on load.
    """
    def __init__(self, stimuli, filename, check_shape=True, **kwargs):
        super(HDF5SaliencyMapModel, self).__init__(**kwargs)

        self.stimuli = stimuli
        self.filename = filename
        self.check_shape = check_shape

        if not os.path.isfile(self.filename):
            raise ValueError(f'File {self.filename} does not exist')

        import h5py
        self.hdf5_file = h5py.File(self.filename, 'r')
        self.version = self.hdf5_file.attrs.get('version')
        self.all_keys = get_keys_recursive(self.hdf5_file)

        self.names = get_keys_from_filenames_with_prefix(get_stimuli_filenames(stimuli), self.all_keys)

    def _key_for_stimulus(self, stimulus):
        stimulus_id = get_image_hash(stimulus)
        stimulus_index = self.stimuli.stimulus_ids.index(stimulus_id)
        return self.names[stimulus_index]

    def _saliency_map(self, stimulus):
        stimulus_key = self._key_for_stimulus(stimulus)
        dataset = self.hdf5_file[stimulus_key]
        smap = dataset[:]

        if 'original_shape' in dataset.attrs:
            # Compact downsampled file: upsample to original resolution
            target_shape = tuple(dataset.attrs['original_shape'])
            zoom_factors = (target_shape[0] / smap.shape[0], target_shape[1] / smap.shape[1])
            import scipy.ndimage
            smap = scipy.ndimage.zoom(smap.astype(np.float64), zoom_factors, order=1, mode='nearest')

        if not smap.shape == (stimulus.shape[0], stimulus.shape[1]):
            if self.check_shape:
                warnings.warn('Wrong shape for stimulus {}'.format(stimulus_key), stacklevel=4)
        return smap
```

- [ ] **Step 4: Run tests to verify they pass**

```bash
python -m pytest --nomatlab tests/test_precomputed_models.py -v
```

Expected: all pass.

- [ ] **Step 5: Commit**

```bash
git add pysaliency/precomputed_models.py tests/test_precomputed_models.py
git commit -m "feat: add transparent upsampling to HDF5SaliencyMapModel"
```

---

## Task 5: Load side — `HDF5Model` two-path normalization

Add `max_normalization_error` parameter and the two-path `_log_density`.

**Files:**
- Modify: `pysaliency/precomputed_models.py:337-355`
- Test: `tests/test_precomputed_models.py`

- [ ] **Step 1: Write failing tests**

```python
@pytest.mark.parametrize('dtype,downscale_factor', [
    (None, 1),
    (np.float32, 1),
    (np.float16, 1),
    (np.float32, 2),
    (np.float16, 4),
])
def test_hdf5_model_roundtrip(file_stimuli, tmpdir, dtype, downscale_factor):
    """HDF5Model returns valid log densities after compact export."""
    import warnings
    base = pysaliency.models.SaliencyMapNormalizingModel(
        pysaliency.GaussianSaliencyMapModel(width=0.1))
    filename = str(tmpdir.join('model.hdf5'))
    with warnings.catch_warnings():
        warnings.simplefilter('ignore')
        export_model_to_hdf5(base, file_stimuli, filename,
                              dtype=dtype, downscale_factor=downscale_factor)

    loaded = pysaliency.HDF5Model(file_stimuli, filename)
    from scipy.special import logsumexp
    for s in file_stimuli:
        result = loaded.log_density(s)
        assert result.dtype == np.float64
        assert result.shape == (s.shape[0], s.shape[1])
        assert abs(logsumexp(result)) < 0.001, f"logsumexp={logsumexp(result):.6f}, not close to 0"


def test_hdf5_model_strict_path_no_renorm(file_stimuli, tmpdir):
    """Non-compact float64/float32 files use strict path: output not renormalized."""
    from scipy.special import logsumexp
    base = pysaliency.models.SaliencyMapNormalizingModel(
        pysaliency.GaussianSaliencyMapModel(width=0.1))
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(base, file_stimuli, filename)

    loaded = pysaliency.HDF5Model(file_stimuli, filename)
    for s in file_stimuli:
        result = loaded.log_density(s)
        # Result should be bit-identical to stored (cast to float64), not renormalized
        expected = base.log_density(s)
        np.testing.assert_array_equal(result, expected)


def test_hdf5_model_strict_path_raises_on_bad_density(file_stimuli, tmpdir):
    """Non-compact file with bad log density raises ValueError (strict ±0.01 check)."""
    filename = str(tmpdir.join('model.hdf5'))
    names = pysaliency.utils.get_minimal_unique_filenames(file_stimuli.filenames)
    with h5py.File(filename, 'w') as f:
        for k, s in enumerate(file_stimuli):
            # deliberately unnormalized: constant map, logsumexp >> 0
            bad_map = np.zeros((s.shape[0], s.shape[1]), dtype=np.float64)
            f.create_dataset(names[k], data=bad_map)

    loaded = pysaliency.HDF5Model(file_stimuli, filename)
    with pytest.raises(ValueError, match='correct log density'):
        loaded.log_density(file_stimuli[0])


def test_hdf5_model_relaxed_path_threshold_exceeded_raises(file_stimuli, tmpdir):
    """Corrupted downsampled file raises ValueError when logsumexp > max_normalization_error."""
    filename = str(tmpdir.join('model.hdf5'))
    names = pysaliency.utils.get_minimal_unique_filenames(file_stimuli.filenames)
    with h5py.File(filename, 'w') as f:
        f.attrs['type'] = 'pysaliency.precomputed_models.predictions'
        f.attrs['version'] = '1.0'
        f.attrs['downscale_factor'] = 2
        f.attrs['dtype'] = 'float64'
        for k, s in enumerate(file_stimuli):
            stored_shape = (s.shape[0] // 2, s.shape[1] // 2)
            bad_map = np.zeros(stored_shape, dtype=np.float64)  # heavily unnormalized
            ds = f.create_dataset(names[k], data=bad_map)
            ds.attrs['original_shape'] = np.array([s.shape[0], s.shape[1]], dtype=np.int64)

    loaded = pysaliency.HDF5Model(file_stimuli, filename)
    with pytest.raises(ValueError, match='normalization error'):
        loaded.log_density(file_stimuli[0])


def test_hdf5_model_custom_max_normalization_error(file_stimuli, tmpdir):
    """Custom max_normalization_error: tighter raises, looser allows."""
    base = pysaliency.models.SaliencyMapNormalizingModel(
        pysaliency.GaussianSaliencyMapModel(width=0.1))
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(base, file_stimuli, filename, downscale_factor=2)

    # Very tight threshold should raise (there is some error after round-trip)
    loaded_tight = pysaliency.HDF5Model(file_stimuli, filename, max_normalization_error=1e-10)
    with pytest.raises(ValueError):
        loaded_tight.log_density(file_stimuli[0])

    # Default threshold should pass
    loaded_default = pysaliency.HDF5Model(file_stimuli, filename)
    loaded_default.log_density(file_stimuli[0])  # should not raise


def test_hdf5_model_threshold_within_bounds_for_float16_4x(file_stimuli, tmpdir):
    """Explicit check: logsumexp before renorm is within log(1.1) for float16+4x export."""
    from scipy.special import logsumexp as _logsumexp
    base = pysaliency.models.SaliencyMapNormalizingModel(
        pysaliency.GaussianSaliencyMapModel(width=0.1))
    filename = str(tmpdir.join('model.hdf5'))
    with warnings.catch_warnings():
        warnings.simplefilter('ignore')
        export_model_to_hdf5(base, file_stimuli, filename, dtype=np.float16, downscale_factor=4)

    # Manually load the raw stored data and upsample to measure pre-renorm logsumexp
    names = pysaliency.utils.get_minimal_unique_filenames(file_stimuli.filenames)
    import scipy.ndimage
    with h5py.File(filename, 'r') as f:
        for k, s in enumerate(file_stimuli):
            ds = f[names[k]]
            smap = ds[:].astype(np.float64)
            target_shape = tuple(ds.attrs['original_shape'])
            zoom_factors = (target_shape[0] / smap.shape[0], target_shape[1] / smap.shape[1])
            smap = scipy.ndimage.zoom(smap, zoom_factors, order=1, mode='nearest')
            err = abs(_logsumexp(smap))
            assert err < np.log(1.1), f"logsumexp error {err:.4f} exceeds log(1.1)={np.log(1.1):.4f}"


def test_hdf5_model_max_normalization_error_none(file_stimuli, tmpdir):
    """max_normalization_error=None skips the guard; renorm is still applied."""
    from scipy.special import logsumexp
    base = pysaliency.models.SaliencyMapNormalizingModel(
        pysaliency.GaussianSaliencyMapModel(width=0.1))
    filename = str(tmpdir.join('model.hdf5'))
    export_model_to_hdf5(base, file_stimuli, filename, downscale_factor=2)

    loaded = pysaliency.HDF5Model(file_stimuli, filename, max_normalization_error=None)
    for s in file_stimuli:
        result = loaded.log_density(s)
        assert abs(logsumexp(result)) < 0.001  # renorm still runs
```

- [ ] **Step 2: Run tests to verify they fail**

```bash
python -m pytest --nomatlab -k "hdf5_model_roundtrip or strict_path_no_renorm or threshold_exceeded_raises" tests/test_precomputed_models.py -v
```

Expected: FAIL — `HDF5Model` doesn't have the new logic yet.

- [ ] **Step 3: Update `HDF5Model` in `pysaliency/precomputed_models.py`**

Replace lines 337–355:

```python
class HDF5Model(Model):
    """ exposes a HDF5 file with log densities as pysaliency model.

        For more detail see HDF5SaliencyMapModel.
        Files created with downscale_factor > 1 or dtype=np.float16 use a
        relaxed normalization check and automatic renormalization on load.
        All other files use the original strict ±0.01 check.
    """
    def __init__(self, stimuli, filename, check_shape=True,
                 max_normalization_error=np.log(1.1), **kwargs):
        super(HDF5Model, self).__init__(**kwargs)
        self.parent_model = HDF5SaliencyMapModel(
            stimuli=stimuli,
            filename=filename,
            caching=False,
            check_shape=check_shape
        )
        self.max_normalization_error = max_normalization_error

    def _log_density(self, stimulus):
        key = self.parent_model._key_for_stimulus(stimulus)
        dataset = self.parent_model.hdf5_file[key]
        use_relaxed_path = (
            'original_shape' in dataset.attrs
            or dataset.dtype.itemsize < np.dtype(np.float32).itemsize
        )

        smap = self.parent_model.saliency_map(stimulus).astype(np.float64)

        if use_relaxed_path:
            if self.max_normalization_error is not None:
                err = abs(logsumexp(smap))
                if err >= self.max_normalization_error:
                    raise ValueError(
                        f'Log density normalization error {err:.4f} exceeds '
                        f'threshold {self.max_normalization_error:.4f}'
                    )
            smap = smap - logsumexp(smap)
        else:
            if not -0.01 <= logsumexp(smap) <= 0.01:
                raise ValueError('Not a correct log density!')

        return smap
```

- [ ] **Step 4: Run all tests**

```bash
python -m pytest --nomatlab tests/test_precomputed_models.py -v
```

Expected: all pass.

- [ ] **Step 5: Commit**

```bash
git add pysaliency/precomputed_models.py tests/test_precomputed_models.py
git commit -m "feat: add two-path normalization and max_normalization_error to HDF5Model"
```

---

## Task 6: Final verification

Run the full test suite, check no regressions.

- [ ] **Step 1: Run full test suite**

```bash
python -m pytest --nomatlab --notheano --nodownload tests/ -v
```

Expected: all pass, no regressions.

- [ ] **Step 2: Spot-check the feature end-to-end**

```python
# Quick smoke test in Python (not a formal test, just sanity check)
import numpy as np
import pysaliency
from pysaliency import export_model_to_hdf5

# Use any FileStimuli you have, or create a small one
# model = pysaliency.GaussianSaliencyMapModel(width=0.1)
# export_model_to_hdf5(model, stimuli, '/tmp/compact.hdf5', dtype=np.float32, downscale_factor=2)
# loaded = pysaliency.HDF5SaliencyMapModel(stimuli, '/tmp/compact.hdf5')
# print(loaded.saliency_map(stimuli[0]).shape)  # should match stimulus shape
```

- [ ] **Step 3: Final commit (if any fixups needed)**

```bash
git add -p
git commit -m "fix: <description of any fixup>"
```


================================================
FILE: docs/superpowers/specs/2026-03-21-model-hdf5-save-reload-design.md
================================================
# Design: HDF5 Save/Reload for Saliency Models

**Date:** 2026-03-21
**Branch:** enh-save-baseline-model-to-hdf5
**Status:** Approved

## Background

`pysaliency.export_model_to_hdf5` serializes model *predictions* (saliency maps) for every stimulus into an HDF5 file. For large stimulus sets this produces huge files. For models that are fast to compute (e.g. kernel density baseline models), it is preferable to serialize the model *parameters* instead, so the model can be reconstructed and run on demand.

The datasets module already has a mature `to_hdf5` / `read_hdf5` pattern per class, with a unified `pysaliency.datasets.read_hdf5` dispatcher. This design extends that pattern to models.

## Scope

- `BaselineModel`: already implemented in the current branch (no changes needed)
- `CrossvalidatedBaselineModel`: new, covered by this spec
- `pysaliency.read_hdf5`: unified dispatcher (already partially implemented); document `**kwargs` passthrough contract

Out of scope: other model types (`GoldModel`, `KDEGoldModel`, etc.) — follow the same pattern incrementally.

## Key Constraints

- **Stimulus IDs are not stable across systems.** SHA1 hashes of pixel data differ between libjpeg versions. Saving only `stimulus_ids` (Option B) was considered and rejected: it silently breaks when a system update changes JPEG decoding. Full stimulus data must be embedded, or the caller must supply stimuli explicitly at load time.
- **Stimuli can be large.** Embedding raw pixel arrays for large datasets is expensive. A flag controls whether stimuli are embedded, analogous to `BaselineModel`'s `include_shape_cache`.

## Design

### `BaselineModel` (existing — no changes)

Saves to HDF5:
- `attrs['type'] = 'pysaliency.baseline_utils.BaselineModel'`
- `attrs['version'] = '1.0'`
- `attrs['bandwidth']`, `attrs['eps']`, `attrs['keep_aspect']`
- dataset `xs`, dataset `ys` — normalized fixation coordinates
- optional group `shape_cache` — precomputed log-density maps per image shape (controlled by `include_shape_cache=True`, default `True`)

No reference to stimuli or fixations is stored; the model is self-contained.

### `CrossvalidatedBaselineModel`

#### Internal refactor: store `fixations_n` directly

As part of this work, `CrossvalidatedBaselineModel.__init__` is refactored to store only the fixation index array rather than the full `fixations` object:

```python
# Before
self.fixations = fixations
self.shape_cache = {}   # unused — remove

# After
self.fixations_n = fixations.n.copy()
```

`_log_density` is updated accordingly: `self.fixations.n` → `self.fixations_n`. This makes the model's stored state minimal and eliminates any need for workarounds during deserialization.

#### `to_hdf5(target, include_stimuli=True)`

Uses the existing `@hdf5_wrapper(mode='w')` decorator for transparent file/group dispatch.

Saves:
- `attrs['type'] = 'pysaliency.baseline_utils.CrossvalidatedBaselineModel'`
- `attrs['version'] = '1.0'`
- `attrs['bandwidth']`, `attrs['eps']` — note: no `keep_aspect`, no `shape_cache`
- dataset `xs`, dataset `ys` — normalized fixation coordinates
- dataset `fixations_n` — `self.fixations_n` (integer array mapping each fixation to its stimulus index)
- group `stimuli` — embedded via `self.stimuli.to_hdf5(target.create_group('stimuli'))` — **only when `include_stimuli=True`**

When `include_stimuli=False`, the `stimuli` group is omitted entirely; the file is not self-contained and requires `stimuli=` at load time.

#### `read_hdf5(source, *, stimuli=None, caching=True, memory_cache_size=None, cache_location=None)`

Classmethod. Decorator stacking must match `BaselineModel` exactly: `@classmethod` outermost, `@hdf5_wrapper(mode='r')` inner.

Load order:
1. Validate `type` and `version` attrs. Raise `ValueError` if `type` is not `'pysaliency.baseline_utils.CrossvalidatedBaselineModel'` or `version` is not `'1.0'`.
2. Resolve stimuli:
   - If `stimuli` kwarg is provided → use it (ignore embedded group even if present)
   - Else if `'stimuli'` group exists in file → load via `pysaliency.hdf5.read_hdf5(source['stimuli'])`
   - Else → raise `ValueError("No stimuli found in HDF5 file. Pass stimuli= explicitly.")`
3. Reconstruct model via `cls.__new__(cls)` + `Model.__init__(...)` (same pattern as `BaselineModel`)
4. Restore fields:
   - `model.bandwidth = source.attrs['bandwidth']`
   - `model.eps = source.attrs['eps']`
   - `model.xs = source['xs'][...]`
   - `model.ys = source['ys'][...]`
   - `model.fixations_n = source['fixations_n'][...]`
   - `model.stimuli = stimuli` (resolved above)

#### Registration

`pysaliency/hdf5.py` `_MODEL_READERS`:

```python
_MODEL_READERS = {
    'pysaliency.baseline_utils.BaselineModel': _read_baseline_model,
    'pysaliency.baseline_utils.CrossvalidatedBaselineModel': _read_crossvalidated_baseline_model,
}
```

where `_read_crossvalidated_baseline_model` is a thin wrapper that delegates to `CrossvalidatedBaselineModel.read_hdf5`.

### `pysaliency.read_hdf5` dispatcher

The dispatcher already accepts and passes through `**kwargs` to the class-specific reader. This is the correct contract — `read_hdf5` remains a thin dispatcher with no knowledge of model-specific parameters.

Usage:
```python
# Self-contained file — no kwargs needed
model = pysaliency.read_hdf5('model.hdf5')

# File saved without stimuli — pass them through **kwargs
model = pysaliency.read_hdf5('model.hdf5', stimuli=my_stimuli)

# Canonical class-level API (always available)
model = CrossvalidatedBaselineModel.read_hdf5('model.hdf5', stimuli=my_stimuli)
```

### `pysaliency.__init__` exports

`read_hdf5` is added to the top-level `pysaliency` namespace (already in progress on this branch).

## Implementation Notes

- **`CrossvalidatedBaselineModel` refactor** is an internal change. The public constructor signature `__init__(self, stimuli, fixations, bandwidth, eps)` is unchanged; only the stored attributes change (`self.fixations_n` replaces `self.fixations`, `self.shape_cache` is removed). This is not a breaking change.
- **`hdf5_wrapper` + `@classmethod` ordering:** `@classmethod` must be the outermost decorator and `@hdf5_wrapper(mode='r')` the inner one — exactly as in `BaselineModel.read_hdf5`. Reversing the order will fail.
- **WeakValueDictionary cache in `pysaliency.read_hdf5`:** The top-level `_read_hdf5_from_file` is cached via `boltons.cacheutils.cached`. Since `boltons` does not include `**kwargs` in the cache key, passing `stimuli=` via `pysaliency.read_hdf5('model.hdf5', stimuli=X)` on a second call would return the cached model from the first call. The existing fallback path (lines 43–45 of `hdf5.py`) bypasses the cache when `kwargs` are not hashable — but a numpy array for `stimuli` is not hashable, so the fallback triggers correctly. This is worth an explicit test to guard against regressions.
- **Stimuli hash stability:** Hash consistency requires that the embedded stimuli and the stimuli passed to `_log_density` are decoded via the same code path. If both are `FileStimuli` pointing to the same JPEG files, hashes will match even across libjpeg updates (both sides re-decode with the same updated library). A mismatch only arises when the two sides use different decoding paths — e.g., stimuli embedded as raw numpy arrays in HDF5 but evaluated with freshly JPEG-decoded `FileStimuli`, or vice versa. In such cases `stimuli=` must be passed explicitly on reload.
- **Unknown stimulus at inference time:** If a stimulus is passed to `_log_density` that was not in the training set, `self.stimuli.stimulus_ids.index(stimulus_id)` raises `ValueError`. This is existing behavior and is not changed by this design.

## Tests

All tests go in `tests/test_baseline_utils.py` (existing file) and `tests/test_hdf5_io.py`:

| Test | File |
|------|------|
| Roundtrip `CrossvalidatedBaselineModel` with `include_stimuli=True` — numerical equality of log-density | `test_baseline_utils.py` |
| Roundtrip `CrossvalidatedBaselineModel` with `include_stimuli=False` + explicit `stimuli=` — numerical equality | `test_baseline_utils.py` |
| `ValueError` when `include_stimuli=False` and no `stimuli=` on reload | `test_baseline_utils.py` |
| `type` attr is `'pysaliency.baseline_utils.CrossvalidatedBaselineModel'` | `test_baseline_utils.py` |
| `pysaliency.read_hdf5` dispatcher round-trips `CrossvalidatedBaselineModel` | `test_hdf5_io.py` |
| `pysaliency.read_hdf5` with `stimuli=` kwarg passthrough bypasses WeakValueDictionary cache | `test_hdf5_io.py` |


================================================
FILE: docs/superpowers/specs/2026-03-23-hdf5-compact-export-design.md
================================================
# Design: Compact HDF5 Export for Model Predictions

**Date:** 2026-03-23
**Status:** Approved

## Background

`pysaliency.export_model_to_hdf5` serializes model predictions (saliency maps or log-density maps) for every stimulus into an HDF5 file. For large stimulus sets these files reach 20–30 GB. Two independently usable optimizations can reduce this significantly with minimal impact on downstream metric scores:

1. **Dtype reduction** — storing predictions as float32 or float16 instead of float64. Pilot experiments show float16 has essentially zero effect on average metric values and only negligible effects on worst-case per-stimulus changes. Float32 is even safer. (Sub-byte float formats such as float8 are out of scope as they are not natively supported by numpy.)
2. **Spatial downsampling** — storing predictions at 1/2, 1/4, or 1/8 of the original resolution. Most saliency models produce smooth outputs. Even 2× downsampling has a noticeably larger effect than dtype reduction, though in practice it is often still acceptable; 4× is larger still; 8× introduces degradation that becomes clearly visible in metric scores.

Both optimizations are independent and can be combined.

## Scope

- `export_model_to_hdf5`: two new parameters (`dtype`, `downscale_factor`)
- `HDF5SaliencyMapModel`: transparent upsampling and upcast on load
- `HDF5Model`: transparent upsampling and renormalization for downsampled or float16 files; strict legacy behavior preserved for float32/float64 non-downsampled files
- New tests in `tests/test_precomputed_models.py`

Out of scope: per-stimulus dtype/downscale selection; formats other than HDF5.

## Design

### Export API

```python
def export_model_to_hdf5(
    model, stimuli, filename,
    compression=9, overwrite=True, flush=False,
    dtype=None,          # e.g. np.float32, np.float16; None = preserve native dtype
    downscale_factor=1,  # integer >= 1; 1 = no downsampling
):
```

Both new parameters default to today's behavior — fully backward compatible.

#### Export pseudocode

The sequencing below is normative; it resolves the ordering of root-attr writing versus stimulus processing:

```python
mode = 'w' if overwrite else 'a'
with h5py.File(filename, mode=mode) as f:

    # Determine which stimuli to process
    if overwrite:
        indices = list(range(len(stimuli)))
    else:
        # append mode is for resuming an interrupted export of the same job,
        # not for combining data from different export runs
        if 'type' in f.attrs:
            # Validate consistency with existing compact file
            _validate_append_consistency(f, downscale_factor)  # raises ValueError on mismatch
        indices = [i for i in range(len(stimuli)) if names[i] not in f]

    if not indices:
        return  # nothing to do

    # Process first stimulus to determine effective_stored_dtype (needed for root attrs)
    first_smap = _compute_smap(model, stimuli[indices[0]])
    effective_stored_dtype = _effective_dtype(first_smap, dtype, downscale_factor)

    # Emit uint8/size guard warning based on first stimulus (once only)
    _check_size_guard(first_smap, effective_stored_dtype)

    # Write root attrs if this is a new file (overwrite=True or file had no attrs)
    if overwrite or 'type' not in f.attrs:
        f.attrs['type'] = 'pysaliency.precomputed_models.predictions'
        f.attrs['version'] = '1.0'
        f.attrs['downscale_factor'] = downscale_factor
        f.attrs['dtype'] = str(effective_stored_dtype)  # informational; based on first stimulus

    # Process all stimuli (reuse already-computed first_smap)
    for i, k in enumerate(indices):
        smap = first_smap if i == 0 else _compute_smap(model, stimuli[k])
        smap = _downsample(smap, downscale_factor)       # step 3; no-op if downscale_factor == 1
        smap = smap.astype(dtype) if dtype is not None else smap  # step 4
        ds = f.create_dataset(names[k], data=smap, compression=compression)
        if downscale_factor > 1:
            ds.attrs['original_shape'] = np.array([H, W], dtype=np.int64)  # pre-padding H, W from step 3
        if flush:
            f.flush()
```

Note on the root-attrs `dtype` field: it is **purely informational**, computed from the first stimulus only. If stimuli have heterogeneous native dtypes (rare in practice), the field may not reflect later stimuli — this is acceptable given it is informational and the loader does not depend on it.

#### `_effective_dtype(smap, dtype, downscale_factor)`

```python
def _effective_dtype(smap, dtype, downscale_factor):
    if dtype is not None:
        return np.dtype(dtype)
    if downscale_factor > 1:
        # numpy .mean() returns float64 for integer inputs, preserves float types
        if np.issubdtype(smap.dtype, np.integer):
            return np.dtype(np.float64)
        else:
            return smap.dtype  # float32 stays float32, float64 stays float64
    return smap.dtype
```

#### `_downsample(smap, downscale_factor)`

```python
def _downsample(smap, k):
    if k == 1:
        return smap
    H, W = smap.shape
    H_pad = int(np.ceil(H / k)) * k
    W_pad = int(np.ceil(W / k)) * k
    smap = np.pad(np.ascontiguousarray(smap),
                  ((0, H_pad - H), (0, W_pad - W)),
                  mode='edge')
    # np.ascontiguousarray ensures C-order before reshape; np.pad preserves C-order
    return smap.reshape(H_pad // k, k, W_pad // k, k).mean(axis=(1, 3))
    # Note: when H is not divisible by k, the last bin is biased toward the
    # border value (edge-padded rows are copies). This is a minor, acceptable
    # artefact for the 2× and 4× factors targeted by this feature.
```

#### `_validate_append_consistency(f, downscale_factor)`

Checks only `int(f.attrs['downscale_factor']) == downscale_factor`. Raises `ValueError` with a descriptive message on mismatch.

The `dtype` root attr is purely informational and is intentionally excluded from this check: determining `effective_stored_dtype` requires computing the first stimulus, which has not happened yet at the point the validator is called. The `downscale_factor` check is sufficient to catch the most dangerous mismatch (spatial layout of stored data).

#### Uint8 / size guard

Implemented in `_check_size_guard(smap, effective_stored_dtype)`:

If `effective_stored_dtype.itemsize > np.dtype(smap.dtype).itemsize`, emit `warnings.warn` explaining:
- The native dtype and its size in bytes
- The effective stored dtype and its size in bytes
- A suggestion to pass `dtype=np.uint8` (if native is integer) or to omit compact options
- Additionally, if native is integer and `downscale_factor > 1` and `dtype` is None: note that casting the float result back to the original integer type would be lossy

### HDF5 File Format

```
/ (root)
  attrs:
    type = 'pysaliency.precomputed_models.predictions'
    version = '1.0'
    downscale_factor = 2
    dtype = 'float32'               # informational; from first stimulus

  images/cat.jpg   (dataset shape ceil(H/2) × ceil(W/2), dtype float32)
    attrs:
      original_shape = np.array([H, W], dtype=np.int64)   # pre-padding; present only when downscale_factor > 1

  images/dog.jpg   (dataset shape ceil(H'/2) × ceil(W'/2), dtype float32)
    attrs:
      original_shape = np.array([H', W'], dtype=np.int64)
```

**Overwritten files** (`overwrite=True`): h5py opens with `mode='w'`, which truncates the file completely before writing begins. The resulting file is always internally consistent.

**Append-mode files** (`overwrite=False`): intended only for resuming an interrupted export. All datasets in the file originate from the same export job with the same settings; mixing data from different jobs is not a supported use case.

**Legacy files** (written by current code) have no root attrs and no `original_shape` on datasets. The loader handles them identically to today.

**Non-compact new files** (`dtype=None`, `downscale_factor=1`) have root attrs but no `original_shape` on datasets — the loader skips upsampling.

Root attrs are purely informational. The loader derives behavior entirely from per-dataset `original_shape` (whether to upsample) and the dataset's native HDF5 dtype (which normalization path to use in `HDF5Model`).

### Load Side

#### `HDF5SaliencyMapModel`

`__init__` reads and stores `f.attrs.get('version')` for future compat; no other init changes.

A new private helper is added:

```python
def _key_for_stimulus(self, stimulus):
    stimulus_id = get_image_hash(stimulus)
    stimulus_index = self.stimuli.stimulus_ids.index(stimulus_id)  # raises ValueError if not found
    return self.names[stimulus_index]
```

`_saliency_map` is refactored to call `_key_for_stimulus` internally, replacing the inline index lookup currently at lines 328–329.

`_saliency_map` updated logic:

1. `stimulus_key = self._key_for_stimulus(stimulus)`
2. `dataset = self.hdf5_file[stimulus_key]`; `smap = dataset[:]`
3. If `'original_shape'` in `dataset.attrs`: cast `smap` to float64, then upsample:
   ```python
   target_shape = tuple(dataset.attrs['original_shape'])
   zoom_factors = (target_shape[0] / smap.shape[0], target_shape[1] / smap.shape[1])
   smap = scipy.ndimage.zoom(smap.astype(np.float64), zoom_factors, order=1, mode='nearest')
   ```
4. Otherwise: return `smap` with native dtype unchanged.

Shape check (`check_shape`) runs against the post-upsampling shape, as today. When using downsampled exports with resized stimuli and `check_shape=False`, upsampling targets `original_shape` (the shape at export time), not the (resized) stimulus shape.

**Dtype behavior:**

| File type | `original_shape` present? | `_saliency_map` returns |
|---|---|---|
| Legacy / uint8 / non-compact | no | native dtype (unchanged) |
| Compact, dtype-reduced only | no | native dtype (float16/32) |
| Compact, downsampled | yes | float64 |

The dtype-reduced-only path intentionally returns the native stored dtype. Downstream code that requires float64 (e.g. `HDF5Model`) is responsible for casting.

#### `HDF5Model`

Constructor gains a new parameter and explicitly stores it:

```python
class HDF5Model(Model):
    def __init__(self, stimuli, filename, check_shape=True,
                 max_normalization_error=np.log(1.1), **kwargs):
        super().__init__(**kwargs)
        self.parent_model = HDF5SaliencyMapModel(
            stimuli=stimuli, filename=filename,
            caching=False, check_shape=check_shape,
        )
        self.max_normalization_error = max_normalization_error  # on HDF5Model, not parent_model
```

`max_normalization_error` can be updated after construction. Setting it to `None` disables the tolerance guard on the relaxed path only — the strict ±0.01 check on the legacy path is always applied regardless.

`_log_density` updated logic:

```python
def _log_density(self, stimulus):
    key = self.parent_model._key_for_stimulus(stimulus)
    dataset = self.parent_model.hdf5_file[key]
    use_relaxed_path = (
        'original_shape' in dataset.attrs                                     # spatially downsampled
        or dataset.dtype.itemsize < np.dtype(np.float32).itemsize             # float16 or narrower
    )
    # Note: this attr lookup is cheap (HDF5 metadata); parent_model has caching=False
    # so the subsequent saliency_map call also reads from disk each time.

    smap = self.parent_model.saliency_map(stimulus).astype(np.float64)

    if use_relaxed_path:
        if self.max_normalization_error is not None:
            if abs(logsumexp(smap)) >= self.max_normalization_error:
                raise ValueError(
                    f'Log density normalization error {abs(logsumexp(smap)):.4f} '
                    f'exceeds threshold {self.max_normalization_error:.4f}'
                )
        smap -= logsumexp(smap)
    else:
        # Legacy path: strict check, no renormalization
        if not -0.01 <= logsumexp(smap) <= 0.01:
            raise ValueError('Not a correct log density!')

    return smap
```

### Interpolation Choices

Motivated by pilot experiments comparing several strategies (always using bilinear upsampling in log space):

- **Downsampling**: area averaging in log/value space via edge-pad then reshape + mean. Non-divisible shapes are handled by padding to the next multiple of `k` with edge values; `original_shape` stores the pre-padding shape so the padding region is implicitly removed during upsampling. Known minor limitation: the last bin in non-divisible dimensions is biased toward the border value (edge-padded rows). This is acceptable for the 2× and 4× factors targeted. Empirically outperforms subsampling and probability-space pooling.
- **Upsampling**: bilinear interpolation in log/value space via `scipy.ndimage.zoom(order=1, mode='nearest')`. `mode='nearest'` matches the existing project convention and avoids reflect-padding edge artefacts. The upsampling strategy has negligible empirical effect on metric scores.
- **Renormalization**: not applied at export; applied at load time only on the relaxed path in `HDF5Model`, after verifying the residual is within the configured tolerance.

## Tests

All new tests in `tests/test_precomputed_models.py`. Roundtrip tests are parametrized over `(dtype, downscale_factor)` combinations: `(None, 1)`, `(np.float32, 1)`, `(np.float16, 1)`, `(np.float32, 2)`, `(np.float16, 4)` for both `HDF5SaliencyMapModel` and `HDF5Model`. The `(None, 1)` case is a regression test (no new code paths). For `HDF5Model`:
- `(np.float32, 1)` and `(None, 1)`: exercise the strict legacy path — no renormalization, strict ±0.01 check
- `(np.float16, 1)`: exercises the relaxed path triggered by dtype (float16 itemsize < float32 itemsize)
- `(np.float32, 2)` and `(np.float16, 4)`: exercise the relaxed path triggered by `original_shape`

| Test | Description |
|------|-------------|
| Root attrs | Correct `type`, `version`, `dtype` (using `effective_stored_dtype`), `downscale_factor` written |
| Dataset dtype | Stored dtype matches `effective_stored_dtype` |
| Dataset shape | Stored shape is `ceil(original_shape / downscale_factor)` |
| `original_shape` attr | Present iff `downscale_factor > 1`; `np.int64` array with correct pre-padding shape |
| Non-divisible shape — stored | Stored shape is `ceil(H/k) × ceil(W/k)` |
| Non-divisible shape — loaded | Upsampled output shape matches `original_shape` exactly |
| Append mode — consistent | Appending with matching settings succeeds |
| Append mode — mismatch (downscale only) | `ValueError` on mismatched `downscale_factor` |
| Append mode — mismatch (dtype only) | `ValueError` on mismatched `dtype` |
| Append mode — new file created | Root attrs written correctly when `overwrite=False` and file is new |
| Append mode — legacy file | Append to legacy file succeeds; no root attrs written |
| uint8 + `downscale_factor>1` — warning | `UserWarning` emitted |
| uint8 + `downscale_factor>1` — stored dtype | Actual stored dtype is float64 |
| uint8 + `dtype=np.float32` — warning | `UserWarning` emitted |
| uint8 + `dtype=np.uint8` | No warning |
| float32 + `downscale_factor>1` + `dtype=None` | Stored dtype is float32 (not float64); root attr `dtype` is `'float32'` |
| Legacy file load | Native dtype returned, shape matches stimulus — no behavioral change |
| float32 load, no downsampling | float32 returned (native dtype preserved) |
| Downsampled load | float64 returned, shape matches `original_shape` |
| Resized stimuli + `check_shape=False` | Stimulus presented at size S ≠ `original_shape`; upsamples to `original_shape`, not S |
| `HDF5Model` float16 relaxed path | float16 file triggers relaxed path; output float64, logsumexp ≈ 0 |
| `HDF5Model` float32 strict path | float32 non-downsampled file uses strict path; logsumexp outside ±0.01 raises `ValueError` |
| `HDF5Model` non-compact no renorm | Non-compact float32/float64 output is bit-identical to stored values (cast to float64) |
| `HDF5Model` downsampled roundtrip | float64 output, logsumexp ≈ 0 after renorm (parametrized) |
| `HDF5Model` threshold | logsumexp before renorm within `log(1.1)` for float16+4× |
| `HDF5Model` corrupted file | `ValueError` raised when logsumexp exceeds `max_normalization_error` |
| Custom `max_normalization_error` | Tighter threshold triggers `ValueError`; looser does not |
| `max_normalization_error=None` | Relaxed path skips guard; output is float64, logsumexp ≈ 0 (renorm still applied) |


================================================
FILE: examples/docker_submission/README.md
================================================
# Submission

This directory contains an example of how to create a Docker container for submitting a scanpath model to the MIT/Tübingen Saliency Benchmark. You'll need to build a docker or singularity container that offers a json API for requesting model predictions. The benchmark will use `pysaliency.http_models.HTTPScanpathModel` to interact with your model.

## Preparing the submission

1. Create a docker or singularity container that exposes your model as an API compatible with `pysaliency.http_models.HTTPScanpathModel`. There are two different examples contained here:
    - `docker_pysaliency`: A docker container exposing a pysaliency model (which is implemented in `sample_submission.py`). Use this if you already have a pysaliency implementation of your model.
    - `docker_deepgaze`: A docker container exposing the DeepGaze model. It demonstrates how to implement the API for an arbitrary model.

2. Build the Docker container as described in the "Launching the submission container" section.


## Launching the submission container

In this example, we will use the `docker_pysaliency` directory to create a Docker container that exposes a pysaliency model. The container will run a Flask server that listens for HTTP requests and responds with model predictions.

First we have to build the container
```bash
docker build -t sample_pysaliency docker
```

Then we can start it
```bash
docker run --rm -it -p 4000:4000 sample_pysaliency
```
The above command will launch the image as interactive container in the foregroun
and expose the port `4000` to the host machine.
If you prefer to run it in the background, use

```bash
docker run --name sample_pysaliency -dp 4000:4000 sample_pysaliency
```
which will launch a container named `sample_pysaliency`. The container will be running in the background.

To test the model server, run the sample_evaluation script. This script will evaluate the model on the MIT1003 dataset.  Make sure to have the `pysaliency` package installed:
```bash
python ./sample_evaluation.py
```

To delete the background container, run the following command:
```bash
docker stop sample_pysaliency && docker rm sample_pysaliency
```

# TODOs

- [ ] Establish and discuss how arguments can be passed to the model server, e.g. information about the image resolution in dva or other parameters.

================================================
FILE: examples/docker_submission/docker_deepgaze3/Dockerfile
================================================
# Specify a base image depending on the project.
FROM bitnami/python:3.8
# For more complex examples, might need to use a different base image.
# FROM pytorch/pytorch:1.9.1-cuda11.1-cudnn8-runtime

WORKDIR /app

ENV HTTP_PORT=4000

RUN apt-get update \
    && apt-get -y install gcc \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /var/cache/apt/*

COPY ./requirements.txt ./
RUN python -m pip install --no-cache -U pip \
    && python -m pip install --no-cache -r requirements.txt

COPY ./model_server.py ./
# COPY ./sample_submission.py ./

# This is needed for Singularity builds.
EXPOSE $HTTP_PORT

# The entrypoint for a container,
CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:4000", "--pythonpath", ".", "--access-logfile", "-", "model_server:app"]

================================================
FILE: examples/docker_submission/docker_deepgaze3/model_server.py
================================================
from flask import Flask, request
import numpy as np
import json
from PIL import Image
from io import BytesIO
import orjson
from scipy.ndimage import zoom
from scipy.special import logsumexp
import torch

# Import your model here
import deepgaze_pytorch

# Flask server
app = Flask("saliency-model-server")
app.logger.setLevel("DEBUG")

# # TODO - replace this with your model
model = deepgaze_pytorch.DeepGazeIII(pretrained=True)

def get_fixation_history(fixation_coordinates, model):
    history = []
    for index in model.included_fixations:
        try:
            history.append(fixation_coordinates[index])
        except IndexError:
            # for early fixations, not all previous fixations exist
            history.append(np.nan)
    return np.array(history)

@app.route('/conditional_log_density', methods=['POST'])
def conditional_log_density():
    # get data
    data = json.loads(request.form['json_data'])

    # extract scanpath history
    x_hist = np.array(data['x_hist'])
    y_hist = np.array(data['y_hist'])
    print(x_hist)

    x_hist = get_fixation_history(x_hist, model)
    print(x_hist)
    y_hist = get_fixation_history(y_hist, model)
    # t_hist = np.array(data['t_hist'])
    # attributes = data.get('attributes', {})

    # extract stimulus
    image_bytes = request.files['stimulus'].read()
    image = Image.open(BytesIO(image_bytes))
    stimulus = np.array(image)

    # centerbias for deepgaze3 model
    centerbias_template = np.zeros((1024, 1024))
    centerbias = zoom(centerbias_template,
                        (stimulus.shape[0]/centerbias_template.shape[0],
                         stimulus.shape[1]/centerbias_template.shape[1]),
                        order=0,
                        mode='nearest'
    )
    centerbias -= logsumexp(centerbias)

    # make tensors for deepgaze3 model
    image_tensor = torch.tensor([stimulus.transpose(2, 0, 1)])
    centerbias_tensor = torch.tensor([centerbias])
    x_hist_tensor = torch.tensor([x_hist[model.included_fixations]])
    y_hist_tensor = torch.tensor([y_hist[model.included_fixations]])

    # return model response
    log_density = model(image_tensor, centerbias_tensor, x_hist_tensor, y_hist_tensor)
    log_density_list = log_density.tolist()
    response = orjson.dumps({'log_density': log_density_list})
    return response


@app.route('/type', methods=['GET'])
def type():
    type = "ScanpathModel"
    version = "v1.0.0"
    return orjson.dumps({'type': type, 'version': version})




def main():
    app.run(host="localhost", port="4000", debug="True", threaded=True)


if __name__ == "__main__":
    main()

================================================
FILE: examples/docker_submission/docker_deepgaze3/requirements.txt
================================================
cython
flask
gunicorn
numpy

# Add additional dependencies here
pysaliency
scipy
torch
flask_orjson
git+https://github.com/matthias-k/deepgaze

================================================
FILE: examples/docker_submission/docker_pysaliency_model/Dockerfile
================================================
# Specify a base image depending on the project.
FROM bitnami/python:3.8
# For more complex examples, might need to use a different base image.
# FROM pytorch/pytorch:1.9.1-cuda11.1-cudnn8-runtime

WORKDIR /app

ENV HTTP_PORT=4000

RUN apt-get update \
    && apt-get -y install gcc \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /var/cache/apt/*

COPY ./requirements.txt ./
RUN python -m pip install --no-cache -U pip \
    && python -m pip install --no-cache -r requirements.txt

COPY ./model_server.py ./
COPY ./sample_submission.py ./

# This is needed for Singularity builds.
EXPOSE $HTTP_PORT

# The entrypoint for a container,
CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:4000", "--pythonpath", ".", "--access-logfile", "-", "model_server:app"]

================================================
FILE: examples/docker_submission/docker_pysaliency_model/model_server.py
================================================
from flask import Flask, request, jsonify
from flask_orjson import OrjsonProvider
import numpy as np
import json
from PIL import Image
from io import BytesIO
import orjson


# Import your model here
from sample_submission import MySimpleScanpathModel

app = Flask("saliency-model-server")
app.json_provider = OrjsonProvider(app)
app.logger.setLevel("DEBUG")

# # TODO - replace this with your model
model = MySimpleScanpathModel()

@app.route('/conditional_log_density', methods=['POST'])
def conditional_log_density():
    data = json.loads(request.form['json_data'])
    x_hist = np.array(data['x_hist'])
    y_hist = np.array(data['y_hist'])
    t_hist = np.array(data['t_hist'])
    attributes = data.get('attributes', {})

    image_bytes = request.files['stimulus'].read()
    image = Image.open(BytesIO(image_bytes))
    stimulus = np.array(image)

    log_density = model.conditional_log_density(stimulus, x_hist, y_hist, t_hist, attributes)
    log_density_list = log_density.tolist()
    response = orjson.dumps({'log_density': log_density_list})
    return response


@app.route('/type', methods=['GET'])
def type():
    type = "ScanpathModel"
    version = "v1.0.0"
    return orjson.dumps({'type': type, 'version': version})
   

def main():
    app.run(host="localhost", port="4000", debug="True", threaded=True)


if __name__ == "__main__":
    main()

================================================
FILE: examples/docker_submission/docker_pysaliency_model/requirements.txt
================================================
cython
flask
gunicorn
numpy

# Add additional dependencies here
pysaliency
scipy
torch
flask_orjson


================================================
FILE: examples/docker_submission/docker_pysaliency_model/sample_submission.py
================================================
"""
This file implements a very simple scanpath model using local constrast and a saccadic prior.
"""

import numpy as np
import sys
from typing import Union
from scipy.ndimage import gaussian_filter
import pysaliency


class LocalContrastModel(pysaliency.Model):
    def __init__(self, bandwidth=0.05, **kwargs):
        super().__init__(**kwargs)
        self.bandwidth = bandwidth

    def _log_density(self, stimulus: Union[pysaliency.datasets.Stimulus, np.ndarray]):

        # _log_density can either take pysaliency Stimulus objects, or, for convenience, simply numpy arrays
        # `as_stimulus` ensures that we have a Stimulus object
        stimulus_object = pysaliency.datasets.as_stimulus(stimulus)

        # grayscale image
        gray_stimulus = np.mean(stimulus_object.stimulus_data, axis=2)

        # size contains the height and width of the image, but not potential color channels
        height, width = stimulus_object.size

        # define kernel size based on image size
        kernel_size = np.round(self.bandwidth * max(width, height)).astype(int)
        sigma = (kernel_size - 1) / 6

        # apply Gausian blur and calculate squared difference between blurred and original image
        blurred_stimulus = gaussian_filter(gray_stimulus, sigma)

        prediction = gaussian_filter((gray_stimulus - blurred_stimulus)**2, sigma)

        # normalize to [1, 255]
        prediction = (254 * (prediction / prediction.max())).astype(int) + 1

        density = prediction / prediction.sum()

        return np.log(density)

class MySimpleScanpathModel(pysaliency.ScanpathModel):
    def __init__(self, spatial_model_bandwidth: float=0.05, saccade_width: float=0.1):
        self.spatial_model_bandwidth = spatial_model_bandwidth
        self.saccade_width = saccade_width
        self.spatial_model = LocalContrastModel(spatial_model_bandwidth)
        # self.spatial_model = pysaliency.UniformModel()


    def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, attributes=None, out=None,):
        stimulus_object = pysaliency.datasets.as_stimulus(stimulus)

        # size contains the height and width of the image, but not potential color channels
        height, width = stimulus_object.size

        spatial_prior_log_density = self.spatial_model.log_density(stimulus)
        spatial_prior_density = np.exp(spatial_prior_log_density)

        # compute saccade bias
        last_x = x_hist[-1]
        last_y = y_hist[-1]

        xs = np.arange(width, dtype=float)
        ys = np.arange(height, dtype=float)
        XS, YS = np.meshgrid(xs, ys)

        XS -= last_x
        YS -= last_y

        # compute prior
        max_size = max(width, height)
        actual_kernel_size = self.saccade_width * max_size

        saccade_bias = np.exp(-0.5 * (XS ** 2 + YS ** 2) / actual_kernel_size ** 2)

        prediction = spatial_prior_density * saccade_bias

        density = prediction / prediction.sum()
        return np.log(density)



================================================
FILE: examples/docker_submission/sample_evaluation.py
================================================
import numpy as np
import sys
from pysaliency.http_models import HTTPScanpathModel
sys.path.insert(0, '..')
import pysaliency


from tqdm import tqdm

import deepgaze_pytorch

def get_fixation_history(fixation_coordinates, model):
    history = []
    for index in model.included_fixations:
        try:
            history.append(fixation_coordinates[index])
        except IndexError:
            history.append(np.nan)
    return history

if __name__ == "__main__":

    # initialize HTTPScanpathModel
    http_model = HTTPScanpathModel("http://localhost:4000")
    http_model.check_type()

    # for testing
    # test_model = deepgaze_pytorch.DeepGazeIII(pretrained=True)

    # get MIT1003 dataset
    stimuli, fixations = pysaliency.get_mit1003(location='pysaliency_datasets')

    # only use first 1000 fixations for testing
    eval_fixations = fixations[fixations.scanpath_history_length > 0][:1000] # error if no history


    information_gain = http_model.information_gain(stimuli, eval_fixations, average="image", verbose=True)
    print("IG:", information_gain)

    # for fixation_index in tqdm(range(len(eval_fixations))):

        # get server response for one stimulus
        # server_density = http_model.conditional_log_density(
        #     stimulus=stimuli.stimuli[eval_fixations.n[fixation_index]],
        #     x_hist=eval_fixations.x_hist[fixation_index],
        #     y_hist=eval_fixations.y_hist[fixation_index],
        #     t_hist=eval_fixations.t_hist[fixation_index]
        # )
        # get test model response
        # test_model_density = test_model(
        #     stimulus=stimuli.stimuli[eval_fixations.n[fixation_index]],
        #     x_hist=eval_fixations.x_hist[fixation_index],
        #     y_hist=eval_fixations.y_hist[fixation_index],
        #     t_hist=eval_fixations.t_hist[fixation_index]
        # )

        # Testing
        # test = np.testing.assert_allclose(server_density, test_model_density)


================================================
FILE: notebooks/LSUN.ipynb
================================================
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# LSUN Challenge"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This document describes how to setup and run the python code for the LSUN saliency evaluation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With your favorite python package management tool, install the needed libraries (here shown for `pip`):\n",
    "\n",
    "    pip install numpy scipy theano Cython natsort dill hdf5storage\n",
    "    pip install git+https://github.com/matthias-k/optpy\n",
    "    pip install git+https://github.com/matthias-k/pysaliency\n",
    "\n",
    "If you want to use the SALICON dataset, you also need to install the\n",
    "[SALICON API](https://github.com/NUS-VIP/salicon-api)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Usage\n",
    "\n",
    "start by importing pysaliency:\n",
    "\n",
    "    import pysaliency\n",
    "\n",
    "you probably also want to load the LSUN datasets:\n",
    "\n",
    "    dataset_location = 'datasets' # where to cache datasets\n",
    "    stimuli_salicon_train, fixations_salicon_train = pysaliency.get_SALICON_train(location=dataset_location)\n",
    "    stimuli_salicon_val, fixations_salicon_val = pysaliency.get_SALICON_val(location=dataset_location)\n",
    "    stimuli_salicon_test = pysaliency.get_SALICON_test(location=dataset_location)\n",
    "    \n",
    "    stimuli_isun_train, stimuli_isun_val, stimuli_isun_test, fixations_isun_train, fixations_isun_val = pysaliency.get_iSUN(location=dataset_location)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# TODO: Add ModelFromDirectory for log densities\n",
    "# TODO: Change defaults for saliency map convertor (at least in LSUN subclass)\n",
    "# TODO: Write fit functions optimize_for_information_gain(model, stimuli, fixations)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Import your saliency model into pysaliency\n",
    "\n",
    "If you did not develop your model in the pysaliency framework, you have to import the generated saliencymaps or log-densities into pysaliency. If you have the saliency maps saved to an directory with names corresponding to the stimuli\n",
    "filenames, use `pysaliency.SaliencyMapModelFromDirectory`. You can save your saliency maps as png, jpg, tiff, mat or npy files."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "my_model = pysaliency.SaliencyMapModelFromDirectory(stimuli_salicon_train, \"my_model/saliency_maps/SALICON_TRAIN\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you have an LSUN submission file prepared, you can load it with `pysaliency.SaliencyMapModelFromDirectory`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "my_model = pysaliency.SaliencyMapModelFromFile(stimuli_salicon_train, \"my_model/salicon_train.mat\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Evaluate your model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Evaluating your model with pysaliency is fairly easy. In general, the evaluation functions take the stimuli and fixations to evaluate on, and maybe some additional configuration parameters. The following metrics are used in the LSUN saliency challenge (additionaly, the information gain metric is used, see below):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "my_model.AUC(stimuli_salicon_train, fixations_salicon_train, nonfixations='uniform')\n",
    "my_model.AUC(stimuli_salicon_train, fixations_salicon_train, nonfixations='shuffled')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Optimize your model for information gain"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you wish to hand in a probabilistic model, you might wish to optimize the model for the nonlinearity and centerbiases\n",
    "of the datasets. Otherwise we will optimize all saliency map models for information gain using a subset of the iSUN dataset using the following code. Feel free to adapt it to your needs (for example, use more images for fitting)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "my_probabilistic_model = pysaliency.SaliencyMapConvertor(my_model, ...)\n",
    "fit_stimuli, fit_fixations = pysaliency.create_subset(stimuli_isun_train, fixations_isun_train, range(0, 500))\n",
    "my_probabilistic_model = pysaliency.optimize_for_information_gain\n",
    "    my_model, fit_stimuli, fit_fixations,\n",
    "    num_nonlinearity=20,\n",
    "    num_centerbias=12,\n",
    "    optimize=[\n",
    "        'nonlinearity',\n",
    "        'centerbias',\n",
    "        'alpha',\n",
    "        #'blurradius', # we do not optimize the bluring.\n",
    "    ])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### hand in your model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.1+"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}


================================================
FILE: notebooks/Tutorial.ipynb
================================================
{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "18dc5e5a-ece3-477c-9090-b4265ba04942",
   "metadata": {},
   "source": [
    "# Pysaliency: A short tutorial\n",
    "\n",
    "`pysaliency` is a python library which aims at making analyzing and modeling of eye movement data convenient. It was build and extended over the course of multiple papers which are reflected in its structure, mainly:\n",
    "\n",
    "* [Kümmerer, Wallis & Bethge: Information-theoretic model comparison unifies saliency metrics. PNAS 2015](http://www.pnas.org/content/112/52/16054)\n",
    "* [Kümmerer, Wallis & Bethge: Saliency Benchmarking Made Easy: Separating Models, Maps and Metrics. ECCV 2018](http://openaccess.thecvf.com/content_ECCV_2018/html/Matthias_Kummerer_Saliency_Benchmarking_Made_ECCV_2018_paper.html)\n",
    "* [Kümmerer & Bethge: Predicting Visual Fixations, Annual Reviews in Vision Science 2023](https://www.annualreviews.org/doi/10.1146/annurev-vision-120822-072528)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "08c68bf6-8d6d-4c13-934d-8555bf7ca985",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import os\n",
    "from typing import Union\n",
    "\n",
    "\n",
    "import sys\n",
    "\n",
    "# only needed if running notebook from within the pysaliency repository\n",
    "sys.path.insert(0, '..')\n",
    "\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import pysaliency\n",
    "\n",
    "from scipy.ndimage import gaussian_filter\n",
    "\n",
    "# change path to your local matlab installation\n",
    "# os.environ['PATH'] = f\"/usr/local/MATLAB/R2024a/bin/:{os.environ['PATH']}\"\n",
    "os.environ['PATH'] = f\"/Applications/MATLAB_R2024b.app//bin/:{os.environ['PATH']}\"\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "06a8bc27-b407-4844-b381-8bd22e3a8f72",
   "metadata": {},
   "source": [
    "## Datasets\n",
    "\n",
    "`pysaliency` has to main classes for handling data. `pysaliency.Stimuli` contains images which have been shown to a subject, `pysaliency.Fixations` keeps track of recorded fixations. For the purpose of this tutorial, we'll use the MIT1003 dataset, which `pysaliency` can download and import on it's own."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c0ec6a86-bebe-4cdb-b863-1557905c1026",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Downloading file: 235MB [01:49, 2.14MB/s]                            \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Checking md5 sum...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Downloading file: 47.1MB [00:21, 2.22MB/s]                            \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Checking md5 sum...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Downloading file: 32.8kB [00:00, 346kB/s]                    \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Checking md5 sum...\n",
      "Creating stimuli\n",
      "Creating fixations\n",
      "Running original code to extract fixations. This can take some minutes.\n",
      "Warning: In the IPython Notebook, the output is shown on the console instead of the notebook.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING: package sun.awt.X11 not in java.desktop\n",
      "WARNING: package sun.awt.X11 not in java.desktop\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Processing scanpath 15044/15045\r"
     ]
    }
   ],
   "source": [
    "stimuli, fixations = pysaliency.get_mit1003(location='pysaliency_datasets')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b31fae21-1642-4041-9cd7-76b4a2ef5298",
   "metadata": {},
   "source": [
    "`Stimuli` hold the images in `Stimuli.stimuli` as numpy array. In the case of large datasets, the subclass `FileStimuli` (which is used here) will make sure that the images are only loaded once they are needed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "343bd172-bb86-4e23-897e-0c6d60042de0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "00: (768, 1024, 3) (pysaliency_datasets/MIT1003/stimuli/i05june05_static_street_boston_p1010764.jpeg)\n",
      "01: (768, 1024, 3) (pysaliency_datasets/MIT1003/stimuli/i05june05_static_street_boston_p1010785.jpeg)\n",
      "02: (768, 1024, 3) (pysaliency_datasets/MIT1003/stimuli/i05june05_static_street_boston_p1010800.jpeg)\n",
      "03: (768, 1024, 3) (pysaliency_datasets/MIT1003/stimuli/i05june05_static_street_boston_p1010806.jpeg)\n",
      "04: (768, 1024, 3) (pysaliency_datasets/MIT1003/stimuli/i05june05_static_street_boston_p1010808.jpeg)\n",
      "05: (768, 1024, 3) (pysaliency_datasets/MIT1003/stimuli/i05june05_static_street_boston_p1010816.jpeg)\n",
      "06: (768, 1024, 3) (pysaliency_datasets/MIT1003/stimuli/i05june05_static_street_boston_p1010855.jpeg)\n",
      "07: (768, 1024, 3) (pysaliency_datasets/MIT1003/stimuli/i05june05_static_street_boston_p1010885.jpeg)\n",
      "08: (768, 1024, 3) (pysaliency_datasets/MIT1003/stimuli/i05june05_static_street_boston_p1010907.jpeg)\n",
      "09: (768, 1024, 3) (pysaliency_datasets/MIT1003/stimuli/i10feb04_static_cars_highland_img_0843.jpeg)\n",
      "Total number of stimuli in dataset: 1003\n"
     ]
    }
   ],
   "source": [
    "for image_index in range(10):\n",
    "    print(f\"{image_index:02d}: {stimuli.stimuli[image_index].shape} ({stimuli.filenames[image_index]})\")\n",
    "\n",
    "print(f\"Total number of stimuli in dataset: {len(stimuli)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ed51a9e1-0cfa-4258-8ae5-195e45ec5053",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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
Download .txt
gitextract_9eg4n1my/

├── .github/
│   └── workflows/
│       └── test-package-conda.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.md
├── MANIFEST.in
├── Makefile
├── README.md
├── docs/
│   └── superpowers/
│       ├── plans/
│       │   ├── 2026-03-21-crossvalidated-baseline-model-hdf5.md
│       │   └── 2026-03-23-compact-hdf5-export.md
│       └── specs/
│           ├── 2026-03-21-model-hdf5-save-reload-design.md
│           └── 2026-03-23-hdf5-compact-export-design.md
├── examples/
│   └── docker_submission/
│       ├── README.md
│       ├── docker_deepgaze3/
│       │   ├── Dockerfile
│       │   ├── model_server.py
│       │   └── requirements.txt
│       ├── docker_pysaliency_model/
│       │   ├── Dockerfile
│       │   ├── model_server.py
│       │   ├── requirements.txt
│       │   └── sample_submission.py
│       └── sample_evaluation.py
├── notebooks/
│   ├── LSUN.ipynb
│   └── Tutorial.ipynb
├── pyproject.toml
├── pysaliency/
│   ├── __init__.py
│   ├── baseline_utils.py
│   ├── dataset_config.py
│   ├── datasets/
│   │   ├── __init__.py
│   │   ├── fixations.py
│   │   ├── scanpaths.py
│   │   ├── stimuli.py
│   │   └── utils.py
│   ├── external_datasets/
│   │   ├── __init__.py
│   │   ├── cat2000.py
│   │   ├── coco_freeview.py
│   │   ├── coco_search18.py
│   │   ├── dut_omrom.py
│   │   ├── figrim.py
│   │   ├── isun.py
│   │   ├── koehler.py
│   │   ├── mit.py
│   │   ├── nusef.py
│   │   ├── osie.py
│   │   ├── pascal_s.py
│   │   ├── salicon.py
│   │   ├── scripts/
│   │   │   ├── extract_fixations.m
│   │   │   └── load_cat2000.m
│   │   ├── toronto.py
│   │   └── utils.py
│   ├── external_models/
│   │   ├── __init__.py
│   │   ├── deepgaze.py
│   │   ├── matlab_models.py
│   │   ├── models.py
│   │   ├── scripts/
│   │   │   ├── AIM_wrapper.m
│   │   │   ├── BMS/
│   │   │   │   ├── BMS_wrapper.m
│   │   │   │   └── patches/
│   │   │   │       ├── adapt_opencv_paths.diff
│   │   │   │       ├── correct_add_path.diff
│   │   │   │       ├── fix_FileGettor.diff
│   │   │   │       └── series
│   │   │   ├── ContextAwareSaliency_wrapper.m
│   │   │   ├── CovSal_wrapper.m
│   │   │   ├── GBVS/
│   │   │   │   ├── GBVSIttiKoch_wrapper.m
│   │   │   │   ├── GBVS_wrapper.m
│   │   │   │   └── patches/
│   │   │   │       ├── get_path
│   │   │   │       ├── make_mex_files_octave_compatible
│   │   │   │       └── series
│   │   │   ├── IttiKoch_wrapper.m
│   │   │   ├── Judd/
│   │   │   │   ├── FaceDetect_patches/
│   │   │   │   │   ├── change_opencv_include
│   │   │   │   │   └── series
│   │   │   │   ├── JuddSaliencyModel_patches/
│   │   │   │   │   ├── find_cascade_file
│   │   │   │   │   ├── locate_FelzenszwalbDetector_files
│   │   │   │   │   └── series
│   │   │   │   ├── Judd_wrapper.m
│   │   │   │   ├── SaliencyToolbox_patches/
│   │   │   │   │   ├── enable_unit16
│   │   │   │   │   └── series
│   │   │   │   └── voc_patches/
│   │   │   │       ├── change_fconv
│   │   │   │       ├── matlabR2014a_compatible
│   │   │   │       ├── matlabR2021a_compatible
│   │   │   │       └── series
│   │   │   ├── RARE2012_wrapper.m
│   │   │   ├── SUN_wrapper.m
│   │   │   └── ensure_image_is_color_image.m
│   │   └── utils.py
│   ├── filter_datasets.py
│   ├── hdf5.py
│   ├── http_models.py
│   ├── metric_optimization.py
│   ├── metric_optimization_tf.py
│   ├── metric_optimization_torch.py
│   ├── metrics.py
│   ├── models.py
│   ├── numba_utils.py
│   ├── optpy/
│   │   ├── README.md
│   │   ├── __init__.py
│   │   ├── jacobian.py
│   │   └── optimization.py
│   ├── plotting.py
│   ├── precomputed_models.py
│   ├── quilt.py
│   ├── roc.py
│   ├── roc_cython.pyx
│   ├── saliency_map_conversion.py
│   ├── saliency_map_conversion_theano.py
│   ├── saliency_map_conversion_torch.py
│   ├── saliency_map_models.py
│   ├── sampling_models.py
│   ├── tf_utils.py
│   ├── theano_utils.py
│   ├── torch_datasets.py
│   ├── torch_utils.py
│   └── utils/
│       ├── __init__.py
│       └── variable_length_array.py
├── pytest.ini
├── requirements.txt
├── setup.py
└── tests/
    ├── conftest.py
    ├── datasets/
    │   ├── test_datasets.py
    │   ├── test_fixations.py
    │   ├── test_scanpaths.py
    │   ├── test_stimuli.py
    │   └── utils.py
    ├── external_datasets/
    │   ├── test_COCO_Search18.py
    │   ├── test_NUSEF.py
    │   ├── test_PASCAL_S.py
    │   ├── test_SALICON.py
    │   └── test_coco_freeview.py
    ├── external_models/
    │   ├── AIM_color_stimulus.npy
    │   ├── AIM_grayscale_stimulus.npy
    │   ├── ContextAwareSaliency_color_stimulus.npy
    │   ├── ContextAwareSaliency_grayscale_stimulus.npy
    │   ├── CovSal_color_stimulus.npy
    │   ├── CovSal_grayscale_stimulus.npy
    │   ├── GBVSIttiKoch_color_stimulus.npy
    │   ├── GBVSIttiKoch_grayscale_stimulus.npy
    │   ├── GBVS_color_stimulus.npy
    │   ├── GBVS_grayscale_stimulus.npy
    │   ├── IttiKoch_color_stimulus.npy
    │   ├── IttiKoch_grayscale_stimulus.npy
    │   ├── Judd_color_stimulus.npy
    │   ├── Judd_grayscale_stimulus.npy
    │   ├── RARE2007_color_stimulus.npy
    │   ├── RARE2007_grayscale_stimulus.npy
    │   ├── RARE2012_color_stimulus.npy
    │   ├── RARE2012_grayscale_stimulus.npy
    │   ├── SUN_color_stimulus.npy
    │   ├── SUN_grayscale_stimulus.npy
    │   ├── color_stimulus.npy
    │   ├── grayscale_stimulus.npy
    │   └── test_deepgaze.py
    ├── skippedtest_theano_utils.py
    ├── test_baseline_utils.py
    ├── test_crossvalidation.py
    ├── test_dataset_config.py
    ├── test_external_datasets.py
    ├── test_external_models.py
    ├── test_filter_datasets.py
    ├── test_hdf5_io.py
    ├── test_helpers.py
    ├── test_http_models.py
    ├── test_metric_optimization.py
    ├── test_metric_optimization_tf.py
    ├── test_metric_optimization_torch.py
    ├── test_models.py
    ├── test_numba_utils.py
    ├── test_precomputed_models.py
    ├── test_quilt/
    │   ├── .pc/
    │   │   ├── .quilt_patches
    │   │   ├── .quilt_series
    │   │   ├── .version
    │   │   ├── add_numbers.diff/
    │   │   │   ├── .timestamp
    │   │   │   └── source.txt
    │   │   └── applied-patches
    │   ├── patches/
    │   │   ├── add_numbers.diff
    │   │   └── series
    │   ├── source/
    │   │   ├── .pc/
    │   │   │   ├── .quilt_patches
    │   │   │   ├── .quilt_series
    │   │   │   └── .version
    │   │   └── source.txt
    │   ├── source.txt
    │   └── target/
    │       └── source.txt
    ├── test_quilt.py
    ├── test_saliency_map_conversion.py
    ├── test_saliency_map_conversion_theano.py
    ├── test_saliency_map_conversion_torch.py
    ├── test_saliency_map_conversion_torch_extended.py
    ├── test_saliency_map_models.py
    ├── test_sampling.py
    ├── test_torch_datasets.py
    ├── test_torch_utils.py
    ├── test_utils.py
    └── utils/
        └── test_variable_length_array.py
Download .txt
SYMBOL INDEX (1253 symbols across 93 files)

FILE: examples/docker_submission/docker_deepgaze3/model_server.py
  function get_fixation_history (line 21) | def get_fixation_history(fixation_coordinates, model):
  function conditional_log_density (line 32) | def conditional_log_density():
  function type (line 76) | def type():
  function main (line 84) | def main():

FILE: examples/docker_submission/docker_pysaliency_model/model_server.py
  function conditional_log_density (line 21) | def conditional_log_density():
  function type (line 39) | def type():
  function main (line 45) | def main():

FILE: examples/docker_submission/docker_pysaliency_model/sample_submission.py
  class LocalContrastModel (line 12) | class LocalContrastModel(pysaliency.Model):
    method __init__ (line 13) | def __init__(self, bandwidth=0.05, **kwargs):
    method _log_density (line 17) | def _log_density(self, stimulus: Union[pysaliency.datasets.Stimulus, n...
  class MySimpleScanpathModel (line 45) | class MySimpleScanpathModel(pysaliency.ScanpathModel):
    method __init__ (line 46) | def __init__(self, spatial_model_bandwidth: float=0.05, saccade_width:...
    method conditional_log_density (line 53) | def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, at...

FILE: examples/docker_submission/sample_evaluation.py
  function get_fixation_history (line 12) | def get_fixation_history(fixation_coordinates, model):

FILE: pysaliency/baseline_utils.py
  function _normalize_fixations (line 22) | def _normalize_fixations(orig_xs, orig_ys, orig_ns, sizes, new_xs, new_y...
  function normalize_fixations (line 31) | def normalize_fixations(stimuli, fixations, keep_aspect=False, add_shape...
  function fixations_to_scikit_learn (line 62) | def fixations_to_scikit_learn(fixations, normalize=None, keep_aspect=Fal...
  class ScikitLearnImageCrossValidationGenerator (line 85) | class ScikitLearnImageCrossValidationGenerator(object):
    method __init__ (line 86) | def __init__(self, stimuli, fixations, within_stimulus_attributes=None...
    method __iter__ (line 98) | def __iter__(self):
    method __len__ (line 128) | def __len__(self):
  class ScikitLearnImageSubjectCrossValidationGenerator (line 132) | class ScikitLearnImageSubjectCrossValidationGenerator(object):
    method __init__ (line 133) | def __init__(self, stimuli, fixations):
    method __iter__ (line 137) | def __iter__(self):
    method __len__ (line 155) | def __len__(self):
  class ScikitLearnWithinImageCrossValidationGenerator (line 159) | class ScikitLearnWithinImageCrossValidationGenerator(object):
    method __init__ (line 160) | def __init__(self, stimuli, fixations, chunks_per_image=10, random_see...
    method __iter__ (line 166) | def __iter__(self):
    method __len__ (line 188) | def __len__(self):
  class GeneralMixtureKernelDensityEstimator (line 198) | class GeneralMixtureKernelDensityEstimator(DensityMixin, BaseEstimator):
    method __init__ (line 213) | def __init__(self, bandwidth: float, regularizations: List[float], reg...
    method setup (line 221) | def setup(self):
    method fit (line 228) | def fit(self, X):
    method score_samples (line 235) | def score_samples(self, X):
    method score (line 248) | def score(self, X):
  class RegularizedKernelDensityEstimator (line 252) | class RegularizedKernelDensityEstimator(DensityMixin, BaseEstimator):
    method __init__ (line 253) | def __init__(self, bandwidth=1.0, regularization = 1.0e-5):
    method setup (line 257) | def setup(self):
    method fit (line 266) | def fit(self, X):
    method score_samples (line 272) | def score_samples(self, X):
    method score (line 281) | def score(self, X):
  class MixtureKernelDensityEstimator (line 285) | class MixtureKernelDensityEstimator(DensityMixin, BaseEstimator):
    method __init__ (line 286) | def __init__(self, bandwidth=1.0, regularization = 1.0e-5, regularizin...
    method setup (line 291) | def setup(self):
    method fit (line 297) | def fit(self, X):
    method score_samples (line 304) | def score_samples(self, X):
    method score (line 317) | def score(self, X):
  class AUCKernelDensityEstimator (line 321) | class AUCKernelDensityEstimator(DensityMixin, BaseEstimator):
    method __init__ (line 322) | def __init__(self, nonfixations, bandwidth=1.0):
    method setup (line 326) | def setup(self):
    method fit (line 329) | def fit(self, X):
    method score_samples (line 335) | def score_samples(self, X):
    method score (line 343) | def score(self, X):
  function _normalize_regularization_factors (line 350) | def _normalize_regularization_factors(args):
  class CrossvalMultipleRegularizations (line 372) | class CrossvalMultipleRegularizations(object):
    method __init__ (line 378) | def __init__(self, stimuli, fixations, regularization_models: OrderedD...
    method score (line 414) | def score(self, log_bandwidth, *args, **kwargs):
  class GoldModel (line 434) | class GoldModel(Model):
    method __init__ (line 435) | def __init__(self, stimuli, fixations, bandwidth, eps = 1e-20, keep_as...
    method _log_density (line 445) | def _log_density(self, stimulus):
  class KDEGoldModel (line 479) | class KDEGoldModel(Model):
    method __init__ (line 480) | def __init__(self, stimuli, fixations, bandwidth, eps=1e-20, keep_aspe...
    method _log_density (line 493) | def _log_density(self, stimulus):
  class CrossvalidatedBaselineModel (line 559) | class CrossvalidatedBaselineModel(Model):
    method __init__ (line 560) | def __init__(self, stimuli, fixations, bandwidth, eps=1e-20, **kwargs):
    method _log_density (line 568) | def _log_density(self, stimulus):
    method to_hdf5 (line 590) | def to_hdf5(self, target, include_stimuli=True):
    method read_hdf5 (line 605) | def read_hdf5(
  class BaselineModel (line 643) | class BaselineModel(Model):
    method __init__ (line 644) | def __init__(self, stimuli, fixations, bandwidth, eps = 1e-20, keep_as...
    method to_hdf5 (line 653) | def to_hdf5(self, target, include_shape_cache=True):
    method read_hdf5 (line 670) | def read_hdf5(
    method _log_density (line 704) | def _log_density(self, stimulus):

FILE: pysaliency/dataset_config.py
  function load_dataset_from_config (line 29) | def load_dataset_from_config(config):
  function apply_dataset_filter_config (line 40) | def apply_dataset_filter_config(stimuli, fixations, filter_config):
  function _clip_out_of_stimulus_fixations (line 65) | def _clip_out_of_stimulus_fixations(stimuli, fixations):
  function _remove_out_of_stimulus_fixations (line 70) | def _remove_out_of_stimulus_fixations(stimuli, fixations):
  function add_stimuli_argument (line 75) | def add_stimuli_argument(fn):

FILE: pysaliency/datasets/__init__.py
  function read_hdf5 (line 12) | def read_hdf5(source, **kwargs):
  function create_subset (line 16) | def create_subset(stimuli, fixations, stimuli_indices):
  function concatenate_stimuli (line 67) | def concatenate_stimuli(stimuli):
  function concatenate_fixations (line 78) | def concatenate_fixations(fixations):
  function concatenate_datasets (line 87) | def concatenate_datasets(stimuli, fixations):
  function remove_out_of_stimulus_fixations (line 107) | def remove_out_of_stimulus_fixations(stimuli, fixations):
  function clip_out_of_stimulus_fixations (line 120) | def clip_out_of_stimulus_fixations(fixations, stimuli=None, width=None, ...
  function calculate_nonfixation_factors (line 163) | def calculate_nonfixation_factors(stimuli, index):
  function create_nonfixations (line 173) | def create_nonfixations(stimuli, fixations, index, adjust_n = True, adju...

FILE: pysaliency/datasets/fixations.py
  class Fixations (line 15) | class Fixations(object):
    method __init__ (line 41) | def __init__(self,
    method _check_lengths (line 102) | def _check_lengths(self, other: VariableLengthArray):
    method _as_variable_length_array (line 108) | def _as_variable_length_array(self, data: Union[np.ndarray, VariableLe...
    method lengths (line 118) | def lengths(self):
    method subjects (line 123) | def subjects(self):
    method create_without_history (line 128) | def create_without_history(cls, x, y, n, subjects=None):
    method from_fixation_matrices (line 137) | def from_fixation_matrices(cls, matrices):
    method concatenate (line 170) | def concatenate(cls, fixations):
    method __getitem__ (line 188) | def __getitem__(self, indices):
    method __len__ (line 191) | def __len__(self):
    method filter (line 194) | def filter(self, inds):
    method _get_previous_values (line 230) | def _get_previous_values(self, name, index):
    method get_saccade (line 240) | def get_saccade(self, index = -1):
    method x_int (line 273) | def x_int(self):
    method y_int (line 278) | def y_int(self):
    method subject_count (line 283) | def subject_count(self):
    method copy (line 286) | def copy(self):
    method FixationsWithoutHistory (line 296) | def FixationsWithoutHistory(cls, x, y, t, n, subject=None, subjects=No...
    method to_hdf5 (line 311) | def to_hdf5(self, target):
    method read_hdf5 (line 335) | def read_hdf5(cls, source):
  class ScanpathFixations (line 390) | class ScanpathFixations(Fixations):
    method __init__ (line 394) | def __init__(self, scanpaths: Scanpaths):
    method copy (line 504) | def copy(self) -> 'ScanpathFixations':
    method concatenate (line 509) | def concatenate(cls, scanpath_fixations: List['ScanpathFixations']) ->...
    method filter_scanpaths (line 513) | def filter_scanpaths(self, indices) -> 'ScanpathFixations':
    method to_hdf5 (line 518) | def to_hdf5(self, target):
    method read_hdf5 (line 529) | def read_hdf5(cls, source):
  class FixationTrains (line 555) | class FixationTrains(ScanpathFixations):
    method __init__ (line 577) | def __init__(self, train_xs, train_ys, train_ts, train_ns, train_subje...
    method from_scanpaths (line 625) | def from_scanpaths(cls, scanpaths: Scanpaths, attributes: Optional[Dic...
    method train_xs (line 637) | def train_xs(self) -> VariableLengthArray:
    method train_ys (line 641) | def train_ys(self) -> VariableLengthArray:
    method train_ts (line 645) | def train_ts(self) -> VariableLengthArray:
    method train_ns (line 649) | def train_ns(self) -> np.ndarray:
    method train_subjects (line 653) | def train_subjects(self) -> VariableLengthArray:
    method train_lengths (line 657) | def train_lengths(self) -> np.ndarray:
    method scanpath_attributes (line 661) | def scanpath_attributes(self) -> Dict[str, np.ndarray]:
    method scanpath_fixation_attributes (line 667) | def scanpath_fixation_attributes(self) -> Dict[str, VariableLengthArray]:
    method scanpath_attribute_mapping (line 673) | def scanpath_attribute_mapping(self) -> Dict[str, str]:
    method non_auto_attributes (line 679) | def non_auto_attributes(self):
    method concatenate (line 684) | def concatenate(cls, fixation_trains: List['FixationTrains']) -> 'Fixa...
    method set_scanpath_attribute (line 696) | def set_scanpath_attribute(self, name, data, fixation_attribute_name=N...
    method copy (line 725) | def copy(self):
    method filter_scanpaths (line 748) | def filter_scanpaths(self, indices):
    method filter_fixation_trains (line 768) | def filter_fixation_trains(self, indices):
    method fixation_trains (line 775) | def fixation_trains(self):
    method from_fixation_trains (line 789) | def from_fixation_trains(cls, xs, ys, ts, ns, subject=None, subjects=N...
    method generate_crossval (line 839) | def generate_crossval(self, splitcount = 10):
    method shuffle_fixations (line 898) | def shuffle_fixations(self, stimuli=None):
    method shuffle_fixation_trains (line 925) | def shuffle_fixation_trains(self, stimuli=None):
    method generate_full_nonfixations (line 952) | def generate_full_nonfixations(self, stimuli=None):
    method generate_nonfixation_partners (line 988) | def generate_nonfixation_partners(self, seed=42):
    method to_hdf5 (line 1015) | def to_hdf5(self, target):
    method read_hdf5 (line 1052) | def read_hdf5(cls, source):
  function _scanpath_from_fixation_index (line 1109) | def _scanpath_from_fixation_index(fixations, fixation_index, scanpath_at...
  function scanpaths_from_fixations (line 1147) | def scanpaths_from_fixations(fixations: Fixations, verbose=False) -> Tup...

FILE: pysaliency/datasets/scanpaths.py
  class Scanpaths (line 10) | class Scanpaths(object):
    method __init__ (line 29) | def __init__(self,
    method _check_lengths (line 91) | def _check_lengths(self, other: VariableLengthArray):
    method _as_variable_length_array (line 97) | def _as_variable_length_array(self, data: Union[np.ndarray, VariableLe...
    method __len__ (line 105) | def __len__(self):
    method ts (line 109) | def ts(self) -> VariableLengthArray:
    method subject (line 113) | def subject(self) -> VariableLengthArray:
    method to_hdf5 (line 118) | def to_hdf5(self, target):
    method read_hdf5 (line 144) | def read_hdf5(cls, source):
    method __getitem__ (line 180) | def __getitem__(self, index):
    method copy (line 196) | def copy(self) -> 'Scanpaths':
    method concatenate (line 203) | def concatenate(cls, scanpaths_list: List['Scanpaths']) -> 'Scanpaths':
  function concatenate_scanpaths (line 207) | def concatenate_scanpaths(scanpaths_list: List[Scanpaths]) -> Scanpaths:

FILE: pysaliency/datasets/stimuli.py
  function get_image_hash (line 20) | def get_image_hash(img):
  class Stimulus (line 31) | class Stimulus(object):
    method __init__ (line 41) | def __init__(self, stimulus_data, stimulus_id = None, shape = None, si...
    method stimulus_id (line 48) | def stimulus_id(self):
    method shape (line 54) | def shape(self):
    method size (line 60) | def size(self):
  function as_stimulus (line 66) | def as_stimulus(img_or_stimulus: Union[np.ndarray, Stimulus]) -> Stimulus:
  class StimuliStimulus (line 73) | class StimuliStimulus(Stimulus):
    method __init__ (line 77) | def __init__(self, stimuli, index):
    method stimulus_data (line 82) | def stimulus_data(self):
    method stimulus_id (line 86) | def stimulus_id(self):
    method shape (line 90) | def shape(self):
    method size (line 94) | def size(self):
  class Stimuli (line 98) | class Stimuli(Sequence):
    method __init__ (line 122) | def __init__(self, stimuli, attributes=None):
    method __len__ (line 139) | def __len__(self):
    method _get_attribute_for_stimulus_subset (line 142) | def _get_attribute_for_stimulus_subset(self, index):
    method __getitem__ (line 152) | def __getitem__(self, index):
    method _propagate_stimulus_ids (line 178) | def _propagate_stimulus_ids(self, sub_stimuli: "Stimuli", index: List[...
    method to_hdf5 (line 184) | def to_hdf5(self, target, verbose=False, compression='gzip', compressi...
    method read_hdf5 (line 200) | def read_hdf5(cls, source):
    method _attributes_to_hdf5 (line 224) | def _attributes_to_hdf5(self, target):
    method _get_attributes_from_hdf5 (line 230) | def _get_attributes_from_hdf5(cls, source, data_version, data_version_...
  class ObjectStimuli (line 251) | class ObjectStimuli(Stimuli):
    method __init__ (line 255) | def __init__(self, stimulus_objects, attributes=None):
    method read_hdf5 (line 274) | def read_hdf5(self, target):
  class FileStimuli (line 278) | class FileStimuli(Stimuli):
    method __init__ (line 282) | def __init__(self, filenames, cached=True, shapes=None, attributes=None):
    method cached (line 337) | def cached(self):
    method cached (line 341) | def cached(self, value):
    method load_stimulus (line 344) | def load_stimulus(self, n):
    method __getitem__ (line 347) | def __getitem__(self, index):
    method to_hdf5 (line 371) | def to_hdf5(self, target):
    method read_hdf5 (line 409) | def read_hdf5(cls, source, cached=True):
  function check_prediction_shape (line 440) | def check_prediction_shape(prediction: np.ndarray, stimulus: Union[np.nd...

FILE: pysaliency/datasets/utils.py
  function hdf5_wrapper (line 26) | def hdf5_wrapper(mode=None):
  function decode_string (line 41) | def decode_string(data):
  function create_hdf5_dataset (line 47) | def create_hdf5_dataset(target, name, data):
  function load_hdf5_dataset (line 64) | def load_hdf5_dataset(source, name):
  function get_merged_attribute_list (line 77) | def get_merged_attribute_list(attributes):
  function _load_attribute_dict_from_hdf5 (line 91) | def _load_attribute_dict_from_hdf5(attribute_group):
  function concatenate_attributes (line 101) | def concatenate_attributes(attributes):

FILE: pysaliency/external_datasets/cat2000.py
  function resource_string (line 14) | def resource_string(package, resource):
  function get_cat2000_test (line 24) | def get_cat2000_test(location=None):
  function get_cat2000_train (line 89) | def get_cat2000_train(location=None, version='1'):
  function _get_cat2000_train (line 136) | def _get_cat2000_train(name, location):
  function _get_cat2000_train_v1_1 (line 260) | def _get_cat2000_train_v1_1(name, location):

FILE: pysaliency/external_datasets/coco_freeview.py
  function get_COCO_Freeview (line 118) | def get_COCO_Freeview(location=None, test_data=None):
  function get_COCO_Freeview_train (line 244) | def get_COCO_Freeview_train(location=None):
  function get_COCO_Freeview_validation (line 249) | def get_COCO_Freeview_validation(location=None):
  function get_COCO_Freeview_test (line 254) | def get_COCO_Freeview_test(location=None):
  function _get_COCO_Freeview_fixations (line 259) | def _get_COCO_Freeview_fixations(json_data, filenames):

FILE: pysaliency/external_datasets/coco_search18.py
  function get_COCO_Search18 (line 26) | def get_COCO_Search18(location=None, split=1, merge_tasks=True, unique_i...
  function _dataset_name (line 170) | def _dataset_name(merge_tasks, unique_images, split):
  function _prepare_stimuli (line 183) | def _prepare_stimuli(source_directory, stimulus_directory, merge_tasks=T...
  function _modify_image (line 230) | def _modify_image(source_filename, target_filename, rst: np.random.Rando...
  function get_COCO_Search18_train (line 252) | def get_COCO_Search18_train(location=None, split=1, merge_tasks=True, un...
  function get_COCO_Search18_validation (line 257) | def get_COCO_Search18_validation(location=None, split=1, merge_tasks=Tru...
  function _get_COCO_Search18_fixations (line 262) | def _get_COCO_Search18_fixations(json_data, filenames, task_in_filename):

FILE: pysaliency/external_datasets/dut_omrom.py
  function get_DUT_OMRON (line 21) | def get_DUT_OMRON(location=None) -> Tuple[Stimuli, ScanpathFixations]:

FILE: pysaliency/external_datasets/figrim.py
  function _load_FIGRIM_data (line 21) | def _load_FIGRIM_data(filename, stimuli_indices, stimulus_type):
  function get_FIGRIM (line 62) | def get_FIGRIM(location=None):

FILE: pysaliency/external_datasets/isun.py
  function get_iSUN (line 15) | def get_iSUN(location=None):
  function get_iSUN_training (line 122) | def get_iSUN_training(location=None):
  function get_iSUN_validation (line 131) | def get_iSUN_validation(location=None):
  function get_iSUN_testing (line 140) | def get_iSUN_testing(location=None):

FILE: pysaliency/external_datasets/koehler.py
  function _get_koehler_fixations (line 20) | def _get_koehler_fixations(data, task, n_stimuli):
  function get_koehler (line 51) | def get_koehler(location=None, datafile=None):

FILE: pysaliency/external_datasets/mit.py
  function resource_string (line 14) | def resource_string(package, resource):
  function _get_mit1003 (line 23) | def _get_mit1003(dataset_name, location=None, include_initial_fixation=F...
  function get_mit1003 (line 283) | def get_mit1003(location=None):
  function get_mit1003_with_initial_fixation (line 319) | def get_mit1003_with_initial_fixation(location=None, replace_initial_inv...
  function get_mit1003_onesize (line 369) | def get_mit1003_onesize(location=None):
  function get_mit300 (line 406) | def get_mit300(location=None):

FILE: pysaliency/external_datasets/nusef.py
  function get_NUSEF_public (line 48) | def get_NUSEF_public(location=None):

FILE: pysaliency/external_datasets/osie.py
  function get_OSIE (line 20) | def get_OSIE(location=None):

FILE: pysaliency/external_datasets/pascal_s.py
  function get_PASCAL_S (line 18) | def get_PASCAL_S(location=None):

FILE: pysaliency/external_datasets/salicon.py
  function get_SALICON (line 17) | def get_SALICON(edition='2015', fixation_type='mouse', location=None):
  function _get_SALICON_name (line 61) | def _get_SALICON_name(edition='2015', fixation_type='mouse'):
  function _get_SALICON_stimuli (line 82) | def _get_SALICON_stimuli(location, name, edition='2015', fixation_type='...
  function _get_SALICON_fixations (line 137) | def _get_SALICON_fixations(location, name, edition='2015', fixation_type...
  function get_SALICON_train (line 219) | def get_SALICON_train(edition='2015', fixation_type='mouse', location=No...
  function get_SALICON_val (line 234) | def get_SALICON_val(edition='2015', fixation_type='mouse', location=None):
  function get_SALICON_test (line 249) | def get_SALICON_test(edition='2015', fixation_type='mouse', location=None):

FILE: pysaliency/external_datasets/toronto.py
  function get_toronto (line 16) | def get_toronto(location=None):
  function get_toronto_with_subjects (line 82) | def get_toronto_with_subjects(location=None):

FILE: pysaliency/external_datasets/utils.py
  function create_memory_stimuli (line 10) | def create_memory_stimuli(filenames, attributes=None):
  function create_stimuli (line 19) | def create_stimuli(stimuli_location, filenames, location=None, attribute...
  function _load (line 55) | def _load(filename):

FILE: pysaliency/external_models/deepgaze.py
  class StaticDeepGazeModel (line 9) | class StaticDeepGazeModel(Model):
    method __init__ (line 10) | def __init__(self, centerbias_model, device=None, *args, **kwargs):
    method _load_model (line 18) | def _load_model(self):
    method _log_density (line 21) | def _log_density(self, stimulus):
  class DeepGazeI (line 38) | class DeepGazeI(StaticDeepGazeModel):
    method __init__ (line 47) | def __init__(self, centerbias_model, device=None, *args, **kwargs):
    method _load_model (line 50) | def _load_model(self):
  class DeepGazeIIE (line 54) | class DeepGazeIIE(StaticDeepGazeModel):
    method __init__ (line 63) | def __init__(self, centerbias_model, device=None, *args, **kwargs):
    method _load_model (line 66) | def _load_model(self):
    method _log_density (line 69) | def _log_density(self, stimulus):

FILE: pysaliency/external_models/matlab_models.py
  function resource_string (line 10) | def resource_string(package, resource):
  class AIM (line 18) | class AIM(ExternalModelMixin, MatlabSaliencyMapModel):
    method __init__ (line 30) | def __init__(self, filters='31jade950', convolve=True, location=None, ...
    method matlab_command (line 36) | def matlab_command(self, stimulus):
    method _setup (line 40) | def _setup(self):
  class SUN (line 57) | class SUN(ExternalModelMixin, MatlabSaliencyMapModel):
    method __init__ (line 84) | def __init__(self, scale=1.0, location=None, **kwargs):
    method matlab_command (line 89) | def matlab_command(self, stimulus):
    method _setup (line 92) | def _setup(self):
  class ContextAwareSaliency (line 110) | class ContextAwareSaliency(ExternalModelMixin, MatlabSaliencyMapModel):
    method __init__ (line 125) | def __init__(self, location=None, **kwargs):
    method _setup (line 129) | def _setup(self):
  class BMS (line 149) | class BMS(ExternalModelMixin, MatlabSaliencyMapModel):
    method __init__ (line 175) | def __init__(self, location=None, **kwargs):
    method _setup (line 179) | def _setup(self):
  class GBVS (line 202) | class GBVS(ExternalModelMixin, MatlabSaliencyMapModel):
    method __init__ (line 220) | def __init__(self, location=None, **kwargs):
    method _setup (line 274) | def _setup(self):
  class GBVSIttiKoch (line 290) | class GBVSIttiKoch(ExternalModelMixin, MatlabSaliencyMapModel):
    method __init__ (line 308) | def __init__(self, location=None, **kwargs):
    method _setup (line 312) | def _setup(self):
  class Judd (line 326) | class Judd(ExternalModelMixin, MatlabSaliencyMapModel):
    method __init__ (line 349) | def __init__(self, location=None, saliency_toolbox_archive=None, inclu...
    method _setup (line 353) | def _setup(self, saliency_toolbox_archive, include_locations, library_...
  class IttiKoch (line 421) | class IttiKoch(ExternalModelMixin, MatlabSaliencyMapModel):
    method __init__ (line 444) | def __init__(self, location=None, saliency_toolbox_archive=None, **kwa...
    method _setup (line 448) | def _setup(self, saliency_toolbox_archive):
  class RARE2007 (line 462) | class RARE2007(ExternalModelMixin, MatlabSaliencyMapModel):
    method __init__ (line 479) | def __init__(self, location=None, **kwargs):
    method _setup (line 483) | def _setup(self):
  class RARE2012 (line 498) | class RARE2012(ExternalModelMixin, MatlabSaliencyMapModel):
    method __init__ (line 532) | def __init__(self, location=None, **kwargs):
    method _setup (line 536) | def _setup(self):
  class CovSal (line 551) | class CovSal(ExternalModelMixin, MatlabSaliencyMapModel):
    method __init__ (line 567) | def __init__(self, location=None, size=512, quantile=0.1, centerbias=T...
    method matlab_command (line 595) | def matlab_command(self, stimulus):
    method _setup (line 601) | def _setup(self):

FILE: pysaliency/external_models/models.py
  function resource_string (line 10) | def resource_string(package, resource):
  function resource_listdir (line 14) | def resource_listdir(package, resource_name):

FILE: pysaliency/external_models/utils.py
  function resource_string (line 12) | def resource_string(package, resource):
  function resource_listdir (line 16) | def resource_listdir(package, resource_name):
  function write_file (line 23) | def write_file(filename, contents):
  function extract_zipfile (line 29) | def extract_zipfile(filename, extract_to):
  function unpack_directory (line 41) | def unpack_directory(package, resource_name, location):
  function apply_quilt (line 48) | def apply_quilt(source_location, package, resource_name, patch_directory...
  function download_extract_patch (line 56) | def download_extract_patch(url, hash, location, location_in_archive=True...
  class ExternalModelMixin (line 80) | class ExternalModelMixin(object):
    method setup (line 96) | def setup(self, location, *args, **kwargs):
    method _setup (line 105) | def _setup(self, *args, **kwargs):

FILE: pysaliency/filter_datasets.py
  function train_split (line 10) | def train_split(stimuli, fixations, crossval_folds, fold_no, val_folds=1...
  function validation_split (line 14) | def validation_split(stimuli, fixations, crossval_folds, fold_no, val_fo...
  function test_split (line 18) | def test_split(stimuli, fixations, crossval_folds, fold_no, val_folds=1,...
  function crossval_splits (line 22) | def crossval_splits(stimuli, fixations, crossval_folds, fold_no, val_fol...
  function crossval_split (line 30) | def crossval_split(stimuli, fixations, crossval_folds, fold_no, val_fold...
  function _get_crossval_split (line 45) | def _get_crossval_split(stimuli, fixations, split_count, included_splits...
  function _get_stratified_crossval_split (line 66) | def _get_stratified_crossval_split(stimuli, fixations, split_count, incl...
  function create_train_folds (line 102) | def create_train_folds(crossval_folds, val_folds, test_folds):
  function get_crossval_folds (line 114) | def get_crossval_folds(crossval_folds, crossval_no, test_folds=1, val_fo...
  function iterate_crossvalidation (line 131) | def iterate_crossvalidation(stimuli, fixations, crossval_folds, val_fold...
  function parse_list_of_intervals (line 160) | def parse_list_of_intervals(description):
  function parse_interval (line 167) | def parse_interval(interval):
  function filter_fixations_by_number (line 174) | def filter_fixations_by_number(fixations, intervals):
  function filter_stimuli_by_number (line 185) | def filter_stimuli_by_number(stimuli, fixations, intervals):
  function _check_intervals (line 197) | def _check_intervals(intervals, type=float):
  function _check_interval (line 207) | def _check_interval(interval, type=float):
  function filter_stimuli_by_size (line 227) | def filter_stimuli_by_size(stimuli, fixations, size=None, sizes=None):
  function filter_scanpaths_by_attribute (line 237) | def filter_scanpaths_by_attribute(scanpaths: ScanpathFixations, attribut...
  function filter_fixations_by_attribute (line 250) | def filter_fixations_by_attribute(fixations: Fixations, attribute_name, ...
  function filter_stimuli_by_attribute (line 263) | def filter_stimuli_by_attribute(stimuli: Stimuli, fixations: Fixations, ...
  function filter_scanpaths_by_length (line 283) | def filter_scanpaths_by_length(scanpath_fixations: ScanpathFixations, in...
  function remove_stimuli_without_fixations (line 299) | def remove_stimuli_without_fixations(stimuli: Stimuli, fixations: Fixati...

FILE: pysaliency/hdf5.py
  function _read_hdf5_from_file (line 13) | def _read_hdf5_from_file(source, **kwargs):
  function _read_baseline_model (line 19) | def _read_baseline_model(source, **kwargs):
  function _read_crossvalidated_baseline_model (line 24) | def _read_crossvalidated_baseline_model(source, **kwargs):
  function read_hdf5 (line 44) | def read_hdf5(source, hdf5_kwargs=None, _expected_kind=None, **kwargs):

FILE: pysaliency/http_models.py
  class HTTPScanpathModel (line 12) | class HTTPScanpathModel(ScanpathModel):
    method __init__ (line 22) | def __init__(self, url):
    method log_density_url (line 27) | def log_density_url(self):
    method type_url (line 31) | def type_url(self):
    method conditional_log_density (line 34) | def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, at...
    method check_type (line 69) | def check_type(self):
  class HTTPScanpathSaliencyMapModel (line 77) | class HTTPScanpathSaliencyMapModel(ScanpathSaliencyMapModel):
    method __init__ (line 87) | def __init__(self, url):
    method saliency_map_url (line 92) | def saliency_map_url(self):
    method type_url (line 96) | def type_url(self):
    method conditional_saliency_map (line 99) | def conditional_saliency_map(self, stimulus, x_hist, y_hist, t_hist, a...
    method check_type (line 134) | def check_type(self):

FILE: pysaliency/metric_optimization.py
  class SIMSaliencyMapModel (line 6) | class SIMSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 7) | def __init__(self, parent_model,
    method _saliency_map (line 53) | def _saliency_map(self, stimulus):

FILE: pysaliency/metric_optimization_tf.py
  function sample_batch_fixations (line 14) | def sample_batch_fixations(log_density, fixations_per_image, batch_size,...
  function _eval_metric (line 21) | def _eval_metric(log_density, test_samples, fn, seed=42, fixation_count=...
  function constrained_descent (line 41) | def constrained_descent(opt, loss, params, constraint, learning_rate):
  function build_fixation_maps (line 78) | def build_fixation_maps(Ns, Ys, Xs, batch_size, height, width, dtype=tf....
  function tf_similarity (line 87) | def tf_similarity(saliency_map, empirical_saliency_maps):
  function build_similarity_graph (line 97) | def build_similarity_graph(saliency_map, ns, ys, xs, batch_size, height,...
  function maximize_expected_sim (line 113) | def maximize_expected_sim(log_density, kernel_size,

FILE: pysaliency/metric_optimization_torch.py
  class DistributionSGD (line 13) | class DistributionSGD(torch.optim.Optimizer):
    method __init__ (line 17) | def __init__(self, params, lr=required):
    method step (line 25) | def step(self, closure=None):
  function build_fixation_maps (line 61) | def build_fixation_maps(Ns, Ys, Xs, batch_size, height, width, dtype=tor...
  function torch_similarity (line 69) | def torch_similarity(saliency_map, empirical_saliency_maps):
  function compute_similarity (line 79) | def compute_similarity(saliency_map, ns, ys, xs, batch_size, kernel_size...
  class Similarities (line 97) | class Similarities(nn.Module):
    method __init__ (line 98) | def __init__(self, initial_saliency_map, kernel_size, truncate_gaussia...
    method forward (line 106) | def forward(self, ns, ys, xs, batch_size):
  function _eval_metric (line 119) | def _eval_metric(log_density_sampler, test_samples, fn, seed=42, fixatio...
  function maximize_expected_sim (line 139) | def maximize_expected_sim(log_density, kernel_size,

FILE: pysaliency/metrics.py
  function normalize_saliency_map (line 6) | def normalize_saliency_map(saliency_map, cdf, cdf_bins):
  function convert_saliency_map_to_density (line 23) | def convert_saliency_map_to_density(saliency_map, minimum_value=0.0):
  function NSS (line 38) | def NSS(saliency_map, xs, ys):
  function CC (line 55) | def CC(saliency_map_1, saliency_map_2):
  function probabilistic_image_based_kl_divergence (line 75) | def probabilistic_image_based_kl_divergence(logp1, logp2, log_regulariza...
  function image_based_kl_divergence (line 82) | def image_based_kl_divergence(saliency_map_1, saliency_map_2, minimum_va...
  function MIT_KLDiv (line 90) | def MIT_KLDiv(saliency_map_1, saliency_map_2):
  function SIM (line 101) | def SIM(saliency_map_1, saliency_map_2):

FILE: pysaliency/models.py
  function _prepare_logprobabilities_for_sampling (line 27) | def _prepare_logprobabilities_for_sampling(log_probabilities):
  function _sample_from_cumsums (line 41) | def _sample_from_cumsums(cumsums, ndxs, valid_indices, size, rst=None):
  function sample_from_logprobabilities (line 53) | def sample_from_logprobabilities(log_probabilities, size=1, rst=None):
  function sample_from_logdensity (line 65) | def sample_from_logdensity(log_density, count=None, rst=None):
  class LogDensitySampler (line 83) | class LogDensitySampler(object):
    method __init__ (line 87) | def __init__(self, log_density):
    method sample (line 92) | def sample(self, size, rst=None):
    method sample_batch_fixations (line 99) | def sample_batch_fixations(self, fixations_per_image, batch_size, rst=...
  function sample_from_image (line 106) | def sample_from_image(densities, count=None, rst=None):
  class ScanpathModel (line 131) | class ScanpathModel(SamplingModelMixin, object, metaclass=ABCMeta):
    method conditional_log_density (line 139) | def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, at...
    method conditional_log_density_for_fixation (line 142) | def conditional_log_density_for_fixation(self, stimuli, fixations, fix...
    method conditional_log_densities (line 152) | def conditional_log_densities(self, stimuli, fixations, verbose=False,...
    method log_likelihoods (line 158) | def log_likelihoods(self, stimuli, fixations, verbose=False):
    method log_likelihood (line 166) | def log_likelihood(self, stimuli, fixations, verbose=False, average='f...
    method information_gains (line 169) | def information_gains(self, stimuli, fixations, baseline_model=None, v...
    method information_gain (line 179) | def information_gain(self, stimuli, fixations, baseline_model=None, ve...
    method _expand_sample_arguments (line 182) | def _expand_sample_arguments(self, stimuli, train_counts, lengths=None...
    method sample (line 218) | def sample(self, stimuli, train_counts, lengths=1, stimulus_indices=No...
    method _sample_fixation_train (line 269) | def _sample_fixation_train(self, stimulus, length, rst=None):
    method sample_fixation (line 273) | def sample_fixation(self, stimulus, x_hist, y_hist, t_hist, attributes...
  class Model (line 279) | class Model(ScanpathModel):
    method __init__ (line 285) | def __init__(self, cache_location=None, caching=True, memory_cache_siz...
    method cache_location (line 294) | def cache_location(self):
    method cache_location (line 298) | def cache_location(self, value):
    method conditional_log_density (line 301) | def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, at...
    method log_density (line 304) | def log_density(self, stimulus):
    method _log_density (line 320) | def _log_density(self, stimulus):
    method log_likelihoods (line 332) | def log_likelihoods(self, stimuli, fixations, verbose=False):
    method _sample_fixation_train (line 345) | def _sample_fixation_train(self, stimulus, length, rst=None):
    method pixel_space_information_gain (line 354) | def pixel_space_information_gain(self, baseline, gold_standard, stimul...
    method kl_divergences (line 363) | def kl_divergences(self, stimuli, gold_standard, log_regularization=0,...
    method set_params (line 388) | def set_params(self, **kwargs):
  class CachedModel (line 398) | class CachedModel(Model):
    method __init__ (line 401) | def __init__(self, cache_location, **kwargs):
    method _log_density (line 406) | def _log_density(self, stimulus):
  class UniformModel (line 410) | class UniformModel(Model):
    method _log_density (line 413) | def _log_density(self, stimulus):
    method log_likelihoods (line 416) | def log_likelihoods(self, stimuli, fixations, verbose=False):
  class MixtureModel (line 428) | class MixtureModel(Model):
    method __init__ (line 431) | def __init__(self, models, weights=None, check_norm=True, **kwargs):
    method _log_density (line 450) | def _log_density(self, stimulus):
  class MixtureScanpathModel (line 467) | class MixtureScanpathModel(ScanpathModel):
    method __init__ (line 470) | def __init__(self, models, weights=None, check_norm=True, **kwargs):
    method conditional_log_density (line 489) | def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, at...
  class ResizingModel (line 504) | class ResizingModel(Model):
    method __init__ (line 505) | def __init__(self, parent_model, verbose=True, **kwargs):
    method _log_density (line 512) | def _log_density(self, stimulus):
  class ResizingScanpathModel (line 533) | class ResizingScanpathModel(ScanpathModel):
    method __init__ (line 534) | def __init__(self, parent_model, verbose=True, **kwargs):
    method conditional_log_density (line 539) | def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, at...
  class DisjointUnionModel (line 560) | class DisjointUnionModel(DisjointUnionMixin, ScanpathModel):
    method conditional_log_density (line 561) | def conditional_log_density(self, stimulus, *args, **kwargs):
    method log_likelihoods (line 564) | def log_likelihoods(self, stimuli, fixations, **kwargs):
  class SubjectDependentModel (line 568) | class SubjectDependentModel(DisjointUnionModel):
    method __init__ (line 569) | def __init__(self, subject_models, **kwargs):
    method _split_fixations (line 573) | def _split_fixations(self, stimuli, fixations):
    method conditional_log_density (line 577) | def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, at...
    method get_saliency_map_model_for_sAUC (line 583) | def get_saliency_map_model_for_sAUC(self, baseline_model):
    method get_saliency_map_model_for_NSS (line 588) | def get_saliency_map_model_for_NSS(self):
  class StimulusDependentModel (line 594) | class StimulusDependentModel(Model):
    method __init__ (line 595) | def __init__(self, stimuli_models, check_stimuli=True, fallback_model=...
    method check_stimuli (line 602) | def check_stimuli(self):
    method _log_density (line 607) | def _log_density(self, stimulus):
  class StimulusDependentScanpathModel (line 619) | class StimulusDependentScanpathModel(ScanpathModel):
    method __init__ (line 620) | def __init__(self, stimuli_models, check_stimuli=True, fallback_model=...
    method check_stimuli (line 627) | def check_stimuli(self):
    method conditional_log_density (line 632) | def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, at...
  class FixationIndexDependentModel (line 644) | class FixationIndexDependentModel(ScanpathModel):
    method __init__ (line 646) | def __init__(self, models, *args, **kwargs):
    method _get_model_for_index (line 651) | def _get_model_for_index(self, fixation_index):
    method conditional_log_density (line 657) | def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, at...
  class ShuffledAUCSaliencyMapModel (line 662) | class ShuffledAUCSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 663) | def __init__(self, probabilistic_model, baseline_model):
    method _saliency_map (line 668) | def _saliency_map(self, stimulus):
  class ShuffledAUCScanpathSaliencyMapModel (line 672) | class ShuffledAUCScanpathSaliencyMapModel(ScanpathSaliencyMapModel):
    method __init__ (line 673) | def __init__(self, probabilistic_model: ScanpathModel, baseline_model:...
    method conditional_saliency_map (line 678) | def conditional_saliency_map(self, stimulus, x_hist, y_hist, t_hist, a...
  function average_predictions (line 685) | def average_predictions(log_densities, log_density_count=None, maximal_c...
  function logsumexp_iterator (line 716) | def logsumexp_iterator(log_density_iterator, iterator_length, maximal_ch...
  class ShuffledBaselineModel (line 756) | class ShuffledBaselineModel(Model):
    method __init__ (line 772) | def __init__(self, parent_model, stimuli,
    method get_average_prediction (line 793) | def get_average_prediction(self, verbose=False):
    method _resize_prediction (line 812) | def _resize_prediction(self, prediction, target_shape):
    method _log_density (line 834) | def _log_density(self, stimulus):
  class GaussianModel (line 858) | class GaussianModel(Model):
    method __init__ (line 859) | def __init__(self, width=0.5, center_x=0.5, center_y=0.5, **kwargs):
    method _log_density (line 863) | def _log_density(self, stimulus):
  class SaliencyMapNormalizingModel (line 870) | class SaliencyMapNormalizingModel(Model):
    method __init__ (line 874) | def __init__(self, model, minimum_value=0.0):
    method _log_density (line 879) | def _log_density(self, stimulus):
  class FixedStimulusSizeModel (line 884) | class FixedStimulusSizeModel(Model):
    method __init__ (line 886) | def __init__(self, size, parent_model, verbose=False, **kwargs):
    method _log_density (line 893) | def _log_density(self, stimulus):
    method ensure_color (line 923) | def ensure_color(self, image):
  class DVAAwareModel (line 929) | class DVAAwareModel(Model):
    method __init__ (line 935) | def __init__(self, dva, parent_model, parent_model_dva, verbose=False,...
    method _log_density (line 946) | def _log_density(self, stimulus):
    method ensure_color (line 978) | def ensure_color(self, image):
  class DVAAwareScanpathModel (line 984) | class DVAAwareScanpathModel(ScanpathModel):
    method __init__ (line 990) | def __init__(self, dva: float, parent_model: ScanpathModel, parent_mod...
    method conditional_log_density (line 1001) | def conditional_log_density(self, stimulus, x_hist, y_hist, t_hist, at...
    method ensure_color (line 1064) | def ensure_color(self, image):

FILE: pysaliency/numba_utils.py
  function fill_fixation_map (line 11) | def fill_fixation_map(fixation_map, fixations, check_bounds=True):
  function _fill_fixation_map (line 23) | def _fill_fixation_map(fixation_map, fixations):
  function auc_for_one_positive (line 30) | def auc_for_one_positive(positive, negatives):
  function _auc_for_one_positive (line 40) | def _auc_for_one_positive(positive, negatives):
  function general_roc_numba (line 56) | def general_roc_numba(positives, negatives, judd=0):
  function _general_roc_numba (line 78) | def _general_roc_numba(all_values, sorted_positives, sorted_negatives, f...
  function general_rocs_per_positive_numba (line 98) | def general_rocs_per_positive_numba(positives, negatives):
  function _general_rocs_per_positive_numba (line 110) | def _general_rocs_per_positive_numba(sorted_positives, sorted_negatives,...

FILE: pysaliency/optpy/jacobian.py
  class FunctionWithApproxJacobian (line 11) | class FunctionWithApproxJacobian(object):
    method __init__ (line 12) | def __init__(self, func, epsilon, verbose=True):
    method __call__ (line 18) | def __call__(self, x, *args, **kwargs):
    method func (line 28) | def func(self, x, *args, **kwargs):
    method log (line 33) | def log(self, msg):
    method jac (line 38) | def jac(self, x, *args, **kwargs):
  class FunctionWithApproxJacobianCentral (line 53) | class FunctionWithApproxJacobianCentral(FunctionWithApproxJacobian):
    method jac (line 54) | def jac(self, x, *args, **kwargs):

FILE: pysaliency/optpy/optimization.py
  class LinearConstraint (line 16) | class LinearConstraint(object):
    method __init__ (line 18) | def __init__(self, A_dict, lb=-np.inf, ub=np.inf, keep_feasible=False):
  class ParameterManager (line 25) | class ParameterManager(object):
    method __init__ (line 26) | def __init__(self, parameters, optimize, **kwargs):
    method extract_parameters (line 38) | def extract_parameters(self, x, return_list=False):
    method build_vector (line 60) | def build_vector(self, **kwargs):
    method get_length (line 69) | def get_length(self, param_name):
  class KeywordParameterManager (line 83) | class KeywordParameterManager(ParameterManager):
    method __init__ (line 84) | def __init__(self, initial_dict, optimize):
  function wrap_parameter_manager (line 95) | def wrap_parameter_manager(f, parameter_manager, additional_kwargs=None):
  function wrap_linear_constraint (line 117) | def wrap_linear_constraint(constraint: LinearConstraint, parameter_manag...
  function minimize (line 157) | def minimize(f, parameter_manager_or_x0, optimize=None, args=(), kwargs=...
  function testfunc (line 251) | def testfunc(x):

FILE: pysaliency/plotting.py
  function plot_information_gain (line 20) | def plot_information_gain(information_gain, ax=None, color_range = None,...
  function normalize_log_density (line 145) | def normalize_log_density(log_density):
  function visualize_distribution (line 156) | def visualize_distribution(log_densities, ax=None, levels=None, level_co...
  function advanced_arrow (line 169) | def advanced_arrow(x, y, dx, dy, linewidth=1.0, headwidth=3.0, headlengt...
  function plot_fixation (line 226) | def plot_fixation(
  function plot_scanpath (line 277) | def plot_scanpath(
  function _plot_fixation (line 319) | def _plot_fixation(

FILE: pysaliency/precomputed_models.py
  function get_stimuli_filenames (line 23) | def get_stimuli_filenames(stimuli):
  class NonUniqueKeysError (line 33) | class NonUniqueKeysError(ValueError):
  class NoCommonPrefixError (line 36) | class NoCommonPrefixError(ValueError):
  function get_keys_from_filenames (line 40) | def get_keys_from_filenames(filenames, keys, verbose_error=True):
  function get_keys_from_filenames_with_prefix (line 75) | def get_keys_from_filenames_with_prefix(filenames, keys):
  function remove_initial_key_parts (line 118) | def remove_initial_key_parts(keys, key_part_index):
  function _effective_dtype (line 130) | def _effective_dtype(smap, dtype, downscale_factor):
  function _downsample_smap (line 143) | def _downsample_smap(smap, k):
  function _check_size_guard (line 156) | def _check_size_guard(smap, effective_stored_dtype, downscale_factor, dt...
  function _validate_append_consistency (line 173) | def _validate_append_consistency(f, downscale_factor):
  function export_model_to_hdf5 (line 183) | def export_model_to_hdf5(model, stimuli, filename, compression=9, overwr...
  class SaliencyMapModelFromFiles (line 264) | class SaliencyMapModelFromFiles(SaliencyMapModel):
    method __init__ (line 265) | def __init__(self, stimuli, files, **kwargs):
    method _saliency_map (line 272) | def _saliency_map(self, stimulus):
    method _file_for_stimulus (line 276) | def _file_for_stimulus(self, stimulus):
    method _load_file (line 286) | def _load_file(self, filename):
  class SaliencyMapModelFromDirectory (line 304) | class SaliencyMapModelFromDirectory(SaliencyMapModelFromFiles):
    method __init__ (line 305) | def __init__(self, stimuli, directory, **kwargs):
  class SaliencyMapModelFromFile (line 327) | class SaliencyMapModelFromFile(SaliencyMapModel):
    method __init__ (line 333) | def __init__(self, stimuli, filename, key='results', **kwargs):
    method load_matlab (line 343) | def load_matlab(self, filename, key='results'):
    method _saliency_map (line 360) | def _saliency_map(self, stimulus):
  class ModelFromDirectory (line 369) | class ModelFromDirectory(Model):
    method __init__ (line 370) | def __init__(self, stimuli, directory, **kwargs):
    method _log_density (line 374) | def _log_density(self, stimulus):
  function get_keys_recursive (line 381) | def get_keys_recursive(group, prefix=''):
  class HDF5SaliencyMapModel (line 396) | class HDF5SaliencyMapModel(SaliencyMapModel):
    method __init__ (line 405) | def __init__(self, stimuli, filename, check_shape=True, **kwargs):
    method _key_for_stimulus (line 422) | def _key_for_stimulus(self, stimulus):
    method _saliency_map (line 427) | def _saliency_map(self, stimulus):
  class HDF5Model (line 444) | class HDF5Model(Model):
    method __init__ (line 452) | def __init__(self, stimuli, filename, check_shape=True,
    method _log_density (line 463) | def _log_density(self, stimulus):
  class TarFileLikeZipFile (line 489) | class TarFileLikeZipFile(object):
    method __init__ (line 491) | def __init__(self, filename, *args, **kwargs):
    method namelist (line 494) | def namelist(self):
    method open (line 500) | def open(self, name, mode='r'):
  class PredictionsFromArchiveMixin (line 504) | class PredictionsFromArchiveMixin(object):
    method __init__ (line 505) | def __init__(self, stimuli, archive_file, *args, **kwargs):
    method _prediction (line 549) | def _prediction(self, stimulus):
    method _load_file (line 555) | def _load_file(self, filename):
    method can_handle (line 576) | def can_handle(filename):
  class SaliencyMapModelFromArchive (line 585) | class SaliencyMapModelFromArchive(PredictionsFromArchiveMixin, SaliencyM...
    method __init__ (line 586) | def __init__(self, stimuli, archive_file, **kwargs):
    method _saliency_map (line 589) | def _saliency_map(self, stimulus):
  class ModelFromArchive (line 593) | class ModelFromArchive(PredictionsFromArchiveMixin, Model):
    method __init__ (line 594) | def __init__(self, stimuli, archive_file, **kwargs):
    method _log_density (line 597) | def _log_density(self, stimulus):

FILE: pysaliency/quilt.py
  class Hunk (line 21) | class Hunk(object):
    method __init__ (line 22) | def __init__(self, lines):
    method parse_position (line 39) | def parse_position(self, position):
    method apply (line 45) | def apply(self, source, target):
  class Diff (line 69) | class Diff(object):
    method __init__ (line 70) | def __init__(self, lines):
    method apply (line 93) | def apply(self, location):
  class PatchFile (line 110) | class PatchFile(object):
    method __init__ (line 111) | def __init__(self, patch):
    method apply (line 126) | def apply(self, location, verbose=True):
  class QuiltSeries (line 133) | class QuiltSeries(object):
    method __init__ (line 134) | def __init__(self, patches_location):
    method apply (line 146) | def apply(self, location, verbose=True):

FILE: pysaliency/saliency_map_conversion.py
  function optimize_for_information_gain (line 9) | def optimize_for_information_gain(

FILE: pysaliency/saliency_map_conversion_theano.py
  function optimize_for_information_gain (line 15) | def optimize_for_information_gain(
  function optimize_saliency_map_conversion (line 60) | def optimize_saliency_map_conversion(saliency_map_processing, saliency_m...
  class SaliencyMapConvertor (line 301) | class SaliencyMapConvertor(Model):
    method __init__ (line 305) | def __init__(self, saliency_map_model, nonlinearity=None, centerbias=N...
    method set_params (line 356) | def set_params(self, **kwargs):
    method _build (line 378) | def _build(self):
    method _prepare_saliency_map (line 391) | def _prepare_saliency_map(self, saliency_map):
    method _log_density (line 403) | def _log_density(self, stimulus):
    method fit (line 409) | def fit(self, stimuli, fixations, optimize=None, verbose=0, baseline_m...
    method __getstate__ (line 460) | def __getstate__(self):
    method __setstate__ (line 471) | def __setstate__(self, state):
  class JointSaliencyMapConvertor (line 476) | class JointSaliencyMapConvertor(object):
    method __init__ (line 477) | def __init__(self, saliency_map_models, nonlinearity=None, centerbias=...
    method set_params (line 517) | def set_params(self, **kwargs):
    method _build (line 531) | def _build(self):
    method _prepare_saliency_map (line 543) | def _prepare_saliency_map(self, saliency_map):
    method fit (line 555) | def fit(self, stimuli, fixations, optimize=None, verbose=0, baseline_m...
    method __getstate__ (line 612) | def __getstate__(self):
    method __setstate__ (line 623) | def __setstate__(self, state):

FILE: pysaliency/saliency_map_conversion_torch.py
  class CenterBias (line 13) | class CenterBias(nn.Module):
    method __init__ (line 14) | def __init__(self, ys=None, num_values=12):
    method forward (line 22) | def forward(self, tensor):
  class SaliencyMapProcessing (line 43) | class SaliencyMapProcessing(nn.Module):
    method __init__ (line 44) | def __init__(self, num_nonlinearity=20, num_centerbias=12, blur_radius...
    method forward (line 68) | def forward(self, tensor):
  class NormalizedSaliencyMapModel (line 96) | class NormalizedSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 97) | def __init__(self, parent_model, saliency_min=None, saliency_max=None,...
    method _saliency_map (line 103) | def _saliency_map(self, stimulus):
  function run_dataset (line 120) | def run_dataset(model, dataset, device, verbose=True):
  function optimize_saliency_map_conversion (line 161) | def optimize_saliency_map_conversion(
  function _optimize_saliency_map_conversion_over_multiple_models_and_datasets (line 233) | def _optimize_saliency_map_conversion_over_multiple_models_and_datasets(
  function _optimize_saliency_map_processing (line 309) | def _optimize_saliency_map_processing(
  class SaliencyMapProcessingModel (line 483) | class SaliencyMapProcessingModel(Model):
    method __init__ (line 484) | def __init__(
    method _log_density (line 524) | def _log_density(self, stimulus):
    method state_dict (line 529) | def state_dict(self):
    method build_from_state_dict (line 553) | def build_from_state_dict(cls, saliency_map_model, state_dict, device=...

FILE: pysaliency/saliency_map_models.py
  function handle_stimulus (line 24) | def handle_stimulus(stimulus):
  function normalize_saliency_map (line 33) | def normalize_saliency_map(saliency_map, cdf, cdf_bins):
  class FullShuffledNonfixationProvider (line 50) | class FullShuffledNonfixationProvider(object):
    method __init__ (line 51) | def __init__(self, stimuli, fixations, max_fixations_in_cache=500*1000...
    method _nonfixations_for_image (line 60) | def _nonfixations_for_image(self, n):
    method __call__ (line 71) | def __call__(self, stimuli, fixations, i):
  function _get_unfixated_values (line 77) | def _get_unfixated_values(saliency_map, ys, xs):
  class ScanpathSaliencyMapModel (line 87) | class ScanpathSaliencyMapModel(object, metaclass=ABCMeta):
    method conditional_saliency_map (line 95) | def conditional_saliency_map(self, stimulus, x_hist, y_hist, t_hist, a...
    method conditional_saliency_map_for_fixation (line 102) | def conditional_saliency_map_for_fixation(self, stimuli, fixations, fi...
    method conditional_saliency_maps (line 112) | def conditional_saliency_maps(self, stimuli, fixations, verbose=False,...
    method AUCs (line 118) | def AUCs(self, stimuli, fixations, nonfixations='uniform', verbose=Fal...
    method AUC (line 183) | def AUC(self, stimuli, fixations, nonfixations='uniform', average='fix...
    method sAUCs (line 214) | def sAUCs(self, stimuli, fixations, verbose=False):
    method sAUC (line 217) | def sAUC(self, stimuli, fixations, average='fixation', verbose=False):
    method NSSs (line 220) | def NSSs(self, stimuli, fixations, verbose=False):
    method NSS (line 228) | def NSS(self, stimuli, fixations, average='fixation', verbose=False):
    method set_params (line 232) | def set_params(self, **kwargs):
  class SaliencyMapModel (line 242) | class SaliencyMapModel(ScanpathSaliencyMapModel):
    method __init__ (line 249) | def __init__(self, cache_location = None, caching=True,
    method cache_location (line 255) | def cache_location(self):
    method cache_location (line 259) | def cache_location(self, value):
    method saliency_map (line 262) | def saliency_map(self, stimulus):
    method _saliency_map (line 278) | def _saliency_map(self, stimulus):
    method conditional_saliency_map (line 290) | def conditional_saliency_map(self, stimulus, *args, **kwargs):
    method AUCs (line 293) | def AUCs(self, stimuli, fixations, nonfixations='uniform', verbose=Fal...
    method AUC_per_image (line 361) | def AUC_per_image(self, stimuli, fixations, nonfixations='uniform', th...
    method AUC (line 442) | def AUC(self, stimuli, fixations, nonfixations='uniform', average='fix...
    method AUC_Judd (line 501) | def AUC_Judd(self, stimuli, fixations, jitter=True, noise_size=1.0/100...
    method fixation_based_KL_divergence (line 519) | def fixation_based_KL_divergence(self, stimuli, fixations, nonfixation...
    method image_based_kl_divergences (line 592) | def image_based_kl_divergences(self, stimuli, gold_standard, minimum_v...
    method image_based_kl_divergence (line 623) | def image_based_kl_divergence(self, stimuli, gold_standard, minimum_va...
    method KLDivs (line 635) | def KLDivs(self, *args, **kwargs):
    method KLDiv (line 639) | def KLDiv(self, *args, **kwargs):
    method CCs (line 643) | def CCs(self, stimuli, other, verbose=False):
    method CC (line 662) | def CC(self, stimuli, other, verbose=False):
    method NSSs (line 665) | def NSSs(self, stimuli, fixations, verbose=False):
    method SIMs (line 677) | def SIMs(self, stimuli, other, verbose=False):
    method SIM (line 696) | def SIM(self, stimuli, other, verbose=False):
    method __add__ (line 699) | def __add__(self, other):
    method __sub__ (line 705) | def __sub__(self, other):
    method __mul__ (line 711) | def __mul__(self, other):
    method __truediv__ (line 717) | def __truediv__(self, other):
  class CachedSaliencyMapModel (line 724) | class CachedSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 727) | def __init__(self, cache_location, **kwargs):
    method _saliency_map (line 732) | def _saliency_map(self, stimulus):
  class MatlabSaliencyMapModel (line 736) | class MatlabSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 748) | def __init__(self, script_file, stimulus_ext = '.png', saliency_map_ex...
    method matlab_command (line 779) | def matlab_command(self, stimulus):
    method _saliency_map (line 797) | def _saliency_map(self, stimulus):
  class GaussianSaliencyMapModel (line 826) | class GaussianSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 828) | def __init__(self, width=0.5, center_x=0.5, center_y=0.5, **kwargs):
    method _saliency_map (line 834) | def _saliency_map(self, stimulus):
  class FixationMap (line 846) | class FixationMap(SaliencyMapModel):
    method __init__ (line 858) | def __init__(self, stimuli, fixations, kernel_size=None, convolution_m...
    method _saliency_map (line 872) | def _saliency_map(self, stimulus):
  class ResizingSaliencyMapModel (line 889) | class ResizingSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 890) | def __init__(self, parent_model, verbose=True, **kwargs):
    method _saliency_map (line 897) | def _saliency_map(self, stimulus):
  class DisjointUnionMixin (line 916) | class DisjointUnionMixin(object):
    method _split_fixations (line 917) | def _split_fixations(self, stimuli, fixations):
    method eval_metric (line 922) | def eval_metric(self, metric_name, stimuli, fixations, **kwargs):
  class DisjointUnionSaliencyMapModel (line 937) | class DisjointUnionSaliencyMapModel(DisjointUnionMixin, ScanpathSaliency...
    method AUCs (line 938) | def AUCs(self, stimuli, fixations, **kwargs):
    method AUC (line 941) | def AUC(self, stimuli, fixations, **kwargs):
    method NSSs (line 947) | def NSSs(self, stimuli, fixations, **kwargs):
  class SubjectDependentSaliencyMapModel (line 951) | class SubjectDependentSaliencyMapModel(DisjointUnionSaliencyMapModel):
    method __init__ (line 952) | def __init__(self, subject_models, **kwargs):
    method _split_fixations (line 956) | def _split_fixations(self, stimuli, fixations):
    method conditional_saliency_map (line 960) | def conditional_saliency_map(self, stimulus, x_hist, y_hist, t_hist, a...
  class StimulusDependentSaliencyMapModel (line 967) | class StimulusDependentSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 968) | def __init__(self, stimuli_models, check_stimuli=True, fallback_model=...
    method check_stimuli (line 975) | def check_stimuli(self):
    method _saliency_map (line 980) | def _saliency_map(self, stimulus):
  class ExpSaliencyMapModel (line 992) | class ExpSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 993) | def __init__(self, parent_model):
    method _saliency_map (line 997) | def _saliency_map(self, stimulus):
  class BluringSaliencyMapModel (line 1001) | class BluringSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 1002) | def __init__(self, parent_model, kernel_size, mode='nearest', **kwargs):
    method _saliency_map (line 1008) | def _saliency_map(self, stimulus):
  class DigitizeMapModel (line 1014) | class DigitizeMapModel(SaliencyMapModel):
    method __init__ (line 1015) | def __init__(self, parent_model, bins=256, return_ints=True):
    method _saliency_map (line 1021) | def _saliency_map(self, stimulus):
  class HistogramNormalizedSaliencyMapModel (line 1033) | class HistogramNormalizedSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 1034) | def __init__(self, parent_model, histogram=None, **kwargs):
    method _saliency_map (line 1047) | def _saliency_map(self, stimulus):
  class LambdaSaliencyMapModel (line 1052) | class LambdaSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 1054) | def __init__(self, parent_models, fn, **kwargs):
    method _saliency_map (line 1059) | def _saliency_map(self, stimulus):
  class RandomNoiseSaliencyMapModel (line 1064) | class RandomNoiseSaliencyMapModel(LambdaSaliencyMapModel):
    method __init__ (line 1065) | def __init__(self, parent_model, noise_size=1.0/10000000, random_seed=...
    method add_jitter (line 1074) | def add_jitter(self, saliency_maps):
  class DensitySaliencyMapModel (line 1079) | class DensitySaliencyMapModel(SaliencyMapModel):
    method __init__ (line 1081) | def __init__(self, parent_model, **kwargs):
    method _saliency_map (line 1085) | def _saliency_map(self, stimulus):
  class LogDensitySaliencyMapModel (line 1089) | class LogDensitySaliencyMapModel(SaliencyMapModel):
    method __init__ (line 1091) | def __init__(self, parent_model, **kwargs):
    method _saliency_map (line 1095) | def _saliency_map(self, stimulus):
  class EqualizedSaliencyMapModel (line 1099) | class EqualizedSaliencyMapModel(SaliencyMapModel):
    method __init__ (line 1101) | def __init__(self, parent_model, **kwargs):
    method _saliency_map (line 1105) | def _saliency_map(self, stimulus):
  function nd_argmax (line 1113) | def nd_argmax(array):
  class WTASamplingMixin (line 1117) | class WTASamplingMixin(SamplingModelMixin):
    method sample_fixation (line 1118) | def sample_fixation(self, stimulus, x_hist, y_hist, t_hist, attributes...

FILE: pysaliency/sampling_models.py
  class SamplingModelMixin (line 8) | class SamplingModelMixin(object, metaclass=ABCMeta):
    method sample_scanpath (line 10) | def sample_scanpath(
    method sample_fixation (line 29) | def sample_fixation(self, stimulus, x_hist, y_hist, t_hist, attributes...
  class ScanpathSamplingModelMixin (line 34) | class ScanpathSamplingModelMixin(SamplingModelMixin):
    method sample_scanpath (line 37) | def sample_scanpath(
    method sample_fixation (line 42) | def sample_fixation(self, stimulus, x_hist, y_hist, t_hist, attributes...

FILE: pysaliency/tf_utils.py
  function normalize_axis (line 7) | def normalize_axis(input_tensor, axis):
  function replication_padding (line 15) | def replication_padding(input_tensor, axis=0, size=1):
  function get_gaussian_kernel (line 33) | def get_gaussian_kernel(sigma, windowradius=5):
  function blowup_1d_kernel (line 41) | def blowup_1d_kernel(kernel, axis=-1):
  function gaussian_convolution_along_axis (line 51) | def gaussian_convolution_along_axis(inputs, axis, sigma, windowradius=5,...
  function gauss_blur (line 77) | def gauss_blur(inputs, sigma, windowradius=5, mode='NEAREST', scope=None,
  function tf_logsumexp (line 95) | def tf_logsumexp(data, axis=0):

FILE: pysaliency/theano_utils.py
  function nonlinearity (line 10) | def nonlinearity(input, x, y, length):
  function gaussian_filter (line 32) | def gaussian_filter(input, sigma, window_radius = 40):
  class Blur (line 84) | class Blur(object):
    method __init__ (line 85) | def __init__(self, input, sigma=20.0, window_radius=60):
  class Nonlinearity (line 94) | class Nonlinearity(object):
    method __init__ (line 95) | def __init__(self, input, nonlinearity_ys = None):
  class LogNonlinearity (line 107) | class LogNonlinearity(object):
    method __init__ (line 108) | def __init__(self, input, nonlinearity_ys = None):
  class CenterBias (line 120) | class CenterBias(object):
    method __init__ (line 121) | def __init__(self, input, centerbias = None, alpha=1.0):
  class AdditiveCenterBias (line 148) | class AdditiveCenterBias(object):
    method __init__ (line 149) | def __init__(self, input, centerbias = None, alpha=1.0):
  class LogDensity (line 176) | class LogDensity(object):
    method __init__ (line 177) | def __init__(self, input):
  class LogDensityFromLogarithmicScale (line 182) | class LogDensityFromLogarithmicScale(object):
    method __init__ (line 183) | def __init__(self, input):
  class AverageLogLikelihood (line 188) | class AverageLogLikelihood(object):
    method __init__ (line 189) | def __init__(self, log_densities, x_inds, y_inds):
  class SaliencyMapProcessing (line 195) | class SaliencyMapProcessing(object):
    method __init__ (line 196) | def __init__(self, saliency_map, x_inds = None, y_inds = None,
  class SaliencyMapProcessingLogNonlinearity (line 236) | class SaliencyMapProcessingLogNonlinearity(object):
    method __init__ (line 237) | def __init__(self, saliency_map, x_inds = None, y_inds = None,
  class SaliencyMapProcessingLogarithmic (line 277) | class SaliencyMapProcessingLogarithmic(object):
    method __init__ (line 278) | def __init__(self, saliency_map, x_inds = None, y_inds = None,

FILE: pysaliency/torch_datasets.py
  function ensure_color_image (line 13) | def ensure_color_image(image):
  function x_y_to_sparse_indices (line 19) | def x_y_to_sparse_indices(xs, ys):
  class ImageDataset (line 39) | class ImageDataset(torch.utils.data.Dataset):
    method __init__ (line 40) | def __init__(self, stimuli, fixations, models=None, transform=None, ca...
    method get_shapes (line 67) | def get_shapes(self):
    method __getitem__ (line 70) | def __getitem__(self, key):
    method __len__ (line 116) | def __len__(self):
  class FixationMaskTransform (line 120) | class FixationMaskTransform(object):
    method __call__ (line 121) | def __call__(self, item):
  class ImageDatasetSampler (line 139) | class ImageDatasetSampler(torch.utils.data.Sampler):
    method __init__ (line 140) | def __init__(self, data_source, batch_size=1, ratio_used=1.0, shuffle=...
    method __iter__ (line 158) | def __iter__(self):
    method __len__ (line 169) | def __len__(self):
  function collate_fn (line 174) | def collate_fn(batch):

FILE: pysaliency/torch_utils.py
  function gaussian_filter_1d_old_torch (line 10) | def gaussian_filter_1d_old_torch(tensor, dim, sigma, truncate=4, kernel_...
  function gaussian_filter_1d_new_torch (line 56) | def gaussian_filter_1d_new_torch(tensor, dim, sigma, truncate=4, kernel_...
  function gaussian_filter_1d (line 119) | def gaussian_filter_1d(tensor, dim, sigma, truncate=4, kernel_size=None,...
  function gaussian_filter (line 127) | def gaussian_filter(tensor, dim, sigma, truncate=4, kernel_size=None, pa...
  class GaussianFilterNd (line 145) | class GaussianFilterNd(nn.Module):
    method __init__ (line 148) | def __init__(self, dims, sigma, truncate=4, kernel_size=None, padding_...
    method forward (line 173) | def forward(self, tensor):
  function nonlinearity (line 189) | def nonlinearity(tensor, xs, ys):
  class Nonlinearity (line 201) | class Nonlinearity(nn.Module):
    method __init__ (line 202) | def __init__(self, xs=None, ys=None, num_values=20, value_scale='linea...
    method forward (line 224) | def forward(self, tensor):
  function zero_grad (line 231) | def zero_grad(model):
  function log_likelihood (line 239) | def log_likelihood(log_density, fixation_mask, weights=None):

FILE: pysaliency/utils/__init__.py
  function build_padded_2d_array (line 27) | def build_padded_2d_array(arrays, max_length=None, padding_value=np.nan):
  function full_split (line 44) | def full_split(filename):
  function get_minimal_unique_filenames (line 59) | def get_minimal_unique_filenames(filenames):
  function remove_trailing_nans (line 71) | def remove_trailing_nans(data):
  function lazy_property (line 79) | def lazy_property(fn):
  class LazyList (line 92) | class LazyList(Sequence):
    method __init__ (line 105) | def __init__(self, generator, length, cache=True, cache_size=None, pic...
    method __len__ (line 136) | def __len__(self):
    method __getitem__ (line 139) | def __getitem__(self, index):
    method _getitem (line 147) | def _getitem(self, index):
    method __getstate__ (line 152) | def __getstate__(self):
    method __setstate__ (line 162) | def __setstate__(self, state):
    method cache (line 175) | def cache(self):
    method cache (line 179) | def cache(self, value):
  function atomic_directory_setup (line 189) | def atomic_directory_setup(directory):
  function which (line 198) | def which(program):
  function filter_files (line 220) | def filter_files(filenames, ignores):
  class MatlabOptions (line 231) | class MatlabOptions(object):
  function get_matlab_or_octave (line 236) | def get_matlab_or_octave():
  function run_matlab_cmd (line 243) | def run_matlab_cmd(cmd, cwd=None):
  function check_file_hash (line 256) | def check_file_hash(filename, md5_hash):
  function download_file (line 274) | def download_file(url, target, verify_ssl=True):
  function download_and_check (line 286) | def download_and_check(url, target, md5_hash, verify_ssl=True):
  function download_file_from_google_drive (line 291) | def download_file_from_google_drive(id, destination):
  class Cache (line 296) | class Cache(MutableMapping):
    method __init__ (line 307) | def __init__(self, cache_location=None, pickle_cache=False,
    method clear (line 316) | def clear(self):
    method filename (line 320) | def filename(self, key):
    method __getitem__ (line 323) | def __getitem__(self, key):
    method __setitem__ (line 334) | def __setitem__(self, key, value):
    method __delitem__ (line 344) | def __delitem__(self, key):
    method __iter__ (line 351) | def __iter__(self):
    method __len__ (line 360) | def __len__(self):
    method __getstate__ (line 364) | def __getstate__(self):
    method __setstate__ (line 371) | def __setstate__(self, state):
  function average_values (line 380) | def average_values(values, fixations, average='fixation'):
  function deprecated_class (line 391) | def deprecated_class(deprecated_in=None, removed_in=None, current_versio...
  function inter_and_extrapolate (line 403) | def inter_and_extrapolate(data, interpolation_method='linear', extrapola...
  function iterator_chunks (line 425) | def iterator_chunks(iterable, chunk_size=10):
  function as_rgb (line 433) | def as_rgb(image: np.ndarray):

FILE: pysaliency/utils/variable_length_array.py
  class VariableLengthArray (line 8) | class VariableLengthArray:
    method __init__ (line 31) | def __init__(self, data: Union[np.ndarray, List[list]], lengths: Optio...
    method __len__ (line 78) | def __len__(self):
    method __getitem__ (line 81) | def __getitem__(self, index):
    method copy (line 101) | def copy(self) -> 'VariableLengthArray':
    method __repr__ (line 104) | def __repr__(self):
  function concatenate_variable_length_arrays (line 119) | def concatenate_variable_length_arrays(arrays: List[VariableLengthArray]...

FILE: tests/conftest.py
  function pytest_addoption (line 13) | def pytest_addoption(parser):
  function pytest_collection_modifyitems (line 24) | def pytest_collection_modifyitems(config, items):
  function matlab (line 49) | def matlab(request, pytestconfig):
  function location (line 78) | def location(tmpdir):
  function cache_requests (line 83) | def cache_requests(monkeypatch):

FILE: tests/datasets/test_datasets.py
  function file_stimuli_with_attributes (line 10) | def file_stimuli_with_attributes(tmpdir):
  function scanpath_fixations (line 33) | def scanpath_fixations() -> pysaliency.ScanpathFixations:
  function fixation_trains (line 72) | def fixation_trains():
  function test_create_subset_scanpath_fixations (line 116) | def test_create_subset_scanpath_fixations(file_stimuli_with_attributes, ...
  function test_create_subset_fixation_trains (line 133) | def test_create_subset_fixation_trains(file_stimuli_with_attributes, fix...
  function test_create_subset_fixations (line 150) | def test_create_subset_fixations(file_stimuli_with_attributes, fixation_...
  function test_create_subset_numpy_indices (line 161) | def test_create_subset_numpy_indices(file_stimuli_with_attributes, fixat...
  function test_create_subset_numpy_mask (line 171) | def test_create_subset_numpy_mask(file_stimuli_with_attributes, fixation...
  function test_concatenate_datasets_with_scanpath_fixations (line 184) | def test_concatenate_datasets_with_scanpath_fixations(file_stimuli_with_...
  function test_concatenate_datasets_with_fixation_trains (line 208) | def test_concatenate_datasets_with_fixation_trains(file_stimuli_with_att...

FILE: tests/datasets/test_fixations.py
  function file_stimuli_with_attributes (line 19) | def file_stimuli_with_attributes(tmpdir):
  class TestFixations (line 41) | class TestFixations(TestWithData):
    method test_from_fixations (line 42) | def test_from_fixations(self):
    method test_filter (line 107) | def test_filter(self):
    method test_scanpaths (line 135) | def test_scanpaths(self):
  function scanpath_fixations (line 167) | def scanpath_fixations() -> ScanpathFixations:
  function fixation_trains (line 208) | def fixation_trains():
  function test_copy_scanpath_fixations (line 247) | def test_copy_scanpath_fixations(scanpath_fixations):
  function test_copy_fixation_trains (line 253) | def test_copy_fixation_trains(fixation_trains):
  function test_copy_fixations (line 258) | def test_copy_fixations(fixation_trains):
  function test_write_read_scanpath_fixations_pickle (line 264) | def test_write_read_scanpath_fixations_pickle(tmp_path, scanpath_fixatio...
  function test_write_read_scanpath_fixations_pathlib (line 277) | def test_write_read_scanpath_fixations_pathlib(tmp_path, scanpath_fixati...
  function test_write_read_fixation_trains_pathlib (line 288) | def test_write_read_fixation_trains_pathlib(tmp_path, fixation_trains):
  function test_write_read_fixation_trains (line 299) | def test_write_read_fixation_trains(tmp_path, fixation_trains):
  function test_scanpath_lengths (line 310) | def test_scanpath_lengths(fixation_trains):
  function test_scanpath_fixations_scanpath_attributes (line 314) | def test_scanpath_fixations_scanpath_attributes(scanpath_fixations):
  function test_scanpath_fixations_scanpath_attributes_zero_values (line 322) | def test_scanpath_fixations_scanpath_attributes_zero_values(scanpath_fix...
  function test_fixation_trains_scanpath_attributes (line 334) | def test_fixation_trains_scanpath_attributes(fixation_trains):
  function test_scanpath_fixations_scanpath_fixation_attributes (line 342) | def test_scanpath_fixations_scanpath_fixation_attributes(scanpath_fixati...
  function test_scanpath_fixations_values (line 373) | def test_scanpath_fixations_values(scanpath_fixations):
  function test_scanpath_fixations_empty (line 424) | def test_scanpath_fixations_empty():
  function test_fixation_trains_scanpath_fixation_attributes (line 442) | def test_fixation_trains_scanpath_fixation_attributes(fixation_trains):
  function test_scanpath_fixations_filter_scanpaths (line 479) | def test_scanpath_fixations_filter_scanpaths(scanpath_fixations, scanpat...
  function test_filter_fixation_trains (line 492) | def test_filter_fixation_trains(fixation_trains, scanpath_indices, fixat...
  function test_read_hdf5_caching (line 533) | def test_read_hdf5_caching(fixation_trains, tmp_path):
  function test_fixations_save_load (line 545) | def test_fixations_save_load(tmp_path, fixation_trains):
  function test_concatenate_fixations (line 557) | def test_concatenate_fixations(fixation_trains):
  function test_concatenate_scanpath_fixations (line 579) | def test_concatenate_scanpath_fixations(scanpath_fixations):
  function test_concatenate_scanpath_fixations_partial_attributes (line 595) | def test_concatenate_scanpath_fixations_partial_attributes(scanpath_fixa...
  function test_concatenate_fixation_trains_partial_attributes (line 618) | def test_concatenate_fixation_trains_partial_attributes(fixation_trains):
  function test_load_scanpath_fixations_from_fixation_trains (line 646) | def test_load_scanpath_fixations_from_fixation_trains(fixation_trains, s...
  function test_scanpaths_from_fixations (line 665) | def test_scanpaths_from_fixations(fixation_indices):

FILE: tests/datasets/test_scanpaths.py
  function assert_variable_length_array_equal (line 11) | def assert_variable_length_array_equal(array1, array2):
  function assert_scanpaths_equal (line 18) | def assert_scanpaths_equal(scanpaths1: Scanpaths, scanpaths2: Scanpaths,...
  function test_scanpaths (line 40) | def test_scanpaths():
  function test_scanpaths_from_lists (line 72) | def test_scanpaths_from_lists():
  function test_scanpaths_from_lists_with_keyword_arguments (line 93) | def test_scanpaths_from_lists_with_keyword_arguments():
  function test_scanpaths_init_inconsistent_lengths (line 125) | def test_scanpaths_init_inconsistent_lengths():
  function test_scanpaths_init_invalid_scanpath_attributes (line 137) | def test_scanpaths_init_invalid_scanpath_attributes():
  function test_scanpaths_init_invalid_scanpath_fixation_attributes (line 149) | def test_scanpaths_init_invalid_scanpath_fixation_attributes():
  function test_scanpaths_init_invalid_scanpath_fixation_attributes_dimensions (line 161) | def test_scanpaths_init_invalid_scanpath_fixation_attributes_dimensions():
  function test_scanpaths_init_invalid_scanpath_lengths (line 173) | def test_scanpaths_init_invalid_scanpath_lengths():
  function test_scanpaths_slicing (line 211) | def test_scanpaths_slicing(inds):
  function test_write_read_scanpaths_pathlib (line 225) | def test_write_read_scanpaths_pathlib(tmp_path):
  function test_scanpaths_copy (line 254) | def test_scanpaths_copy():
  function test_concatenate_scanpaths (line 270) | def test_concatenate_scanpaths():
  function test_concatenate_scanpaths_no_mapping (line 302) | def test_concatenate_scanpaths_no_mapping():
  function test_concatenate_scanpaths_missing_fixation_attribute (line 334) | def test_concatenate_scanpaths_missing_fixation_attribute():
  function test_concatenate_scanpaths_inconsistent_attribute_mappings (line 365) | def test_concatenate_scanpaths_inconsistent_attribute_mappings():

FILE: tests/datasets/test_stimuli.py
  class TestStimuli (line 21) | class TestStimuli(TestWithData):
    method test_stimuli (line 22) | def test_stimuli(self):
    method test_slicing (line 44) | def test_slicing(self):
  class TestFileStimuli (line 76) | class TestFileStimuli(TestWithData):
    method test_file_stimuli (line 77) | def test_file_stimuli(self):
    method test_slicing (line 106) | def test_slicing(self):
  function stimuli_with_attributes (line 147) | def stimuli_with_attributes():
  function test_stimuli_attributes (line 157) | def test_stimuli_attributes(stimuli_with_attributes, tmp_path):
  function file_stimuli_with_attributes (line 191) | def file_stimuli_with_attributes(tmpdir):
  function test_file_stimuli_attributes (line 213) | def test_file_stimuli_attributes(file_stimuli_with_attributes, tmp_path):
  function test_file_stimuli_readhdf5_cached (line 246) | def test_file_stimuli_readhdf5_cached(file_stimuli_with_attributes, tmp_...
  function test_concatenate_stimuli_with_attributes (line 259) | def test_concatenate_stimuli_with_attributes(stimuli_with_attributes, fi...
  function test_concatenate_file_stimuli (line 267) | def test_concatenate_file_stimuli(file_stimuli_with_attributes):
  function test_check_prediction_shape (line 274) | def test_check_prediction_shape():
  function test_substimuli_inherit_cachedstimulus_ids (line 314) | def test_substimuli_inherit_cachedstimulus_ids(stimuli, request):

FILE: tests/datasets/utils.py
  function assert_variable_length_array_equal (line 10) | def assert_variable_length_array_equal(array1, array2):
  function assert_scanpaths_equal (line 19) | def assert_scanpaths_equal(scanpaths1: Scanpaths, scanpaths2: Scanpaths,...
  function compare_fixations_subset (line 41) | def compare_fixations_subset(f1, f2, f2_inds):
  function assert_fixations_equal (line 55) | def assert_fixations_equal(f1, f2, crop_length=False):
  function assert_fixation_trains_equal (line 88) | def assert_fixation_trains_equal(fixation_trains1, fixation_trains2):
  function assert_scanpath_fixations_equal (line 110) | def assert_scanpath_fixations_equal(scanpath_fixations1: ScanpathFixatio...

FILE: tests/external_datasets/test_COCO_Search18.py
  function test_COCO_Search18_task_merge (line 12) | def test_COCO_Search18_task_merge(location):
  function test_COCO_Search18_no_task_merge_redundant_images (line 90) | def test_COCO_Search18_no_task_merge_redundant_images(location):
  function test_COCO_Search18_no_task_merge_unique_images (line 181) | def test_COCO_Search18_no_task_merge_unique_images(location):

FILE: tests/external_datasets/test_NUSEF.py
  function test_NUSEF (line 12) | def test_NUSEF(location):

FILE: tests/external_datasets/test_PASCAL_S.py
  function test_PASCAL_S (line 13) | def test_PASCAL_S(location):

FILE: tests/external_datasets/test_SALICON.py
  function test_SALICON_stimuli (line 13) | def test_SALICON_stimuli(tmpdir):
  function test_SALICON_fixations_2015_mouse (line 37) | def test_SALICON_fixations_2015_mouse(tmpdir):
  function test_SALICON_fixations_2015_fixations (line 110) | def test_SALICON_fixations_2015_fixations(tmpdir):
  function test_SALICON_fixations_2017_mouse (line 184) | def test_SALICON_fixations_2017_mouse(tmpdir):
  function test_SALICON_fixations_2017_fixations (line 258) | def test_SALICON_fixations_2017_fixations(tmpdir):

FILE: tests/external_datasets/test_coco_freeview.py
  function test_COCO_Freeview (line 12) | def test_COCO_Freeview(location):
  function test_COCO_Freeview_with_test (line 100) | def test_COCO_Freeview_with_test(location):

FILE: tests/external_models/test_deepgaze.py
  function color_stimulus (line 11) | def color_stimulus():
  function grayscale_stimulus (line 16) | def grayscale_stimulus():
  function stimuli (line 21) | def stimuli(color_stimulus, grayscale_stimulus):
  function fixations (line 26) | def fixations():
  function test_deepgaze1 (line 37) | def test_deepgaze1(stimuli, fixations):
  function test_deepgaze2e (line 45) | def test_deepgaze2e(stimuli, fixations):

FILE: tests/skippedtest_theano_utils.py
  function dtype (line 19) | def dtype(request):
  function input (line 24) | def input(request):
  function sigma (line 29) | def sigma(request):
  class TestNonlinearity (line 33) | class TestNonlinearity(unittest.TestCase):
    method setUp (line 34) | def setUp(self):
    method test_id (line 45) | def test_id(self):
    method test_mult_id (line 52) | def test_mult_id(self):
    method test_shifted_id (line 59) | def test_shifted_id(self):
    method test_random (line 66) | def test_random(self):
    method test_constant (line 73) | def test_constant(self):
  class TestBlur (line 81) | class TestBlur(object):
    method setUp (line 82) | def setUp(self):
    method test_blur_zeros (line 85) | def test_blur_zeros(self):
    method test_blur_ones (line 100) | def test_blur_ones(self):
    method test_other (line 116) | def test_other(self, dtype, input, sigma):
  class TestBlurObject (line 149) | class TestBlurObject(object):
    method test_blur (line 150) | def test_blur(self):
    method test_no_blur (line 162) | def test_no_blur(self):
  class TestCenterBias (line 175) | class TestCenterBias(object):
    method test_centerbias_ones (line 176) | def test_centerbias_ones(self):
    method test_centerbias_ones_times_two (line 186) | def test_centerbias_ones_times_two(self):
    method test_centerbias_random (line 196) | def test_centerbias_random(self):
    method test_centerbias_ones_nontrivial (line 206) | def test_centerbias_ones_nontrivial(self):
    method test_centerbias_empty (line 217) | def test_centerbias_empty(self):

FILE: tests/test_baseline_utils.py
  function scanpath_fixations (line 22) | def scanpath_fixations():
  function stimuli (line 41) | def stimuli():
  function test_fixation_map (line 46) | def test_fixation_map():
  function test_kde_gold_model (line 64) | def test_kde_gold_model(stimuli, scanpath_fixations):
  function test_general_mixture_kernel_density_estimator (line 85) | def test_general_mixture_kernel_density_estimator():
  function test_mixture_kernel_density_estimator (line 115) | def test_mixture_kernel_density_estimator():
  function test_crossval_multiple_regularizations (line 144) | def test_crossval_multiple_regularizations(stimuli, scanpath_fixations):
  function test_baseline_model_does_not_store_stimuli_and_fixations (line 163) | def test_baseline_model_does_not_store_stimuli_and_fixations(stimuli, sc...
  function test_baseline_model_hdf5_roundtrip_with_shape_cache (line 169) | def test_baseline_model_hdf5_roundtrip_with_shape_cache(tmp_path, stimul...
  function test_baseline_model_hdf5_roundtrip_without_shape_cache (line 182) | def test_baseline_model_hdf5_roundtrip_without_shape_cache(tmp_path, sti...
  function test_baseline_model_hdf5_type (line 194) | def test_baseline_model_hdf5_type(tmp_path, stimuli, scanpath_fixations):
  function test_crossvalidated_baseline_model_stores_fixations_n (line 208) | def test_crossvalidated_baseline_model_stores_fixations_n(stimuli, scanp...
  function test_crossvalidated_baseline_model_hdf5_type (line 216) | def test_crossvalidated_baseline_model_hdf5_type(tmp_path, stimuli, scan...
  function test_crossvalidated_baseline_model_hdf5_roundtrip_with_stimuli (line 229) | def test_crossvalidated_baseline_model_hdf5_roundtrip_with_stimuli(tmp_p...
  function test_crossvalidated_baseline_model_hdf5_roundtrip_without_stimuli (line 240) | def test_crossvalidated_baseline_model_hdf5_roundtrip_without_stimuli(tm...
  function test_crossvalidated_baseline_model_hdf5_error_without_stimuli (line 251) | def test_crossvalidated_baseline_model_hdf5_error_without_stimuli(tmp_pa...
  function test_crossvalidated_baseline_model_hdf5_stimuli_kwarg_overrides_embedded (line 260) | def test_crossvalidated_baseline_model_hdf5_stimuli_kwarg_overrides_embe...

FILE: tests/test_crossvalidation.py
  class ConstantSaliencyModel (line 11) | class ConstantSaliencyModel(pysaliency.Model):
    method _log_density (line 12) | def _log_density(self, stimulus):
  class GaussianSaliencyModel (line 16) | class GaussianSaliencyModel(pysaliency.Model):
    method _log_density (line 17) | def _log_density(self, stimulus):
  function scanpath_fixations (line 29) | def scanpath_fixations():
  function stimuli (line 51) | def stimuli():
  function _unpack_crossval (line 57) | def _unpack_crossval(cv):
  function unpack_crossval (line 62) | def unpack_crossval(cv):
  function test_image_crossvalidation (line 66) | def test_image_crossvalidation(stimuli, scanpath_fixations):
  function test_image_subject_crossvalidation (line 89) | def test_image_subject_crossvalidation(stimuli, scanpath_fixations):

FILE: tests/test_dataset_config.py
  function scanpath_fixations (line 18) | def scanpath_fixations():
  function file_stimuli_with_attributes (line 56) | def file_stimuli_with_attributes(tmpdir):
  function stimuli (line 79) | def stimuli():
  function hdf5_dataset (line 85) | def hdf5_dataset(tmpdir, scanpath_fixations, stimuli):
  function test_load_dataset (line 91) | def test_load_dataset(hdf5_dataset, stimuli, scanpath_fixations):
  function test_load_dataset_with_filter (line 101) | def test_load_dataset_with_filter(hdf5_dataset, stimuli, scanpath_fixati...
  function test_apply_dataset_filter_config_filter_scanpaths_by_attribute_task (line 118) | def test_apply_dataset_filter_config_filter_scanpaths_by_attribute_task(...
  function test_apply_dataset_filter_config_filter_scanpaths_by_attribute_multi_dim_attribute_invert_match (line 135) | def test_apply_dataset_filter_config_filter_scanpaths_by_attribute_multi...
  function test_apply_dataset_filter_config_filter_fixations_by_attribute_subject_invert_match (line 152) | def test_apply_dataset_filter_config_filter_fixations_by_attribute_subje...
  function test_apply_dataset_filter_config_filter_stimuli_by_attribute_dva (line 169) | def test_apply_dataset_filter_config_filter_stimuli_by_attribute_dva(fil...
  function test_apply_dataset_filter_config_filter_scanpaths_by_length_multiple_inputs (line 186) | def test_apply_dataset_filter_config_filter_scanpaths_by_length_multiple...
  function test_apply_dataset_filter_config_filter_scanpaths_by_length_single_input (line 201) | def test_apply_dataset_filter_config_filter_scanpaths_by_length_single_i...

FILE: tests/test_external_datasets.py
  function _location (line 12) | def _location(location):
  function entropy (line 18) | def entropy(labels):
  function test_toronto (line 26) | def test_toronto(location):
  function test_cat2000_train_v1_0 (line 73) | def test_cat2000_train_v1_0(location, matlab):
  function test_cat2000_train_v1_1 (line 126) | def test_cat2000_train_v1_1(location, matlab):
  function test_cat2000_test (line 178) | def test_cat2000_test(location):
  function test_mit1003 (line 203) | def test_mit1003(location, matlab):
  function test_mit1003_onesize (line 259) | def test_mit1003_onesize(location, matlab):
  function test_koehler (line 283) | def test_koehler(location):
  function test_FIGRIM (line 384) | def test_FIGRIM(location):
  function test_OSIE (line 428) | def test_OSIE(location):

FILE: tests/test_external_models.py
  function color_stimulus (line 14) | def color_stimulus():
  function grayscale_stimulus (line 19) | def grayscale_stimulus():
  function test_AIM (line 25) | def test_AIM(tmpdir, matlab, color_stimulus, grayscale_stimulus):
  function test_SUN (line 40) | def test_SUN(tmpdir, matlab, color_stimulus, grayscale_stimulus):
  function test_ContextAwareSaliency (line 55) | def test_ContextAwareSaliency(tmpdir, matlab, color_stimulus, grayscale_...
  function test_GBVS (line 69) | def test_GBVS(tmpdir, matlab, color_stimulus, grayscale_stimulus):
  function test_GBVSIttiKoch (line 83) | def test_GBVSIttiKoch(tmpdir, matlab, color_stimulus, grayscale_stimulus):
  function test_Judd (line 97) | def test_Judd(tmpdir, matlab, color_stimulus, grayscale_stimulus):
  function test_IttiKoch (line 111) | def test_IttiKoch(tmpdir, matlab, color_stimulus, grayscale_stimulus):
  function test_RARE2007 (line 125) | def test_RARE2007(tmpdir, matlab, color_stimulus, grayscale_stimulus):
  function test_RARE2012 (line 139) | def test_RARE2012(tmpdir, matlab, color_stimulus, grayscale_stimulus):
  function test_CovSal (line 153) | def test_CovSal(tmpdir, matlab, color_stimulus, grayscale_stimulus):

FILE: tests/test_filter_datasets.py
  function fixation_trains (line 21) | def fixation_trains():
  function file_stimuli_with_attributes (line 63) | def file_stimuli_with_attributes(tmpdir):
  function stimuli (line 86) | def stimuli():
  function test_filter_fixations_by_number (line 91) | def test_filter_fixations_by_number(fixation_trains):
  function stimuli_with_different_sizes (line 110) | def stimuli_with_different_sizes():
  function assert_stimuli_equal (line 124) | def assert_stimuli_equal(actual, expected):
  function test_filter_stimuli_by_size_tuple (line 134) | def test_filter_stimuli_by_size_tuple(stimuli_with_different_sizes, fixa...
  function test_filter_stimuli_by_size_array (line 147) | def test_filter_stimuli_by_size_array(stimuli_with_different_sizes, fixa...
  function test_filter_stimuli_by_size_multiple (line 164) | def test_filter_stimuli_by_size_multiple(stimuli_with_different_sizes, f...
  function many_stimuli (line 178) | def many_stimuli():
  function test_crossval_splits (line 188) | def test_crossval_splits(many_stimuli, crossval_folds, val_folds, test_f...
  function test_stratified_crossval_splits (line 234) | def test_stratified_crossval_splits(many_stimuli, crossval_folds, val_fo...
  function test_stratified_crossval_splits_multiple_attributes (line 296) | def test_stratified_crossval_splits_multiple_attributes(many_stimuli, cr...
  function test_filter_stimuli_by_attribute_dva (line 355) | def test_filter_stimuli_by_attribute_dva(file_stimuli_with_attributes, f...
  function test_filter_stimuli_by_attribute_multiple_values (line 366) | def test_filter_stimuli_by_attribute_multiple_values(file_stimuli_with_a...
  function test_filter_stimuli_by_attribute_some_strings_invert_match (line 377) | def test_filter_stimuli_by_attribute_some_strings_invert_match(file_stim...
  function test_filter_fixations_by_attribute_subject_invert_match (line 389) | def test_filter_fixations_by_attribute_subject_invert_match(fixation_tra...
  function test_filter_fixations_by_attribute_some_attribute (line 400) | def test_filter_fixations_by_attribute_some_attribute(fixation_trains):
  function test_filter_fixations_by_attribute_some_attribute_invert_match (line 411) | def test_filter_fixations_by_attribute_some_attribute_invert_match(fixat...
  function test_filter_scanpaths_by_attribute_task (line 422) | def test_filter_scanpaths_by_attribute_task(fixation_trains):
  function test_filter_scanpaths_by_attribute_multi_dim_attribute (line 433) | def test_filter_scanpaths_by_attribute_multi_dim_attribute(fixation_trai...
  function test_filter_scanpaths_by_attribute_multi_dim_attribute_invert_match (line 444) | def test_filter_scanpaths_by_attribute_multi_dim_attribute_invert_match(...
  function test_filter_scanpaths_by_length (line 456) | def test_filter_scanpaths_by_length(fixation_trains, intervals):
  function test_remove_stimuli_without_fixations (line 477) | def test_remove_stimuli_without_fixations(file_stimuli_with_attributes, ...

FILE: tests/test_hdf5_io.py
  function test_unified_read_hdf5_reads_dataset_files (line 7) | def test_unified_read_hdf5_reads_dataset_files(tmp_path):
  function test_unified_read_hdf5_reads_baseline_model_files (line 18) | def test_unified_read_hdf5_reads_baseline_model_files(tmp_path):
  function test_legacy_datasets_read_hdf5_wrapper_still_works (line 36) | def test_legacy_datasets_read_hdf5_wrapper_still_works(tmp_path):
  function test_unified_read_hdf5_reads_crossvalidated_baseline_model (line 46) | def test_unified_read_hdf5_reads_crossvalidated_baseline_model(tmp_path):
  function test_unified_read_hdf5_crossvalidated_baseline_model_stimuli_kwarg (line 64) | def test_unified_read_hdf5_crossvalidated_baseline_model_stimuli_kwarg(t...
  function test_unified_read_hdf5_crossvalidated_baseline_model_stimuli_kwarg_bypasses_cache (line 84) | def test_unified_read_hdf5_crossvalidated_baseline_model_stimuli_kwarg_b...

FILE: tests/test_helpers.py
  function assert_equal (line 9) | def assert_equal(a, b):
  class TestWithData (line 13) | class TestWithData(unittest.TestCase):
    method setUp (line 16) | def setUp(self):
    method tearDown (line 22) | def tearDown(self):
    method pickle_and_reload (line 25) | def pickle_and_reload(self, data, pickler = pickle):
  function check_dircmp (line 37) | def check_dircmp(dircmp):
  function assertDirsEqual (line 46) | def assertDirsEqual(dir1, dir2, ignore=[]):

FILE: tests/test_http_models.py
  class TestHTTPScanpathModel (line 10) | class TestHTTPScanpathModel(unittest.TestCase):
    method test_init (line 13) | def test_init(self):
    method test_init_invalid_type (line 27) | def test_init_invalid_type(self):
    method test_conditional_log_density (line 37) | def test_conditional_log_density(self):
  class TestHTTPScanpathSaliencyMapModel (line 73) | class TestHTTPScanpathSaliencyMapModel(unittest.TestCase):
    method test_init (line 76) | def test_init(self):
    method test_init_invalid_type (line 90) | def test_init_invalid_type(self):
    method test_init_invalid_version (line 100) | def test_init_invalid_version(self):
    method test_conditional_saliency_map (line 110) | def test_conditional_saliency_map(self):
    method test_conditional_saliency_map_with_attributes (line 145) | def test_conditional_saliency_map_with_attributes(self):
    method test_conditional_saliency_map_server_error (line 174) | def test_conditional_saliency_map_server_error(self):
    method test_inheritance (line 198) | def test_inheritance(self):

FILE: tests/test_metric_optimization.py
  class GaussianSaliencyModel (line 9) | class GaussianSaliencyModel(pysaliency.Model):
    method _log_density (line 10) | def _log_density(self, stimulus):
  function stimuli (line 22) | def stimuli():
  function test_sim_saliency_map (line 27) | def test_sim_saliency_map(stimuli):

FILE: tests/test_metric_optimization_tf.py
  function test_maximize_expected_sim_decay_1overk (line 9) | def test_maximize_expected_sim_decay_1overk():
  function test_maximize_expected_sim_decay_on_plateau (line 28) | def test_maximize_expected_sim_decay_on_plateau():

FILE: tests/test_metric_optimization_torch.py
  function test_maximize_expected_sim_decay_1overk (line 6) | def test_maximize_expected_sim_decay_1overk():
  function test_maximize_expected_sim_decay_on_plateau (line 27) | def test_maximize_expected_sim_decay_on_plateau():

FILE: tests/test_models.py
  class ConstantSaliencyModel (line 9) | class ConstantSaliencyModel(pysaliency.Model):
    method _log_density (line 10) | def _log_density(self, stimulus):
  class GaussianSaliencyModel (line 14) | class GaussianSaliencyModel(pysaliency.Model):
    method _log_density (line 15) | def _log_density(self, stimulus):
  function scanpath_fixations (line 27) | def scanpath_fixations():
  function stimuli (line 46) | def stimuli():
  function test_log_likelihood_constant (line 51) | def test_log_likelihood_constant(stimuli, scanpath_fixations):
  function test_log_likelihood_gauss (line 58) | def test_log_likelihood_gauss(stimuli, scanpath_fixations):
  function test_sampling (line 70) | def test_sampling(stimuli):
  function long_stimuli (line 78) | def long_stimuli():
  function test_model (line 83) | def test_model(long_stimuli):
  function pixel_model (line 102) | def pixel_model(long_stimuli):
  function test_average_predictions (line 120) | def test_average_predictions(long_stimuli, pixel_model):
  function test_average_predictions_iter (line 129) | def test_average_predictions_iter(long_stimuli, test_model):
  function test_average_predictions_iter (line 139) | def test_average_predictions_iter(long_stimuli, test_model):
  function test_average_predictions_torch (line 155) | def test_average_predictions_torch(long_stimuli, test_model):
  function test_average_predictions_tensorflow (line 165) | def test_average_predictions_tensorflow(long_stimuli, test_model):
  function test_shuffled_baseline_model (line 177) | def test_shuffled_baseline_model(long_stimuli, test_model, library):
  function test_conditional_log_densities (line 186) | def test_conditional_log_densities(long_stimuli, test_model, scanpath_fi...

FILE: tests/test_numba_utils.py
  function test_auc_for_one_positive (line 8) | def test_auc_for_one_positive():
  function test_simple_auc_hypothesis (line 16) | def test_simple_auc_hypothesis(negatives, positive):
  function test_numba_auc_test1 (line 24) | def test_numba_auc_test1(positives,negatives):
  function test_numba_auc_test2 (line 36) | def test_numba_auc_test2(positives,temp_variable):
  function test_numba_rocs_per_positive (line 48) | def test_numba_rocs_per_positive(positives,negatives):

FILE: tests/test_precomputed_models.py
  class TestSaliencyMapModel (line 17) | class TestSaliencyMapModel(pysaliency.SaliencyMapModel):
    method _saliency_map (line 18) | def _saliency_map(self, stimulus):
  function file_stimuli (line 27) | def file_stimuli(tmpdir):
  function stimuli_with_filenames (line 45) | def stimuli_with_filenames(tmpdir):
  function stimuli (line 63) | def stimuli(file_stimuli, stimuli_with_filenames, request):
  function sub_stimuli (line 73) | def sub_stimuli(stimuli):
  function saliency_maps_in_directory (line 81) | def saliency_maps_in_directory(file_stimuli, tmpdir):
  function test_export_model_to_hdf5 (line 98) | def test_export_model_to_hdf5(stimuli, tmpdir):
  function test_hdf5_model_sub_stimuli (line 108) | def test_hdf5_model_sub_stimuli(stimuli, sub_stimuli, tmpdir):
  function test_hdf5_model_empty_stimuli (line 118) | def test_hdf5_model_empty_stimuli(stimuli, tmpdir):
  function test_export_model_overwrite (line 128) | def test_export_model_overwrite(file_stimuli, tmpdir):
  function test_export_model_no_overwrite (line 144) | def test_export_model_no_overwrite(file_stimuli, tmpdir):
  function test_hdf5_model_sub_stimuli_different_prefix (line 163) | def test_hdf5_model_sub_stimuli_different_prefix(tmpdir):
  function test_hdf5_model_wrong_keys (line 207) | def test_hdf5_model_wrong_keys(tmpdir):
  function test_hdf5_model_sub_stimuli_different_prefix_nonunique (line 241) | def test_hdf5_model_sub_stimuli_different_prefix_nonunique(tmpdir):
  function test_saliency_map_model_from_directory (line 276) | def test_saliency_map_model_from_directory(stimuli, saliency_maps_in_dir...
  function test_saliency_map_model_from_directory_sub_stimuli (line 286) | def test_saliency_map_model_from_directory_sub_stimuli(stimuli, sub_stim...
  function test_saliency_map_model_from_archive (line 297) | def test_saliency_map_model_from_archive(stimuli, saliency_maps_in_direc...
  function test_saliency_map_model_from_archive_sub_stimuli (line 321) | def test_saliency_map_model_from_archive_sub_stimuli(stimuli, sub_stimul...
  function test_export_root_attrs_written (line 346) | def test_export_root_attrs_written(file_stimuli, tmpdir):
  function test_export_dtype_float32 (line 358) | def test_export_dtype_float32(file_stimuli, tmpdir):
  function test_export_dtype_float16 (line 369) | def test_export_dtype_float16(file_stimuli, tmpdir):
  function test_export_dtype_none_preserves_native (line 380) | def test_export_dtype_none_preserves_native(file_stimuli, tmpdir):
  function test_export_downscale_stored_shape (line 391) | def test_export_downscale_stored_shape(file_stimuli, tmpdir):
  function test_export_downscale_original_shape_attr (line 402) | def test_export_downscale_original_shape_attr(file_stimuli, tmpdir):
  function test_export_no_downscale_no_original_shape_attr (line 415) | def test_export_no_downscale_no_original_shape_attr(file_stimuli, tmpdir):
  function file_stimuli_nondivisible (line 426) | def file_stimuli_nondivisible(tmpdir):
  function test_export_downscale_nondivisible_stored_shape (line 436) | def test_export_downscale_nondivisible_stored_shape(file_stimuli_nondivi...
  function test_export_downscale_nondivisible_original_shape_attr (line 447) | def test_export_downscale_nondivisible_original_shape_attr(file_stimuli_...
  function test_export_float32_downscale_dtype_is_float32 (line 457) | def test_export_float32_downscale_dtype_is_float32(file_stimuli, tmpdir):
  function test_export_append_consistency_mismatch_downscale (line 480) | def test_export_append_consistency_mismatch_downscale(file_stimuli, tmpd...
  function test_export_append_new_file_root_attrs (line 490) | def test_export_append_new_file_root_attrs(file_stimuli, tmpdir):
  function test_export_append_legacy_file_no_root_attrs (line 502) | def test_export_append_legacy_file_no_root_attrs(file_stimuli, tmpdir):
  function test_export_uint8_model_downscale_warns (line 523) | def test_export_uint8_model_downscale_warns(tmpdir):
  function test_export_uint8_model_downscale_stored_as_float64 (line 542) | def test_export_uint8_model_downscale_stored_as_float64(tmpdir):
  function test_export_uint8_model_float32_dtype_warns (line 565) | def test_export_uint8_model_float32_dtype_warns(tmpdir):
  function test_export_uint8_model_uint8_dtype_no_warn (line 582) | def test_export_uint8_model_uint8_dtype_no_warn(tmpdir):
  function test_hdf5_saliency_map_model_roundtrip (line 607) | def test_hdf5_saliency_map_model_roundtrip(file_stimuli, tmpdir, dtype, ...
  function test_hdf5_saliency_map_model_dtype_reduced_returns_native (line 621) | def test_hdf5_saliency_map_model_dtype_reduced_returns_native(file_stimu...
  function test_hdf5_saliency_map_model_downsampled_returns_float64 (line 632) | def test_hdf5_saliency_map_model_downsampled_returns_float64(file_stimul...
  function test_hdf5_saliency_map_model_nondivisible_loaded_shape (line 643) | def test_hdf5_saliency_map_model_nondivisible_loaded_shape(file_stimuli_...
  function test_hdf5_saliency_map_model_resized_stimuli (line 654) | def test_hdf5_saliency_map_model_resized_stimuli(tmpdir):
  function test_hdf5_saliency_map_model_legacy_file_unchanged (line 675) | def test_hdf5_saliency_map_model_legacy_file_unchanged(file_stimuli, tmp...
  function test_hdf5_model_roundtrip (line 698) | def test_hdf5_model_roundtrip(file_stimuli, tmpdir, dtype, downscale_fac...
  function test_hdf5_model_strict_path_no_renorm (line 717) | def test_hdf5_model_strict_path_no_renorm(file_stimuli, tmpdir):
  function test_hdf5_model_strict_path_raises_on_bad_density (line 733) | def test_hdf5_model_strict_path_raises_on_bad_density(file_stimuli, tmpd...
  function test_hdf5_model_relaxed_path_threshold_exceeded_raises (line 748) | def test_hdf5_model_relaxed_path_threshold_exceeded_raises(file_stimuli,...
  function test_hdf5_model_custom_max_normalization_error (line 768) | def test_hdf5_model_custom_max_normalization_error(file_stimuli, tmpdir):
  function test_hdf5_model_threshold_within_bounds_for_float16_4x (line 785) | def test_hdf5_model_threshold_within_bounds_for_float16_4x(file_stimuli,...
  function test_hdf5_model_max_normalization_error_none (line 809) | def test_hdf5_model_max_normalization_error_none(file_stimuli, tmpdir):

FILE: tests/test_quilt.py
  class TestPatchFile (line 13) | class TestPatchFile(TestWithData):
    method test_parsing (line 14) | def test_parsing(self):
    method test_apply (line 30) | def test_apply(self):
  class TestSeries (line 42) | class TestSeries(TestWithData):
    method test_parsing (line 43) | def test_parsing(self):
    method test_apply (line 47) | def test_apply(self):

FILE: tests/test_saliency_map_conversion.py
  function stimuli (line 10) | def stimuli():
  function saliency_model (line 15) | def saliency_model():
  function transformed_saliency_model (line 20) | def transformed_saliency_model(saliency_model):
  function probabilistic_model (line 28) | def probabilistic_model(saliency_model):
  function fixations (line 41) | def fixations(stimuli, probabilistic_model):
  function framework (line 46) | def framework(request):
  function test_optimize_for_information_gain (line 59) | def test_optimize_for_information_gain(stimuli, fixations, transformed_s...

FILE: tests/test_saliency_map_conversion_theano.py
  function test_optimize_for_IG (line 19) | def test_optimize_for_IG(optimize):
  function test_saliency_map_converter (line 58) | def test_saliency_map_converter(tmpdir):

FILE: tests/test_saliency_map_conversion_torch.py
  function test_optimize_for_IG (line 17) | def test_optimize_for_IG(optimize):

FILE: tests/test_saliency_map_conversion_torch_extended.py
  function stimuli (line 15) | def stimuli():
  function saliency_model (line 20) | def saliency_model():
  function num_nonlinearity (line 31) | def num_nonlinearity(request):
  function is_blurring (line 38) | def is_blurring(request):
  function num_centerbias (line 48) | def num_centerbias(request):
  function has_alpha (line 56) | def has_alpha(request):
  function probabilistic_model (line 61) | def probabilistic_model(saliency_model, is_blurring, num_nonlinearity, h...
  function fixations (line 101) | def fixations(stimuli, probabilistic_model):
  function test_optimize_for_information_gain (line 105) | def test_optimize_for_information_gain(stimuli, fixations, saliency_mode...
  function test_saliency_map_processing_model_save_and_load (line 161) | def test_saliency_map_processing_model_save_and_load(stimuli, saliency_m...
  function test_optimize_saliency_map_processing_disk_caching (line 175) | def test_optimize_saliency_map_processing_disk_caching(tmp_path, stimuli...

FILE: tests/test_saliency_map_models.py
  class ConstantSaliencyMapModel (line 12) | class ConstantSaliencyMapModel(pysaliency.SaliencyMapModel):
    method __init__ (line 13) | def __init__(self, value=1.0, *args, **kwargs):
    method _saliency_map (line 17) | def _saliency_map(self, stimulus):
  class GaussianSaliencyMapModel (line 21) | class GaussianSaliencyMapModel(pysaliency.SaliencyMapModel, pysaliency.s...
    method _saliency_map (line 22) | def _saliency_map(self, stimulus):
  class MixedSaliencyMapModel (line 31) | class MixedSaliencyMapModel(pysaliency.SaliencyMapModel):
    method __init__ (line 32) | def __init__(self, *args, **kwargs):
    method _saliency_map (line 38) | def _saliency_map(self, stimulus):
  class GaussianDensityModel (line 46) | class GaussianDensityModel(pysaliency.Model):
    method _log_density (line 47) | def _log_density(self, stimulus):
  function scanpath_fixations (line 60) | def scanpath_fixations():
  function stimuli (line 79) | def stimuli():
  function more_scanpath_fixations (line 85) | def more_scanpath_fixations():
  function more_stimuli (line 107) | def more_stimuli():
  function test_auc_constant (line 113) | def test_auc_constant(stimuli, scanpath_fixations):
  function test_auc_gauss (line 129) | def test_auc_gauss(stimuli, scanpath_fixations):
  function test_auc_per_image (line 187) | def test_auc_per_image(stimuli, scanpath_fixations):
  function test_auc_per_image_images_without_fixations (line 196) | def test_auc_per_image_images_without_fixations(stimuli, scanpath_fixati...
  function test_auc_image_average_with_images_without_fixations (line 205) | def test_auc_image_average_with_images_without_fixations(stimuli, scanpa...
  function test_nss_gauss (line 212) | def test_nss_gauss(stimuli, scanpath_fixations):
  function test_nss_uniform (line 236) | def test_nss_uniform(stimuli, scanpath_fixations):
  function test_cc_constant (line 252) | def test_cc_constant(stimuli, scanpath_fixations):
  function test_cc_gauss (line 260) | def test_cc_gauss(stimuli, scanpath_fixations):
  function test_SIM_gauss (line 268) | def test_SIM_gauss(stimuli, scanpath_fixations):
  function test_fixation_based_kldiv_constant (line 284) | def test_fixation_based_kldiv_constant(stimuli, scanpath_fixations):
  function test_fixation_based_kldiv_gauss (line 297) | def test_fixation_based_kldiv_gauss(stimuli, scanpath_fixations):
  function test_fixation_based_kldiv_mixed (line 310) | def test_fixation_based_kldiv_mixed(stimuli, scanpath_fixations):
  function test_image_based_kldiv_gauss (line 323) | def test_image_based_kldiv_gauss(stimuli, scanpath_fixations):
  function test_image_based_kldiv_gauss_functions (line 361) | def test_image_based_kldiv_gauss_functions(stimuli, scanpath_fixations):
  function test_shuffled_nonfixation_provider (line 403) | def test_shuffled_nonfixation_provider(more_stimuli, more_scanpath_fixat...
  function test_lambda_saliency_map_model (line 413) | def test_lambda_saliency_map_model():
  function test_saliency_map_model_operators (line 431) | def test_saliency_map_model_operators():
  function test_fixation_map_model (line 447) | def test_fixation_map_model(stimuli, scanpath_fixations):
  function test_fixation_map_model_ignore_doublicates (line 455) | def test_fixation_map_model_ignore_doublicates(stimuli, scanpath_fixatio...
  function test_get_unfixated_values (line 464) | def test_get_unfixated_values():
  function test_density_map_model (line 471) | def test_density_map_model(stimuli):
  function test_log_density_map_model (line 480) | def test_log_density_map_model(stimuli):
  function test_wta_sampling (line 489) | def test_wta_sampling(stimuli):
  function test_subject_specific_saliency_map_model (line 502) | def test_subject_specific_saliency_map_model(stimuli, scanpath_fixations):
  function test_conditional_saliency_maps (line 514) | def test_conditional_saliency_maps(stimuli, scanpath_fixations):
  function test_stimulus_dependent_saliency_map_model (line 523) | def test_stimulus_dependent_saliency_map_model(stimuli, scanpath_fixatio...

FILE: tests/test_sampling.py
  function test_fixation_sampling (line 7) | def test_fixation_sampling():
  function test_scanpath_sampling (line 20) | def test_scanpath_sampling():

FILE: tests/test_torch_datasets.py
  function stimuli (line 19) | def stimuli(tmp_path):
  function fixations (line 32) | def fixations(stimuli):
  function saliency_model (line 37) | def saliency_model():
  function png_saliency_map_model (line 42) | def png_saliency_map_model(tmp_path, stimuli, saliency_model):
  function test_dataset (line 56) | def test_dataset(stimuli, fixations, png_saliency_map_model):

FILE: tests/test_torch_utils.py
  function sigma (line 15) | def sigma(request):
  function dtype (line 20) | def dtype(request):
  function test_gaussian_filter (line 24) | def test_gaussian_filter(sigma, dtype):
  function test_compare_gaussian_1d_implementations (line 65) | def test_compare_gaussian_1d_implementations(data, sigma, dim):

FILE: tests/test_utils.py
  function test_minimal_unique_filenames (line 14) | def test_minimal_unique_filenames():
  class TestLazyList (line 29) | class TestLazyList(TestWithData):
    method test_lazy_list (line 30) | def test_lazy_list(self):
    method test_pickle_no_cache (line 48) | def test_pickle_no_cache(self):
    method test_pickle_with_cache (line 61) | def test_pickle_with_cache(self):
  function test_atomic_directory_setup_success (line 77) | def test_atomic_directory_setup_success(tmp_path):
  function test_atomic_directory_setup_failure (line 87) | def test_atomic_directory_setup_failure(tmp_path):
  function test_atomic_directory_setup_success_no_location (line 103) | def test_atomic_directory_setup_success_no_location():
  function test_atomic_directory_setup_failure_no_location (line 109) | def test_atomic_directory_setup_failure_no_location():
  class TestCache (line 122) | class TestCache(TestWithData):
    method test_basics (line 123) | def test_basics(self):
    method test_cache_to_disk (line 138) | def test_cache_to_disk(self):
    method test_cache_to_disk_nonexisting_location (line 162) | def test_cache_to_disk_nonexisting_location(self):
    method test_pickle_cache (line 187) | def test_pickle_cache(self):
    method test_pickle_cache_with_location (line 202) | def test_pickle_cache_with_location(self):
  function test_build_padded_2d_array (line 222) | def test_build_padded_2d_array():

FILE: tests/utils/test_variable_length_array.py
  function test_variable_length_array_from_padded_array_basics (line 8) | def test_variable_length_array_from_padded_array_basics():
  function test_variable_length_array_from_padded_array (line 20) | def test_variable_length_array_from_padded_array():
  function test_variable_length_array_slicing_with_slices (line 72) | def test_variable_length_array_slicing_with_slices():
  function test_variable_length_array_slicing_with_indices (line 92) | def test_variable_length_array_slicing_with_indices():
  function test_variable_length_array_slicing_with_mask (line 105) | def test_variable_length_array_slicing_with_mask():
  function test_variable_length_array_from_list_of_arrays (line 118) | def test_variable_length_array_from_list_of_arrays():
  function test_variable_length_array_from_list_of_arrays_without_specified_lengths (line 127) | def test_variable_length_array_from_list_of_arrays_without_specified_len...
  function test_variable_length_array_inconsistent_lengths (line 136) | def test_variable_length_array_inconsistent_lengths():
  function test_variable_length_array_copy (line 150) | def test_variable_length_array_copy():
  function test_variable_length_array_concatenate (line 167) | def test_variable_length_array_concatenate():
  function test_variable_length_array_zero_length_from_2d_array (line 183) | def test_variable_length_array_zero_length_from_2d_array():
  function test_variable_length_array_zero_length_from_list (line 194) | def test_variable_length_array_zero_length_from_list():
Condensed preview — 190 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,472K chars).
[
  {
    "path": ".github/workflows/test-package-conda.yml",
    "chars": 1710,
    "preview": "name: Tests\n\non: [push, pull_request]\n\njobs:\n  build-linux:\n    runs-on: ubuntu-latest\n    strategy:\n      max-parallel:"
  },
  {
    "path": ".gitignore",
    "chars": 140,
    "preview": ".hypothesis\n.mypy_cache\n/tmp/\n/build/\n/pysaliency_datasets/\n/test_datasets/\n/test_models/\n*.pyc\n*.swp\n*.c\n*.so\n*.egg-inf"
  },
  {
    "path": ".travis.yml",
    "chars": 1886,
    "preview": "language: python\npython:\n#  - 2.7\n  - 3.6\n  - 3.7\nbefore_install:\n  - sudo apt-get update\n  - sudo apt-get install g++\n "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 8893,
    "preview": "# Changelog\n\n* 0.3 (dev):\n  This is a major release, refactoring most of `pysaliency.datasets` including some breaking c"
  },
  {
    "path": "LICENSE.md",
    "chars": 1084,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Matthias Kümmerer\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "MANIFEST.in",
    "chars": 357,
    "preview": "include README.md\ninclude LICENSE.md\ninclude pysaliency/*.pyx\ninclude pysaliency/scripts/*.m\ninclude pysaliency/scripts/"
  },
  {
    "path": "Makefile",
    "chars": 566,
    "preview": "cython:\n\t#python setup.py build_ext --inplace\n\tpython3 setup.py build_ext --inplace\n\ntest: cython\n\tpython3 -m pytest --n"
  },
  {
    "path": "README.md",
    "chars": 2825,
    "preview": "Pysaliency\n==========\n\n![test](https://github.com/matthias-k/pysaliency/actions/workflows/test-package-conda.yml/badge.s"
  },
  {
    "path": "docs/superpowers/plans/2026-03-21-crossvalidated-baseline-model-hdf5.md",
    "chars": 17722,
    "preview": "# CrossvalidatedBaselineModel HDF5 Save/Reload Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Use s"
  },
  {
    "path": "docs/superpowers/plans/2026-03-23-compact-hdf5-export.md",
    "chars": 40645,
    "preview": "# Compact HDF5 Export Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-drive"
  },
  {
    "path": "docs/superpowers/specs/2026-03-21-model-hdf5-save-reload-design.md",
    "chars": 8481,
    "preview": "# Design: HDF5 Save/Reload for Saliency Models\n\n**Date:** 2026-03-21\n**Branch:** enh-save-baseline-model-to-hdf5\n**Statu"
  },
  {
    "path": "docs/superpowers/specs/2026-03-23-hdf5-compact-export-design.md",
    "chars": 16423,
    "preview": "# Design: Compact HDF5 Export for Model Predictions\n\n**Date:** 2026-03-23\n**Status:** Approved\n\n## Background\n\n`pysalien"
  },
  {
    "path": "examples/docker_submission/README.md",
    "chars": 2337,
    "preview": "# Submission\n\nThis directory contains an example of how to create a Docker container for submitting a scanpath model to "
  },
  {
    "path": "examples/docker_submission/docker_deepgaze3/Dockerfile",
    "chars": 764,
    "preview": "# Specify a base image depending on the project.\nFROM bitnami/python:3.8\n# For more complex examples, might need to use "
  },
  {
    "path": "examples/docker_submission/docker_deepgaze3/model_server.py",
    "chars": 2625,
    "preview": "from flask import Flask, request\nimport numpy as np\nimport json\nfrom PIL import Image\nfrom io import BytesIO\nimport orjs"
  },
  {
    "path": "examples/docker_submission/docker_deepgaze3/requirements.txt",
    "chars": 142,
    "preview": "cython\nflask\ngunicorn\nnumpy\n\n# Add additional dependencies here\npysaliency\nscipy\ntorch\nflask_orjson\ngit+https://github.c"
  },
  {
    "path": "examples/docker_submission/docker_pysaliency_model/Dockerfile",
    "chars": 762,
    "preview": "# Specify a base image depending on the project.\nFROM bitnami/python:3.8\n# For more complex examples, might need to use "
  },
  {
    "path": "examples/docker_submission/docker_pysaliency_model/model_server.py",
    "chars": 1366,
    "preview": "from flask import Flask, request, jsonify\nfrom flask_orjson import OrjsonProvider\nimport numpy as np\nimport json\nfrom PI"
  },
  {
    "path": "examples/docker_submission/docker_pysaliency_model/requirements.txt",
    "chars": 100,
    "preview": "cython\nflask\ngunicorn\nnumpy\n\n# Add additional dependencies here\npysaliency\nscipy\ntorch\nflask_orjson\n"
  },
  {
    "path": "examples/docker_submission/docker_pysaliency_model/sample_submission.py",
    "chars": 2990,
    "preview": "\"\"\"\nThis file implements a very simple scanpath model using local constrast and a saccadic prior.\n\"\"\"\n\nimport numpy as n"
  },
  {
    "path": "examples/docker_submission/sample_evaluation.py",
    "chars": 1956,
    "preview": "import numpy as np\nimport sys\nfrom pysaliency.http_models import HTTPScanpathModel\nsys.path.insert(0, '..')\nimport pysal"
  },
  {
    "path": "notebooks/LSUN.ipynb",
    "chars": 6179,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# LSUN Challenge\"\n   ]\n  },\n  {\n   "
  },
  {
    "path": "notebooks/Tutorial.ipynb",
    "chars": 4347442,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"18dc5e5a-ece3-477c-9090-b4265ba04942\",\n   \"metadata\": {},\n   \"so"
  },
  {
    "path": "pyproject.toml",
    "chars": 247,
    "preview": "[tool.ruff]\nselect = [\"B\", \"E\", \"F\", \"FIX\", \"I\", \"T20\"]\nline-length = 200\nignore = [\"T201\"]  # ignore print statements\n\n"
  },
  {
    "path": "pysaliency/__init__.py",
    "chars": 2383,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom . import datasets\nfrom . import"
  },
  {
    "path": "pysaliency/baseline_utils.py",
    "chars": 27158,
    "preview": "from collections import OrderedDict\nfrom typing import List\n\nimport numba\nimport numpy as np\nfrom boltons.iterutils impo"
  },
  {
    "path": "pysaliency/dataset_config.py",
    "chars": 2829,
    "preview": "from .datasets import read_hdf5, clip_out_of_stimulus_fixations, remove_out_of_stimulus_fixations\nfrom .filter_datasets "
  },
  {
    "path": "pysaliency/datasets/__init__.py",
    "chars": 7239,
    "preview": "from typing import Dict, List, Optional, Union\n\nimport numpy as np\n\nfrom ..hdf5 import read_hdf5 as _read_hdf5\nfrom .fix"
  },
  {
    "path": "pysaliency/datasets/fixations.py",
    "chars": 52796,
    "preview": "import json\nimport warnings\nfrom typing import Dict, List, Optional, Tuple, Union\n\nimport deprecation\nimport numpy as np"
  },
  {
    "path": "pysaliency/datasets/scanpaths.py",
    "chars": 10770,
    "preview": "import json\nfrom typing import Dict, List, Optional, Union\n\nimport numpy as np\n\nfrom ..utils.variable_length_array impor"
  },
  {
    "path": "pysaliency/datasets/stimuli.py",
    "chars": 16173,
    "preview": "import json\nimport os\nfrom collections.abc import Sequence\nfrom hashlib import sha1\nfrom typing import List, Union\n\nimpo"
  },
  {
    "path": "pysaliency/datasets/utils.py",
    "chars": 3910,
    "preview": "import json\nimport os\nimport pathlib\nimport warnings\nfrom collections.abc import Sequence\nfrom functools import wraps\nfr"
  },
  {
    "path": "pysaliency/external_datasets/__init__.py",
    "chars": 1141,
    "preview": "from __future__ import absolute_import, print_function, division\n\nimport zipfile\nimport os\nimport glob\n\nimport numpy as "
  },
  {
    "path": "pysaliency/external_datasets/cat2000.py",
    "chars": 17034,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport glob\nimport os\nimport warnings\nimport zipfile\nf"
  },
  {
    "path": "pysaliency/external_datasets/coco_freeview.py",
    "chars": 14785,
    "preview": "import json\nimport os\nimport zipfile\nfrom tempfile import TemporaryDirectory\n\nimport numpy as np\nfrom tqdm import tqdm\n\n"
  },
  {
    "path": "pysaliency/external_datasets/coco_search18.py",
    "chars": 13631,
    "preview": "import glob\nimport json\nimport os\nimport shutil\nimport zipfile\nfrom hashlib import md5\nfrom tempfile import TemporaryDir"
  },
  {
    "path": "pysaliency/external_datasets/dut_omrom.py",
    "chars": 4883,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport glob\nimport os\nimport zipfile\nfrom tempfile imp"
  },
  {
    "path": "pysaliency/external_datasets/figrim.py",
    "chars": 7303,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport glob\nimport os\nimport zipfile\nfrom tempfile imp"
  },
  {
    "path": "pysaliency/external_datasets/isun.py",
    "chars": 6649,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport os\nimport zipfile\nfrom tempfile import Temporar"
  },
  {
    "path": "pysaliency/external_datasets/koehler.py",
    "chars": 4623,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport itertools\nimport os\nimport zipfile\nfrom tempfil"
  },
  {
    "path": "pysaliency/external_datasets/mit.py",
    "chars": 20647,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport glob\nimport os\nimport zipfile\nfrom tempfile imp"
  },
  {
    "path": "pysaliency/external_datasets/nusef.py",
    "chars": 9423,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport glob\nimport os\nimport zipfile\nfrom datetime imp"
  },
  {
    "path": "pysaliency/external_datasets/osie.py",
    "chars": 3899,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport os\nimport urllib\nfrom tempfile import Temporary"
  },
  {
    "path": "pysaliency/external_datasets/pascal_s.py",
    "chars": 4505,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport os\nimport zipfile\nfrom tempfile import Temporar"
  },
  {
    "path": "pysaliency/external_datasets/salicon.py",
    "chars": 12178,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport glob\nimport os\nimport zipfile\nfrom tempfile imp"
  },
  {
    "path": "pysaliency/external_datasets/scripts/extract_fixations.m",
    "chars": 783,
    "preview": "function [ ] = extract_fixations(filename, datafolder, outname)\n\t% fprintf('Loading %s %s\\n', datafolder, filename);\n\tad"
  },
  {
    "path": "pysaliency/external_datasets/scripts/load_cat2000.m",
    "chars": 340,
    "preview": "load trainSet/allFixData.mat;\nind = 0\nks=keys(allData);\nfor i=1:size(ks,2);\n\tk=ks(i);\n\ttmp=allData(char(k));\n\tfor j=1:si"
  },
  {
    "path": "pysaliency/external_datasets/toronto.py",
    "chars": 8176,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport os\nimport warnings\nimport zipfile\nfrom tempfile"
  },
  {
    "path": "pysaliency/external_datasets/utils.py",
    "chars": 2480,
    "preview": "from __future__ import absolute_import, print_function, division\n\nimport os\nimport shutil\nimport warnings\n\nfrom ..datase"
  },
  {
    "path": "pysaliency/external_models/__init__.py",
    "chars": 272,
    "preview": "from .deepgaze import (\n    DeepGazeI,\n    DeepGazeIIE,\n)\nfrom .matlab_models import (\n    AIM,\n    BMS,\n    GBVS,\n    R"
  },
  {
    "path": "pysaliency/external_models/deepgaze.py",
    "chars": 2533,
    "preview": "import numpy as np\nimport torch\n\nfrom ..datasets import as_stimulus\nfrom ..models import Model\nfrom ..utils import as_rg"
  },
  {
    "path": "pysaliency/external_models/matlab_models.py",
    "chars": 28282,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nimport os\nimport zipfile\nfrom tempfi"
  },
  {
    "path": "pysaliency/external_models/models.py",
    "chars": 817,
    "preview": "from __future__ import absolute_import, print_function, division, unicode_literals\n\nimport os\nimport tempfile\nimport zip"
  },
  {
    "path": "pysaliency/external_models/scripts/AIM_wrapper.m",
    "chars": 192,
    "preview": "function [ ] = AIM_wrapper(filename, outname, convolve, filters)\n\n    addpath('AIM');\n\n    saliency_map = AIM(filename, "
  },
  {
    "path": "pysaliency/external_models/scripts/BMS/BMS_wrapper.m",
    "chars": 576,
    "preview": "function [ ] = BMS(filename, outname)\r\n\r\n    addpath('source')\r\n\r\n\tdirectory = sprintf('tmp_%s', int2str(randi(1e15)));\r"
  },
  {
    "path": "pysaliency/external_models/scripts/BMS/patches/adapt_opencv_paths.diff",
    "chars": 1719,
    "preview": "Index: src/mex/compile.m\n===================================================================\n--- src.orig/mex/compile.m\t"
  },
  {
    "path": "pysaliency/external_models/scripts/BMS/patches/correct_add_path.diff",
    "chars": 525,
    "preview": "Index: src/BMS.m\n===================================================================\n--- src.orig/BMS.m\t2013-09-14 12:28"
  },
  {
    "path": "pysaliency/external_models/scripts/BMS/patches/fix_FileGettor.diff",
    "chars": 676,
    "preview": "Index: src/mex/fileGettor.h\n===================================================================\n--- src.orig/mex/fileGet"
  },
  {
    "path": "pysaliency/external_models/scripts/BMS/patches/series",
    "chars": 66,
    "preview": "adapt_opencv_paths.diff\ncorrect_add_path.diff\nfix_FileGettor.diff\n"
  },
  {
    "path": "pysaliency/external_models/scripts/ContextAwareSaliency_wrapper.m",
    "chars": 338,
    "preview": "function [ ] = ContextAwareSaliency(filename, outname)\r\n\r\n    addpath('source')\r\n\r\n\tfile_names{1} = filename;\r\n\timg = im"
  },
  {
    "path": "pysaliency/external_models/scripts/CovSal_wrapper.m",
    "chars": 377,
    "preview": "function [ ] = CovSal_wrapper(filename, outname, size, quantile, centerbias, modeltype)\r\n    addpath('saliency')\r\n\r\n    "
  },
  {
    "path": "pysaliency/external_models/scripts/GBVS/GBVSIttiKoch_wrapper.m",
    "chars": 374,
    "preview": "function [ ] = GBVSIttiKoch_wrapper(filename, outname)\r\n\r\n    addpath('gbvs')\r\n    addpath('gbvs/algsrc')\r\n    addpath('"
  },
  {
    "path": "pysaliency/external_models/scripts/GBVS/GBVS_wrapper.m",
    "chars": 365,
    "preview": "function [ ] = GBVS_rwapper(filename, outname)\r\n\r\n    addpath('gbvs')\r\n    addpath('gbvs/algsrc')\r\n    addpath('gbvs/com"
  },
  {
    "path": "pysaliency/external_models/scripts/GBVS/patches/get_path",
    "chars": 1158,
    "preview": "Index: src/algsrc/initGBVS.m\n===================================================================\n--- src.orig/algsrc/ini"
  },
  {
    "path": "pysaliency/external_models/scripts/GBVS/patches/make_mex_files_octave_compatible",
    "chars": 3376,
    "preview": "Index: src/algsrc/mexArrangeLinear.cc\n===================================================================\n--- src.orig/a"
  },
  {
    "path": "pysaliency/external_models/scripts/GBVS/patches/series",
    "chars": 42,
    "preview": "get_path\nmake_mex_files_octave_compatible\n"
  },
  {
    "path": "pysaliency/external_models/scripts/IttiKoch_wrapper.m",
    "chars": 298,
    "preview": "function [ ] = IttiKoch(filename, outname)\r\n\r\n    addpath('SaliencyToolbox')\r\n\r\n    img = initializeImage(filename);\r\n  "
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/FaceDetect_patches/change_opencv_include",
    "chars": 483,
    "preview": "Index: FaceDetect/src/FaceDetect.cpp\n===================================================================\n--- FaceDetect."
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/FaceDetect_patches/series",
    "chars": 22,
    "preview": "change_opencv_include\n"
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/JuddSaliencyModel_patches/find_cascade_file",
    "chars": 580,
    "preview": "Index: JuddSaliencyModel/findObjectFeatures.m\n===================================================================\n--- Ju"
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/JuddSaliencyModel_patches/locate_FelzenszwalbDetector_files",
    "chars": 1226,
    "preview": "Index: JuddSaliencyModel/findObjectFeatures.m\n===================================================================\n--- Ju"
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/JuddSaliencyModel_patches/series",
    "chars": 52,
    "preview": "locate_FelzenszwalbDetector_files\nfind_cascade_file\n"
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/Judd_wrapper.m",
    "chars": 457,
    "preview": "function [ ] = Judd(filename, outname)\r\n\r\n    addpath('source/FaceDetect')\r\n    addpath('source/FaceDetect/src')\r\n    ad"
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/SaliencyToolbox_patches/enable_unit16",
    "chars": 2698,
    "preview": "Index: SaliencyToolbox/centerSurround.m\n===================================================================\n--- Saliency"
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/SaliencyToolbox_patches/series",
    "chars": 14,
    "preview": "enable_unit16\n"
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/voc_patches/change_fconv",
    "chars": 577,
    "preview": "Index: voc-release3.1/compile.m\n===================================================================\n--- voc-release3.1.o"
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/voc_patches/matlabR2014a_compatible",
    "chars": 622,
    "preview": "Index: voc-release3.1/compile.m\n===================================================================\n--- voc-release3.1.o"
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/voc_patches/matlabR2021a_compatible",
    "chars": 2241,
    "preview": "Index: voc-release3.1/resize.cc\n===================================================================\n--- voc-release3.1.o"
  },
  {
    "path": "pysaliency/external_models/scripts/Judd/voc_patches/series",
    "chars": 61,
    "preview": "change_fconv\nmatlabR2014a_compatible\nmatlabR2021a_compatible\n"
  },
  {
    "path": "pysaliency/external_models/scripts/RARE2012_wrapper.m",
    "chars": 244,
    "preview": "function [ ] =  RARE2012(filename, outname)\r\n\r\n    addpath('source/VisualAttention-Rare2012-55ba7414b971429e5e899ddfa574"
  },
  {
    "path": "pysaliency/external_models/scripts/SUN_wrapper.m",
    "chars": 644,
    "preview": "% created by Aykut Erdem\r\n% adapted by Matthias Kuemmerer for pysaliency\r\n\r\nfunction [ ] = SUN(filename, outname, scale)"
  },
  {
    "path": "pysaliency/external_models/scripts/ensure_image_is_color_image.m",
    "chars": 281,
    "preview": "function [new_img] = ensure_image_is_color_image(img)\n    if length(size(img)) == 2\n        new_img = ones(size(img,1), "
  },
  {
    "path": "pysaliency/external_models/utils.py",
    "chars": 3641,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nimport os\nimport tarfile\nimport temp"
  },
  {
    "path": "pysaliency/filter_datasets.py",
    "chars": 11421,
    "preview": "from __future__ import division, print_function\n\nimport numpy as np\n\nfrom boltons.iterutils import chunked\n\nfrom .datase"
  },
  {
    "path": "pysaliency/hdf5.py",
    "chars": 2390,
    "preview": "import pathlib\nfrom weakref import WeakValueDictionary\n\nfrom boltons.cacheutils import cached\n\nfrom .datasets.fixations "
  },
  {
    "path": "pysaliency/http_models.py",
    "chars": 5499,
    "preview": "from .models import ScanpathModel\nfrom .saliency_map_models import ScanpathSaliencyMapModel\nfrom PIL import Image\nfrom i"
  },
  {
    "path": "pysaliency/metric_optimization.py",
    "chars": 3760,
    "preview": "from __future__ import print_function, division, absolute_import, unicode_literals\n\nfrom .saliency_map_models import Sal"
  },
  {
    "path": "pysaliency/metric_optimization_tf.py",
    "chars": 11711,
    "preview": "from __future__ import print_function, division, absolute_import, unicode_literals\n\nfrom tqdm import tqdm\n\nimport numpy "
  },
  {
    "path": "pysaliency/metric_optimization_torch.py",
    "chars": 12048,
    "preview": "import numpy as np\nfrom scipy.ndimage import gaussian_filter as sp_gaussian_filter\nfrom scipy.special import logsumexp\ni"
  },
  {
    "path": "pysaliency/metrics.py",
    "chars": 3526,
    "preview": "from __future__ import absolute_import, print_function, division, unicode_literals\n\nimport numpy as np\n\n\ndef normalize_s"
  },
  {
    "path": "pysaliency/models.py",
    "chars": 44707,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nimport warnings\nfrom abc import ABCM"
  },
  {
    "path": "pysaliency/numba_utils.py",
    "chars": 5272,
    "preview": "from __future__ import print_function, unicode_literals, division, absolute_import\n\nimport numba\nimport numpy as np\n\n# n"
  },
  {
    "path": "pysaliency/optpy/README.md",
    "chars": 145,
    "preview": "Copied from https://github.com/matthias-k/optpy since that name is already taken on pypi and I'm not sure whether it's w"
  },
  {
    "path": "pysaliency/optpy/__init__.py",
    "chars": 195,
    "preview": "from __future__ import absolute_import\n\nfrom .optimization import ParameterManager, minimize, LinearConstraint\nfrom .jac"
  },
  {
    "path": "pysaliency/optpy/jacobian.py",
    "chars": 2193,
    "preview": "\"\"\"\nAuthor: Matthias Kuemmerer, 2014\n\n\"\"\"\nfrom __future__ import print_function, division, unicode_literals, absolute_im"
  },
  {
    "path": "pysaliency/optpy/optimization.py",
    "chars": 10661,
    "preview": "\"\"\"\nAuthor: Matthias Kuemmerer, 2014\n\nSome wrappers around scipy.optimize.minimize to make optimization\nof functions wit"
  },
  {
    "path": "pysaliency/plotting.py",
    "chars": 12758,
    "preview": "from __future__ import absolute_import, print_function, division, unicode_literals\nfrom typing import List, Union\n\ntry:\n"
  },
  {
    "path": "pysaliency/precomputed_models.py",
    "chars": 24222,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport glob\nimport logging\nimport os.path\nimport tarfi"
  },
  {
    "path": "pysaliency/quilt.py",
    "chars": 5072,
    "preview": "\"\"\"\nCode to apply quilt patches to files\n\nThis module enables pysaliency to use quilt patches\nto patch code from externa"
  },
  {
    "path": "pysaliency/roc.py",
    "chars": 120,
    "preview": "from .numba_utils import general_roc_numba as general_roc, general_rocs_per_positive_numba as general_rocs_per_positive\n"
  },
  {
    "path": "pysaliency/roc_cython.pyx",
    "chars": 6595,
    "preview": "#%%cython\n# Circumvents a bug(?) in cython:\n# http://stackoverflow.com/a/13976504\nSTUFF = \"Hi\"\n\n\nimport numpy as np\ncimp"
  },
  {
    "path": "pysaliency/saliency_map_conversion.py",
    "chars": 2973,
    "preview": "# -*- coding: UTF-8 -*-\nfrom __future__ import absolute_import, division, print_function  # , unicode_literals\n\nimport n"
  },
  {
    "path": "pysaliency/saliency_map_conversion_theano.py",
    "chars": 27456,
    "preview": "# -*- coding: UTF-8 -*-\nfrom __future__ import absolute_import, division, print_function  # , unicode_literals\n\nimport s"
  },
  {
    "path": "pysaliency/saliency_map_conversion_torch.py",
    "chars": 20630,
    "preview": "import numpy as np\nimport torch\nimport torch.nn as nn\nfrom tqdm import tqdm\n\nfrom .models import Model\nfrom .optpy impor"
  },
  {
    "path": "pysaliency/saliency_map_models.py",
    "chars": 47782,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nimport os\nimport warnings\nfrom abc i"
  },
  {
    "path": "pysaliency/sampling_models.py",
    "chars": 1864,
    "preview": "from __future__ import print_function, absolute_import, division, unicode_literals\n\nfrom abc import ABCMeta, abstractmet"
  },
  {
    "path": "pysaliency/tf_utils.py",
    "chars": 3922,
    "preview": "from __future__ import print_function, division, absolute_import, unicode_literals\n\nimport tensorflow as tf\nslim = tf.co"
  },
  {
    "path": "pysaliency/theano_utils.py",
    "chars": 13801,
    "preview": "from __future__ import absolute_import, print_function, division#, unicode_literals\n\nimport numpy as np\n\nimport theano\ni"
  },
  {
    "path": "pysaliency/torch_datasets.py",
    "chars": 5642,
    "preview": "import random\n\nimport numpy as np\nimport torch\nfrom boltons.iterutils import chunked\nfrom torch.utils.data.dataloader im"
  },
  {
    "path": "pysaliency/torch_utils.py",
    "chars": 8932,
    "preview": "import math\n\nfrom boltons.iterutils import windowed\nimport numpy as np\nfrom packaging import version\nimport torch\nimport"
  },
  {
    "path": "pysaliency/utils/__init__.py",
    "chars": 14635,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport hashlib\nimport os\nimport os as _os\nimport shuti"
  },
  {
    "path": "pysaliency/utils/variable_length_array.py",
    "chars": 5777,
    "preview": "from typing import Optional, Union, List\n\nimport numpy as np\n\nfrom . import build_padded_2d_array\n\n\nclass VariableLength"
  },
  {
    "path": "pytest.ini",
    "chars": 522,
    "preview": "[pytest]\nmarkers =\n    slow: marks tests as being slow (skipped by default, select with '--runslow')\n    nonfree: marks "
  },
  {
    "path": "requirements.txt",
    "chars": 45,
    "preview": "Cython\ndill\nh5py\nimageio\nnatsort\nnumpy\nscipy\n"
  },
  {
    "path": "setup.py",
    "chars": 2660,
    "preview": "# -*- coding: utf-8 -*-\nfrom os import path\n\nfrom setuptools import setup, find_packages\nfrom setuptools.extension impor"
  },
  {
    "path": "tests/conftest.py",
    "chars": 5081,
    "preview": "import os\nimport sys\nfrom pathlib import Path\nfrom unittest.mock import MagicMock\n\nimport pytest\nimport requests\nfrom tq"
  },
  {
    "path": "tests/datasets/test_datasets.py",
    "chars": 8750,
    "preview": "import numpy as np\nimport pytest\nfrom imageio import imwrite\n\nimport pysaliency\nfrom tests.datasets.utils import assert_"
  },
  {
    "path": "tests/datasets/test_fixations.py",
    "chars": 26719,
    "preview": "import os.path\nimport pickle\n\nimport numpy as np\nimport pytest\nfrom hypothesis import given\nfrom hypothesis import strat"
  },
  {
    "path": "tests/datasets/test_scanpaths.py",
    "chars": 21072,
    "preview": "from copy import deepcopy\n\nimport numpy as np\nimport pytest\n\nimport pysaliency\nfrom pysaliency.datasets import Scanpaths"
  },
  {
    "path": "tests/datasets/test_stimuli.py",
    "chars": 15482,
    "preview": "from __future__ import absolute_import, division, print_function\n\nimport os.path\nimport pickle\nimport unittest\nfrom copy"
  },
  {
    "path": "tests/datasets/utils.py",
    "chars": 5477,
    "preview": "from pysaliency.datasets import ScanpathFixations\nfrom pysaliency.datasets.scanpaths import Scanpaths\n\n\nimport numpy as "
  },
  {
    "path": "tests/external_datasets/test_COCO_Search18.py",
    "chars": 13753,
    "preview": "import numpy as np\nimport pytest\nfrom pytest import approx\nimport pysaliency\nfrom scipy.stats import kurtosis, skew\n\nfro"
  },
  {
    "path": "tests/external_datasets/test_NUSEF.py",
    "chars": 2247,
    "preview": "import numpy as np\nimport pytest\nfrom pytest import approx\nfrom scipy.stats import kurtosis, skew\n\nimport pysaliency\nfro"
  },
  {
    "path": "tests/external_datasets/test_PASCAL_S.py",
    "chars": 2116,
    "preview": "import numpy as np\nimport pytest\nfrom pytest import approx\nfrom scipy.stats import kurtosis, skew\n\nimport pysaliency\nimp"
  },
  {
    "path": "tests/external_datasets/test_SALICON.py",
    "chars": 14251,
    "preview": "import numpy as np\nimport pytest\nfrom pytest import approx\nfrom scipy.stats import kurtosis, skew\n\nimport pysaliency\nimp"
  },
  {
    "path": "tests/external_datasets/test_coco_freeview.py",
    "chars": 10897,
    "preview": "import numpy as np\nimport pytest\nfrom pytest import approx\nimport pysaliency\nfrom scipy.stats import kurtosis, skew\n\nfro"
  },
  {
    "path": "tests/external_models/test_deepgaze.py",
    "chars": 1310,
    "preview": "import os\n\nimport numpy as np\nimport pytest\n\nimport pysaliency\nfrom pysaliency.external_models.deepgaze import DeepGazeI"
  },
  {
    "path": "tests/skippedtest_theano_utils.py",
    "chars": 7221,
    "preview": "from __future__ import absolute_import, print_function, division, unicode_literals\n\nimport unittest\nimport pytest\n\nimpor"
  },
  {
    "path": "tests/test_baseline_utils.py",
    "chars": 10138,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom collections import OrderedDict\n"
  },
  {
    "path": "tests/test_crossvalidation.py",
    "chars": 3372,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nimport numpy as np\nimport pytest\nfro"
  },
  {
    "path": "tests/test_dataset_config.py",
    "chars": 7729,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nimport os\n\nimport numpy as np\nimport"
  },
  {
    "path": "tests/test_external_datasets.py",
    "chars": 21021,
    "preview": "import numpy as np\nimport pytest\nfrom pathlib import Path\nfrom pytest import approx\nfrom scipy.stats import kurtosis, sk"
  },
  {
    "path": "tests/test_external_models.py",
    "chars": 7706,
    "preview": "from __future__ import absolute_import, print_function, division\n\nimport os\n\nimport pytest\n\nimport numpy as np\n\nimport p"
  },
  {
    "path": "tests/test_filter_datasets.py",
    "chars": 19360,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nimport numpy as np\nimport pytest\nfro"
  },
  {
    "path": "tests/test_hdf5_io.py",
    "chars": 4343,
    "preview": "import numpy as np\n\nimport pysaliency\nfrom pysaliency.baseline_utils import BaselineModel, CrossvalidatedBaselineModel\n\n"
  },
  {
    "path": "tests/test_helpers.py",
    "chars": 1198,
    "preview": "from __future__ import absolute_import, print_function, division\n\nimport unittest\nimport pickle\nimport os.path\nimport sh"
  },
  {
    "path": "tests/test_http_models.py",
    "chars": 10198,
    "preview": "import unittest\nimport numpy as np\nfrom unittest.mock import patch, MagicMock, Mock\nimport json\n\nimport pysaliency\nfrom "
  },
  {
    "path": "tests/test_metric_optimization.py",
    "chars": 1074,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nimport pytest\nimport numpy as np\n\nim"
  },
  {
    "path": "tests/test_metric_optimization_tf.py",
    "chars": 1523,
    "preview": "import numpy as np\nimport pytest\n\n\n# from pysaliency.metric_optimization_tf import maximize_expected_sim\n\n\n@pytest.mark."
  },
  {
    "path": "tests/test_metric_optimization_torch.py",
    "chars": 1378,
    "preview": "import numpy as np\n\nfrom pysaliency.metric_optimization_torch import maximize_expected_sim\n\n\ndef test_maximize_expected_"
  },
  {
    "path": "tests/test_models.py",
    "chars": 7480,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nimport pytest\nimport numpy as np\n\nim"
  },
  {
    "path": "tests/test_numba_utils.py",
    "chars": 2522,
    "preview": "from hypothesis import given, strategies as st, assume, settings\nimport numpy as np\n\nfrom pysaliency.numba_utils import "
  },
  {
    "path": "tests/test_precomputed_models.py",
    "chars": 35992,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nimport os\nimport pathlib\nimport warn"
  },
  {
    "path": "tests/test_quilt/.pc/.quilt_patches",
    "chars": 8,
    "preview": "patches\n"
  },
  {
    "path": "tests/test_quilt/.pc/.quilt_series",
    "chars": 7,
    "preview": "series\n"
  },
  {
    "path": "tests/test_quilt/.pc/.version",
    "chars": 2,
    "preview": "2\n"
  },
  {
    "path": "tests/test_quilt/.pc/add_numbers.diff/.timestamp",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/test_quilt/.pc/add_numbers.diff/source.txt",
    "chars": 76,
    "preview": "foo\nbar\nbaz\nabcefg\nblub\n3.14156\n2.718281828459045\n0.7071\n1.4142135623730951\n"
  },
  {
    "path": "tests/test_quilt/.pc/applied-patches",
    "chars": 17,
    "preview": "add_numbers.diff\n"
  },
  {
    "path": "tests/test_quilt/patches/add_numbers.diff",
    "chars": 305,
    "preview": "Index: test_quilt/source.txt\n===================================================================\n--- test_quilt.orig/sou"
  },
  {
    "path": "tests/test_quilt/patches/series",
    "chars": 17,
    "preview": "add_numbers.diff\n"
  },
  {
    "path": "tests/test_quilt/source/.pc/.quilt_patches",
    "chars": 8,
    "preview": "patches\n"
  },
  {
    "path": "tests/test_quilt/source/.pc/.quilt_series",
    "chars": 7,
    "preview": "series\n"
  },
  {
    "path": "tests/test_quilt/source/.pc/.version",
    "chars": 2,
    "preview": "2\n"
  },
  {
    "path": "tests/test_quilt/source/source.txt",
    "chars": 76,
    "preview": "foo\nbar\nbaz\nabcefg\nblub\n3.14156\n2.718281828459045\n0.7071\n1.4142135623730951\n"
  },
  {
    "path": "tests/test_quilt/source.txt",
    "chars": 82,
    "preview": "foo\nbar\nbaz\nabcefg\nblub\n42\n23\n3.14156\n2.718281828459045\n0.7071\n1.4142135623730951\n"
  },
  {
    "path": "tests/test_quilt/target/source.txt",
    "chars": 82,
    "preview": "foo\nbar\nbaz\nabcefg\nblub\n42\n23\n3.14156\n2.718281828459045\n0.7071\n1.4142135623730951\n"
  },
  {
    "path": "tests/test_quilt.py",
    "chars": 1976,
    "preview": "from __future__ import print_function, division, unicode_literals, absolute_import\n\nimport os\nimport shutil\nimport filec"
  },
  {
    "path": "tests/test_saliency_map_conversion.py",
    "chars": 2491,
    "preview": "import numpy as np\nimport pytest\n\nimport pysaliency\nfrom pysaliency import optimize_for_information_gain\nfrom pysaliency"
  },
  {
    "path": "tests/test_saliency_map_conversion_theano.py",
    "chars": 2597,
    "preview": "import numpy as np\nimport dill\nimport pytest\n\nfrom pysaliency import GaussianSaliencyMapModel, Stimuli, Fixations\nfrom p"
  },
  {
    "path": "tests/test_saliency_map_conversion_torch.py",
    "chars": 1133,
    "preview": "import numpy as np\nimport pytest\n\nfrom pysaliency.saliency_map_conversion import optimize_for_information_gain\nfrom pysa"
  },
  {
    "path": "tests/test_saliency_map_conversion_torch_extended.py",
    "chars": 7356,
    "preview": "import time\n\nimport numpy as np\nimport pytest\n\nimport pysaliency\nfrom pysaliency.saliency_map_conversion import optimize"
  },
  {
    "path": "tests/test_saliency_map_models.py",
    "chars": 21853,
    "preview": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nimport pytest\n\nimport numpy as np\nim"
  },
  {
    "path": "tests/test_sampling.py",
    "chars": 1251,
    "preview": "import numpy as np\n\nfrom pysaliency.saliency_map_models import GaussianSaliencyMapModel\nfrom pysaliency.sampling_models "
  },
  {
    "path": "tests/test_torch_datasets.py",
    "chars": 2291,
    "preview": "from pathlib import Path\n\nfrom PIL import Image\nimport numpy as np\nimport pytest\n\nfrom pysaliency import (\n    FileStimu"
  },
  {
    "path": "tests/test_torch_utils.py",
    "chars": 2419,
    "preview": "import numpy as np\nfrom packaging import version\nfrom scipy.ndimage import gaussian_filter as scipy_filter\nimport torch\n"
  },
  {
    "path": "tests/test_utils.py",
    "chars": 6897,
    "preview": "from __future__ import absolute_import, print_function, division\n\nimport unittest\nimport dill\nimport glob\nimport os\n\nimp"
  },
  {
    "path": "tests/utils/test_variable_length_array.py",
    "chars": 6854,
    "preview": "import numpy as np\nimport pytest\n\nfrom pysaliency.utils import build_padded_2d_array\nfrom pysaliency.utils.variable_leng"
  }
]

// ... and 22 more files (download for full content)

About this extraction

This page contains the full source code of the matthias-k/pysaliency GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 190 files (5.2 MB), approximately 1.4M tokens, and a symbol index with 1253 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!