Hazelbean workflow diagrams

Two views of the same thing. The first is the pattern you write — the shape almost every Hazelbean script takes, from a 30-line script to the full SEALS model. The second is what ProjectFlow does underneath when you call p.execute(), which is worth reading once you have written a few run files and want to know why a task ran, skipped, or ran in parallel.

For the prose version of the first diagram see the learning path; for the API and the reasoning behind the second see ProjectFlow.

Core workflow pattern

Hazelbean follows a consistent, organized workflow pattern that makes geospatial analysis predictable and reproducible:

flowchart TD
    A[Initialize ProjectFlow] --> B[Create Directory Structure]
    B --> C[Discover and Load Data]
    C --> D[Process and Transform]
    D --> E[Analyze and Compute]
    E --> F[Export Results]
    F --> G[Document and Share]

    A1["p = hb.ProjectFlow(project_name=...)"] --> A
    B1["input/, intermediate/, output/"] --> B
    C1["p.get_path() intelligent search"] --> C
    D1["Array operations & transformations"] --> D
    E1["Multi-raster analysis & statistics"] --> E
    F1[Organized file structure] --> F
    G1[Automated documentation] --> G

    classDef stepBox fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    classDef codeBox fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1px

    class A,B,C,D,E,F,G stepBox
    class A1,B1,C1,D1,E1,F1,G1 codeBox

Detailed ProjectFlow execution model

Understanding how ProjectFlow coordinates complex geospatial workflows:

flowchart TD
    Start([p = hb.ProjectFlow]) --> CreateDir{"Create project_dir?"}
    CreateDir -->|Fails| Error[NotADirectoryError]
    CreateDir -->|Success| SetupDirs["Set input_dir, intermediate_dir, output_dir"]

    SetupDirs --> InitTree["Initialize task_tree root"]
    InitTree --> UserTasks[User adds tasks via add_task]

    UserTasks --> Execute["p.execute()"]
    Execute --> RegisterDirs["Register search directories"]
    RegisterDirs --> ShowTasks["show_tasks() displays tree"]
    ShowTasks --> RunTask["run_task(task_tree)"]

    RunTask --> GetTask["Get next task from tree"]
    GetTask --> SetTaskDir["Set task_dir based on parent type"]
    SetTaskDir --> CheckSkip{"skip_existing and dir exists?"}

    CheckSkip -->|Yes| SkipRun["Set run_this = False"]
    CheckSkip -->|No| CreateTaskDir["Create task directory"]

    SkipRun --> RunFunc
    CreateTaskDir --> RunFunc["Run task.function(p)"]

    RunFunc --> IsIterator{"Task is iterator?"}
    IsIterator -->|Yes| CheckParallel{"run_in_parallel?"}
    IsIterator -->|No| HasChildren{"Has children?"}

    CheckParallel -->|Yes| ParallelExec["multiprocessing.Pool.starmap()"]
    CheckParallel -->|No| SeqExec["Sequential child execution"]

    ParallelExec --> HasChildren
    SeqExec --> HasChildren

    HasChildren -->|Yes| RunTask
    HasChildren -->|No| Complete([Script complete])

    %% Styling
    classDef process fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    classDef decision fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    classDef error fill:#ffebee,stroke:#d32f2f,stroke-width:2px
    classDef success fill:#e8f5e8,stroke:#388e3c,stroke-width:2px

    class SetupDirs,InitTree,UserTasks,Execute,RegisterDirs,ShowTasks,RunTask,GetTask,SetTaskDir,CreateTaskDir,RunFunc,ParallelExec,SeqExec,SkipRun process
    class CreateDir,CheckSkip,IsIterator,CheckParallel,HasChildren decision
    class Error error
    class Complete success

Note the two decision points that do most of the work in practice. skip_existing and dir exists? is the skip-if-already-computed re-run — the reason a finished run costs seconds the second time. Task is iterator? combined with run_in_parallel? is the fan-out: an iterator task’s children are dispatched across a process pool rather than run in sequence, which is how a model iterates over thousands of tiles or scenarios.

This workflow ensures that every analysis is:

  • Organized: Clear directory structure and file management
  • Reproducible: Consistent patterns and automated documentation
  • Discoverable: Intelligent file location and path resolution
  • Scalable: Efficient processing suitable for large datasets