Skip to content

Resources

This module performs static analysis on raster files and system hardware.

It checks two key aspects before processing: - Memory safety for loading into RAM (Memory Estimation) - Internal block/tile structure of the raster (Block Structure Analysis)

BlockStructure dataclass

Analysis of a raster's internal storage layout.

Parameters:

Name Type Description Default
is_tiled bool

True if raster has native tiles (not full-width strips)

required
is_striped bool

True if raster is structured as strips (full-width blocks)

required
block_shape Tuple[int, int]

Tuple of (block_height, block_width) in pixels

required
Source code in src/phytospatial/raster/resources.py
@dataclass(frozen=True)
class BlockStructure:
    """
    Analysis of a raster's internal storage layout.

    Args:
        is_tiled: True if raster has native tiles (not full-width strips)
        is_striped: True if raster is structured as strips (full-width blocks)
        block_shape: Tuple of (block_height, block_width) in pixels
    """
    is_tiled: bool
    is_striped: bool
    block_shape: Tuple[int, int]
    """
    Analysis of the raster's internal block structure, indicating if it's tiled or striped and the shape of the blocks.
    """

    @property
    def _recommend_blocked(self) -> bool:
        """Helper to determine if BLOCKED mode is recommended."""
        return self.is_tiled and not self.is_striped

block_shape instance-attribute

Analysis of the raster's internal block structure, indicating if it's tiled or striped and the shape of the blocks.

MemoryEstimate dataclass

"Estimation of memory requirements and safety for loading a raster.

Parameters:

Name Type Description Default
total_required_bytes int

Total bytes required to load raster (with overhead)

required
available_system_bytes int

Currently available system memory in bytes

required
is_safe bool

Boolean indicating if loading is considered safe

required
reason str

Explanation for the safety assessment

required
Source code in src/phytospatial/raster/resources.py
@dataclass(frozen=True)
class MemoryEstimate:
    """"Estimation of memory requirements and safety for loading a raster.

    Args:
        total_required_bytes: Total bytes required to load raster (with overhead)
        available_system_bytes: Currently available system memory in bytes
        is_safe: Boolean indicating if loading is considered safe
        reason: Explanation for the safety assessment
    """
    total_required_bytes: int
    available_system_bytes: int
    is_safe: bool
    reason: str

ProcessingMode

Bases: Enum

Strategic recommendation for how to process a raster.

Modes

IN_MEMORY: Load entire raster into RAM. Fastest but requires sufficient memory.

BLOCKED: Use raster's internal blocks/tiles for optimized streaming. Best for tiled files.

TILED: Use standard windowed reading. Safe fallback for large or scanline-based rasters.

Source code in src/phytospatial/raster/resources.py
class ProcessingMode(Enum):
    """
    Strategic recommendation for how to process a raster.

    Modes:
        IN_MEMORY: Load entire raster into RAM. Fastest but requires sufficient memory.

        BLOCKED: Use raster's internal blocks/tiles for optimized streaming. Best for tiled files.

        TILED: Use standard windowed reading. Safe fallback for large or scanline-based rasters.
    """
    IN_MEMORY = "in_memory"
    BLOCKED = "blocked"
    TILED = "tiled"

StrategyReport dataclass

Contains the decision (mode) and the full context (reason, stats).

Parameters:

Name Type Description Default
mode ProcessingMode

Recommended processing mode (ProcessingMode)

required
reason str

Explanation for the recommendation

required
memory_stats MemoryEstimate

MemoryEstimate object with details on memory safety

required
structure_stats BlockStructure

BlockStructure object with details on raster structure

required
Source code in src/phytospatial/raster/resources.py
@dataclass(frozen=True)
class StrategyReport:
    """
    Contains the decision (mode) and the full context (reason, stats).

    Args:
        mode: Recommended processing mode (ProcessingMode)
        reason: Explanation for the recommendation
        memory_stats: MemoryEstimate object with details on memory safety
        structure_stats: BlockStructure object with details on raster structure
    """
    mode: ProcessingMode
    reason: str
    memory_stats: MemoryEstimate
    structure_stats: BlockStructure

determine_strategy(raster_input, user_mode='auto')

Determines the optimal processing strategy for a raster based on memory and internal structure.

Parameters:

Name Type Description Default
raster_input Union[str, Path, DatasetReader]

Polymorphic input for the raster file to analyze (Path or rasterio DatasetReader)

required
user_mode str

Defines available processing modes ('auto', 'in_memory', 'blocked', 'tiled') auto: Automatically determine best mode based on analysis in_memory: Force loading entire raster into RAM (only if safe) blocked: Force using BLOCKED mode (only if structure is suitable) tiled: Force using TILED mode (safe fallback)

'auto'

Returns:

Name Type Description
StrategyReport StrategyReport

The determined processing strategy.

Source code in src/phytospatial/raster/resources.py
def determine_strategy(
    raster_input: Union[str, Path, rasterio.DatasetReader],
    user_mode: str = "auto"
    ) -> StrategyReport:
    """
    Determines the optimal processing strategy for a raster based on memory and internal structure.

    Args:
        raster_input: Polymorphic input for the raster file to analyze (Path or rasterio DatasetReader)
        user_mode: Defines available processing modes ('auto', 'in_memory', 'blocked', 'tiled')
            auto: Automatically determine best mode based on analysis
            in_memory: Force loading entire raster into RAM (only if safe)
            blocked: Force using BLOCKED mode (only if structure is suitable)
            tiled: Force using TILED mode (safe fallback)

    Returns:
        StrategyReport: The determined processing strategy.
    """

    def _build_report(estimate: MemoryEstimate, struct: BlockStructure) -> StrategyReport:
        # User Override Logic
        if user_mode != "auto":
            try:
                mode = ProcessingMode(user_mode)
                reason = f"User forced mode: {user_mode}"
                if mode == ProcessingMode.BLOCKED and not struct._recommend_blocked:
                    mode = ProcessingMode.TILED
                    reason = "Override: Forced TILED because file is STRIPED (User requested BLOCKED)"
                return StrategyReport(mode, reason, estimate, struct)
            except ValueError:
                valid_modes = [m.value for m in ProcessingMode] + ["auto"]
                raise ValueError(f"Invalid mode '{user_mode}'. Must be one of: {valid_modes}")

        # Auto Logic
        if estimate.is_safe:
            return StrategyReport(
                mode=ProcessingMode.IN_MEMORY,
                reason=f"Safe for RAM. {estimate.reason}",
                memory_stats=estimate,
                structure_stats=struct
            )
        if struct._recommend_blocked:
            return StrategyReport(
                mode=ProcessingMode.BLOCKED,
                reason="RAM full, but detected NATIVE TILES. Using BLOCKED mode.",
                memory_stats=estimate,
                structure_stats=struct
            )
        else:
            return StrategyReport(
                mode=ProcessingMode.TILED,
                reason="RAM full and detected STRIPS. Using TILED mode.",
                memory_stats=estimate,
                structure_stats=struct
            )

    # If the file is already open, use it directly
    if isinstance(raster_input, rasterio.DatasetReader):
        try:
            estimate = _estimate_memory_safety(raster_input)
            struct = _analyze_structure(raster_input)
        except Exception as e:
            log.error(f"Failed to analyze resources for open dataset: {e}")
            estimate = MemoryEstimate(0, 0, False, f"Read Error: {e}")
            struct = BlockStructure(False, True, (0,0))
        return _build_report(estimate, struct)

    # Otherwise, resolve the path and open it
    else:
        path = resolve_envi_path(Path(raster_input))
        try:
            with rasterio.open(path) as src:
                estimate = _estimate_memory_safety(src)
                struct = _analyze_structure(src)
        except Exception as e:
            log.error(f"Failed to analyze resources for {path}: {e}")
            estimate = MemoryEstimate(0, 0, False, f"Read Error: {e}")
            struct = BlockStructure(False, True, (0,0))
        return _build_report(estimate, struct)