asp_plot.selections
===================

.. py:module:: asp_plot.selections

.. autoapi-nested-parse::

   Reproducible "figure selections" for asp_plot reports.

   When re-processing the same scene with different ASP parameters, the diagnostic
   figures silently change *what they show* between runs: a fresh ICESat-2 request
   returns a slightly different point set, the "best" profile track flips, the
   best/worst agreement segments move, and the detailed-hillshade clip boxes are
   re-selected from the (re-processed) intersection-error raster. That makes
   before/after comparison impossible.

   This module persists every non-deterministic selection a run made to a small
   YAML sidecar next to the report, and reads it back so a later run can replay the
   same choices. It deliberately imports nothing from ``report.py`` / ``fpdf`` so
   it stays safe to use from notebooks.

   See issue: https://github.com/uw-cryo/asp_plot/issues/121



Attributes
----------

.. autoapisummary::

   asp_plot.selections.HILLSHADE_CLIP_LABELS
   asp_plot.selections.SELECTIONS_SCHEMA_VERSION
   asp_plot.selections.logger


Classes
-------

.. autoapisummary::

   asp_plot.selections.FigureSelections


Functions
---------

.. autoapisummary::

   asp_plot.selections.bbox_to_pixel_offset
   asp_plot.selections.pixel_window_to_bbox
   asp_plot.selections.read_selections_yaml
   asp_plot.selections.reproject_bbox
   asp_plot.selections.write_selections_yaml


Module Contents
---------------

.. py:class:: FigureSelections

   Container for the reproducible selections made while building a report.

   All nested values are plain JSON/YAML-serializable types (dicts, lists,
   numbers, strings) so the object round-trips cleanly through YAML.

   .. attribute:: asp_plot_version

      asp_plot version that wrote the file (informational).

      :type: str or None

   .. attribute:: dem_filename

      DEM the selections were computed against (informational).

      :type: str or None

   .. attribute:: map_crs

      Map CRS string used by the report (informational).

      :type: str or None

   .. attribute:: detailed_hillshade

      ``{"subset_km": float, "intersection_error_percentiles": [..],
      "dem_crs": "EPSG:XXXX", "clips": [{"label": str,
      "bbox": [xmin, ymin, xmax, ymax], "pixel_offset": [row, col]}, ...]}``.
      ``bbox`` is in ``dem_crs`` map coordinates (robust to a re-gridded DEM).

      :type: dict or None

   .. attribute:: icesat2

      ``{"request": {..}, "parquet_cache": {key: path}, "profile_track":
      {"rgt": int, "cycle": int, "spot": int}, "segments": {"best": {..},
      "worst": {..}}}``. Omitted (None) for planetary DEMs.

      :type: dict or None


   .. py:method:: from_dict(data)
      :classmethod:


      Build a FigureSelections from a parsed YAML/JSON dict.



   .. py:method:: to_dict()

      Return a plain dict suitable for YAML serialization.



   .. py:attribute:: asp_plot_version
      :type:  Optional[str]
      :value: None



   .. py:attribute:: dem_filename
      :type:  Optional[str]
      :value: None



   .. py:attribute:: detailed_hillshade
      :type:  Optional[dict]
      :value: None



   .. py:attribute:: icesat2
      :type:  Optional[dict]
      :value: None



   .. py:attribute:: map_crs
      :type:  Optional[str]
      :value: None



   .. py:attribute:: schema_version
      :type:  int
      :value: 1



.. py:function:: bbox_to_pixel_offset(transform, bbox)

   Convert a map-coordinate bbox to a top-left pixel offset (row, col).

   The window size is *not* returned: on reuse the subset size is recomputed
   from ``subset_km`` and the current DEM's GSD so the ground footprint stays
   constant even if the DEM resolution changed. Only the top-left anchor is
   needed.

   :param transform: Geotransform of the DEM being clipped on reuse.
   :type transform: affine.Affine
   :param bbox: ``[xmin, ymin, xmax, ymax]`` in the DEM's CRS.
   :type bbox: sequence of float

   :returns: ``(row, col)`` top-left pixel offset (clamped to be non-negative).
   :rtype: tuple of int


.. py:function:: pixel_window_to_bbox(transform, row, col, n_rows, n_cols)

   Convert a pixel window (top-left row/col + size) to a map-coordinate bbox.

   :param transform: Raster geotransform (``raster.ds.transform``).
   :type transform: affine.Affine
   :param row: Top-left pixel of the window.
   :type row: int
   :param col: Top-left pixel of the window.
   :type col: int
   :param n_rows: Window height/width in pixels.
   :type n_rows: int
   :param n_cols: Window height/width in pixels.
   :type n_cols: int

   :returns: ``[xmin, ymin, xmax, ymax]`` in the raster's CRS.
   :rtype: list of float


.. py:function:: read_selections_yaml(path)

   Read a FigureSelections from a YAML file.

   :param path: Path to a previously written selections file.
   :type path: str

   :rtype: FigureSelections


.. py:function:: reproject_bbox(bbox, src_crs, dst_crs)

   Reproject a map-coordinate bbox from one CRS to another.

   Used when replaying detailed-hillshade clips against a DEM in a different
   CRS than the run that wrote them (e.g. a mapprojected vs. non-mapprojected
   stereo variant of the same scene, which can land in different projections).
   Returns the input unchanged when either CRS is missing or they are equal.

   :param bbox: ``[xmin, ymin, xmax, ymax]`` in ``src_crs``.
   :type bbox: sequence of float
   :param src_crs: CRS the bbox is currently expressed in.
   :type src_crs: str or rasterio.crs.CRS or None
   :param dst_crs: Target CRS (the DEM being clipped on reuse).
   :type dst_crs: str or rasterio.crs.CRS or None

   :returns: ``[xmin, ymin, xmax, ymax]`` in ``dst_crs``.
   :rtype: list of float


.. py:function:: write_selections_yaml(path, selections)

   Write a FigureSelections to a YAML file.

   :param path: Destination path (e.g. ``<report_stem>_figure_selections.yml``).
   :type path: str
   :param selections: Selections to serialize.
   :type selections: FigureSelections


.. py:data:: HILLSHADE_CLIP_LABELS
   :value: ['low', 'medium', 'high']


.. py:data:: SELECTIONS_SCHEMA_VERSION
   :value: 1


.. py:data:: logger

