From 9f1092cb134431cfd5de3ed1cd100a681bd5e961 Mon Sep 17 00:00:00 2001 From: relifet <3380383714@qq.com> Date: Thu, 15 Aug 2024 09:39:33 +0800 Subject: [PATCH] Removed all_types, updated basic_types and extend_types. --- pytdml/io/tdml_writers.py | 4 +- pytdml/type/__init__.py | 26 +- pytdml/type/_utils.py | 8 +- pytdml/type/all_types.py | 2307 ----------------- pytdml/type/basic_types.py | 1374 ++++++++-- pytdml/type/extended_types.py | 144 +- .../data}/UiT_HCD_California_2017.json | 0 .../data}/UiT_HCD_California_2017.yml | 0 8 files changed, 1269 insertions(+), 2594 deletions(-) delete mode 100644 pytdml/type/all_types.py rename {pytdml/type => tests/data}/UiT_HCD_California_2017.json (100%) rename {pytdml/type => tests/data}/UiT_HCD_California_2017.yml (100%) diff --git a/pytdml/io/tdml_writers.py b/pytdml/io/tdml_writers.py index 9513597..d0b2866 100644 --- a/pytdml/io/tdml_writers.py +++ b/pytdml/io/tdml_writers.py @@ -31,6 +31,8 @@ # ------------------------------------------------------------------------------ import json from typing import Union + +from pytdml.io import read_from_json from pytdml.type import TrainingDataset, EOTrainingDataset @@ -40,4 +42,4 @@ def write_to_json(td: TrainingDataset or EOTrainingDataset, file_path: str, inde """ with open(file_path, "w", encoding='utf-8') as f: json.dump(td.to_dict(), f, indent=indent, ensure_ascii=False) - # json.dump(remove_empty(td.dict()), f, indent=indent, ensure_ascii=False) + # json.dump(remove_empty(td.dict()), f, indent=indent, ensure_ascii=False) \ No newline at end of file diff --git a/pytdml/type/__init__.py b/pytdml/type/__init__.py index 68365eb..e129b5a 100644 --- a/pytdml/type/__init__.py +++ b/pytdml/type/__init__.py @@ -29,16 +29,16 @@ # SOFTWARE. # # ------------------------------------------------------------------------------ -from .all_types import BaseCamelModel -from .all_types import KeyValuePair -from .all_types import MD_ScopeDescription -from .all_types import MD_Band -from .all_types import MD_Scope -from .all_types import CI_Date -from .all_types import MD_BrowseGraphic -from .all_types import CI_Citation -from .all_types import MD_Identifier -from .all_types import QualityElement -from .all_types import DataQuality -from .all_types import TrainingDataset -from .all_types import EOTrainingDataset +from .basic_types import BaseCamelModel +from .basic_types import KeyValuePair +from .basic_types import MD_ScopeDescription +from .basic_types import MD_Band +from .basic_types import MD_Scope +from .basic_types import CI_Date +from .basic_types import MD_BrowseGraphic +from .basic_types import CI_Citation +from .basic_types import MD_Identifier +from .basic_types import QualityElement +from .basic_types import DataQuality +from .basic_types import TrainingDataset +from .extended_types import EOTrainingDataset diff --git a/pytdml/type/_utils.py b/pytdml/type/_utils.py index 1c6acba..0501b6d 100644 --- a/pytdml/type/_utils.py +++ b/pytdml/type/_utils.py @@ -169,7 +169,7 @@ def _validate_image_format(image_format: str): if image_format in image_format_list: return image_format else: - pass + return None def _valid_methods(labeling_methods: str): @@ -177,7 +177,7 @@ def _valid_methods(labeling_methods: str): if labeling_methods in labeling_methods_list: return labeling_methods else: - pass + return None def _validate_training_type(training_type: str): @@ -185,7 +185,7 @@ def _validate_training_type(training_type: str): if training_type in training_type_list: return training_type else: - pass + return None def _validate_evaluation_method_type(evaluation_method_type: str): @@ -193,7 +193,7 @@ def _validate_evaluation_method_type(evaluation_method_type: str): if evaluation_method_type in evaluation_method_type_list: return evaluation_method_type else: - pass + return None def to_camel(string: str) -> str: diff --git a/pytdml/type/all_types.py b/pytdml/type/all_types.py deleted file mode 100644 index f9d21c5..0000000 --- a/pytdml/type/all_types.py +++ /dev/null @@ -1,2307 +0,0 @@ -# ------------------------------------------------------------------------------ -# -# Project: pytdml -# Authors: Boyi Shangguan, Kaixuan Wang, Zhaoyan Wu -# Created: 2022-05-04 -# Modified: 2023-10-27 -# Email: sgby@whu.edu.cn -# -# ------------------------------------------------------------------------------ -# -# Copyright (c) 2022 OGC Training Data Markup Language for AI Standard Working Group -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# ------------------------------------------------------------------------------ - -import json -import copy -import geojson -from geojson import Feature -from typing import List, Union, Optional, Literal -from pydantic import BaseModel, Field, field_validator, model_validator -from pytdml.type._utils import _validate_date, to_camel, _valid_methods, _validate_training_type, _validate_image_format, _validate_evaluation_method_type, to_interior_class, list_to_interior_class - - -class BaseCamelModel(BaseModel): - """ - Basic model with camel case alias - Since python use snake case as default - We need to convert it to camel case for JSON - """ - - class Config: - alias_generator = to_camel - populate_by_name = True - arbitrary_types_allowed = True - - def json(self, **kwargs): - data = self.dict(by_alias=True, exclude_unset=True) - data_without_none = {k: v for k, v in data.items() if v is not None} - return data_without_none - - -class KeyValuePair(BaseCamelModel): - """ - Key/Value pair type - """ - - key: list - value: list - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - key = json_dict.keys() - value = json_dict.values() - key_value_pair = {"key": key, "value": value} - return KeyValuePair(**key_value_pair) - - -class NamedValue(BaseCamelModel): - """ - From ISO 19156: 2023(E) NamedValue - """ - - key: str - value: Union[str, object, int, float, list, bool, None] - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return NamedValue(**new_dict) - - -class CI_Date(BaseCamelModel): - """ - From ISO 19115-1 CI_Date - """ - - date: str - dateType: str - - @field_validator("date") - def validate_date(cls, v): - return _validate_date(v) - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return CI_Date(**new_dict) - - -class CI_Citation(BaseCamelModel): - """ - From ISO 19115-1 CI_Citation - """ - - title: str - alternateTitle: Optional[List[str]] = None - date: Optional[List[CI_Date]] = None - edition: Optional[str] = None - edition_date: Optional[str] = None - graphic: Optional[List[KeyValuePair]] = None - identifier: Optional[List[KeyValuePair]] = None - ISBN: Optional[str] = None - ISSN: Optional[str] = None - - @field_validator("edition_date") - def validate_edition_date(cls, v): - return _validate_date(v) - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('date'): - list_to_interior_class(new_dict, 'date', CI_Date) - if new_dict.__contains__('graphic'): - list_to_interior_class(new_dict, 'graphic', KeyValuePair) - if new_dict.__contains__('identifier'): - list_to_interior_class(new_dict, 'identifier', KeyValuePair) - return CI_Citation(**new_dict) - - -class LinearRing(BaseCamelModel): - """ - gml: LinearRing - NIEM 2.1 - """ - - posList: List[float] = Field(min_items=4) - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return LinearRing(**new_dict) - - -class LinearRing_Object(BaseCamelModel): - """ - LinearRing Object Type - """ - - linearRing: LinearRing = Field(alias="LinearRing") - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - to_interior_class(new_dict, "linearRing", LinearRing) - return LinearRing_Object(**new_dict) - - -class Polygon(BaseCamelModel): - """ - gml: Polygon - NIEM 2.1 - """ - - description: Optional[str] = None - description_Reference: Optional[str] = None - identifier: Optional[str] = None - name: Optional[List[str]] = None - exterior: Optional[LinearRing_Object] = None - interior: Optional[List[LinearRing_Object]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('exterior'): - to_interior_class(new_dict, 'exterior', LinearRing_Object) - if new_dict.__contains__('interior'): - list_to_interior_class(new_dict, 'interior', LinearRing_Object) - return Polygon(**new_dict) - - -class MD_Identifier(BaseCamelModel): - """ - From ISO 19115-1 MD_Identifier - """ - - code: str - authority: Optional[CI_Citation] = None - codeSpace: Optional[str] = None - version: Optional[str] = None - description: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('authority'): - list_to_interior_class(new_dict, 'authority', CI_Citation) - return MD_Identifier(**new_dict) - - -class MemberName(BaseCamelModel): - """ - From ISO 19115-1 MD_Identifier - """ - - aName: str - attributeType: str - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return MemberName(**new_dict) - - -class MI_RangeElementDescription(BaseCamelModel): - """ - From ISO 19115-1 MD_Identifier - """ - - name: str - definition: str - rangeElement: List[str] = Field(min_items=1) - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return MI_RangeElementDescription(**new_dict) - - -class MD_Band(BaseCamelModel): - """ - From ISO 19115-1 MD_Band - """ - - sequenceIdentifier: Optional[MemberName] = None - description: Optional[str] = None - name: Optional[List[MD_Identifier]] = None - maxValue: Optional[float] = None - minValue: Optional[float] = None - units: Optional[str] = None - scaleFactor: Optional[float] = None - offset: Optional[float] = None - meanValue: Optional[float] = None - numberOfValues: Optional[int] = None - standardDeviation: Optional[float] = None - otherPropertyType: Optional[float] = None - otherProperty: Optional[str] = None - bitsPerValue: Optional[int] = None - rangeElementDescription: Optional[List[MI_RangeElementDescription]] = None - boundMax: Optional[float] = None - boundMin: Optional[float] = None - boundUnits: Optional[str] = None - peakResponse: Optional[float] = None - toneGradation: Optional[int] = None - - @model_validator(mode="before") - def check_dependent_required(cls, v): - bound_units = v.get("boundUnits") - bound_max = v.get("boundMax") - bound_min = v.get("boundMin") - - # check dependRequired in jsonschema - if bound_units is not None and (bound_max is None or bound_min is None): - raise ValueError( - "boundMax and boundMin are required when boundUnits is present" - ) - - return v - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('sequenceIdentifier'): - to_interior_class(new_dict, 'sequenceIdentifier', MemberName) - if new_dict.__contains__('rangeElementDescription'): - list_to_interior_class(new_dict, 'rangeElementDescription', MI_RangeElementDescription) - return MD_Band(**new_dict) - - -class EX_BoundingPolygon(BaseCamelModel): - """ - From ISO 19115:2003 - """ - - polygon: List[Polygon] = Field(min_items=1) - extentTypeCode: Optional[bool] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('polygon'): - list_to_interior_class(new_dict, 'polygon', Polygon) - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - return EX_BoundingPolygon(**new_dict) - - def can_build_from_data(data): - try: - EX_BoundingPolygon.from_dict(data) - return True - except Exception: - return False - - -class EX_GeographicBoundingBox(BaseCamelModel): - """ - From ISO 19168-2:2022 - """ - - westBoundLongitude: int - eastBoundLongitude: int - southBoundLatitude: int - northBoundLatitude: int - - extentTypeCode: Optional[bool] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return EX_GeographicBoundingBox(**new_dict) - - def can_build_from_data(data): - try: - EX_GeographicBoundingBox.from_dict(data) - return True - except Exception: - return False - - -class EX_GeographicDescription(BaseCamelModel): - """ - From ISO 19115: 2003 Metadata - """ - - geographicIdentifier: MD_Identifier - - extentTypeCode: Optional[bool] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('geographicIdentifier'): - to_interior_class(new_dict, 'geographicIdentifier', MD_Identifier) - return EX_GeographicDescription(**new_dict) - - def can_build_from_data(data): - try: - EX_GeographicDescription.from_dict(data) - return True - except Exception: - return False - - -class TimeInstant(BaseCamelModel): - """ - Time Instant Type - """ - - timePosition: str - - description: Optional[str] = None - descriptionReference: Optional[str] = None - identifier: Optional[str] = None - name: Optional[List[str]] = None - relatedTime: Optional[List[KeyValuePair]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('relatedTime'): - to_interior_class(new_dict, 'relatedTime', KeyValuePair) - return TimeInstant(**new_dict) - - def can_build_from_data(data): - try: - TimeInstant.from_dict(data) - return True - except Exception: - return False - - -class TimePeriod(BaseCamelModel): - """ - Time Period Type - """ - - beginPosition: str - endPosition: str - - description: Optional[str] = None - descriptionReference: Optional[str] = None - identifier: Optional[str] = None - name: Optional[List[str]] = None - duration: Optional[str] = None - timeInterval: Optional[int] = None - relatedTime: Optional[List[KeyValuePair]] = None - - @field_validator("beginPosition") - def validate_begin_position(cls, v): - return _validate_date(v) - - @field_validator("endPosition") - def validate_end_position(cls, v): - return _validate_date(v) - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('relatedTime'): - to_interior_class(new_dict, 'relatedTime', KeyValuePair) - return TimePeriod(**new_dict) - - def can_build_from_data(data): - try: - TimePeriod.from_dict(data) - return True - except Exception: - return False - - -class EX_TemporalExtent(BaseCamelModel): - """ - From ISO 19108:2002 - """ - - extent: Union[TimeInstant, TimePeriod] - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('extent'): - extent = new_dict['extent'] - if TimeInstant.can_build_from_data(extent): - extent = TimeInstant.from_dict(extent) - else: - extent = TimePeriod.from_dict(extent) - new_dict['extent'] = extent - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - return EX_TemporalExtent(**new_dict) - - def can_build_from_data(data): - try: - EX_TemporalExtent.from_dict(data) - return True - except Exception: - return False - - -class EX_ReferenceSystem(BaseCamelModel): - """ - From ISO 19111: 2019 - """ - - referenceSystemIdentifier: Optional[MD_Identifier] = None - referenceSystemType: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('referenceSystemIdentifier'): - to_interior_class(new_dict, 'referenceSystemIdentifier', MD_Identifier) - return EX_ReferenceSystem(**new_dict) - - -class VerticalCRS(BaseCamelModel): - """ - From ISO 19111 edition 2 - """ - - identifier: str - scope: List[str] = Field(min_items=1) - verticalCS: List[str] = Field(min_items=1) - verticalDatum: List[str] = Field(min_items=1) - - description: Optional[str] = None - description_Reference: Optional[str] = None - name: Optional[List[str]] = None - remarks: Optional[List[str]] = None - domain_Of_Validity: Optional[List[str]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return VerticalCRS(**new_dict) - - -class EX_VerticalExtent(BaseCamelModel): - """ - From ISO 19115 SpiralTracker Report - """ - - minimumValue: int - maximumValue: int - - verticalCRSId: Optional[EX_ReferenceSystem] = None - verticalCRS: Optional[VerticalCRS] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('verticalCRSId'): - to_interior_class(new_dict, 'verticalCRSId', EX_ReferenceSystem) - if new_dict.__contains__('verticalCRS'): - to_interior_class(new_dict, 'verticalCRS', VerticalCRS) - return EX_VerticalExtent(**new_dict) - - -class EX_SpatialTemporalExtent(BaseCamelModel): - """ - From ISO 19115: 2003 Metadata - """ - - extent: Union[TimeInstant, TimePeriod] - spatialExtent: Union[EX_BoundingPolygon, EX_GeographicBoundingBox, EX_GeographicDescription] - - verticalExtent: Optional[EX_VerticalExtent] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('extent') and new_dict.__contains__('spatialExtent'): - extent = new_dict['extent'] - if TimeInstant.can_build_from_data(extent): - extent = TimeInstant.from_dict(extent) - else: - extent = TimePeriod.from_dict(extent) - new_dict['extent'] = extent - - spatialExtent = new_dict['spatialExtent'] - if EX_BoundingPolygon.can_build_from_data(spatialExtent): - spatialExtent = EX_BoundingPolygon.from_dict(spatialExtent) - elif EX_GeographicBoundingBox.can_build_from_data(spatialExtent): - spatialExtent = EX_GeographicBoundingBox.from_dict(spatialExtent) - else: - spatialExtent = EX_GeographicDescription.from_dict(spatialExtent) - new_dict['spatialExtent'] = spatialExtent - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - - if new_dict.__contains__('verticalExtent'): - to_interior_class(new_dict, 'verticalExtent', EX_VerticalExtent) - return EX_SpatialTemporalExtent(**new_dict) - - def can_build_from_data(data): - try: - EX_SpatialTemporalExtent.from_dict(data) - return True - except Exception: - return False - - -class EX_Extent(BaseCamelModel): - """ - From ISO 19115: 2003 Metadata - """ - - description: Optional[str] = None - geographicElement: Optional[List[Union[EX_BoundingPolygon, EX_GeographicBoundingBox, EX_GeographicDescription]]] = None - temporalElement: Optional[List[Union[EX_TemporalExtent, EX_SpatialTemporalExtent]]] = None - verticalElement: Optional[List[EX_VerticalExtent]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('geographicElement'): - geographic_element = new_dict['geographicElement'] - for i in range(len(geographic_element)): - if EX_BoundingPolygon.can_build_from_data(geographic_element[i]): - geographic_element[i] = EX_BoundingPolygon.from_dict(geographic_element[i]) - elif EX_GeographicBoundingBox.can_build_from_data(geographic_element[i]): - geographic_element[i] = EX_GeographicBoundingBox.from_dict(geographic_element[i]) - else: - geographic_element[i] = EX_GeographicDescription.from_dict(geographic_element[i]) - new_dict['geographicElement'] = geographic_element - - if new_dict.__contains__('temporalElement'): - temporal_element = new_dict['temporalElement'] - for i in range(len(temporal_element)): - if EX_TemporalExtent.can_build_from_data(temporal_element[i]): - temporal_element[i] = EX_TemporalExtent.from_dict(temporal_element[i]) - else: - temporal_element[i] = EX_SpatialTemporalExtent.from_dict(temporal_element[i]) - new_dict['temporalElement'] = temporal_element - - if new_dict.__contains__('verticalElement'): - list_to_interior_class(new_dict, 'verticalElement',EX_VerticalExtent) - return EX_Extent(**new_dict) - - def can_build_from_data(data): - try: - EX_Extent.from_dict(data) - return True - except Exception: - return False - - -# class BoundingBox(BaseCamelModel): -# """ -# From GeoJSON bounding box -# """ -# -# extent: List[float] = Field(min_items=4) -# -# def to_dict(self): -# return self.json() -# -# @staticmethod -# def from_dict(json_dict): -# return BoundingBox(**json_dict) - - -class MD_ScopeDescription(BaseCamelModel): - """ - From ISO 19115-1 MD_ScopeDescription - """ - - attributes: Optional[str] = None - features: Optional[str] = None - featureInstances: Optional[str] = None - attributeInstances: Optional[str] = None - dataset: Optional[str] = None - other: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return MD_ScopeDescription(**new_dict) - - -class MD_Scope(BaseCamelModel): - """ - From ISO 19115-1 MD_Scope - """ - - level: str - extent: Optional[List[Union[EX_Extent, List[float]]]] = None - levelDescription: Optional[MD_ScopeDescription] = Field(None, min_items=4) - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('extent'): - extent = new_dict['extent'] - for i in range(len(extent)): - if EX_Extent.can_build_from_data(extent[i]): - extent[i] = EX_Extent.from_dict(extent[i]) - else: - pass - - if new_dict.__contains__('levelDescription'): - to_interior_class(new_dict, 'levelDescription', MD_ScopeDescription) - return MD_Scope(**new_dict) - - -class CI_Telephone(BaseCamelModel): - - number: str - numberType: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return CI_Telephone(**new_dict) - - -class CI_Address(BaseCamelModel): - - deliveryPoint: Optional[List[str]] = None - city: Optional[str] = None - administrativeArea: Optional[str] = None - postalCode: Optional[str] = None - country: Optional[str] = None - electronicMailAddress: Optional[List[str]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return CI_Address(**new_dict) - - -class CI_OnlineResource(BaseCamelModel): - - linkage: str - protocol: Optional[str] = None - applicationProfile: Optional[str] = None - name: Optional[str] = None - description: Optional[str] = None - function: Optional[str] = None - protocolRequest: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return CI_OnlineResource(**new_dict) - - -class CI_Contact(BaseCamelModel): - - phone: Optional[List[CI_Telephone]] = None - address: Optional[List[CI_Address]] = None - onlineResource: Optional[List[CI_OnlineResource]] = None - hoursOfService: Optional[List[str]] = None - contactInstructions: Optional[str] = None - contactType: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('phone'): - list_to_interior_class(new_dict, 'phone', CI_Telephone) - if new_dict.__contains__('address'): - list_to_interior_class(new_dict, 'address', CI_Address) - if new_dict.__contains__('onlineResource'): - list_to_interior_class(new_dict, 'onlineResource', CI_OnlineResource) - return CI_Contact(**new_dict) - - -class CI_Individual(BaseCamelModel): - - name: Optional[str] = None - contactInfo: Optional[List[CI_Contact]] = None - partyIdentifier: Optional[List[MD_Identifier]] = None - positionName: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('contactInfo'): - list_to_interior_class(new_dict, 'contactInfo', CI_Contact) - if new_dict.__contains__('partyIdentifier'): - list_to_interior_class(new_dict, 'partyIdentifier', MD_Identifier) - return CI_Individual(**new_dict) - - def can_build_from_data(data): - try: - CI_Individual.from_dict(data) - return True - except Exception: - return False - - -class CI_Organisation(BaseCamelModel): - - name: Optional[str] = None - contactInfo: Optional[List[CI_Contact]] = None - partyIdentifier: Optional[List[MD_Identifier]] = None - logo: Optional[List[KeyValuePair]] = None - individual: Optional[List[CI_Individual]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('contactInfo'): - list_to_interior_class(new_dict, 'contactInfo', CI_Contact) - if new_dict.__contains__('partyIdentifier'): - list_to_interior_class(new_dict, 'partyIdentifier', MD_Identifier) - if new_dict.__contains__('logo'): - list_to_interior_class(new_dict, 'logo', KeyValuePair) - if new_dict.__contains__('individual'): - list_to_interior_class(new_dict, 'individual', CI_Individual) - return CI_Organisation(**new_dict) - - def can_build_from_data(data): - try: - CI_Individual.from_dict(data) - return True - except Exception: - return False - - -class CI_Responsibility(BaseCamelModel): - - role: str - party: List[Union[CI_Individual, CI_Organisation]] - - extent: Optional[List[Union[EX_Extent, List[float]]]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('party'): - party = new_dict['party'] - for i in range(len(party)): - if CI_Individual.can_build_from_data(party[i]): - party[i] = CI_Individual.from_dict(party[i]) - else: - party[i] = CI_Organisation.from_dict(party[i]) - new_dict['party'] = party - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - - if new_dict.__contains__('extent'): - extent = new_dict['extent'] - for i in range(len(extent)): - if EX_Extent.can_build_from_data(extent[i]): - extent[i] = EX_Extent.from_dict(extent[i]) - else: - pass - new_dict['extent'] = extent - - return CI_Responsibility(**new_dict) - - -class MD_Releasability(BaseCamelModel): - - addressee: Optional[List[CI_Responsibility]] = None - statement: Optional[str] = None - disseminationConstraints: Optional[List[str]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('addressee'): - list_to_interior_class(new_dict, 'addressee', CI_Responsibility) - return MD_Releasability(**new_dict) - - -class MD_Constraints(BaseCamelModel): - - useLimitation: Optional[List[str]] = None - constraintApplicationScope: Optional[MD_Scope] = None - graphic: Optional[List[KeyValuePair]] = None - reference: Optional[List[CI_Citation]] = None - releasability: Optional[MD_Releasability] = None - responsibleParty: Optional[List[CI_Responsibility]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('constraintApplicationScope'): - to_interior_class(new_dict, 'constraintApplicationScope', MD_Scope) - if new_dict.__contains__('graphic'): - list_to_interior_class(new_dict, 'graphic', KeyValuePair) - if new_dict.__contains__('reference'): - list_to_interior_class(new_dict, 'reference', CI_Citation) - if new_dict.__contains__('releasability'): - to_interior_class(new_dict, 'releasability', MD_Releasability) - if new_dict.__contains__('responsibleParty'): - list_to_interior_class(new_dict, 'responsibleParty', CI_Responsibility) - return MD_Constraints(**new_dict) - - -class MD_BrowseGraphic(BaseCamelModel): - """ - From ISO 19115-1 MD_BrowseGraphic - """ - - file_name: str - fileDescription: Optional[str] = None - fileType: Optional[str] = None - imageConstraints: Optional[List[MD_Constraints]] = None - linkage: Optional[List[CI_OnlineResource]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('imageConstraints'): - list_to_interior_class(new_dict, 'imageConstraints', MD_Constraints) - if new_dict.__contains__('linkage'): - list_to_interior_class(new_dict, 'linkage', CI_OnlineResource) - return MD_BrowseGraphic(**new_dict) - - -# class MetricsPair(BaseCamelModel): -# """ -# Metrics pair type -# """ -# -# name: str -# value: float -# -# def to_dict(self): -# return self.json() -# -# @staticmethod -# def from_dict(json_dict): -# new_dict = copy.deepcopy(json_dict) -# return MetricsPair(**new_dict) - - -class AI_MetricsInLiterature(BaseCamelModel): - """ - Metrics in literature type - """ - - doi: str - metrics: List[NamedValue] = Field(min_items=1) - - algorithm: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('metrics'): - to_interior_class(new_dict, "metrics", NamedValue) - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - return AI_MetricsInLiterature(**new_dict) - - -class AI_Task(BaseCamelModel): - """ - Basic task type - """ - - id: str - type: Literal["AI_AbstractTask"] - - datasetId: Optional[str] = None - description: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return AI_Task(**new_dict) - - -class AI_EOTask(AI_Task): - """ - Extended task type for EO training data - """ - type: Literal["AI_EOTask"] - taskType: str - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return AI_EOTask(**new_dict) - - -class AI_Labeler(BaseCamelModel): - """ - Labeler type - """ - - id: str - name: str - type: Literal["AI_Labeler"] - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return AI_Labeler(**new_dict) - - -class AI_LabelingProcedure(BaseCamelModel): - """ - Labeling procedure type - """ - - type: Literal["AI_LabelingProcedure"] - id: str - methods: List[str] = Field(min_items=1) - - tools: Optional[List[str]] = None - - @field_validator("methods") - def valid_methods(cls, v): - return _valid_methods(v) - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return AI_LabelingProcedure(**new_dict) - - -class AI_Labeling(BaseCamelModel): - """ - Labeling type - """ - - id: str - scope: MD_Scope - type: Literal["AI_Labeling"] - - labelers: Optional[List[AI_Labeler]] = None - procedure: Optional[AI_LabelingProcedure] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('scope'): - to_interior_class(new_dict, "scope", MD_Scope) - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - if new_dict.__contains__('labelers'): - list_to_interior_class(new_dict, 'labelers', AI_Labeler) - if new_dict.__contains__('procedure'): - to_interior_class(new_dict, "procedure", AI_LabelingProcedure) - return AI_Labeling(**new_dict) - - -class MeasureReference(BaseCamelModel): - - measureIdentification: Optional[MD_Identifier] = None - nameOfMeasure: Optional[List[str]] = None - measureDescription: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('measureIdentification'): - list_to_interior_class(new_dict, 'measureIdentification', MD_Identifier) - return MeasureReference(**new_dict) - - -class EvaluationMethod(BaseCamelModel): - - name: Optional[str] = None - evaluationMethodDescription: Optional[str] = None - evaluationMethodType: Optional[List[str]] = None - evaluationProcedure: Optional[CI_Citation] = None - dateTime: Optional[List[str]] = None - referenceDoc: Optional[List[CI_Citation]] = None - deductiveSource: Optional[str] = None - - @field_validator("dateTime") - def validate_date_time(cls, v): - validated_date = [] - for item in v: - validated_date.append(_validate_date(item)) - return validated_date - - @field_validator("evaluationMethodType") - def validate_data_time(cls, v): - evaluation_method = [] - for item in v: - evaluation_method.append(_validate_evaluation_method_type(item)) - return evaluation_method - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('referenceDoc'): - list_to_interior_class(new_dict, 'referenceDoc', CI_Citation) - if new_dict.__contains__('evaluationProcedure'): - to_interior_class(new_dict, 'evaluationProcedure', CI_Citation) - return EvaluationMethod(**new_dict) - - -class QuantitativeResult(BaseCamelModel): - - value: List[Union[str, object, int, float, list, bool, None]] - - valueUnit: Optional[str] = None - valueRecordType: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return QuantitativeResult(**new_dict) - - -class ConformanceResult(BaseCamelModel): - - passBool: bool = Field(alias="pass") - specification: CI_Citation - - explanation: Optional[List[str]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('specification'): - to_interior_class(new_dict, 'specification', CI_Citation) - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - return ConformanceResult(**new_dict) - - -class DescriptiveResult(BaseCamelModel): - - statement: str - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return DescriptiveResult(**new_dict) - - -class MD_Dimension(BaseCamelModel): - - dimensionName: str - dimensionSize: int - - resolution: Optional[str] = None - dimensionTitle: Optional[str] = None - dimensionDescription: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return MD_Dimension(**new_dict) - - -class MD_GridSpatialRepresentation(BaseCamelModel): - - numberOfDimensions: int - cellGeometry: str - transformationParameterAvailability: bool - - scope: Optional[MD_Scope] = None - axisDimensionProperties: Optional[List[MD_Dimension]] - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('scope'): - to_interior_class(new_dict, 'scope', MD_Scope) - if new_dict.__contains__('axisDimensionProperties'): - list_to_interior_class(new_dict, 'axisDimensionProperties', MD_Dimension) - return MD_GridSpatialRepresentation(**new_dict) - - def can_build_from_data(data): - try: - MD_GridSpatialRepresentation.from_dict(data) - return True - except Exception: - return False - - -class MD_GeometricObjects(BaseCamelModel): - - geometricObjectType: str - geometricObjectCount: Optional[int] - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return MD_GeometricObjects(**new_dict) - - -class MD_VectorSpatialRepresentation(BaseCamelModel): - - scope: Optional[MD_Scope] - topologyLevel: Optional[str] - geometricObjects: Optional[MD_GeometricObjects] - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('scope'): - to_interior_class(new_dict, "scope", MD_Scope) - if new_dict.__contains__('geometricObjects'): - to_interior_class(new_dict, "geometricObjects", MD_GeometricObjects) - return MD_VectorSpatialRepresentation(**new_dict) - - def can_build_from_data(data): - try: - MD_VectorSpatialRepresentation.from_dict(data) - return True - except Exception: - return False - - -class MD_RangeDimension(BaseCamelModel): - - sequenceIdentifier: Optional[MemberName] = None - description: Optional[str] = None - name: Optional[List[MD_Identifier]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('sequenceIdentifier'): - to_interior_class(new_dict, "sequenceIdentifier", MemberName) - if new_dict.__contains__('name'): - list_to_interior_class(new_dict, "name", MD_Identifier) - return MD_RangeDimension(**new_dict) - - -class CoverageResult(BaseCamelModel): - - spatialRepresentationType: str - resultSpatialRepresentation: Union[MD_GridSpatialRepresentation, MD_VectorSpatialRepresentation] - - resultContent: Optional[List[MD_RangeDimension]] = None - resultFormat: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('resultSpatialRepresentation'): - representation = new_dict['resultSpatialRepresentation'] - if MD_GridSpatialRepresentation.can_build_from_data(representation): - representation = MD_GridSpatialRepresentation.from_dict(representation) - else: - representation = MD_VectorSpatialRepresentation.from_dict(representation) - new_dict['resultSpatialRepresentation'] = representation - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - - if new_dict.__contains__('resultContent'): - list_to_interior_class(new_dict, "resultContent", MD_RangeDimension) - return CoverageResult(**new_dict) - - -class QualityElement(BaseCamelModel): - """ - From ISO 19157-1 QualityElement - """ - - type: str - measure: MeasureReference - evaluationMethod: EvaluationMethod - result: List[Union[QuantitativeResult, ConformanceResult, DescriptiveResult, CoverageResult]] - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('measure') and new_dict.__contains__('evaluationMethod') and new_dict.__contains__('result'): - to_interior_class(new_dict, "measure", MeasureReference) - to_interior_class(new_dict, "evaluationMethod", EvaluationMethod) - - result = new_dict['result'] - for i in range(len(result)): - if QuantitativeResult.can_build_from_data(result[i]): - result[i] = QuantitativeResult.from_dict(result[i]) - elif ConformanceResult.can_build_from_data(result[i]): - result[i] = ConformanceResult.from_dict(result[i]) - elif DescriptiveResult.can_build_from_data(result[i]): - result[i] = DescriptiveResult.from_dict(result[i]) - else: - result[i] = CoverageResult.from_dict(result[i]) - new_dict['resultSpatialRepresentation'] = result - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - return QualityElement(**new_dict) - - -class DataQuality(BaseCamelModel): - """ - From ISO 19157-1 DataQuality - """ - - type: Literal["DataQuality"] - scope: MD_Scope - report: Optional[List[QualityElement]] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('scope'): - to_interior_class(new_dict, "scope", MD_Scope) - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - if new_dict.__contains__('report'): - list_to_interior_class(new_dict, "report", QualityElement) - return DataQuality(**new_dict) - - -# class GeoJSON Point -# -# -# class Feature(BaseCamelModel): -# -# type: Literal["Feature"] -# properties: Union[None, object] -# geometry: Union[] -# -# id: Optional[Union[str, float]] = None -# bbox: Optional[List[float]] = Field(min_items=4) - - -class AI_Label(BaseCamelModel): - """ - Basic label type - """ - - type: Literal["AI_AbstractLabel"] - - is_negative: Optional[bool] = False # Optional without default value - confidence: Optional[float] = Field(1.0, ge=0.0, le=1.0) - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return AI_Label(**new_dict) - - -class AI_PixelLabel(AI_Label): - """ - Extended label type for pixel level training data - """ - - type: Literal["AI_PixelLabel"] - imageURL: List[str] = Field(min_items=1) - imageFormat: List[str] = Field(min_items=1) - - @field_validator("imageFormat") - def validate_image_format(cls, v): - valid_format = [] - for item in v: - valid_format.append(_validate_image_format(item)) - return valid_format - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return AI_PixelLabel(**new_dict) - - -class AI_ObjectLabel(AI_Label): - """ - Extended label type for object level training data - """ - - type: Literal["AI_ObjectLabel"] - object: Feature - label_class: str = Field(alias="class") - - dateTime: Optional[str] = None - bboxType: Optional[str] = None - - @field_validator("dateTime") - def validate_date_time(cls, v): - return _validate_date(v) - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('object'): - new_dict["object"] = geojson.loads(json.dumps(json_dict["object"])) - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - return AI_ObjectLabel(**new_dict) - - -class AI_SceneLabel(AI_Label): - """ - Extended label type for scene level training data - """ - - type: Literal["AI_SceneLabel"] - label_class: str = Field(alias="class") - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - return AI_SceneLabel(**new_dict) - - -class AI_TrainingData(BaseCamelModel): - """ - Basic training data type - """ - - type: Literal["AI_AbstractTrainingData"] - id: str - labels: List[Union[AI_Label, AI_PixelLabel, AI_ObjectLabel, AI_SceneLabel]] - - datasetId: Optional[str] = None - dataSources: Optional[List[CI_Citation]] = None - numberOfLabels: Optional[int] = None - labeling: Optional[List[AI_Labeling]] = None - trainingType: Optional[str] = None - quality: Optional[List[DataQuality]] = None - - @field_validator("trainingType") - def validate_training_type(cls, v): - valid_format = [] - for item in v: - valid_format.append(_validate_training_type(item)) - return valid_format - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('labels'): - labels = new_dict['labels'] - for i in range(len(labels)): - if labels[i]["type"] == "AI_AbstractLabel": - labels[i] = AI_Label.from_dict(labels[i]) - elif labels[i]["type"] == "AI_PixelLabel": - labels[i] = AI_PixelLabel.from_dict(labels[i]) - elif labels[i]["type"] == "AI_ObjectLabel": - labels[i] = AI_ObjectLabel.from_dict(labels[i]) - else: - labels[i] = AI_SceneLabel.from_dict(labels[i]) - new_dict['resultSpatialRepresentation'] = labels - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - - if new_dict.__contains__('dataSources'): - list_to_interior_class(new_dict, "dataSources", CI_Citation) - if new_dict.__contains__('labeling'): - list_to_interior_class(new_dict, "labeling", AI_Labeling) - if new_dict.__contains__('quality'): - list_to_interior_class(new_dict, "quality", DataQuality) - return AI_TrainingData(**new_dict) - - -class AI_EOTrainingData(AI_TrainingData): - """ - Extended training data type for EO training data - """ - - type: Literal["AI_EOTrainingData"] - dataURL: List[str] = Field(min_items=1) # That one should be uri-format - - extent: Optional[Union[EX_Extent, List[float]]] = None - dataTime: Optional[List[str]] = None - - @field_validator("dataTime") - def validate_data_time(cls, v): - validated_date = [] - for item in v: - validated_date.append(_validate_date(item)) - return validated_date - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - labels = new_dict['labels'] - for i in range(len(labels)): - if labels[i]["type"] == "AI_AbstractLabel": - labels[i] = AI_Label.from_dict(labels[i]) - elif labels[i]["type"] == "AI_PixelLabel": - labels[i] = AI_PixelLabel.from_dict(labels[i]) - elif labels[i]["type"] == "AI_ObjectLabel": - labels[i] = AI_ObjectLabel.from_dict(labels[i]) - else: - labels[i] = AI_SceneLabel.from_dict(labels[i]) - new_dict['labels'] = labels - if new_dict.__contains__('dataSources'): - list_to_interior_class(new_dict, "dataSources", CI_Citation) - if new_dict.__contains__('labeling'): - list_to_interior_class(new_dict, "labeling", AI_Labeling) - if new_dict.__contains__('quality'): - list_to_interior_class(new_dict, "quality", DataQuality) - - if new_dict.__contains__('extent'): - extent = new_dict['extent'] - for i in range(len(extent)): - if EX_Extent.can_build_from_data(extent[i]): - extent[i] = EX_Extent.from_dict(extent[i]) - else: - pass - new_dict['extent'] = extent - - return AI_EOTrainingData(**new_dict) - - -# class Changeset(BaseCamelModel): -# """ -# Training Data Changeset -# """ -# -# type: Literal["AI_TDChangeset"] -# id: str -# change_count: int -# -# add: Optional[List[AI_TrainingData]] -# change_count: Optional[int] -# dataset_id: Optional[str] -# delete: Optional[List[AI_TrainingData]] -# modify: Optional[List[AI_TrainingData]] -# version: Optional[str] -# created_time: Optional[str] -# -# @field_validator("created_time") -# def validate_created_time(cls, v): -# return _validate_date(v) -# -# def to_dict(self): -# return self.json() -# -# @staticmethod -# def from_dict(json_dict): -# return Changeset(**json_dict) - - -# class StatisticsInfoType(BaseCamelModel): -# """ -# Statistics info type -# """ -# -# key: str -# value: int -# -# def to_dict(self): -# return self.json() -# -# @staticmethod -# def from_dict(json_dict): -# return StatisticsInfoType(**json_dict) -# -# -# class StatisticsInfo(BaseCamelModel): -# """ -# Statistics info -# """ -# -# type: Optional[List[StatisticsInfoType]] = Field(min_items=1) -# -# def to_dict(self): -# return self.json() -# -# @staticmethod -# def from_dict(json_dict): -# return StatisticsInfo(**json_dict) - - -class AI_TDChangeset(BaseCamelModel): - - type: Literal["AI_TDChangeset"] - id: str - change_count: int - - dataset_id: Optional[str] = None - version: Optional[str] = None - created_time: Optional[str] = None - add: Optional[List[Union[AI_TrainingData, AI_EOTrainingData]]] = None - modify: Optional[List[Union[AI_TrainingData, AI_EOTrainingData]]] = None - delete: Optional[List[Union[AI_TrainingData, AI_EOTrainingData]]] = None - - @field_validator("created_time") - def validate_created_time(cls, v): - return _validate_date(v) - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('add'): - add = new_dict['add'] - for i in range(len(add)): - if add[i]["type"] == "AI_EOTrainingData": - add[i] = AI_EOTrainingData.from_dict(add[i]) - else: - add[i] = AI_TrainingData.from_dict(add[i]) - new_dict['add'] = add - - if new_dict.__contains__('modify'): - modify = new_dict['modify'] - for i in range(len(modify)): - if modify[i]["type"] == "AI_EOTrainingData": - modify[i] = AI_EOTrainingData.from_dict(modify[i]) - else: - modify[i] = AI_TrainingData.from_dict(modify[i]) - new_dict['modify'] = modify - - if new_dict.__contains__('delete'): - delete = new_dict['delete'] - for i in range(len(delete)): - if AI_EOTrainingData.can_build_from_data(delete[i]): - delete[i] = AI_EOTrainingData.from_dict(delete[i]) - else: - delete[i] = AI_TrainingData.from_dict(delete[i]) - new_dict['delete'] = delete - - return AI_TDChangeset(**new_dict) - - -class TrainingDataset(BaseCamelModel): - """ - Basic training dataset type - """ - - id: str - name: str - description: str - license: str - tasks: List[Union[AI_Task, AI_EOTask]] = Field(min_items=1) - data: List[Union[AI_TrainingData, AI_EOTrainingData]] = Field(min_items=1) # That one should be uri-format - type: Literal["AI_AbstractTrainingDataset"] - - amountOfTrainingData: Optional[int] = None - classes: Optional[List[NamedValue]] = None - classificationScheme: Optional[str] = None # That one should be uri-format - createdTime: Optional[str] = None - dataSources: Optional[List[CI_Citation]] = None # That string one should be uri-format - doi: Optional[str] = None - keywords: Optional[List[str]] = None - numberOfClasses: Optional[int] = None - providers: Optional[List[str]] = None - scope: Optional[MD_Scope] = None - statisticsInfo: Optional[List[NamedValue]] = None - updatedTime: Optional[str] = None - version: Optional[str] = None - labeling: Optional[List[AI_Labeling]] = None - metricsInLIT: Optional[List[AI_MetricsInLiterature]] = None - quality: Optional[List[DataQuality]] = None - changesets: Optional[List[AI_TDChangeset]] = None - - @field_validator("createdTime") - def validate_created_time(cls, v): - return _validate_date(v) - - @field_validator("updatedTime") - def validate_updated_time(cls, v): - if v is not None: - return _validate_date(v) - else: - return v - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('tasks') and new_dict.__contains__('data'): - tasks = new_dict['tasks'] - for i in range(len(tasks)): - if tasks[i]["type"] == "AI_EOTask": - tasks[i] = AI_EOTask.from_dict(tasks[i]) - else: - tasks[i] = AI_Task.from_dict(tasks[i]) - new_dict['tasks'] = tasks - - data = new_dict['data'] - for i in range(len(data)): - if data[i]["type"] == "AI_EOTrainingData": - data[i] = AI_EOTrainingData.from_dict(data[i]) - else: - data[i] = AI_TrainingData.from_dict(data[i]) - new_dict['data'] = data - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - - if new_dict.__contains__('classes'): - list_to_interior_class(new_dict, "classes", NamedValue) - if new_dict.__contains__('dataSources'): - list_to_interior_class(new_dict, "dataSources", CI_Citation) - if new_dict.__contains__('scope'): - to_interior_class(new_dict, "scope", MD_Scope) - if new_dict.__contains__('statisticsInfo'): - list_to_interior_class(new_dict, "statisticsInfo", NamedValue) - if new_dict.__contains__('labeling'): - list_to_interior_class(new_dict, "labeling", AI_Labeling) - if new_dict.__contains__('metricsInLIT'): - list_to_interior_class(new_dict, "metricsInLIT", AI_MetricsInLiterature) - if new_dict.__contains__('quality'): - list_to_interior_class(new_dict, "quality", DataQuality) - if new_dict.__contains__('changesets'): - list_to_interior_class(new_dict, "changesets", AI_TDChangeset) - return TrainingDataset(**new_dict) - - -class EOTrainingDataset(TrainingDataset): - """ - Extended training dataset type for EO training dataset - """ - - type: Literal["AI_EOTrainingDataset"] - tasks: List[AI_EOTask] = Field(min_items=1) - data: List[AI_EOTrainingData] = Field(min_items=1) - # For Convinience, we allow the user to specify the bands by name - - bands: Optional[List[MD_Band]] = None - extent: Optional[EX_Extent] = None - imageSize: Optional[str] = None - - def to_dict(self): - return self.json() - - @staticmethod - def from_dict(json_dict): - new_dict = copy.deepcopy(json_dict) - if new_dict.__contains__('tasks') and new_dict.__contains__('data'): - list_to_interior_class(new_dict, "tasks", AI_EOTask) - list_to_interior_class(new_dict, "data", AI_EOTrainingData) - else: - print("Some necessary parameters are missing from the provided data.") - exit(1) - if new_dict.__contains__('bands'): - list_to_interior_class(new_dict, "bands", MD_Band) - if new_dict.__contains__('extent'): - list_to_interior_class(new_dict, "extent", EX_Extent) - if new_dict.__contains__('classes'): - list_to_interior_class(new_dict, "classes", NamedValue) - if new_dict.__contains__('dataSources'): - list_to_interior_class(new_dict, "dataSources", CI_Citation) - if new_dict.__contains__('scope'): - to_interior_class(new_dict, "scope", MD_Scope) - if new_dict.__contains__('statisticsInfo'): - list_to_interior_class(new_dict, "statisticsInfo", NamedValue) - if new_dict.__contains__('labeling'): - list_to_interior_class(new_dict, "labeling", AI_Labeling) - if new_dict.__contains__('metricsInLIT'): - list_to_interior_class(new_dict, "metricsInLIT", AI_MetricsInLiterature) - if new_dict.__contains__('quality'): - list_to_interior_class(new_dict, "quality", DataQuality) - if new_dict.__contains__('changesets'): - list_to_interior_class(new_dict, "changesets", AI_TDChangeset) - return EOTrainingDataset(**new_dict) - - -# if __name__ == '__main__': - # CI_Citation - # dict = [ - # { - # "title": "Landsat-8", - # "date": [ - # { - # "date": "2017-01-05", - # "dateType": "q" - # }, - # { - # "date": "2017-02-18", - # "dateType": "1" - # }, - # ] - # } - # ] - # for i in dict: - # print(CI_Citation.from_dict(i)) - - # MD_Band - # dict = [ - # { - # "name": [ - # { - # "code": "red" - # } - # ] - # }, - # { - # "name": [ - # { - # "code": "green" - # } - # ] - # }, - # { - # "name": [ - # { - # "code": "blue" - # } - # ] - # }, - # { - # "name": [ - # { - # "code": "VH" - # } - # ] - # }, - # { - # "name": [ - # { - # "code": "VV" - # } - # ] - # }, - # { - # "name": [ - # { - # "code": "VV/VH" - # } - # ] - # } - # ] - # for i in dict: - # print(MD_Band.from_dict(i)) - - # RangeElementDescription_dic = { - # "name": "B3", - # "definition": "dwd", - # "rangeElement": ["dwa", "wa"] - # } - # print(MI_RangeElementDescription.from_dict(RangeElementDescription_dic)) - - # scope = { - # "level": "12", - # "extent": [ - # [ - # 1560160.0, - # 5176338.4, - # 1566661.4, - # 5179409.2 - # ] - # ] - # # "level_description": - # } - # scope = { - # "level": "12", - # "extent": [ - # { - # "description": "feqfqw" - # } - # ] - # } - # print(MD_Scope.from_dict(scope)) - - # polygon - # polygon = { - # "description": "test", - # "name": [ - # "dayuan", - # "xiaoyuan" - # ], - # "interior": [ - # { - # "linearRing": { - # "posList": [ - # 2306.0, - # 729.0, - # 2330.0, - # 729.0 - # ] - # } - # } - # ] - # } - # print(Polygon.from_dict(polygon)) - - # EX_Extent - - # extent = { - # "description": "test", - # "geographicElement": [ - # { - # "polygon": [{ - # "description": "test", - # "name": [ - # "dayuan", - # "xiaoyuan" - # ], - # "interior": [ - # { - # "linearRing": { - # "posList": [ - # 2306.0, - # 729.0, - # 2330.0, - # 729.0 - # ] - # } - # } - # ] - # }], - # "extentTypeCode": True - # } - # ], - # "temporalElement": [ - # { - # "extent": { - # "beginPosition": "cwewc", - # "endPosition": "cwec" - # } - # } - # ], - # "verticalElement": [ - # { - # "minimumValue": 12, - # "maximumValue": 12 - # } - # ] - # } - # print(EX_Extent.from_dict(extent)) - - # AI_EOTrainingData - # data = [ - # { - # "type": "AI_EOTrainingData", - # "id": "P0000", - # "dataSources": [ - # { - # "title": "GoogleEarth" - # } - # ], - # "dataURL": [ - # "train/images/P0000.png" - # ], - # "numberOfLabels": 444, - # "trainingType": "training", - # "labels": [ - # { - # "type": "AI_PixelLabel", - # "imageURL": [ - # "train/Instance_masks/P0000_instance_id_RGB.png", - # "train/Semantic_masks/P0000_instance_color_RGB.png" - # ], - # "imageFormat":["image/png", "image/png"] - # }, - # { - # "type": "AI_ObjectLabel", - # "class": "1", - # "bboxType": "Horizontal BBox", - # "object": { - # "type": "Feature", - # "properties": { - # "iscrowd": 0, - # "area": 2580 - # }, - # "geometry": { - # "type": "Polygon", - # "bbox": [ - # 244.0, - # 1602.0, - # 306.0, - # 1653.0 - # ], - # "coordinates": [ - # [ - # [ - # 274, - # 1602 - # ], - # [ - # 273, - # 1603 - # ], - # [ - # 272, - # 1603 - # ], - # [ - # 271, - # 1603 - # ], - # [ - # 270, - # 1604 - # ] - # ] - # ] - # } - # } - # } - # ] - # } - # ] - # print(AI_EOTrainingData.from_dict(data[0])) - - # AI_TDChangeset - - # changeset = { - # "type": "AI_TDChangeset", - # "id": "changeset-dota_v1.5", - # "datasetId": "dota_v1.5", - # "createdTime": "2019-01-01", - # "changeCount": 9, - # "modify": [ - # { - # "type": "AI_EOTrainingData", - # "id": "P1228", - # "dataSources": [ - # { - # "title": "GF" - # } - # ], - # "dataURL": [ - # "train/images/P1228.png" - # ], - # "numberOfLabels": 50, - # "trainingType": "training", - # "labels": [ - # { - # "type": "AI_ObjectLabel", - # "class": "ship", - # "object": { - # "type": "Feature", - # "properties": { - # "type": "Object" - # }, - # "geometry": { - # "type": "Polygon", - # "coordinates": [ - # [ - # [ - # 2306.0, - # 729.0 - # ], - # [ - # 2330.0, - # 729.0 - # ], - # [ - # 2330.0, - # 744.0 - # ], - # [ - # 2306.0, - # 744.0 - # ], - # [ - # 2306.0, - # 729.0 - # ] - # ] - # ] - # }, - # "bboxType": "Horizontal BBox", - # "isDiffDetectable": False - # } - # } - # ] - # } - # ] - # } - # print(AI_TDChangeset.from_dict(changeset)) - - # EOTrainingDataset - - # dataset = { - # "type": "AI_EOTrainingDataset", - # "id": "uit_hcd_california_2017", - # "name": "UiT HCD California 2017", - # "description": "This dataset is composed of two images and a label image.", - # "license": "CC BY-SA 4.0", - # "version": "1.0", - # "amountOfTrainingData": 1, - # "createdTime": "2017-01-01", - # "providers": [ - # "LP DAAC", - # "ESA" - # ], - # "classes": [ - # { - # "key": "change", - # "value": 1 - # }, - # { - # "key": "unchanged", - # "value": 0 - # } - # ], - # "numberOfClasses": 2, - # "bands": [ - # { - # "name": [ - # { - # "code": "red" - # } - # ] - # }, - # { - # "name": [ - # { - # "code": "green" - # } - # ] - # }, - # { - # "name": [ - # { - # "code": "blue" - # } - # ] - # }, - # { - # "name": [ - # { - # "code": "VH" - # } - # ] - # }, - # { - # "name": [ - # { - # "code": "VV" - # } - # ] - # }, - # { - # "name": [ - # { - # "code": "VV/VH" - # } - # ] - # } - # ], - # "imageSize": "2000x3500", - # "tasks": [ - # { - # "type": "AI_EOTask", - # "id": "uit_hcd_california_2017-task", - # "description": "Multi-source images change detection", - # "taskType": "http://demo#change_detection" - # } - # ], - # "data": [ - # { - # "type": "AI_EOTrainingData", - # "id": "0", - # "dataTime": [ - # "2017-01-05", - # "2017-02-18" - # ], - # "dataURL": [ - # "t1_L8.png", - # "t2_SAR.png" - # ], - # "dataSources": [ - # { - # "title": "Landsat-8" - # } - # ], - # "numberOfLabels": 1, - # "labels": [ - # { - # "type": "AI_PixelLabel", - # "imageURL": [ - # "change_label.png" - # ], - # "imageFormat": [ - # "image/png" - # ] - # } - # ] - # } - # ] - # } - # - # print(EOTrainingDataset.from_dict(dataset).to_dict()) diff --git a/pytdml/type/basic_types.py b/pytdml/type/basic_types.py index 8c3bba3..49544ee 100644 --- a/pytdml/type/basic_types.py +++ b/pytdml/type/basic_types.py @@ -30,9 +30,11 @@ # # ------------------------------------------------------------------------------ +import copy +from typing_extensions import TypedDict from typing import List, Union, Optional, Literal from pydantic import BaseModel, Field, field_validator, model_validator -from pytdml.type._utils import _validate_date, to_camel, _valid_methods, _validate_training_type +from pytdml.type._utils import _validate_date, to_camel, _valid_methods, _validate_training_type, _validate_evaluation_method_type, to_interior_class, list_to_interior_class class BaseCamelModel(BaseModel): @@ -41,14 +43,11 @@ class BaseCamelModel(BaseModel): Since python use snake case as default We need to convert it to camel case for JSON """ - - class Config: - alias_generator = to_camel - populate_by_name = True - arbitrary_types_allowed = True - - def json(self, **kwargs): - return super().json(by_alias=True, **kwargs) + model_config: TypedDict = { + 'alias_generator': to_camel, + 'populate_by_name': True, + 'arbitrary_types_allowed': True + } class KeyValuePair(BaseCamelModel): @@ -56,8 +55,18 @@ class KeyValuePair(BaseCamelModel): Key/Value pair type """ - key: str - value: Union[str, int, float, bool, None] + key: list + value: list + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + key = json_dict.keys() + value = json_dict.values() + key_value_pair = {"key": key, "value": value} + return KeyValuePair(**key_value_pair) class NamedValue(BaseCamelModel): @@ -66,52 +75,15 @@ class NamedValue(BaseCamelModel): """ key: str - value: Union[str, object, int, list, bool, None] - - -class MD_ScopeDescription(BaseCamelModel): - """ - From ISO 19115-1 MD_ScopeDescription - """ - - dataset: str - features: Optional[List[str]] - + value: Union[str, object, int, float, list, bool, None] -class MD_Band(BaseCamelModel): - """ - From ISO 19115-1 MD_Band - """ + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) - bound_max: Optional[float] - bound_min: Optional[float] - bound_units: Optional[Literal["nm", "um", "cm", "dm", "m", "km"]] - peak_response: Optional[float] - tone_gradation: Optional[int] - - @model_validator(mode="before") - def check_dependent_required(self): - bound_units = self.get("boundUnits") - bound_max = self.get("boundMax") - bound_min = self.get("boundMin") - - # check dependRequired in jsonschema - if bound_units is not None and (bound_max is None or bound_min is None): - raise ValueError( - "boundMax and boundMin are required when boundUnits is present" - ) - - return self - - -class MD_Scope(BaseCamelModel): - """ - From ISO 19115-1 MD_Scope - """ - - level: str - extent: Optional[List[float]] = Field(min_items=4) - level_description: Optional[MD_ScopeDescription] + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return NamedValue(**new_dict) class CI_Date(BaseCamelModel): @@ -120,21 +92,19 @@ class CI_Date(BaseCamelModel): """ date: str - dateType: str + date_type: str @field_validator("date") def validate_date(cls, v): return _validate_date(v) + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) -class MD_BrowseGraphic(BaseCamelModel): - """ - From ISO 19115-1 MD_BrowseGraphic - """ - - file_name: str - file_description: Optional[str] - file_type: Optional[str] + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return CI_Date(**new_dict) class CI_Citation(BaseCamelModel): @@ -143,26 +113,42 @@ class CI_Citation(BaseCamelModel): """ title: str - alternateTitle: Optional[List[str]] - date: Optional[CI_Date] - edition: Optional[str] - edition_date: Optional[str] - graphic: Optional[List[MD_BrowseGraphic]] - identifier: Optional[List[KeyValuePair]] - ISBN: Optional[str] - ISSN: Optional[str] + alternate_title: Optional[List[str]] = None + date: Optional[List[CI_Date]] = None + edition: Optional[str] = None + edition_date: Optional[str] = None + graphic: Optional[List[KeyValuePair]] = None + identifier: Optional[List[KeyValuePair]] = None + ISBN: Optional[str] = None + ISSN: Optional[str] = None @field_validator("edition_date") def validate_edition_date(cls, v): return _validate_date(v) + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return CI_Citation(**new_dict) + class LinearRing(BaseCamelModel): """ gml: LinearRing - NIEM 2.1 """ - posList: List[int] = Field(min_items=4) + pos_list: List[float] = Field(min_length=4) + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return LinearRing(**new_dict) class LinearRing_Object(BaseCamelModel): @@ -170,7 +156,15 @@ class LinearRing_Object(BaseCamelModel): LinearRing Object Type """ - linearRing: LinearRing = Field(alias="LinearRing") + linear_ring: LinearRing + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return LinearRing_Object(**new_dict) class Polygon(BaseCamelModel): @@ -178,12 +172,20 @@ class Polygon(BaseCamelModel): gml: Polygon - NIEM 2.1 """ - description: Optional[str] - description_Reference: Optional[str] - identifier: Optional[str] - name: Optional[List[str]] - exterior: Optional[LinearRing_Object] - interior: Optional[List[LinearRing_Object]] + description: Optional[str] = None + description_reference: Optional[str] = None + identifier: Optional[str] = None + name: Optional[List[str]] = None + exterior: Optional[LinearRing_Object] = None + interior: Optional[List[LinearRing_Object]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return Polygon(**new_dict) class MD_Identifier(BaseCamelModel): @@ -192,41 +194,179 @@ class MD_Identifier(BaseCamelModel): """ code: str - authority: Optional[CI_Citation] - code_space: Optional[str] - version: Optional[str] - description: Optional[str] + authority: Optional[CI_Citation] = None + code_space: Optional[str] = None + version: Optional[str] = None + description: Optional[str] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MD_Identifier(**new_dict) + + +class MemberName(BaseCamelModel): + """ + From ISO 19115-1 MD_Identifier + """ + + a_name: str + attribute_type: str + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MemberName(**new_dict) + + +class MI_RangeElementDescription(BaseCamelModel): + """ + From ISO 19115-1 MD_Identifier + """ + + name: str + definition: str + range_element: List[str] = Field(min_length=1) + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MI_RangeElementDescription(**new_dict) + + +class MD_Band(BaseCamelModel): + """ + From ISO 19115-1 MD_Band + """ + + sequence_identifier: Optional[MemberName] = None + description: Optional[str] = None + name: Optional[List[MD_Identifier]] = None + max_value: Optional[float] = None + min_value: Optional[float] = None + units: Optional[str] = None + scale_factor: Optional[float] = None + offset: Optional[float] = None + mean_value: Optional[float] = None + number_of_values: Optional[int] = None + standard_deviation: Optional[float] = None + other_property_type: Optional[float] = None + other_property: Optional[str] = None + bits_per_value: Optional[int] = None + range_element_description: Optional[List[MI_RangeElementDescription]] = None + bound_max: Optional[float] = None + bound_min: Optional[float] = None + bound_units: Optional[str] = None + peak_response: Optional[float] = None + tone_gradation: Optional[int] = None + + @model_validator(mode="before") + def check_dependent_required(cls, v): + bound_units = v.get("boundUnits") + bound_max = v.get("boundMax") + bound_min = v.get("boundMin") + + # check dependRequired in jsonschema + if bound_units is not None and (bound_max is None or bound_min is None): + raise ValueError( + "boundMax and boundMin are required when boundUnits is present" + ) + + return v + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) -class BoundingPolygon(BaseCamelModel): + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MD_Band(**new_dict) + + +class EX_BoundingPolygon(BaseCamelModel): """ From ISO 19115:2003 """ - polygon: List[Polygon] = Field(min_items=1) - extentTypeCode: Optional[bool] + polygon: List[Polygon] = Field(min_length=1) + extent_type_code: Optional[bool] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return EX_BoundingPolygon(**new_dict) + + def can_build_from_data(data): + try: + EX_BoundingPolygon.from_dict(data) + return True + except Exception: + return False -class GeographicBoundingBox(BaseCamelModel): +class EX_GeographicBoundingBox(BaseCamelModel): """ From ISO 19168-2:2022 """ - westBoundLongitude: int - eastBoundLongitude: int - southBoundLatitude: int - northBoundLatitude: int + west_bound_longitude: int + east_bound_longitude: int + south_bound_latitude: int + north_bound_latitude: int - extentTypeCode: Optional[bool] + extent_type_code: Optional[bool] = None + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) -class GeographicDescription(BaseCamelModel): + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return EX_GeographicBoundingBox(**new_dict) + + def can_build_from_data(data): + try: + EX_GeographicBoundingBox.from_dict(data) + return True + except Exception: + return False + + +class EX_GeographicDescription(BaseCamelModel): """ From ISO 19115: 2003 Metadata """ - geographicIdentifier: MD_Identifier - extentTypeCode: Optional[bool] + geographic_identifier: MD_Identifier + + extent_type_code: Optional[bool] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return EX_GeographicDescription(**new_dict) + + def can_build_from_data(data): + try: + EX_GeographicDescription.from_dict(data) + return True + except Exception: + return False class TimeInstant(BaseCamelModel): @@ -234,13 +374,28 @@ class TimeInstant(BaseCamelModel): Time Instant Type """ - timePosition: str + time_position: str + + description: Optional[str] = None + description_reference: Optional[str] = None + identifier: Optional[str] = None + name: Optional[List[str]] = None + related_time: Optional[List[KeyValuePair]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return TimeInstant(**new_dict) - description: Optional[str] - descriptionReference: Optional[str] - identifier: Optional[str] - name: Optional[List[str]] - relatedTime: Optional[List[KeyValuePair]] + def can_build_from_data(data): + try: + TimeInstant.from_dict(data) + return True + except Exception: + return False class TimePeriod(BaseCamelModel): @@ -248,41 +403,86 @@ class TimePeriod(BaseCamelModel): Time Period Type """ - beginPosition: str - endPosition: str + begin_position: str + end_position: str - description: Optional[str] - descriptionReference: Optional[str] - identifier: Optional[str] - name: Optional[List[str]] - duration: Optional[str] - timeInterval: Optional[int] - relatedTime: Optional[List[KeyValuePair]] + description: Optional[str] = None + description_reference: Optional[str] = None + identifier: Optional[str] = None + name: Optional[List[str]] = None + duration: Optional[str] = None + time_interval: Optional[int] = None + related_time: Optional[List[KeyValuePair]] = None - @field_validator("beginPosition") - def validate_date_time(cls, v): + @field_validator("begin_position") + def validate_begin_position(cls, v): return _validate_date(v) - @field_validator("endPosition") - def validate_date_time(cls, v): + @field_validator("end_position") + def validate_end_position(cls, v): return _validate_date(v) + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return TimePeriod(**new_dict) + + def can_build_from_data(data): + try: + TimePeriod.from_dict(data) + return True + except Exception: + return False + -class TemporalExtent(BaseCamelModel): +class EX_TemporalExtent(BaseCamelModel): """ From ISO 19108:2002 """ extent: Union[TimeInstant, TimePeriod] + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) -class ReferenceSystem(BaseCamelModel): + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + if new_dict.__contains__('extent'): + extent = new_dict['extent'] + if TimeInstant.can_build_from_data(extent): + extent = TimeInstant.from_dict(extent) + else: + extent = TimePeriod.from_dict(extent) + new_dict['extent'] = extent + return EX_TemporalExtent(**new_dict) + + def can_build_from_data(data): + try: + EX_TemporalExtent.from_dict(data) + return True + except Exception: + return False + + +class EX_ReferenceSystem(BaseCamelModel): """ From ISO 19111: 2019 """ - referenceSystemIdentifier: Optional[MD_Identifier] - referenceSystemType: Optional[str] + reference_system_identifier: Optional[MD_Identifier] = None + reference_system_type: Optional[str] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return EX_ReferenceSystem(**new_dict) class VerticalCRS(BaseCamelModel): @@ -291,79 +491,435 @@ class VerticalCRS(BaseCamelModel): """ identifier: str - scope: List[str] = Field(min_items=1) - verticalCS: List[str] = Field(min_items=1) - verticalDatum: List[str] = Field(min_items=1) + scope: List[str] = Field(min_length=1) + vertical_CS: List[str] = Field(min_length=1) + vertical_datum: List[str] = Field(min_length=1) - description: Optional[str] - description_Reference: Optional[str] - name: Optional[List[str]] - remarks: Optional[List[str]] - domain_Of_Validity: Optional[List[str]] + description: Optional[str] = None + description_reference: Optional[str] = None + name: Optional[List[str]] = None + remarks: Optional[List[str]] = None + domain_of_validity: Optional[List[str]] = None + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) -class VerticalExtent(BaseCamelModel): + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return VerticalCRS(**new_dict) + + +class EX_VerticalExtent(BaseCamelModel): """ From ISO 19115 SpiralTracker Report """ - minimumValue: int - maximumValue: int + minimum_value: int + maximum_value: int + + vertical_CRS_id: Optional[EX_ReferenceSystem] = None + vertical_CRS: Optional[VerticalCRS] = None - verticalCRSId: Optional[ReferenceSystem] - verticalCRS: Optional[VerticalCRS] + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return EX_VerticalExtent(**new_dict) -class SpatialTemporalExtent(BaseCamelModel): + +class EX_SpatialTemporalExtent(BaseCamelModel): """ From ISO 19115: 2003 Metadata """ extent: Union[TimeInstant, TimePeriod] - spatialExtent: Union[BoundingPolygon, GeographicBoundingBox, GeographicDescription] + spatial_extent: Union[EX_BoundingPolygon, EX_GeographicBoundingBox, EX_GeographicDescription] + + vertical_extent: Optional[EX_VerticalExtent] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + if new_dict.__contains__('extent'): + extent = new_dict['extent'] + if TimeInstant.can_build_from_data(extent): + extent = TimeInstant.from_dict(extent) + else: + extent = TimePeriod.from_dict(extent) + new_dict['extent'] = extent + if new_dict.__contains__('spatialExtent'): + spatial_extent = new_dict['spatialExtent'] + if EX_BoundingPolygon.can_build_from_data(spatial_extent): + spatial_extent = EX_BoundingPolygon.from_dict(spatial_extent) + elif EX_GeographicBoundingBox.can_build_from_data(spatial_extent): + spatial_extent = EX_GeographicBoundingBox.from_dict(spatial_extent) + else: + spatial_extent = EX_GeographicDescription.from_dict(spatial_extent) + new_dict['spatialExtent'] = spatial_extent + return EX_SpatialTemporalExtent(**new_dict) + + def can_build_from_data(data): + try: + EX_SpatialTemporalExtent.from_dict(data) + return True + except Exception: + return False + + +class EX_Extent(BaseCamelModel): + """ + From ISO 19115: 2003 Metadata + """ - verticalExtent: Optional[VerticalExtent] + description: Optional[str] = None + geographic_element: Optional[List[Union[EX_BoundingPolygon, EX_GeographicBoundingBox, EX_GeographicDescription]]] = None + temporal_element: Optional[List[Union[EX_TemporalExtent, EX_SpatialTemporalExtent]]] = None + vertical_element: Optional[List[EX_VerticalExtent]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + if new_dict.__contains__('geographicElement'): + geographic_element = new_dict['geographicElement'] + for i in range(len(geographic_element)): + if EX_BoundingPolygon.can_build_from_data(geographic_element[i]): + geographic_element[i] = EX_BoundingPolygon.from_dict(geographic_element[i]) + elif EX_GeographicBoundingBox.can_build_from_data(geographic_element[i]): + geographic_element[i] = EX_GeographicBoundingBox.from_dict(geographic_element[i]) + else: + geographic_element[i] = EX_GeographicDescription.from_dict(geographic_element[i]) + new_dict['geographicElement'] = geographic_element + + if new_dict.__contains__('temporalElement'): + temporal_element = new_dict['temporalElement'] + for i in range(len(temporal_element)): + if EX_TemporalExtent.can_build_from_data(temporal_element[i]): + temporal_element[i] = EX_TemporalExtent.from_dict(temporal_element[i]) + else: + temporal_element[i] = EX_SpatialTemporalExtent.from_dict(temporal_element[i]) + new_dict['temporalElement'] = temporal_element + return EX_Extent(**new_dict) + + def can_build_from_data(data): + try: + EX_Extent.from_dict(data) + return True + except Exception: + return False + + +# class BoundingBox(BaseCamelModel): +# """ +# From GeoJSON bounding box +# """ +# +# extent: List[float] = Field(min_length=4) +# +# def to_dict(self): +# return self.model_dump(by_alias=True, exclude_none=True) +# +# @staticmethod +# def from_dict(json_dict): +# return BoundingBox(**json_dict) -class Extent(BaseCamelModel): +class MD_ScopeDescription(BaseCamelModel): """ - From ISO 19115: 2003 Metadata + From ISO 19115-1 MD_ScopeDescription """ - description: str - geographicElement: List[Union[BoundingPolygon, GeographicBoundingBox, GeographicDescription]] - temporalElement: List[Union[TemporalExtent, SpatialTemporalExtent]] - verticalElement: List[VerticalExtent] + attributes: Optional[str] = None + features: Optional[str] = None + feature_instances: Optional[str] = None + attribute_instances: Optional[str] = None + dataset: Optional[str] = None + other: Optional[str] = None + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) -class BoundingBox(BaseCamelModel): + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MD_ScopeDescription(**new_dict) + + +class MD_Scope(BaseCamelModel): """ - From GeoJSON bounding box + From ISO 19115-1 MD_Scope """ - extent: List[int] = Field(min_items=4) + level: str + extent: Optional[List[Union[EX_Extent, List[float]]]] = None + level_description: Optional[MD_ScopeDescription] = Field(None, min_length=4) + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + if new_dict.__contains__('extent'): + extent = new_dict['extent'] + for i in range(len(extent)): + if EX_Extent.can_build_from_data(extent[i]): + extent[i] = EX_Extent.from_dict(extent[i]) + else: + pass + return MD_Scope(**new_dict) + + +class CI_Telephone(BaseCamelModel): + + number: str + number_type: Optional[str] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return CI_Telephone(**new_dict) + + +class CI_Address(BaseCamelModel): + + delivery_point: Optional[List[str]] = None + city: Optional[str] = None + administrative_area: Optional[str] = None + postal_code: Optional[str] = None + country: Optional[str] = None + electronic_mail_address: Optional[List[str]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return CI_Address(**new_dict) + + +class CI_OnlineResource(BaseCamelModel): + + linkage: str + protocol: Optional[str] = None + application_profile: Optional[str] = None + name: Optional[str] = None + description: Optional[str] = None + function: Optional[str] = None + protocol_request: Optional[str] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return CI_OnlineResource(**new_dict) + + +class CI_Contact(BaseCamelModel): + + phone: Optional[List[CI_Telephone]] = None + address: Optional[List[CI_Address]] = None + online_resource: Optional[List[CI_OnlineResource]] = None + hours_of_service: Optional[List[str]] = None + contact_instructions: Optional[str] = None + contact_type: Optional[str] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return CI_Contact(**new_dict) + + +class CI_Individual(BaseCamelModel): + + name: Optional[str] = None + contact_info: Optional[List[CI_Contact]] = None + party_identifier: Optional[List[MD_Identifier]] = None + position_name: Optional[str] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return CI_Individual(**new_dict) + + def can_build_from_data(data): + try: + CI_Individual.from_dict(data) + return True + except Exception: + return False + +class CI_Organisation(BaseCamelModel): -class MetricsPair(BaseCamelModel): + name: Optional[str] = None + contact_info: Optional[List[CI_Contact]] = None + party_identifier: Optional[List[MD_Identifier]] = None + logo: Optional[List[KeyValuePair]] = None + individual: Optional[List[CI_Individual]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return CI_Organisation(**new_dict) + + def can_build_from_data(data): + try: + CI_Individual.from_dict(data) + return True + except Exception: + return False + + +class CI_Responsibility(BaseCamelModel): + + role: str + party: List[Union[CI_Individual, CI_Organisation]] + + extent: Optional[List[Union[EX_Extent, List[float]]]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + if new_dict.__contains__('party'): + party = new_dict['party'] + for i in range(len(party)): + if CI_Individual.can_build_from_data(party[i]): + party[i] = CI_Individual.from_dict(party[i]) + else: + party[i] = CI_Organisation.from_dict(party[i]) + new_dict['party'] = party + else: + print("Some necessary parameters of party are not provided.") + exit() + + if new_dict.__contains__('extent'): + extent = new_dict['extent'] + for i in range(len(extent)): + if EX_Extent.can_build_from_data(extent[i]): + extent[i] = EX_Extent.from_dict(extent[i]) + else: + pass + new_dict['extent'] = extent + return CI_Responsibility(**new_dict) + + +class MD_Releasability(BaseCamelModel): + + addressee: Optional[List[CI_Responsibility]] = None + statement: Optional[str] = None + dissemination_constraints: Optional[List[str]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MD_Releasability(**new_dict) + + +class MD_Constraints(BaseCamelModel): + + use_limitation: Optional[List[str]] = None + constraint_application_scope: Optional[MD_Scope] = None + graphic: Optional[List[KeyValuePair]] = None + reference: Optional[List[CI_Citation]] = None + releasability: Optional[MD_Releasability] = None + responsible_party: Optional[List[CI_Responsibility]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MD_Constraints(**new_dict) + + +class MD_BrowseGraphic(BaseCamelModel): """ - Metrics pair type + From ISO 19115-1 MD_BrowseGraphic """ - name: str - value: float + file_name: str + file_description: Optional[str] = None + file_type: Optional[str] = None + image_constraints: Optional[List[MD_Constraints]] = None + linkage: Optional[List[CI_OnlineResource]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MD_BrowseGraphic(**new_dict) -class MetricsInLiterature(BaseCamelModel): + +# class MetricsPair(BaseCamelModel): +# """ +# Metrics pair type +# """ +# +# name: str +# value: float +# +# def to_dict(self): +# return self.model_dump(by_alias=True, exclude_none=True) +# +# @staticmethod +# def from_dict(json_dict): +# new_dict = copy.deepcopy(json_dict) +# return MetricsPair(**new_dict) + + +class AI_MetricsInLiterature(BaseCamelModel): """ Metrics in literature type """ doi: str - algorithm: str = None - metrics: Optional[List[MetricsPair]] = Field(min_items=1) + metrics: List[NamedValue] = Field(min_length=1) + + algorithm: Optional[str] = None + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) -class Task(BaseCamelModel): + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return AI_MetricsInLiterature(**new_dict) + + +class AI_Task(BaseCamelModel): """ Basic task type """ @@ -371,11 +927,19 @@ class Task(BaseCamelModel): id: str type: Literal["AI_AbstractTask"] - dataset_id: Optional[str] - description: Optional[str] = "" + dataset_id: Optional[str] = None + description: Optional[str] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return AI_Task(**new_dict) -class Labeler(BaseCamelModel): +class AI_Labeler(BaseCamelModel): """ Labeler type """ @@ -384,24 +948,40 @@ class Labeler(BaseCamelModel): name: str type: Literal["AI_Labeler"] + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) -class LabelingProcedure(BaseCamelModel): + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return AI_Labeler(**new_dict) + + +class AI_LabelingProcedure(BaseCamelModel): """ Labeling procedure type """ type: Literal["AI_LabelingProcedure"] id: str - methods: List[str] = Field(min_items=1) + methods: List[str] = Field(min_length=1) - tools: Optional[List[str]] + tools: Optional[List[str]] = None @field_validator("methods") def valid_methods(cls, v): return _valid_methods(v) + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return AI_LabelingProcedure(**new_dict) -class Labeling(BaseCamelModel): + +class AI_Labeling(BaseCamelModel): """ Labeling type """ @@ -409,8 +989,232 @@ class Labeling(BaseCamelModel): id: str scope: MD_Scope type: Literal["AI_Labeling"] - labelers: Optional[List[Labeler]] - procedure: Optional[LabelingProcedure] + + labelers: Optional[List[AI_Labeler]] = None + procedure: Optional[AI_LabelingProcedure] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return AI_Labeling(**new_dict) + + +class MeasureReference(BaseCamelModel): + + measure_identification: Optional[MD_Identifier] = None + name_of_measure: Optional[List[str]] = None + measure_description: Optional[str] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MeasureReference(**new_dict) + + +class EvaluationMethod(BaseCamelModel): + + name: Optional[str] = None + evaluation_method_description: Optional[str] = None + evaluation_method_type: Optional[List[str]] = None + evaluation_procedure: Optional[CI_Citation] = None + date_time: Optional[List[str]] = None + reference_doc: Optional[List[CI_Citation]] = None + deductive_source: Optional[str] = None + + @field_validator("date_time") + def validate_date_time(cls, v): + validated_date = [] + for item in v: + validated_date.append(_validate_date(item)) + return validated_date + + @field_validator("evaluation_method_type") + def validate_data_time(cls, v): + evaluation_method = [] + for item in v: + if _validate_evaluation_method_type(item): + evaluation_method.append(item) + return evaluation_method + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return EvaluationMethod(**new_dict) + + +class QuantitativeResult(BaseCamelModel): + + value: List[Union[str, object, int, float, list, bool, None]] + + value_unit: Optional[str] = None + value_record_type: Optional[str] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return QuantitativeResult(**new_dict) + + +class ConformanceResult(BaseCamelModel): + + pass_bool: bool = Field(alias="pass") + specification: CI_Citation + + explanation: Optional[List[str]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return ConformanceResult(**new_dict) + + +class DescriptiveResult(BaseCamelModel): + + statement: str + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return DescriptiveResult(**new_dict) + + +class MD_Dimension(BaseCamelModel): + + dimension_name: str + dimension_size: int + + resolution: Optional[str] = None + dimension_title: Optional[str] = None + dimension_description: Optional[str] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MD_Dimension(**new_dict) + + +class MD_GridSpatialRepresentation(BaseCamelModel): + + number_of_dimensions: int + cell_geometry: str + transformation_parameter_availability: bool + + scope: Optional[MD_Scope] = None + axis_dimension_properties: Optional[List[MD_Dimension]] + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MD_GridSpatialRepresentation(**new_dict) + + def can_build_from_data(data): + try: + MD_GridSpatialRepresentation.from_dict(data) + return True + except Exception: + return False + + +class MD_GeometricObjects(BaseCamelModel): + + geometric_object_type: str + geometric_object_count: Optional[int] + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MD_GeometricObjects(**new_dict) + + +class MD_VectorSpatialRepresentation(BaseCamelModel): + + scope: Optional[MD_Scope] + topology_level: Optional[str] + geometric_objects: Optional[MD_GeometricObjects] + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MD_VectorSpatialRepresentation(**new_dict) + + def can_build_from_data(data): + try: + MD_VectorSpatialRepresentation.from_dict(data) + return True + except Exception: + return False + + +class MD_RangeDimension(BaseCamelModel): + + sequence_identifier: Optional[MemberName] = None + description: Optional[str] = None + name: Optional[List[MD_Identifier]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return MD_RangeDimension(**new_dict) + + +class CoverageResult(BaseCamelModel): + + spatial_representation_type: str + result_spatial_representation: Union[MD_GridSpatialRepresentation, MD_VectorSpatialRepresentation] + + result_content: Optional[List[MD_RangeDimension]] = None + result_format: Optional[str] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + if new_dict.__contains__('resultSpatialRepresentation'): + representation = new_dict['resultSpatialRepresentation'] + if MD_GridSpatialRepresentation.can_build_from_data(representation): + representation = MD_GridSpatialRepresentation.from_dict(representation) + else: + representation = MD_VectorSpatialRepresentation.from_dict(representation) + new_dict['resultSpatialRepresentation'] = representation + else: + print("Some necessary parameters of resultSpatialRepresentation are not provided.") + exit(0) + return CoverageResult(**new_dict) class QualityElement(BaseCamelModel): @@ -419,9 +1223,32 @@ class QualityElement(BaseCamelModel): """ type: str - measure: str - evaluation_method: str - result: List[str] + measure: MeasureReference + evaluation_method: EvaluationMethod + result: List[Union[QuantitativeResult, ConformanceResult, DescriptiveResult, CoverageResult]] + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + if new_dict.__contains__('result'): + result = new_dict['result'] + for i in range(len(result)): + if QuantitativeResult.can_build_from_data(result[i]): + result[i] = QuantitativeResult.from_dict(result[i]) + elif ConformanceResult.can_build_from_data(result[i]): + result[i] = ConformanceResult.from_dict(result[i]) + elif DescriptiveResult.can_build_from_data(result[i]): + result[i] = DescriptiveResult.from_dict(result[i]) + else: + result[i] = CoverageResult.from_dict(result[i]) + new_dict['resultSpatialRepresentation'] = result + else: + print("Parameter \"result\" must be provided.") + exit(0) + return QualityElement(**new_dict) class DataQuality(BaseCamelModel): @@ -431,10 +1258,31 @@ class DataQuality(BaseCamelModel): type: Literal["DataQuality"] scope: MD_Scope - report: Optional[List[QualityElement]] + report: Optional[List[QualityElement]] = None + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return DataQuality(**new_dict) -class Label(BaseCamelModel): + +# class GeoJSON Point +# +# +# class Feature(BaseCamelModel): +# +# type: Literal["Feature"] +# properties: Union[None, object] +# geometry: Union[] +# +# id: Optional[Union[str, float]] = None +# bbox: Optional[List[float]] = Field(min_length=4) + + +class AI_Label(BaseCamelModel): """ Basic label type """ @@ -442,70 +1290,62 @@ class Label(BaseCamelModel): type: Literal["AI_AbstractLabel"] is_negative: Optional[bool] = False # Optional without default value - confidence: Optional[float] = Field(ge=0.0, le=1.0) # Optional without default value + confidence: Optional[float] = Field(1.0, ge=0.0, le=1.0) + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return AI_Label(**new_dict) -class TrainingData(BaseCamelModel): + +class AI_TrainingData(BaseCamelModel): """ Basic training data type """ type: Literal["AI_AbstractTrainingData"] id: str - labels: List[Union[Label, "PixelLabel", "ObjectLabel", "SceneLabel"]] + labels: List[Union[AI_Label, "AI_PixelLabel", "AI_ObjectLabel", "AI_SceneLabel"]] - dataset_id: Optional[str] + dataset_id: Optional[str] = None data_sources: Optional[List[CI_Citation]] = None - number_of_labels: Optional[int] - labeling: Optional[List[Labeling]] = None + number_of_labels: Optional[int] = None + labeling: Optional[List[AI_Labeling]] = None training_type: Optional[str] = None quality: Optional[List[DataQuality]] = None @field_validator("training_type") def validate_training_type(cls, v): - valid_format = [] - for item in v: - valid_format.append(_validate_training_type(item)) - return valid_format - - -class Changeset(BaseCamelModel): - """ - Training Data Changeset - """ - - type: Literal["AI_TDChangeset"] - id: str - change_count: int - - add: Optional[List[TrainingData]] - change_count: Optional[int] - dataset_id: Optional[str] - delete: Optional[List[TrainingData]] - modify: Optional[List[TrainingData]] - version: Optional[str] - created_time: Optional[str] - - @field_validator("created_time") - def validate_created_time(cls, v): - return _validate_date(v) - - -class StatisticsInfoType(BaseCamelModel): - """ - Statistics info type - """ - - key: str - value: int - - -class StatisticsInfo(BaseCamelModel): - """ - Statistics info - """ + if _validate_training_type(v): + return v - type: Optional[List[StatisticsInfoType]] = Field(min_items=1) + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + from pytdml.type.extended_types import AI_PixelLabel, AI_ObjectLabel, AI_SceneLabel + new_dict = copy.deepcopy(json_dict) + new_dict = copy.deepcopy(json_dict) + if new_dict.__contains__('labels'): + labels = new_dict['labels'] + for i in range(len(labels)): + if labels[i]["type"] == "AI_AbstractLabel": + labels[i] = AI_Label.from_dict(labels[i]) + elif labels[i]["type"] == "AI_PixelLabel": + labels[i] = AI_PixelLabel.from_dict(labels[i]) + elif labels[i]["type"] == "AI_ObjectLabel": + labels[i] = AI_ObjectLabel.from_dict(labels[i]) + else: + labels[i] = AI_SceneLabel.from_dict(labels[i]) + new_dict['resultSpatialRepresentation'] = labels + else: + print("Parameter \"labels\" must be provided.") + exit(0) + return AI_TrainingData(**new_dict) class AI_TDChangeset(BaseCamelModel): @@ -513,46 +1353,80 @@ class AI_TDChangeset(BaseCamelModel): id: str change_count: int - dataset_id: Optional[str] - version: Optional[str] - created_time: Optional[str] - add: Optional[List[Union[TrainingData, "EOTrainingData"]]] - modify: Optional[List[Union[TrainingData, "EOTrainingData"]]] - delete: Optional[List[Union[TrainingData, "EOTrainingData"]]] + dataset_id: Optional[str] = None + version: Optional[str] = None + created_time: Optional[str] = None + add: Optional[List[Union[AI_TrainingData, "AI_EOTrainingData"]]] = None + modify: Optional[List[Union[AI_TrainingData, "AI_EOTrainingData"]]] = None + delete: Optional[List[Union[AI_TrainingData, "AI_EOTrainingData"]]] = None @field_validator("created_time") def validate_created_time(cls, v): return _validate_date(v) + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + from pytdml.type.extended_types import AI_EOTrainingData + new_dict = copy.deepcopy(json_dict) + if new_dict.__contains__('add'): + add = new_dict['add'] + for i in range(len(add)): + if add[i]["type"] == "AI_EOTrainingData": + add[i] = AI_EOTrainingData.from_dict(add[i]) + else: + add[i] = AI_TrainingData.from_dict(add[i]) + new_dict['add'] = add + + if new_dict.__contains__('modify'): + modify = new_dict['modify'] + for i in range(len(modify)): + if modify[i]["type"] == "AI_EOTrainingData": + modify[i] = AI_EOTrainingData.from_dict(modify[i]) + else: + modify[i] = AI_TrainingData.from_dict(modify[i]) + new_dict['modify'] = modify + + if new_dict.__contains__('delete'): + delete = new_dict['delete'] + for i in range(len(delete)): + if AI_EOTrainingData.can_build_from_data(delete[i]): + delete[i] = AI_EOTrainingData.from_dict(delete[i]) + else: + delete[i] = AI_TrainingData.from_dict(delete[i]) + new_dict['delete'] = delete + return AI_TDChangeset(**new_dict) + class TrainingDataset(BaseCamelModel): """ Basic training dataset type """ - id: str name: str description: str license: str - tasks: List[Union[Task, "EOTask"]] = Field(min_items=1) - data: List[Union[TrainingData, "EOTrainingData"]] = Field(min_items=1) # That one should be uri-format + tasks: List[Union[AI_Task, "AI_EOTask"]] = Field(min_length=1) + data: List[Union[AI_TrainingData, "AI_EOTrainingData"]] = Field(min_length=1) # That one should be uri-format type: Literal["AI_AbstractTrainingDataset"] - amount_Of_TrainingData: Optional[int] - classes: Optional[List[NamedValue]] - classification_schema: Optional[str] = "" # That one should be uri-format - created_time: Optional[str] - dataSources: Optional[List[CI_Citation]] = [] # That string one should be uri-format - doi: Optional[str] = "" - keywords: Optional[List[str]] - number_of_classes: Optional[int] - providers: Optional[List[str]] = [] + amount_of_trainingData: Optional[int] = None + classes: Optional[List[NamedValue]] = None + classification_scheme: Optional[str] = None # That one should be uri-format + created_time: Optional[str] = None + data_sources: Optional[List[CI_Citation]] = None # That string one should be uri-format + doi: Optional[str] = None + keywords: Optional[List[str]] = None + number_of_classes: Optional[int] = None + providers: Optional[List[str]] = None scope: Optional[MD_Scope] = None statistics_info: Optional[List[NamedValue]] = None - updated_time: Optional[str] = "" - version: Optional[str] - labeling: Optional[List[Labeling]] = [] - metrics_in_LIT: Optional[List[MetricsInLiterature]] = None + updated_time: Optional[str] = None + version: Optional[str] = None + labeling: Optional[List[AI_Labeling]] = None + metrics_in_LIT: Optional[List[AI_MetricsInLiterature]] = None quality: Optional[List[DataQuality]] = None changesets: Optional[List[AI_TDChangeset]] = None @@ -566,3 +1440,31 @@ def validate_updated_time(cls, v): return _validate_date(v) else: return v + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + from pytdml.type.extended_types import AI_EOTask, AI_EOTrainingData + new_dict = copy.deepcopy(json_dict) + if new_dict.__contains__('tasks') and new_dict.__contains__('data'): + tasks = new_dict['tasks'] + for i in range(len(tasks)): + if tasks[i]["type"] == "AI_EOTask": + tasks[i] = AI_EOTask.from_dict(tasks[i]) + else: + tasks[i] = AI_Task.from_dict(tasks[i]) + new_dict['tasks'] = tasks + + data = new_dict['data'] + for i in range(len(data)): + if data[i]["type"] == "AI_EOTrainingData": + data[i] = AI_EOTrainingData.from_dict(data[i]) + else: + data[i] = AI_TrainingData.from_dict(data[i]) + new_dict['data'] = data + else: + print("Parameter \"tasks\" and \"data\" must be provided.") + exit() + return TrainingDataset(**new_dict) diff --git a/pytdml/type/extended_types.py b/pytdml/type/extended_types.py index 78f57d5..10f7eeb 100644 --- a/pytdml/type/extended_types.py +++ b/pytdml/type/extended_types.py @@ -31,99 +31,177 @@ # ------------------------------------------------------------------------------ import json - +import copy +import geojson from geojson import Feature from typing import List, Union, Optional, Literal from pydantic import Field, field_validator -from pytdml.type._utils import _validate_image_format, _validate_date -from pytdml.type.basic_types import Label, TrainingDataset, TrainingData, Task, MD_Band, Extent, BoundingBox +from pytdml.type._utils import _validate_date, _validate_image_format, to_interior_class, list_to_interior_class +from pytdml.type.basic_types import AI_Label, TrainingDataset, AI_TrainingData, MD_Band, EX_Extent, CI_Citation, AI_Labeling, DataQuality, AI_Task, NamedValue, MD_Scope, AI_MetricsInLiterature, AI_TDChangeset -class PixelLabel(Label): +class AI_PixelLabel(AI_Label): """ Extended label type for pixel level training data """ + type: Literal["AI_PixelLabel"] - image_URL: List[str] = Field(min_items=1) - image_format: List[str] = Field(min_items=1) + image_URL: List[str] = Field(min_length=1) + image_format: List[str] = Field(min_length=1) @field_validator("image_format") def validate_image_format(cls, v): valid_format = [] for item in v: - valid_format.append(_validate_image_format(item)) + if _validate_image_format(item): + valid_format.append(item) return valid_format + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return AI_PixelLabel(**new_dict) + -class ObjectLabel(Label): +class AI_ObjectLabel(AI_Label): """ Extended label type for object level training data """ + type: Literal["AI_ObjectLabel"] object: Feature label_class: str = Field(alias="class") - date_time: Optional[str] - bbox_type: Optional[str] + date_time: Optional[str] = None + bbox_type: Optional[str] = None @field_validator("date_time") def validate_date_time(cls, v): return _validate_date(v) + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + if new_dict.__contains__('object'): + new_dict["object"] = geojson.loads(json.dumps(json_dict["object"])) + else: + print("Parameter \"object\" must be provided.") + exit() + return AI_ObjectLabel(**new_dict) -class SceneLabel(Label): + +class AI_SceneLabel(AI_Label): """ Extended label type for scene level training data """ + type: Literal["AI_SceneLabel"] label_class: str = Field(alias="class") + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) -class EOTask(Task): + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return AI_SceneLabel(**new_dict) + + +class AI_EOTask(AI_Task): """ Extended task type for EO training data """ type: Literal["AI_EOTask"] - taskType: str + task_type: str + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return AI_EOTask(**new_dict) -class EOTrainingData(TrainingData): +class AI_EOTrainingData(AI_TrainingData): """ Extended training data type for EO training data """ + type: Literal["AI_EOTrainingData"] - dataURL: List[str] = Field(min_items=1) # That one should be uri-format + data_URL: List[str] = Field(min_length=1) # That one should be uri-format + labels: List[Union[AI_Label, AI_PixelLabel, AI_ObjectLabel, AI_SceneLabel]] - extent: Optional[Union[Extent, BoundingBox]] - date_time: Optional[List[str]] = [] + extent: Optional[Union[EX_Extent, List[Union[int, float]]]] = None + data_time: Optional[List[str]] = None - @field_validator("date_time") + @field_validator("data_time") def validate_data_time(cls, v): - validated_data = [] + validated_date = [] for item in v: - validated_data.append(_validate_date(item)) - return validated_data + validated_date.append(_validate_date(item)) + return validated_date + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + labels = new_dict['labels'] + for i in range(len(labels)): + if labels[i]["type"] == "AI_AbstractLabel": + labels[i] = AI_Label.from_dict(labels[i]) + elif labels[i]["type"] == "AI_PixelLabel": + labels[i] = AI_PixelLabel.from_dict(labels[i]) + elif labels[i]["type"] == "AI_ObjectLabel": + labels[i] = AI_ObjectLabel.from_dict(labels[i]) + else: + labels[i] = AI_SceneLabel.from_dict(labels[i]) + new_dict['labels'] = labels + if new_dict.__contains__('dataSources'): + list_to_interior_class(new_dict, "dataSources", CI_Citation) + if new_dict.__contains__('labeling'): + list_to_interior_class(new_dict, "labeling", AI_Labeling) + if new_dict.__contains__('quality'): + list_to_interior_class(new_dict, "quality", DataQuality) + + if new_dict.__contains__('extent'): + extent = new_dict['extent'] + for i in range(len(extent)): + if EX_Extent.can_build_from_data(extent[i]): + extent[i] = EX_Extent.from_dict(extent[i]) + else: + pass + new_dict['extent'] = extent + return AI_EOTrainingData(**new_dict) class EOTrainingDataset(TrainingDataset): """ Extended training dataset type for EO training dataset """ + type: Literal["AI_EOTrainingDataset"] + tasks: List[AI_EOTask] = Field(min_length=1) + data: List[AI_EOTrainingData] = Field(min_length=1) # For Convinience, we allow the user to specify the bands by name - bands: Optional[List[Union[str, MD_Band]]] = [] - extent: Optional[Extent] - imageSize: Optional[str] = "" - tasks: List[EOTask] = Field(min_items=1) - data: List[EOTrainingData] = Field(min_items=1) - + bands: Optional[List[MD_Band]] = None + extent: Optional[EX_Extent] = None + image_size: Optional[str] = None -if __name__ == "__main__": - tdml_path = r"C:\Users\zhaoyan\Desktop\TrainingDML-AI-master-use-cases-examples\use-cases\examples\1.0\WHU-building.json" - with open(tdml_path, 'r') as f: - data = json.load(f) + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) - td = EOTrainingDataset(**data).dict(by_alias=True,exclude_none=True) - print(td['data']) + @staticmethod + def from_dict(json_dict): + new_dict = copy.deepcopy(json_dict) + return EOTrainingDataset(**new_dict) diff --git a/pytdml/type/UiT_HCD_California_2017.json b/tests/data/UiT_HCD_California_2017.json similarity index 100% rename from pytdml/type/UiT_HCD_California_2017.json rename to tests/data/UiT_HCD_California_2017.json diff --git a/pytdml/type/UiT_HCD_California_2017.yml b/tests/data/UiT_HCD_California_2017.yml similarity index 100% rename from pytdml/type/UiT_HCD_California_2017.yml rename to tests/data/UiT_HCD_California_2017.yml