Skip to content

initializer

tit.project_init.initializer

BIDS-compliant project structure initializer.

Creates directory scaffolding, dataset description files, README, and project status metadata for a new TI-Toolbox project.

This module is the single source of truth for project_status.json. All other modules must use :func:load_project_status and :func:update_project_status rather than reading/writing the file directly.

has_project_data_or_markers

has_project_data_or_markers(project_dir: Path) -> bool

Return True if project_dir contains any project data or marker files.

Parameters

project_dir : Path Root directory of the project.

Returns

bool True when initialization markers, subject folders, source data, derivatives, or loose NIfTI files are detected.

Source code in tit/project_init/initializer.py
def has_project_data_or_markers(project_dir: Path) -> bool:
    """Return ``True`` if *project_dir* contains any project data or marker files.

    Parameters
    ----------
    project_dir : Path
        Root directory of the project.

    Returns
    -------
    bool
        ``True`` when initialization markers, subject folders, source data,
        derivatives, or loose NIfTI files are detected.
    """
    for marker in MARKER_FILES:
        if (project_dir / marker).exists():
            return True

    if any(project_dir.glob("sub-*")):
        return True

    if _dir_has_files(
        project_dir / "sourcedata", patterns=("*.dcm", "*.nii", "*.nii.gz")
    ):
        return True

    if _dir_has_files(project_dir / "derivatives"):
        return True

    if any(project_dir.glob("*.nii*")):
        return True

    return False

is_new_project

is_new_project(project_dir: Path) -> bool

Return True if project_dir exists and contains no project data.

Parameters

project_dir : Path Root directory of the project.

Returns

bool True when the directory is empty of project data and markers.

Source code in tit/project_init/initializer.py
def is_new_project(project_dir: Path) -> bool:
    """Return ``True`` if *project_dir* exists and contains no project data.

    Parameters
    ----------
    project_dir : Path
        Root directory of the project.

    Returns
    -------
    bool
        ``True`` when the directory is empty of project data and markers.
    """
    return (
        project_dir.exists()
        and project_dir.is_dir()
        and not has_project_data_or_markers(project_dir)
    )

initialize_readme

initialize_readme(project_dir: Path) -> bool

Create a top-level README in project_dir. Returns True when it wrote one.

Source code in tit/project_init/initializer.py
def initialize_readme(project_dir: Path) -> bool:
    """Create a top-level README in *project_dir*. Returns ``True`` when it wrote one."""
    readme_file = project_dir / "README"
    if readme_file.exists():
        return False
    project_name = project_dir.name
    readme_content = f"""# {project_name}

This is a BIDS-compliant neuroimaging dataset generated by TI-Toolbox for temporal interference (TI) stimulation modeling and analysis.

## Overview

This project contains structural MRI data and derivatives for simulating and analyzing temporal interference electric field patterns in the brain.

## Dataset Structure

- `sourcedata/` - Raw DICOM source files
- `sub-*/` - Subject-level BIDS-formatted neuroimaging data (NIfTI files)
- `derivatives/` - Processed data and analysis results
  - `freesurfer/` - FreeSurfer anatomical segmentation and surface reconstructions
  - `SimNIBS/` - SimNIBS head models and electric field simulations
  - `ti-toolbox/` - TI-Toolbox simulation results and analyses

## Software

Data processing and simulations were performed using:
- **TI-Toolbox** - Temporal interference modeling pipeline
- **FreeSurfer** - Cortical reconstruction and volumetric segmentation
- **SimNIBS** - Finite element modeling for electric field simulations

## More Information

For more information about TI-Toolbox, visit:
- GitHub: https://github.com/idossha/TI-Toolbox
- Documentation: https://idossha.github.io/TI-toolbox/

## BIDS Compliance

This dataset follows the Brain Imaging Data Structure (BIDS) specification for organizing and describing neuroimaging data. For more information about BIDS, visit: https://bids.neuroimaging.io/
"""
    readme_file.write_text(readme_content)
    return True

initialize_dataset_description

initialize_dataset_description(project_dir: Path) -> bool

Write a BIDS dataset_description.json at the project root.

Returns True when it wrote one, False when the file was already there.

Source code in tit/project_init/initializer.py
def initialize_dataset_description(project_dir: Path) -> bool:
    """Write a BIDS ``dataset_description.json`` at the project root.

    Returns ``True`` when it wrote one, ``False`` when the file was already there.
    """
    dataset_file = project_dir / "dataset_description.json"
    if dataset_file.exists():
        return False
    payload = {
        "Name": project_dir.name,
        "BIDSVersion": "1.6.0",
        "DatasetType": "raw",
        "License": "",
        "Authors": [],
        "Acknowledgements": "",
        "HowToAcknowledge": "",
        "Funding": [],
        "ReferencesAndLinks": ["https://github.com/idossha/TI-Toolbox"],
        "DatasetDOI": "",
    }
    dataset_file.write_text(json.dumps(payload, indent=2))
    return True

initialize_derivative_dataset_description

initialize_derivative_dataset_description(project_dir: Path, derivative_name: str) -> bool

Write a BIDS derivative dataset_description.json for derivative_name.

Returns True when it wrote one, False when the file was already there.

Source code in tit/project_init/initializer.py
def initialize_derivative_dataset_description(
    project_dir: Path, derivative_name: str
) -> bool:
    """Write a BIDS derivative ``dataset_description.json`` for *derivative_name*.

    Returns ``True`` when it wrote one, ``False`` when the file was already there.
    """
    derivative_dir = project_dir / "derivatives" / derivative_name
    dataset_file = derivative_dir / "dataset_description.json"
    if dataset_file.exists():
        return False
    derivative_dir.mkdir(parents=True, exist_ok=True)
    current_date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    payload = {
        "Name": f"{derivative_name} derivatives",
        "BIDSVersion": "1.6.0",
        "DatasetType": "derivative",
        "GeneratedBy": [{"Name": derivative_name}],
        "SourceDatasets": [
            {"URI": f"bids:{project_dir.name}@{current_date}", "Version": "1.0.0"}
        ],
        "DatasetLinks": {project_dir.name: "../../"},
    }
    dataset_file.write_text(json.dumps(payload, indent=2))
    return True

load_project_status

load_project_status(project_dir: Path) -> dict[str, Any]

Read project_status.json and return its contents.

Returns an empty dict when the file is missing or unreadable. This function never writes to disk.

Parameters

project_dir : Path Root directory of the project.

Source code in tit/project_init/initializer.py
def load_project_status(project_dir: Path) -> dict[str, Any]:
    """Read ``project_status.json`` and return its contents.

    Returns an **empty dict** when the file is missing or unreadable.
    This function never writes to disk.

    Parameters
    ----------
    project_dir : Path
        Root directory of the project.
    """
    status_file = _status_file_path(project_dir)
    if not status_file.exists():
        return {}
    try:
        return json.loads(status_file.read_text())
    except Exception as exc:
        logger.warning("Could not read %s: %s", status_file, exc)
        return {}

update_project_status

update_project_status(project_dir: Path, updates: dict[str, Any]) -> bool

Merge updates into project_status.json and write back.

Performs a recursive (deep) merge so that nested keys such as user_preferences.show_welcome can be updated without clobbering sibling keys. Automatically sets last_updated.

If the file does not yet exist a warning is logged and the function returns False — the file should have been created by :func:initialize_project_status.

Parameters

project_dir : Path Root directory of the project. updates : dict Fields to merge into the existing status.

Returns

bool True on success.

Source code in tit/project_init/initializer.py
def update_project_status(project_dir: Path, updates: dict[str, Any]) -> bool:
    """Merge *updates* into ``project_status.json`` and write back.

    Performs a recursive (deep) merge so that nested keys such as
    ``user_preferences.show_welcome`` can be updated without clobbering
    sibling keys.  Automatically sets ``last_updated``.

    If the file does not yet exist a warning is logged and the function
    returns ``False`` — the file should have been created by
    :func:`initialize_project_status`.

    Parameters
    ----------
    project_dir : Path
        Root directory of the project.
    updates : dict
        Fields to merge into the existing status.

    Returns
    -------
    bool
        ``True`` on success.
    """
    status_file = _status_file_path(project_dir)
    current = load_project_status(project_dir)
    if not current:
        logger.warning(
            "project_status.json does not exist at %s — skipping update",
            status_file,
        )
        return False

    _deep_merge(current, updates)
    current["last_updated"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")

    try:
        status_file.write_text(json.dumps(current, indent=2))
        return True
    except Exception as exc:
        logger.error("Failed to write %s: %s", status_file, exc)
        return False

initialize_project_status

initialize_project_status(project_dir: Path) -> bool

Create project_status.json only if it does not already exist.

This is the sole place in the codebase that creates the file. Subsequent mutations must go through :func:update_project_status. Returns True when it wrote the file.

Source code in tit/project_init/initializer.py
def initialize_project_status(project_dir: Path) -> bool:
    """Create ``project_status.json`` **only if it does not already exist**.

    This is the sole place in the codebase that creates the file.
    Subsequent mutations must go through :func:`update_project_status`.
    Returns ``True`` when it wrote the file.
    """
    config_dir = project_dir / "code" / "ti-toolbox" / "config"
    status_file = _status_file_path(project_dir)
    if status_file.exists():
        logger.debug("project_status.json already exists — skipping creation")
        return False
    config_dir.mkdir(parents=True, exist_ok=True)
    current_time = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
    payload = {
        "project_created": current_time,
        "last_updated": current_time,
        "config_created": True,
        "user_preferences": {"show_welcome": True},
        "project_metadata": {
            "name": project_dir.name,
            "path": str(project_dir),
            "version": "unknown",
        },
    }
    status_file.write_text(json.dumps(payload, indent=2))
    return True

initialize_project_structure

initialize_project_structure(project_dir: Path) -> None

Scaffold a full BIDS-compliant directory structure for project_dir.

Idempotent, and honest about it: the "New project detected" banner and the per-item ✓ … created lines are printed only for work this call actually did. An established project -- one :func:has_project_data_or_markers recognises -- gets a single Project structure verified line, because that is all that happened. (Before 2026-09-15 the banner and every ✓ printed unconditionally, so asking an established project for anything that re-ran this looked like it was being re-initialised.)

Parameters

project_dir : Path Root directory of the project.

Source code in tit/project_init/initializer.py
def initialize_project_structure(project_dir: Path) -> None:
    """Scaffold a full BIDS-compliant directory structure for *project_dir*.

    Idempotent, and **honest about it**: the "New project detected" banner and the per-item
    ``✓ … created`` lines are printed only for work this call actually did. An established
    project -- one :func:`has_project_data_or_markers` recognises -- gets a single
    ``Project structure verified`` line, because that is all that happened. (Before
    2026-09-15 the banner and every ✓ printed unconditionally, so asking an established
    project for anything that re-ran this looked like it was being re-initialised.)

    Parameters
    ----------
    project_dir : Path
        Root directory of the project.
    """
    # Decided *before* anything is created, or the first mkdir would answer the question.
    is_new = not has_project_data_or_markers(project_dir)
    created: list[str] = []

    def note(label: str, did: bool) -> None:
        if did:
            created.append(label)

    if is_new:
        print("")
        print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
        print(f"  New project detected: {project_dir.name}")
        print("  Initializing BIDS-compliant structure...")
        print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
        print("")

    for rel in (
        Path("code") / "ti-toolbox" / "config",
        Path("derivatives") / "freesurfer",
        Path("derivatives") / "SimNIBS",
        Path("sourcedata"),
    ):
        target = project_dir / rel
        existed = target.is_dir()
        target.mkdir(parents=True, exist_ok=True)
        note(f"{rel}/", not existed)

    note("README", initialize_readme(project_dir))
    note("dataset_description.json", initialize_dataset_description(project_dir))
    for derivative in ("ti-toolbox", "freesurfer", "SimNIBS"):
        note(
            f"derivatives/{derivative}/dataset_description.json",
            initialize_derivative_dataset_description(project_dir, derivative),
        )
    note("project_status.json", initialize_project_status(project_dir))

    marker = project_dir / "code" / "ti-toolbox" / "config" / ".initialized"
    existed = marker.exists()
    marker.touch()
    note("initialization marker", not existed)

    if not is_new and not created:
        print(f"Project structure verified: {project_dir}", flush=True)
        return

    for label in created:
        print(f"  ✓ {label} created")
    if not created:
        print("  (everything was already in place)")

    if is_new:
        print("")
        print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
        print("  ✓ Project initialization complete!")
        print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
        print("")
    else:
        print(f"Project structure verified: {project_dir}", flush=True)