Large Image

repository·master·Indexed 42 days ago

https://github.com/girder/large_image

A Python-based ecosystem for processing, converting, and serving large, multiresolution geospatial and medical images. It provides a modular framework of tile sources and converters for formats such as DICOM, GeoTIFF, and OME-Zarr, featuring tile and tile source caching mechanisms and support for GeoJSON annotations.

Tokens
28.7K
Snippets
86
Records
125
Agent score
47%

What's inside Large Image

  1. Structure plottable data in metadata and attributes

    master

    Plottable data must be referenced by a dictionary key and can be a string, number (float or integer), or boolean. Data can be nested in dictionaries and arrays (but not recursively).

    Supported data arrangements include:

    • Key-value: "meta": { "key": 1.0 }
    • Key-value list: "meta": { "key": [1.0, 2.0] }
    • Nested key-value: "meta": { "parent": { "key": 1.0 } }
    • Nested key-value list: "meta": { "parent": { "key": [1.0, 2.0] } }
    • Key-list of key-value: "meta": { "parent": [ { "key": 1.0 }, { "key": 2.0 } ] }
    // Example: Key-list of key-value
    "meta": {
      "nucleus": [
        {
          "radius": 4.5,
          "circularity": 0.9
        },
        {
          "radius": 5.5,
          "circularity": 0.86
        }
      ]
    }
  2. Understand the Tile Source Cache mechanism

    master

    The Tile Source Cache optimizes the reading of specific large images by storing file handles, parsed metadata, and other values.

    Key Characteristics:

    • Storage: Always stored in the Python process memory and is not shared between processes.
    • Cache Keys: The key includes the tile source, default tile format, and style.
    • Resource Sharing: File handles and metadata are shared across sources that only differ in style (e.g., if one request applies ICC color correction and another does not).
    • Memory Management: Memory usage varies significantly by tile source. The system estimates usage based on sample files and calculates the maximum number of sources to cache based on a fraction of total memory reported by psutils.
    • Implementation Note for Developers: Because file handles are shared across sources that only differ in style, if you implement a custom __del__ operator in a source, it must check if it is the unstyled source before performing cleanup.
  3. Conversion logic and file formats

    master

    The converter optimizes images for use with large_image based on their type:

    • Geospatial files: Converted into cloud-optimized GeoTIFFs via gdal_translate.
    • Single-image non-geospatial files: Converted into pyramidal TIFF files via pyvips.
    • Multi-image TIFF files: Converted into TIFF files containing multiple pyramidal TIFF images, using a custom image description to store frame details.

    Why convert? While some files (like uncompressed nd2 files) can be read directly by large_image tile sources, converting them results in significantly more efficient data access.

  4. Create a multi-source tile source

    master

    A multi-source tile source allows you to composite multiple individual image sources into a single conceptual tile source. This is configured using a YAML or JSON file that defines the target dimensions and a list of constituent sources. Each source can have its own path, position, scaling, and axis values.

    width: 720
    height: 720
    sources:
      - path: ./image1.tif
        position:
          x: 0
          y: 0
          scale: 2
      - path: ./image2.tif
        position:
          x: 360
          y: 0
          scale: 2
  5. Configure folder metadata for item resolution and bounding boxes

    master

    When providing plottable data in the meta key of a folder, you can use specific naming conventions to trigger special behaviors:

    1. Item Resolution: If the most nested key matches the regex r'(?i)(item|image)_(id|name)$', the value is resolved to the canonical name or _id of an item within that folder. This only matches items included in the results (controlled by the adjacentItems flag).
    2. Bounding Box Coordinates: If the key matches the regex r'(?i)((low|min|high|max)(_|)(x|y)|(^(x|y)(1|2)$))', the value is treated as a coordinate of an image bounding box. This allows clients to visualize the specific portion of the image associated with the record.
  6. Structure of a YAML configuration file

    master

    YAML configuration files support hierarchical settings and access control based on user roles and group membership.

    Key structural elements:

    • Base level: Standard key-value pairs.
    • access: Defines settings for specific user roles:
      • user: Settings applied to logged-in users.
      • admin: Settings applied to admin users.
      • Note: If a dictionary key matches a base-level key, values are merged. Use __all__: true to completely replace the base value.
    • groups: Specifies settings for specific user groups.
    • users: Specifies settings for specific user logins.
    • __inherit__: A boolean that, if true, merges this file with its parent's configuration.
    ---
    # Base settings
    key: value
    
    access:
      user:
        key: value
      admin:
        key: value
    
    groups:
      group_name:
        key: value
        access:
          user: ...
    
    users:
      user_login:
        key: value
    
    __inherit__: true
    ---
  7. How the tile serving workflow works

    master

    When the GET /item/{id}/tile/zxy/{z}/{x}/{y}?style=<style>&encoding=<encoding>&... endpoint is called, Girder follows a hierarchical lookup and synthesis process:

    1. Tile Cache Check: It first checks if the specific requested tile is already in the tile cache.
    2. Source Cache Check: If not in the tile cache, it checks if the file-like object (the source) is in the source cache with the requested style and encoding.
    3. Unstyled Source Handling: If the styled source isn't cached, it looks for an unstyled version with the requested encoding. If missing, it opens the unstyled file, caches it, and then creates/caches the styled version.
    4. Tile Synthesis/Extraction:
      • If the unstyled tile is not in the cache, Girder either reads it directly from the file or synthesizes it from higher-resolution tiles.
      • The resulting unstyled tile is then placed in the tile cache.
    5. Styling and Encoding:
      • If a style is required, Girder converts the tile (to a numpy array if necessary) and generates the styled version.
      • The tile is then encoded to the requested encoding format.
      • The final encoded tile is placed in the tile cache and served.
    graph TD;
        apicall(["GET /item/{id}/tile/zxy/{z}/{x}/{y}?style=<style>&encoding=<encoding>&..."])
        checktile1{Is tile in\ntile cache?}
        apicall --> checktile1;
        getfile[("Get file-like object\nor url from Girder")]
        checktile1 -- No --> getfile;
        servetile(["Serve tile 🖼️"])
        checktile1 -- Yes --> servetile;
        checksource{"Is file-like object\nin source cache\nwith {style}\nand {encoding}?"}
        getfile --> checksource;
        checkunstyled{"Is unstyled file\nin source cache\nwith {encoding}?"}
        checksource -- No --> checkunstyled;
        openunstyle["Open unstyled file with {encoding}"]
        checkunstyled -- No --> openunstyle;
        putunstyledsource["/Put unstyled source in cache"/]
        openunstyle --> putunstyledsource;
        alter["Copy unstyled source\nand alter for {style}"]
        putunstyledsource --> alter;
        checkunstyled -- Yes --> alter;
        putsource["/Put styled source in cache"/]
        alter --> putsource;
        checktile2{"Is unstyled tile\n{x}, {y}, {z},\n{frame}, {encoding}\nin tile cache?"}
        putsource --> checktile2;
        checksource -- Yes --> checktile2;
        checktile3{"Does tile exist\nin the file?"}
        checktile2 -- No --> checktile3;
        maketile[["Synthesize tile\nfrom higher\nresolution tiles"]]
        checktile3 -- No --> maketile;
        readfile[("Read tile from file")]
        checktile3 -- Yes --> readfile;
        puttile1["/Put unstyled\ntile in cache"/]
        maketile --> puttile1;
        readfile --> puttile1;
        gettile["Read unstyled tile\nfrom cache"]
        checktile2 -- Yes --> gettile;
        needstyle{"Is {style}\nrequired?"}
        puttile1 --> needstyle;
        gettile --> needstyle;
        checkencoding{"Is tile\nin {encoding}?"}
        needstyle -- No --> checkencoding;
        isnumpy{"Is unstyled\ntile a\nnumpy array?"}
        needstyle -- Yes --> isnumpy;
        makenumpy["Convert tile to numpy"]
        isnumpy -- No --> makenumpy;
        makestyle["Generate styled tile in numpy format\n(Note that this could fetch other tiles)"]
        isnumpy -- Yes --> makestyle;
        makenumpy --> makestyle;
        makestyle --> checkencoding;
        encode["Encode tile to {encoding}"]
        checkencoding -- No --> encode;
        puttile2["/Put encoded tile\nin cache"/]
        encode --> puttile2;
        checkencoding -- Yes --> puttile2;
        puttile2 --> servetile;
  8. How YAML configuration files are resolved

    master

    Settings can be specified per-folder tree using .large_image_config.yaml files. The system searches for configuration files using the following hierarchy:

    1. The current folder.
    2. Parent folders (iteratively up to the parent collection or user).
    3. The .config folder in the collection or user.
    4. The Configuration Folder specified in the plugin settings page.

    If __inherit__: true is set in a YAML file, the configuration will be merged with the next configuration file found in the parent folder hierarchy.

  9. Understand image axes: bands, channels, and frames

    master

    Large-image harmonizes terminology for multi-dimensional data:

    • Bands/Samples: Image plane components like RGB or NIR. These are typically returned together by default.
    • Channels: Often stored as separate frames (e.g., different fluorescence modalities like DAPI).
    • Axes:
      • x, y: Horizontal and vertical dimensions.
      • s: Bands/Samples.
      • c: Channels (distinct frames).
      • z: Depth/Altitude.
      • t: Time.

    Use frameFromAxes and axesFromFrame utility functions to navigate between axis values and frame indices.

  10. Understand the Annotation Schema structure

    master

    An annotation in the Large Image system is composed of a basic structure containing a free-form attributes object and a list of elements. The elements are strictly defined by the schema and primarily consist of specific shapes or image overlays.

    Key components include:

    • name: A non-empty string identifying the annotation (optional).
    • description: A string providing context (optional).
    • display: An object controlling visibility (optional).
    • attributes: A free-form object for storing arbitrary metadata (optional).
    • elements: A list of shapes or overlays (optional).
    {
      "name": "MyAnnotationName",
      "description": "This is a description",
      "display": {
          "visible": "new"
      },
      "attributes": {
        "key1": "value1",
        "key2": ["any", {"value": "can"}, "go", "here"]
      },
      "elements": []
    }
  11. Understand the Tile Cache mechanism

    master

    The Tile Cache stores individual images or numpy arrays for each processed tile. This allows for rapid retrieval of previously computed tiles.

    Key Characteristics:

    • Storage Backends: Can be stored in the Python process memory or in memcached. Custom cache backends can also be added.
    • Cache Keys: The key is a hash derived from the tile source, tile location within the source, format, compression, and style.
    • Sharing: Using memcached allows cached tiles to be shared across multiple processes. In-process memory caching is local to the specific Python process.
    • Content: Cached tiles can include original as-read data, styled data, or transformed data. It also caches synthesized tiles for sources missing specific resolutions.
    • Memory Management:
      • memcached: Memory usage and location are determined by the memcached configuration. Note that memcached may need configuration to allow the larger values required by tiles.
      • Python Process: Memory is limited to a fraction of the total system memory as reported by psutils.
  12. How large-image selects source readers for files

    master

    When calling large_image.open(), the library attempts to open the file using available tile source readers in sequence. While images can generally be read regardless of their filename, large-image uses file extensions and MIME types to optimize the selection process.

    Each source reader defines a list of preferred extensions and MIME types with associated priority levels. If a file matches these criteria, the order in which source readers are tried is adjusted based on those priorities to ensure the most appropriate reader is used first.

    To inspect the specific priorities and available sources, use:

    • large_image.listSources() to see the priority of different sources.
    • large_image.constants.SourcePriority to understand the meaning of the priority levels.