Skip to content

chore: unify example output directories#97

Merged
Routhleck merged 3 commits intomasterfrom
chore/example-output-helper
Feb 6, 2026
Merged

chore: unify example output directories#97
Routhleck merged 3 commits intomasterfrom
chore/example-output-helper

Conversation

@Routhleck
Copy link
Owner

@Routhleck Routhleck commented Feb 6, 2026

Summary

  • add shared helper to compute example output directories
  • refactor examples to use the helper for consistent output paths
  • remove existing example outputs and ignore future ones

Testing

  • not run

Summary by Sourcery

Unify example script output handling around a shared helper that writes artifacts into a consistent examples/outputs directory hierarchy.

Enhancements:

  • Introduce a reusable get_example_output_dir utility to compute per-script example output directories with optional environment-variable override.
  • Refactor multiple example scripts to use the shared output helper and pathlib-style paths for saving figures, animations, and data artifacts.
  • Adjust example CLIs and defaults so outputs are written under a common examples/outputs//<script_name>/ structure instead of ad-hoc locations.

Documentation:

  • Document the new examples output directory convention and usage in a top-level examples README and update references in existing example-specific READMEs.

Chores:

  • Ignore generated example output artifacts in version control by treating the examples/outputs directory as build output.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 6, 2026

Reviewer's Guide

Centralizes example output directory handling via a new get_example_output_dir utility and refactors many example scripts to use it, so that all generated artifacts are written under examples/outputs (or an env-configured directory) instead of ad-hoc paths, while updating documentation and ignoring generated outputs from version control.

Class diagram for example output directory utility

classDiagram
    class ExampleOutputsUtils {
        +Path get_example_output_dir(script_path, category, base_env_var)
        +Path _resolve_base_output_dir(script_path, base_env_var)
        +str _infer_category(script_path)
    }

    class PathlibPath {
        +Path resolve()
        +Path expanduser()
        +Path parent
        +Path~list~ parents
        +str name
        +str stem
        +Path __truediv__(other)
        +None mkdir(parents, exist_ok)
        +Path relative_to(parent)
        +tuple parts
    }

    class OsEnviron {
        +str get(key)
    }

    ExampleOutputsUtils ..> PathlibPath : uses
    ExampleOutputsUtils ..> OsEnviron : reads

    class ExampleScript {
        +Path OUTPUT_DIR
        +None main()
    }

    ExampleScript ..> ExampleOutputsUtils : calls get_example_output_dir
    ExampleScript ..> PathlibPath : joins_paths_and_writes_files
Loading

Flow diagram for resolving example output directories

flowchart TD
    A["Example script calls\nget_example_output_dir(__file__, category)"] --> B["Normalize script_path\nusing Path.resolve"]
    B --> C{"Environment variable\nCANNS_EXAMPLES_OUTPUT_DIR set?"}

    C -- "Yes" --> D["base_dir = Path(env_value).expanduser"]
    C -- "No" --> E{"Is script under an\nexamples/ directory?"}

    E -- "Yes" --> F["base_dir = examples_dir / 'outputs'"]
    E -- "No" --> G["base_dir = script_path.parent / 'outputs'"]

    D --> H
    F --> H
    G --> H

    H{"category argument is None?"} -->|Yes| I["Infer category from path:\nfirst segment under examples/\n(e.g. cann, brain_inspired)"]
    H -->|No| J["Use provided category"]

    I --> K
    J --> K

    K["Compute out_dir = base_dir / category / script_stem\n(or base_dir / script_stem if no category)"] --> L["Create out_dir with mkdir(parents=True, exist_ok=True)"]
    L --> M["Return out_dir to caller\n(example uses out_dir / filename for outputs)"]
Loading

File-Level Changes

Change Details Files
Introduce shared helper for computing example output directories and export it from the utils package.
  • Add get_example_output_dir helper that computes a base examples/outputs directory (optionally overridden via CANNS_EXAMPLES_OUTPUT_DIR) and returns a per-script subdirectory, creating it as needed.
  • Implement helper internals to infer the example category from script location and to fall back to script-local outputs when not under the examples tree.
  • Expose get_example_output_dir from the canns.utils package via all and import.
src/canns/utils/example_outputs.py
src/canns/utils/__init__.py
Refactor CANN and navigation examples to write outputs via the shared helper and use pathlib paths where needed.
  • Replace hard-coded os.path/pathlib output paths with OUTPUT_DIR/get_example_output_dir(file) in hierarchical_path_integration and navigation_complex_environment, adjusting directory creation and PlotConfig save_path usage to accept str(path) where necessary.
  • Update various CANN scripts (1D/2D tracking, grid cell analyses, velocity path integration, import_external_trajectory) to construct outputs using get_example_output_dir instead of local outputs/ directories or bare filenames.
  • Adjust README for CANN examples to describe the new examples/outputs/cann/<script_name>/ layout.
examples/cann/hierarchical_path_integration.py
examples/cann/theta_sweep_place_cell_network.py
examples/cann/theta_sweep_grid_cell_network.py
examples/cann/navigation_complex_environment.py
examples/cann/cann1d_oscillatory_tracking.py
examples/cann/cann2d_tracking.py
examples/cann/grid_cell_velocity_path_integration.py
examples/cann/grid_cell_position_spatial_analysis.py
examples/cann/grid_cell_velocity_spatial_analysis.py
examples/cann/import_external_trajectory.py
examples/cann/README.md
Refactor brain-inspired examples to write plots and figures into script-specific example output directories.
  • Import get_example_output_dir, define a module-level OUTPUT_DIR, and save figures (e.g., diagnostics, receptive fields, tuning curves, PCA plots, STDP plots, Hopfield training results) under OUTPUT_DIR with updated print messages that show the resolved path.
  • Update README references for brain_inspired outputs to point to the new examples/outputs/brain_inspired/<script_name>/ paths.
examples/brain_inspired/hopfield_energy_diagnostics.py
examples/brain_inspired/bcm_receptive_fields.py
examples/brain_inspired/hopfield_train_mnist.py
examples/brain_inspired/hopfield_hebbian_vs_antihebbian.py
examples/brain_inspired/hopfield_train_1d.py
examples/brain_inspired/oja_pca_extraction.py
examples/brain_inspired/oja_vs_sanger_comparison.py
examples/brain_inspired/stdp_temporal_learning.py
examples/brain_inspired/hopfield_train.py
examples/brain_inspired/README.md
Refactor experimental data analysis examples to use the shared output directory helper instead of ad-hoc Paths or current-directory outputs.
  • Replace manual Path-based out_dir construction in firing_field_map and firing_rate_heatmap with OUTPUT_DIR = get_example_output_dir(file) and write save_path as str(OUTPUT_DIR / filename).
  • Update other experimental_data_analysis scripts (BTN, fly ROI bump fit, legacy experimental_cann2d_analysis) to use OUTPUT_DIR/get_example_output_dir for artifacts such as PNGs and MP4s, including printed filenames.
  • Change cohomap/cohospace CLI examples so their --out-dir defaults to the resolved example outputs directory instead of the current working directory.
examples/experimental_data_analysis/firing_field_map.py
examples/experimental_data_analysis/firing_rate_heatmap.py
examples/experimental_data_analysis/btn_example.py
examples/experimental_data_analysis/fly_roi_bump_fit.py
examples/experimental_data_analysis/legacy/experimental_cann2d_analysis.py
examples/experimental_data_analysis/cohomap_example.py
examples/experimental_data_analysis/cohomap_vectors_example.py
examples/experimental_data_analysis/cohospace_example.py
examples/experimental_data_analysis/cohospace_phase_centers_example.py
Refactor slow points and cell classification examples to use example output directories.
  • In flipflop_fixed_points and sinewave_fixed_points, introduce OUTPUT_DIR via get_example_output_dir and route 2D/3D fixed-point plot save paths through it, converting to str where APIs expect strings.
  • Update cell_classification/try_classification_methods to save its NPZ output into an example output directory resolved by get_example_output_dir.
examples/slow_points_analysis/flipflop_fixed_points.py
examples/slow_points_analysis/sinewave_fixed_points.py
examples/cell_classification/try_classification_methods.py
Document the new examples output convention and ensure generated example outputs are ignored by git.
  • Add a top-level examples/README.md describing the examples layout, the examples/outputs//<script_name>/ convention, and configuration via CANNS_EXAMPLES_OUTPUT_DIR plus per-script overrides.
  • Update .gitignore to exclude the examples/outputs directory tree so generated artifacts from running the examples are not tracked.
examples/README.md
.gitignore

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In examples/cann/navigation_complex_environment.py, the main signature now annotates output_dir: str | Path but Path is not imported, which will raise a NameError; add the appropriate from pathlib import Path import or adjust the annotation.
  • Usage of get_example_output_dir results in a mix of Path and str being passed as save_path (sometimes wrapped with str(), sometimes not); consider standardizing either on Path in the called APIs or always converting to str so the interfaces are consistent and less error-prone.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `examples/cann/navigation_complex_environment.py`, the `main` signature now annotates `output_dir: str | Path` but `Path` is not imported, which will raise a `NameError`; add the appropriate `from pathlib import Path` import or adjust the annotation.
- Usage of `get_example_output_dir` results in a mix of `Path` and `str` being passed as `save_path` (sometimes wrapped with `str()`, sometimes not); consider standardizing either on `Path` in the called APIs or always converting to `str` so the interfaces are consistent and less error-prone.

## Individual Comments

### Comment 1
<location> `src/canns/utils/example_outputs.py:22-25` </location>
<code_context>
+    return out_dir
+
+
+def _resolve_base_output_dir(script_path: Path, base_env_var: str) -> Path:
+    base_env = os.environ.get(base_env_var)
+    if base_env:
+        return Path(base_env).expanduser()
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Consider normalizing the environment-provided base directory with `.resolve()` for more predictable behavior.

When `base_env_var` is set, `Path(base_env).expanduser()` may still be relative to the current working directory, so behavior can change depending on where the script is launched. Using `.expanduser().resolve()` makes the path deterministic and consistent with how `script_path` is handled in `get_example_output_dir`. For example:

```python
base_env = os.environ.get(base_env_var)
if base_env:
    return Path(base_env).expanduser().resolve()
```

```suggestion
def _resolve_base_output_dir(script_path: Path, base_env_var: str) -> Path:
    base_env = os.environ.get(base_env_var)
    if base_env:
        return Path(base_env).expanduser().resolve()
```
</issue_to_address>

### Comment 2
<location> `examples/README.md:13` </location>
<code_context>
+  (Hopfield, Oja, BCM, STDP, etc.)
+- `cann/`: continuous attractor neural network (CANN) models and navigation
+  demos
+- `cell_classification/`: grid / cell classification examples
+- `experimental_data_analysis/`: ASA pipeline utilities (TDA, decoding,
+  CohoMap/CohoSpace, path compare, firing-rate maps)
</code_context>

<issue_to_address>
**nitpick (typo):** Consider clarifying the phrase "grid / cell classification examples"

This phrasing is ambiguous; if you mean classification of grid cells, consider something like “grid-cell classification examples” or “grid cell classification examples” for clarity.

```suggestion
- `cell_classification/`: grid-cell classification examples
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@Routhleck Routhleck merged commit e24e923 into master Feb 6, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant