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) -> None

Create a top-level README in project_dir if it does not exist.

Source code in tit/project_init/initializer.py
def initialize_readme(project_dir: Path) -> None:
    """Create a top-level README in *project_dir* if it does not exist."""
    readme_file = project_dir / "README"
    if readme_file.exists():
        return
    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)

initialize_dataset_description

initialize_dataset_description(project_dir: Path) -> None

Write a BIDS dataset_description.json at the project root.

Source code in tit/project_init/initializer.py
def initialize_dataset_description(project_dir: Path) -> None:
    """Write a BIDS ``dataset_description.json`` at the project root."""
    dataset_file = project_dir / "dataset_description.json"
    if dataset_file.exists():
        return
    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))

initialize_derivative_dataset_description

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

Write a BIDS derivative dataset_description.json for derivative_name.

Source code in tit/project_init/initializer.py
def initialize_derivative_dataset_description(
    project_dir: Path, derivative_name: str
) -> None:
    """Write a BIDS derivative ``dataset_description.json`` for *derivative_name*."""
    derivative_dir = project_dir / "derivatives" / derivative_name
    dataset_file = derivative_dir / "dataset_description.json"
    if dataset_file.exists():
        return
    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))

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) -> None

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.

Source code in tit/project_init/initializer.py
def initialize_project_status(project_dir: Path) -> None:
    """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`.
    """
    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
    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,
        "example_data_copied": False,
        "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))

initialize_project_structure

initialize_project_structure(project_dir: Path) -> None

Scaffold a full BIDS-compliant directory structure for a new project.

Parameters

project_dir : Path Root directory of the new project. Directories, metadata files, README, and an initialization marker are created idempotently.

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

    Parameters
    ----------
    project_dir : Path
        Root directory of the new project.  Directories, metadata files,
        README, and an initialization marker are created idempotently.
    """
    print("")
    print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
    print(f"  New project detected: {project_dir.name}")
    print("  Initializing BIDS-compliant structure...")
    print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
    print("")

    print("Creating directory structure...")
    (project_dir / "code" / "ti-toolbox" / "config").mkdir(parents=True, exist_ok=True)
    (project_dir / "derivatives" / "freesurfer").mkdir(parents=True, exist_ok=True)
    (project_dir / "derivatives" / "SimNIBS").mkdir(parents=True, exist_ok=True)
    (project_dir / "sourcedata").mkdir(parents=True, exist_ok=True)
    print("  ✓ Directories created")

    print("Creating BIDS metadata files...")
    initialize_readme(project_dir)
    print("  ✓ README created")

    initialize_dataset_description(project_dir)
    print("  ✓ Root dataset_description.json created")

    initialize_derivative_dataset_description(project_dir, "ti-toolbox")
    print("  ✓ ti-toolbox dataset_description.json created")

    initialize_derivative_dataset_description(project_dir, "freesurfer")
    print("  ✓ freesurfer dataset_description.json created")

    initialize_derivative_dataset_description(project_dir, "SimNIBS")
    print("  ✓ SimNIBS dataset_description.json created")

    print("Creating project configuration...")
    initialize_project_status(project_dir)
    print("  ✓ Project status file created")

    (project_dir / "code" / "ti-toolbox" / "config" / ".initialized").touch()
    print("  ✓ Initialization marker created")

    print("")
    print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
    print("  ✓ Project initialization complete!")
    print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
    print("")

setup_example_data

setup_example_data(toolbox_root: Path, project_dir: Path) -> bool

Copy bundled example data into project_dir.

Parameters

toolbox_root : Path Root of the TI-Toolbox installation (contains example data). project_dir : Path Target project directory.

Returns

bool True on success, False on failure.

Source code in tit/project_init/initializer.py
def setup_example_data(toolbox_root: Path, project_dir: Path) -> bool:
    """Copy bundled example data into *project_dir*.

    Parameters
    ----------
    toolbox_root : Path
        Root of the TI-Toolbox installation (contains example data).
    project_dir : Path
        Target project directory.

    Returns
    -------
    bool
        ``True`` on success, ``False`` on failure.
    """
    try:
        success, _subjects = example_data_manager.setup_example_data(
            str(toolbox_root), str(project_dir)
        )
        return bool(success)
    except Exception:
        return False