# generated by datamodel-codegen:
#   filename:  https://raw.githubusercontent.com/mobie/mobie.github.io/master/schema/dataset.schema.json
#   timestamp: 2024-07-09T08:23:17+00:00

from __future__ import annotations

from enum import Enum
from typing import Annotated, Dict, List, Optional, Union

from pydantic import (
    BaseModel,
    ConfigDict,
    Field,
    PositiveFloat,
    RootModel,
    confloat,
    conint,
    constr,
    field_validator,
)


class BdvHdf5(BaseModel):
    """
    Data stored in the bdv.hdf5 format, i.e. hdf5 data that is stored on the local fileystem.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    relativePath: str = Field(
        ...,
        description='The file path to the xml storing the bdv metadata, relative to the dataset root location.',
    )
    absolutePath: Optional[str] = Field(
        None, description='The absolute file path to the xml storing the bdv metadata.'
    )
    channel: Optional[int] = Field(
        None,
        description='Optional setup to display from the bdv.xml, in case it contains multiple setups.',
    )


class BdvHdf51(BaseModel):
    """
    Data stored in the bdv.hdf5 format, i.e. hdf5 data that is stored on the local fileystem.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    relativePath: Optional[str] = Field(
        None,
        description='The file path to the xml storing the bdv metadata, relative to the dataset root location.',
    )
    absolutePath: str = Field(
        ..., description='The absolute file path to the xml storing the bdv metadata.'
    )
    channel: Optional[int] = Field(
        None,
        description='Optional setup to display from the bdv.xml, in case it contains multiple setups.',
    )


class BdvN5(BaseModel):
    """
    Data stored in the bdv.n5 format, i.e. n5 data that is stored on the local fileystem.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    relativePath: str = Field(
        ...,
        description='The file path to the xml storing the bdv metadata, relative to the dataset root location.',
    )
    absolutePath: Optional[str] = Field(
        None, description='The absolute file path to the xml storing the bdv metadata.'
    )
    channel: Optional[int] = Field(
        None,
        description='Optional setup to display from the bdv.xml, in case it contains multiple setups.',
    )


class BdvN51(BaseModel):
    """
    Data stored in the bdv.n5 format, i.e. n5 data that is stored on the local fileystem.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    relativePath: Optional[str] = Field(
        None,
        description='The file path to the xml storing the bdv metadata, relative to the dataset root location.',
    )
    absolutePath: str = Field(
        ..., description='The absolute file path to the xml storing the bdv metadata.'
    )
    channel: Optional[int] = Field(
        None,
        description='Optional setup to display from the bdv.xml, in case it contains multiple setups.',
    )


class BdvN5S3(BaseModel):
    """
    Data stored in the bdv.n5.s3 format, i.e. n5 data that is stored on a s3 object store.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    relativePath: str = Field(
        ...,
        description='The file path to the xml storing the bdv metadata, relative to the dataset root location.',
    )
    channel: Optional[int] = Field(
        None,
        description='Optional setup to display from the bdv.xml, in case it contains multiple setups.',
    )


class BdvOmeZarr(BaseModel):
    """
    Data stored in the bdv.ome.zarr format, i.e. ome.zarr data that is stored on the local fileystem with additional bdv xml metadata. This data format is deprecated, please use ome.zarr instead.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    relativePath: str = Field(
        ...,
        description='The file path to the xml storing the bdv metadata, relative to the dataset root location.',
    )
    absolutePath: Optional[str] = Field(
        None, description='The absolute file path to the xml storing the bdv metadata.'
    )
    channel: Optional[int] = Field(
        None,
        description='Optional setup to display from the bdv.xml, in case it contains multiple setups.',
    )


class BdvOmeZarr1(BaseModel):
    """
    Data stored in the bdv.ome.zarr format, i.e. ome.zarr data that is stored on the local fileystem with additional bdv xml metadata. This data format is deprecated, please use ome.zarr instead.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    relativePath: Optional[str] = Field(
        None,
        description='The file path to the xml storing the bdv metadata, relative to the dataset root location.',
    )
    absolutePath: str = Field(
        ..., description='The absolute file path to the xml storing the bdv metadata.'
    )
    channel: Optional[int] = Field(
        None,
        description='Optional setup to display from the bdv.xml, in case it contains multiple setups.',
    )


class BdvOmeZarrS3(BaseModel):
    """
    Data stored in the bdv.ome.zarr.s3 format, i.e. ome.zarr data that is stored on a s3 object store with additional bdv xml metadata. This data format is deprecated, please use ome.zarr.s3 instead.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    relativePath: str = Field(
        ...,
        description='The file path to the xml storing the bdv metadata, relative to the dataset root location.',
    )
    channel: Optional[int] = Field(
        None,
        description='Optional setup to display from the bdv.xml, in case it contains multiple setups.',
    )


class OmeZarr(BaseModel):
    """
    Data stored in the ome.zarr format, i.e. ome.zarr data that is stored on the local fileystem.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    relativePath: str = Field(
        ...,
        description='The file path to the ome.zarr file, relative to the dataset root location.',
    )
    absolutePath: Optional[str] = Field(
        None, description='The absolute file path to the ome.zarr file.'
    )
    channel: Optional[int] = Field(
        None,
        description='Optional channel to display from the ome.zarr file, in case it contains multiple channels.',
    )


class OmeZarr1(BaseModel):
    """
    Data stored in the ome.zarr format, i.e. ome.zarr data that is stored on the local fileystem.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    relativePath: Optional[str] = Field(
        None,
        description='The file path to the ome.zarr file, relative to the dataset root location.',
    )
    absolutePath: str = Field(
        ..., description='The absolute file path to the ome.zarr file.'
    )
    channel: Optional[int] = Field(
        None,
        description='Optional channel to display from the ome.zarr file, in case it contains multiple channels.',
    )


class OmeZarrS3(BaseModel):
    """
    Data stored in the ome.zarr.s3 format, i.e. ome.zarr data that is stored on a s3 object store.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    s3Address: str = Field(..., description='The s3 address for this image data.')
    signingRegion: Optional[str] = Field(
        None, description='The signing region for aws, e.g. us-east-1'
    )
    channel: Optional[int] = Field(
        None,
        description='Optional channel to display from the ome.zarr file, in case it contains multiple channels.',
    )


class OpenOrganelleS3(BaseModel):
    """
    Data stored in the openOrganelle file format on a s3 object store.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    s3Address: str = Field(..., description='The s3 address for this image data.')
    signingRegion: Optional[str] = Field(
        None, description='The signing region for aws, e.g. us-east-1'
    )
    channel: Optional[int] = Field(
        None,
        description='Optional channel to display from the open organelle file, in case it contains multiple channels.',
    )


class ImageData(BaseModel):
    """
    Description of the image data for this source, including the file format and the location of the data.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    bdv_hdf5: Optional[Union[BdvHdf5, BdvHdf51]] = Field(
        None,
        alias='bdv.hdf5',
        description='Data stored in the bdv.hdf5 format, i.e. hdf5 data that is stored on the local fileystem.',
    )
    bdv_n5: Optional[Union[BdvN5, BdvN51]] = Field(
        None,
        alias='bdv.n5',
        description='Data stored in the bdv.n5 format, i.e. n5 data that is stored on the local fileystem.',
    )
    bdv_n5_s3: Optional[BdvN5S3] = Field(
        None,
        alias='bdv.n5.s3',
        description='Data stored in the bdv.n5.s3 format, i.e. n5 data that is stored on a s3 object store.',
    )
    bdv_ome_zarr: Optional[Union[BdvOmeZarr, BdvOmeZarr1]] = Field(
        None,
        alias='bdv.ome.zarr',
        description='Data stored in the bdv.ome.zarr format, i.e. ome.zarr data that is stored on the local fileystem with additional bdv xml metadata. This data format is deprecated, please use ome.zarr instead.',
    )
    bdv_ome_zarr_s3: Optional[BdvOmeZarrS3] = Field(
        None,
        alias='bdv.ome.zarr.s3',
        description='Data stored in the bdv.ome.zarr.s3 format, i.e. ome.zarr data that is stored on a s3 object store with additional bdv xml metadata. This data format is deprecated, please use ome.zarr.s3 instead.',
    )
    ome_zarr: Optional[Union[OmeZarr, OmeZarr1]] = Field(
        None,
        alias='ome.zarr',
        description='Data stored in the ome.zarr format, i.e. ome.zarr data that is stored on the local fileystem.',
    )
    ome_zarr_s3: Optional[OmeZarrS3] = Field(
        None,
        alias='ome.zarr.s3',
        description='Data stored in the ome.zarr.s3 format, i.e. ome.zarr data that is stored on a s3 object store.',
    )
    openOrganelle_s3: Optional[OpenOrganelleS3] = Field(
        None,
        alias='openOrganelle.s3',
        description='Data stored in the openOrganelle file format on a s3 object store.',
    )


class Tsv(BaseModel):
    """
    Table data in tsv file format, specified as root location for the folder with tables. The folder MUST contain the table default.tsv, which will always be loaded as the first table for this source; this table MUST contain the mandatory columns for the given source type.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    relativePath: str = Field(
        ...,
        description='The relative path of the table data w.r.t the dataset root location.',
    )


class TableData(BaseModel):
    """
    Description of the table data for this source, including the format and the location of the table data.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    tsv: Tsv = Field(
        ...,
        description='Table data in tsv file format, specified as root location for the folder with tables. The folder MUST contain the table default.tsv, which will always be loaded as the first table for this source; this table MUST contain the mandatory columns for the given source type.',
    )


class ImageSource(BaseModel):
    """
    An image source. The source name (=key for this source entry) must be the same as the setup name in the bdv.xml.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    imageData: ImageData


class SegmentationSource(BaseModel):
    """
    A segmentation source. The source name (=key for this source entry) must be the same as the setup name in the bdv.xml.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    imageData: ImageData
    tableData: Optional[TableData] = None


class SpotSource(BaseModel):
    """
    A spot source, corresponding to a collection of points loaded from a table. The table must contain the columns 'spot_id', as well as 'x', 'y', 'z' ('z' is not required for 2d datasets.)
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    boundingBoxMin: List[float] = Field(
        ...,
        description='The minimum of the bounding box of physical cooridinates for the spots.',
        max_length=3,
        min_length=2,
    )
    boundingBoxMax: List[float] = Field(
        ...,
        description='The maximum of the bounding box of physical cooridinates for the spots.',
        max_length=3,
        min_length=2,
    )
    tableData: TableData
    unit: str = Field(
        ...,
        description='The unit of measurement for the coordinate system, e.g. micrometer or nanometer',
    )


class RegionSource(BaseModel):
    """
    A region table, corresponding to a image region attributes. The table must contain the column 'region_id'. Can be referenced in regionDisplays
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    tableData: TableData


class Name(RootModel[constr(pattern=r'^[^;\/ ]+$')]):
    root: constr(pattern=r'^[^;\/ ]+$') = Field(..., description='Name of this field.')


class AdditionalTables(
    RootModel[
        List[
            Union[
                constr(pattern=r'^[^;\/ ]+\.csv$'), constr(pattern=r'^[^;\/ ]+\.tsv$')
            ]
        ]
    ]
):
    """
    Additional tables to load for this display. This only needs to be specified if additional tables are loaded; the default table (default.tsv) will always be loaded.
    """

    root: List[
        Union[constr(pattern=r'^[^;\/ ]+\.csv$'), constr(pattern=r'^[^;\/ ]+\.tsv$')]
    ] = Field(
        ...,
        description='Additional tables to load for this display. This only needs to be specified if additional tables are loaded; the default table (default.tsv) will always be loaded.',
        min_length=1,
    )


class Lut(Enum):
    """
    The look-up-table for categorical coloring modes. Note: if the lut is numeric ('viridis', 'blueWhiteRed'), the valueLimits field must be given. 'argbColumn' is deprecated, please use 'rgbaColumn' instead
    """

    argbColumn = 'argbColumn'
    blueWhiteRed = 'blueWhiteRed'
    glasbey = 'glasbey'
    glasbeyZeroTransparent = 'glasbeyZeroTransparent'
    viridis = 'viridis'
    rgbaColumn = 'rgbaColumn'


class Opacity(RootModel[confloat(ge=0.0, le=1.0)]):
    root: confloat(ge=0.0, le=1.0) = Field(
        ...,
        description='The alpha value used for blending segmentation and image data in the viewer.',
    )


class OpacityNotSelected(RootModel[confloat(ge=0.0, le=1.0)]):
    root: confloat(ge=0.0, le=1.0) = Field(
        ...,
        description='The alpha value used for blending segmentation and image data in the viewer that is applied to segments that are not currently selected. By default this is set to 0.15',
    )


class ValueLimits(RootModel[List[float]]):
    """
    Value limits for numerical color maps: 'blueWhiteRed', 'viridis'.
    """

    root: List[float] = Field(
        ...,
        description="Value limits for numerical color maps: 'blueWhiteRed', 'viridis'.",
    )


class ScatterPlotAxes(RootModel[List[Name]]):
    """
    The names of columns which should be used for the scatter plot.
    """

    root: List[Name] = Field(
        ...,
        description='The names of columns which should be used for the scatter plot.',
        max_length=2,
        min_length=2,
    )


class MapOfSources(RootModel[Optional[Dict[str, List[Name]]]]):
    root: Optional[Dict[str, List[Name]]] = Field(None, min_length=1)


class NestedListOfSources(RootModel[List[List[Name]]]):
    root: List[List[Name]]


class Positions(RootModel[List[List[int]]]):
    """
    Grid positions for the sources. If not specified, the sources will be arranged in a square grid. If given, must have the same length as `sources` and contain 2d grid positions specified as [y, x].
    """

    root: List[List[int]] = Field(
        ...,
        description='Grid positions for the sources. If not specified, the sources will be arranged in a square grid. If given, must have the same length as `sources` and contain 2d grid positions specified as [y, x].',
        min_length=2,
    )


class ColorModel1(Enum):
    black = 'black'
    blue = 'blue'
    cyan = 'cyan'
    darkGray = 'darkGray'
    randomFromGlasbey = 'randomFromGlasbey'
    gray = 'gray'
    green = 'green'
    lightGray = 'lightGray'
    magenta = 'magenta'
    orange = 'orange'
    pink = 'pink'
    red = 'red'
    white = 'white'
    yellow = 'yellow'


class ColorModel(
    RootModel[
        Union[
            ColorModel1,
            constr(pattern=r'^(\d+)-(\d+)-(\d+)-(\d+)$'),
            constr(pattern=r'^r=(\d+),g=(\d+),b=(\d+),a=(\d+)$'),
            constr(pattern=r'^r(\d+)-g(\d+)-b(\d+)-a(\d+)$'),
        ]
    ]
):
    root: Union[
        ColorModel1,
        constr(pattern=r'^(\d+)-(\d+)-(\d+)-(\d+)$'),
        constr(pattern=r'^r=(\d+),g=(\d+),b=(\d+),a=(\d+)$'),
        constr(pattern=r'^r(\d+)-g(\d+)-b(\d+)-a(\d+)$'),
    ]


class SelectionColor(RootModel[ColorModel]):
    model_config = ConfigDict(
        use_enum_values=True,
    )

    root: ColorModel = Field(
        ...,
        description='Color for the selected objects (segments, regions or spots). By default the color that follows from the current coloring scheme is used.',
    )


class BlendingMode(Enum):
    """
    The blending mode for rendeting multiple image sources on top of each other. Use 'sum' for additive blending and 'alpha' for occluded blending. If not specified 'sum' will be used
    """

    sum = 'sum'
    alpha = 'alpha'


class Resolution3dViewItem(RootModel[PositiveFloat]):
    root: PositiveFloat


class ImageDisplay1(BaseModel):
    """
    Viewer state for a group of image sources.
    """

    model_config = ConfigDict(
        extra='forbid',
        use_enum_values=True,
    )
    color: Union[
        ColorModel1,
        constr(pattern=r'^(\d+)-(\d+)-(\d+)-(\d+)$'),
        constr(pattern=r'^r=(\d+),g=(\d+),b=(\d+),a=(\d+)$'),
        constr(pattern=r'^r(\d+)-g(\d+)-b(\d+)-a(\d+)$'),
    ] = Field(..., description='The color map.')
    contrastLimits: List[float] = Field(..., description='The contrast limits.')
    name: Name = Field(..., description='Name of this image display.')
    opacity: Opacity
    blendingMode: Optional[BlendingMode] = Field(
        None,
        description="The blending mode for rendeting multiple image sources on top of each other. Use 'sum' for additive blending and 'alpha' for occluded blending. If not specified 'sum' will be used",
    )
    resolution3dView: Optional[List[Resolution3dViewItem]] = Field(
        None,
        description="The resolution used for the 3d viewer, in physical units. Only relevant if 'showImageIn3d' is true. Will be determined automatically if not specified.",
        max_length=3,
        min_length=3,
    )
    showImagesIn3d: Optional[bool] = Field(
        None, description='Whether to show the images in the 3d viewer.'
    )
    sources: List[Name] = Field(
        ...,
        description='The image sources that are part of this display group. Multiple sources should be moved apart spatially with source transform(s), e.g. grid, otherwise they will not be correctly displayed in the viewer.',
        min_length=1,
    )
    visible: Optional[bool] = Field(
        None, description='Are the sources of this display visible? Default is true.'
    )
    invert: Optional[bool] = Field(
        None, description='Whether to invert the colormap. Default is false'
    )


class ImageDisplay(BaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    imageDisplay: ImageDisplay1 = Field(
        ..., description='Viewer state for a group of image sources.'
    )


class SegmentationDisplay1(BaseModel):
    model_config = ConfigDict(
        extra='forbid',
        use_enum_values=True,
    )
    opacity: Opacity
    opacityNotSelected: Optional[OpacityNotSelected] = None
    lut: Lut
    colorByColumn: Optional[Name] = Field(
        None,
        description="Name of table column that is used for coloring. By default the 'label_id' column is used.",
    )
    resolution3dView: Optional[List[Resolution3dViewItem]] = Field(
        None,
        description="Resolution used for the 3d viewer, in physical units. Only relevant if 'showSelectedSegmentsIn3d' is true. Will be determined automatically if not specified.",
        max_length=3,
        min_length=3,
    )
    name: Name = Field(..., description='Name of this segmentation display.')
    scatterPlotAxes: Optional[ScatterPlotAxes] = None
    selectedSegmentIds: Optional[List[constr(pattern=r'^[^; \/]+;\d+;\d+$')]] = Field(
        None,
        description='List of selected segment ids, each of the form sourceName;timePoint;label_id',
    )
    showScatterPlot: Optional[bool] = Field(
        None,
        description="Whether to show the scatter plot. The default is 'false', i.e. if this property is not present the scatter plot should not be shown. If it is 'true' then 'scatterPlotAxes' must be given.",
    )
    showSelectedSegmentsIn3d: Optional[bool] = Field(
        None, description='Whether to show the selected segments in the 3d viewer.'
    )
    sources: List[Name] = Field(
        ...,
        description='The segmentation sources that are part of this display group. Multiple sources should be moved apart spatially with source transform(s), e.g. grid, otherwise they will not be correctly displayed in the viewer.',
        min_length=1,
    )
    additionalTables: Optional[AdditionalTables] = None
    valueLimits: Optional[ValueLimits] = None
    visible: Optional[bool] = Field(
        None, description='Are the sources of this display visible? Default is true.'
    )
    showTable: Optional[bool] = Field(
        None,
        description='Show the table GUI element. Default is true (if the display has a table).',
    )
    showAsBoundaries: Optional[bool] = Field(
        None,
        description='Show boundary mask instead of segment masks. Default is false.',
    )
    boundaryThickness: Optional[float] = Field(
        None,
        description='Thickness of the boundary masks. Only used if showAsBoundaries is true.',
    )
    randomColorSeed: Optional[float] = Field(
        None,
        description='Random seed for the random color lut (e.g. glasbey) to reproduce the exact colors of the view. (Optional)',
    )
    selectionColor: Optional[SelectionColor] = None


class SegmentationDisplay(BaseModel):
    """
    Viewer state for a group of segmentation sources.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    segmentationDisplay: SegmentationDisplay1 = Field(..., description='')


class RegionDisplay1(BaseModel):
    model_config = ConfigDict(
        extra='forbid',
        use_enum_values=True,
    )
    name: Name
    sources: MapOfSources = Field(
        ...,
        description='Map of region_ids (first table column) to source per location.',
    )
    tableSource: Optional[Name] = None
    additionalTables: Optional[AdditionalTables] = None
    lut: Lut
    colorByColumn: Optional[Name] = Field(
        None,
        description="Name of table column that is used for coloring. By default the 'region_id' column is used.",
    )
    opacity: Opacity
    opacityNotSelected: Optional[OpacityNotSelected] = None
    valueLimits: Optional[ValueLimits] = None
    selectedRegionIds: Optional[List[constr(pattern=r'^\d+;[^;\/ ]+$')]] = Field(
        None,
        description='List of selected source region ids, each of the form timePoint;region_id',
    )
    scatterPlotAxes: Optional[ScatterPlotAxes] = None
    showScatterPlot: Optional[bool] = Field(
        None,
        description="Whether to show the scatter plot. The default is 'false', i.e. if this property is not present the scatter plot should not be shown.",
    )
    showTable: Optional[bool] = Field(
        None, description='Show the table GUI element. Default is true.'
    )
    visible: Optional[bool] = Field(
        None,
        description='Is the color overlay of this display visible? Default is true.',
    )
    showAsBoundaries: Optional[bool] = Field(
        None,
        description='Show boundary mask instead of region masks. Default is false.',
    )
    boundaryThickness: Optional[float] = Field(
        None,
        description='Thickness of the boundary masks. Only used if showAsBoundaries is true.',
    )
    boundaryThicknessIsRelative: Optional[bool] = Field(
        None,
        description='Whether the boundary thicknes is given relative to the region size, or in absolute units (default: False)',
    )
    randomColorSeed: Optional[float] = Field(
        None,
        description='Random seed for the random color lut (e.g. glasbey) to reproduce the exact colors of the view. (Optional)',
    )
    selectionColor: Optional[SelectionColor] = None


class RegionDisplay(BaseModel):
    """
    Viewer state for a spatial arrangement of sources, e.g. via grid transform, with an associated table.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    regionDisplay: RegionDisplay1


class SpotDisplay1(BaseModel):
    model_config = ConfigDict(
        extra='forbid',
        use_enum_values=True,
    )
    opacity: Opacity
    opacityNotSelected: Optional[OpacityNotSelected] = None
    lut: Lut
    colorByColumn: Optional[Name] = Field(
        None,
        description="Name of table column that is used for coloring. By default the 'label_id' column is used.",
    )
    name: Name = Field(..., description='Name of this spot display.')
    scatterPlotAxes: Optional[ScatterPlotAxes] = None
    selectedSpotIds: Optional[List[constr(pattern=r'^[^; \/]+;\d+;\d+$')]] = Field(
        None,
        description='List of selected segment ids, each of the form sourceName;timePoint;spot_id',
    )
    showScatterPlot: Optional[bool] = Field(
        None,
        description="Whether to show the scatter plot. The default is 'false', i.e. if this property is not present the scatter plot should not be shown. If it is 'true' then 'scatterPlotAxes' must be given.",
    )
    sources: List[Name] = Field(
        ...,
        description='The spot sources that are part of this display group.',
        min_length=1,
    )
    additionalTables: Optional[AdditionalTables] = None
    valueLimits: Optional[ValueLimits] = None
    visible: Optional[bool] = Field(
        None, description='Are the sources of this display visible? Default is true.'
    )
    showTable: Optional[bool] = Field(
        None,
        description='Show the table GUI element. Default is true (if the display has a table).',
    )
    showAsBoundaries: Optional[bool] = Field(
        None,
        description='Show boundary mask instead of segment masks. Default is false.',
    )
    boundaryThickness: Optional[float] = Field(
        None,
        description='Thickness of the boundary masks. Only used if showAsBoundaries is true.',
    )
    randomColorSeed: Optional[float] = Field(
        None,
        description='Random seed for the random color lut (e.g. glasbey) to reproduce the exact colors of the view. (Optional)',
    )
    spotRadius: Optional[float] = Field(
        None,
        description='The radius that is used for rendering the spots. In physical units',
    )
    selectionColor: Optional[SelectionColor] = None


class SpotDisplay(BaseModel):
    """
    Viewer state for a group of spot sources.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    spotDisplay: SpotDisplay1 = Field(..., description='')


class Timepoint(RootModel[conint(ge=0)]):
    root: conint(ge=0)


class Affine1(BaseModel):
    """
    Affine transformation applied to a list of sources.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    parameters: List[float] = Field(
        ...,
        description='Parameters of the affine transformation, using the BigDataViewer convention.',
        max_length=12,
        min_length=12,
    )
    sources: List[Name] = Field(
        ..., description='The sources this transformation is applied to.', min_length=1
    )
    sourceNamesAfterTransform: Optional[List[Name]] = Field(
        None,
        description='Names of the sources after transformation. If given, must have the same number of elements as `sources`.',
        min_length=1,
    )
    name: Optional[Name] = Field(None, description='Name of this transformation')
    timepoints: Optional[List[Timepoint]] = Field(
        None,
        description='The valid timepoints for this transformation. If none is given, the transformation is valid for all timepoints.',
        min_length=1,
    )


class Affine(BaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    affine: Affine1 = Field(
        ..., description='Affine transformation applied to a list of sources.'
    )


class Crop1(BaseModel):
    """
    Crop transformation applied to a list of sources.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    boxAffine: Optional[List[float]] = Field(
        None,
        description="The transformation to place the crop's bounding box in the global coordinate system (if not given assumes identity)",
        max_length=12,
        min_length=12,
    )
    min: List[float] = Field(
        ..., description='Minimum coordinates for the crop.', max_length=3, min_length=3
    )
    max: List[float] = Field(
        ..., description='Maximum coordinates for the crop.', max_length=3, min_length=3
    )
    rectify: Optional[bool] = Field(
        None,
        description="Whether to align the crop's bounding box with the coordinate system. By default true.",
    )
    sources: List[Name] = Field(
        ..., description='The sources this transformation is applied to.', min_length=1
    )
    sourceNamesAfterTransform: Optional[List[Name]] = Field(
        None,
        description='Names of the sources after transformation. If given, must have the same number of elements as `sources`.',
        min_length=1,
    )
    name: Optional[Name] = Field(None, description='Name of this transformation')
    timepoints: Optional[List[Timepoint]] = Field(
        None,
        description='The valid timepoints for this transformation. If none is given, the transformation is valid for all timepoints.',
        min_length=1,
    )
    centerAtOrigin: Optional[bool] = Field(
        None,
        description='Whether to center the source at the coordinate space origin after applying the crop. By default true.',
    )


class Crop(BaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    crop: Crop1 = Field(
        ..., description='Crop transformation applied to a list of sources.'
    )


class MergedGrid1(BaseModel):
    """
    A grid view of multiple sources that creates an new merged source. Only valid if all sources have the same size (both in pixels and physical space).
    """

    model_config = ConfigDict(
        extra='allow',
    )
    sources: List[Name] = Field(
        ...,
        description='The sources this transformation is applied to. After transformation all sources will get the name `<sourceName>_<mergedGridSourceName>`. `<sourceName>` still refers to the source befor transformation (useful e.g. for specifying a metadataSource)',
        min_length=2,
    )
    positions: Optional[Positions] = None
    mergedGridSourceName: Name
    metadataSource: Optional[str] = Field(
        None,
        description='Name of the source to be used as reference to load the metadata for all sources in this transformation. This can decrease the load time significantly for large grids. Note that the image related metadata (shape, datatype, etc.) must exactly match.',
    )
    margin: Optional[float] = Field(
        None,
        description='The margin to add around a position in the grid. By default a fraction of 0.1 of the position size will be used.',
    )


class MergedGrid(BaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    mergedGrid: MergedGrid1 = Field(
        ...,
        description='A grid view of multiple sources that creates an new merged source. Only valid if all sources have the same size (both in pixels and physical space).',
    )


class Parameter(RootModel[List[int]]):
    root: List[int]


class Timepoints1(BaseModel):
    """
    Move sources to different timepoints.
    """

    sources: List[Name] = Field(
        ..., description='The sources this transformation is applied to.', min_length=1
    )
    parameters: List[Parameter] = Field(
        ...,
        description='List of the timepoint shifts. The list contains elements of size 2, the first entry gives the new timepoint (where the source is moved to), the second the old one (where it comes from). The first entries must be unique across the whole list, the second entries may be duplicated.',
        min_length=1,
    )
    sourceNamesAfterTransform: Optional[List[Name]] = Field(
        None,
        description='Names of the sources after transformation. If given, must have the same number of elements as `sources`.',
        min_length=1,
    )
    name: Optional[Name] = Field(None, description='Name of this transformation')
    keep: Optional[bool] = Field(
        None,
        description='Whether to keep timepoints that are not transformed, or to only display timepoints that are explicitly given in the transformation.',
    )


class Timepoints(BaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    timepoints: Timepoints1 = Field(
        ..., description='Move sources to different timepoints.'
    )


class TransformedGrid1(BaseModel):
    """
    Arrange multiple sources in a grid by offseting sources with a grid spacing.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    positions: Optional[Positions] = None
    nestedSources: NestedListOfSources = Field(
        ...,
        description='The sources for the grid. Map of grid posititions to sources per position.',
    )
    sourceNamesAfterTransform: Optional[NestedListOfSources] = Field(
        None,
        description='Names of the sources after transformation. If given, must have the same structure as `sources`.',
    )
    name: Optional[Name] = Field(None, description='Name of this transformation')
    timepoints: Optional[List[int]] = Field(
        None,
        description='The valid timepoints for this transformation. If none is given, the transformation is valid for all timepoints.',
        min_length=1,
    )
    centerAtOrigin: Optional[bool] = Field(
        None, description='Center the views at the origin for 3d sources.'
    )
    margin: Optional[float] = Field(
        None,
        description='The margin to add around a position in the grid. By default a fraction of 0.1 of the position size will be used.',
    )


class TransformedGrid(BaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    transformedGrid: TransformedGrid1 = Field(
        ...,
        description='Arrange multiple sources in a grid by offseting sources with a grid spacing.',
    )


class ViewerTransform1(BaseModel):
    """
    A viewer transform to specify position, rotation, timepoint and/or zoom.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    timepoint: conint(ge=0) = Field(
        ..., description='The initial timepoint shown in the viewer.'
    )


class ViewerTransform2(BaseModel):
    """
    A viewer transform to specify position, rotation, timepoint and/or zoom.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    normalVector: List[float] = Field(
        ...,
        description='The normal vector to the view plane.',
        max_length=3,
        min_length=3,
    )
    timepoint: Optional[conint(ge=0)] = Field(
        None, description='The initial timepoint shown in the viewer.'
    )


class ViewerTransform3(BaseModel):
    """
    A viewer transform to specify position, rotation, timepoint and/or zoom.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    affine: List[float] = Field(
        ...,
        description='Affine transformation applied by the viewer.',
        max_length=12,
        min_length=12,
    )
    timepoint: Optional[conint(ge=0)] = Field(
        None, description='The initial timepoint shown in the viewer.'
    )


class ViewerTransform4(BaseModel):
    """
    A viewer transform to specify position, rotation, timepoint and/or zoom.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    normalizedAffine: List[float] = Field(
        ...,
        description='Normalized affine transformation applied by the viewer.',
        max_length=12,
        min_length=12,
    )
    timepoint: Optional[conint(ge=0)] = Field(
        None, description='The initial timepoint shown in the viewer.'
    )


class PositionItem(RootModel[confloat(ge=0.0)]):
    root: confloat(ge=0.0)


class ViewerTransform5(BaseModel):
    """
    A viewer transform to specify position, rotation, timepoint and/or zoom.
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    position: List[PositionItem] = Field(
        ...,
        description='Position that will be centered in the viewer.',
        max_length=3,
        min_length=3,
    )
    timepoint: Optional[conint(ge=0)] = Field(
        None, description='The initial timepoint shown in the viewer.'
    )


class ViewerTransform(
    RootModel[
        Union[
            ViewerTransform1,
            ViewerTransform2,
            ViewerTransform3,
            ViewerTransform4,
            ViewerTransform5,
        ]
    ]
):
    root: Union[
        ViewerTransform1,
        ViewerTransform2,
        ViewerTransform3,
        ViewerTransform4,
        ViewerTransform5,
    ] = Field(
        ...,
        description='A viewer transform to specify position, rotation, timepoint and/or zoom.',
    )


class MoBIESourceSchema(BaseModel):
    """
    Schema describing a MoBIE source
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    image: ImageSource


class MoBIESourceSchema1(BaseModel):
    """
    Schema describing a MoBIE source
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    segmentation: SegmentationSource


class MoBIESourceSchema2(BaseModel):
    """
    Schema describing a MoBIE source
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    spots: SpotSource


class MoBIESourceSchema3(BaseModel):
    """
    Schema describing a MoBIE source
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    regions: RegionSource


class View(BaseModel):
    """
    Schema for serializing the MoBIE viewer state
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    uiSelectionGroup: Name = Field(
        ..., description='Name of the UI from which this view can be selected.'
    )
    isExclusive: bool = Field(
        ...,
        description='Does this view replace the current viewer state (exclusive) or is it added to it (additive)?',
    )
    description: Optional[str] = Field(
        None,
        description='Description of this view. Will be displayed when the view is loaded.',
    )
    sourceDisplays: Optional[
        List[Union[ImageDisplay, SegmentationDisplay, SpotDisplay, RegionDisplay]]
    ] = Field(None, description='The display groups of this view.')
    sourceTransforms: Optional[
        List[Union[Affine, Crop, MergedGrid, Timepoints, TransformedGrid]]
    ] = Field(
        None,
        description='The source transformations of this view. The transformations must be defined in the physical coordinate space and are applied in addition to the transformations given in the bdv.xml.',
    )
    viewerTransform: Optional[ViewerTransform] = None


class Dataset(BaseModel):
    """
    Schema describing a MoBIE dataset
    """

    model_config = ConfigDict(
        extra='forbid',
        validate_assignment=True,
    )
    is2D: bool
    description: Optional[str] = Field(None, description='Description of the dataset')
    sources: Dict[str, Source] = Field(
        ..., description='The list of sources in this dataset.'
    )
    views: Dict[str, View] = Field(
        ..., description='The list of views in this dataset.'
    )
    defaultLocation: Optional[ViewerTransform] = Field(
        None,
        description="Default location for the 'location' menu in the viewer. Must be a valid viewer transform. If none is given the menu will be empty.",
    )

    @field_validator('views')
    @classmethod
    def validate_default_item(cls, views: Dict[str, View]):
        if 'default' not in views:
            message = "The 'views' list must contain a view named 'default'"
            raise ValueError(message)
        return views


class Source(
    RootModel[
        Union[
            MoBIESourceSchema,
            MoBIESourceSchema1,
            MoBIESourceSchema2,
            MoBIESourceSchema3,
        ]
    ]
):
    root: Union[
        MoBIESourceSchema, MoBIESourceSchema1, MoBIESourceSchema2, MoBIESourceSchema3
    ] = Field(
        ..., description='Schema describing a MoBIE source', title='MoBIESourceSchema'
    )


class Project(BaseModel):
    """
    Schema describing a MoBIE project
    """

    model_config = ConfigDict(
        extra='forbid',
        validate_assignment=True,
    )
    datasets: List[Name] = Field(
        ..., description='The list of datasets available in this project.'
    )
    defaultDataset: str = Field(
        ..., description='The default dataset for this project.'
    )
    description: Optional[str] = None
    references: Optional[List[str]] = Field(
        None, description='Publication or other references for this project'
    )
    specVersion: Annotated[str, constr(pattern=r'0\.3\.\d+')] = Field(
        ..., description='The MoBIE spec version of this project.'
    )
