Skip to content

Architecture

The shape of the system

flowchart TB
    subgraph app["Graphical application (amazeing.app)"]
        form[Config form<br/>one panel per mode]
        wave[Stimulus preview<br/>waveform, spectrum, spectrogram]
        panel[Process panel<br/>console and controls]
    end

    subgraph pkg["Package (amazeing.auditory)"]
        cfg[ExperimentConfig<br/>every session setting]
        fac[ExperimentFactory<br/>builds the trial table]
        aud[Audio<br/>synthesis and speaker compensation]
        vis[ROIMonitor<br/>arm occupancy]
        hw[Camera and Arduino]
        dm[DataManager<br/>folders, logs, manifest]
        an[SessionAnalyzer<br/>figures]
    end

    yaml[(session YAML)]
    data[(session folder<br/>CSVs, video, manifest)]

    form -->|writes| yaml
    yaml -->|read by| cfg
    panel -->|launches| cfg
    cfg --> fac --> aud
    cfg --> vis
    cfg --> hw
    fac --> loop
    loop[Session loop<br/>main.py]
    vis --> loop
    hw --> loop
    loop --> dm --> data
    data --> an
    cfg -.->|same objects| wave

The application never runs an experiment in its own process. It writes a configuration file and launches the same command-line entry point a terminal user would, then shows that process's output. This is why a session started from the window and one started from a terminal are identical, and why the interface cannot introduce a behaviour the scripts do not have.

The session loop

sequenceDiagram
    participant C as Camera
    participant V as ROIMonitor
    participant M as Session loop
    participant A as Audio
    participant D as DataManager

    Note over M: Calibration: measure each arm's<br/>empty brightness (maze must be empty)
    loop every frame
        C->>M: frame
        M->>V: thresholded frame
        V-->>M: arms entered this frame
        alt an arm was entered
            M->>A: play that arm's stimulus
            M->>D: start the visit timer
        end
        alt all arms empty
            M->>A: stop
        end
        alt an arm was left
            M->>D: log the visit
        end
    end
    Note over M,D: At each block boundary, visits still<br/>open are closed and logged

The nine-block cycle

Every mode uses the same structure. Odd positions are silent, even positions are active, and the stimulus-to-arm mapping is reshuffled at the start of each active block.

flowchart LR
    B1[1<br/>silent] --> B2[2<br/>active] --> B3[3<br/>silent] --> B4[4<br/>active]
    B4 --> B5[5<br/>silent] --> B6[6<br/>active] --> B7[7<br/>silent] --> B8[8<br/>active] --> B9[9<br/>silent]

    style B2 fill:#1565C0,color:#fff
    style B4 fill:#1565C0,color:#fff
    style B6 fill:#1565C0,color:#fff
    style B8 fill:#1565C0,color:#fff

The reshuffle is the load-bearing part of the design. If an animal returns to the same physical arm regardless of what plays there, that is a place preference. If it follows a stimulus as the stimulus moves between arms, that is a sound preference. Without the reshuffle the two are indistinguishable.

Because each active block needs a mapping that has not been used yet, and there are only as many mappings as there are orderings of the arms, a maze with fewer than three arms cannot supply four distinct mappings. The search gives up after a bounded number of attempts and reuses one rather than looping forever.

The tactile paradigm

The tactile maze runs a different loop. Instead of a stimulus following the animal, the apparatus is configured per trial and the animal's route is scored against it.

sequenceDiagram
    participant M as Session loop<br/>simplerCode.py
    participant A as Arduino + PCA9685
    participant C as Camera
    participant D as Data

    loop every trial
        M->>A: all gratings to neutral
        M->>A: grating positions for this trial's reward arm
        Note over M,C: trial starts when the animal passes<br/>entrance1 then entrance2
        loop every frame
            C->>M: frame
            alt animal in a reward arm
                alt it is the rewarded arm, no earlier mistake
                    M->>A: rew<arm>
                    A->>A: rock the dispenser until the IR beam breaks
                    M->>D: hit
                else wrong arm
                    M->>D: incorrect
                end
            end
        end
        Note over M,C: trial ends when the animal passes<br/>entrance2 then entrance1
        M->>D: append the trial row, with start and end video frames
    end

Two files drive it: grating_maps.csv gives the grating positions that cue each reward arm, and reward_sequences.csv gives the training stages from which the trial list is drawn. Both sit beside the script and are described on the tactile paradigm page.

The script has not been rebuilt on the shared modules. Its settings are flags at the top of the file rather than a session configuration, and the application runs it as it is. That is deliberate, since it is the code that produced the data, and rewriting it would mean revalidating it against the animals.

Data flow through an experiment

flowchart LR
    subgraph session["During the session"]
        s1[trials CSV<br/>per block and arm]
        s2[detailed visits CSV<br/>per visit]
        s3[maze entries CSV]
        s4[video]
        s5[manifest]
    end

    subgraph post["Afterwards"]
        p1[Per-session figures]
        p2[Cross-session summary<br/>figures and tables]
        p3[Pose estimation<br/>crop, infer, filter]
        p4[Statistics and models]
    end

    s1 --> p1 --> p2 --> p4
    s2 --> p1
    s3 --> p1
    s4 --> p3 --> p4
    s5 -.->|units and settings| p2

Package layout

Module Responsibility
amazeing.auditory.config ExperimentConfig: every setting, the block schedule, the per-mode stimulus parameters
amazeing.auditory.session_config Reading and writing that config as YAML
amazeing.auditory.experiments ExperimentFactory: turns a config into a trial table plus waveforms
amazeing.auditory.audio Synthesis, speaker compensation, playback
amazeing.auditory.vision ROIMonitor: occupancy with debouncing
amazeing.auditory.camera_check Live camera and detection view used to set both up before a session
amazeing.auditory.hardware Camera and Arduino
amazeing.auditory.data_manager Session folders, visit logs, the manifest and its column units
amazeing.auditory.analysis Per-session figures, and the colour palette shared with the application
amazeing.auditory.summary_analysis Cross-session figures and tables
amazeing.auditory.grammar_stimuli Markov grammars, tone synthesis, training-day playback
amazeing.simplermaze Tactile paradigm: session loop, trial generation from the training stages, grating and reward commands
amazeing.app The graphical interface

Design rules

The application is a layer, not a fork. Anything it can do, the command line can do. If a feature would only work in the window, it belongs in the package instead.

The configuration file is the contract. The form, the YAML file and the dataclass describe the same thing, and a round trip through any of them changes nothing. Unknown fields are rejected rather than ignored, so a typo cannot silently leave a default in place.

Defaults reproduce the published protocols. The per-mode stimulus values were literals in the code until they became configurable. Their defaults are pinned by tests so they cannot drift away from what was actually run.

Units live in the data. Every session writes a manifest describing its own columns, because the same column name meant milliseconds in the first version of this software and seconds in the second.