diff --git a/caom2/caom2/__init__.py b/caom2/caom2/__init__.py
index 5d459811..440cff43 100644
--- a/caom2/caom2/__init__.py
+++ b/caom2/caom2/__init__.py
@@ -86,6 +86,7 @@
from .checksum import * # noqa
from .chunk import * # noqa
from .common import * # noqa
+from .dali import * # noqa
from .diff import * # noqa
from .obs_reader_writer import * # noqa
from .observation import * # noqa
diff --git a/caom2/caom2/artifact.py b/caom2/caom2/artifact.py
index b09ea22e..d3edac54 100644
--- a/caom2/caom2/artifact.py
+++ b/caom2/caom2/artifact.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -74,10 +74,10 @@
from urllib.parse import urlparse
from . import caom_util
-from .chunk import ProductType
-from .common import AbstractCaomEntity
-from .common import ChecksumURI, OrderedEnum
+from .common import AbstractCaomEntity, CaomObject, compute_bucket
+from .common import OrderedEnum
from .part import Part
+from .chunk import DataLinkSemantics
from datetime import datetime
@@ -95,8 +95,30 @@ class ReleaseType(OrderedEnum):
META = "meta"
+class ArtifactDescription(CaomObject):
+ """ Short description with a URI reference for details"""
+
+ def __init__(self, uri, description):
+ super().__init__()
+ try:
+ urlparse(uri)
+ except ValueError:
+ raise TypeError('Expected any IVOA URI for ArtifactDescription.uri, '
+ 'received {}'.format(uri))
+ self._uri = uri
+ self._description = description
+
+ @property
+ def uri(self):
+ return self._uri
+
+ @property
+ def description(self):
+ return self._description
+
+
class Artifact(AbstractCaomEntity):
- """Contains the meta data assocaited with a file.
+ """Contains the metadata associated with a file.
- location of the file (uri)
- the http content-type
@@ -104,8 +126,8 @@ class Artifact(AbstractCaomEntity):
As well as a pointer (parts) to content of the file.
- eg: Artifact('ad:CFHT/1234567o')
- where 'ad:CFHT/1234567o' is a uri that refernce the file...
+ eg: Artifact('cadc:CFHT/1234567o')
+ where 'cadc:CFHT/1234567o' is an uri that reference the file...
"""
@@ -118,17 +140,23 @@ def __init__(self,
content_checksum=None,
content_release=None,
content_read_groups=None,
- parts=None
+ parts=None,
+ description_id=None
):
"""
- Initialize a Artifact instance.
+ Initialize an Artifact instance.
Arguments: uri of the artifact. eg:
vos://cadc.nrc.ca!vospace/APASS/apass_north/proc/100605/n100605.fz
ad:CFHT/123456p
"""
super(Artifact, self).__init__()
- self.uri = uri
+ try:
+ urlparse(uri)
+ except ValueError:
+ raise TypeError('Expected URI for Artifact.uri, received {}'.format(uri))
+ self._uri = uri
+ self._uri_bucket = compute_bucket(uri)
self.product_type = product_type
self.release_type = release_type
self.content_type = content_type
@@ -139,6 +167,7 @@ def __init__(self,
if parts is None:
parts = caom_util.TypedOrderedDict(Part, )
self.parts = parts
+ self.description_id = description_id
def _key(self):
return self.uri
@@ -160,15 +189,9 @@ def uri(self):
"""
return self._uri
- @uri.setter
- def uri(self, value):
- caom_util.type_check(value, str, 'uri')
- uri = urlparse(value)
- if not uri.scheme:
- raise ValueError('URI without scheme: {}'.format(value))
- uri_str = uri.geturl()
- caom_util.value_check(value, None, None, 'uri', override=uri_str)
- self._uri = uri_str
+ @property
+ def uri_bucket(self):
+ return self._uri_bucket
@property
def product_type(self):
@@ -183,7 +206,7 @@ def product_type(self):
@product_type.setter
def product_type(self, value):
- caom_util.type_check(value, ProductType, "product_type", False)
+ caom_util.type_check(value, DataLinkSemantics, "product_type", False)
self._product_type = value
@property
@@ -235,7 +258,7 @@ def content_length(self, value):
def content_checksum(self):
"""the checksum value for the artifact data
- type: ChecksumURI
+ type: uri
"""
return self._content_checksum
@@ -245,7 +268,7 @@ def content_checksum(self, value):
if value is None:
self._content_checksum = None
else:
- caom_util.type_check(value, ChecksumURI, "checksum_uri", False)
+ caom_util.type_check(value, str, "checksum_uri", False)
self._content_checksum = value
@property
@@ -306,3 +329,13 @@ def parts(self, value):
caom_util.type_check(value, caom_util.TypedOrderedDict, 'parts',
override=False)
self._parts = value
+
+ @property
+ def description_id(self):
+ return self._description_id
+
+ @description_id.setter
+ def description_id(self, value):
+ if value is not None:
+ caom_util.type_check(value, str, 'description_id')
+ self._description_id = value
diff --git a/caom2/caom2/caom_util.py b/caom2/caom2/caom_util.py
index 6595cf76..e70af79f 100644
--- a/caom2/caom2/caom_util.py
+++ b/caom2/caom2/caom_util.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2016. (c) 2016.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -74,7 +74,6 @@
the first point of use could be implemented. This helps the data
engineer get the correct meta data more quickly.
"""
-
import sys
import collections
from datetime import datetime
@@ -84,7 +83,7 @@
__all__ = ['TypedList', 'TypedSet', 'TypedOrderedDict', 'ClassProperty',
- 'URISet']
+ 'URISet', 'validate_uri']
# TODO both these are very bad, implement more sensibly
IVOA_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
@@ -235,8 +234,8 @@ def __repr__(self):
def check(self, v):
if not isinstance(v, self._oktypes):
- raise TypeError("Wrong type in list. OK Types: {0}".
- format(self._oktypes))
+ raise TypeError("Wrong type ({0}) in list. OK Types: {1}".
+ format(type(v), self._oktypes))
def __len__(self):
return len(self.list)
@@ -328,6 +327,22 @@ def __contains__(self, item):
return False
+def validate_uri(uri, scheme=None):
+ """
+ Validates a URI. If a scheme is provided, the URI must have that scheme.
+ :param uri:
+ :param scheme:
+ :raise TypeError: when uri not valid
+ """
+ if uri:
+ tmp = urlsplit(uri)
+ if scheme and scheme != tmp.scheme:
+ raise TypeError("Invalid URI scheme: {}".format(uri))
+ if tmp.geturl() == uri:
+ return
+ raise TypeError("Invalid URI: " + uri)
+
+
class URISet(TypedSet):
"""
Class that customizes a TypedSet to check for URIs
@@ -350,13 +365,7 @@ def check(self, v):
:param v: value to check
:return:
"""
- if v:
- tmp = urlsplit(v)
- if self.scheme and tmp.scheme != self.scheme:
- raise TypeError("Invalid URI scheme: {}".format(v))
- if tmp.geturl() == v:
- return
- raise TypeError("Invalid URI: " + v)
+ validate_uri(v, self.scheme)
class TypedOrderedDict(collections.OrderedDict):
diff --git a/caom2/caom2/checksum.py b/caom2/caom2/checksum.py
index c858bb82..0409d272 100644
--- a/caom2/caom2/checksum.py
+++ b/caom2/caom2/checksum.py
@@ -78,9 +78,11 @@
from builtins import bytes, int, str
from caom2.caom_util import TypedSet, TypedList, TypedOrderedDict, int_32
-from caom2.common import CaomObject, AbstractCaomEntity, ObservationURI
-from caom2.common import ChecksumURI
+from caom2.common import CaomObject, AbstractCaomEntity
from caom2.observation import Observation
+from .obs_reader_writer import CAOM25_NAMESPACE, CAOM24_NAMESPACE, \
+ CAOM23_NAMESPACE
+
with warnings.catch_warnings():
warnings.simplefilter('ignore')
from aenum import Enum
@@ -147,7 +149,7 @@ def get_meta_checksum(entity):
raise AttributeError('AbstractCaomEntity expected')
md5 = hashlib.md5()
update_caom_checksum(md5, entity)
- return ChecksumURI('md5:{}'.format(md5.hexdigest()))
+ return 'md5:{}'.format(md5.hexdigest())
def get_acc_meta_checksum(entity, no_logging=False):
@@ -168,7 +170,7 @@ def get_acc_meta_checksum(entity, no_logging=False):
update_acc_checksum(md5, entity)
if no_logging:
logger.setLevel(log_level)
- return ChecksumURI('md5:{}'.format(md5.hexdigest()))
+ return 'md5:{}'.format(md5.hexdigest())
def update_meta_checksum(obs):
@@ -261,11 +263,9 @@ def update_checksum(checksum, value, attribute=''):
b = None
- if isinstance(value, ObservationURI) or isinstance(value, ChecksumURI):
- b = value.uri.encode('utf-8')
- elif isinstance(value, CaomObject):
+ if isinstance(value, CaomObject):
logger.debug('Process object {}'.format(attribute))
- update_caom_checksum(checksum, value, attribute)
+ return update_caom_checksum(checksum, value, attribute)
elif isinstance(value, bytes):
b = value
elif isinstance(value, bool):
@@ -281,18 +281,22 @@ def update_checksum(checksum, value, attribute=''):
b = value.strip().encode('utf-8')
elif isinstance(value, datetime):
b = struct.pack('!q', int(
- (value - datetime(1970, 1, 1)).total_seconds()))
+ (value - datetime(1970, 1, 1)).total_seconds()*1000))
elif isinstance(value, set) or \
(isinstance(value, TypedSet) and not
isinstance(value.key_type, AbstractCaomEntity)):
+ updated = False
for i in sorted(value):
- update_checksum(checksum, i, attribute)
+ updated |= update_checksum(checksum, i, attribute)
+ return updated
elif isinstance(value, list) or isinstance(value, TypedList):
+ updated = False
for i in value:
if not isinstance(i, AbstractCaomEntity):
- update_checksum(checksum, i, attribute)
+ updated |= update_checksum(checksum, i, attribute)
+ return updated
elif isinstance(value, Enum):
- update_checksum(checksum, value.value, attribute)
+ return update_checksum(checksum, value.value, attribute)
elif isinstance(value, uuid.UUID):
b = value.bytes
elif isinstance(value, TypedOrderedDict):
@@ -300,11 +304,13 @@ def update_checksum(checksum, value, attribute=''):
# alphabetical order of their ids
# Note: ignore dictionaries of AbstractCaomEntity types
checksums = []
+ updated = False
for i in value:
if not isinstance(value[i], AbstractCaomEntity):
checksums.append(value[i]._id)
for i in sorted(checksums):
- update_checksum(checksum, checksum[i], attribute)
+ updated &= update_checksum(checksum, checksum[i], attribute)
+ return updated
else:
raise ValueError(
'Cannot transform in bytes: {}({})'.format(value, type(value)))
@@ -312,11 +318,22 @@ def update_checksum(checksum, value, attribute=''):
if b is not None:
checksum.update(b)
if logger.isEnabledFor(logging.DEBUG):
- md5 = hashlib.md5()
- md5.update(b)
- logger.debug('Encoded attribute ({}) {} = {} -- {}'.
- format(type(value), attribute,
- value, md5.hexdigest()))
+ logger.debug("Encoded attribute value - {} = {} {} bytes".format(attribute, value, len(b)))
+ return True
+ return False
+
+
+def to_model_name(attribute):
+ """
+ Converts the attribute name to the corresponding model name
+ :param attribute: name of attribute
+ :return: camel case name of the attribute in the model
+ """
+ # Replace underscores and capitalize the first letter of each word
+ components = attribute.split('_')
+ # The first component should remain lowercase
+ return components[0] + ''.join(
+ word.capitalize() for word in components[1:])
def update_caom_checksum(checksum, entity, parent=None):
@@ -331,10 +348,16 @@ def update_caom_checksum(checksum, entity, parent=None):
if not isinstance(entity, CaomObject):
raise AttributeError('CaomObject expected')
# get the id first
+ updated = False
if isinstance(entity, AbstractCaomEntity):
- update_checksum(checksum, entity._id)
+ update_checksum(checksum, entity._id, "Entity.id")
if entity._meta_producer:
- update_checksum(checksum, entity._meta_producer)
+ if update_checksum(checksum, entity._meta_producer, "Entity.metaProducer"):
+ updated = True
+ model_name = "Entity.metaProducer"
+ checksum.update(model_name.encode('utf-8'))
+ logger.debug('Encoded attribute name {} = {}'.
+ format('_meta_producer', model_name))
# determine the excluded fields if necessary
checksum_excluded_fields = []
@@ -349,9 +372,18 @@ def update_caom_checksum(checksum, entity, parent=None):
for i in sorted(dir(entity)):
if not callable(getattr(entity, i)) and not i.startswith('_') and \
i not in checksum_excluded_fields:
- if getattr(entity, i) is not None:
+ val = getattr(entity, i)
+ if val is not None:
atrib = '{}.{}'.format(parent, i) if parent is not None else i
- update_checksum(checksum, getattr(entity, i), atrib)
+ if update_checksum(checksum, val, atrib):
+ updated = True
+ type_name = type(entity).__name__
+ if type_name in ['DerivedObservation', 'SimpleObservation'] and to_model_name(i) != 'members':
+ type_name = 'Observation'
+ model_name = (type_name + "." + to_model_name(i)).lower()
+ checksum.update(model_name.encode('utf-8'))
+ logger.debug('Encoded attribute name - {} = {}'.format(atrib, model_name))
+ return updated
def checksum_diff():
@@ -399,8 +431,13 @@ def checksum_diff():
mistmatches += _print_diff(plane[0], plane[1])
mistmatches += _print_diff(orig, actual)
+ ns = CAOM25_NAMESPACE
+ if reader.version == 24:
+ ns = CAOM24_NAMESPACE
+ if reader.version == 23:
+ ns = CAOM23_NAMESPACE
if args.output:
- writer = obs_reader_writer.ObservationWriter(validate=True)
+ writer = obs_reader_writer.ObservationWriter(validate=True, namespace=ns)
writer.write(actual, args.output)
print("Total: {} mistmatches".format(mistmatches))
@@ -413,23 +450,23 @@ def _print_diff(orig, actual):
mistmatches = 0
if orig.meta_checksum == actual.meta_checksum:
print('{}: {} {} == {}'.format(elem_type, orig._id,
- orig.meta_checksum.checksum,
- actual.meta_checksum.checksum))
+ orig.meta_checksum,
+ actual.meta_checksum))
else:
print('{}: {} {} != {} [MISMATCH]'.
- format(elem_type, orig._id, orig.meta_checksum.checksum,
- actual.meta_checksum.checksum))
+ format(elem_type, orig._id, orig.meta_checksum,
+ actual.meta_checksum))
mistmatches += 1
if elem_type != 'chunk':
- # do the accummulated checksums
+ # do the accumulated checksums
if orig.acc_meta_checksum == actual.acc_meta_checksum:
print('{}: {} {} == {}'.
- format(elem_type, orig._id, orig.acc_meta_checksum.checksum,
- actual.acc_meta_checksum.checksum))
+ format(elem_type, orig._id, orig.acc_meta_checksum,
+ actual.acc_meta_checksum))
else:
print('{}: {} {} != {} [MISMATCH]'.
- format(elem_type, orig._id, orig.acc_meta_checksum.checksum,
- actual.acc_meta_checksum.checksum))
+ format(elem_type, orig._id, orig.acc_meta_checksum,
+ actual.acc_meta_checksum))
mistmatches += 1
return mistmatches
diff --git a/caom2/caom2/chunk.py b/caom2/caom2/chunk.py
index 43c67360..67f16709 100644
--- a/caom2/caom2/chunk.py
+++ b/caom2/caom2/chunk.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2024. (c) 2024.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -76,14 +76,19 @@
from caom2.caom_util import int_32
from . import caom_util
from . import wcs
-from .common import AbstractCaomEntity
-from .common import CaomObject, OrderedEnum
+from .common import AbstractCaomEntity, OrderedEnum, VocabularyTerm, \
+ _DATA_LINK_VOCAB_NS, _CAOM_PRODUCT_TYPE_NS
+from .common import CaomObject
-class ProductType(OrderedEnum):
+__all__ = ['Chunk', 'ObservableAxis', 'SpatialWCS', 'DataLinkSemantics',
+ 'SpectralWCS', 'TemporalWCS', 'PolarizationWCS', 'CustomWCS']
+
+
+class DataLinkSemantics(OrderedEnum):
"""
Subset of IVOA DataLink terms at:
- https://www.ivoa.net/rdf/datalink/core/2022-01-27/datalink.html
+ https://www.ivoa.net/rdf/datalink/core/
THIS = "this"
AUXILIARY = "auxiliary"
@@ -102,23 +107,22 @@ class ProductType(OrderedEnum):
WEIGHT = 'weight'
"""
-
- THIS = "this"
-
- AUXILIARY = "auxiliary"
- BIAS = 'bias'
- CALIBRATION = 'calibration'
- CODERIVED = 'coderived'
- DARK = 'dark'
- DOCUMENTATION = 'documentation'
- ERROR = 'error'
- FLAT = 'flat'
- NOISE = 'noise'
- PREVIEW = 'preview'
- PREVIEW_IMAGE = 'preview-image'
- PREVIEW_PLOT = 'preview-plot'
- THUMBNAIL = 'thumbnail'
- WEIGHT = 'weight'
+ THIS = VocabularyTerm(_DATA_LINK_VOCAB_NS, "this", True).get_value()
+
+ AUXILIARY = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'auxiliary', True).get_value()
+ BIAS = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'bias', True).get_value()
+ CALIBRATION = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'calibration', True).get_value()
+ CODERIVED = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'coderived', True).get_value()
+ DARK = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'dark', True).get_value()
+ DOCUMENTATION = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'documentation', True).get_value()
+ ERROR = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'error', True).get_value()
+ FLAT = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'flat', True).get_value()
+ NOISE = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'noise', True).get_value()
+ PREVIEW = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'preview', True).get_value()
+ PREVIEW_IMAGE = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'preview-image', True).get_value()
+ PREVIEW_PLOT = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'preview-plot', True).get_value()
+ THUMBNAIL = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'thumbnail', True).get_value()
+ WEIGHT = VocabularyTerm(_DATA_LINK_VOCAB_NS, 'weight', True).get_value()
# DataLink terms explicitly not included
# counterpart
@@ -130,17 +134,13 @@ class ProductType(OrderedEnum):
# progenitor
# CAOM specific terms public
- SCIENCE = 'science' # this
+ SCIENCE = VocabularyTerm(_CAOM_PRODUCT_TYPE_NS, 'science', True).get_value() # this
# deprecated
# INFO = 'info'
# CATALOG = 'catalog'
-__all__ = ['ProductType', 'Chunk', 'ObservableAxis', 'SpatialWCS',
- 'SpectralWCS', 'TemporalWCS', 'PolarizationWCS', 'CustomWCS']
-
-
class Chunk(AbstractCaomEntity):
"""A caom2.Chunk object. A chunk is a peice of file part.
@@ -199,10 +199,10 @@ def __init__(self, product_type=None,
def product_type(self):
"""A word that describes the content of the chunk.
- eg. Chunk.product_type = ProductType.SCIENCE
+ eg. Chunk.product_type = DataLinkSemantics.SCIENCE
Allowed values:
- """ + str(list(ProductType)) + """
+ """ + str(list(DataLinkSemantics)) + """
"""
@@ -210,10 +210,10 @@ def product_type(self):
@product_type.setter
def product_type(self, value):
- if isinstance(value, str) and value in ProductType.names():
+ if isinstance(value, str) and value in DataLinkSemantics.names():
# be helpful
- value = ProductType('value')
- caom_util.type_check(value, ProductType, 'product_type')
+ value = DataLinkSemantics('value')
+ caom_util.type_check(value, DataLinkSemantics, 'product_type')
self._product_type = value
@property
diff --git a/caom2/caom2/common.py b/caom2/caom2/common.py
index e4174efe..a8f9cd42 100644
--- a/caom2/caom2/common.py
+++ b/caom2/caom2/common.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -65,13 +65,13 @@
#
# ***********************************************************************
#
-
+import hashlib
import inspect
import uuid
from datetime import datetime
from builtins import int, str
-from urllib.parse import SplitResult, urlparse, urlsplit
+from urllib.parse import urlparse, urlsplit
import logging
from . import caom_util
@@ -81,14 +81,18 @@
from aenum import Enum
-__all__ = ['CaomObject', 'AbstractCaomEntity', 'ObservationURI', 'ChecksumURI',
- 'VocabularyTerm']
-
-_OBSCORE_VOCAB_NS = "http://www.ivoa.net/std/ObsCore"
-_CAOM_VOCAB_NS = "http://www.opencadc.org/caom2/DataProductType"
+__all__ = ['CaomObject', 'AbstractCaomEntity', 'VocabularyTerm', 'compute_bucket']
logger = logging.getLogger('caom2')
+_DATA_LINK_VOCAB_NS = 'https://www.ivoa.net/rdf/datalink/core'
+# CAOM2 vocabularies
+_CAOM_DATA_PRODUCT_TYPE_NS = "http://www.opencadc.org/caom2/DataProductType"
+_CAOM_PRODUCT_TYPE_NS = "http://www.opencadc.org/caom2/ProductType"
+_CAOM_QUALITY_NS = "http://www.opencadc.org/caom2/Quality"
+_CAOM_STATUS_NS = "http://www.opencadc.org/caom2/Status"
+_CAOM_TARGET_TYPE_NS = "http://www.opencadc.org/caom2/TargetType"
+
def get_current_ivoa_time():
"""Generate a datetime with 3 digit microsecond precision.
@@ -101,6 +105,17 @@ def get_current_ivoa_time():
now.second, int(str(now.microsecond)[:-3] + '000'))
+def compute_bucket(uri):
+ """
+ Compute a bucket name from a URI as the first 3 characters of the MD5 hash
+ :param uri: uri to compute bucket for
+ :return: bucket name
+ """
+ md5 = hashlib.sha1()
+ md5.update(uri.encode('utf-8'))
+ return md5.hexdigest()[:3]
+
+
class OrderedEnum(Enum):
"""
Enums are in the order of their definition.
@@ -150,7 +165,7 @@ def __str__(self):
for arg in args])
def __eq__(self, other):
- if type(other) == type(self):
+ if type(other) is type(self):
return self.__dict__ == other.__dict__
else:
return False
@@ -231,7 +246,7 @@ def max_last_modified(self, value):
def meta_checksum(self):
"""the meta checksum value
- type: ChecksumURI
+ type: URI
"""
return self._meta_checksum
@@ -241,14 +256,14 @@ def meta_checksum(self, value):
if value is None:
self._meta_checksum = None
else:
- caom_util.type_check(value, ChecksumURI, "meta_checksum", False)
+ caom_util.type_check(value, str, "meta_checksum", False)
self._meta_checksum = value
@property
def acc_meta_checksum(self):
"""the accumulated meta checksum value
- type: ChecksumURI
+ type: URI
"""
return self._acc_meta_checksum
@@ -258,7 +273,7 @@ def acc_meta_checksum(self, value):
if value is None:
self._acc_meta_checksum = None
else:
- caom_util.type_check(value, ChecksumURI, "acc_meta_checksum",
+ caom_util.type_check(value, str, "acc_meta_checksum",
False)
self._acc_meta_checksum = value
@@ -284,7 +299,7 @@ def meta_producer(self, value):
self._meta_producer = value
-class VocabularyTerm(object):
+class VocabularyTerm(CaomObject):
""" VocabularyTerm """
def __init__(self, namespace, term, base=False):
@@ -351,173 +366,173 @@ def base(self, value):
caom_util.type_check(value, bool, "base")
self._base = value
-
-class ObservationURI(CaomObject):
- """ Observation URI """
-
- _SCHEME = str("caom")
-
- def __init__(self, uri):
- """
- Initializes an Observation instance
-
- Arguments:
- uri : URI corresponding to observation
- """
- super(CaomObject, self).__init__()
- tmp = urlparse(uri)
-
- if tmp.scheme != ObservationURI._SCHEME:
- raise ValueError(
- "uri must be have scheme of {}. received: {}"
- .format(ObservationURI._SCHEME, uri))
- if tmp.geturl() != uri:
- raise ValueError(
- "uri parsing failure. received: {}".format(uri))
-
- self._uri = tmp.geturl()
- (collection, observation_id) = tmp.path.split("/")
- if collection is None:
- raise ValueError(
- "uri did not contain a collection part. received: {}"
- .format(uri))
- caom_util.validate_path_component(self, "collection", collection)
- if observation_id is None:
- raise ValueError(
- "uri did not contain an observation_id part. received: {}"
- .format(uri))
- caom_util.validate_path_component(self, "observation_id",
- observation_id)
- (self._collection, self._observation_id) = (collection, observation_id)
- self._print_attributes = ['uri', 'collection', 'observation_id']
-
- def _key(self):
- return self.uri
-
- def __hash__(self):
- return hash(self._key())
-
- def __lt__(self, other):
- if not isinstance(other, ObservationURI):
- raise ValueError(
- 'Cannot compare ObservationURI with {}'.format(type(other)))
- return self.uri < other.uri
-
- def __eq__(self, other):
- if not isinstance(other, ObservationURI):
- raise ValueError(
- 'Cannot compare ObservationURI with {}'.format(type(other)))
- return self.uri == other.uri
-
- @classmethod
- def get_observation_uri(cls, collection, observation_id):
- """
- Initializes an Observation URI instance
-
- Arguments:
- collection : collection
- observation_id : ID of the observation
- """
-
- caom_util.type_check(collection, str, "collection", override=False)
- caom_util.type_check(observation_id, str, "observation_id",
- override=False)
-
- caom_util.validate_path_component(cls, "collection", collection)
- caom_util.validate_path_component(cls, "observation_id",
- observation_id)
-
- uri = SplitResult(ObservationURI._SCHEME, "",
- collection + "/" + observation_id,
- "", "").geturl()
- return cls(uri)
-
- # Properties
-
- @property
- @classmethod
- def scheme(cls):
- """The scheme defines where this Observation can be looked up.
-
- Only 'caom' is currently supported."""
- return cls._SCHEME
-
- @property
- def uri(self):
- """The uri that the caom service can use to find the observation"""
- return self._uri
-
- @property
- def collection(self):
- """The collection part of this Observations uri"""
- return self._collection
-
- @property
- def observation_id(self):
- """The observation_id of this Observations uri"""
- return self._observation_id
-
-
-class ChecksumURI(CaomObject):
- """ Checksum URI """
-
- def __init__(self, uri):
- """
- Initializes an Checksum URI instance
-
- Arguments:
- uri : Checksum URI in the format Algorithm:ChecksumValue
- """
- super(CaomObject, self).__init__()
- # note: urlparse does not recognize scheme in uri of form scheme:val
- tmp = uri.split(':', 1)
-
- # TODO change this raise a ValueError when the rule is being enforced
- if len(tmp) < 2:
- logger.warning(("A checksum scheme noting the algorithm is "
- "required.. received: {}").format(uri))
- algorithm = None
- checksum = tmp[0]
- else:
- algorithm = tmp[0]
- checksum = tmp[1]
-
- if checksum is None:
- raise ValueError(
- "checksum uri did not contain an checksum part. received: {}"
- .format(uri))
- caom_util.validate_path_component(self, "checksum", checksum)
-
- (self._uri, self._algorithm, self._checksum) = (
- uri, algorithm, checksum)
- self._print_attributes = ['uri', 'algorithm', 'checksum']
-
- def _key(self):
- return self.uri
-
- def __eq__(self, y):
- if isinstance(y, ChecksumURI):
- return self._key() == y._key()
- return False
-
- def __hash__(self):
- return hash(self._key())
-
- def get_bytes(self):
- return bytearray.fromhex(self._checksum)
-
- # Properties
- @property
- def uri(self):
- """The uri that the caom service can use to find the observation"""
- return self._uri
-
- @property
- def algorithm(self):
- """The checksum algorithm"""
- return self._algorithm
-
- @property
- def checksum(self):
- """The checksum value"""
- return self._checksum
+#
+# class ObservationURI(CaomObject):
+# """ Observation URI """
+#
+# _SCHEME = str("caom")
+#
+# def __init__(self, uri):
+# """
+# Initializes an Observation instance
+#
+# Arguments:
+# uri : URI corresponding to observation
+# """
+# super(CaomObject, self).__init__()
+# tmp = urlparse(uri)
+#
+# if tmp.scheme != ObservationURI._SCHEME:
+# raise ValueError(
+# "uri must be have scheme of {}. received: {}"
+# .format(ObservationURI._SCHEME, uri))
+# if tmp.geturl() != uri:
+# raise ValueError(
+# "uri parsing failure. received: {}".format(uri))
+#
+# self._uri = tmp.geturl()
+# (collection, observation_id) = tmp.path.split("/")
+# if collection is None:
+# raise ValueError(
+# "uri did not contain a collection part. received: {}"
+# .format(uri))
+# caom_util.validate_path_component(self, "collection", collection)
+# if observation_id is None:
+# raise ValueError(
+# "uri did not contain an observation_id part. received: {}"
+# .format(uri))
+# caom_util.validate_path_component(self, "observation_id",
+# observation_id)
+# (self._collection, self._observation_id) = (collection, observation_id)
+# self._print_attributes = ['uri', 'collection', 'observation_id']
+#
+# def _key(self):
+# return self.uri
+#
+# def __hash__(self):
+# return hash(self._key())
+#
+# def __lt__(self, other):
+# if not isinstance(other, ObservationURI):
+# raise ValueError(
+# 'Cannot compare ObservationURI with {}'.format(type(other)))
+# return self.uri < other.uri
+#
+# def __eq__(self, other):
+# if not isinstance(other, ObservationURI):
+# raise ValueError(
+# 'Cannot compare ObservationURI with {}'.format(type(other)))
+# return self.uri == other.uri
+#
+# @classmethod
+# def get_observation_uri(cls, collection, observation_id):
+# """
+# Initializes an Observation URI instance
+#
+# Arguments:
+# collection : collection
+# observation_id : ID of the observation
+# """
+#
+# caom_util.type_check(collection, str, "collection", override=False)
+# caom_util.type_check(observation_id, str, "observation_id",
+# override=False)
+#
+# caom_util.validate_path_component(cls, "collection", collection)
+# caom_util.validate_path_component(cls, "observation_id",
+# observation_id)
+#
+# uri = SplitResult(ObservationURI._SCHEME, "",
+# collection + "/" + observation_id,
+# "", "").geturl()
+# return cls(uri)
+#
+# # Properties
+#
+# @property
+# @classmethod
+# def scheme(cls):
+# """The scheme defines where this Observation can be looked up.
+#
+# Only 'caom' is currently supported."""
+# return cls._SCHEME
+#
+# @property
+# def uri(self):
+# """The uri that the caom service can use to find the observation"""
+# return self._uri
+#
+# @property
+# def collection(self):
+# """The collection part of this Observations uri"""
+# return self._collection
+#
+# @property
+# def observation_id(self):
+# """The observation_id of this Observations uri"""
+# return self._observation_id
+#
+#
+# class ChecksumURI(CaomObject):
+# """ Checksum URI """
+#
+# def __init__(self, uri):
+# """
+# Initializes an Checksum URI instance
+#
+# Arguments:
+# uri : Checksum URI in the format Algorithm:ChecksumValue
+# """
+# super(CaomObject, self).__init__()
+# # note: urlparse does not recognize scheme in uri of form scheme:val
+# tmp = uri.split(':', 1)
+#
+# # TODO change this raise a ValueError when the rule is being enforced
+# if len(tmp) < 2:
+# logger.warning(("A checksum scheme noting the algorithm is "
+# "required.. received: {}").format(uri))
+# algorithm = None
+# checksum = tmp[0]
+# else:
+# algorithm = tmp[0]
+# checksum = tmp[1]
+#
+# if checksum is None:
+# raise ValueError(
+# "checksum uri did not contain an checksum part. received: {}"
+# .format(uri))
+# caom_util.validate_path_component(self, "checksum", checksum)
+#
+# (self._uri, self._algorithm, self._checksum) = (
+# uri, algorithm, checksum)
+# self._print_attributes = ['uri', 'algorithm', 'checksum']
+#
+# def _key(self):
+# return self.uri
+#
+# def __eq__(self, y):
+# if isinstance(y, ChecksumURI):
+# return self._key() == y._key()
+# return False
+#
+# def __hash__(self):
+# return hash(self._key())
+#
+# def get_bytes(self):
+# return bytearray.fromhex(self._checksum)
+#
+# # Properties
+# @property
+# def uri(self):
+# """The uri that the caom service can use to find the observation"""
+# return self._uri
+#
+# @property
+# def algorithm(self):
+# """The checksum algorithm"""
+# return self._algorithm
+#
+# @property
+# def checksum(self):
+# """The checksum value"""
+# return self._checksum
diff --git a/caom2/caom2/dali.py b/caom2/caom2/dali.py
new file mode 100644
index 00000000..667df815
--- /dev/null
+++ b/caom2/caom2/dali.py
@@ -0,0 +1,164 @@
+# ***********************************************************************
+# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
+# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
+#
+# (c) 2025. (c) 2025.
+# Government of Canada Gouvernement du Canada
+# National Research Council Conseil national de recherches
+# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
+# All rights reserved Tous droits réservés
+#
+# NRC disclaims any warranties, Le CNRC dénie toute garantie
+# expressed, implied, or énoncée, implicite ou légale,
+# statutory, of any kind with de quelque nature que ce
+# respect to the software, soit, concernant le logiciel,
+# including without limitation y compris sans restriction
+# any warranty of merchantability toute garantie de valeur
+# or fitness for a particular marchande ou de pertinence
+# purpose. NRC shall not be pour un usage particulier.
+# liable in any event for any Le CNRC ne pourra en aucun cas
+# damages, whether direct or être tenu responsable de tout
+# indirect, special or general, dommage, direct ou indirect,
+# consequential or incidental, particulier ou général,
+# arising from the use of the accessoire ou fortuit, résultant
+# software. Neither the name de l'utilisation du logiciel. Ni
+# of the National Research le nom du Conseil National de
+# Council of Canada nor the Recherches du Canada ni les noms
+# names of its contributors may de ses participants ne peuvent
+# be used to endorse or promote être utilisés pour approuver ou
+# products derived from this promouvoir les produits dérivés
+# software without specific prior de ce logiciel sans autorisation
+# written permission. préalable et particulière
+# par écrit.
+#
+# This file is part of the Ce fichier fait partie du projet
+# OpenCADC project. OpenCADC.
+#
+# OpenCADC is free software: OpenCADC est un logiciel libre ;
+# you can redistribute it and/or vous pouvez le redistribuer ou le
+# modify it under the terms of modifier suivant les termes de
+# the GNU Affero General Public la “GNU Affero General Public
+# License as published by the License” telle que publiée
+# Free Software Foundation, par la Free Software Foundation
+# either version 3 of the : soit la version 3 de cette
+# License, or (at your option) licence, soit (à votre gré)
+# any later version. toute version ultérieure.
+#
+# OpenCADC is distributed in the OpenCADC est distribué
+# hope that it will be useful, dans l’espoir qu’il vous
+# but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE
+# without even the implied GARANTIE : sans même la garantie
+# warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ
+# or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF
+# PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence
+# General Public License for Générale Publique GNU Affero
+# more details. pour plus de détails.
+#
+# You should have received Vous devriez avoir reçu une
+# a copy of the GNU Affero copie de la Licence Générale
+# General Public License along Publique GNU Affero avec
+# with OpenCADC. If not, see OpenCADC ; si ce n’est
+# . pas le cas, consultez :
+# .
+#
+# $Revision: 4 $
+#
+# ***********************************************************************
+#
+
+from . import common
+from . import caom_util
+
+__all__ = ['Interval']
+
+
+class Interval(common.CaomObject):
+ def __init__(self, lower, upper):
+
+ super().__init__()
+ self.lower = lower
+ self.upper = upper
+
+ def get_width(self):
+ return self._upper - self._lower
+
+ @classmethod
+ def intersection(cls, i1, i2):
+ if i1.lower > i2.upper or i1.upper < i2.lower:
+ return None
+
+ lb = max(i1.lower, i2.lower)
+ ub = min(i1.upper, i2.upper)
+ return cls(lb, ub)
+
+ # Properties
+
+ @property
+ def lower(self):
+ """
+ type: float
+ """
+ return self._lower
+
+ @lower.setter
+ def lower(self, value):
+ caom_util.type_check(value, float, 'lower', override=False)
+ has_upper = True
+ try:
+ self._upper
+ except AttributeError:
+ has_upper = False
+ if has_upper and self._upper < value:
+ raise ValueError("Interval: attempt to set upper < lower "
+ "for {}, {}".format(self._upper, value))
+ self._lower = value
+
+ @property
+ def upper(self):
+ """
+ type: float
+ """
+ return self._upper
+
+ @upper.setter
+ def upper(self, value):
+ caom_util.type_check(value, float, 'upper', override=False)
+ has_lower = True
+ try:
+ self._lower
+ except AttributeError:
+ has_lower = False
+ if has_lower and value < self._lower:
+ raise ValueError("Interval: attempt to set upper < lower "
+ "for {}, {}".format(value, self._lower))
+ self._upper = value
+
+ # def validate(self):
+ # """
+ # Performs a validation of the current object.
+ #
+ # An AssertionError is thrown if the object does not represent an
+ # Interval
+ # """
+ # if self._samples is not None:
+ #
+ # if len(self._samples) == 0:
+ # raise ValueError(
+ # 'invalid interval (samples cannot be empty)')
+ #
+ # prev = None
+ # for sample in self._samples:
+ # if sample.lower < self._lower:
+ # raise ValueError(
+ # 'invalid interval: sample extends below lower bound: '
+ # '{} vs {}'.format(sample, self._lower))
+ # if sample.upper > self._upper:
+ # raise ValueError(
+ # 'invalid interval: sample extends above upper bound: '
+ # '{} vs {}'.format(sample, self._upper))
+ # if prev is not None:
+ # if sample.lower <= prev.upper:
+ # raise ValueError(
+ # 'invalid interval: sample overlaps previous '
+ # 'sample:\n{}\nvs\n{}'.format(sample, prev))
+ # prev = sample
diff --git a/caom2/caom2/data/CAOM-2.4.xsd b/caom2/caom2/data/CAOM-2.4.xsd
index 84028aa8..35ba8113 100644
--- a/caom2/caom2/data/CAOM-2.4.xsd
+++ b/caom2/caom2/data/CAOM-2.4.xsd
@@ -446,7 +446,7 @@
-
+
@@ -481,7 +481,7 @@
-
+
@@ -500,7 +500,7 @@
-
+
@@ -541,7 +541,7 @@
-
+
@@ -764,6 +764,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The metaRelease date is expected to be in IVOA date format:
+ yyyy-MM-dd'T'HH:mm:ss.SSS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The metaRelease date is expected to be in IVOA date format:
+ yyyy-MM-dd'T'HH:mm:ss.SSS
+
+
+
+
+
+
+
+ The dataRelease date is expected to be in IVOA date format:
+ yyyy-MM-dd'T'HH:mm:ss.SSS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The lastExecuted date is expected to be in IVOA date format:
+ yyyy-MM-dd'T'HH:mm:ss.SSS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The contentRelease date is expected to be in IVOA date format:
+ yyyy-MM-dd'T'HH:mm:ss.SSS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/caom2/caom2/diff.py b/caom2/caom2/diff.py
index 047baf4e..a4015131 100644
--- a/caom2/caom2/diff.py
+++ b/caom2/caom2/diff.py
@@ -105,7 +105,7 @@ def get_differences(expected, actual, parent=None):
"""
report = []
- if type(expected) != type(actual):
+ if type(expected) is not type(actual):
report.append(
'Types:: expected \'{}\' actual \'{}\''.format(type(expected),
type(actual)))
diff --git a/caom2/caom2/obs_reader_writer.py b/caom2/caom2/obs_reader_writer.py
index ffb38aaa..294ccd65 100644
--- a/caom2/caom2/obs_reader_writer.py
+++ b/caom2/caom2/obs_reader_writer.py
@@ -3,7 +3,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -73,40 +73,44 @@
import os
import uuid
from builtins import str, int
-from urllib.parse import urlparse
+from enum import Enum
from lxml import etree
+import caom2
from . import artifact
from . import caom_util
from . import chunk
+from . import dali
from . import observation
from . import part
from . import plane
from . import shape
from . import wcs
-from . import common
import logging
+from .plane import CalibrationStatus, Ucd, Polarization
+
DATA_PKG = 'data'
CAOM22_SCHEMA_FILE = 'CAOM-2.2.xsd'
CAOM23_SCHEMA_FILE = 'CAOM-2.3.xsd'
CAOM24_SCHEMA_FILE = 'CAOM-2.4.xsd'
+CAOM25_SCHEMA_FILE = 'CAOM-2.5.xsd'
-CAOM22_NAMESPACE = 'vos://cadc.nrc.ca!vospace/CADC/xml/CAOM/v2.2'
CAOM23_NAMESPACE = 'http://www.opencadc.org/caom2/xml/v2.3'
CAOM24_NAMESPACE = 'http://www.opencadc.org/caom2/xml/v2.4'
+CAOM25_NAMESPACE = 'http://www.opencadc.org/caom2/xml/v2.5'
CAOM_VERSION = {
- CAOM22_NAMESPACE: 22,
CAOM23_NAMESPACE: 23,
- CAOM24_NAMESPACE: 24
+ CAOM24_NAMESPACE: 24,
+ CAOM25_NAMESPACE: 25
}
-CAOM22 = "{%s}" % CAOM22_NAMESPACE
CAOM23 = "{%s}" % CAOM23_NAMESPACE
CAOM24 = "{%s}" % CAOM24_NAMESPACE
+CAOM25 = "{%s}" % CAOM25_NAMESPACE
XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance"
XSI = "{%s}" % XSI_NAMESPACE
@@ -119,6 +123,45 @@
logger = logging.getLogger(__name__)
+def _to_samples(vertices):
+ samples = []
+ last_closed_point = None
+ points = []
+ for vertex in vertices:
+ if vertex.type == shape.SegmentType.MOVE:
+ points.append(shape.Point(vertex.cval1, vertex.cval2))
+ elif vertex.type == shape.SegmentType.CLOSE:
+ last_closed_point = shape.Point(vertex.cval1, vertex.cval2)
+ points.append(last_closed_point)
+ samples.append(shape.Polygon(points))
+ points = [] # continue with a new polygon
+ else:
+ if not points:
+ # no move so start from the last closed point
+ points.append(last_closed_point)
+ points.append(shape.Point(vertex.cval1, vertex.cval2))
+ return caom2.MultiShape(samples)
+
+
+def _to_vertices(samples):
+ vertices = []
+ for sample_shape in samples.shapes:
+ if isinstance(sample_shape, shape.Polygon):
+ vertices.append(shape.Vertex(sample_shape.points[0].cval1,
+ sample_shape.points[0].cval2,
+ shape.SegmentType.MOVE))
+ for point in sample_shape.points[1:-1]:
+ vertices.append(shape.Vertex(point.cval1, point.cval2,
+ shape.SegmentType.LINE))
+ vertices.append(shape.Vertex(sample_shape.points[-1].cval1,
+ sample_shape.points[-1].cval2,
+ shape.SegmentType.CLOSE))
+ else:
+ raise ValueError("Only polygons can be converted to Vertices/MultiPolygon")
+
+ return vertices
+
+
class ObservationReader(object):
"""ObservationReader """
@@ -131,27 +174,23 @@ def __init__(self, validate=False):
validate : If True enable schema validation, False otherwise
"""
self._validate = validate
-
if self._validate:
- # caom20_schema_path = pkg_resources.resource_filename(
- # DATA_PKG, CAOM20_SCHEMA_FILE)
- caom22_schema_path = os.path.join(THIS_DIR + '/' + DATA_PKG,
- CAOM22_SCHEMA_FILE)
-
+ caom23_schema_path = os.path.join(THIS_DIR + '/' + DATA_PKG,
+ CAOM23_SCHEMA_FILE)
parser = etree.XMLParser(remove_blank_text=True)
- xsd = etree.parse(caom22_schema_path, parser)
-
- caom23_schema = etree.Element(
- '{http://www.w3.org/2001/XMLSchema}import',
- namespace=CAOM23_NAMESPACE,
- schemaLocation=CAOM23_SCHEMA_FILE)
- xsd.getroot().insert(1, caom23_schema)
+ xsd = etree.parse(caom23_schema_path, parser)
caom24_schema = etree.Element(
'{http://www.w3.org/2001/XMLSchema}import',
namespace=CAOM24_NAMESPACE,
schemaLocation=CAOM24_SCHEMA_FILE)
- xsd.getroot().insert(2, caom24_schema)
+ xsd.getroot().insert(1, caom24_schema)
+
+ caom25_schema = etree.Element(
+ '{http://www.w3.org/2001/XMLSchema}import',
+ namespace=CAOM25_NAMESPACE,
+ schemaLocation=CAOM25_SCHEMA_FILE)
+ xsd.getroot().insert(2, caom25_schema)
self._xmlschema = etree.XMLSchema(xsd)
self.version = None
@@ -173,11 +212,9 @@ def _set_entity_attributes(self, element, ns, caom2_entity):
caom2_entity._max_last_modified = caom_util.str2ivoa(
element_max_last_modified)
if element_meta_checksum:
- caom2_entity._meta_checksum = common.ChecksumURI(
- element_meta_checksum)
+ caom2_entity._meta_checksum = element_meta_checksum
if element_acc_meta_checksum:
- caom2_entity._acc_meta_checksum = common.ChecksumURI(
- element_acc_meta_checksum)
+ caom2_entity._acc_meta_checksum = element_acc_meta_checksum
if element_meta_producer:
caom2_entity._meta_producer = element_meta_producer
@@ -247,18 +284,12 @@ def _add_keywords(self, keywords_list, element, ns, required):
:param ns: name space
:param required: keywords sub-element required or not
"""
- if self.version < 23:
- keywords = self._get_child_text("keywords", element, ns, required)
- if keywords is not None:
- for keyword in keywords.split():
- keywords_list.add(keyword)
- else:
- keywords_element = self._get_child_element("keywords", element, ns,
- required)
- if keywords_element is not None:
- for keyword in keywords_element.iterchildren(
- tag=("{" + ns + "}keyword")):
- keywords_list.add(keyword.text)
+ keywords_element = self._get_child_element("keywords", element, ns,
+ required)
+ if keywords_element is not None:
+ for keyword in keywords_element.iterchildren(
+ tag=("{" + ns + "}keyword")):
+ keywords_list.add(keyword.text)
def _get_algorithm(self, element_tag, parent, ns, required):
"""Build an Algorithm object from an XML representation
@@ -339,10 +370,11 @@ def _get_proposal(self, element_tag, parent, ns, required):
else:
proposal = observation.Proposal(
self._get_child_text("id", el, ns, True))
- proposal.pi_name = self._get_child_text("pi", el, ns, False)
+ proposal.pi = self._get_child_text("pi", el, ns, False)
proposal.project = self._get_child_text("project", el, ns, False)
proposal.title = self._get_child_text("title", el, ns, False)
self._add_keywords(proposal.keywords, el, ns, False)
+ proposal.reference = self._get_child_text("reference", el, ns, False)
return proposal
def _get_target(self, element_tag, parent, ns, required):
@@ -365,7 +397,7 @@ def _get_target(self, element_tag, parent, ns, required):
self._get_child_text("name", el, ns, True))
target_type = self._get_child_text("type", el, ns, False)
if target_type:
- target.target_type = observation.TargetType(target_type)
+ target.type = observation.TargetType(target_type)
target_standard = self._get_child_text("standard", el, ns, False)
if target_standard is not None:
target.standard = ("true" == target_standard)
@@ -450,6 +482,7 @@ def _get_telescope(self, element_tag, parent, ns, required):
telescope.geo_location_z = (
self._get_child_text_as_float("geoLocationZ", el, ns, False))
self._add_keywords(telescope.keywords, el, ns, False)
+ telescope.tracking_mode = self._get_child_text("trackingMode", el, ns, False)
return telescope
def _get_instrument(self, element_tag, parent, ns, required):
@@ -508,9 +541,9 @@ def _get_environment(self, element_tag, parent, ns, required):
return environment
def _add_members(self, members, parent, ns):
- """Create ObservationURI objects from an XML representation of
- ObservationURI elements found in members element, and add them to the
- set of ObservationURI's
+ """Create observation URI objects from an XML representation of
+ observation URI elements found in members element, and add them to the
+ set of observation URI's
Arguments:
members : Set of member's from the parent Observation object
@@ -522,24 +555,33 @@ def _add_members(self, members, parent, ns):
"""
el = self._get_child_element("members", parent, ns, False)
if el is not None:
- for member_element in el.iterchildren(
- "{" + ns + "}observationURI"):
- members.add(observation.ObservationURI(member_element.text))
+ if self.version < 25:
+ for member_element in el.iterchildren(
+ "{" + ns + "}observationURI"):
+ members.add(member_element.text)
+ else:
+ for member_element in el.iterchildren(
+ "{" + ns + "}member"):
+ members.add(member_element.text)
def _add_inputs(self, inputs, parent, ns):
- """Create PlaneURI objects from an XML representation of the planeURI
- elements and add them to the set of PlaneURIs.
+ """Create URI objects from an XML representation of the planeURI
+ elements and add them to the set of plane URIs.
Arguments:
- inputs : set of PlaneURI from the Provenance
- parent : element containing the PlaneURI elements
+ inputs : set of plane URIs from the Provenance
+ parent : element containing the plane uri elements
ns : namespace of the document
raise : ObservationParsingException
"""
el = self._get_child_element("inputs", parent, ns, False)
if el is not None:
- for uri_element in el.iterchildren("{" + ns + "}planeURI"):
- inputs.add(plane.PlaneURI(str(uri_element.text)))
+ if self.version < 25:
+ for uri_element in el.iterchildren("{" + ns + "}planeURI"):
+ inputs.add(str(uri_element.text))
+ else:
+ for uri_element in el.iterchildren("{" + ns + "}input"):
+ inputs.add(str(uri_element.text))
if not inputs:
error = "No planeURI element found in members"
@@ -612,7 +654,7 @@ def _get_metrics(self, element_tag, parent, ns, required):
return metrics
def _get_quality(self, element_tag, parent, ns, required):
- """Build an Quality object from an XML representation
+ """Build a Quality object from an XML representation
Arguments:
elTag : element tag which identifies the element
@@ -631,12 +673,32 @@ def _get_quality(self, element_tag, parent, ns, required):
data_quality = plane.DataQuality(plane.Quality(flag))
return data_quality
+ def _get_observable(self, parent, ns):
+ """Build an Observable object from an XML representation
+
+ Arguments:
+ parent : element containing the Observale element
+ ns : namespace of the document
+ return : a Observable object or None if the document does not contain one
+ raise : ObservationParsingException
+ """
+ el = self._get_child_element("observable", parent, ns, False)
+ if el is None:
+ return None
+ else:
+ ucd = self._get_child_text("ucd", el, ns, True)
+ observable = plane.Observable(Ucd(ucd))
+ calib = self._get_child_text("calibration", el, ns, False)
+ if calib:
+ observable.calibration = CalibrationStatus(calib)
+ return observable
+
def _get_point(self, point, ns, required):
"""Build an Point object from an XML representation
of an Point element.
Arguments:
- point : the point element element
+ point : the point element
ns : namespace of the document
required : indicate whether the element is required
return : an Point object
@@ -1132,7 +1194,7 @@ def _get_spectral_wcs(self, element_tag, parent, ns, required):
Arguments:
elTag : element tag which indentifies the element
- parent : element containing the position element
+ parent : element containing the spectral wcs element
ns : namespace of the document
required : boolean indicating whether the element is required
return : a SpectralWCS object or
@@ -1173,8 +1235,8 @@ def _get_temporal_wcs(self, element_tag, parent, ns, required):
element.
Arguments:
- elTag : element tag which indentifies the element
- parent : element containing the position element
+ elTag : element tag which identifies the element
+ parent : element containing the temporal wcs element
ns : namespace of the document
required : boolean indicating whether the element is required
return : a TemporalWCS object or
@@ -1205,7 +1267,7 @@ def _get_polarization_wcs(self, element_tag, parent, ns, required):
Arguments:
elTag : element tag which indentifies the element
- parent : element containing the position element
+ parent : element containing the polarization element
ns : namespace of the document
required : boolean indicating whether the element is required
return : a PolarizationWCS object or
@@ -1225,7 +1287,7 @@ def _get_custom_wcs(self, element_tag, parent, ns, required):
Arguments:
elTag : element tag which indentifies the element
- parent : element containing the position element
+ parent : element containing the custom axis element
ns : namespace of the document
required : boolean indicating whether the element is required
return : a CustomWCS object or
@@ -1255,17 +1317,23 @@ def _get_position(self, element_tag, parent, ns, required):
el = self._get_child_element(element_tag, parent, ns, required)
if el is None:
return None
- pos = plane.Position()
- pos.bounds = self._get_shape("bounds", el, ns, False)
+ bounds, samples = self._get_shape("bounds", el, ns, False)
+ pos = plane.Position(bounds=bounds, samples=samples)
+ min_bounds = self._get_shape("minBounds", el, ns, False)
+ if min_bounds:
+ # ignore samples returned by get_shape
+ pos.min_bounds = min_bounds[0]
pos.dimension = self._get_dimension2d("dimension", el, ns, False)
+ pos.max_recoverable_scale = self._get_interval("maxRecoverableScale", el, ns, False)
pos.resolution = self._get_child_text_as_float("resolution", el, ns,
False)
pos.resolution_bounds = self._get_interval("resolutionBounds", el,
ns, False)
pos.sample_size = self._get_child_text_as_float("sampleSize", el, ns,
False)
- pos.time_dependent = self._get_child_text_as_boolean("timeDependent",
- el, ns, False)
+ # pos.time_dependent = self._get_child_text_as_boolean("timeDependent",
+ # el, ns, False)
+ pos.calibration = self._get_child_text("calibration", el, ns, False)
return pos
def _get_energy(self, element_tag, parent, ns, required):
@@ -1284,21 +1352,31 @@ def _get_energy(self, element_tag, parent, ns, required):
el = self._get_child_element(element_tag, parent, ns, required)
if el is None:
return None
- energy = plane.Energy()
- energy.bounds = self._get_interval("bounds", el, ns, False)
+
+ bounds = self._get_interval("bounds", el, ns, True)
+ if self.version < 25:
+ samples = self._get_samples(self._get_child_element("bounds", el, ns, True), ns, True)
+ else:
+ samples = self._get_samples(el, ns, True)
+ energy = plane.Energy(bounds, samples)
energy.dimension = \
self._get_child_text_as_int("dimension", el, ns, False)
energy.resolving_power = self._get_child_text_as_float(
"resolvingPower", el, ns, False)
energy.resolving_power_bounds = self._get_interval(
"resolvingPowerBounds", el, ns, False)
+ energy.resolution = self._get_child_text_as_float("resolution", el, ns, False)
+ energy.resolution_bounds = self._get_interval("resolutionBounds", el, ns, False)
energy.sample_size = \
self._get_child_text_as_float("sampleSize", el, ns, False)
energy.bandpass_name = \
self._get_child_text("bandpassName", el, ns, False)
self._add_energy_bands(energy.energy_bands, el, ns)
- energy.restwav = \
- self._get_child_text_as_float("restwav", el, ns, False)
+ if self.version < 25:
+ energy.rest = self._get_child_text_as_float("restwav", el, ns, False)
+ else:
+ energy.rest = \
+ self._get_child_text_as_float("rest", el, ns, False)
_transition_el = \
self._get_child_element("transition", el, ns, required)
if _transition_el is not None:
@@ -1306,6 +1384,7 @@ def _get_energy(self, element_tag, parent, ns, required):
transition = \
self._get_child_text("transition", _transition_el, ns, True)
energy.transition = wcs.EnergyTransition(species, transition)
+ energy.calibration = self._get_child_text("calibration", el, ns, False)
return energy
@@ -1325,8 +1404,12 @@ def _get_time(self, element_tag, parent, ns, required):
el = self._get_child_element(element_tag, parent, ns, required)
if el is None:
return None
- time = plane.Time()
- time.bounds = self._get_interval("bounds", el, ns, False)
+ bounds = self._get_interval("bounds", el, ns, False)
+ if self.version < 25:
+ samples = self._get_samples(self._get_child_element("bounds", el, ns, True), ns, True)
+ else:
+ samples = self._get_samples(el, ns, True)
+ time = plane.Time(bounds, samples)
time.dimension = \
self._get_child_text_as_int("dimension", el, ns, False)
time.resolution = \
@@ -1337,6 +1420,8 @@ def _get_time(self, element_tag, parent, ns, required):
self._get_child_text_as_float("sampleSize", el, ns, False)
time.exposure = \
self._get_child_text_as_float("exposure", el, ns, False)
+ time.exposure_bounds = self._get_interval("exposureBounds", el, ns, False)
+ time.calibration = self._get_child_text("calibration", el, ns, False)
return time
@@ -1357,12 +1442,33 @@ def _get_custom(self, element_tag, parent, ns, required):
if el is None:
return None
ctype = self._get_child_text("ctype", el, ns, False)
- custom = plane.CustomAxis(ctype)
- custom.bounds = self._get_interval("bounds", el, ns, False)
+ bounds = self._get_interval("bounds", el, ns, False)
+ if self.version < 25:
+ samples = self._get_samples(self._get_child_element("bounds", el, ns, True), ns, True)
+ else:
+ samples = self._get_samples(el, ns, True)
+ custom = plane.CustomAxis(ctype, bounds, samples=samples)
custom.dimension = \
self._get_child_text_as_int("dimension", el, ns, False)
return custom
+ def _get_visibility(self, parent, ns):
+ """Build a Visibility object from an XML representation
+
+ Arguments:
+ parent : element containing the position element
+ ns : namespace of the document
+ return : a Visibility object or None if the document does not contain one
+ raise : ObservationParsingException
+ """
+ el = self._get_child_element("visibility", parent, ns, False)
+ if el is None:
+ return None
+ distance = self._get_interval("distance", el, ns, True)
+ de = self._get_child_text_as_float("distributionEccentricity", el, ns, True)
+ df = self._get_child_text_as_float("distributionFill", el, ns, True)
+ return plane.Visibility(distance, de, df)
+
def _get_polarization(self, element_tag, parent, ns, required):
"""Build a Polarization object from an XML representation of a
polarization element.
@@ -1379,19 +1485,19 @@ def _get_polarization(self, element_tag, parent, ns, required):
el = self._get_child_element(element_tag, parent, ns, required)
if el is None:
return None
- polarization = plane.Polarization()
_pstates_el = self._get_child_element("states", el, ns, False)
if _pstates_el is not None:
- _polarization_states = list()
+ _states = list()
for _pstate_el in _pstates_el.iterchildren("{" + ns + "}state"):
_pstate = _pstate_el.text
- _polarization_state = plane.PolarizationState(_pstate)
- _polarization_states.append(_polarization_state)
- polarization.polarization_states = _polarization_states
- polarization.dimension = self._get_child_text_as_int("dimension", el,
- ns, False)
+ _state = plane.PolarizationState(_pstate)
+ _states.append(_state)
+ else:
+ return None
+ dimension = self._get_child_text_as_int("dimension", el,
+ ns, False)
- return polarization
+ return Polarization(dimension=dimension, states=_states)
def _get_shape(self, element_tag, parent, ns, required):
shape_element = self._get_child_element(element_tag, parent, ns,
@@ -1400,34 +1506,54 @@ def _get_shape(self, element_tag, parent, ns, required):
return None
shape_type = shape_element.get(XSI + "type")
if "caom2:Polygon" == shape_type:
- if self.version < 23:
- raise TypeError(
- ("Polygon element not supported for "
- "CAOM releases prior to 2.3"))
- points_element = self._get_child_element("points", shape_element,
- ns, True)
- points = list()
- for point in points_element.iterchildren(
- tag=("{" + ns + "}point")):
- points.append(self._get_point(point, ns, True))
- samples_element = self._get_child_element("samples", shape_element,
- ns, True)
- vertices = list()
- self._add_vertices(vertices, samples_element, ns)
- return shape.Polygon(points=points,
- samples=shape.MultiPolygon(vertices=vertices))
+ polygon = self._get_polygon(ns, shape_element)
+ samples = None
+ if self.version < 25:
+ samples_element = self._get_child_element("samples", shape_element,
+ ns, True)
+ vertices = list()
+ self._add_vertices(vertices, samples_element, ns)
+ samples = _to_samples(vertices)
+ else:
+ samples = self._get_child_element("samples", parent,
+ ns, True)
+ sample_list = []
+ for _shape in samples.iterchildren("{" + ns + "}shape"):
+ sample_type = _shape.get(XSI + "type")
+ if "caom2:Polygon" == sample_type:
+ sample_list.append(self._get_polygon(ns, _shape))
+ elif "caom2:Circle" == sample_type:
+ sample_list.append(self._get_circle(ns, _shape))
+ else:
+ raise TypeError("Unsupported sample type " + sample_type)
+ samples = shape.MultiShape(sample_list)
+ return polygon, samples
elif "caom2:Circle" == shape_type:
- center = self._get_child_element("center", shape_element, ns, True)
- center_point = self._get_point(center, ns, True)
- radius = self._get_child_text_as_float(
- "radius", shape_element, ns, True)
- return shape.Circle(center=center_point, radius=radius)
+ circle = self._get_circle(ns, shape_element)
+ return circle, shape.MultiShape([circle])
else:
raise TypeError("Unsupported shape type " + shape_type)
+ def _get_circle(self, ns, shape_element):
+ center = self._get_child_element("center", shape_element, ns, True)
+ center_point = self._get_point(center, ns, True)
+ radius = self._get_child_text_as_float(
+ "radius", shape_element, ns, True)
+ circle = shape.Circle(center=center_point, radius=radius)
+ return circle
+
+ def _get_polygon(self, ns, shape_element):
+ points_element = self._get_child_element("points", shape_element,
+ ns, True)
+ points = list()
+ for point in points_element.iterchildren(
+ tag=("{" + ns + "}point")):
+ points.append(self._get_point(point, ns, True))
+ return shape.Polygon(points)
+
def _add_energy_bands(self, energy_bands, parent, ns):
"""Create EnergyBand objects from an XML representation of
- ObservationURI elements found in energy_band element, and add them to
+ observation URI elements found in energy_band element, and add them to
the set of energy_bads
Arguments:
@@ -1472,20 +1598,21 @@ def _get_interval(self, element_tag, parent, ns, required):
return None
_lower = self._get_child_text_as_float("lower", _interval_el, ns, True)
_upper = self._get_child_text_as_float("upper", _interval_el, ns, True)
- _samples_el = self._get_child_element("samples", _interval_el, ns,
- required)
- _interval = shape.Interval(_lower, _upper)
- if _samples_el is not None:
- _samples = list()
- for _sample_el in _samples_el.iterchildren("{" + ns + "}sample"):
- _si_lower = self._get_child_text_as_float("lower", _sample_el,
- ns, required)
- _si_upper = self._get_child_text_as_float("upper", _sample_el,
- ns, required)
- _sub_interval = shape.SubInterval(_si_lower, _si_upper)
- _samples.append(_sub_interval)
- _interval.samples = _samples
- return _interval
+ return dali.Interval(_lower, _upper)
+
+ def _get_samples(self, parent, ns, required):
+ _samples_el = self._get_child_element("samples", parent, ns, required)
+ if _samples_el is None:
+ return None
+ _samples = []
+ for _sample_el in _samples_el.iterchildren("{" + ns + "}sample"):
+ _si_lower = self._get_child_text_as_float("lower", _sample_el,
+ ns, required)
+ _si_upper = self._get_child_text_as_float("upper", _sample_el,
+ ns, required)
+ _sub_interval = dali.Interval(_si_lower, _si_upper)
+ _samples.append(_sub_interval)
+ return _samples
def _add_chunks(self, chunks, parent, ns):
"""Build Chunk objects from an XML representation of Chunk elements
@@ -1508,7 +1635,7 @@ def _add_chunks(self, chunks, parent, ns):
False)
if product_type:
_chunk.product_type = \
- chunk.ProductType(product_type)
+ chunk.DataLinkSemantics(product_type)
_chunk.naxis = \
self._get_child_text_as_int("naxis", chunk_element, ns,
False)
@@ -1574,7 +1701,7 @@ def _add_parts(self, parts, parent, ns):
False)
if product_type:
_part.product_type = \
- chunk.ProductType(product_type)
+ chunk.DataLinkSemantics(product_type)
self._add_chunks(_part.chunks, part_element, ns)
self._set_entity_attributes(part_element, ns, _part)
parts[_part.name] = _part
@@ -1600,12 +1727,12 @@ def _add_artifacts(self, artifacts, parent, ns):
artifact_element, ns,
False)
if product_type is None:
- product_type = chunk.ProductType.SCIENCE
+ product_type = chunk.DataLinkSemantics.SCIENCE
print(
"Using default Artifact.productType value {0}".format(
- str(chunk.ProductType.SCIENCE)))
+ str(chunk.DataLinkSemantics.SCIENCE)))
else:
- product_type = chunk.ProductType(product_type)
+ product_type = chunk.DataLinkSemantics(product_type)
release_type = self._get_child_text("releaseType",
artifact_element, ns,
@@ -1619,6 +1746,15 @@ def _add_artifacts(self, artifacts, parent, ns):
release_type = artifact.ReleaseType(release_type)
_artifact = artifact.Artifact(uri, product_type, release_type)
+ if self.version >= 25:
+ sub = self._get_child_text("uriBucket", artifact_element, ns, True)
+ if sub != _artifact.uri_bucket:
+ raise ObservationParsingException(
+ "Parsed artifact URI bucket {} does not match calculated artifact URI bucket {}".
+ format(sub, _artifact.uri_bucket))
+ _artifact.description_id = self._get_child_text("descriptionID",
+ artifact_element,
+ ns, False)
cr = self._get_child_text("contentRelease", artifact_element,
ns, False)
_artifact.content_release = caom_util.str2ivoa(cr)
@@ -1635,18 +1771,17 @@ def _add_artifacts(self, artifacts, parent, ns):
artifact_element, ns,
False)
if content_checksum:
- _artifact.content_checksum = common.ChecksumURI(
- content_checksum)
+ _artifact.content_checksum = content_checksum
self._add_parts(_artifact.parts, artifact_element, ns)
self._set_entity_attributes(artifact_element, ns, _artifact)
artifacts[_artifact.uri] = _artifact
- def _add_planes(self, planes, parent, ns):
+ def _add_planes(self, obs, parent, ns):
"""Create Planes object from XML representation of Plane elements
and add them to the set of Planes.
Arguments:
- planes : Set of planes from the parent Observation object
+ obs : Observation object containing the Planes
parent : element containing the Plane elements
ns : namespace of the document
raise : ObservationParsingException
@@ -1656,8 +1791,15 @@ def _add_planes(self, planes, parent, ns):
return None
else:
for plane_element in el.iterchildren("{" + ns + "}plane"):
- _plane = plane.Plane(
- self._get_child_text("productID", plane_element, ns, True))
+ if self.version < 25:
+ # TODO
+ _uri = "{}/{}".format(
+ obs.uri,
+ self._get_child_text("productID",
+ plane_element, ns, True))
+ else:
+ _uri = self._get_child_text("uri", plane_element, ns, True)
+ _plane = plane.Plane(_uri)
_plane.meta_release = caom_util.str2ivoa(
self._get_child_text("metaRelease", plane_element, ns,
False))
@@ -1674,17 +1816,7 @@ def _add_planes(self, planes, parent, ns):
self._get_child_text("dataProductType", plane_element, ns,
False)
if data_product_type:
- if (data_product_type == 'catalog') and \
- (self.version < 23):
- # TODO backawards compatibility. To be removed when 2.2
- # and older version no longer supported
- _plane.data_product_type = \
- plane.DataProductType(
- '{}#{}'.format(plane._CAOM_VOCAB_NS,
- data_product_type))
- else:
- _plane.data_product_type = \
- plane.DataProductType(data_product_type)
+ _plane.data_product_type = plane.DataProductType(data_product_type)
_plane.creator_id = \
self._get_child_text("creatorID", plane_element, ns, False)
calibration_level = \
@@ -1696,6 +1828,8 @@ def _add_planes(self, planes, parent, ns):
_plane.provenance = \
self._get_provenance("provenance", plane_element, ns,
False)
+ _plane.observable = self._get_observable(plane_element, ns)
+
_plane.metrics = \
self._get_metrics("metrics", plane_element, ns, False)
_plane.quality = \
@@ -1711,9 +1845,10 @@ def _add_planes(self, planes, parent, ns):
False)
_plane.custom = \
self._get_custom("custom", plane_element, ns, False)
+ _plane.visibility = self._get_visibility(plane_element, ns)
self._add_artifacts(_plane.artifacts, plane_element, ns)
self._set_entity_attributes(plane_element, ns, _plane)
- planes[_plane.product_id] = _plane
+ obs.planes[_plane.uri] = _plane
def read(self, source):
"""Build an Observation object from an XML document located in source.
@@ -1734,19 +1869,28 @@ def read(self, source):
self.version = CAOM_VERSION[ns]
collection = str(
self._get_child_element("collection", root, ns, True).text)
- observation_id = \
- str(self._get_child_element("observationID", root, ns, True).text)
+ if self.version < 25:
+ observation_id = \
+ str(self._get_child_text("observationID", root, ns, True))
+ uri = "caom:" + collection + "/" + observation_id
+ else:
+ uri = self._get_child_text("uri", root, ns, True)
# Instantiate Algorithm
algorithm = self._get_algorithm("algorithm", root, ns, True)
# Instantiate Observation
if root.get("{http://www.w3.org/2001/XMLSchema-instance}type") \
== "caom2:SimpleObservation":
- obs = observation.SimpleObservation(collection, observation_id)
+ obs = observation.SimpleObservation(collection, uri)
obs.algorithm = algorithm
else:
obs = \
- observation.DerivedObservation(collection, observation_id,
- algorithm)
+ observation.DerivedObservation(collection, uri, algorithm)
+ if self.version >= 25:
+ sub = self._get_child_text("uriBucket", root, ns, True)
+ if sub != obs.uri_bucket:
+ raise ObservationParsingException(
+ "Parsed obs URI bucket {} does not match calculated obs URI bucket {}".
+ format(sub, obs.uri_bucket))
# Instantiate children of Observation
obs.sequence_number = \
self._get_child_text_as_int("sequenceNumber", root, ns, False)
@@ -1773,7 +1917,7 @@ def read(self, source):
self._get_environment("environment", root, ns, False)
obs.requirements = \
self._get_requirements("requirements", root, ns, False)
- self._add_planes(obs.planes, root, ns)
+ self._add_planes(obs, root, ns)
if isinstance(obs, observation.DerivedObservation):
self._add_members(obs.members, root, ns)
@@ -1799,28 +1943,28 @@ def __init__(self, validate=False, write_empty_collections=False,
if namespace_prefix is None or not namespace_prefix:
raise RuntimeError('null or empty namespace_prefix not allowed')
- if namespace is None or namespace == CAOM24_NAMESPACE:
+ if namespace is None or namespace == CAOM25_NAMESPACE:
+ self._output_version = 25
+ self._caom2_namespace = CAOM25
+ self._namespace = CAOM25_NAMESPACE
+ elif namespace == CAOM24_NAMESPACE:
self._output_version = 24
self._caom2_namespace = CAOM24
self._namespace = CAOM24_NAMESPACE
- elif namespace is None or namespace == CAOM23_NAMESPACE:
+ elif namespace == CAOM23_NAMESPACE:
self._output_version = 23
self._caom2_namespace = CAOM23
self._namespace = CAOM23_NAMESPACE
- elif namespace == CAOM22_NAMESPACE:
- self._output_version = 22
- self._caom2_namespace = CAOM22
- self._namespace = CAOM22_NAMESPACE
else:
raise RuntimeError('invalid namespace {}'.format(namespace))
if self._validate:
- if self._output_version == 24:
+ if self._output_version == 25:
+ schema_file = CAOM25_SCHEMA_FILE
+ elif self._output_version == 24:
schema_file = CAOM24_SCHEMA_FILE
elif self._output_version == 23:
schema_file = CAOM23_SCHEMA_FILE
- else:
- schema_file = CAOM22_SCHEMA_FILE
schema_path = os.path.join(THIS_DIR + '/' + DATA_PKG,
schema_file)
# schema_path = pkg_resources.resource_filename(
@@ -1851,7 +1995,12 @@ def write(self, obs, out):
self._add_entity_attributes(obs, obs_element)
self._add_element("collection", obs.collection, obs_element)
- self._add_element("observationID", obs.observation_id, obs_element)
+ if self._output_version < 25:
+ observation_id = obs.uri.split('/')[-1]
+ self._add_element("observationID", observation_id, obs_element)
+ else:
+ self._add_element("uri", obs.uri, obs_element)
+ self._add_element('uriBucket', obs.uri_bucket, obs_element)
self._add_datetime_element("metaRelease", obs.meta_release,
obs_element)
if self._output_version < 24 and obs.meta_read_groups:
@@ -1901,17 +2050,16 @@ def _add_entity_attributes(self, entity, element):
"lastModified", caom_util.date2ivoa(entity._last_modified),
element)
- if self._output_version >= 23:
- if entity._max_last_modified is not None:
- self._add_attribute(
- "maxLastModified",
- caom_util.date2ivoa(entity._max_last_modified), element)
- if entity._meta_checksum is not None:
- self._add_attribute(
- "metaChecksum", entity._meta_checksum.uri, element)
- if entity._acc_meta_checksum is not None:
- self._add_attribute(
- "accMetaChecksum", entity._acc_meta_checksum.uri, element)
+ if entity._max_last_modified is not None:
+ self._add_attribute(
+ "maxLastModified",
+ caom_util.date2ivoa(entity._max_last_modified), element)
+ if entity._meta_checksum is not None:
+ self._add_attribute(
+ "metaChecksum", entity._meta_checksum, element)
+ if entity._acc_meta_checksum is not None:
+ self._add_attribute(
+ "accMetaChecksum", entity._acc_meta_checksum, element)
if self._output_version >= 24:
if entity._meta_producer is not None:
@@ -1931,9 +2079,10 @@ def _add_proposal_element(self, proposal, parent):
element = self._get_caom_element("proposal", parent)
self._add_element("id", proposal.id, element)
- self._add_element("pi", proposal.pi_name, element)
+ self._add_element("pi", proposal.pi, element)
self._add_element("project", proposal.project, element)
self._add_element("title", proposal.title, element)
+ self._add_element("reference", proposal.reference, element)
self._add_keywords_element(proposal.keywords, element)
def _add_target_element(self, target, parent):
@@ -1949,8 +2098,8 @@ def _add_target_element(self, target, parent):
raise AttributeError(
"Attempt to write CAOM2.4 element (target.targetID) "
"as CAOM2.3 Observation")
- if target.target_type is not None:
- self._add_element("type", target.target_type.value, element)
+ if target.type is not None:
+ self._add_element("type", target.type.value, element)
self._add_boolean_element("standard", target.standard, element)
self._add_element("redshift", target.redshift, element)
self._add_boolean_element("moving", target.moving, element)
@@ -1984,6 +2133,7 @@ def _add_telescope_element(self, telescope, parent):
self._add_element("geoLocationX", telescope.geo_location_x, element)
self._add_element("geoLocationY", telescope.geo_location_y, element)
self._add_element("geoLocationZ", telescope.geo_location_z, element)
+ self._add_element("trackingMode", telescope.tracking_mode, element)
self._add_keywords_element(telescope.keywords, element)
def _add_instrument_element(self, instrument, parent):
@@ -2015,8 +2165,11 @@ def _add_members_element(self, members, parent):
element = self._get_caom_element("members", parent)
for member in members:
- member_element = self._get_caom_element("observationURI", element)
- member_element.text = member.uri
+ if self._output_version < 25:
+ member_element = self._get_caom_element("observationURI", element)
+ else:
+ member_element = self._get_caom_element("member", element)
+ member_element.text = member
def _add_groups_element(self, name, groups, parent):
if self._output_version < 24:
@@ -2038,13 +2191,14 @@ def _add_planes_element(self, planes, parent):
for _plane in planes.values():
plane_element = self._get_caom_element("plane", element)
self._add_entity_attributes(_plane, plane_element)
- self._add_element("productID", _plane.product_id, plane_element)
- if _plane.creator_id is not None:
- if self._output_version >= 23:
- self._add_element("creatorID", _plane.creator_id,
- plane_element)
- else:
- raise AttributeError('creatorID only available in CAOM2.3')
+ if self._output_version < 25:
+ _comp = _plane.uri.split('/')
+ if len(_comp) != 3:
+ raise ValueError("Attempt to write CAOM2.4 but can't deduce "
+ "Plane.productID in Plane.uri=" + _plane.uri)
+ self._add_element("productID", _comp[-1], plane_element)
+ else:
+ self._add_element("uri", _plane.uri, plane_element)
self._add_datetime_element("metaRelease", _plane.meta_release,
plane_element)
if self._output_version < 24 and _plane.meta_read_groups:
@@ -2062,24 +2216,15 @@ def _add_planes_element(self, planes, parent):
self._add_groups_element("dataReadGroups", _plane.data_read_groups,
plane_element)
if _plane.data_product_type is not None:
- if self._output_version < 23:
- dpt = urlparse(_plane.data_product_type.value)
- if dpt.fragment != '':
- dpt = dpt.fragment
- else:
- dpt = _plane.data_product_type.value
- self._add_element("dataProductType",
- dpt,
- plane_element)
- else:
- self._add_element("dataProductType",
- _plane.data_product_type.value,
- plane_element)
+ self._add_element("dataProductType",
+ _plane.data_product_type.value,
+ plane_element)
if _plane.calibration_level is not None:
self._add_element("calibrationLevel",
_plane.calibration_level.value,
plane_element)
self._add_provenance_element(_plane.provenance, plane_element)
+ self._add_observable_element(_plane.observable, plane_element)
self._add_metrics_element(_plane.metrics, plane_element)
self._add_quality_element(_plane.quality, plane_element)
@@ -2095,15 +2240,34 @@ def _add_planes_element(self, planes, parent):
else:
self._add_custom_element(_plane.custom, plane_element)
+ self._add_visibility_element(_plane.visibility, plane_element)
+
self._add_artifacts_element(_plane.artifacts, plane_element)
+ def _add_visibility_element(self, visibility, parent):
+ if visibility is None:
+ return
+
+ if self._output_version < 25:
+ raise AttributeError("Attempt to output CAOM2.5 attribute (Plane.visibility) as "
+ "{} Observation".format(self._output_version))
+
+ element = self._get_caom_element("visibility", parent)
+ self._add_interval_element("distance", visibility.distance, element)
+ self._add_element("distributionEccentricity", visibility.distribution_eccentricity,
+ element)
+ self._add_element("distributionFill", visibility.distribution_fill, element)
+
def _add_position_element(self, position, parent):
if position is None:
return
element = self._get_caom_element("position", parent)
- self._add_shape_element("bounds", position.bounds, element)
+ self._add_bounds_and_samples(position, element)
+ if position.min_bounds:
+ self._add_shape_element("minBounds", element, position.min_bounds)
self._add_dimension2d_element("dimension", position.dimension, element)
+ self._add_interval_element("maxRecoverableScale", position.max_recoverable_scale, element)
self._add_element("resolution", position.resolution, element)
if self._output_version < 24:
if position.resolution_bounds is not None:
@@ -2114,14 +2278,25 @@ def _add_position_element(self, position, parent):
self._add_interval_element("resolutionBounds",
position.resolution_bounds, element)
self._add_element("sampleSize", position.sample_size, element)
- self._add_boolean_element("timeDependent", position.time_dependent,
- element)
+ if position.calibration:
+ if self._output_version < 25:
+ raise AttributeError(
+ "Attempt to write CAOM2.5 element (position.calibration) as "
+ "{} Observation".format(self._output_version))
+ else:
+ self._add_element("calibration", position.calibration, element)
def _add_energy_element(self, energy, parent):
if energy is None:
return
element = self._get_caom_element("energy", parent)
- self._add_interval_element("bounds", energy.bounds, element)
+ bounds_elem = self._add_interval_element("bounds", energy.bounds, element)
+ if self._output_version < 25:
+ # samples element is within bounds element
+ self._add_samples_element(energy.samples, bounds_elem)
+ else:
+ self._add_samples_element(energy.samples, element)
+
self._add_element("dimension", energy.dimension, element)
self._add_element("resolvingPower", energy.resolving_power, element)
if energy.resolving_power_bounds is not None:
@@ -2131,9 +2306,25 @@ def _add_energy_element(self, energy, parent):
"Attempt to write CAOM2.4 element "
"(energy.resolving_power_bands) as "
"CAOM2.3 Observation")
- else:
- self._add_interval_element("resolvingPowerBounds",
- energy.resolving_power_bounds, element)
+ else:
+ self._add_interval_element("resolvingPowerBounds",
+ energy.resolving_power_bounds, element)
+ if energy.resolution is not None:
+ if self._output_version < 25:
+ raise AttributeError(
+ "Attempt to write CAOM2.5 element (energy.resolution) as "
+ "{} Observation".format(self._output_version))
+ else:
+ self._add_element("resolution", energy.resolution, element)
+ if energy.resolution_bounds is not None:
+ if self._output_version < 25:
+ raise AttributeError(
+ "Attempt to write CAOM2.5 element (energy.resolution_bounds) as "
+ "{} Observation".format(self._output_version))
+ else:
+ self._add_interval_element("resolutionBounds",
+ energy.resolution_bounds, element)
+
self._add_element("sampleSize", energy.sample_size, element)
self._add_element("bandpassName", energy.bandpass_name, element)
if energy.energy_bands:
@@ -2151,18 +2342,33 @@ def _add_energy_element(self, energy, parent):
eb_element = self._get_caom_element("energyBands", element)
for bb in energy.energy_bands:
self._add_element("emBand", bb.value, eb_element)
- self._add_element("restwav", energy.restwav, element)
+ if self._output_version < 25:
+ self._add_element("restwav", energy.rest, element)
+ else:
+ self._add_element("rest", energy.rest, element)
if energy.transition:
transition = self._get_caom_element("transition", element)
self._add_element("species", energy.transition.species, transition)
self._add_element("transition", energy.transition.transition,
transition)
+ if energy.calibration:
+ if self._output_version < 25:
+ raise AttributeError(
+ "Attempt to write CAOM2.5 element (energy.calibration) as "
+ "{} Observation".format(self._output_version))
+ else:
+ self._add_element("calibration", energy.calibration, element)
def _add_time_element(self, time, parent):
if time is None:
return
element = self._get_caom_element("time", parent)
- self._add_interval_element("bounds", time.bounds, element)
+ bounds_elem = self._add_interval_element("bounds", time.bounds, element)
+ if self._output_version < 25:
+ # samples element is within bounds element
+ self._add_samples_element(time.samples, bounds_elem)
+ else:
+ self._add_samples_element(time.samples, element)
self._add_element("dimension", time.dimension, element)
self._add_element("resolution", time.resolution, element)
if self._output_version < 24:
@@ -2175,55 +2381,95 @@ def _add_time_element(self, time, parent):
time.resolution_bounds, element)
self._add_element("sampleSize", time.sample_size, element)
self._add_element("exposure", time.exposure, element)
+ if time.exposure_bounds is not None:
+ if self._output_version < 25:
+ if len(time.exposure_bounds) > 1:
+ raise AttributeError(
+ "Attempt to write CAOM2.5 element "
+ "(time.exposure_bounds) as {} Observation".format(self._output_version))
+ else:
+ self._add_interval_element("exposureBounds", time.exposure_bounds, element)
+ if time.calibration:
+ if self._output_version < 25:
+ raise AttributeError(
+ "Attempt to write CAOM2.5 element (time.calibration) as {} "
+ "Observation".format(self._output_version))
+ else:
+ self._add_element("calibration", time.calibration, element)
def _add_custom_element(self, custom, parent):
if custom is None:
return
element = self._get_caom_element("custom", parent)
self._add_element("ctype", custom.ctype, element)
- self._add_interval_element("bounds", custom.bounds, element)
+ bounds_elem = self._add_interval_element("bounds", custom.bounds, element)
+ if self._output_version < 25:
+ # samples element is within bounds element
+ self._add_samples_element(custom.samples, bounds_elem)
+ else:
+ self._add_samples_element(custom.samples, element)
self._add_element("dimension", custom.dimension, element)
def _add_polarization_element(self, polarization, parent):
if polarization is None:
return
element = self._get_caom_element("polarization", parent)
- if polarization.polarization_states:
+ if not polarization.states:
+ raise AttributeError("Polarization.states missing")
+ if polarization.states:
_pstates_el = self._get_caom_element("states", element)
- for _state in polarization.polarization_states:
+ for _state in polarization.states:
self._add_element("state", _state.value, _pstates_el)
self._add_element("dimension", polarization.dimension, element)
- def _add_shape_element(self, name, the_shape, parent):
- if the_shape is None:
+ def _add_bounds_and_samples(self, position, parent):
+ if position is None:
return
- if self._output_version < 23:
- raise TypeError(
- 'Polygon shape not supported in CAOM2 previous to v2.3')
- if isinstance(the_shape, shape.Polygon):
+ shape_element = self._add_shape_element("bounds", parent, position.bounds)
+ if self._output_version < 25:
+ if isinstance(position.bounds, shape.Circle):
+ # samples not supported for circles so need to make sure that
+ # samples is the same with bounds
+ if len(position.samples.shapes) > 1 or position.samples.shapes[0] != position.bounds:
+ raise AttributeError(
+ "Cannot write a CAOM25 circle bounds position as CAOM{} "
+ "Observation".format(self._output_version))
+ else:
+ pass # samples is not defined with circles
+ else:
+ samples_element = self._get_caom_element("samples", shape_element)
+ vertices_element = self._get_caom_element("vertices",
+ samples_element)
+ vertices = _to_vertices(position.samples)
+ for vertex in vertices:
+ vertex_element = self._get_caom_element("vertex",
+ vertices_element)
+ self._add_element("cval1", vertex.cval1, vertex_element)
+ self._add_element("cval2", vertex.cval2, vertex_element)
+ self._add_element("type", vertex.type.value, vertex_element)
+ else:
+ samples_element = self._get_caom_element("samples", parent=parent)
+ for samples_shape in position.samples.shapes:
+ self._add_shape_element("shape", samples_element, samples_shape)
+
+ def _add_shape_element(self, name, parent, elem_shape):
+ if isinstance(elem_shape, shape.Polygon):
shape_element = self._get_caom_element(name, parent)
shape_element.set(XSI + "type", "caom2:Polygon")
points_element = self._get_caom_element("points", shape_element)
- for point in the_shape.points:
+ for point in elem_shape.points:
self._add_point_element("point", point, points_element)
- samples_element = self._get_caom_element("samples", shape_element)
- vertices_element = self._get_caom_element("vertices",
- samples_element)
- for vertex in the_shape.samples.vertices:
- vertex_element = self._get_caom_element("vertex",
- vertices_element)
- self._add_element("cval1", vertex.cval1, vertex_element)
- self._add_element("cval2", vertex.cval2, vertex_element)
- self._add_element("type", vertex.type.value, vertex_element)
- elif isinstance(the_shape, shape.Circle):
+ elif isinstance(elem_shape, shape.Circle):
+
shape_element = self._get_caom_element(name, parent)
shape_element.set(XSI + "type", "caom2:Circle")
- self._add_point_element("center", the_shape.center, shape_element)
- self._add_element("radius", the_shape.radius, shape_element)
+ self._add_point_element("center", elem_shape.center,
+ shape_element)
+ self._add_element("radius", elem_shape.radius, shape_element)
else:
- raise TypeError("Unsupported shape type "
- + the_shape.__class__.__name__)
+ raise TypeError("Unsupported shape type " + elem_shape.__class__.__name__)
+ return shape_element
def _add_interval_element(self, name, interval, parent):
if interval is None:
@@ -2232,14 +2478,18 @@ def _add_interval_element(self, name, interval, parent):
_interval_element = self._get_caom_element(name, parent)
self._add_element("lower", interval.lower, _interval_element)
self._add_element("upper", interval.upper, _interval_element)
- if interval.samples:
- _samples_element = self._get_caom_element("samples",
- _interval_element)
- for _sample in interval.samples:
- _sample_element = self._get_caom_element("sample",
- _samples_element)
- self._add_element("lower", _sample.lower, _sample_element)
- self._add_element("upper", _sample.upper, _sample_element)
+ return _interval_element
+
+ def _add_samples_element(self, samples, parent):
+ if not samples:
+ raise AttributeError("non empty samples attribute is required")
+
+ _samples_element = self._get_caom_element("samples", parent)
+ for _sample in samples:
+ _sample_element = self._get_caom_element("sample",
+ _samples_element)
+ self._add_element("lower", _sample.lower, _sample_element)
+ self._add_element("upper", _sample.upper, _sample_element)
def _add_provenance_element(self, provenance, parent):
if provenance is None:
@@ -2280,6 +2530,20 @@ def _add_quality_element(self, quality, parent):
element = self._get_caom_element("quality", parent)
self._add_element("flag", quality.flag.value, element)
+ def _add_observable_element(self, observable, parent):
+ if observable is None:
+ return
+
+ element = self._get_caom_element("observable", parent)
+ self._add_element("ucd", observable.ucd, element)
+ if observable.calibration:
+ if self._output_version < 25:
+ raise AttributeError(
+ "Attempt to write CAOM2.5 element (observable.calibration) as "
+ "{} Observation".format(self._output_version))
+ else:
+ self._add_element("calibration", observable.calibration, element)
+
def _add_transition_element(self, transition, parent):
if transition is None:
return
@@ -2297,6 +2561,8 @@ def _add_artifacts_element(self, artifacts, parent):
artifact_element = self._get_caom_element("artifact", element)
self._add_entity_attributes(_artifact, artifact_element)
self._add_element("uri", _artifact.uri, artifact_element)
+ if self._output_version >= 25:
+ self._add_element("uriBucket", _artifact.uri_bucket, artifact_element)
self._add_element("productType", _artifact.product_type.value,
artifact_element)
self._add_element("releaseType", _artifact.release_type.value,
@@ -2319,11 +2585,18 @@ def _add_artifacts_element(self, artifacts, parent):
artifact_element)
self._add_element("contentLength", _artifact.content_length,
artifact_element)
- if self._output_version > 22:
- if _artifact.content_checksum:
- self._add_element("contentChecksum",
- _artifact.content_checksum.uri,
- artifact_element)
+ if _artifact.content_checksum:
+ self._add_element("contentChecksum",
+ _artifact.content_checksum,
+ artifact_element)
+ if _artifact.description_id is not None:
+ if self._output_version < 25:
+ raise AttributeError(
+ "Attempt to write CAOM2.5 element "
+ "(artifact.description_id) as CAOM{} Observation".format(self._output_version))
+ else:
+ self._add_element("descriptionID",
+ _artifact.description_id, artifact_element)
self._add_parts_element(_artifact.parts, artifact_element)
def _add_parts_element(self, parts, parent):
@@ -2341,7 +2614,7 @@ def _add_parts_element(self, parts, parent):
self._add_chunks_element(_part.chunks, part_element)
def _add_chunks_element(self, chunks, parent):
- if chunks is None:
+ if not chunks:
return
element = self._get_caom_element("chunks", parent)
@@ -2674,6 +2947,8 @@ def _add_element(self, name, value, parent):
element = self._get_caom_element(name, parent)
if isinstance(value, str):
element.text = value
+ elif isinstance(value, Enum):
+ element.text = value.value
else:
element.text = str(value)
@@ -2695,11 +2970,8 @@ def _add_keywords_element(self, collection, parent):
(len(collection) == 0 and not self._write_empty_collections):
return
element = self._get_caom_element("keywords", parent)
- if self._output_version < 23:
- element.text = ' '.join(collection)
- else:
- for keyword in collection:
- self._get_caom_element("keyword", element).text = keyword
+ for keyword in collection:
+ self._get_caom_element("keyword", element).text = keyword
def _add_coord_range_1d_list_element(self, name, values, parent):
if values is None:
@@ -2714,7 +2986,10 @@ def _add_inputs_element(self, name, collection, parent):
return
element = self._get_caom_element(name, parent)
for plane_uri in collection:
- self._add_element("planeURI", plane_uri.uri, element)
+ if self._output_version < 25:
+ self._add_element("planeURI", plane_uri, element)
+ else:
+ self._add_element("input", plane_uri, element)
def _get_caom_element(self, tag, parent):
return etree.SubElement(parent, self._caom2_namespace + tag)
diff --git a/caom2/caom2/observation.py b/caom2/caom2/observation.py
index 7d5d8245..01c756cc 100644
--- a/caom2/caom2/observation.py
+++ b/caom2/caom2/observation.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -75,19 +75,19 @@
from deprecated import deprecated
from . import caom_util
-from .caom_util import int_32
-from .common import AbstractCaomEntity, CaomObject, ObservationURI, \
- VocabularyTerm, OrderedEnum
-from .common import _CAOM_VOCAB_NS
+from .caom_util import int_32, validate_uri
+from .common import AbstractCaomEntity, CaomObject, VocabularyTerm, OrderedEnum, compute_bucket
+from .common import _CAOM_DATA_PRODUCT_TYPE_NS
from .plane import Plane
from .shape import Point
from urllib.parse import urlsplit
+
with warnings.catch_warnings():
warnings.simplefilter('ignore')
from aenum import Enum
__all__ = ['ObservationIntentType', 'Status', 'TargetType',
- 'Observation', 'ObservationURI', 'Algorithm', 'SimpleObservation',
+ 'Observation', 'Algorithm', 'SimpleObservation',
'DerivedObservation', 'Environment', 'Instrument', 'Proposal',
'Requirements', 'Target', 'TargetPosition', 'Telescope',
'CompositeObservation']
@@ -106,7 +106,7 @@ class Status(Enum):
"""
FAIL: "fail"
"""
- FAIL = VocabularyTerm(_CAOM_VOCAB_NS, "fail", True).get_value()
+ FAIL = VocabularyTerm(_CAOM_DATA_PRODUCT_TYPE_NS, "fail", True).get_value()
class TargetType(Enum):
@@ -114,8 +114,16 @@ class TargetType(Enum):
FIELD: "field",
OBJECT: "object"
"""
- FIELD = VocabularyTerm(_CAOM_VOCAB_NS, "field", True).get_value()
- OBJECT = VocabularyTerm(_CAOM_VOCAB_NS, "object", True).get_value()
+ FIELD = VocabularyTerm(_CAOM_DATA_PRODUCT_TYPE_NS, "field", True).get_value()
+ OBJECT = VocabularyTerm(_CAOM_DATA_PRODUCT_TYPE_NS, "object", True).get_value()
+
+
+class Tracking(Enum):
+ """
+ FIELD: "field",
+ OBJECT: "object"
+ """
+ FIELD = VocabularyTerm(_CAOM_DATA_PRODUCT_TYPE_NS, "sidereal", True).get_value()
class Observation(AbstractCaomEntity):
@@ -168,7 +176,7 @@ class Observation(AbstractCaomEntity):
def __init__(self,
collection,
- observation_id,
+ uri,
algorithm,
sequence_number=None,
intent=None,
@@ -190,8 +198,7 @@ def __init__(self,
Arguments: collection : where the observation is from
(eg. 'HST')
- observation_id : a unique identifier within that collection
- (eg. '111')
+ uri : a unique logical identifier for the observation (eg. 'caom:HST/123456abc')
algorithm : the algorithm used to create the observation. For
a telescope observation this is always 'exposure'
@@ -199,14 +206,13 @@ def __init__(self,
super(Observation, self).__init__()
self.collection = collection
- self.observation_id = observation_id
+ validate_uri(uri)
+ self._uri = uri
+ self._uri_bucket = compute_bucket(uri)
if not algorithm:
raise AttributeError('Algorithm required')
self.algorithm = algorithm
- self._uri = ObservationURI.get_observation_uri(collection,
- observation_id)
-
self.sequence_number = sequence_number
self.intent = intent
self.type = type
@@ -237,27 +243,17 @@ def collection(self, value):
self._collection = value
@property
- def observation_id(self):
- """A string that uniquely identifies this obseravtion within the given
- collection.
-
- type: unicode string
- """
- return self._observation_id
-
- @observation_id.setter
- def observation_id(self, value):
- caom_util.type_check(value, str, 'observation_id', override=False)
- self._observation_id = value
+ def uri(self):
+ """A unique identifier for the observation.
- def get_uri(self):
- """A URI for this observation referenced in the caom system.
-
- This attribute is auto geneqrated from the other metadata.
type: unicode string
"""
return self._uri
+ @property
+ def uri_bucket(self):
+ return self._uri_bucket
+
@property
def planes(self):
"""A typed ordered dictionary containing plane objects associated with
@@ -541,7 +537,7 @@ class SimpleObservation(Observation):
def __init__(self,
collection,
- observation_id,
+ uri,
algorithm=_DEFAULT_ALGORITHM_NAME,
sequence_number=None,
intent=None,
@@ -560,10 +556,10 @@ def __init__(self,
collection - A name that describes a collection of data,
nominally the name of a telescope
- observation_id - A UNIQUE identifier with in that collection
+ uri - A unique identifier for the observation
"""
super(SimpleObservation, self).__init__(collection,
- observation_id,
+ uri,
algorithm,
sequence_number,
intent,
@@ -610,7 +606,7 @@ class DerivedObservation(Observation):
def __init__(self,
collection,
- observation_id,
+ uri,
algorithm,
sequence_number=None,
intent=None,
@@ -626,7 +622,7 @@ def __init__(self,
target_position=None):
super(DerivedObservation, self).__init__(
collection=collection,
- observation_id=observation_id,
+ uri=uri,
algorithm=algorithm,
sequence_number=sequence_number,
intent=intent,
@@ -640,7 +636,7 @@ def __init__(self,
planes=planes,
environment=environment,
target_position=target_position)
- self._members = caom_util.TypedSet(ObservationURI, )
+ self._members = caom_util.TypedSet(str, )
@property
def algorithm(self):
@@ -670,7 +666,7 @@ class CompositeObservation(DerivedObservation):
def __init__(self,
collection,
- observation_id,
+ uri,
algorithm,
sequence_number=None,
intent=None,
@@ -685,7 +681,7 @@ def __init__(self,
target_position=None):
super(CompositeObservation, self).__init__(
collection=collection,
- observation_id=observation_id,
+ uri=uri,
algorithm=algorithm,
sequence_number=sequence_number,
intent=intent,
@@ -882,9 +878,10 @@ class Proposal(CaomObject):
def __init__(self,
id,
- pi_name=None,
+ pi=None,
project=None,
- title=None):
+ title=None,
+ reference=None):
"""
Initializes a Proposal instance
@@ -893,11 +890,11 @@ def __init__(self,
"""
self.id = id
- self.pi_name = pi_name
+ self.pi = pi
self.project = project
self.title = title
-
self.keywords = set()
+ self.reference = reference
# Properties
@@ -933,18 +930,18 @@ def keywords(self, value):
self._keywords = value
@property
- def pi_name(self):
+ def pi(self):
"""The name (First Last) of the Principle Investigator of the
Proposal.
type: unicode string
"""
- return self._pi_name
+ return self._pi
- @pi_name.setter
- def pi_name(self, value):
- caom_util.type_check(value, str, 'pi_name')
- self._pi_name = value
+ @pi.setter
+ def pi(self, value):
+ caom_util.type_check(value, str, 'pi')
+ self._pi = value
@property
def project(self):
@@ -972,6 +969,19 @@ def title(self, value):
caom_util.type_check(value, str, 'title')
self._title = value
+ @property
+ def reference(self):
+ return self._reference
+
+ @reference.setter
+ def reference(self, value):
+ caom_util.type_check(value, str, 'reference')
+ if value is not None:
+ tmp = urlsplit(value)
+ if tmp.geturl() != value:
+ raise ValueError("Invalid URI: " + value)
+ self._reference = value
+
class Requirements(CaomObject):
""" Requirements """
@@ -1000,7 +1010,7 @@ class Target(CaomObject):
""" Target """
def __init__(self, name,
- target_type=None,
+ type=None,
standard=None,
redshift=None,
keywords=None,
@@ -1015,7 +1025,7 @@ def __init__(self, name,
"""
self.name = name
- self.target_type = target_type
+ self.type = type
self.standard = standard
self.redshift = redshift
if keywords is None:
@@ -1042,7 +1052,7 @@ def name(self, value):
self._name = value
@property
- def target_type(self):
+ def type(self):
"""A keyword describing the type of target.
must be from the list
""" + str(list(TargetType)) + """
@@ -1051,11 +1061,11 @@ def target_type(self):
"""
return self._type
- @target_type.setter
- def target_type(self, value):
+ @type.setter
+ def type(self, value):
if isinstance(value, str):
value = TargetType(value)
- caom_util.type_check(value, TargetType, "target_type")
+ caom_util.type_check(value, TargetType, "type")
self._type = value
@property
@@ -1189,7 +1199,8 @@ def __init__(self, name,
geo_location_x=None,
geo_location_y=None,
geo_location_z=None,
- keywords=None
+ keywords=None,
+ tracking_mode=None
):
"""
Initializes a Telescope instance
@@ -1207,9 +1218,26 @@ def __init__(self, name,
if keywords is None:
keywords = set()
self.keywords = keywords
+ self.tracking_mode = tracking_mode
# Properties
+ @property
+ def tracking_mode(self):
+ """A keyword indicating how the telescope moves during data aquisition.
+ must be from the list
+ """ + str(list(Tracking)) + """
+ type: Tracking
+
+ """
+ return self._tracking_mode
+
+ @tracking_mode.setter
+ def tracking_mode(self, value):
+ if isinstance(value, str):
+ value = Tracking(value)
+ self._tracking_mode = value
+
@property
def name(self):
"""a name for this facility.
diff --git a/caom2/caom2/part.py b/caom2/caom2/part.py
index 49979083..9bc90fe0 100644
--- a/caom2/caom2/part.py
+++ b/caom2/caom2/part.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -72,8 +72,7 @@
from builtins import str
from . import caom_util
-from .chunk import Chunk
-from .chunk import ProductType
+from .chunk import Chunk, DataLinkSemantics
from .common import AbstractCaomEntity
__all__ = ['Part']
@@ -117,7 +116,7 @@ def product_type(self):
@product_type.setter
def product_type(self, value):
- caom_util.type_check(value, ProductType, "product_type")
+ caom_util.type_check(value, DataLinkSemantics, "product_type")
self._product_type = value
@property
diff --git a/caom2/caom2/plane.py b/caom2/caom2/plane.py
index c47af0e2..1ab6fe56 100644
--- a/caom2/caom2/plane.py
+++ b/caom2/caom2/plane.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -71,26 +71,24 @@
from datetime import datetime
from builtins import str, int
-from urllib.parse import SplitResult, urlsplit
+from urllib.parse import urlsplit
from deprecated import deprecated
-from caom2.caom_util import int_32
+from caom2.caom_util import int_32, validate_uri
from . import caom_util
from . import shape
from . import wcs
from .artifact import Artifact
-from .common import AbstractCaomEntity, CaomObject, ObservationURI,\
- VocabularyTerm, OrderedEnum
-from .common import _CAOM_VOCAB_NS, _OBSCORE_VOCAB_NS
+from .common import AbstractCaomEntity, CaomObject, VocabularyTerm, OrderedEnum
+from .common import _CAOM_DATA_PRODUCT_TYPE_NS
import warnings
-from enum import Enum
with warnings.catch_warnings():
warnings.simplefilter('ignore')
from aenum import Enum, extend_enum
__all__ = ['CalibrationLevel', 'DataProductType', 'EnergyBand',
'PolarizationState', 'Quality', 'Plane',
- 'PlaneURI', 'DataQuality', 'Metrics', 'Provenance', 'Position',
+ 'DataQuality', 'Metrics', 'Provenance', 'Position',
'Energy', 'Polarization', 'Time', 'Observable']
@@ -110,23 +108,42 @@ class CalibrationLevel(Enum):
ANALYSIS_PRODUCT = int_32(4)
+class Ucd:
+ """ UCD - enum of UCDs"""
+ UCD_VOCAB = "https://ivoa.net/documents/UCD1+/20230125/ucd-list.txt"
+
+ def __init__(self, value):
+ self.value = value
+
+
+class CalibrationStatus(OrderedEnum):
+ """ CalibrationStatus - enum of calibration status"""
+ CALIB_STATUS_VOCAB = "http://www.opencadc.org/caom2/CalibrationStatus"
+
+ ABSOLUTE = VocabularyTerm(CALIB_STATUS_VOCAB, "absolute", True).get_value()
+ NORMALIZED = VocabularyTerm(CALIB_STATUS_VOCAB, "normalized", True).get_value()
+ RELATIVE = VocabularyTerm(CALIB_STATUS_VOCAB, "relative", True).get_value()
+
+
class DataProductType(OrderedEnum):
""" DataproductType - enum of data product types"""
+ DATA_PRODUCT_TYPE_VOCAB = "http://www.ivoa.net/rdf/product-type"
- IMAGE = VocabularyTerm(_OBSCORE_VOCAB_NS, "image", True).get_value()
- CUBE = VocabularyTerm(_OBSCORE_VOCAB_NS, "cube", True).get_value()
- EVENTLIST = VocabularyTerm(_OBSCORE_VOCAB_NS, "eventlist",
+ IMAGE = VocabularyTerm(DATA_PRODUCT_TYPE_VOCAB, "image", True).get_value()
+ CUBE = VocabularyTerm(DATA_PRODUCT_TYPE_VOCAB, "cube", True).get_value()
+ EVENTLIST = VocabularyTerm(DATA_PRODUCT_TYPE_VOCAB, "eventlist",
True).get_value()
- SPECTRUM = VocabularyTerm(_OBSCORE_VOCAB_NS, "spectrum", True).get_value()
- TIMESERIES = VocabularyTerm(_OBSCORE_VOCAB_NS, "timeseries",
+ SPECTRUM = VocabularyTerm(DATA_PRODUCT_TYPE_VOCAB, "spectrum", True).get_value()
+ TIMESERIES = VocabularyTerm(DATA_PRODUCT_TYPE_VOCAB, "timeseries",
True).get_value()
- VISIBILITY = VocabularyTerm(_OBSCORE_VOCAB_NS, "visibility",
+ VISIBILITY = VocabularyTerm(DATA_PRODUCT_TYPE_VOCAB, "visibility",
True).get_value()
- MEASUREMENTS = VocabularyTerm(_OBSCORE_VOCAB_NS, "measurements",
+ MEASUREMENTS = VocabularyTerm(DATA_PRODUCT_TYPE_VOCAB, "measurements",
True).get_value()
- CATALOG = VocabularyTerm(_CAOM_VOCAB_NS, "catalog").get_value()
- EVENT = VocabularyTerm(_CAOM_VOCAB_NS, "event", True).get_value()
- SED = VocabularyTerm(_CAOM_VOCAB_NS, "sed", True).get_value()
+ EVENT = VocabularyTerm(DATA_PRODUCT_TYPE_VOCAB, "event-list", True).get_value()
+ SED = VocabularyTerm(DATA_PRODUCT_TYPE_VOCAB, "sed", True).get_value()
+
+ CATALOG = VocabularyTerm(_CAOM_DATA_PRODUCT_TYPE_NS, "catalog").get_value()
@staticmethod
def extend(namespace, name):
@@ -205,30 +222,75 @@ class Quality(Enum):
"""
JUNK: junk
"""
- JUNK = VocabularyTerm(_CAOM_VOCAB_NS, "junk", True).get_value()
+ JUNK = VocabularyTerm(_CAOM_DATA_PRODUCT_TYPE_NS, "junk", True).get_value()
-class Observable():
+class Observable(CaomObject):
""" Observable class"""
- def __init__(self, ucd):
- self.ucd = ucd
+ def __init__(self, ucd, calibration=None):
+ if not ucd:
+ raise ValueError("Observable.ucd cannot be None")
+ caom_util.type_check(ucd, Ucd, 'ucd')
+ self._ucd = ucd.value
+ self.calibration = calibration
@property
def ucd(self):
return self._ucd
- @ucd.setter
- def ucd(self, value):
- caom_util.type_check(value, str, 'ucd', override=False)
- self._ucd = value
+ @property
+ def calibration(self):
+ return self._calibration
+
+ @calibration.setter
+ def calibration(self, value):
+ if value is not None:
+ caom_util.type_check(value, CalibrationStatus, 'calibration', override=False)
+ self._calibration = CalibrationStatus(value)
+ else:
+ self._calibration = None
+
+
+class Visibility(CaomObject):
+
+ def __init__(self, distance, distribution_eccentricity, distribution_fill):
+
+ if distance is not None:
+ caom_util.type_check(distance, shape.Interval, 'distance')
+ else:
+ raise ValueError("Visibility.distance cannot be None")
+ self._distance = distance
+
+ if distribution_eccentricity is not None:
+ caom_util.type_check(distribution_eccentricity, float, 'distribution_eccentricity')
+ else:
+ raise ValueError("Visibility.distribution_eccentricity cannot be None")
+ self._distribution_eccentricity = distribution_eccentricity
+
+ if distribution_fill is not None:
+ caom_util.type_check(distribution_fill, float, 'distribution_fill')
+ else:
+ raise ValueError("Visibility.distribution_fill cannot be None")
+ self._distribution_fill = distribution_fill
+
+ @property
+ def distance(self):
+ return self._distance
+
+ @property
+ def distribution_eccentricity(self):
+ return self._distribution_eccentricity
+
+ @property
+ def distribution_fill(self):
+ return self._distribution_fill
class Plane(AbstractCaomEntity):
""" Plane class """
- def __init__(self, product_id,
- creator_id=None,
+ def __init__(self, uri,
artifacts=None,
meta_release=None,
data_release=None,
@@ -239,18 +301,19 @@ def __init__(self, product_id,
provenance=None,
metrics=None,
quality=None,
- observable=None):
+ observable=None,
+ visibility=None):
"""
Initialize a Plane instance
Arguments:
- product_id : product ID
+ uri : product URI
"""
super(Plane, self).__init__()
- self.product_id = product_id
+ validate_uri(uri)
+ self._uri = uri
if artifacts is None:
artifacts = caom_util.TypedOrderedDict(Artifact, )
- self.creator_id = creator_id
self.artifacts = artifacts
self.meta_release = meta_release
@@ -271,46 +334,24 @@ def __init__(self, product_id,
self._polarization = None
self._custom = None
self.observable = observable
+ self.visibility = visibility
def _key(self):
- return self.product_id
+ return self.uri
def __hash__(self):
return hash(self._key())
# Properties
@property
- def product_id(self):
- """A string that identifies the data product, within a given
+ def uri(self):
+ """A URI that identifies the data product, within a given
observation, that is stored in this plane.
eg: '1234567p'
type: unicode string
"""
- return self._product_id
-
- @product_id.setter
- def product_id(self, value):
- caom_util.type_check(value, str, 'product_id', override=False)
- self._product_id = value
-
- @property
- def creator_id(self):
- """A URI that identifies the creator of this plane.
-
- eg: ivo://cadc.nrc.ca/users?tester
- type: URI
- """
- return self._creator_id
-
- @creator_id.setter
- def creator_id(self, value):
- caom_util.type_check(value, str, 'creator_id')
- if value is not None:
- tmp = urlsplit(value)
- if tmp.geturl() != value:
- raise ValueError("Invalid URI: " + value)
- self._creator_id = value
+ return self._uri
@property
def artifacts(self):
@@ -487,7 +528,7 @@ def observable(self):
@observable.setter
def observable(self, value):
- caom_util.type_check(value, str, 'observable')
+ caom_util.type_check(value, Observable, 'observable')
self._observable = value
@property
@@ -590,101 +631,102 @@ def compute_polarization(self):
"Aggregation of polarization " +
"has not been implemented in this module")
-
-class PlaneURI(CaomObject):
- """ Plane URI """
-
- def __init__(self, uri):
- """
- Initializes an Plane instance
-
- Arguments:
- uri : URI corresponding to the plane
-
- Throws:
- TypeError : if uri is not a string
- ValueError : if uri is invalid
- ValueError : if the uri is valid but does not contain the expected
- fields (collection, observation_id and product_id)
- """
-
- self.uri = uri
-
- def _key(self):
- return self.uri
-
- def __hash__(self):
- return hash(self._key())
-
- def __lt__(self, other):
- if not isinstance(other, PlaneURI):
- raise ValueError(
- 'Cannot compare PlaneURI with {}'.format(type(other)))
- return self.uri < other.uri
-
- def __eq__(self, other):
- if not isinstance(other, PlaneURI):
- raise ValueError(
- 'Cannot compare PlaneURI with {}'.format(type(other)))
- return self.uri == other.uri
-
- @classmethod
- def get_plane_uri(cls, observation_uri, product_id):
- """
- Initializes an Plane URI instance
-
- Arguments:
- observation_uri : the uri of the observation
- product_id : ID of the product
- """
- caom_util.type_check(observation_uri, ObservationURI,
- "observation_uri",
- override=False)
- caom_util.type_check(product_id, str, "observation_uri",
- override=False)
- caom_util.validate_path_component(cls, "product_id", product_id)
-
- path = urlsplit(observation_uri.uri).path
- uri = SplitResult(ObservationURI._SCHEME, "", path + "/" +
- product_id, "", "").geturl()
- return cls(uri)
-
- # Properties
@property
- def uri(self):
- """A uri that locates the plane object inside caom"""
- return self._uri
-
- @uri.setter
- def uri(self, value):
-
- caom_util.type_check(value, str, "uri", override=False)
- tmp = urlsplit(value)
-
- if tmp.scheme != ObservationURI._SCHEME:
- raise ValueError("{} doesn't have an allowed scheme".format(value))
- if tmp.geturl() != value:
- raise ValueError("Failed to parse uri correctly: {}".format(value))
-
- (collection, observation_id, product_id) = tmp.path.split("/")
-
- if product_id is None:
- raise ValueError("Faield to get product ID from uri: {}"
- .format(value))
-
- self._product_id = product_id
- self._observation_uri = \
- ObservationURI.get_observation_uri(collection, observation_id)
- self._uri = value
-
- def get_product_id(self):
- """return the product_id associated with this plane"""
- return self._product_id
-
- def get_observation_uri(self):
- """Return the uri that can be used to find the caom2 observation object that
- this plane belongs to"""
- return self._observation_uri
+ def visibility(self):
+ return self._visibility
+
+ @visibility.setter
+ def visibility(self, value):
+ if value:
+ caom_util.type_check(value, Visibility, "visibility")
+ self._visibility = value
+
+
+# TODO not sure this is needed anymore
+# class PlaneURI(CaomObject):
+# """ Plane URI """
+# def __init__(self, uri):
+# """
+# Initializes a Plane instance
+#
+# Arguments:
+# uri : URI corresponding to the plane
+#
+# Throws:
+# TypeError : if uri is not a string
+# ValueError : if uri is invalid
+# ValueError : if the uri is valid but does not contain the expected
+# fields (collection, observation_id and product_id)
+# """
+#
+# self.uri = uri
+#
+# def _key(self):
+# return self.uri
+#
+# def __hash__(self):
+# return hash(self._key())
+#
+# def __lt__(self, other):
+# if not isinstance(other, PlaneURI):
+# raise ValueError(
+# 'Cannot compare PlaneURI with {}'.format(type(other)))
+# return self.uri < other.uri
+#
+# def __eq__(self, other):
+# if not isinstance(other, PlaneURI):
+# raise ValueError(
+# 'Cannot compare PlaneURI with {}'.format(type(other)))
+# return self.uri == other.uri
+#
+# @classmethod
+# def get_plane_uri(cls, observation_uri, product_id):
+# """
+# Initializes an Plane URI instance
+#
+# Arguments:
+# observation_uri : the uri of the observation
+# product_id : ID of the product
+# """
+# caom_util.type_check(observation_uri, ObservationURI,
+# "observation_uri",
+# override=False)
+# caom_util.type_check(product_id, str, "product_id",
+# override=False)
+# caom_util.validate_path_component(cls, "product_id", product_id)
+#
+# path = urlsplit(observation_uri.uri).path
+# uri = SplitResult(ObservationURI._SCHEME, "", path + "/" +
+# product_id, "", "").geturl()
+# return cls(uri)
+#
+# # Properties
+# @property
+# def uri(self):
+# """A uri that locates the plane object inside caom"""
+# return self._uri
+#
+# @uri.setter
+# def uri(self, value):
+#
+# caom_util.type_check(value, str, "uri", override=False)
+# tmp = urlsplit(value)
+#
+# if tmp.scheme != ObservationURI._SCHEME:
+# raise ValueError("{} doesn't have an allowed scheme".format(value))
+# if tmp.geturl() != value:
+# raise ValueError("Failed to parse uri correctly: {}".format(value))
+#
+# (collection, observation_id, product_id) = tmp.path.split("/")
+#
+# if product_id is None:
+# raise ValueError("Faield to get product ID from uri: {}"
+# .format(value))
+#
+# self._product_id = product_id
+# self._observation_uri = \
+# ObservationURI.get_observation_uri(collection, observation_id)
+# self._uri = value
class DataQuality(CaomObject):
@@ -852,7 +894,7 @@ def __init__(self, name,
self.last_executed = last_executed
self._keywords = set()
- self._inputs = caom_util.TypedSet(PlaneURI, )
+ self._inputs = caom_util.TypedSet(str, )
# Properties
@@ -939,12 +981,15 @@ def inputs(self):
class Position(CaomObject):
""" Position """
- def __init__(self, bounds=None,
+ def __init__(self, bounds,
+ samples,
+ min_bounds=None,
dimension=None,
+ max_recoverable_scale=None,
resolution=None,
resolution_bounds=None,
sample_size=None,
- time_dependent=None
+ calibration=None
):
"""
Initialize a Position instance.
@@ -952,12 +997,24 @@ def __init__(self, bounds=None,
Arguments:
None
"""
- self.bounds = bounds
+ if not bounds:
+ raise ValueError("No bounds provided")
+ caom_util.type_check(bounds,
+ (shape.Box, shape.Circle, shape.Polygon),
+ 'bounds', override=False)
+ self._bounds = bounds
+ if not samples:
+ # TODO - not sure whether to do this or create default samples from bounds
+ raise ValueError("No samples provided")
+ caom_util.type_check(samples, shape.MultiShape, 'samples')
+ self._samples = samples
+ self.min_bounds = min_bounds
self.dimension = dimension
+ self.max_recoverable_scale = max_recoverable_scale
self.resolution = resolution
self.resolution_bounds = resolution_bounds
self.sample_size = sample_size
- self.time_dependent = time_dependent
+ self.calibration = calibration
# Properties
@@ -966,13 +1023,23 @@ def bounds(self):
""" Bounds """
return self._bounds
- @bounds.setter
- def bounds(self, value):
+ @property
+ def samples(self):
+ """ Samples """
+ return self._samples
+
+ @property
+ def min_bounds(self):
+ """ Minimum bounds """
+ return self._min_bounds
+
+ @min_bounds.setter
+ def min_bounds(self, value):
if value is not None:
caom_util.type_check(value,
(shape.Box, shape.Circle, shape.Polygon),
- 'bounds', override=False)
- self._bounds = value
+ 'min_bounds', override=False)
+ self._min_bounds = value
@property
def dimension(self):
@@ -986,6 +1053,18 @@ def dimension(self, value):
'dimension', override=False)
self._dimension = value
+ @property
+ def max_recoverable_scale(self):
+ """ Maximum Recoverable Scale """
+ return self._max_recoverable_scale
+
+ @max_recoverable_scale.setter
+ def max_recoverable_scale(self, value):
+ if value is not None:
+ caom_util.type_check(value, shape.Interval, 'max_recoverable_scale',
+ override=False)
+ self._max_recoverable_scale = value
+
@property
def resolution(self):
""" Resolution """
@@ -1020,24 +1099,25 @@ def sample_size(self, value):
self._sample_size = value
@property
- def time_dependent(self):
- """ Time dependent """
- return self._time_dependent
+ def calibration(self):
+ return self._calibration
- @time_dependent.setter
- def time_dependent(self, value):
+ @calibration.setter
+ def calibration(self, value):
if value is not None:
- caom_util.type_check(value, bool, 'time_dependent')
- self._time_dependent = value
+ caom_util.type_check(value, str, 'calibration', override=False)
+ self._calibration = CalibrationStatus(value)
+ else:
+ self._calibration = None
class Energy(CaomObject):
""" Energy """
- def __init__(self, bounds=None, dimension=None, resolving_power=None,
- resolving_power_bounds=None, energy_bands=None,
- sample_size=None, bandpass_name=None, em_band=None,
- transition=None, restwav=None):
+ def __init__(self, bounds, samples, dimension=None, resolving_power=None,
+ resolving_power_bounds=None, resolution=None, resolution_bounds=None,
+ energy_bands=None, sample_size=None, bandpass_name=None, em_band=None,
+ transition=None, rest=None, calibration=None):
"""
Initialize an Energy instance.
@@ -1045,9 +1125,12 @@ def __init__(self, bounds=None, dimension=None, resolving_power=None,
None
"""
self.bounds = bounds
+ self.samples = samples
self.dimension = dimension
self.resolving_power = resolving_power
self.resolving_power_bounds = resolving_power_bounds
+ self.resolution = resolution
+ self.resolution_bounds = resolution_bounds
self.sample_size = sample_size
self.bandpass_name = bandpass_name
self.energy_bands = energy_bands
@@ -1055,7 +1138,8 @@ def __init__(self, bounds=None, dimension=None, resolving_power=None,
if em_band is not None:
self.energy_bands.add(em_band)
self.transition = transition
- self.restwav = restwav
+ self.rest = rest
+ self.calibration = calibration
# Properties
@@ -1070,6 +1154,24 @@ def bounds(self, value):
caom_util.type_check(value, shape.Interval, 'bounds')
self._bounds = value
+ @property
+ def samples(self):
+ return self._samples
+
+ @samples.setter
+ def samples(self, value):
+ """
+ value is a List of intervals
+ """
+ if value is None:
+ raise AttributeError('samples in Energy cannot be None')
+ else:
+ caom_util.type_check(value, list, 'samples')
+ if len(value) == 0:
+ raise ValueError('samples in Energy cannot be empty')
+ # TODO - could check that the intervals are within the bounds?
+ self._samples = value
+
@property
def dimension(self):
"""DIMENSION (NUMBER OF PIXELS) ALONG ENERGY AXIS."""
@@ -1104,6 +1206,26 @@ def resolving_power_bounds(self, value):
'resolving power bounds')
self._resolving_power_bounds = value
+ @property
+ def resolution(self):
+ return self._resolution
+
+ @resolution.setter
+ def resolution(self, value):
+ if value is not None:
+ caom_util.type_check(value, float, 'resolution')
+ self._resolution = value
+
+ @property
+ def resolution_bounds(self):
+ return self._resolution_bounds
+
+ @resolution_bounds.setter
+ def resolution_bounds(self, value):
+ if value is not None:
+ caom_util.type_check(value, shape.Interval, 'resolution bounds')
+ self._resolution_bounds = value
+
@property
def sample_size(self):
""" Sample size """
@@ -1167,15 +1289,27 @@ def transition(self, value):
self._transition = value
@property
- def restwav(self):
+ def rest(self):
""" rest wavelength of the target energy transition """
- return self._restwav
+ return self._rest
- @restwav.setter
- def restwav(self, value):
+ @rest.setter
+ def rest(self, value):
if value is not None:
- caom_util.type_check(value, float, 'restwav')
- self._restwav = value
+ caom_util.type_check(value, float, 'rest')
+ self._rest = value
+
+ @property
+ def calibration(self):
+ return self._calibration
+
+ @calibration.setter
+ def calibration(self, value):
+ if value is not None:
+ caom_util.type_check(value, str, 'calibration', override=False)
+ self._calibration = CalibrationStatus(value)
+ else:
+ self._calibration = None
class Polarization(CaomObject):
@@ -1183,7 +1317,7 @@ class Polarization(CaomObject):
def __init__(self,
dimension=None,
- polarization_states=None):
+ states=None):
"""
Initialize a Polarization instance.
@@ -1191,7 +1325,7 @@ def __init__(self,
None
"""
self.dimension = dimension
- self.polarization_states = polarization_states
+ self.states = states
# Properties
@property
@@ -1205,35 +1339,38 @@ def dimension(self):
@dimension.setter
def dimension(self, value):
- caom_util.type_check(value, int, 'dimension')
+ caom_util.type_check(value, int_32, 'dimension')
caom_util.value_check(value, 0, 1E10, 'dimension')
- self._dimension = value
+ self._dimension = int_32(value) if value is not None else None
@property
- def polarization_states(self):
+ def states(self):
"""
type: list
"""
- return self._polarization_states
+ return self._states
- @polarization_states.setter
- def polarization_states(self, value):
- if value is not None:
- caom_util.type_check(value, list, 'polarization_states',
- override=False)
- self._polarization_states = value
+ @states.setter
+ def states(self, value):
+ if not value:
+ raise AttributeError('Polarization.state required')
+ caom_util.type_check(value, list, 'states', override=False)
+ self._states = value
class Time(CaomObject):
""" Time """
def __init__(self,
- bounds=None,
+ bounds,
+ samples,
dimension=None,
resolution=None,
resolution_bounds=None,
sample_size=None,
- exposure=None):
+ exposure=None,
+ exposure_bounds=None,
+ calibration=None):
"""
Initialize a Time instance.
@@ -1241,11 +1378,14 @@ def __init__(self,
None
"""
self.bounds = bounds
+ self.samples = samples
self.dimension = dimension
self.resolution = resolution
self.resolution_bounds = resolution_bounds
self.sample_size = sample_size
self.exposure = exposure
+ self.exposure_bounds = exposure_bounds
+ self.calibration = calibration
# Properties
@@ -1267,6 +1407,25 @@ def bounds(self, value):
caom_util.type_check(value, shape.Interval, 'bounds')
self._bounds = value
+ @property
+ def samples(self):
+ return self._samples
+
+ @samples.setter
+ def samples(self, value):
+ """
+ value is a List of intervals
+ """
+ if value is None:
+ raise AttributeError('samples in Time cannot be None')
+ else:
+ caom_util.type_check(value, list,
+ 'samples')
+ if len(value) == 0:
+ raise ValueError('samples in Time cannot be empty')
+ # TODO - could check that the intervals are within the bounds?
+ self._samples = value
+
@property
def dimension(self):
"""Number of pixel in the time direction, normally 1.
@@ -1335,6 +1494,29 @@ def exposure(self, value):
caom_util.type_check(value, float, 'exposure')
self._exposure = value
+ @property
+ def exposure_bounds(self):
+ """ Exposure bounds"""
+ return self._exposure_bounds
+
+ @exposure_bounds.setter
+ def exposure_bounds(self, value):
+ if value is not None:
+ caom_util.type_check(value, shape.Interval, 'exposure bounds')
+ self._exposure_bounds = value
+
+ @property
+ def calibration(self):
+ return self._calibration
+
+ @calibration.setter
+ def calibration(self, value):
+ if value is not None:
+ caom_util.type_check(value, str, 'calibration', override=False)
+ self._calibration = CalibrationStatus(value)
+ else:
+ self._calibration = None
+
class CustomAxis(CaomObject):
"""
@@ -1344,7 +1526,8 @@ class CustomAxis(CaomObject):
def __init__(self,
ctype,
- bounds=None,
+ bounds,
+ samples,
dimension=None):
"""
Initialize a Custom Axis instance.
@@ -1352,8 +1535,11 @@ def __init__(self,
if ctype is None:
raise AttributeError('ctype of CustomAxis cannot be None')
self._ctype = ctype
+ if bounds is None:
+ raise AttributeError('bounds of CustomAxis cannot be None')
self.bounds = bounds
self.dimension = dimension
+ self.samples = samples
# Properties
@@ -1384,3 +1570,21 @@ def dimension(self):
def dimension(self, value):
caom_util.type_check(value, int, 'dimension')
self._dimension = value
+
+ @property
+ def samples(self):
+ return self._samples
+
+ @samples.setter
+ def samples(self, value):
+ """
+ value is a List of intervals
+ """
+ if value is None:
+ raise AttributeError('samples in CustomAxis cannot be None')
+ else:
+ caom_util.type_check(value, list, 'samples')
+ if len(value) == 0:
+ raise ValueError('samples in CustomAxis cannot be empty')
+ # TODO - could check that the intervals are within the bounds?
+ self._samples = value
diff --git a/caom2/caom2/shape.py b/caom2/caom2/shape.py
index 2c4ee304..29fb8a1c 100644
--- a/caom2/caom2/shape.py
+++ b/caom2/caom2/shape.py
@@ -70,13 +70,14 @@
from caom2.caom_util import int_32
from . import caom_util
from . import common
+from . import dali
import warnings
with warnings.catch_warnings():
warnings.simplefilter('ignore')
from aenum import Enum
__all__ = ['SegmentType', 'Box', 'Circle', 'Interval', 'Point',
- 'Polygon', 'Vertex', 'MultiPolygon']
+ 'Polygon', 'Vertex', 'MultiShape']
class SegmentType(Enum):
@@ -84,6 +85,8 @@ class SegmentType(Enum):
CLOSE: 0
LINE: 1
MOVE: 2
+
+ Deprecated: starting CAOM2.5 Vertex and SegmentType are deprecated
"""
CLOSE = int_32(0)
LINE = int_32(1)
@@ -241,110 +244,7 @@ def upper(self, value):
self._upper = value
-class Interval(common.CaomObject):
- def __init__(self, lower, upper, samples=None):
-
- self.lower = lower
- self.upper = upper
- self.samples = samples
- self.validate()
-
- def get_width(self):
- return self._upper - self._lower
-
- @classmethod
- def intersection(cls, i1, i2):
- if i1.lower > i2.upper or i1.upper < i2.lower:
- return None
-
- lb = max(i1.lower, i2.lower)
- ub = min(i1.upper, i2.upper)
- return cls(lb, ub)
-
- # Properties
-
- @property
- def lower(self):
- """
- type: float
- """
- return self._lower
-
- @lower.setter
- def lower(self, value):
- caom_util.type_check(value, float, 'lower', override=False)
- has_upper = True
- try:
- self._upper
- except AttributeError:
- has_upper = False
- if has_upper and self._upper < value:
- raise ValueError("Interval: attempt to set upper < lower "
- "for {}, {}".format(self._upper, value))
- self._lower = value
-
- @property
- def upper(self):
- """
- type: float
- """
- return self._upper
-
- @upper.setter
- def upper(self, value):
- caom_util.type_check(value, float, 'upper', override=False)
- has_lower = True
- try:
- self._lower
- except AttributeError:
- has_lower = False
- if has_lower and value < self._lower:
- raise ValueError("Interval: attempt to set upper < lower "
- "for {}, {}".format(value, self._lower))
- self._upper = value
-
- @property
- def samples(self):
- """
- type: list
- """
- return self._samples
-
- @samples.setter
- def samples(self, value):
- if value is not None:
- caom_util.type_check(value, list, 'samples', override=False)
- self._samples = value
-
- def validate(self):
- """
- Performs a validation of the current object.
-
- An AssertionError is thrown if the object does not represent an
- Interval
- """
- if self._samples is not None:
-
- if len(self._samples) == 0:
- raise ValueError(
- 'invalid interval (samples cannot be empty)')
-
- prev = None
- for sample in self._samples:
- if sample.lower < self._lower:
- raise ValueError(
- 'invalid interval: sample extends below lower bound: '
- '{} vs {}'.format(sample, self._lower))
- if sample.upper > self._upper:
- raise ValueError(
- 'invalid interval: sample extends above upper bound: '
- '{} vs {}'.format(sample, self._upper))
- if prev is not None:
- if sample.lower <= prev.upper:
- raise ValueError(
- 'invalid interval: sample overlaps previous '
- 'sample:\n{}\nvs\n{}'.format(sample, prev))
- prev = sample
+Interval = dali.Interval # Moved to dali
class Point(common.CaomObject):
@@ -404,29 +304,35 @@ def samples(self):
@samples.setter
def samples(self, value):
if value is not None:
- caom_util.type_check(value, MultiPolygon, 'multipolygon',
+ caom_util.type_check(value, MultiShape, 'multipolygon',
override=False)
self._samples = value
-class MultiPolygon(common.CaomObject):
- def __init__(self, vertices=None):
- if vertices is None:
- self._vertices = []
- else:
- self._vertices = vertices
+class MultiShape(common.CaomObject):
+ def __init__(self, shapes):
+ """
+ :param shapes: list of shapes
+ """
+ super().__init__()
+ if not shapes:
+ raise ValueError("MultiShape: shapes must be non-empty")
+ self._shapes = shapes
# Properties
@property
- def vertices(self):
+ def shapes(self):
"""
- type: list of Vertices
+ type: list of shapes
"""
- return self._vertices
+ return self._shapes
class Vertex(Point):
+ """
+ Deprecated: starting CAOM2.5 Vertex and SegmentType are deprecated
+ """
def __init__(self, cval1, cval2, type):
super(Vertex, self).__init__(cval1, cval2)
self.type = type
diff --git a/caom2/caom2/tests/caom_test_instances.py b/caom2/caom2/tests/caom_test_instances.py
index 61acf90f..7115622b 100644
--- a/caom2/caom2/tests/caom_test_instances.py
+++ b/caom2/caom2/tests/caom_test_instances.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -73,10 +73,11 @@
import uuid
from builtins import int
-from caom2 import artifact
+from caom2 import artifact, MultiShape, Polarization
from caom2 import caom_util
from caom2 import chunk
from caom2 import common
+from caom2 import dali
from caom2 import observation
from caom2 import part
from caom2 import plane
@@ -105,7 +106,7 @@ def get_64bit_uuid(id):
class Caom2TestInstances(object):
_collection = "collection"
- _observation_id = "observationID"
+ _uri = "caom:collection/observationID"
_product_id = "productId"
_keywords = {"keyword1", "keyword2"}
_ivoa_date = datetime(2012, 7, 11, 13, 26, 37, 0)
@@ -151,7 +152,7 @@ def caom_version(self, v):
def get_simple_observation(self, short_uuid=False):
simple_observation = \
observation.SimpleObservation(Caom2TestInstances._collection,
- Caom2TestInstances._observation_id)
+ Caom2TestInstances._uri)
if self.complete:
simple_observation.sequence_number = int(5)
simple_observation.obs_type = "flat"
@@ -168,10 +169,8 @@ def get_simple_observation(self, short_uuid=False):
if self.caom_version >= 23:
simple_observation.max_last_modified =\
common.get_current_ivoa_time()
- simple_observation.meta_checksum = common.ChecksumURI(
- "md5:9882dbbf9cadc221019b712fd402bcbd")
- simple_observation.acc_meta_checksum = common.ChecksumURI(
- "md5:844ce247db0844ad9f721430c80e7a21")
+ simple_observation.meta_checksum = "md5:9882dbbf9cadc221019b712fd402bcbd"
+ simple_observation.acc_meta_checksum = "md5:844ce247db0844ad9f721430c80e7a21"
if self.caom_version >= 24:
simple_observation.meta_read_groups.add(
"ivo://cadc.nrc.ca/groups?A")
@@ -187,7 +186,7 @@ def get_composite_observation(self, short_uuid=False):
composite_observation = \
observation.CompositeObservation(
Caom2TestInstances._collection,
- Caom2TestInstances._observation_id,
+ Caom2TestInstances._uri,
self.get_algorithm())
print("Creating test composite observation of version " + str(
self.caom_version))
@@ -208,10 +207,8 @@ def get_composite_observation(self, short_uuid=False):
if self.caom_version >= 23:
composite_observation.max_last_modified = \
common.get_current_ivoa_time()
- composite_observation.meta_checksum = common.ChecksumURI(
- "md5:9882dbbf9cadc221019b712fd402bcbd")
- composite_observation.acc_meta_checksum = common.ChecksumURI(
- "md5:844ce247db0844ad9f721430c80e7a21")
+ composite_observation.meta_checksum = "md5:9882dbbf9cadc221019b712fd402bcbd"
+ composite_observation.acc_meta_checksum = "md5:844ce247db0844ad9f721430c80e7a21"
if self.depth > 1:
composite_observation.planes.update(self.get_planes())
composite_observation.members.update(self.get_members())
@@ -221,7 +218,7 @@ def get_derived_observation(self, short_uuid=False):
derived_observation = \
observation.DerivedObservation(
Caom2TestInstances._collection,
- Caom2TestInstances._observation_id,
+ Caom2TestInstances._uri,
self.get_algorithm())
print("Creating test composite observation of version " + str(
self.caom_version))
@@ -241,10 +238,8 @@ def get_derived_observation(self, short_uuid=False):
common.get_current_ivoa_time()
derived_observation.max_last_modified = \
common.get_current_ivoa_time()
- derived_observation.meta_checksum = common.ChecksumURI(
- "md5:9882dbbf9cadc221019b712fd402bcbd")
- derived_observation.acc_meta_checksum = common.ChecksumURI(
- "md5:844ce247db0844ad9f721430c80e7a21")
+ derived_observation.meta_checksum = "md5:9882dbbf9cadc221019b712fd402bcbd"
+ derived_observation.acc_meta_checksum = "md5:844ce247db0844ad9f721430c80e7a21"
derived_observation.meta_read_groups.add(
"ivo://cadc.nrc.ca/groups?A")
derived_observation.meta_read_groups.add(
@@ -309,9 +304,7 @@ def get_environment(self):
return env
def get_members(self):
- members = caom_util.TypedSet(
- observation.ObservationURI,
- observation.ObservationURI("caom:foo/bar"))
+ members = caom_util.TypedSet(str, "caom:foo/bar")
return members
def get_planes(self):
@@ -324,8 +317,8 @@ def get_planes(self):
shapes = ['polygon']
for s in shapes:
- prod_id = "productID{}".format(s)
- _plane = plane.Plane(prod_id)
+ plane_uri = "{}/{}".format(self._uri, s)
+ _plane = plane.Plane(plane_uri)
if self.complete:
_plane.meta_release = Caom2TestInstances._ivoa_date
_plane.data_release = Caom2TestInstances._ivoa_date
@@ -337,10 +330,8 @@ def get_planes(self):
if self.caom_version >= 23:
_plane.creator_id = "ivo://cadc.nrc.ca?testuser"
_plane.max_last_modified = common.get_current_ivoa_time()
- _plane.meta_checksum = common.ChecksumURI(
- "md5:9882dbbf9cadc221019b712fd402bcbd")
- _plane.acc_meta_checksum = common.ChecksumURI(
- "md5:844ce247db0844ad9f721430c80e7a21")
+ _plane.meta_checksum = "md5:9882dbbf9cadc221019b712fd402bcbd"
+ _plane.acc_meta_checksum = "md5:844ce247db0844ad9f721430c80e7a21"
if s == 'polygon':
_plane.position = self.get_poly_position()
if s == 'circle':
@@ -358,67 +349,50 @@ def get_planes(self):
if self.depth > 2:
for k, v in self.get_artifacts().items():
_plane.artifacts[k] = v
- planes[prod_id] = _plane
+ planes[_plane.uri] = _plane
return planes
def get_poly_position(self):
- position = plane.Position()
-
- if self.caom_version >= 23:
- v0 = shape.Vertex(0.0, 0.0, shape.SegmentType.MOVE)
- v1 = shape.Vertex(3.0, 4.0, shape.SegmentType.LINE)
- v2 = shape.Vertex(2.0, 3.0, shape.SegmentType.LINE)
- v3 = shape.Vertex(1.0, 2.0, shape.SegmentType.LINE)
- v4 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
- vl = [v0, v1, v2, v3, v4]
-
- samples = shape.MultiPolygon(vertices=vl)
-
- p1 = shape.Point(0.0, 0.0)
- p2 = shape.Point(3.0, 4.0)
- p3 = shape.Point(2.0, 3.0)
- p4 = shape.Point(1.0, 2.0)
- p = [p1, p2, p3, p4]
- polygon = shape.Polygon(points=p, samples=samples)
-
- position.bounds = polygon
+ p1 = shape.Point(0.0, 0.0)
+ p2 = shape.Point(3.0, 4.0)
+ p3 = shape.Point(2.0, 3.0)
+ p4 = shape.Point(1.0, 2.0)
+ p = [p1, p2, p3, p4]
+ polygon = shape.Polygon(points=p)
+ position = plane.Position(polygon, MultiShape([polygon]))
position.dimension = wcs.Dimension2D(10, 20)
position.resolution = 0.5
position.sample_size = 1.1
position.time_dependent = False
if self.caom_version >= 24:
- position.resolution_bounds = shape.Interval(1.0, 2.0)
+ position.resolution_bounds = dali.Interval(1.0, 2.0)
return position
def get_circle_position(self):
- position = plane.Position()
- position.bounds = shape.Circle(shape.Point(1.1, 2.2), 3.0)
+ circle = shape.Circle(shape.Point(1.1, 2.2), 3.0)
+ position = plane.Position(circle, MultiShape([circle]))
position.dimension = wcs.Dimension2D(10, 20)
position.resolution = 0.5
position.sample_size = 1.1
position.time_dependent = False
if self.caom_version >= 24:
- position.resolution_bounds = shape.Interval(1.0, 2.0)
+ position.resolution_bounds = dali.Interval(1.0, 2.0)
return position
def get_energy(self):
- energy = plane.Energy()
-
lower = 1.0
upper = 2.0
lower1 = 1.1
upper1 = 2.1
lower2 = 1.2
upper2 = 2.2
- samples = [shape.SubInterval(lower, lower1),
- shape.SubInterval(lower2, upper),
- shape.SubInterval(upper1, upper2)]
-
- interval = shape.Interval(lower, upper2, samples)
+ samples = [dali.Interval(lower, lower1),
+ dali.Interval(lower2, upper),
+ dali.Interval(upper1, upper2)]
+ energy = plane.Energy(dali.Interval(lower, upper2), samples)
- energy.bounds = interval
energy.dimension = 100
energy.resolving_power = 2.0
energy.sample_size = 1.1
@@ -431,23 +405,19 @@ def get_energy(self):
return energy
def get_time(self):
- time = plane.Time()
-
lower = 1.0
upper = 2.0
lower1 = 1.1
upper1 = 2.1
lower2 = 1.2
upper2 = 2.2
- samples = [shape.SubInterval(lower, lower1),
- shape.SubInterval(lower2, upper),
- shape.SubInterval(upper1, upper2)]
-
- interval = shape.Interval(lower, upper2, samples)
+ samples = [dali.Interval(lower, lower1),
+ dali.Interval(lower2, upper),
+ dali.Interval(upper1, upper2)]
+ time = plane.Time(dali.Interval(lower, upper2), samples)
- time.bounds = interval
if self.caom_version >= 24:
- time.resolution_bounds = shape.Interval(22.2, 33.3)
+ time.resolution_bounds = dali.Interval(22.2, 33.3)
time.dimension = 1
time.resolution = 2.1
time.sample_size = 3.0
@@ -456,19 +426,14 @@ def get_time(self):
return time
def get_custom(self):
- custom = plane.CustomAxis('MyAxis')
- custom.bounds = shape.Interval(2.2, 3.3)
+ bounds = dali.Interval(2.2, 3.3)
+ samples = [bounds]
+ custom = plane.CustomAxis('MyAxis', bounds=bounds, samples=samples)
custom.dimension = 1
def get_polarization(self):
- polarization = plane.Polarization()
-
p_states = [plane.PolarizationState.LL, plane.PolarizationState.XY]
-
- polarization.dimension = 2
- polarization.polarization_states = p_states
-
- return polarization
+ return Polarization(dimension=2, states=p_states)
def get_provenance(self):
provenance = plane.Provenance("name")
@@ -483,9 +448,9 @@ def get_provenance(self):
return provenance
def get_inputs(self):
- return caom_util.TypedSet(plane.PlaneURI,
- plane.PlaneURI("caom:foo/bar/plane1"),
- plane.PlaneURI("caom:foo/bar/plane2"))
+ return caom_util.TypedSet(str,
+ "caom:foo/bar/plane1",
+ "caom:foo/bar/plane2")
def get_metrics(self):
metrics = plane.Metrics()
@@ -504,7 +469,7 @@ def get_quality(self):
def get_artifacts(self):
artifacts = collections.OrderedDict()
_artifact = artifact.Artifact("ad:foo/bar1",
- chunk.ProductType.SCIENCE,
+ chunk.DataLinkSemantics.SCIENCE,
artifact.ReleaseType.META)
if self.complete:
_artifact.content_type = "application/fits"
@@ -512,10 +477,8 @@ def get_artifacts(self):
_artifact.last_modified = common.get_current_ivoa_time()
if self.caom_version >= 23:
_artifact.max_last_modified = common.get_current_ivoa_time()
- _artifact.meta_checksum = common.ChecksumURI(
- "md5:9882dbbf9cadc221019b712fd402bcbd")
- _artifact.acc_meta_checksum = common.ChecksumURI(
- "md5:844ce247db0844ad9f721430c80e7a21")
+ _artifact.meta_checksum = "md5:9882dbbf9cadc221019b712fd402bcbd"
+ _artifact.acc_meta_checksum = "md5:844ce247db0844ad9f721430c80e7a21"
if self.depth > 3:
for k, v in self.get_parts().items():
_artifact.parts[k] = v
@@ -531,7 +494,7 @@ def get_parts(self):
parts = collections.OrderedDict()
_part = part.Part("x")
if self.complete:
- _part.product_type = chunk.ProductType.SCIENCE
+ _part.product_type = chunk.DataLinkSemantics.SCIENCE
if self.depth > 4:
for _chunk in self.get_chunks():
_part.chunks.append(_chunk)
@@ -542,7 +505,7 @@ def get_chunks(self):
chunks = caom_util.TypedList(chunk.Chunk, )
_chunk = chunk.Chunk()
if self.complete:
- _chunk.product_type = chunk.ProductType.SCIENCE
+ _chunk.product_type = chunk.DataLinkSemantics.SCIENCE
_chunk.naxis = 5
_chunk.observable_axis = 1
_chunk.position_axis_1 = 1
@@ -558,10 +521,8 @@ def get_chunks(self):
_chunk.last_modified = common.get_current_ivoa_time()
if self.caom_version >= 23:
_chunk.max_last_modified = common.get_current_ivoa_time()
- _chunk.meta_checksum = common.ChecksumURI(
- "md5:9882dbbf9cadc221019b712fd402bcbd")
- _chunk.acc_meta_checksum = common.ChecksumURI(
- "md5:844ce247db0844ad9f721430c80e7a21")
+ _chunk.meta_checksum = "md5:9882dbbf9cadc221019b712fd402bcbd"
+ _chunk.acc_meta_checksum = "md5:844ce247db0844ad9f721430c80e7a21"
if self.caom_version >= 24:
_chunk.custom_axis = 3
_chunk.custom = self.get_custom_wcs()
diff --git a/caom2/caom2/tests/data/CompleteCompositeCircle-CAOM-2.2.xml b/caom2/caom2/tests/data/CompleteCompositeCircle-CAOM-2.2.xml
deleted file mode 100644
index 2e0b3506..00000000
--- a/caom2/caom2/tests/data/CompleteCompositeCircle-CAOM-2.2.xml
+++ /dev/null
@@ -1,105 +0,0 @@
-
-
- collection
- observationID
- 2012-07-11T13:26:37.000
- 10
-
- algorithmName
-
- filed
- science
-
- proposalId
- proposalPi
- proposalProject
- proposalTitle
- keyword
-
-
- targetName
- object
- false
- 1.5
- false
- keyword
-
-
- coordsys
- 3.0
-
- 1.0
- 2.0
-
-
-
- fail
-
-
- telescopeName
- 1.0
- 2.0
- 3.0
- keyword
-
-
- 0.08
- 0.35
- 2.7
- 0.7
- 0.00045
- 20.0
- true
-
-
-
- productID
- 2012-07-11T13:26:37.000
- 2012-07-11T13:26:37.000
- image
- 3
-
- name
- version
- producer
- run_id
- http://foo/bar
- 2012-07-11T13:26:37.000
- keyword
-
- caom:foo/bar/plane2
- caom:foo/bar/plane1
-
-
-
- 1.0
- 2.0
- 3.0
- 4.0
- 5.0
-
-
- junk
-
-
-
- ad:foo/bar1
- science
- meta
- application/fits
- 12345
-
-
- x
- science
-
-
-
-
-
-
-
-
- caom:foo/bar
-
-
diff --git a/caom2/caom2/tests/data/CompleteCompositeCircle-CAOM-2.3.xml b/caom2/caom2/tests/data/CompleteCompositeCircle-CAOM-2.3.xml
index 74f74055..ac687762 100644
--- a/caom2/caom2/tests/data/CompleteCompositeCircle-CAOM-2.3.xml
+++ b/caom2/caom2/tests/data/CompleteCompositeCircle-CAOM-2.3.xml
@@ -100,7 +100,6 @@
x
science
-
diff --git a/caom2/caom2/tests/data/CompleteCompositePolygon-CAOM-2.2.xml b/caom2/caom2/tests/data/CompleteCompositePolygon-CAOM-2.2.xml
deleted file mode 100644
index 8dfd6c7c..00000000
--- a/caom2/caom2/tests/data/CompleteCompositePolygon-CAOM-2.2.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
- collection
- observationID
- 2012-07-11T13:26:37.000
- 10
-
- algorithmName
-
- filed
- science
-
- proposalId
- proposalPi
- proposalProject
- proposalTitle
- keyword
-
-
- targetName
- object
- false
- 1.5
- false
- keyword
-
-
- coordsys
- 3.0
-
- 1.0
- 2.0
-
-
-
- telescopeName
- 1.0
- 2.0
- 3.0
- keyword
-
-
- 0.08
- 0.35
- 2.7
- 0.7
- 0.00045
- 20.0
- true
-
-
-
- productID
- 2012-07-11T13:26:37.000
- 2012-07-11T13:26:37.000
- image
- 3
-
- name
- version
- producer
- run_id
- http://foo/bar
- 2012-07-11T13:26:37.000
- keyword
-
- caom:foo/bar/plane2
- caom:foo/bar/plane1
-
-
-
- 1.0
- 2.0
- 3.0
- 4.0
- 5.0
-
-
- junk
-
-
-
- ad:foo/bar1
- science
- meta
- application/fits
- 12345
-
-
- x
- science
-
-
-
-
-
-
-
-
- caom:foo/bar
-
-
diff --git a/caom2/caom2/tests/data/CompleteCompositePolygon-CAOM-2.3.xml b/caom2/caom2/tests/data/CompleteCompositePolygon-CAOM-2.3.xml
index 91c3a8a9..311c6495 100644
--- a/caom2/caom2/tests/data/CompleteCompositePolygon-CAOM-2.3.xml
+++ b/caom2/caom2/tests/data/CompleteCompositePolygon-CAOM-2.3.xml
@@ -97,7 +97,6 @@
x
science
-
diff --git a/caom2/caom2/tests/data/CompleteSimpleCircle-CAOM-2.2.xml b/caom2/caom2/tests/data/CompleteSimpleCircle-CAOM-2.2.xml
deleted file mode 100644
index dc4af87e..00000000
--- a/caom2/caom2/tests/data/CompleteSimpleCircle-CAOM-2.2.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
- collection
- observationID
- 2012-07-11T13:26:37.000
- 5
-
- exposure
-
- flat
- calibration
-
- proposalId
- proposalPi
- proposalProject
- proposalTitle
- keyword
-
-
- targetName
- object
- false
- 1.5
- false
- keyword
-
-
- coordsys
- 3.0
-
- 1.0
- 2.0
-
-
-
- fail
-
-
- telescopeName
- 1.0
- 2.0
- 3.0
- keyword
-
-
- 0.08
- 0.35
- 2.7
- 0.7
- 0.00045
- 20.0
- true
-
-
-
- productID
- 2012-07-11T13:26:37.000
- 2012-07-11T13:26:37.000
- image
- 3
-
- name
- version
- producer
- run_id
- http://foo/bar
- 2012-07-11T13:26:37.000
- keyword
-
- caom:foo/bar/plane2
- caom:foo/bar/plane1
-
-
-
- 1.0
- 2.0
- 3.0
- 4.0
- 5.0
-
-
- junk
-
-
-
- ad:foo/bar1
- science
- meta
- application/fits
- 12345
-
-
- x
- science
-
-
-
-
-
-
-
-
diff --git a/caom2/caom2/tests/data/CompleteSimpleCircle-CAOM-2.3.xml b/caom2/caom2/tests/data/CompleteSimpleCircle-CAOM-2.3.xml
index 2f123365..3c90e938 100644
--- a/caom2/caom2/tests/data/CompleteSimpleCircle-CAOM-2.3.xml
+++ b/caom2/caom2/tests/data/CompleteSimpleCircle-CAOM-2.3.xml
@@ -100,7 +100,6 @@
x
science
-
diff --git a/caom2/caom2/tests/data/CompleteSimplePolygon-CAOM-2.2.xml b/caom2/caom2/tests/data/CompleteSimplePolygon-CAOM-2.2.xml
deleted file mode 100644
index 9a8308d5..00000000
--- a/caom2/caom2/tests/data/CompleteSimplePolygon-CAOM-2.2.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
- collection
- observationID
- 2012-07-11T13:26:37.000
- 5
-
- exposure
-
- flat
- calibration
-
- proposalId
- proposalPi
- proposalProject
- proposalTitle
- keyword
-
-
- targetName
- object
- false
- 1.5
- false
- keyword
-
-
- coordsys
- 3.0
-
- 1.0
- 2.0
-
-
-
- fail
-
-
- telescopeName
- 1.0
- 2.0
- 3.0
- keyword
-
-
- 0.08
- 0.35
- 2.7
- 0.7
- 0.00045
- 20.0
- true
-
-
-
- productID
- 2012-07-11T13:26:37.000
- 2012-07-11T13:26:37.000
- catalog
- 3
-
- name
- version
- producer
- run_id
- http://foo/bar
- 2012-07-11T13:26:37.000
- keyword
-
- caom:foo/bar/plane2
- caom:foo/bar/plane1
-
-
-
- 1.0
- 2.0
- 3.0
- 4.0
- 5.0
-
-
- junk
-
-
-
- ad:foo/bar1
- science
- meta
- application/fits
- 12345
-
-
- x
- science
-
-
-
-
-
-
-
-
diff --git a/caom2/caom2/tests/data/CompleteSimplePolygon-CAOM-2.3.xml b/caom2/caom2/tests/data/CompleteSimplePolygon-CAOM-2.3.xml
index 025ab4de..bc622296 100644
--- a/caom2/caom2/tests/data/CompleteSimplePolygon-CAOM-2.3.xml
+++ b/caom2/caom2/tests/data/CompleteSimplePolygon-CAOM-2.3.xml
@@ -100,7 +100,6 @@
x
science
-
diff --git a/caom2/caom2/tests/data/MinimalCompositeCircle-CAOM-2.2.xml b/caom2/caom2/tests/data/MinimalCompositeCircle-CAOM-2.2.xml
deleted file mode 100644
index 03226c1f..00000000
--- a/caom2/caom2/tests/data/MinimalCompositeCircle-CAOM-2.2.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
- collection
- observationID
-
- algorithmName
-
-
-
- productID
-
-
- ad:foo/bar1
- science
- meta
-
-
- x
-
-
-
-
-
-
-
-
- caom:foo/bar
-
-
diff --git a/caom2/caom2/tests/data/MinimalCompositeCircle-CAOM-2.3.xml b/caom2/caom2/tests/data/MinimalCompositeCircle-CAOM-2.3.xml
index 93bf7a10..6894718c 100644
--- a/caom2/caom2/tests/data/MinimalCompositeCircle-CAOM-2.3.xml
+++ b/caom2/caom2/tests/data/MinimalCompositeCircle-CAOM-2.3.xml
@@ -16,7 +16,6 @@
x
-
diff --git a/caom2/caom2/tests/data/MinimalCompositePolygon-CAOM-2.2.xml b/caom2/caom2/tests/data/MinimalCompositePolygon-CAOM-2.2.xml
deleted file mode 100644
index c46144e4..00000000
--- a/caom2/caom2/tests/data/MinimalCompositePolygon-CAOM-2.2.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
- collection
- observationID
-
- algorithmName
-
-
-
- productID
-
-
- ad:foo/bar1
- science
- meta
-
-
- x
-
-
-
-
-
-
-
-
- caom:foo/bar
-
-
diff --git a/caom2/caom2/tests/data/MinimalCompositePolygon-CAOM-2.3.xml b/caom2/caom2/tests/data/MinimalCompositePolygon-CAOM-2.3.xml
index 64bbf828..372c2f47 100644
--- a/caom2/caom2/tests/data/MinimalCompositePolygon-CAOM-2.3.xml
+++ b/caom2/caom2/tests/data/MinimalCompositePolygon-CAOM-2.3.xml
@@ -16,7 +16,6 @@
x
-
diff --git a/caom2/caom2/tests/data/MinimalSimpleCircle-CAOM-2.2.xml b/caom2/caom2/tests/data/MinimalSimpleCircle-CAOM-2.2.xml
deleted file mode 100644
index 85f47d63..00000000
--- a/caom2/caom2/tests/data/MinimalSimpleCircle-CAOM-2.2.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
- collection
- observationID
-
- exposure
-
-
-
- productID
-
-
- ad:foo/bar1
- science
- meta
-
-
- x
-
-
-
-
-
-
-
-
diff --git a/caom2/caom2/tests/data/MinimalSimpleCircle-CAOM-2.3.xml b/caom2/caom2/tests/data/MinimalSimpleCircle-CAOM-2.3.xml
index bd2abefb..952713bd 100644
--- a/caom2/caom2/tests/data/MinimalSimpleCircle-CAOM-2.3.xml
+++ b/caom2/caom2/tests/data/MinimalSimpleCircle-CAOM-2.3.xml
@@ -16,7 +16,6 @@
x
-
diff --git a/caom2/caom2/tests/data/MinimalSimplePolygon-CAOM-2.2.xml b/caom2/caom2/tests/data/MinimalSimplePolygon-CAOM-2.2.xml
deleted file mode 100644
index 742be676..00000000
--- a/caom2/caom2/tests/data/MinimalSimplePolygon-CAOM-2.2.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
- collection
- observationID
-
- exposure
-
-
-
- productID
-
-
- ad:foo/bar1
- science
- meta
-
-
- x
-
-
-
-
-
-
-
-
diff --git a/caom2/caom2/tests/data/MinimalSimplePolygon-CAOM-2.3.xml b/caom2/caom2/tests/data/MinimalSimplePolygon-CAOM-2.3.xml
index 22c1a03c..051b6894 100644
--- a/caom2/caom2/tests/data/MinimalSimplePolygon-CAOM-2.3.xml
+++ b/caom2/caom2/tests/data/MinimalSimplePolygon-CAOM-2.3.xml
@@ -16,7 +16,6 @@
x
-
diff --git a/caom2/caom2/tests/data/SampleComposite-CAOM-2.3.xml b/caom2/caom2/tests/data/SampleComposite-CAOM-2.3.xml
index 26660ac9..7ba88675 100644
--- a/caom2/caom2/tests/data/SampleComposite-CAOM-2.3.xml
+++ b/caom2/caom2/tests/data/SampleComposite-CAOM-2.3.xml
@@ -70,7 +70,6 @@
productID0
- ivo://example.org/foo?productID0
2017-10-10T19:57:03.770
2017-10-10T19:57:03.770
image
@@ -149,7 +148,6 @@
0.05
0.025
- false
@@ -2478,7 +2476,6 @@
productID1
- ivo://example.org/foo?productID1
2017-10-10T19:57:03.770
2017-10-10T19:57:03.770
image
@@ -2524,7 +2521,6 @@
0.05
0.025
- false
diff --git a/caom2/caom2/tests/data/SampleDerived-CAOM-2.4.xml b/caom2/caom2/tests/data/SampleDerived-CAOM-2.4.xml
index 43741dac..9aab32ba 100644
--- a/caom2/caom2/tests/data/SampleDerived-CAOM-2.4.xml
+++ b/caom2/caom2/tests/data/SampleDerived-CAOM-2.4.xml
@@ -75,7 +75,6 @@
productID0
- ivo://example.org/foo?productID0
2019-10-18T20:22:53.979
ivo://cadc.nrc.ca/gms?A
@@ -163,7 +162,6 @@
0.05
0.025
- false
@@ -2881,7 +2879,6 @@
productID1
- ivo://example.org/foo?productID1
2019-10-18T20:22:53.979
ivo://cadc.nrc.ca/gms?A
@@ -2936,7 +2933,6 @@
0.05
0.025
- false
diff --git a/caom2/caom2/tests/data/SampleDerived-CAOM-2.5.xml b/caom2/caom2/tests/data/SampleDerived-CAOM-2.5.xml
new file mode 100644
index 00000000..79ff0377
--- /dev/null
+++ b/caom2/caom2/tests/data/SampleDerived-CAOM-2.5.xml
@@ -0,0 +1,5501 @@
+
+
+ TEST
+ caom:TEST/observationID
+ 7c1
+ 2025-02-05T00:12:21.039
+
+ ivo://cadc.nrc.ca/gms?A
+ ivo://cadc.nrc.ca/gms?B
+
+ 123
+
+ algorithmName
+
+ field
+ science
+
+ proposalId
+ proposalPi
+ proposalProject
+ proposalTitle
+ mast:HST:proposal/123
+
+ keyword1
+ keyword2
+
+
+
+ targetName
+ naif:1701
+ object
+ false
+ 1.5
+ false
+
+ keyword1
+ keyword2
+
+
+
+ FK5
+ 2000.0
+
+ 1.0
+ 2.0
+
+
+
+ fail
+
+
+ telescopeName
+ 1.0
+ 2.0
+ 3.0
+ sidereal
+
+ keyword1
+ keyword2
+
+
+
+ instrumentName
+
+ keyword1
+ keyword2
+
+
+
+ 0.08
+ 0.35
+ 2.7
+ 1.7
+ 4.5E-4
+ 666.0
+ true
+
+
+
+ caom:collection/observation/plane0
+ 2025-02-05T00:12:21.039
+
+ ivo://cadc.nrc.ca/gms?A
+ ivo://cadc.nrc.ca/gms?B
+
+ 2025-02-05T00:12:21.039
+
+ ivo://cadc.nrc.ca/gms?C
+ ivo://cadc.nrc.ca/gms?D
+
+ image
+ 3
+
+ provenanceName
+ provenanceVersion
+ provenanceProject
+ provenanceProducer
+ provenanceRunID
+ http://foo/bar
+ 2025-02-05T00:12:21.039
+
+ keyword1
+ keyword2
+
+
+ caom:foo/bar/plane1
+ caom:foo/bar/plane2
+
+
+
+ phot.count
+ relative
+
+
+ 100.0
+ 33.3
+ 0.2
+ 1.0E-8
+ 27.5
+ 2.7
+
+
+ junk
+
+
+
+
+
+ 2.0
+ 2.0
+
+
+ 1.0
+ 4.0
+
+
+ 3.0
+ 3.0
+
+
+
+
+
+
+
+ 2.0
+ 2.0
+
+
+ 1.0
+ 4.0
+
+
+ 3.0
+ 3.0
+
+
+
+
+
+
+
+ 2.0
+ 2.0
+
+
+ 1.0
+ 4.0
+
+
+ 3.0
+ 3.0
+
+
+
+
+ 1024
+ 2048
+
+
+ 0.2
+ 0.4
+
+ 0.05
+
+ 0.04
+ 0.06
+
+ 0.025
+ absolute
+
+
+
+ 4.0E-4
+ 9.0E-4
+
+
+
+ 4.0E-4
+ 5.0E-4
+
+
+ 8.0E-4
+ 9.0E-4
+
+
+ 2
+ 2.0
+
+ 1.0
+ 8.0
+
+ 1.0E-4
+
+ 1.0E-4
+ 1.0E-4
+
+ 1.0E-4
+ V
+
+ Radio
+ Millimeter
+ Infrared
+ Optical
+ UV
+ EUV
+ X-ray
+ Gamma-ray
+
+ 6.0E-7
+
+ H
+ alpha
+
+ normalized
+
+
+
+ 50000.25
+ 50000.75
+
+
+
+ 50000.25
+ 50000.4
+
+
+ 50000.5
+ 50000.75
+
+
+ 2
+ 0.5
+
+ 0.5
+ 1.1
+
+ 0.15
+ 600.0
+
+ 500.0
+ 700.0
+
+ absolute
+
+
+
+ I
+ Q
+ U
+
+ 3
+
+
+ FARADAY
+
+ 10.0
+ 20.0
+
+
+
+ 10.0
+ 13.0
+
+
+ 17.0
+ 20.0
+
+
+ 10
+
+
+
+ 20.0
+ 120.0
+
+ 0.5
+ 0.85
+
+
+
+ ad:foo/bar0
+ 109
+ this
+ data
+ 2025-02-05T00:12:21.039
+
+ ivo://cadc.nrc.ca/gms?C
+ ivo://cadc.nrc.ca/gms?D
+
+ application/fits
+ 12345
+ md5:d41d8cd98f00b204e9800998ecf8427e
+ desc:TEST/science-ready-data
+
+
+ x0
+ this
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+
+ x1
+ this
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+
+
+
+ ad:foo/bar1
+ 6de
+ this
+ data
+ 2025-02-05T00:12:21.039
+
+ ivo://cadc.nrc.ca/gms?C
+ ivo://cadc.nrc.ca/gms?D
+
+ application/fits
+ 12345
+ md5:d41d8cd98f00b204e9800998ecf8427e
+ desc:TEST/science-ready-data
+
+
+ x0
+ this
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+
+ x1
+ this
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+
+
+
+
+
+ caom:collection/observation/plane1
+ 2025-02-05T00:12:21.039
+
+ ivo://cadc.nrc.ca/gms?A
+ ivo://cadc.nrc.ca/gms?B
+
+ 2025-02-05T00:12:21.039
+
+ ivo://cadc.nrc.ca/gms?C
+ ivo://cadc.nrc.ca/gms?D
+
+ image
+ 3
+
+ provenanceName
+ provenanceVersion
+ provenanceProject
+ provenanceProducer
+ provenanceRunID
+ http://foo/bar
+ 2025-02-05T00:12:21.039
+
+ keyword1
+ keyword2
+
+
+ caom:foo/bar/plane1
+ caom:foo/bar/plane2
+
+
+
+ phot.count
+ relative
+
+
+ 100.0
+ 33.3
+ 0.2
+ 1.0E-8
+ 27.5
+ 2.7
+
+
+ junk
+
+
+
+
+ 2.0
+ 4.0
+
+ 1.0
+
+
+
+
+ 2.0
+ 4.0
+
+ 1.0
+
+
+
+
+ 2.0
+ 4.0
+
+ 0.6
+
+
+ 1024
+ 2048
+
+
+ 0.2
+ 0.4
+
+ 0.05
+
+ 0.04
+ 0.06
+
+ 0.025
+ absolute
+
+
+
+ 4.0E-4
+ 9.0E-4
+
+
+
+ 4.0E-4
+ 5.0E-4
+
+
+ 8.0E-4
+ 9.0E-4
+
+
+ 2
+ 2.0
+
+ 1.0
+ 8.0
+
+ 1.0E-4
+
+ 1.0E-4
+ 1.0E-4
+
+ 1.0E-4
+ V
+
+ Radio
+ Millimeter
+ Infrared
+ Optical
+ UV
+ EUV
+ X-ray
+ Gamma-ray
+
+ 6.0E-7
+
+ H
+ alpha
+
+ normalized
+
+
+
+ 50000.25
+ 50000.75
+
+
+
+ 50000.25
+ 50000.4
+
+
+ 50000.5
+ 50000.75
+
+
+ 2
+ 0.5
+
+ 0.5
+ 1.1
+
+ 0.15
+ 600.0
+
+ 500.0
+ 700.0
+
+ absolute
+
+
+
+ I
+ Q
+ U
+
+ 3
+
+
+ FARADAY
+
+ 10.0
+ 20.0
+
+
+
+ 10.0
+ 13.0
+
+
+ 17.0
+ 20.0
+
+
+ 10
+
+
+
+ 20.0
+ 120.0
+
+ 0.5
+ 0.85
+
+
+
+ ad:foo/bar0
+ 109
+ this
+ data
+ 2025-02-05T00:12:21.039
+
+ ivo://cadc.nrc.ca/gms?C
+ ivo://cadc.nrc.ca/gms?D
+
+ application/fits
+ 12345
+ md5:d41d8cd98f00b204e9800998ecf8427e
+ desc:TEST/science-ready-data
+
+
+ x0
+ this
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+
+ x1
+ this
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+
+
+
+ ad:foo/bar1
+ 6de
+ this
+ data
+ 2025-02-05T00:12:21.039
+
+ ivo://cadc.nrc.ca/gms?C
+ ivo://cadc.nrc.ca/gms?D
+
+ application/fits
+ 12345
+ md5:d41d8cd98f00b204e9800998ecf8427e
+ desc:TEST/science-ready-data
+
+
+ x0
+ this
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+
+ x1
+ this
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+ this
+ 5
+ 6
+ 1
+ 2
+ 3
+ 4
+ 5
+ 7
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+ sliceCtype
+ sliceCunit
+
+ 1
+
+
+
+
+
+ RA
+ deg
+
+
+ DEC
+ deg
+
+
+ 1.0
+ 1.5
+
+
+ 2.0
+ 2.5
+
+
+
+
+ 1.0
+ 10.0
+
+
+ 1.0
+ 11.0
+
+
+
+
+ 20.0
+ 12.0
+
+
+ 40.0
+ 13.0
+
+
+
+
+
+
+ 11.0
+ 12.0
+
+ 0.5
+
+
+
+
+ 20
+ 20
+
+
+
+ 10.0
+ 11.0
+
+
+ 20.0
+ 12.0
+
+
+ 0.05
+ 0.0
+ 0.0
+ 0.05
+
+
+ ICRS
+ 2000.0
+ 0.5
+
+
+
+
+ WAVE
+ m
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ BARCENT
+ BARCENT
+ BARCENT
+ 1.0
+ 2.0
+ 3.0
+ 4.0
+ 5.0
+ energy bandpassName
+ 6.0
+
+ H
+ 21cm
+
+
+
+
+
+ TIME
+ d
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+
+ 0.5
+ 100.0
+
+
+ 4.5
+ 140.0
+
+
+
+
+
+ 4
+ 10.0
+
+ 0.5
+ 100.0
+
+
+
+ UTC
+ TOPOCENTER
+ 50000.0
+ 1.0
+ 2.0
+
+
+
+
+ STOKES
+
+
+ 1.0
+ 1.5
+
+
+
+ 0.5
+ 1.0
+
+
+ 3.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ -4.0
+
+
+ 3.5
+ -1.0
+
+
+
+
+ 3.5
+ 1.0
+
+
+ 7.5
+ 4.0
+
+
+
+
+
+ 4
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+ RM
+ rad/m**2
+
+
+ 1.0E-4
+ 1.0E-6
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+
+ 0.5
+ 1.0
+
+
+ 10.5
+ 4.0
+
+
+
+
+
+ 10
+ 1.0
+
+ 0.5
+ 1.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+ caom:foo/bar
+ caom:foo/baz
+
+
diff --git a/caom2/caom2/tests/data/SampleSimple-CAOM-2.3.xml b/caom2/caom2/tests/data/SampleSimple-CAOM-2.3.xml
index 87351d0b..7564808d 100644
--- a/caom2/caom2/tests/data/SampleSimple-CAOM-2.3.xml
+++ b/caom2/caom2/tests/data/SampleSimple-CAOM-2.3.xml
@@ -63,7 +63,6 @@
y34b0101t-RAW_STANDARD
- ivo://archive.stsci.edu/HST?y34b0101t/y34b0101t-RAW_STANDARD
1996-02-28T17:38:41.000
1997-02-28T00:54:45.000
spectrum
@@ -125,7 +124,6 @@
- false
diff --git a/caom2/caom2/tests/test_artifact.py b/caom2/caom2/tests/test_artifact.py
index 5fe0b54e..4b63edaf 100644
--- a/caom2/caom2/tests/test_artifact.py
+++ b/caom2/caom2/tests/test_artifact.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -73,7 +73,6 @@
from urllib.parse import urlparse
from .. import artifact
-from .. import common
from .. import part
@@ -90,10 +89,10 @@ def test_all(self):
with self.assertRaises(TypeError):
test_artifact = artifact.Artifact("caom:GEMINI/12345",
artifact.ReleaseType.META,
- artifact.ProductType.THUMBNAIL)
+ artifact.DataLinkSemantics.THUMBNAIL)
with self.assertRaises(TypeError):
test_artifact = artifact.Artifact("caom:GEMINI/12345",
- artifact.ProductType.THUMBNAIL,
+ artifact.DataLinkSemantics.THUMBNAIL,
None)
with self.assertRaises(TypeError):
test_artifact = artifact.Artifact("caom:GEMINI/12345",
@@ -101,15 +100,15 @@ def test_all(self):
artifact.ReleaseType.META)
test_artifact = artifact.Artifact("caom:GEMINI/12345",
- artifact.ProductType.THUMBNAIL,
+ artifact.DataLinkSemantics.THUMBNAIL,
artifact.ReleaseType.META)
urlparse("caom:GEMINI/12345")
self.assertEqual("caom:GEMINI/12345",
test_artifact.uri,
"Artifact URI")
- self.assertEqual(artifact.ProductType.THUMBNAIL,
+ self.assertEqual(artifact.DataLinkSemantics.THUMBNAIL,
test_artifact.product_type,
- "Artifact ProductType")
+ "Artifact DataLinkSemantics")
self.assertEqual(artifact.ReleaseType.META,
test_artifact.release_type,
"Artifact ReleaseType")
@@ -122,14 +121,14 @@ def test_all(self):
test_artifact.content_length = 23000000000000
self.assertEqual(23000000000000,
test_artifact.content_length, "Content length")
- test_artifact.product_type = artifact.ProductType.PREVIEW
- self.assertEqual(artifact.ProductType.PREVIEW,
+ test_artifact.product_type = artifact.DataLinkSemantics.PREVIEW
+ self.assertEqual(artifact.DataLinkSemantics.PREVIEW,
test_artifact.product_type,
"Product type")
self.assertIsNone(test_artifact.content_checksum,
"Default content checksum")
- cs_uri = common.ChecksumURI("md5:e30580c1db513487f495fba09f64600e")
+ cs_uri = "md5:e30580c1db513487f495fba09f64600e"
test_artifact.content_checksum = cs_uri
self.assertEqual(test_artifact.content_checksum, cs_uri,
"Content checksum")
@@ -165,7 +164,7 @@ def test_all(self):
test_artifact = artifact.Artifact(
"caom://#observation://? something#//",
artifact.ReleaseType('META'),
- artifact.ProductType('THUMBNAIL'))
+ artifact.DataLinkSemantics('THUMBNAIL'))
except ValueError:
exception = True
self.assertTrue(exception, "Missing exception")
@@ -175,9 +174,9 @@ def test_all(self):
test_artifact = artifact.Artifact(
"observation/something",
artifact.ReleaseType('META'),
- artifact.ProductType('THUMBNAIL'))
+ artifact.DataLinkSemantics('THUMBNAIL'))
# TODO re-enable when check enforced
# with self.assertRaises(ValueError):
- test_artifact.content_checksum = common.ChecksumURI('0x1234')
- assert test_artifact.content_checksum.uri == '0x1234'
+ test_artifact.content_checksum = '0x1234'
+ assert test_artifact.content_checksum == '0x1234'
diff --git a/caom2/caom2/tests/test_caom_util.py b/caom2/caom2/tests/test_caom_util.py
index 04c3b36b..e045ac5a 100644
--- a/caom2/caom2/tests/test_caom_util.py
+++ b/caom2/caom2/tests/test_caom_util.py
@@ -71,7 +71,7 @@
from builtins import str, int
-from .. import artifact
+from .. import artifact, dali
from .. import caom_util
from .. import chunk
from .. import part
@@ -130,7 +130,8 @@ def test_typed_list(self):
self.assertEqual(0, len(my_list2), "list2 length")
def test_validate_path_component(self):
- energy = plane.Energy()
+ bounds = dali.Interval(1.0, 2.0)
+ energy = plane.Energy(bounds, [bounds])
caom_util.validate_path_component(energy, "something",
"some:test\\path")
@@ -177,12 +178,12 @@ def test_typed_set(self):
def test_typed_ordered_dict(self):
# test validation and constructor with an empty dictionary
- test_plane10 = plane.Plane('key10')
+ test_plane10 = plane.Plane('caom:CFHT/anobs/key10')
test_artifact66 = artifact.Artifact("caom:CFHT/55/66",
- chunk.ProductType.SCIENCE,
+ chunk.DataLinkSemantics.SCIENCE,
artifact.ReleaseType.DATA)
test_part10 = part.Part("10")
- test_plane_uri = plane.PlaneURI('caom:CFHT/55/66')
+ test_plane_uri = 'caom:CFHT/55/66'
my_dict_plane = caom_util.TypedOrderedDict(plane.Plane, )
with self.assertRaises(ValueError):
my_dict_plane['key11'] = test_plane10
@@ -192,8 +193,8 @@ def test_typed_ordered_dict(self):
my_dict_part = caom_util.TypedOrderedDict(part.Part, )
with self.assertRaises(ValueError):
my_dict_part['11'] = test_part10
- my_dict_wrong_type = caom_util.TypedOrderedDict(plane.PlaneURI, )
- with self.assertRaises(ValueError):
+ my_dict_wrong_type = caom_util.TypedOrderedDict(plane.Plane, )
+ with self.assertRaises(TypeError):
my_dict_wrong_type['caom:CFHT/55/67'] = test_plane_uri
with self.assertRaises(TypeError):
my_dict_plane['key2'] = 'value2'
@@ -201,87 +202,85 @@ def test_typed_ordered_dict(self):
my_dict_plane['key1'] = float(2.0)
# test assignment
my_dict = caom_util.TypedOrderedDict(plane.Plane, )
- test_plane2 = plane.Plane('key2')
- test_plane1 = plane.Plane('key1')
- my_dict['key2'] = test_plane2
- my_dict['key1'] = test_plane1
+ obs_uri = 'caom:TEST/obs1'
+ test_plane2 = plane.Plane(obs_uri + '/key2')
+ test_plane1 = plane.Plane(obs_uri + '/key1')
+ my_dict[test_plane2.uri] = test_plane2
+ my_dict[test_plane1.uri] = test_plane1
# need to cast to list in order to make it work with both python
# 2 and 3
self.assertEqual(2, len(my_dict),
'mismatch in the number of entries in dictionary.')
- self.assertEqual('key2', list(my_dict.keys())[0],
+ self.assertEqual(test_plane2.uri, list(my_dict.keys())[0],
'key mismatch for 1st key')
- self.assertEqual('key1', list(my_dict.keys())[1],
+ self.assertEqual(test_plane1.uri, list(my_dict.keys())[1],
'key mismatch for 2nd key')
self.assertEqual(test_plane2, list(my_dict.values())[0],
'value mismatch for 1st value')
self.assertEqual(test_plane1, list(my_dict.values())[1],
'value mismatch for 2nd value')
# test constructor with non-empty dictionary
- test_plane1 = plane.Plane('key1')
- test_plane2 = plane.Plane('key2')
my_dict1 = caom_util.TypedOrderedDict(plane.Plane,
- ('key1', test_plane1),
- ('key2', test_plane2))
+ (test_plane1.uri, test_plane1),
+ (test_plane2.uri, test_plane2))
self.assertEqual(2, len(my_dict1),
'mismatch in the number of entries in dictionary.')
# test assignment via setdefault
self.assertRaises(TypeError, my_dict1.setdefault,
'key3', 'wrong value')
- my_dict1.setdefault('key3', plane.Plane('key3'))
+ test_plane3 = plane.Plane('caom:TEST/obs1/key3')
+ my_dict1.setdefault(test_plane3.uri, test_plane3)
self.assertEqual(3, len(my_dict1),
'mismatch in the number of entries in dictionary.')
# test assignment via update
my_dict1.update(my_dict)
self.assertEqual(3, len(my_dict1),
'mismatch in the number of entries in dictionary.')
- self.assertEqual('key2', list(my_dict.keys())[0],
+ self.assertEqual(test_plane2.uri, list(my_dict.keys())[0],
'key mismatch for 1st key')
- self.assertEqual('key1', list(my_dict.keys())[1],
+ self.assertEqual(test_plane1.uri, list(my_dict.keys())[1],
'key mismatch for 2nd key')
# test add function
- my_dict1.add(plane.Plane('key4'))
+ test_plane4 = plane.Plane('caom:TEST/obs1/key4')
+ my_dict1.add(test_plane4)
self.assertEqual(4, len(my_dict1),
'mismatch in the number of entries in dictionary.')
- self.assertEqual('key1', list(my_dict1.keys())[0],
+ self.assertEqual(test_plane1.uri, list(my_dict1.keys())[0],
'key mismatch for 1st key')
- self.assertEqual('key2', list(my_dict1.keys())[1],
+ self.assertEqual(test_plane2.uri, list(my_dict1.keys())[1],
'key mismatch for 2nd key')
- self.assertEqual('key3', list(my_dict1.keys())[2],
+ self.assertEqual(test_plane3.uri, list(my_dict1.keys())[2],
'key mismatch for 3rd key')
- self.assertEqual('key4', list(my_dict1.keys())[3],
+ self.assertEqual(test_plane4.uri, list(my_dict1.keys())[3],
'key mismatch for 4th key')
- plane5 = plane.Plane("key5")
- my_dict1[plane5._key()] = plane5
-
- with self.assertRaises(TypeError):
- my_dict1.add(test_plane_uri)
+ test_plane5 = plane.Plane('caom:TEST/obs1/key5')
+ my_dict1[test_plane5.uri] = test_plane5
# test pop function
self.assertEqual(5, len(my_dict1),
'mismatch in the number of entries in dictionary.')
- my_value = my_dict1.pop('key4')
- self.assertEqual('key4', my_value._key(),
+ my_value = my_dict1.pop(test_plane4.uri)
+ self.assertEqual(test_plane4.uri, my_value._key(),
'popped the wrong entry from dictionary.')
self.assertEqual(4, len(my_dict1),
'mismatch in the number of entries in dictionary.')
- my_value = my_dict1.pop('key5')
- self.assertEqual('key5', my_value._key(),
+ my_value = my_dict1.pop(test_plane5.uri)
+ self.assertEqual(test_plane5.uri, my_value._key(),
'popped the wrong entry from dictionary.')
self.assertEqual(3, len(my_dict1),
'mismatch in the number of entries in dictionary.')
- my_value = my_dict1.pop('key3')
- self.assertEqual('key3', my_value._key(),
+ my_value = my_dict1.pop(test_plane3.uri)
+ self.assertEqual(test_plane3.uri, my_value._key(),
'popped the wrong entry from dictionary.')
self.assertEqual(2, len(my_dict1),
'mismatch in the number of entries in dictionary.')
- my_value = my_dict1.pop('key2')
- self.assertEqual('key2', my_value._key(),
+ my_value = my_dict1.pop(test_plane2.uri)
+ self.assertEqual(test_plane2.uri, my_value._key(),
'popped the wrong entry from dictionary.')
self.assertEqual(1, len(my_dict1),
'mismatch in the number of entries in dictionary.')
- my_value = my_dict1.pop('key1')
- self.assertEqual('key1', my_value._key(),
+ my_value = my_dict1.pop(test_plane1.uri)
+ self.assertEqual(test_plane1.uri, my_value._key(),
'popped the wrong entry from dictionary.')
diff --git a/caom2/caom2/tests/test_checksum.py b/caom2/caom2/tests/test_checksum.py
index d167fa9e..cdefd15c 100644
--- a/caom2/caom2/tests/test_checksum.py
+++ b/caom2/caom2/tests/test_checksum.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2023. (c) 2023.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -119,14 +119,13 @@ def test_primitive_checksum():
update_checksum(md5, value, False)
assert ('0fec383169e99d1a6bebd89d1cd8fad9' == md5.hexdigest())
md5 = hashlib.md5()
- value = str2ivoa('2012-07-11T13:26:37.123')
+ value = str2ivoa('2012-07-11T13:26:37.123200')
update_checksum(md5, value, False)
- assert ('aedbcf5e27a17fc2daa5a0e0d7840009' == md5.hexdigest())
- # ensure that the milliseconds part is not part of checksum
+ assert ('9f8af3a440b6e1c8e2a7ea86d90685ac' == md5.hexdigest())
md5 = hashlib.md5()
- value = str2ivoa('2012-07-11T13:26:37.000')
+ value = str2ivoa('2012-07-11T13:26:37.000000')
update_checksum(md5, value, False)
- assert ('aedbcf5e27a17fc2daa5a0e0d7840009' == md5.hexdigest())
+ assert ('b35eea8d6e70a117ae7804f4e0f6cf58' == md5.hexdigest())
md5 = hashlib.md5()
value = str('ad:file')
update_checksum(md5, value, False)
@@ -137,7 +136,7 @@ def test_primitive_checksum():
assert ('5b71d023d4729575d550536dce8439e6' == md5.hexdigest())
-def test_compatibility():
+def atest_compatibility():
# tests loads a previously generated observation and checks the checksums
# against the previously calculated (in Java) checksums
@@ -304,7 +303,7 @@ def test_compatibility():
_common_check(obs)
-def test_compatibility_simple_obs():
+def atest_compatibility_simple_obs():
# tests loads a previously generated observation and checks the checksums
# against the previously calculated (in Java) checksums
logger = logging.getLogger('checksum')
@@ -327,7 +326,7 @@ def test_compatibility_simple_obs():
logger.setLevel(level)
-def test_round_trip():
+def atest_round_trip():
source_file_path = os.path.join(THIS_DIR, TEST_DATA,
'SampleComposite-CAOM-2.3.xml')
reader = obs_reader_writer.ObservationReader(True)
@@ -351,8 +350,8 @@ def test_round_trip():
def test_checksum_diff():
for source_file_path in \
- [os.path.join(THIS_DIR, TEST_DATA, x) for x in
- ['SampleDerived-CAOM-2.4.xml', 'SampleComposite-CAOM-2.3.xml']]:
+ [os.path.join(THIS_DIR, TEST_DATA, x) for x in ['SampleDerived-CAOM-2.5.xml']]:
+ # TODO Maybe ['SampleDerived-CAOM-2.4.xml', 'SampleComposite-CAOM-2.3.xml']]:
logging.debug(source_file_path)
output_file = tempfile.NamedTemporaryFile()
sys.argv = 'caom2_checksum -d -o {} {}'.format(
@@ -368,7 +367,7 @@ def test_checksum_diff():
assert 'plane' in output
assert 'observation' in output
- # original observation and the one outputed should be identical
+ # original observation and the one output should be identical
reader = obs_reader_writer.ObservationReader()
expected = reader.read(source_file_path)
actual = reader.read(output_file.name)
diff --git a/caom2/caom2/tests/test_chunk.py b/caom2/caom2/tests/test_chunk.py
index aaa8d923..196dbba0 100644
--- a/caom2/caom2/tests/test_chunk.py
+++ b/caom2/caom2/tests/test_chunk.py
@@ -78,35 +78,35 @@ class TestEnums(unittest.TestCase):
def test_all(self):
# test for invalid value
with self.assertRaises(ValueError):
- chunk.ProductType("no_such_string")
+ chunk.DataLinkSemantics("no_such_string")
with self.assertRaises(ValueError):
- chunk.ProductType(None)
+ chunk.DataLinkSemantics(None)
with self.assertRaises(ValueError):
- chunk.ProductType(1)
+ chunk.DataLinkSemantics(1)
# test that we can get the object for each enum by name
- self.assertEqual(chunk.ProductType.SCIENCE.name, "SCIENCE")
- self.assertEqual(chunk.ProductType[
- chunk.ProductType.SCIENCE.name].name, "SCIENCE")
- self.assertEqual(chunk.ProductType['SCIENCE'].value, "science")
- self.assertEqual(chunk.ProductType[
- chunk.ProductType.SCIENCE.name].value, "science")
-
- self.assertEqual(chunk.ProductType.THIS.value, "this")
- self.assertEqual(chunk.ProductType.SCIENCE.value, "science")
- self.assertEqual(chunk.ProductType.CALIBRATION.value, "calibration")
- self.assertEqual(chunk.ProductType.PREVIEW.value, "preview")
- self.assertEqual(chunk.ProductType.NOISE.value, "noise")
- self.assertEqual(chunk.ProductType.WEIGHT.value, "weight")
- self.assertEqual(chunk.ProductType.AUXILIARY.value, "auxiliary")
- self.assertEqual(chunk.ProductType.THUMBNAIL.value, "thumbnail")
- self.assertEqual(chunk.ProductType.BIAS.value, "bias")
- self.assertEqual(chunk.ProductType.DARK.value, "dark")
- self.assertEqual(chunk.ProductType.FLAT.value, "flat")
- self.assertEqual(chunk.ProductType.CODERIVED.value, "coderived")
- self.assertEqual(chunk.ProductType.DOCUMENTATION.value, "documentation")
- self.assertEqual(chunk.ProductType.PREVIEW_IMAGE.value, "preview-image")
- self.assertEqual(chunk.ProductType.PREVIEW_PLOT.value, "preview-plot")
+ self.assertEqual(chunk.DataLinkSemantics.SCIENCE.name, "SCIENCE")
+ self.assertEqual(chunk.DataLinkSemantics[
+ chunk.DataLinkSemantics.SCIENCE.name].name, "SCIENCE")
+ self.assertEqual(chunk.DataLinkSemantics['SCIENCE'].value, "science")
+ self.assertEqual(chunk.DataLinkSemantics[
+ chunk.DataLinkSemantics.SCIENCE.name].value, "science")
+
+ self.assertEqual(chunk.DataLinkSemantics.THIS.value, "this")
+ self.assertEqual(chunk.DataLinkSemantics.SCIENCE.value, "science")
+ self.assertEqual(chunk.DataLinkSemantics.CALIBRATION.value, "calibration")
+ self.assertEqual(chunk.DataLinkSemantics.PREVIEW.value, "preview")
+ self.assertEqual(chunk.DataLinkSemantics.NOISE.value, "noise")
+ self.assertEqual(chunk.DataLinkSemantics.WEIGHT.value, "weight")
+ self.assertEqual(chunk.DataLinkSemantics.AUXILIARY.value, "auxiliary")
+ self.assertEqual(chunk.DataLinkSemantics.THUMBNAIL.value, "thumbnail")
+ self.assertEqual(chunk.DataLinkSemantics.BIAS.value, "bias")
+ self.assertEqual(chunk.DataLinkSemantics.DARK.value, "dark")
+ self.assertEqual(chunk.DataLinkSemantics.FLAT.value, "flat")
+ self.assertEqual(chunk.DataLinkSemantics.CODERIVED.value, "coderived")
+ self.assertEqual(chunk.DataLinkSemantics.DOCUMENTATION.value, "documentation")
+ self.assertEqual(chunk.DataLinkSemantics.PREVIEW_IMAGE.value, "preview-image")
+ self.assertEqual(chunk.DataLinkSemantics.PREVIEW_PLOT.value, "preview-plot")
class TestChunk(unittest.TestCase):
@@ -145,8 +145,8 @@ def test_attributes(self):
test_chunk.polarization = float(1.0)
test_chunk.custom = float(1.0)
- test_chunk.product_type = chunk.ProductType.SCIENCE
- self.assertEqual(chunk.ProductType.SCIENCE.name,
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
+ self.assertEqual(chunk.DataLinkSemantics.SCIENCE.name,
test_chunk.product_type.name)
test_chunk.naxis = int(5)
diff --git a/caom2/caom2/tests/test_common.py b/caom2/caom2/tests/test_common.py
index 52d67f91..b5217e57 100644
--- a/caom2/caom2/tests/test_common.py
+++ b/caom2/caom2/tests/test_common.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -68,7 +68,6 @@
""" Defines TestCaom2IdGenerator class """
-import binascii
import unittest
from .. import artifact
@@ -85,7 +84,7 @@ def test_all(self):
test_entity = common.AbstractCaomEntity()
print(test_entity._id, test_entity._last_modified)
test_artifact = artifact.Artifact("caom2:/blah/blah",
- chunk.ProductType.SCIENCE,
+ chunk.DataLinkSemantics.SCIENCE,
artifact.ReleaseType.DATA)
print(test_artifact._id, test_artifact._last_modified)
@@ -93,13 +92,14 @@ def test_all(self):
print(test_chunk._id, test_chunk._last_modified)
algorithm = observation.Algorithm("myAlg")
- test_observation = observation.Observation("colect", "obs", algorithm)
+ test_observation = observation.Observation("colect", "caom:COLLECTION/obs", algorithm)
print(test_observation._id, test_observation._last_modified)
test_part = part.Part("part")
print(test_part._id, test_part._last_modified)
- test_plane = plane.Plane("prodid")
+ plane_uri = '{}/{}'.format(test_observation.uri, "obs")
+ test_plane = plane.Plane(plane_uri)
print(test_plane._id, test_plane._last_modified)
self.assertIsNone(test_plane.last_modified, "last_modified null")
@@ -110,10 +110,8 @@ def test_all(self):
"acc_meta_checksum null")
d1 = common.get_current_ivoa_time()
d2 = common.get_current_ivoa_time()
- cs_uri_meta = common.ChecksumURI(
- "md5:e30580c1db513487f495fba09f64600e")
- cs_uri_acc = common.ChecksumURI(
- "sha1:7e2b74edf8ff7ddfda5ee3917dc65946b515b1f7")
+ cs_uri_meta = "md5:e30580c1db513487f495fba09f64600e"
+ cs_uri_acc = "sha1:7e2b74edf8ff7ddfda5ee3917dc65946b515b1f7"
test_plane.last_modified = d1
test_plane.max_last_modified = d2
test_plane.meta_checksum = cs_uri_meta
@@ -132,50 +130,50 @@ def test_all(self):
test_entity = common.AbstractCaomEntity()
print(test_entity._id, test_entity._last_modified)
test_artifact = artifact.Artifact("caom2:/blah/blah",
- chunk.ProductType.SCIENCE,
+ chunk.DataLinkSemantics.SCIENCE,
artifact.ReleaseType.DATA)
with self.assertRaises(NotImplementedError):
test_artifact.compute_meta_checksum()
-
-class TestObservationURI(unittest.TestCase):
- def test_all(self):
- obs_uri = observation.ObservationURI("caom:GEMINI/12345")
- self.assertEqual("caom:GEMINI/12345", obs_uri.uri, "Observation URI")
- self.assertEqual("GEMINI", obs_uri.collection, "Collection")
- self.assertEqual("12345", obs_uri.observation_id, "Observation ID")
-
- obs_uri = observation.ObservationURI.get_observation_uri("CFHT",
- "654321")
- self.assertEqual("caom:CFHT/654321", obs_uri.uri, "Observation URI")
- self.assertEqual("CFHT", obs_uri.collection, "Collection")
- self.assertEqual("654321", obs_uri.observation_id, "Observation ID")
-
- exception = False
- try:
- obs_uri = observation.ObservationURI.get_observation_uri(None,
- "123")
- except TypeError:
- exception = True
- self.assertTrue(exception, "Missing exception")
-
- exception = False
- try:
- obs_uri = observation.ObservationURI.get_observation_uri("GEMINI",
- None)
- except TypeError:
- exception = True
- self.assertTrue(exception, "Missing exception")
-
-
-class TestChecksumURI(unittest.TestCase):
- def test_all(self):
- cs_uri = common.ChecksumURI("md5:e30580c1db513487f495fba09f64600e")
- self.assertEqual("md5:e30580c1db513487f495fba09f64600e", cs_uri.uri,
- "Checksum URI")
- self.assertEqual("md5", cs_uri.algorithm, "Algorithm")
- self.assertEqual("e30580c1db513487f495fba09f64600e", cs_uri.checksum,
- "Checksum")
- self.assertEqual(binascii.hexlify(
- bytearray.fromhex("e30580c1db513487f495fba09f64600e")),
- binascii.hexlify(cs_uri.get_bytes()), "Round trip")
+#
+# class TestObservationURI(unittest.TestCase):
+# def test_all(self):
+# obs_uri = "caom:GEMINI/12345"
+# self.assertEqual("caom:GEMINI/12345", obs_uri, "Observation URI")
+# self.assertEqual("GEMINI", obs_uri.collection, "Collection")
+# self.assertEqual("12345", obs_uri.observation_id, "Observation ID")
+#
+# obs_uri = observation.ObservationURI.get_observation_uri("CFHT",
+# "654321")
+# self.assertEqual("caom:CFHT/654321", obs_uri, "Observation URI")
+# self.assertEqual("CFHT", obs_uri.collection, "Collection")
+# self.assertEqual("654321", obs_uri.observation_id, "Observation ID")
+#
+# exception = False
+# try:
+# obs_uri = observation.ObservationURI.get_observation_uri(None,
+# "123")
+# except TypeError:
+# exception = True
+# self.assertTrue(exception, "Missing exception")
+#
+# exception = False
+# try:
+# obs_uri = observation.ObservationURI.get_observation_uri("GEMINI",
+# None)
+# except TypeError:
+# exception = True
+# self.assertTrue(exception, "Missing exception")
+#
+#
+# class TestChecksumURI(unittest.TestCase):
+# def test_all(self):
+# cs_uri = "md5:e30580c1db513487f495fba09f64600e"
+# self.assertEqual("md5:e30580c1db513487f495fba09f64600e", cs_uri,
+# "Checksum URI")
+# self.assertEqual("md5", cs_uri.algorithm, "Algorithm")
+# self.assertEqual("e30580c1db513487f495fba09f64600e", cs_uri.checksum,
+# "Checksum")
+# self.assertEqual(binascii.hexlify(
+# bytearray.fromhex("e30580c1db513487f495fba09f64600e")),
+# binascii.hexlify(cs_uri.get_bytes()), "Round trip")
diff --git a/caom2/caom2/tests/test_dali.py b/caom2/caom2/tests/test_dali.py
new file mode 100644
index 00000000..6f7e6279
--- /dev/null
+++ b/caom2/caom2/tests/test_dali.py
@@ -0,0 +1,107 @@
+# ***********************************************************************
+# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
+# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
+#
+# (c) 2025. (c) 2025.
+# Government of Canada Gouvernement du Canada
+# National Research Council Conseil national de recherches
+# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
+# All rights reserved Tous droits réservés
+#
+# NRC disclaims any warranties, Le CNRC dénie toute garantie
+# expressed, implied, or énoncée, implicite ou légale,
+# statutory, of any kind with de quelque nature que ce
+# respect to the software, soit, concernant le logiciel,
+# including without limitation y compris sans restriction
+# any warranty of merchantability toute garantie de valeur
+# or fitness for a particular marchande ou de pertinence
+# purpose. NRC shall not be pour un usage particulier.
+# liable in any event for any Le CNRC ne pourra en aucun cas
+# damages, whether direct or être tenu responsable de tout
+# indirect, special or general, dommage, direct ou indirect,
+# consequential or incidental, particulier ou général,
+# arising from the use of the accessoire ou fortuit, résultant
+# software. Neither the name de l'utilisation du logiciel. Ni
+# of the National Research le nom du Conseil National de
+# Council of Canada nor the Recherches du Canada ni les noms
+# names of its contributors may de ses participants ne peuvent
+# be used to endorse or promote être utilisés pour approuver ou
+# products derived from this promouvoir les produits dérivés
+# software without specific prior de ce logiciel sans autorisation
+# written permission. préalable et particulière
+# par écrit.
+#
+# This file is part of the Ce fichier fait partie du projet
+# OpenCADC project. OpenCADC.
+#
+# OpenCADC is free software: OpenCADC est un logiciel libre ;
+# you can redistribute it and/or vous pouvez le redistribuer ou le
+# modify it under the terms of modifier suivant les termes de
+# the GNU Affero General Public la “GNU Affero General Public
+# License as published by the License” telle que publiée
+# Free Software Foundation, par la Free Software Foundation
+# either version 3 of the : soit la version 3 de cette
+# License, or (at your option) licence, soit (à votre gré)
+# any later version. toute version ultérieure.
+#
+# OpenCADC is distributed in the OpenCADC est distribué
+# hope that it will be useful, dans l’espoir qu’il vous
+# but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE
+# without even the implied GARANTIE : sans même la garantie
+# warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ
+# or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF
+# PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence
+# General Public License for Générale Publique GNU Affero
+# more details. pour plus de détails.
+#
+# You should have received Vous devriez avoir reçu une
+# a copy of the GNU Affero copie de la Licence Générale
+# General Public License along Publique GNU Affero avec
+# with OpenCADC. If not, see OpenCADC ; si ce n’est
+# . pas le cas, consultez :
+# .
+#
+# $Revision: 4 $
+#
+# ***********************************************************************
+#
+
+import unittest
+
+from .. import dali
+
+
+class TestInterval(unittest.TestCase):
+ def test_all(self):
+
+ lower = 1.0
+ upper = 2.0
+ self.assertRaises(TypeError, dali.Interval, None, None)
+ self.assertRaises(TypeError, dali.Interval, None, 1.0)
+ self.assertRaises(TypeError, dali.Interval, 1.0, None)
+ self.assertRaises(TypeError, dali.Interval, None, "string")
+ self.assertRaises(TypeError, dali.Interval, "string", None)
+ self.assertRaises(TypeError, dali.Interval, "string1", "string2")
+ # validate errors
+ self.assertRaises(ValueError, dali.Interval, upper, lower)
+
+ # test cannot set interval with upper < lower
+ interval = dali.Interval(lower, upper)
+ with self.assertRaises(ValueError):
+ interval.upper = 0.5
+ # test instance methods
+ i1 = dali.Interval(10.0, 15.0)
+ self.assertEqual(i1.get_width(), 5)
+
+ # test class methods
+ i1 = dali.Interval(10.0, 15.0)
+ i2 = dali.Interval(5.0, 8.0)
+ intersect1 = dali.Interval.intersection(i1, i2)
+ self.assertEqual(intersect1, None)
+ intersect2 = dali.Interval.intersection(i2, i1)
+ self.assertEqual(intersect2, None)
+ i3 = dali.Interval(8.0, 12.0)
+ lb = max(i1.lower, i3.lower)
+ ub = min(i1.upper, i3.upper)
+ intersect3 = dali.Interval.intersection(i1, i3)
+ self.assertEqual(intersect3, dali.Interval(lb, ub))
diff --git a/caom2/caom2/tests/test_diffs.py b/caom2/caom2/tests/test_diffs.py
index d14e8adb..df7b0cab 100644
--- a/caom2/caom2/tests/test_diffs.py
+++ b/caom2/caom2/tests/test_diffs.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -69,7 +69,7 @@
import os
import unittest
-from caom2 import Point, shape, Vertex, SegmentType, Position
+from caom2 import Point, shape, Position, MultiShape
from .. import diff
from .. import observation
@@ -81,7 +81,7 @@ class TestCaomUtil(unittest.TestCase):
def test_get_differences(self):
expected_simple = observation.SimpleObservation(
collection='test_collection',
- observation_id='test_observation_id',
+ uri='caom:test_collection/test_observation_id',
algorithm=observation.Algorithm('EXPOSURE'))
# meta_producer field is ignored by get_difference
expected_simple.meta_producer = 'meta_producer/0.1'
@@ -91,22 +91,23 @@ def test_get_differences(self):
actual_simple = observation.SimpleObservation(
collection='test_collection',
- observation_id='test_observation_id',
+ uri='caom:test_collection/test_observation_id',
algorithm=observation.Algorithm('EXPOSURE'))
report = diff.get_differences(expected_simple, actual_simple,
'obs')
self.assertTrue(report is None, repr(report))
-
- act_plane = observation.Plane(product_id='test_product_id1')
- actual_simple.planes['test_product_id1'] = act_plane
+ puri = 'caom:TEST/TESTOBS/test_plane1'
+ act_plane = observation.Plane(puri)
+ actual_simple.planes[act_plane.uri] = act_plane
report = diff.get_differences(expected_simple, actual_simple,
'obs')
self.assertTrue(report is not None, repr(report))
self.assertTrue(len(report) == 1, repr(report))
- ex_plane = observation.Plane(product_id='test_product_id2')
- expected_simple.planes['test_product_id2'] = ex_plane
+ puri2 = 'caom:TEST/TESTOBS/test_plane2'
+ ex_plane = observation.Plane(uri=puri2)
+ expected_simple.planes[ex_plane.uri] = ex_plane
report = diff.get_differences(expected_simple, actual_simple,
'obs')
self.assertTrue(report is not None, repr(report))
@@ -163,22 +164,11 @@ def test_plane_level_position(self):
Point(cval1=100.25, cval2=30.0),
Point(cval1=float('nan'), cval2=float('nan'))]
- v1 = [Vertex(p1[0].cval1, p1[0].cval2, SegmentType.MOVE),
- Vertex(p1[1].cval1, p1[1].cval2, SegmentType.LINE),
- Vertex(p1[2].cval1, p1[2].cval2, SegmentType.LINE),
- Vertex(p1[3].cval1, p1[3].cval2, SegmentType.LINE),
- Vertex(p1[0].cval1, p1[0].cval2, SegmentType.CLOSE)]
- v2 = [Vertex(p2[0].cval1, p2[0].cval2, SegmentType.MOVE),
- Vertex(p2[1].cval1, p2[1].cval2, SegmentType.LINE),
- Vertex(p2[2].cval1, p2[2].cval2, SegmentType.LINE),
- Vertex(p2[3].cval1, p2[3].cval2, SegmentType.LINE),
- Vertex(p2[0].cval1, p2[0].cval2, SegmentType.CLOSE)]
-
- poly1 = shape.Polygon(points=p1, samples=shape.MultiPolygon(v1))
- poly2 = shape.Polygon(points=p2, samples=shape.MultiPolygon(v2))
-
- o1 = Position(time_dependent=False, bounds=poly1)
- o2 = Position(time_dependent=False, bounds=poly2)
+ poly1 = shape.Polygon(points=p1)
+ poly2 = shape.Polygon(points=p2)
+
+ o1 = Position(bounds=poly1, samples=MultiShape([poly1]))
+ o2 = Position(bounds=poly2, samples=MultiShape([poly2]))
report = diff.get_differences(o1, o2, 'caom test instances')
assert report is None, 'NaN comparison failure'
diff --git a/caom2/caom2/tests/test_obs_reader_writer.py b/caom2/caom2/tests/test_obs_reader_writer.py
index 528b8ca1..7ffe1c02 100644
--- a/caom2/caom2/tests/test_obs_reader_writer.py
+++ b/caom2/caom2/tests/test_obs_reader_writer.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -77,6 +77,7 @@
from . import caom_test_instances
from .xml_compare import xml_compare
+from .. import dali
from .. import obs_reader_writer
from .. import observation
from .. import plane
@@ -130,9 +131,9 @@ def complete_derived(depth, bounds_is_circle, version, short_uuid=False):
class TestObservationReaderWriter(unittest.TestCase):
def test_invalid_long_id(self):
- simple_observation = minimal_simple(1, False, 22)
+ simple_observation = minimal_simple(1, False, 23)
writer = obs_reader_writer.ObservationWriter(
- False, False, "caom2", obs_reader_writer.CAOM22_NAMESPACE)
+ False, False, "caom2", obs_reader_writer.CAOM23_NAMESPACE)
output = BytesIO()
writer.write(simple_observation, output)
xml = output.getvalue()
@@ -149,9 +150,9 @@ def test_invalid_long_id(self):
pass
def test_invalid_uuid(self):
- simple_observation = minimal_simple(1, False, 22)
+ simple_observation = minimal_simple(1, False, 23)
writer = obs_reader_writer.ObservationWriter(False,
- False) # default is 2.1
+ False)
output = BytesIO()
writer.write(simple_observation, output)
xml = output.getvalue()
@@ -168,7 +169,7 @@ def test_invalid_uuid(self):
pass
def test_complete_simple(self):
- for version in (22, 23, 24):
+ for version in (23, 24, 25):
for i in range(1, 6):
print("Test Complete Simple {} version {}".format(i, version))
# CoordBounds2D as CoordCircle2D
@@ -186,7 +187,7 @@ def test_complete_simple(self):
def test_minimal_derived(self):
# * composite for the pre-2.4 versions
- for version in (22, 23, 24):
+ for version in (23, 24, 25):
for i in range(1, 6):
if version >= 24:
print("Test Minimal Derived {} version {}".
@@ -213,7 +214,7 @@ def test_minimal_derived(self):
def test_complete_derived(self):
# * formerly known as composite
- for version in (22, 23, 24):
+ for version in (23, 24, 25):
for i in range(1, 6):
if version >= 24:
print("Test Complete Derived {} version {}".
@@ -239,31 +240,17 @@ def test_complete_derived(self):
version)
def test_versions(self):
- derived_observation = complete_derived(6, True, 22)
- complete_derived(6, True, 22, short_uuid=True)
- print("check 2.2 schema with 2.2 doc")
- self.observation_test(derived_observation, True, True, 22)
- print("check 2.3 schema with 2.2 doc")
- self.observation_test(derived_observation, True, True, 23)
- print("check 2.4 schema with 2.2 doc")
- self.observation_test(derived_observation, True, True, 24)
+ derived_observation = complete_derived(6, True, 23)
+ complete_derived(6, True, 23, short_uuid=True)
derived_observation = complete_derived(6, True, 23)
complete_derived(6, True, 23, short_uuid=True)
- print("check 2.2 schema with 2.3 doc")
- with self.assertRaises(AttributeError):
- # creator ID and shape cannot be serialized in v22.
- self.observation_test(derived_observation, True, True, 22)
print("check 2.3 schema with 2.3 doc")
self.observation_test(derived_observation, True, True, 23)
print("check 2.4 schema with 2.3 doc")
self.observation_test(derived_observation, True, True, 24)
derived_observation = complete_derived(6, True, 24)
- print("check 2.2 schema with 2.4 doc")
- with self.assertRaises(AttributeError):
- # creator ID and shape cannot be serialized in v22.
- self.observation_test(derived_observation, True, True, 22)
print("check 2.3 schema with 2.4 doc")
with self.assertRaises(AttributeError):
self.observation_test(derived_observation, True, True, 23)
@@ -291,25 +278,22 @@ def test_versions(self):
self.observation_test(derived_observation, True, True, 23)
- # remove shape and creator IDs ad retest with v22
- for p in derived_observation.planes.values():
- p.position.bounds = None
- p.creator_id = None
- self.observation_test(derived_observation, True, True, 22)
-
def observation_test(self, obs, validate, write_empty_collections,
version):
- if version == 22:
+ if version == 23:
writer = obs_reader_writer.ObservationWriter(
validate, write_empty_collections, "caom2",
- obs_reader_writer.CAOM22_NAMESPACE)
- elif version == 23:
+ obs_reader_writer.CAOM23_NAMESPACE)
+ elif version == 24:
writer = obs_reader_writer.ObservationWriter(
validate, write_empty_collections, "caom2",
- obs_reader_writer.CAOM23_NAMESPACE)
- else:
+ obs_reader_writer.CAOM24_NAMESPACE)
+ elif version == 25:
writer = obs_reader_writer.ObservationWriter(
- validate, write_empty_collections)
+ validate, write_empty_collections, "caom2",
+ obs_reader_writer.CAOM25_NAMESPACE)
+ else:
+ raise ValueError("Unsupported version {}".format(version))
xml_file = open('/tmp/test.xml', 'wb')
writer.write(obs, xml_file)
xml_file.close()
@@ -338,9 +322,9 @@ def compare_observations(self, expected, actual, version):
self.assertIsNotNone(actual.collection)
self.assertEqual(expected.collection, actual.collection)
- self.assertIsNotNone(expected.observation_id)
- self.assertIsNotNone(actual.observation_id)
- self.assertEqual(expected.observation_id, actual.observation_id)
+ self.assertIsNotNone(expected.uri)
+ self.assertIsNotNone(actual.uri)
+ self.assertEqual(expected.uri, actual.uri)
self.assertIsNotNone(expected._id)
self.assertIsNotNone(actual._id)
@@ -378,7 +362,7 @@ def compare_proposal(self, expected, actual):
self.assertIsNotNone(expected)
self.assertIsNotNone(actual)
self.assertEqual(expected.id, actual.id)
- self.assertEqual(expected.pi_name, actual.pi_name)
+ self.assertEqual(expected.pi, actual.pi)
self.assertEqual(expected.project, actual.project)
self.assertEqual(expected.title, actual.title)
self.assertEqual(len(expected.keywords), len(actual.keywords))
@@ -391,7 +375,7 @@ def compare_target(self, expected, actual):
self.assertIsNotNone(expected)
self.assertIsNotNone(actual)
self.assertEqual(expected.name, actual.name)
- self.assertEqual(expected.target_type, actual.target_type)
+ self.assertEqual(expected.type, actual.type)
self.assertEqual(expected.redshift, actual.redshift)
self.assertEqual(expected.moving, actual.moving)
self.assertEqual(expected.standard, actual.standard)
@@ -420,6 +404,7 @@ def compare_telescope(self, expected, actual):
self.assertEqual(expected.geo_location_z, actual.geo_location_z)
for keyword in expected.keywords:
self.assertTrue(keyword in actual.keywords)
+ self.assertEqual(expected.tracking_mode, actual.tracking_mode)
def compare_instrument(self, expected, actual):
if expected is None and actual is None:
@@ -459,7 +444,7 @@ def compare_observation_uri(self, expected, actual):
self.assertIsNotNone(actual)
self.assertEqual(expected.uri, actual.uri)
self.assertEqual(expected.collection, actual.collection)
- self.assertEqual(expected.observation_id, actual.observation_id)
+ self.assertEqual(expected.uri, actual.uri)
def compare_requirements(self, expected, actual):
if expected is None and actual is None:
@@ -480,16 +465,12 @@ def compare_planes(self, expected, actual, version):
actual_plane = actual[key]
self.assertIsNotNone(expected_plane)
self.assertIsNotNone(actual_plane)
- self.assertEqual(expected_plane.product_id,
- actual_plane.product_id)
+ self.assertEqual(expected_plane.uri,
+ actual_plane.uri)
self.assertIsNotNone(expected_plane._id)
self.assertIsNotNone(actual_plane._id)
self.assertEqual(expected_plane._id, actual_plane._id)
- self.assertEqual(
- expected_plane.creator_id, actual_plane.creator_id,
- "creator_id")
-
self.compare_entity_attributes(expected_plane, actual_plane)
self.assertEqual(expected_plane.meta_release,
@@ -527,13 +508,19 @@ def compare_position(self, expected, actual):
self.assertIsNone(actual, "position")
else:
self.compare_shape(expected.bounds, actual.bounds)
+ self.assertTrue(isinstance(expected.samples, shape.MultiShape),
+ "mismatched samples" +
+ actual.__class__.__name__)
+ self.assertEqual(len(expected.samples.shapes), len(actual.samples.shapes),
+ "mismatched number of samples")
+ for index, sample in enumerate(expected.samples.shapes):
+ # assumes that the shapes are in the same order
+ self.compare_shape(sample, actual.samples.shapes[index])
self.compare_dimension2d(expected.dimension, actual.dimension)
self.assertEqual(expected.resolution, actual.resolution,
"resolution")
self.assertEqual(expected.sample_size, actual.sample_size,
"sample_size")
- self.assertEqual(expected.time_dependent, actual.time_dependent,
- "time_dependent")
def compare_energy(self, expected, actual):
print("comparing energy")
@@ -541,6 +528,7 @@ def compare_energy(self, expected, actual):
self.assertIsNone(actual, "energy")
else:
self.compare_interval(expected.bounds, actual.bounds)
+ self.compare_samples(expected.samples, actual.samples)
self.assertEqual(expected.dimension, actual.dimension, "dimension")
self.assertEqual(expected.resolving_power, actual.resolving_power,
"resolving_power")
@@ -559,6 +547,7 @@ def compare_time(self, expected, actual):
self.assertIsNone(actual, "time")
else:
self.compare_interval(expected.bounds, actual.bounds)
+ self.compare_samples(expected.samples, actual.samples)
self.assertEqual(expected.dimension, actual.dimension, "dimension")
self.assertEqual(expected.resolution, actual.resolution,
"resolution")
@@ -571,16 +560,16 @@ def compare_polarization(self, expected, actual):
self.assertIsNone(expected, "polarization")
else:
self.assertEqual(expected.dimension, actual.dimension, "dimension")
- if expected.polarization_states is None:
- self.assertIsNone(actual.polarization_states,
- "polarization_states")
+ if expected.states is None:
+ self.assertIsNone(actual.states,
+ "polarization.states")
else:
- self.assertEqual(len(expected.polarization_states),
- len(actual.polarization_states),
- "different number of polarization_states")
- for index, state in enumerate(expected.polarization_states):
- self.assertEqual(state, actual.polarization_states[index],
- "polarization_state")
+ self.assertEqual(len(expected.states),
+ len(actual.states),
+ "different number of polarization.states")
+ for index, state in enumerate(expected.states):
+ self.assertEqual(state, actual.states[index],
+ "polarization state")
def compare_custom(self, expected, actual):
if expected is None:
@@ -588,6 +577,7 @@ def compare_custom(self, expected, actual):
else:
self.assertEqual(expected.ctype, actual.ctype, 'ctype')
self.compare_interval(expected.bounds, actual.bounds)
+ self.compare_samples(expected.samples, actual.samples)
self.assertEqual(expected.dimension, actual.dimension, "dimension")
def compare_shape(self, expected, actual):
@@ -605,18 +595,6 @@ def compare_shape(self, expected, actual):
"different number of points")
for index, point in enumerate(expected_points):
self.compare_point(point, actual_points[index])
- actual_samples = actual.samples
- self.assertIsNotNone(actual_samples, "shape is None")
- self.assertTrue(isinstance(actual_samples, shape.MultiPolygon),
- "mismatched shapes" +
- actual.__class__.__name__)
- expected_samples = expected.samples
- expected_vertices = expected_samples.vertices
- actual_vertices = actual_samples.vertices
- self.assertEqual(len(expected_vertices), len(actual_vertices),
- "different number of vertices")
- for index, vertex in enumerate(expected_vertices):
- self.compare_vertices(vertex, actual_vertices[index])
elif isinstance(expected, shape.Circle):
self.assertIsNotNone(actual, "shape is None")
self.assertTrue(isinstance(actual, shape.Circle),
@@ -638,13 +616,12 @@ def compare_interval(self, expected, actual):
else:
self.assertEqual(expected.lower, actual.lower, "lower")
self.assertEqual(expected.upper, actual.upper, "upper")
- if expected.samples is None:
- self.assertIsNone(actual.samples, "samples")
- else:
- self.assertEqual(len(actual.samples), len(expected.samples),
- "samples")
- for index, sample in enumerate(expected.samples):
- self.compare_sub_interval(sample, actual.samples[index])
+
+ def compare_samples(self, expected, actual):
+ self.assertEqual(len(actual), len(expected),
+ "samples")
+ for index, sample in enumerate(expected):
+ self.compare_sub_interval(sample, actual[index])
def compare_sub_interval(self, expected, actual):
if expected is None:
@@ -1082,11 +1059,11 @@ def compare_entity_attributes(self, expected, actual):
def test_roundtrip_floats(self):
"""
- Tests floats precission in a round trip
+ Tests floats precision in a round trip
"""
expected_obs = observation.SimpleObservation(
- "TEST_COLLECTION", "33", "ALG")
+ "TEST_COLLECTION", "caom:TEST_COLLECTION/33", "ALG")
writer = obs_reader_writer.ObservationWriter(
True, False, "caom2", obs_reader_writer.CAOM23_NAMESPACE)
@@ -1095,9 +1072,10 @@ def test_roundtrip_floats(self):
shape.Point(-0.00518884856598203, -0.00518884856598), 'test')
# create empty energy
- pl = plane.Plane('productID')
- pl.energy = plane.Energy(sample_size=2.0)
- expected_obs.planes['productID'] = pl
+ plane_uri = '{}/{}'.format(expected_obs.uri, 'planeID')
+ pl = plane.Plane(plane_uri)
+ pl.energy = plane.Energy(dali.Interval(1.0, 2.0), [dali.Interval(1.0, 2.0)])
+ expected_obs.planes[pl.uri] = pl
tmpfile = tempfile.TemporaryFile()
writer.write(expected_obs, tmpfile)
@@ -1147,21 +1125,22 @@ def test_round_trip(self):
'No XML files in test data directory')
reader = obs_reader_writer.ObservationReader(True)
- writer22 = obs_reader_writer.ObservationWriter(
- True, False, "caom2", obs_reader_writer.CAOM22_NAMESPACE)
writer23 = obs_reader_writer.ObservationWriter(
True, False, "caom2", obs_reader_writer.CAOM23_NAMESPACE)
writer24 = obs_reader_writer.ObservationWriter(
True, False, "caom2", obs_reader_writer.CAOM24_NAMESPACE)
+ writer25 = obs_reader_writer.ObservationWriter(
+ True, False, "caom2", obs_reader_writer.CAOM25_NAMESPACE)
for filename in files:
- if filename.endswith("CAOM-2.4.xml"):
+ if filename.endswith("CAOM-2.5.xml"):
+ print("test: {}".format(filename))
+ self.do_test(reader, writer25, filename)
+ elif filename.endswith("CAOM-2.4.xml"):
print("test: {}".format(filename))
self.do_test(reader, writer24, filename)
- elif filename.endswith("CAOM-2.3.xml"):
+ else:
print("test: {}".format(filename))
self.do_test(reader, writer23, filename)
- else:
- self.do_test(reader, writer22, filename)
except Exception:
raise
@@ -1172,7 +1151,7 @@ class TestSchemaValidator(unittest.TestCase):
def _create_observation(self):
obs = observation.SimpleObservation(
- "SCHEMA_VALIDATOR_COLLECTION", "schemaValidatorObsID")
+ "SCHEMA_VALIDATOR_COLLECTION", "caom:SCHEMA_VALIDATOR_COLLECTION/schemaValidatorObsID")
obs.intent = observation.ObservationIntentType.SCIENCE
return obs
diff --git a/caom2/caom2/tests/test_observation.py b/caom2/caom2/tests/test_observation.py
index bd8f5533..8b29aabf 100644
--- a/caom2/caom2/tests/test_observation.py
+++ b/caom2/caom2/tests/test_observation.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -117,9 +117,9 @@ def test_all(self):
class TestObservation(unittest.TestCase):
def test_all(self):
algorithm = observation.Algorithm("myAlg")
- obs = observation.Observation("GSA", "A12345", algorithm)
+ obs = observation.Observation("GSA", "caom:GSA/A12345", algorithm)
self.assertEqual("GSA", obs.collection, "Collection")
- self.assertEqual("A12345", obs.observation_id, "Observation ID")
+ self.assertEqual("caom:GSA/A12345", obs.uri, "Observation URI")
self.assertEqual(algorithm, obs.algorithm, "Algorithm")
new_algorithm = observation.Algorithm("myNewAlg")
@@ -183,27 +183,29 @@ def test_all(self):
obs.meta_release, "Metadata release")
self.assertEqual(0, len(obs.planes), "Default planes")
- plane1 = plane.Plane("myPlaneID")
- obs.planes["myPlaneID"] = plane1
+ puri1 = "caom:TEST/TESTOBS/myPlaneID"
+ plane1 = plane.Plane(puri1)
+ obs.planes[plane1.uri] = plane1
self.assertEqual(1, len(obs.planes), "Planes")
- self.assertTrue("myPlaneID" in obs.planes.keys())
+ self.assertTrue(plane1.uri in obs.planes.keys())
- plane2 = plane.Plane("myPlaneID2")
- obs.planes["myPlaneID2"] = plane2
+ puri2 = "caom:TEST/TESTOBS/myPlaneID2"
+ plane2 = plane.Plane(puri2)
+ obs.planes[plane2.uri] = plane2
self.assertEqual(2, len(obs.planes), "Planes")
- self.assertTrue("myPlaneID" in obs.planes)
- self.assertTrue("myPlaneID2" in obs.planes.keys())
+ self.assertTrue(plane1.uri in obs.planes)
+ self.assertTrue(plane2.uri in obs.planes.keys())
# test duplicates
- plane3 = plane.Plane("myPlaneID2")
- obs.planes["myPlaneID2"] = plane3
+ plane3 = plane.Plane(puri2)
+ obs.planes[plane2.uri] = plane3
self.assertEqual(2, len(obs.planes), "Planes")
- self.assertTrue("myPlaneID" in obs.planes)
- self.assertTrue("myPlaneID2" in obs.planes.keys())
+ self.assertTrue(plane3.uri in obs.planes)
+ self.assertTrue(plane3.uri in obs.planes.keys())
observation.Observation(
obs.collection,
- obs.observation_id,
+ obs.uri,
obs.algorithm,
planes=obs.planes,
sequence_number=obs.sequence_number,
@@ -222,9 +224,9 @@ class TestSimpleObservation(unittest.TestCase):
def test_all(self):
algorithm = observation.Algorithm(
observation.SimpleObservation._DEFAULT_ALGORITHM_NAME)
- obs = observation.SimpleObservation("GSA", "A12345")
+ obs = observation.SimpleObservation("GSA", "caom:GSA/A12345")
self.assertEqual("GSA", obs.collection, "Collection")
- self.assertEqual("A12345", obs.observation_id, "Observation ID")
+ self.assertEqual("caom:GSA/A12345", obs.uri, "Observation URI")
self.assertEqual(algorithm, obs.algorithm, "Algorithm")
obs.algorithm = algorithm
@@ -300,7 +302,7 @@ def test_all(self):
# Test the complete constructor
def test_complete_init(self):
collection = "CFHT"
- observation_id = "543210"
+ uri = "caom:CFHT/543210"
algorithm = observation.Algorithm(
observation.SimpleObservation._DEFAULT_ALGORITHM_NAME)
sequence_number = int(3)
@@ -316,7 +318,7 @@ def test_complete_init(self):
obs = observation.SimpleObservation(
collection,
- observation_id,
+ uri,
algorithm,
sequence_number,
intent,
@@ -334,8 +336,8 @@ def test_complete_init(self):
self.assertIsNotNone(obs.collection, "Collection")
self.assertEqual(collection, obs.collection, "Collection")
- self.assertIsNotNone(obs.observation_id, "Observation ID")
- self.assertEqual(observation_id, obs.observation_id, "Observation ID")
+ self.assertIsNotNone(obs.uri, "Observation URI")
+ self.assertEqual(uri, obs.uri, "Observation URI")
self.assertIsNotNone(obs.algorithm, "Algorithm")
self.assertEqual(algorithm, obs.algorithm, "Algorithm")
@@ -374,9 +376,9 @@ def test_complete_init(self):
class TestCompositeObservation(unittest.TestCase):
def test_all(self):
algorithm = observation.Algorithm("mozaic")
- obs = observation.CompositeObservation("GSA", "A12345", algorithm)
+ obs = observation.CompositeObservation("GSA", "caom:GSA/A12345", algorithm)
self.assertEqual("GSA", obs.collection, "Collection")
- self.assertEqual("A12345", obs.observation_id, "Observation ID")
+ self.assertEqual("caom:GSA/A12345", obs.uri, "Observation URI")
self.assertEqual(algorithm, obs.algorithm, "Algorithm")
obs.algorithm = algorithm
self.assertEqual(algorithm, obs.algorithm, "Algorithm")
@@ -399,19 +401,19 @@ def test_all(self):
self.assertTrue(exception, "Missing exception")
self.assertEqual(0, len(obs.members), "Members")
- observation_uri1 = observation.ObservationURI("caom:collection/obsID")
+ observation_uri1 = "caom:collection/obsID"
obs.members.add(observation_uri1)
self.assertEqual(1, len(obs.members), "Members")
self.assertTrue(observation_uri1 in obs.members)
- observation_uri2 = observation.ObservationURI("caom:collection/obsID2")
+ observation_uri2 = "caom:collection/obsID2"
obs.members.add(observation_uri2)
self.assertEqual(2, len(obs.members), "Members")
self.assertTrue(observation_uri1 in obs.members)
self.assertTrue(observation_uri2 in obs.members)
# duplicates
- observation_uri3 = observation.ObservationURI("caom:collection/obsID")
+ observation_uri3 = "caom:collection/obsID"
obs.members.add(observation_uri3)
self.assertEqual(2, len(obs.members), "Members")
self.assertTrue(observation_uri1 in obs.members)
@@ -479,7 +481,7 @@ def test_all(self):
# Test the complete constructor
def test_complete_init(self):
collection = "CFHT"
- observation_id = "543210"
+ uri = "caom:CFHT/543210"
algorithm = observation.Algorithm("algo")
sequence_number = int(3)
intent = observation.ObservationIntentType.SCIENCE
@@ -496,7 +498,7 @@ def test_complete_init(self):
obs = observation.DerivedObservation(
collection,
- observation_id,
+ uri,
algorithm,
sequence_number,
intent,
@@ -515,8 +517,8 @@ def test_complete_init(self):
self.assertIsNotNone(obs.collection, "Collection")
self.assertEqual(collection, obs.collection, "Collection")
- self.assertIsNotNone(obs.observation_id, "Observation ID")
- self.assertEqual(observation_id, obs.observation_id, "Observation ID")
+ self.assertIsNotNone(obs.uri, "Observation URI")
+ self.assertEqual(uri, obs.uri, "Observation URI")
self.assertIsNotNone(obs.algorithm, "Algorithm")
self.assertEqual(algorithm, obs.algorithm, "Algorithm")
@@ -624,9 +626,9 @@ def test_all(self):
proposal.keywords.add("optical")
self.assertEqual(1, len(proposal.keywords), "Number of keywords")
self.assertTrue("optical" in proposal.keywords, "Keyword not found")
- self.assertIsNone(proposal.pi_name, "Default PI")
- proposal.pi_name = "John Doe"
- self.assertEqual("John Doe", proposal.pi_name, "PI")
+ self.assertIsNone(proposal.pi, "Default PI")
+ proposal.pi = "John Doe"
+ self.assertEqual("John Doe", proposal.pi, "PI")
self.assertIsNone(proposal.project, "Default PI")
proposal.project = "Project A"
self.assertEqual("Project A", proposal.project, "Project")
@@ -648,9 +650,9 @@ def test_all(self):
target = observation.Target("myTarget")
self.assertEqual("myTarget", target.name, "target name")
- target.target_type = observation.TargetType.FIELD
+ target.type = observation.TargetType.FIELD
self.assertEqual(observation.TargetType.FIELD.name,
- target.target_type.name, "target type")
+ target.type.name, "target type")
self.assertEqual(0, len(target.keywords), "Default number of keywords")
target.keywords.add("optical")
@@ -677,7 +679,7 @@ def test_all(self):
observation.TargetType.OBJECT, False, 1.2,
{"radio"}, False, target_id='mytargetID')
self.assertEqual("myOtherTarget", target.name, "target name")
- self.assertEqual(observation.TargetType.OBJECT, target.target_type,
+ self.assertEqual(observation.TargetType.OBJECT, target.type,
"target type")
self.assertFalse(target.standard, "Standard")
self.assertEqual(1.2, target.redshift, "Redshift")
diff --git a/caom2/caom2/tests/test_part.py b/caom2/caom2/tests/test_part.py
index 98e5286b..c7cd189b 100644
--- a/caom2/caom2/tests/test_part.py
+++ b/caom2/caom2/tests/test_part.py
@@ -81,8 +81,8 @@ def test_init(self):
self.assertIsNone(test_part.product_type)
self.assertTrue(len(test_part.chunks) == 0)
- test_part.product_type = chunk.ProductType.SCIENCE
- self.assertEqual(chunk.ProductType.SCIENCE, test_part.product_type)
+ test_part.product_type = chunk.DataLinkSemantics.SCIENCE
+ self.assertEqual(chunk.DataLinkSemantics.SCIENCE, test_part.product_type)
test_chunk = chunk.Chunk()
test_chunk.naxis = 5
diff --git a/caom2/caom2/tests/test_plane.py b/caom2/caom2/tests/test_plane.py
index b0a66f61..47967f04 100644
--- a/caom2/caom2/tests/test_plane.py
+++ b/caom2/caom2/tests/test_plane.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -71,10 +71,10 @@
import unittest
from datetime import datetime
-from .. import artifact
+from .. import artifact, PolarizationState
from .. import chunk
-from .. import observation
from .. import plane
+from .. import dali
from .. import shape
from .. import wcs
@@ -163,12 +163,8 @@ def test_all(self):
class TestPlane(unittest.TestCase):
def test_all(self):
- test_plane = plane.Plane("ProdID")
- self.assertEqual("ProdID", test_plane.product_id, "Product ID")
- self.assertEqual(None, test_plane.creator_id, "Creator ID")
- test_plane.creator_id = "ivo://cadc.nrc.ca/users?tester"
- self.assertEqual("ivo://cadc.nrc.ca/users?tester",
- test_plane.creator_id, "Creator ID")
+ test_plane = plane.Plane("caom:TEST/obs/ProdID")
+ self.assertEqual("caom:TEST/obs/ProdID", test_plane.uri, "Plane URI")
self.assertEqual(0, len(test_plane.artifacts),
"Default number of artifacts")
self.assertIsNone(test_plane.meta_release, "Default meta release date")
@@ -220,14 +216,14 @@ def test_all(self):
self.assertIsNone(test_plane.polarization, "Default polarization")
test_artifact1 = artifact.Artifact("caom:GEMINI/222/333",
- chunk.ProductType.SCIENCE,
+ chunk.DataLinkSemantics.SCIENCE,
artifact.ReleaseType.DATA)
test_plane.artifacts["caom:GEMINI/222/333"] = test_artifact1
self.assertEqual(1, len(test_plane.artifacts), "Artifacts")
self.assertTrue("caom:GEMINI/222/333" in test_plane.artifacts.keys())
test_artifact2 = artifact.Artifact("caom:CFHT/55/66",
- chunk.ProductType.SCIENCE,
+ chunk.DataLinkSemantics.SCIENCE,
artifact.ReleaseType.DATA)
test_plane.artifacts["caom:CFHT/55/66"] = test_artifact2
self.assertEqual(2, len(test_plane.artifacts), "Artifacts")
@@ -236,7 +232,7 @@ def test_all(self):
# try to append a duplicate artifact
test_artifact3 = artifact.Artifact("caom:GEMINI/222/333",
- chunk.ProductType.SCIENCE,
+ chunk.DataLinkSemantics.SCIENCE,
artifact.ReleaseType.DATA)
test_plane.artifacts["caom:GEMINI/222/333"] = test_artifact3
self.assertEqual(2, len(test_plane.artifacts), "Artifacts")
@@ -290,59 +286,48 @@ def test_all(self):
self.assertTrue(exception, "compute_polarization implemented"
" - Testing needed")
-
-class TestPlaneURI(unittest.TestCase):
- def test_all(self):
- plane_uri = plane.PlaneURI("caom:GEMINI/12345/3333")
- self.assertEqual("caom:GEMINI/12345/3333", plane_uri.uri,
- "Plane URI")
- self.assertEqual("GEMINI", plane_uri.get_observation_uri().collection,
- "Collection")
- self.assertEqual("12345",
- plane_uri.get_observation_uri().observation_id,
- "Observation ID")
- self.assertEqual("3333", plane_uri.get_product_id(), "Product ID")
-
- plane_uri = plane.PlaneURI.get_plane_uri(
- observation.ObservationURI("caom:CFHT/654321"),
- "555")
- self.assertEqual("caom:CFHT/654321/555", plane_uri.uri,
- "Observation URI")
- self.assertEqual("CFHT", plane_uri.get_observation_uri().collection,
- "Collection")
- self.assertEqual("654321",
- plane_uri.get_observation_uri().observation_id,
- "Observation ID")
- self.assertEqual("555", plane_uri.get_product_id(), "Product ID")
-
- exception = False
- try:
- plane_uri = plane.PlaneURI.get_plane_uri(None, "123")
- except TypeError:
- exception = True
- self.assertTrue(exception, "Missing exception")
-
- exception = False
- try:
- plane_uri = plane.PlaneURI.get_plane_uri("GEMINI", None)
- except TypeError:
- exception = True
- self.assertTrue(exception, "Missing exception")
-
- # wrong scheme
- exception = False
- try:
- plane_uri = plane.PlaneURI("somescheme:GEMINI/12345/3333")
- except ValueError:
- exception = True
- self.assertTrue(exception, "Missing exception")
-
- exception = False
- try:
- plane_uri = plane.PlaneURI("caom:GEMINI/12345")
- except ValueError:
- exception = True
- self.assertTrue(exception, "Missing exception")
+#
+# class TestPlaneURI(unittest.TestCase):
+# def test_all(self):
+# plane_uri = plane.PlaneURI("caom:GEMINI/12345/3333")
+# self.assertEqual("caom:GEMINI/12345/3333", plane_uri.uri,
+# "Plane URI")
+#
+# plane_uri = plane.PlaneURI.get_plane_uri(
+# observation.ObservationURI("caom:CFHT/654321"),
+# "555")
+# self.assertEqual("caom:CFHT/654321/555", plane_uri.uri,
+# "Observation URI")
+#
+# exception = False
+# try:
+# plane_uri = plane.PlaneURI.get_plane_uri(None, "123")
+# except TypeError:
+# exception = True
+# self.assertTrue(exception, "Missing exception")
+#
+# exception = False
+# try:
+# plane_uri = plane.PlaneURI.get_plane_uri("GEMINI", None)
+# except TypeError:
+# exception = True
+# self.assertTrue(exception, "Missing exception")
+#
+# # wrong scheme
+# exception = False
+# try:
+# plane_uri = plane.PlaneURI("somescheme:GEMINI/12345/3333")
+# except ValueError:
+# exception = True
+# self.assertTrue(exception, "Missing exception")
+#
+# exception = False
+# try:
+# plane_uri = plane.PlaneURI("caom:GEMINI/12345")
+# except ValueError:
+# exception = True
+# self.assertTrue(exception, "Missing exception")
+#
class TestDataQuality(unittest.TestCase):
@@ -400,19 +385,19 @@ def test_all(self):
self.assertIsNone(provenance.reference, "Default reference")
self.assertEqual(0, len(provenance.inputs), "Default inputs")
- plane_uri1 = plane.PlaneURI("caom:HST/11/00")
+ plane_uri1 = "caom:HST/11/00"
provenance.inputs.add(plane_uri1)
self.assertEqual(1, len(provenance.inputs), "Default inputs")
self.assertTrue(plane_uri1 in provenance.inputs)
- plane_uri2 = plane.PlaneURI("caom:HST/22/00")
+ plane_uri2 = "caom:HST/22/00"
provenance.inputs.add(plane_uri2)
self.assertEqual(2, len(provenance.inputs), "Default inputs")
self.assertTrue(plane_uri1 in provenance.inputs)
self.assertTrue(plane_uri2 in provenance.inputs)
# testing duplicates
- plane_uri3 = plane.PlaneURI("caom:HST/22/00")
+ plane_uri3 = "caom:HST/22/00"
provenance.inputs.add(plane_uri3)
self.assertEqual(2, len(provenance.inputs), "Default inputs")
self.assertTrue(plane_uri1 in provenance.inputs)
@@ -460,9 +445,10 @@ def test_all(self):
class TestPosition(unittest.TestCase):
def test_all(self):
- position = plane.Position()
+ circle = shape.Circle(center=shape.Point(1.0, 2.0), radius=3.0)
+ position = plane.Position(bounds=circle, samples=shape.MultiShape([circle]))
- self.assertIsNone(position.bounds, "Default bounds")
+ self.assertEqual(circle, position.bounds, "Default bounds")
# position.bounds = 123
# self.assertEqual(123, position.bounds, "Bounds")
self.assertIsNone(position.dimension, "Default dimension")
@@ -474,18 +460,16 @@ def test_all(self):
self.assertIsNone(position.sample_size, "Default sample size")
position.sample_size = 321.123
self.assertEqual(321.123, position.sample_size, "Sample size")
- self.assertFalse(position.time_dependent, "Default time dependent")
- position.time_dependent = True
- self.assertTrue(position.time_dependent, "Time dependent")
class TestEnergy(unittest.TestCase):
def test_all(self):
- energy = plane.Energy()
- self.assertIsNone(energy.bounds, "Default energy bounds")
- energy.bounds = shape.Interval(1.0, 2.0)
+ bounds = dali.Interval(1.0, 2.0)
+ samples = [dali.Interval(1.1, 1.2), dali.Interval(1.9, 2.0)]
+ energy = plane.Energy(bounds, samples)
self.assertEqual(1.0, energy.bounds.lower, "Energy lower bounds")
self.assertEqual(2.0, energy.bounds.upper, "Energy upper bounds")
+ self.assertEqual(2, len(energy.samples))
self.assertIsNone(energy.dimension, "Default energy dimension")
energy.dimension = 1000
self.assertEqual(1000, energy.dimension, "Energy dimension")
@@ -541,23 +525,22 @@ def test_setters(self):
class TestPolarizaton(unittest.TestCase):
def test_all(self):
- polarization = plane.Polarization()
-
- self.assertIsNone(polarization.dimension,
- "Default polarization dimension")
+ polarization = plane.Polarization(dimension=2, states=[PolarizationState.I, PolarizationState.Q])
- # TODO add test for state
+ self.assertEqual(2, polarization.dimension, "Default polarization dimension")
+ self.assertEqual(2, len(polarization.states), "Polarization states")
+ self.assertTrue(PolarizationState.I in polarization.states, "I state")
+ self.assertTrue(PolarizationState.Q in polarization.states, "Q state")
class TestTime(unittest.TestCase):
def test_all(self):
- time = plane.Time()
- self.assertIsNone(time.bounds, "Default bounds")
- self.assertIsNone(time.dimension, "Default dimension")
- self.assertIsNone(time.resolution, "Default resolution")
- self.assertIsNone(time.sample_size, "Default sample size")
- self.assertIsNone(time.exposure, "Default exposure")
-
+ bounds = dali.Interval(1.0, 2.0)
+ samples = [dali.Interval(1.1, 1.2)]
+ time = plane.Time(bounds, samples)
+ self.assertEqual(1.0, time.bounds.lower, "Time lower bounds")
+ self.assertEqual(2.0, time.bounds.upper, "Time upper bounds")
+ self.assertEqual(1, len(time.samples))
time.dimension = 777
self.assertEqual(777, time.dimension, "Dimension")
time.resolution = 77.777
@@ -571,21 +554,23 @@ def test_all(self):
class TestCustomAxis(unittest.TestCase):
def test_all(self):
with self.assertRaises(AttributeError):
- plane.CustomAxis(None)
- my_axis = plane.CustomAxis('Foo')
+ plane.CustomAxis(None, None, None)
+ my_axis = plane.CustomAxis('Foo', dali.Interval(1.0, 2.0),
+ samples=[dali.Interval(1.0, 2.0)])
self.assertEqual('Foo', my_axis.ctype, 'CTYPE missmatch')
- self.assertIsNone(my_axis.bounds, "Default bounds")
+ self.assertEqual(dali.Interval(1.0, 2.0), my_axis.bounds, "Bounds mismatch")
self.assertIsNone(my_axis.dimension, "Default dimension")
my_axis.dimension = 777
- my_axis.bounds = shape.Interval(1.0, 2.0)
self.assertEqual(777, my_axis.dimension, "Dimension")
- self.assertEqual(1.0, my_axis.bounds.lower, "Bounds mismatch")
+ self.assertEqual(1.0, len(my_axis.samples), "Samples mismatch")
self.assertEqual(2.0, my_axis.bounds.upper, "Bounad mismatch")
- my_axis = plane.CustomAxis('Blah', bounds=shape.Interval(3.0, 4.0),
- dimension=33)
+ bounds = dali.Interval(1.0, 2.0)
+ my_axis = plane.CustomAxis('Blah', bounds=dali.Interval(3.0, 4.0),
+ dimension=33, samples=[bounds])
self.assertEqual('Blah', my_axis.ctype, 'CTYPE missmatch')
self.assertEqual(33, my_axis.dimension, 'Dimension missmatch')
- self.assertEqual(3.0, my_axis.bounds.lower, "Bounds mismatch")
- self.assertEqual(4.0, my_axis.bounds.upper, "Bounad mismatch")
+ self.assertEqual(3.0, my_axis.bounds.lower, "Bounds lower mismatch")
+ self.assertEqual(4.0, my_axis.bounds.upper, "Bounds upper mismatch")
+ self.assertEqual(1.0, len(my_axis.samples), "Samples mismatch")
diff --git a/caom2/caom2/tests/test_shape.py b/caom2/caom2/tests/test_shape.py
index 3ced4b11..237dacb2 100644
--- a/caom2/caom2/tests/test_shape.py
+++ b/caom2/caom2/tests/test_shape.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2022. (c) 2022.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -67,33 +67,11 @@
#
import math
-import pytest
import unittest
from .. import shape
-class TestEnums(unittest.TestCase):
- def test_all(self):
- # test for invalid key
- with self.assertRaises(KeyError):
- shape.SegmentType["foo"]
- with self.assertRaises(Exception):
- shape.SegmentType[None]
- with self.assertRaises(Exception):
- shape.SegmentType[999]
- # test for invalid value
- with self.assertRaises(ValueError):
- shape.SegmentType("foo")
- with self.assertRaises(ValueError):
- shape.SegmentType(None)
- with self.assertRaises(ValueError):
- shape.SegmentType(4)
- self.assertEqual(shape.SegmentType.CLOSE.value, 0)
- self.assertEqual(shape.SegmentType.LINE.value, 1)
- self.assertEqual(shape.SegmentType.MOVE.value, 2)
-
-
class TestBox(unittest.TestCase):
def test_all(self):
self.assertRaises(TypeError, shape.Box, None, None, None)
@@ -138,101 +116,6 @@ def test_all(self):
self.assertEqual(circle.get_size(), 2.0 * radius)
-class TestInterval(unittest.TestCase):
- def test_all(self):
-
- lower = 1.0
- upper = 2.0
- lower1 = 1.1
- upper1 = 2.1
- lower2 = 1.2
- upper2 = 2.2
- samples = [shape.SubInterval(lower, lower1),
- shape.SubInterval(lower2, upper),
- shape.SubInterval(upper1, upper2)]
- invalid_samples_lower_mismatch = [shape.SubInterval(lower, upper)]
- invalid_samples_upper_mismatch = [shape.SubInterval(lower, upper2)]
- invalid_samples_middle_bounds_overlap = [
- shape.SubInterval(lower, upper), shape.SubInterval(lower1, upper1)]
-
- self.assertRaises(TypeError, shape.Interval, None, None, None)
- self.assertRaises(TypeError, shape.Interval, None, None, 1.0)
- self.assertRaises(TypeError, shape.Interval, None, 1.0, None)
- self.assertRaises(TypeError, shape.Interval, 1.0, None, None)
- self.assertRaises(TypeError, shape.Interval, None, None, samples)
- self.assertRaises(TypeError, shape.Interval, None, int(1), samples)
- self.assertRaises(TypeError, shape.Interval, int(1), None, samples)
- self.assertRaises(TypeError, shape.Interval, None, "string", samples)
- self.assertRaises(TypeError, shape.Interval, "string", None, samples)
- self.assertRaises(TypeError, shape.Interval, "string1", "string2",
- int(1))
- self.assertRaises(ValueError, shape.Interval, 2.0, 1.0, None)
- # validate errors
- self.assertRaises(ValueError, shape.Interval, lower, lower, [])
- self.assertRaises(ValueError, shape.Interval, lower1, upper,
- invalid_samples_lower_mismatch)
- self.assertRaises(ValueError, shape.Interval, lower, upper,
- invalid_samples_upper_mismatch)
- self.assertRaises(ValueError, shape.Interval, lower, upper2,
- invalid_samples_middle_bounds_overlap)
-
- # test cannot set interval with upper < lower
- interval = shape.Interval(lower, upper2, samples)
- has_assertionError = False
- try:
- interval.upper = 0.5
- except ValueError:
- has_assertionError = True
- self.assertEqual(has_assertionError, True)
-
- # test intervals in samples
- actual_samples = interval.samples
-
- actual_subInterval = actual_samples[0]
- expected_subInterval = samples[0]
- actual_lower = actual_subInterval.lower
- actual_upper = actual_subInterval.upper
- expected_lower = expected_subInterval.lower
- expected_upper = expected_subInterval.upper
- self.assertEqual(actual_lower, expected_lower)
- self.assertEqual(actual_upper, expected_upper)
-
- actual_subInterval = actual_samples[1]
- expected_subInterval = samples[1]
- actual_lower = actual_subInterval.lower
- actual_upper = actual_subInterval.upper
- expected_lower = expected_subInterval.lower
- expected_upper = expected_subInterval.upper
- self.assertEqual(actual_lower, expected_lower)
- self.assertEqual(actual_upper, expected_upper)
-
- actual_subInterval = actual_samples[2]
- expected_subInterval = samples[2]
- actual_lower = actual_subInterval.lower
- actual_upper = actual_subInterval.upper
- expected_lower = expected_subInterval.lower
- expected_upper = expected_subInterval.upper
- self.assertEqual(actual_lower, expected_lower)
- self.assertEqual(actual_upper, expected_upper)
-
- # test instance methods
- i1 = shape.Interval(10.0, 15.0)
- self.assertEqual(i1.get_width(), 5)
-
- # test class methods
- i1 = shape.Interval(10.0, 15.0)
- i2 = shape.Interval(5.0, 8.0)
- intersec1 = shape.Interval.intersection(i1, i2)
- self.assertEqual(intersec1, None)
- intersec2 = shape.Interval.intersection(i2, i1)
- self.assertEqual(intersec2, None)
- i3 = shape.Interval(8.0, 12.0)
- lb = max(i1.lower, i3.lower)
- ub = min(i1.upper, i3.upper)
- intersec3 = shape.Interval.intersection(i1, i3)
- self.assertEqual(intersec3, shape.Interval(lb, ub))
-
-
class TestPoint(unittest.TestCase):
def test_all(self):
self.assertRaises(TypeError, shape.Point, None, None)
@@ -244,51 +127,3 @@ def test_all(self):
point = shape.Point(1.0, 2.0)
self.assertEqual(point.cval1, 1.0)
self.assertEqual(point.cval2, 2.0)
-
-
-class TestSubInterval(unittest.TestCase):
- def test_all(self):
-
- self.assertRaises(TypeError, shape.SubInterval, None, None)
- self.assertRaises(TypeError, shape.SubInterval, None, 1.0)
- self.assertRaises(TypeError, shape.SubInterval, 1.0, None)
- self.assertRaises(TypeError, shape.SubInterval, "string1", "string2")
- self.assertRaises(ValueError, shape.SubInterval, 2.0, 1.0)
-
- # test cannot set subInterval with upper < lower
- subInterval = shape.SubInterval(1.0, 2.0)
- has_assertionError = False
- try:
- subInterval.upper = 0.5
- except ValueError:
- has_assertionError = True
- self.assertEqual(has_assertionError, True)
-
- # test construction method
- shape.SubInterval(10.0, 15.0)
-
-
-class TestVertex():
- def test_all(self):
- pytest.raises(TypeError, shape.Vertex, None, None, None)
- pytest.raises(TypeError, shape.Vertex, 1.0, 2.0, None)
- pytest.raises(TypeError, shape.Vertex, 1.0, 2.0, 1.0)
- pytest.raises(TypeError, shape.Vertex, None, None,
- shape.SegmentType.LINE)
- pytest.raises(TypeError, shape.Vertex, None, 2.0,
- shape.SegmentType.LINE)
- pytest.raises(TypeError, shape.Vertex, 1.0, None,
- shape.SegmentType.LINE)
- pytest.raises(TypeError, shape.Vertex, None, "string",
- shape.SegmentType.LINE)
- pytest.raises(TypeError, shape.Vertex, "string", None,
- shape.SegmentType.LINE)
- pytest.raises(TypeError, shape.Vertex, None, int(1),
- shape.SegmentType.LINE)
- pytest.raises(TypeError, shape.Vertex, int(1), None,
- shape.SegmentType.LINE)
-
- vertex = shape.Vertex(1.0, 2.0, shape.SegmentType.LINE)
- assert(vertex.cval1 == 1.0)
- assert(vertex.cval2 == 2.0)
- assert(vertex.type == shape.SegmentType.LINE)
diff --git a/caom2/setup.cfg b/caom2/setup.cfg
index 0d349833..8f1e2593 100644
--- a/caom2/setup.cfg
+++ b/caom2/setup.cfg
@@ -25,6 +25,9 @@ testpaths = caom2
[bdist_wheel]
universal=1
+[flake8]
+max-line-length = 120
+
[metadata]
package_name = caom2
description = CAOM-2.4 library
diff --git a/caom2repo/caom2repo/tests/test_core.py b/caom2repo/caom2repo/tests/test_core.py
index f7ba1303..df0b55d8 100644
--- a/caom2repo/caom2repo/tests/test_core.py
+++ b/caom2repo/caom2repo/tests/test_core.py
@@ -78,7 +78,7 @@
from cadcutils import util, exceptions
from cadcutils.net import auth
from caom2.obs_reader_writer import ObservationWriter
-from caom2 import obs_reader_writer, ChecksumURI
+from caom2 import obs_reader_writer
from caom2.observation import SimpleObservation
from unittest.mock import Mock, patch, MagicMock, ANY, call
# TODO to be changed to io.BytesIO when caom2 is prepared for python3
@@ -577,7 +577,7 @@ def test_visit_retry_on_412(self):
level = logging.DEBUG
visitor = CAOM2RepoClient(auth.Subject(), level)
observation = SimpleObservation('cfht', 'a')
- observation.acc_meta_checksum = ChecksumURI('md5:abc')
+ observation.acc_meta_checksum = 'md5:abc'
visitor.get_observation = MagicMock(side_effect=[observation,
observation])
diff --git a/caom2utils/caom2utils/blueprints.py b/caom2utils/caom2utils/blueprints.py
index 1044dc89..6839de01 100644
--- a/caom2utils/caom2utils/blueprints.py
+++ b/caom2utils/caom2utils/blueprints.py
@@ -378,7 +378,6 @@ def __init__(
'Plane.dataProductType': ([], DataProductType.IMAGE),
'Plane.metaRelease': (['RELEASE', 'REL_DATE'], None),
'Plane.dataRelease': (['RELEASE', 'REL_DATE'], None),
- 'Plane.productID': (['RUNID'], None),
'Plane.provenance.name': (['XPRVNAME'], None),
'Plane.provenance.project': (['ADC_ARCH'], None),
'Plane.provenance.producer': (['ORIGIN'], None),
diff --git a/caom2utils/caom2utils/caom2blueprint.py b/caom2utils/caom2utils/caom2blueprint.py
index 0f1ed206..55047165 100755
--- a/caom2utils/caom2utils/caom2blueprint.py
+++ b/caom2utils/caom2utils/caom2blueprint.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2016. (c) 2016.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -98,13 +98,12 @@
from caom2 import (
Artifact,
Algorithm,
- ChecksumURI,
CompositeObservation,
DerivedObservation,
ObservationReader,
ObservationWriter,
Plane,
- ProductType,
+ DataLinkSemantics,
ReleaseType,
SimpleObservation,
)
@@ -263,9 +262,9 @@ def update_artifact_meta(artifact, file_info):
)
if file_info.md5sum is not None:
if file_info.md5sum.startswith('md5:'):
- checksum = ChecksumURI(file_info.md5sum)
+ checksum = file_info.md5sum
else:
- checksum = ChecksumURI(f'md5:{file_info.md5sum}')
+ checksum = f'md5:{file_info.md5sum}'
artifact.content_checksum = checksum
artifact.content_length = _to_int(file_info.size)
artifact.content_type = _to_str(file_info.file_type)
@@ -354,13 +353,15 @@ def _augment(
if dumpconfig:
print(f'Blueprint for {uri}: {blueprint}')
- if product_id not in obs.planes.keys():
- obs.planes.add(Plane(product_id=str(product_id)))
+ plane_uri = f'{obs.uri}/{product_id}'
+ if plane_uri not in obs.planes.keys():
+ obs.planes[plane_uri] = Plane(uri=plane_uri)
- plane = obs.planes[product_id]
+ plane = obs.planes[plane_uri]
if uri not in plane.artifacts.keys():
- plane.artifacts.add(Artifact(uri=str(uri), product_type=ProductType.SCIENCE, release_type=ReleaseType.DATA))
+ plane.artifacts.add(Artifact(uri=str(uri), product_type=DataLinkSemantics.SCIENCE,
+ release_type=ReleaseType.DATA))
meta_uri = uri
visit_local = None
@@ -427,7 +428,10 @@ def _augment(
result = None
else:
_get_and_update_artifact_meta(meta_uri, plane.artifacts[uri], subject, connected, client)
- parser.augment_observation(observation=obs, artifact_uri=uri, product_id=plane.product_id)
+ product_id = None
+ if plane.uri:
+ product_id = plane.uri.split('/')[-1]
+ parser.augment_observation(observation=obs, artifact_uri=uri, product_id=product_id)
result = _visit(plugin, parser, obs, visit_local, product_id, uri, subject, **kwargs)
@@ -556,23 +560,26 @@ def _gen_obs(obs_blueprints, in_obs_xml, collection=None, obs_id=None):
else:
# determine the type of observation to create by looking for the the DerivedObservation.members in the
# blueprints. If present in any of it assume derived
+ if not collection or not obs_id:
+ raise ValueError('collection and obs_id must be provided if no input observation is provided')
+ obs_uri = f'caom:{collection}/{obs_id}'
for bp in obs_blueprints.values():
if bp._get('DerivedObservation.members') is not None:
logging.debug('Build a DerivedObservation')
obs = DerivedObservation(
- collection=collection, observation_id=obs_id, algorithm=Algorithm('composite')
+ collection=collection, uri=obs_uri, algorithm=Algorithm('composite')
)
break
elif bp._get('CompositeObservation.members') is not None:
logging.debug('Build a CompositeObservation with obs_id {}'.format(obs_id))
obs = CompositeObservation(
- collection=collection, observation_id=obs_id, algorithm=Algorithm('composite')
+ collection=collection, uri=obs_uri, algorithm=Algorithm('composite')
)
break
if not obs:
# build a simple observation
- logging.debug(f'Build a SimpleObservation with obs_id {obs_id}')
- obs = SimpleObservation(collection=collection, observation_id=obs_id, algorithm=Algorithm('exposure'))
+ logging.debug(f'Build a SimpleObservation with uri {obs_uri}')
+ obs = SimpleObservation(collection=collection, uri=obs_uri, algorithm=Algorithm('exposure'))
return obs
@@ -773,7 +780,7 @@ def _visit(plugin_name, parser, obs, visit_local, product_id=None, uri=None, sub
if plugin_name is not None and len(plugin_name) > 0:
# TODO make a check that's necessary under both calling conditions here
logging.debug(
- 'Begin plugin execution {!r} update method on ' 'observation {!r}'.format(plugin_name, obs.observation_id)
+ 'Begin plugin execution {!r} update method on ' 'observation {!r}'.format(plugin_name, obs.uri)
)
plgin = _load_plugin(plugin_name)
if isinstance(parser, FitsParser):
diff --git a/caom2utils/caom2utils/parsers.py b/caom2utils/caom2utils/parsers.py
index a5d839b6..8f4d0a65 100644
--- a/caom2utils/caom2utils/parsers.py
+++ b/caom2utils/caom2utils/parsers.py
@@ -159,13 +159,13 @@ def augment_observation(self, observation, artifact_uri, product_id=None):
if product_id is None:
raise ValueError('product ID required')
- for ii in observation.planes:
- if observation.planes[ii].product_id == product_id:
- plane = observation.planes[product_id]
- break
- if plane is None:
- plane = caom2.Plane(product_id=product_id)
- observation.planes[product_id] = plane
+ plane_uri = f'{observation.uri}/{product_id}'
+
+ if plane_uri in observation.planes:
+ plane = observation.planes[plane_uri]
+ else:
+ plane = caom2.Plane(uri=plane_uri)
+ observation.planes[plane_uri] = plane
self.augment_plane(plane, artifact_uri)
self.logger.debug(f'End CAOM2 observation augmentation for {artifact_uri}.')
@@ -305,7 +305,7 @@ def _to_calibration_level(self, value):
return self._to_enum_type(value, caom2.CalibrationLevel)
def _to_product_type(self, value):
- return self._to_enum_type(value, caom2.ProductType)
+ return self._to_enum_type(value, caom2.DataLinkSemantics)
def _to_release_type(self, value):
return self._to_enum_type(value, caom2.ReleaseType)
@@ -551,7 +551,7 @@ def _get_provenance(self, current):
prov.inputs.add(i)
else:
for i in inputs.split():
- prov.inputs.add(caom2.PlaneURI(str(i)))
+ prov.inputs.add(str(i))
else:
if current is not None and len(current.inputs) > 0:
# preserve the original value
@@ -658,7 +658,7 @@ def augment_observation(self, observation, artifact_uri, product_id=None):
observation.members.add(m)
else:
for m in members.split():
- observation.members.add(caom2.ObservationURI(m))
+ observation.members.add(m)
observation.algorithm = self._get_algorithm(observation)
observation.sequence_number = _to_int(self._get_from_list('Observation.sequenceNumber', index=0))
@@ -910,7 +910,7 @@ def _get_proposal(self, current):
'Observation.proposal.id', index=0, current=None if current is None else current.id
)
pi = self._get_from_list(
- 'Observation.proposal.pi', index=0, current=None if current is None else current.pi_name
+ 'Observation.proposal.pi', index=0, current=None if current is None else current.pi
)
project = self._get_from_list(
'Observation.proposal.project', index=0, current=None if current is None else current.project
@@ -950,7 +950,7 @@ def _get_target(self, current):
'Observation.target.name', index=0, current=None if current is None else current.name
)
target_type = self._get_from_list(
- 'Observation.target.type', index=0, current=None if current is None else current.target_type
+ 'Observation.target.type', index=0, current=None if current is None else current.type
)
standard = self._cast_as_bool(
self._get_from_list(
@@ -2094,7 +2094,7 @@ def _set_by_type(header, keyword, value):
def _to_checksum_uri(value):
if value is None:
return None
- elif isinstance(value, caom2.ChecksumURI):
+ elif isinstance(value, str):
return value
else:
- return caom2.ChecksumURI(value)
+ raise TypeError(f'Expected a string, got {type(value)}')
diff --git a/caom2utils/caom2utils/polygonvalidator.py b/caom2utils/caom2utils/polygonvalidator.py
index 115e16d2..c9e2d1af 100644
--- a/caom2utils/caom2utils/polygonvalidator.py
+++ b/caom2utils/caom2utils/polygonvalidator.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2018. (c) 2018.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -68,10 +68,10 @@
import numpy as np
from spherical_geometry import polygon, vector
-from caom2 import Point, Polygon, MultiPolygon, SegmentType, Circle
+from caom2 import Polygon, MultiShape, Circle
-__all__ = ['validate_polygon', 'validate_multipolygon']
+__all__ = ['validate_polygon', 'validate_multishape']
def validate_polygon(poly):
@@ -156,82 +156,19 @@ def _validate_self_intersection_and_direction(ras, decs):
_validate_is_clockwise(ras, lon)
-def validate_multipolygon(mp):
+def validate_multishape(mp):
"""
Performs a basic validation of a multipolygon.
An AssertionError is thrown if the multipolygon is invalid ie (invalid indexes, invalid polygons etc.)
"""
-
+ # TODO - not sure this is useful anymore
if not mp:
return
- if not isinstance(mp, MultiPolygon):
- raise ValueError(f'MultiPoligon expected in validation received {type(mp)}')
-
- _validate_size_and_end_vertices(mp)
+ if not isinstance(mp, MultiShape):
+ raise ValueError(f'MultiShape expected in validation received {type(mp)}')
# perform a more detailed validation of this multipolygon object
- mp_validator = MultiPolygonValidator()
- for i in range(len(mp.vertices)):
- mp_validator.validate(mp.vertices[i])
-
-
-def _validate_size_and_end_vertices(mp):
- if len(mp.vertices) < 4:
- # triangle
- raise AssertionError('invalid polygon: {} vertices (min 4)'.format(len(mp.vertices)))
-
- if mp.vertices[0].type != SegmentType.MOVE:
- raise AssertionError('invalid polygon: first vertex is not a MOVE vertex')
-
- if mp.vertices[-1].type != SegmentType.CLOSE:
- raise AssertionError('invalid polygon: last vertex is not a CLOSE vertex')
-
-
-class MultiPolygonValidator:
- """
- A class to validate the sequencing of vertices in a polygon, as well as constructing and validating the polygon.
-
- An AssertionError is thrown if an incorrect polygon is detected.
- """
-
- def __init__(self):
- self._lines = 0
- self._open_loop = False
- self._polygon = Polygon()
-
- def validate(self, vertex):
- if vertex.type == SegmentType.MOVE:
- self._validate_move(vertex)
- elif vertex.type == SegmentType.CLOSE:
- self._validate_close(vertex)
- else:
- self._validate_line(vertex)
-
- def _validate_move(self, vertex):
- if self._open_loop:
- raise AssertionError('invalid polygon: MOVE vertex when loop open')
- self._lines = 0
- self._open_loop = True
- self._polygon.points.append(Point(vertex.cval1, vertex.cval2))
-
- def _validate_close(self, vertex):
- # close the polygon
- if not self._open_loop:
- raise AssertionError('invalid polygon: CLOSE vertex when loop close')
- if self._lines < 2:
- raise AssertionError('invalid polygon: minimum 2 lines required')
- self._open_loop = False
- # SphericalPolygon requires point[0] == point[-1]
- point = self._polygon.points[0]
- self._polygon.points.append(Point(point.cval1, point.cval2))
- # validate the polygons in the multipolygon
- validate_polygon(self._polygon)
- # instantiate a new Polygon for the next iteration
- self._polygon = Polygon()
-
- def _validate_line(self, vertex):
- if not self._open_loop:
- raise AssertionError('invalid polygon: LINE vertex when loop close')
- self._lines += 1
- self._polygon.points.append(Point(vertex.cval1, vertex.cval2))
+ for shape in mp.shapes:
+ if shape not in [Circle, Polygon]:
+ raise ValueError(f'Invalid shape in MultiShape: {shape}')
diff --git a/caom2utils/caom2utils/tests/data/brite/HD36486/HD36486.py b/caom2utils/caom2utils/tests/data/brite/HD36486/HD36486.py
index 243fcc91..3c863b05 100644
--- a/caom2utils/caom2utils/tests/data/brite/HD36486/HD36486.py
+++ b/caom2utils/caom2utils/tests/data/brite/HD36486/HD36486.py
@@ -1,8 +1,8 @@
-from caom2 import ProductType
+from caom2 import DataLinkSemantics
def _get_artifact_product_type(uri):
- return ProductType.SCIENCE
+ return DataLinkSemantics.SCIENCE
def _get_time_axis_range_end_val(uri):
diff --git a/caom2utils/caom2utils/tests/test_collections.py b/caom2utils/caom2utils/tests/test_collections.py
index df2ea0fe..78610c1e 100644
--- a/caom2utils/caom2utils/tests/test_collections.py
+++ b/caom2utils/caom2utils/tests/test_collections.py
@@ -101,7 +101,8 @@ def test_differences(directory):
assert len(expected_fname) == 1
expected = _read_observation(expected_fname[0]) # expected observation
assert len(expected.planes) == 1
- prod_id = [p.product_id for p in expected.planes.values()][0]
+ plane_uri = [p.uri for p in expected.planes.values()][0]
+ prod_id = plane_uri.split('/')[-1]
product_id = f'--productID {prod_id}'
collection_id = expected.collection
data_files = _get_files(['header', 'png', 'gif', 'cat', 'fits', 'h5', 'orig'], directory)
@@ -203,10 +204,11 @@ def _header(fqn):
header_mock.side_effect = _header
temp = tempfile.NamedTemporaryFile()
+ observation_id = expected.uri.split('/')[-1]
sys.argv = (
'{} -o {} --no_validate --observation {} {} {} {} '
'--resource-id ivo://cadc.nrc.ca/test'.format(
- application, temp.name, expected.collection, expected.observation_id, inputs, cardinality
+ application, temp.name, expected.collection, observation_id, inputs, cardinality
)
).split()
print(sys.argv)
@@ -303,7 +305,7 @@ def _get_uris(collection, fnames, obs):
id=a.uri,
file_type=a.content_type,
size=a.content_length,
- md5sum=a.content_checksum.checksum,
+ md5sum=a.content_checksum,
)
file_url = urlparse(a.uri)
file_id = file_url.path.split('/')[-1]
@@ -336,11 +338,11 @@ def _compare_observations(expected, actual, output_dir):
result = get_differences(expected, actual, 'Observation')
if result:
tmp = '\n'.join([r for r in result])
- msg = f'Differences found observation {expected.observation_id} in ' f'{output_dir}\n{tmp}'
+ msg = f'Differences found observation {expected.uri} in ' f'{output_dir}\n{tmp}'
_write_observation(actual)
raise AssertionError(msg)
else:
- logging.info('Observation {} in {} match'.format(expected.observation_id, output_dir))
+ logging.info('Observation {} in {} match'.format(expected.uri, output_dir))
def _read_observation(fname):
diff --git a/caom2utils/caom2utils/tests/test_custom_axis_util.py b/caom2utils/caom2utils/tests/test_custom_axis_util.py
index 59ba69de..7cf2d0c2 100644
--- a/caom2utils/caom2utils/tests/test_custom_axis_util.py
+++ b/caom2utils/caom2utils/tests/test_custom_axis_util.py
@@ -131,7 +131,6 @@ def test_function1d_to_interval_happy_path(self):
expected_interval = Interval(-502.5, -2.5)
self.assertEqual(expected_interval.lower, actual_interval.lower)
self.assertEqual(expected_interval.upper, actual_interval.upper)
- self.assertEqual(None, actual_interval.samples)
# function_1d.delta == 0.0 && function_1d.naxis > 1
naxis = int(100)
delta = 0.0
@@ -151,7 +150,6 @@ def test_range1d_to_interval(self):
expected_interval = Interval(1.1, 11.1)
self.assertEqual(expected_interval.lower, actual_interval.lower)
self.assertEqual(expected_interval.upper, actual_interval.upper)
- self.assertEqual(None, actual_interval.samples)
# function_1d.delta == 0.0 && function_1d.naxis > 1
start = RefCoord(float(0.9), float(1.1))
end = RefCoord(float(10.9), float(1.1))
@@ -163,14 +161,14 @@ def test_range1d_to_interval(self):
def test_compute_dimension_from_range_bounds(self):
# user_chunk = False, matches is None
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.SCIENCE
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.SCIENCE
+ part_product_type = chunk.DataLinkSemantics.SCIENCE
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.SCIENCE
+ artifact_product_type = chunk.DataLinkSemantics.SCIENCE
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -184,19 +182,19 @@ def test_compute_dimension_from_range_bounds(self):
self.assertEqual(expected_num_pixels, actual_num_pixels)
# user_chunk = False, ctype not match
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.SCIENCE
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.SCIENCE
+ part_product_type = chunk.DataLinkSemantics.SCIENCE
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.SCIENCE
+ artifact_product_type = chunk.DataLinkSemantics.SCIENCE
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "RM"
actual_num_pixels = wcs_util.CustomAxisUtil.compute_dimension_from_range_bounds(
artifacts, product_type, expected_ctype
@@ -205,19 +203,19 @@ def test_compute_dimension_from_range_bounds(self):
self.assertEqual(expected_num_pixels, actual_num_pixels)
# user_chunk = False, ptype not match
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.SCIENCE
+ part_product_type = chunk.DataLinkSemantics.SCIENCE
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.SCIENCE
+ artifact_product_type = chunk.DataLinkSemantics.SCIENCE
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "RM"
actual_num_pixels = wcs_util.CustomAxisUtil.compute_dimension_from_range_bounds(
artifacts, product_type, expected_ctype
@@ -226,19 +224,19 @@ def test_compute_dimension_from_range_bounds(self):
self.assertEqual(expected_num_pixels, actual_num_pixels)
# user_chunk = False, atype not match
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.SCIENCE
+ artifact_product_type = chunk.DataLinkSemantics.SCIENCE
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "RM"
actual_num_pixels = wcs_util.CustomAxisUtil.compute_dimension_from_range_bounds(
artifacts, product_type, expected_ctype
@@ -247,38 +245,38 @@ def test_compute_dimension_from_range_bounds(self):
self.assertEqual(expected_num_pixels, actual_num_pixels)
# user_chunk = True, current_type != expected_ctype
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs_with_function()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "FARADAY"
with pytest.raises(ValueError) as ex:
wcs_util.CustomAxisUtil.compute_dimension_from_range_bounds(artifacts, product_type, expected_ctype)
assert 'CTYPE must be the same across all Artifacts' in str(ex.value)
# user_chunk = True, get_num_pixels: range is not None
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs_with_range()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "RM"
actual_num_pixels = wcs_util.CustomAxisUtil.compute_dimension_from_range_bounds(
artifacts, product_type, expected_ctype
@@ -288,19 +286,19 @@ def test_compute_dimension_from_range_bounds(self):
# user_chunk = True, get_num_pixels: bounds with 3 samples that
# traverses _merge_into_list completely
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs_with_bounds_3_samples()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "RM"
actual_num_pixels = wcs_util.CustomAxisUtil.compute_dimension_from_range_bounds(
artifacts, product_type, expected_ctype
@@ -310,19 +308,19 @@ def test_compute_dimension_from_range_bounds(self):
# user_chunk = True, range = None, bounds = None, use_func and
# function = None
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "RM"
actual_num_pixels = wcs_util.CustomAxisUtil.compute_dimension_from_range_bounds(
artifacts, product_type, expected_ctype
@@ -344,19 +342,19 @@ def test_compute_dimension_from_wcs(self):
# bounds is not None, user_chunk = True, current_type != expected_ctype
bounds = Interval(1.1, 11.1)
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs_with_function()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "FARADAY"
with pytest.raises(ValueError) as ex:
wcs_util.CustomAxisUtil.compute_dimension_from_wcs(bounds, artifacts, product_type, expected_ctype)
@@ -365,19 +363,19 @@ def test_compute_dimension_from_wcs(self):
# ss >= scale, num = 1
bounds = Interval(1.1, 11.1)
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs_with_negative_delta()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "RM"
actual_dimension = wcs_util.CustomAxisUtil.compute_dimension_from_wcs(
bounds, artifacts, product_type, expected_ctype
@@ -386,14 +384,14 @@ def test_compute_dimension_from_wcs(self):
self.assertEqual(expected_dimension, actual_dimension)
# bounds is not None, user_chunk = False, sw = None
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.SCIENCE
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.SCIENCE
+ part_product_type = chunk.DataLinkSemantics.SCIENCE
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.SCIENCE
+ artifact_product_type = chunk.DataLinkSemantics.SCIENCE
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -409,22 +407,22 @@ def test_compute_dimension_from_wcs(self):
# ss >= scale, num = 2
bounds = Interval(1.1, 11.1)
test_chunk1 = Chunk()
- test_chunk1.product_type = chunk.ProductType.CALIBRATION
+ test_chunk1.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk1.custom = CustomTestUtil.good_wcs_with_negative_delta()
test_chunk2 = Chunk()
- test_chunk2.product_type = chunk.ProductType.CALIBRATION
+ test_chunk2.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk2.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk1, test_chunk2)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "RM"
actual_dimension = wcs_util.CustomAxisUtil.compute_dimension_from_wcs(
bounds, artifacts, product_type, expected_ctype
@@ -435,14 +433,14 @@ def test_compute_dimension_from_wcs(self):
def test_compute_bounds(self):
# user_chunk = False
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.SCIENCE
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.SCIENCE
+ part_product_type = chunk.DataLinkSemantics.SCIENCE
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.SCIENCE
+ artifact_product_type = chunk.DataLinkSemantics.SCIENCE
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -454,38 +452,38 @@ def test_compute_bounds(self):
self.assertEqual(expected_bounds, actual_bounds)
# user_chunk = True, current_type != expected_ctype
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs_with_function()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "FARADAY"
with pytest.raises(ValueError) as ex:
wcs_util.CustomAxisUtil.compute_bounds(artifacts, product_type, expected_ctype)
assert 'CTYPE must be the same across all Artifacts' in str(ex.value)
# user_chunk = True, range is not None
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs_with_range()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "RM"
actual_interval = wcs_util.CustomAxisUtil.compute_bounds(artifacts, product_type, expected_ctype)
expected_interval = Interval(1.1, 11.1)
@@ -493,19 +491,19 @@ def test_compute_bounds(self):
self.assertEqual(expected_interval.upper, actual_interval.upper)
# user_chunk = True, get_num_pixels: bounds with 3 samples that traverses _merge_into_list completely
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs_with_bounds_3_samples()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "RM"
actual_interval = wcs_util.CustomAxisUtil.compute_bounds(artifacts, product_type, expected_ctype)
expected_interval = Interval(-1.2, 11.2)
@@ -513,19 +511,19 @@ def test_compute_bounds(self):
self.assertEqual(expected_interval.upper, actual_interval.upper)
# user_chunk = True, function is not None
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs_with_function()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
artifacts = TypedList(Artifact, artifact)
- product_type = chunk.ProductType.CALIBRATION
+ product_type = chunk.DataLinkSemantics.CALIBRATION
expected_ctype = "RM"
actual_interval = wcs_util.CustomAxisUtil.compute_bounds(artifacts, product_type, expected_ctype)
expected_interval = Interval(-49.5, 19950.5)
@@ -535,14 +533,14 @@ def test_compute_bounds(self):
def test_compute(self):
# _choose_product returns Artifact.product (SCIENCE), user_chunk = False
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.SCIENCE
+ artifact_product_type = chunk.DataLinkSemantics.SCIENCE
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -552,14 +550,14 @@ def test_compute(self):
self.assertEqual(expected_axis, actual_axis)
# _choose_product returns Artifact.product (CALIBRATION), user_chunk = False
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.SCIENCE
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.SCIENCE
+ part_product_type = chunk.DataLinkSemantics.SCIENCE
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.CALIBRATION
+ artifact_product_type = chunk.DataLinkSemantics.CALIBRATION
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -569,14 +567,14 @@ def test_compute(self):
self.assertEqual(expected_axis, actual_axis)
# _choose_product returns Part.product (SCIENCE), user_chunk = False
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.SCIENCE
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.SCIENCE
+ part_product_type = chunk.DataLinkSemantics.SCIENCE
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.PREVIEW
+ artifact_product_type = chunk.DataLinkSemantics.PREVIEW
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -586,14 +584,14 @@ def test_compute(self):
self.assertEqual(expected_axis, actual_axis)
# _choose_product returns Part.product (CALIBRATION), user_chunk = False
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.SCIENCE
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.CALIBRATION
+ part_product_type = chunk.DataLinkSemantics.CALIBRATION
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.PREVIEW
+ artifact_product_type = chunk.DataLinkSemantics.PREVIEW
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -603,14 +601,14 @@ def test_compute(self):
self.assertEqual(expected_axis, actual_axis)
# _choose_product returns Chunk.product (SCIENCE), user_chunk = False
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.SCIENCE
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.PREVIEW
+ part_product_type = chunk.DataLinkSemantics.PREVIEW
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.PREVIEW
+ artifact_product_type = chunk.DataLinkSemantics.PREVIEW
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -620,14 +618,14 @@ def test_compute(self):
self.assertEqual(expected_axis, actual_axis)
# _choose_product returns Chunk.product (CALIBRATION), user_chunk = False
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.CALIBRATION
+ test_chunk.product_type = chunk.DataLinkSemantics.CALIBRATION
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.PREVIEW
+ part_product_type = chunk.DataLinkSemantics.PREVIEW
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.PREVIEW
+ artifact_product_type = chunk.DataLinkSemantics.PREVIEW
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -637,14 +635,14 @@ def test_compute(self):
self.assertEqual(expected_axis, actual_axis)
# _choose_product returns None, user_chunk = False
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.PREVIEW
+ test_chunk.product_type = chunk.DataLinkSemantics.PREVIEW
test_chunk.custom = CustomTestUtil.good_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.PREVIEW
+ part_product_type = chunk.DataLinkSemantics.PREVIEW
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.PREVIEW
+ artifact_product_type = chunk.DataLinkSemantics.PREVIEW
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -654,14 +652,14 @@ def test_compute(self):
self.assertEqual(expected_axis, actual_axis)
# _choose_product returns Artifact.product (SCIENCE), user_chunk = True, Chunk.custom is None
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.SCIENCE
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk.custom = None
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.SCIENCE
+ part_product_type = chunk.DataLinkSemantics.SCIENCE
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.SCIENCE
+ artifact_product_type = chunk.DataLinkSemantics.SCIENCE
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -672,14 +670,14 @@ def test_compute(self):
# _choose_product returns Artifact.product (SCIENCE), user_chunk = True, Chunk.custom is not None
# bad Chunk.custom.axis.axis.ctype
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.SCIENCE
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk.custom = CustomTestUtil.bad_ctype_wcs()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.SCIENCE
+ part_product_type = chunk.DataLinkSemantics.SCIENCE
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.SCIENCE
+ artifact_product_type = chunk.DataLinkSemantics.SCIENCE
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -690,14 +688,14 @@ def test_compute(self):
# _choose_product returns Artifact.product (SCIENCE), user_chunk = True, Chunk.custom is not None
# first_ctype == Chunk.custom.axis.axis.ctype
test_chunk = Chunk()
- test_chunk.product_type = chunk.ProductType.SCIENCE
+ test_chunk.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk.custom = CustomTestUtil.good_wcs_with_function()
test_chunks = TypedList(Chunk, test_chunk)
part_name = "test_part"
- part_product_type = chunk.ProductType.SCIENCE
+ part_product_type = chunk.DataLinkSemantics.SCIENCE
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.SCIENCE
+ artifact_product_type = chunk.DataLinkSemantics.SCIENCE
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -705,31 +703,29 @@ def test_compute(self):
expected_ctype = "RM"
expected_sample = Interval(-49.5, 19950.5)
expected_samples = [expected_sample]
- expected_bounds = Interval(-49.5, 19950.5, expected_samples)
+ expected_bounds = Interval(-49.5, 19950.5)
expected_dimension = 200
- expected_axis = plane.CustomAxis(expected_ctype, expected_bounds, expected_dimension)
+ expected_axis = plane.CustomAxis(expected_ctype, expected_bounds, expected_samples, expected_dimension)
actual_axis = wcs_util.CustomAxisUtil.compute(artifacts)
self.assertEqual(expected_axis.ctype, actual_axis.ctype)
self.assertEqual(expected_axis.bounds.lower, actual_axis.bounds.lower)
self.assertEqual(expected_axis.bounds.upper, actual_axis.bounds.upper)
- self.assertEqual(expected_axis.bounds.samples[0].lower, actual_axis.bounds.samples[0].lower)
- self.assertEqual(expected_axis.bounds.samples[0].upper, actual_axis.bounds.samples[0].upper)
self.assertEqual(expected_axis.dimension, actual_axis.dimension)
# _choose_product returns Artifact.product (SCIENCE), user_chunk = True, Chunk.custom is not None
# first_ctype == Chunk.custom.axis.axis.ctype
test_chunk_1 = Chunk()
- test_chunk_1.product_type = chunk.ProductType.SCIENCE
+ test_chunk_1.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk_1.custom = CustomTestUtil.good_wcs_with_function()
test_chunk_2 = Chunk()
- test_chunk_2.product_type = chunk.ProductType.SCIENCE
+ test_chunk_2.product_type = chunk.DataLinkSemantics.SCIENCE
test_chunk_2.custom = CustomTestUtil.good_wcs_with_function()
test_chunk_2.custom.axis.axis.ctype = "FARADAY"
test_chunks = TypedList(Chunk, test_chunk_1, test_chunk_2)
part_name = "test_part"
- part_product_type = chunk.ProductType.SCIENCE
+ part_product_type = chunk.DataLinkSemantics.SCIENCE
part = Part(part_name, part_product_type, test_chunks)
uri = 'mast:HST/product/test_file.jpg'
- artifact_product_type = chunk.ProductType.SCIENCE
+ artifact_product_type = chunk.DataLinkSemantics.SCIENCE
release_type = ReleaseType.DATA
artifact = Artifact(uri, artifact_product_type, release_type)
artifact.parts = TypedOrderedDict((Part), (part_name, part))
@@ -737,9 +733,9 @@ def test_compute(self):
expected_ctype = "RM"
expected_sample = Interval(-49.5, 19950.5)
expected_samples = [expected_sample]
- expected_bounds = Interval(-49.5, 19950.5, expected_samples)
+ expected_bounds = Interval(-49.5, 19950.5)
expected_dimension = 200
- expected_axis = plane.CustomAxis(expected_ctype, expected_bounds, expected_dimension)
+ expected_axis = plane.CustomAxis(expected_ctype, expected_bounds, expected_samples, expected_dimension)
with pytest.raises(ValueError) as ex:
actual_axis = wcs_util.CustomAxisUtil.compute(artifacts)
assert 'CTYPE must be the same across all Artifacts' in str(ex.value)
diff --git a/caom2utils/caom2utils/tests/test_fits2caom2.py b/caom2utils/caom2utils/tests/test_fits2caom2.py
index 880121ec..e35d0f91 100755
--- a/caom2utils/caom2utils/tests/test_fits2caom2.py
+++ b/caom2utils/caom2utils/tests/test_fits2caom2.py
@@ -79,8 +79,9 @@
from caom2utils.caom2blueprint import _get_and_update_artifact_meta
from caom2utils.wcs_parsers import FitsWcsParser, Hdf5WcsParser
-from caom2 import ObservationWriter, SimpleObservation, Algorithm, Artifact, ProductType, ReleaseType, DataProductType
-from caom2 import get_differences, obs_reader_writer, ObservationReader, Chunk, ObservationIntentType, ChecksumURI
+from caom2 import (ObservationWriter, SimpleObservation, Algorithm, Artifact,
+ DataLinkSemantics, ReleaseType, DataProductType)
+from caom2 import get_differences, obs_reader_writer, ObservationReader, Chunk, ObservationIntentType
from caom2 import CustomWCS, SpectralWCS, TemporalWCS, PolarizationWCS, SpatialWCS, Axis, CoordAxis1D, CoordAxis2D
from caom2 import CalibrationLevel
@@ -145,7 +146,7 @@ class MyExitError(Exception):
def test_augment_energy():
bp = ObsBlueprint(energy_axis=1)
test_fitsparser = FitsParser(sample_file_4axes, bp)
- artifact = Artifact('ad:{}/{}'.format('TEST', sample_file_4axes), ProductType.SCIENCE, ReleaseType.DATA)
+ artifact = Artifact('ad:{}/{}'.format('TEST', sample_file_4axes), DataLinkSemantics.PREVIEW_IMAGE, ReleaseType.DATA)
test_fitsparser.augment_artifact(artifact)
energy = artifact.parts['0'].chunks[0].energy
ex = _get_from_str_xml(EXPECTED_ENERGY_XML, ObservationReader()._get_spectral_wcs, 'energy')
@@ -163,7 +164,7 @@ def test_hdf5_wcs_parser_set_wcs():
test_f_name = 'taos2_test.h5'
test_uri = f'cadc:TEST/{test_f_name}'
test_fqn = f'{TESTDATA_DIR}/taos_h5file/20220201T200117/{test_f_name}'
- test_artifact = Artifact(test_uri, ProductType.SCIENCE, ReleaseType.DATA)
+ test_artifact = Artifact(test_uri, DataLinkSemantics.PREVIEW_IMAGE, ReleaseType.DATA)
# check the error messages
test_position_bp.configure_position_axes((4, 5))
@@ -197,7 +198,7 @@ def test_hdf5_wcs_parser_set_wcs():
def test_augment_failure():
bp = ObsBlueprint()
test_fitsparser = FitsParser(sample_file_4axes, bp)
- artifact = Artifact('ad:{}/{}'.format('TEST', sample_file_4axes), ProductType.SCIENCE, ReleaseType.DATA)
+ artifact = Artifact('ad:{}/{}'.format('TEST', sample_file_4axes), DataLinkSemantics.PREVIEW_IMAGE, ReleaseType.DATA)
with pytest.raises(TypeError):
test_fitsparser.augment_artifact(artifact)
@@ -242,7 +243,7 @@ def test_augment_artifact_energy_from_blueprint():
def test_augment_polarization():
test_fitsparser = FitsParser(sample_file_4axes, ObsBlueprint(polarization_axis=1))
- artifact = Artifact('ad:{}/{}'.format('TEST', sample_file_4axes), ProductType.SCIENCE, ReleaseType.DATA)
+ artifact = Artifact('ad:{}/{}'.format('TEST', sample_file_4axes), DataLinkSemantics.PREVIEW_IMAGE, ReleaseType.DATA)
test_fitsparser.augment_artifact(artifact)
polarization = artifact.parts['0'].chunks[0].polarization
ex = _get_from_str_xml(EXPECTED_POLARIZATION_XML, ObservationReader()._get_polarization_wcs, 'polarization')
@@ -306,7 +307,7 @@ def test_augment_artifact_polarization_from_blueprint():
def test_augment_artifact():
test_blueprint = ObsBlueprint(position_axes=(1, 2))
test_fitsparser = FitsParser(sample_file_4axes, test_blueprint)
- artifact = Artifact('ad:{}/{}'.format('TEST', sample_file_4axes), ProductType.SCIENCE, ReleaseType.DATA)
+ artifact = Artifact('ad:{}/{}'.format('TEST', sample_file_4axes), DataLinkSemantics.PREVIEW_IMAGE, ReleaseType.DATA)
test_fitsparser.augment_artifact(artifact)
assert artifact.parts is not None
assert len(artifact.parts) == 1
@@ -375,7 +376,8 @@ def test_augment_artifact_position_from_blueprint():
def test_augment_artifact_time():
test_fitsparser = FitsParser(sample_file_time_axes, ObsBlueprint(time_axis=1))
- artifact = Artifact('ad:{}/{}'.format('TEST', sample_file_time_axes), ProductType.SCIENCE, ReleaseType.DATA)
+ artifact = Artifact('ad:{}/{}'.format('TEST', sample_file_time_axes),
+ DataLinkSemantics.PREVIEW_IMAGE, ReleaseType.DATA)
test_fitsparser.augment_artifact(artifact)
assert artifact.parts is not None
assert len(artifact.parts) == 6
@@ -427,7 +429,7 @@ def test_get_wcs_values():
def test_wcs_parser_augment_failures():
test_parser = FitsWcsParser(get_test_header(sample_file_4axes)[0].header, sample_file_4axes, 0)
- test_obs = SimpleObservation('collection', 'MA1_DRAO-ST', Algorithm('exposure'))
+ test_obs = SimpleObservation(collection='collection', uri='caom:MA1_DRAO-ST', algorithm=Algorithm('exposure'))
with pytest.raises(ValueError):
test_parser.augment_custom(test_obs)
@@ -697,12 +699,13 @@ def test_augment_observation():
test_obs_blueprint.set('Plane.calibrationLevel', '2')
test_fitsparser = FitsParser(sample_file_4axes_obs, test_obs_blueprint)
test_fitsparser.blueprint = test_obs_blueprint
- test_obs = SimpleObservation('collection', 'MA1_DRAO-ST', Algorithm('exposure'))
+ test_obs = SimpleObservation('collection', 'caom:collection/MA1_DRAO-ST', Algorithm('exposure'))
test_fitsparser.augment_observation(test_obs, sample_file_4axes_uri, product_id='HI-line')
assert test_obs is not None
assert test_obs.planes is not None
assert len(test_obs.planes) == 1
- test_plane = test_obs.planes['HI-line']
+ test_plane_uri = f'{test_obs.uri}/HI-line'
+ test_plane = test_obs.planes[test_plane_uri]
assert test_plane.artifacts is not None
assert len(test_plane.artifacts) == 1
test_artifact = test_plane.artifacts[sample_file_4axes_uri]
@@ -725,9 +728,9 @@ def test_augment_value_errors():
ob = ObsBlueprint(position_axes=(1, 2))
ob.set('Plane.productID', None)
test_parser = BlueprintParser(obs_blueprint=ob)
- test_obs = SimpleObservation('collection', 'MA1_DRAO-ST', Algorithm('exposure'))
+ test_obs = SimpleObservation('collection', 'caom:MA1_DRAO-ST', Algorithm('exposure'))
with pytest.raises(ValueError):
- test_parser.augment_observation(test_obs, 'cadc:TEST/abc.fits.gz', product_id=None)
+ test_parser.augment_observation(test_obs, 'cadc:TEST/abc.fits.gz')
with pytest.raises(ValueError):
test_parser.augment_plane(test_obs, 'cadc:TEST/abc.fits.gz')
@@ -1054,22 +1057,25 @@ def _get_obs(from_xml_string):
EXPECTED_GENERIC_PARSER_FILE_SCHEME_XML = (
"""
test_collection_id
- test_observation_id
+ caom:test_collection_id/test_observation_id
+ 6df
exposure
- test_product_id
+ caom:test_collection_id/test_observation_id/test_product_id
image
3
+ ad:foo/bar0
+ 109
thumbnail
data
text/plain
@@ -1108,7 +1114,7 @@ def test_generic_parser():
java_config_file,
'--override',
text_override,
- fname,
+ 'ad:foo/bar0',
]
main_app()
if stdout_mock.getvalue():
@@ -1352,7 +1358,7 @@ def test_visit_generic_parser():
test_plugin = __name__
kwargs = {}
test_obs = SimpleObservation(
- collection='test_collection', observation_id='test_obs_id', algorithm=Algorithm('exposure')
+ collection='test_collection', uri='caom:test_obs_id', algorithm=Algorithm('exposure')
)
_visit(test_plugin, test_parser, test_obs, visit_local=None, **kwargs)
except ImportError:
@@ -1390,10 +1396,10 @@ def test_get_vos_meta(vos_mock):
)
vos_mock.return_value.get_node.side_effect = _get_node
test_uri = 'vos://cadc.nrc.ca!vospace/CAOMworkshop/Examples/DAO/' 'dao_c122_2016_012725.fits'
- test_artifact = Artifact(test_uri, ProductType.SCIENCE, ReleaseType.DATA)
+ test_artifact = Artifact(test_uri, DataLinkSemantics.PREVIEW_IMAGE, ReleaseType.DATA)
_get_and_update_artifact_meta(test_uri, test_artifact, subject=None)
assert test_artifact is not None
- assert test_artifact.content_checksum.uri == 'md5:5b00b00d4b06aba986c3663d09aa581f', 'checksum wrong'
+ assert test_artifact.content_checksum == 'md5:5b00b00d4b06aba986c3663d09aa581f', 'checksum wrong'
assert test_artifact.content_length == 682560, 'length wrong'
assert test_artifact.content_type == 'application/fits', 'content_type wrong'
assert vos_mock.called, 'mock not called'
@@ -1434,7 +1440,7 @@ def test_get_external_headers_fails(get_external_mock):
test_product_id = 'TEST_PRODUCT_ID'
test_blueprint = caom2utils.caom2blueprint.ObsBlueprint()
test_observation = SimpleObservation(
- collection=test_collection, observation_id=test_obs_id, algorithm=Algorithm(name='exposure')
+ collection=test_collection, uri=f'caom:{test_obs_id}', algorithm=Algorithm(name='exposure')
)
test_result = caom2utils.caom2blueprint._augment(
obs=test_observation,
@@ -1446,7 +1452,8 @@ def test_get_external_headers_fails(get_external_mock):
)
assert test_result is not None, 'expect a result'
assert len(test_result.planes.values()) == 1, 'plane added to result'
- test_plane = test_result.planes[test_product_id]
+ plane_uri = f'{test_result.uri}/{test_product_id}'
+ test_plane = test_result.planes[plane_uri]
assert len(test_plane.artifacts.values()) == 1, 'artifact added to plane'
assert test_uri in test_plane.artifacts.keys(), 'wrong artifact uri'
@@ -1528,10 +1535,11 @@ def get_time_exposure(self, ext):
test_blueprint.set('Artifact.releaseType', 'data')
test_blueprint.set('Chunk.time.exposure', 'get_time_exposure()', 1)
test_parser = FitsParser(src=[hdr1, hdr2], obs_blueprint=test_blueprint)
- test_obs = SimpleObservation('collection', 'MA1_DRAO-ST', Algorithm('exposure'))
+ test_obs = SimpleObservation('collection', 'caom:MA1_DRAO-ST', Algorithm('exposure'))
test_parser.augment_observation(test_obs, 'cadc:TEST/test_file_name.fits')
- assert 'PRODUCT_ID' in test_obs.planes.keys(), 'expect plane'
- test_plane = test_obs.planes['PRODUCT_ID']
+ plane_uri = f'{test_obs.uri}/PRODUCT_ID'
+ assert plane_uri in test_obs.planes.keys(), 'expect plane'
+ test_plane = test_obs.planes[plane_uri]
assert 'cadc:TEST/test_file_name.fits' in test_plane.artifacts.keys(), 'expect artifact'
test_artifact = test_plane.artifacts.pop('cadc:TEST/test_file_name.fits')
test_part = test_artifact.parts.pop('1')
@@ -1545,7 +1553,7 @@ def get_time_exposure(self, ext):
test_blueprint2.set('Plane.calibrationLevel', 'getCalibrationLevel()')
test_blueprint2.set('Plane.dataProductType', 'broken_function()')
test_parser2 = BlueprintParser(obs_blueprint=test_blueprint2)
- test_obs2 = SimpleObservation('collection', 'MA1_DRAO-ST', Algorithm('exposure'))
+ test_obs2 = SimpleObservation('collection', 'caom:MA1_DRAO-ST', Algorithm('exposure'))
with pytest.raises(ValueError):
test_parser2.augment_observation(test_obs2, 'cadc:TEST/abc.fits.gz')
@@ -1573,7 +1581,7 @@ def test_apply_blueprint_execute_external():
def test_update_artifact_meta_errors():
test_uri = 'gemini:GEMINI/abc.jpg'
- test_artifact = Artifact(uri=test_uri, product_type=ProductType.SCIENCE, release_type=ReleaseType.DATA)
+ test_artifact = Artifact(uri=test_uri, product_type=DataLinkSemantics.PREVIEW_IMAGE, release_type=ReleaseType.DATA)
client_mock = Mock(autospec=True)
client_mock.info.return_value = FileInfo(id=test_uri, file_type='application/octet', size=42, md5sum='md5:42')
test_uri = 'gemini://test.fits'
@@ -1584,13 +1592,13 @@ def test_update_artifact_meta_errors():
test_uri = 'gemini:GEMINI/abc.jpg'
_get_and_update_artifact_meta(test_uri, test_artifact, client=client_mock)
- assert test_artifact.content_checksum == ChecksumURI(uri='md5:42'), 'checksum'
+ assert test_artifact.content_checksum == 'md5:42', 'checksum'
assert test_artifact.content_length == 42, 'length'
assert test_artifact.content_type == 'application/octet', 'type'
# TODO - does this increase coverage?
test_uri = 'file:///test.fits.header'
- test_artifact = Artifact(uri=test_uri, product_type=ProductType.SCIENCE, release_type=ReleaseType.DATA)
+ test_artifact = Artifact(uri=test_uri, product_type=DataLinkSemantics.PREVIEW_IMAGE, release_type=ReleaseType.DATA)
client_mock.info.return_value = None
_get_and_update_artifact_meta(test_uri, test_artifact, net.Subject(), client=client_mock)
assert test_artifact.content_type is None, 'type'
@@ -1628,7 +1636,7 @@ def test_gen_proc_failure(augment_mock, stdout_mock, cap_mock, client_mock):
@patch('sys.stdout', new_callable=StringIO)
@patch('caom2utils.caom2blueprint.Client')
def test_parser_construction(vos_mock, stdout_mock):
- vos_mock.get_node.side_effect = _get_node
+ vos_mock.return_value.get_node.side_effect = _get_node
test_uri = 'vos:goliaths/abc.fits.gz'
test_blueprint = ObsBlueprint()
test_blueprint.set('Observation.instrument.keywords', 'instrument keyword')
diff --git a/caom2utils/caom2utils/tests/test_obs_blueprint.py b/caom2utils/caom2utils/tests/test_obs_blueprint.py
index a396309c..b16f3a5c 100644
--- a/caom2utils/caom2utils/tests/test_obs_blueprint.py
+++ b/caom2utils/caom2utils/tests/test_obs_blueprint.py
@@ -82,7 +82,7 @@ def test_obs_blueprint():
assert elems != ObsBlueprint.CAOM2_ELEMENTS
# default config (one entry per row...)
- assert str(ObsBlueprint()).count('\n') == 24
+ assert str(ObsBlueprint()).count('\n') == 23
print(ObsBlueprint())
# default config with WCS info
@@ -92,7 +92,7 @@ def test_obs_blueprint():
position_axes=(1, 2), energy_axis=3, polarization_axis=4, time_axis=5, obs_axis=6, custom_axis=7
)
).count('\n')
- == 90
+ == 89 # TODO why?
)
ob = ObsBlueprint()
diff --git a/caom2utils/caom2utils/tests/test_polygonvalidator.py b/caom2utils/caom2utils/tests/test_polygonvalidator.py
index f73e3253..eaf24148 100644
--- a/caom2utils/caom2utils/tests/test_polygonvalidator.py
+++ b/caom2utils/caom2utils/tests/test_polygonvalidator.py
@@ -70,7 +70,7 @@
import pytest
from caom2 import shape
-from caom2utils import validate_polygon, validate_multipolygon
+from caom2utils import validate_polygon
def test_open_polygon():
@@ -102,23 +102,20 @@ def test_open_polygon():
validate_polygon(shape.Polygon(closed_points))
# should detect that multipolygon is not closed
- v0 = shape.Vertex(-126.210938, 67.991108, shape.SegmentType.MOVE)
- v1 = shape.Vertex(-108.984375, 70.480896, shape.SegmentType.LINE)
- v2 = shape.Vertex(-98.789063, 66.912834, shape.SegmentType.LINE)
- v3 = shape.Vertex(-75.234375, 60.217991, shape.SegmentType.LINE)
- v4 = shape.Vertex(-87.890625, 52.241256, shape.SegmentType.LINE)
- v5 = shape.Vertex(-110.742188, 54.136696, shape.SegmentType.LINE)
- v6 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
- v7 = shape.Vertex(24.609375, 62.895218, shape.SegmentType.MOVE)
- v8 = shape.Vertex(43.593750, 67.322924, shape.SegmentType.LINE)
- v9 = shape.Vertex(55.898438, 62.734601, shape.SegmentType.LINE)
- v10 = shape.Vertex(46.757813, 56.145550, shape.SegmentType.LINE)
- v11 = shape.Vertex(26.015625, 55.354135, shape.SegmentType.LINE)
- v12 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
- closed_vertices = [v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12]
-
- # should detect that multipolygon is closed
- validate_multipolygon(shape.MultiPolygon(closed_vertices))
+ # v0 = shape.Vertex(-126.210938, 67.991108, shape.SegmentType.MOVE)
+ # v1 = shape.Vertex(-108.984375, 70.480896, shape.SegmentType.LINE)
+ # v2 = shape.Vertex(-98.789063, 66.912834, shape.SegmentType.LINE)
+ # v3 = shape.Vertex(-75.234375, 60.217991, shape.SegmentType.LINE)
+ # v4 = shape.Vertex(-87.890625, 52.241256, shape.SegmentType.LINE)
+ # v5 = shape.Vertex(-110.742188, 54.136696, shape.SegmentType.LINE)
+ # v6 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
+ # v7 = shape.Vertex(24.609375, 62.895218, shape.SegmentType.MOVE)
+ # v8 = shape.Vertex(43.593750, 67.322924, shape.SegmentType.LINE)
+ # v9 = shape.Vertex(55.898438, 62.734601, shape.SegmentType.LINE)
+ # v10 = shape.Vertex(46.757813, 56.145550, shape.SegmentType.LINE)
+ # v11 = shape.Vertex(26.015625, 55.354135, shape.SegmentType.LINE)
+ # v12 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
+ # closed_vertices = [v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12]
def test_polygon_self_intersection():
@@ -171,171 +168,171 @@ def test_polygon_self_intersection():
assert 'self intersecting' in str(ex.value)
-def test_open_multipolygon():
- # should detect that multipolygon is not closed
- v0 = shape.Vertex(-126.210938, 67.991108, shape.SegmentType.MOVE)
- v1 = shape.Vertex(-108.984375, 70.480896, shape.SegmentType.LINE)
- v2 = shape.Vertex(-98.789063, 66.912834, shape.SegmentType.LINE)
- v3 = shape.Vertex(-75.234375, 60.217991, shape.SegmentType.LINE)
- v4 = shape.Vertex(-87.890625, 52.241256, shape.SegmentType.LINE)
- v5 = shape.Vertex(-110.742188, 54.136696, shape.SegmentType.LINE)
- v6 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
- v7 = shape.Vertex(24.609375, 62.895218, shape.SegmentType.MOVE)
- v8 = shape.Vertex(43.593750, 67.322924, shape.SegmentType.LINE)
- v9 = shape.Vertex(55.898438, 62.734601, shape.SegmentType.LINE)
- v10 = shape.Vertex(46.757813, 56.145550, shape.SegmentType.LINE)
- v11 = shape.Vertex(26.015625, 55.354135, shape.SegmentType.LINE)
- v12 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
- no_vertices = []
- too_few_vertices = [v0, v1, v6]
- two_moves_vertices = [v0, v1, v7, v2, v3, v4, v5, v6]
- no_move_vertices = [v1, v2, v3, v4, v5, v6]
- two_closes_vertices = [v0, v1, v2, v3, v4, v5, v7, v8, v9, v10, v11, v12]
- no_close_vertices = [v0, v1, v2, v3, v4, v5]
- min_closed_vertices = [v0, v1, v2, v6]
- closed_vertices = [v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12]
- rv0 = shape.Vertex(26.015625, 55.354135, shape.SegmentType.MOVE)
- rv1 = shape.Vertex(46.757813, 56.145550, shape.SegmentType.LINE)
- rv2 = shape.Vertex(55.898438, 62.734601, shape.SegmentType.LINE)
- rv3 = shape.Vertex(43.593750, 67.322924, shape.SegmentType.LINE)
- rv4 = shape.Vertex(24.609375, 62.895218, shape.SegmentType.LINE)
- rv5 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
- rv6 = shape.Vertex(-110.742188, 54.136696, shape.SegmentType.MOVE)
- rv7 = shape.Vertex(-87.890625, 52.241256, shape.SegmentType.LINE)
- rv8 = shape.Vertex(-75.234375, 60.217991, shape.SegmentType.LINE)
- rv9 = shape.Vertex(-98.789063, 66.912834, shape.SegmentType.LINE)
- rv10 = shape.Vertex(-108.984375, 70.480896, shape.SegmentType.LINE)
- rv11 = shape.Vertex(-126.210938, 67.991108, shape.SegmentType.LINE)
- rv12 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
- counter_clockwise_vertices = [rv0, rv1, rv2, rv3, rv4, rv5, rv6, rv7, rv8, rv9, rv10, rv11, rv12]
- # should detect that the polygons is not clockwise
- with pytest.raises(AssertionError) as ex:
- validate_multipolygon(shape.MultiPolygon(counter_clockwise_vertices))
- assert 'clockwise winding direction' in str(ex.value)
- # should detect that there are not enough number of vertices to produce a multipolygon
- with pytest.raises(AssertionError) as ex:
- validate_multipolygon(shape.MultiPolygon(no_vertices))
- assert 'invalid polygon: 0 vertices' in str(ex.value)
- with pytest.raises(AssertionError) as ex:
- validate_multipolygon(shape.MultiPolygon(too_few_vertices))
- assert 'invalid polygon: 3 vertices' in str(ex.value)
- # no close between two 'MOVE'
- with pytest.raises(AssertionError) as ex:
- validate_multipolygon(shape.MultiPolygon(two_moves_vertices))
- assert 'invalid polygon: MOVE vertex when loop open' in str(ex.value)
- # no 'MOVE' before a 'CLOSE'
- with pytest.raises(AssertionError) as ex:
- validate_multipolygon(shape.MultiPolygon(no_move_vertices))
- assert 'invalid polygon: first vertex is not a MOVE' in str(ex.value)
- # no 'MOVE' between two 'CLOSE'
- with pytest.raises(AssertionError) as ex:
- validate_multipolygon(shape.MultiPolygon(two_closes_vertices))
- assert 'invalid polygon: MOVE vertex when loop open' in str(ex.value)
- # no 'CLOSE' after a 'MOVE'
- with pytest.raises(AssertionError) as ex:
- validate_multipolygon(shape.MultiPolygon(no_close_vertices))
- assert 'invalid polygon: last vertex is not a CLOSE' in str(ex.value)
- # multipolygon default constructor -> too few vertices
- with pytest.raises(AssertionError):
- validate_multipolygon(shape.MultiPolygon(None))
- # should detect that multipolygon is closed
- validate_multipolygon(shape.MultiPolygon(min_closed_vertices))
- # should detect that multipolygon is closed
- validate_multipolygon(shape.MultiPolygon(closed_vertices))
- # instantiated multipolygon should contain the same vertices
- p = shape.MultiPolygon(vertices=closed_vertices)
- validate_multipolygon(p)
- actual_vertices = p.vertices
- assert actual_vertices[0].cval1 == closed_vertices[0].cval1
- assert actual_vertices[0].cval2 == closed_vertices[0].cval2
- assert actual_vertices[0].type == shape.SegmentType.MOVE
- assert actual_vertices[1].cval1 == closed_vertices[1].cval1
- assert actual_vertices[1].cval2 == closed_vertices[1].cval2
- assert actual_vertices[1].type == shape.SegmentType.LINE
- assert actual_vertices[2].cval1 == closed_vertices[2].cval1
- assert actual_vertices[2].cval2 == closed_vertices[2].cval2
- assert actual_vertices[2].type == shape.SegmentType.LINE
- assert actual_vertices[3].cval1 == closed_vertices[3].cval1
- assert actual_vertices[3].cval2 == closed_vertices[3].cval2
- assert actual_vertices[3].type == shape.SegmentType.LINE
- assert actual_vertices[4].cval1 == closed_vertices[4].cval1
- assert actual_vertices[4].cval2 == closed_vertices[4].cval2
- assert actual_vertices[4].type == shape.SegmentType.LINE
- assert actual_vertices[5].cval1 == closed_vertices[5].cval1
- assert actual_vertices[5].cval2 == closed_vertices[5].cval2
- assert actual_vertices[5].type == shape.SegmentType.LINE
- assert actual_vertices[6].cval1 == closed_vertices[6].cval1
- assert actual_vertices[6].cval2 == closed_vertices[6].cval2
- assert actual_vertices[6].type == shape.SegmentType.CLOSE
- assert actual_vertices[7].cval1 == closed_vertices[7].cval1
- assert actual_vertices[7].cval2 == closed_vertices[7].cval2
- assert actual_vertices[7].type == shape.SegmentType.MOVE
- assert actual_vertices[8].cval1 == closed_vertices[8].cval1
- assert actual_vertices[8].cval2 == closed_vertices[8].cval2
- assert actual_vertices[8].type == shape.SegmentType.LINE
- assert actual_vertices[9].cval1 == closed_vertices[9].cval1
- assert actual_vertices[9].cval2 == closed_vertices[9].cval2
- assert actual_vertices[9].type == shape.SegmentType.LINE
- assert actual_vertices[10].cval1 == closed_vertices[10].cval1
- assert actual_vertices[10].cval2 == closed_vertices[10].cval2
- assert actual_vertices[10].type == shape.SegmentType.LINE
- assert actual_vertices[11].cval1 == closed_vertices[11].cval1
- assert actual_vertices[11].cval2 == closed_vertices[11].cval2
- assert actual_vertices[11].type == shape.SegmentType.LINE
- assert actual_vertices[12].cval1 == closed_vertices[12].cval1
- assert actual_vertices[12].cval2 == closed_vertices[12].cval2
- assert actual_vertices[12].type == shape.SegmentType.CLOSE
-
-
-def test_multipoly_self_intersect():
- # should detect self segment intersection of the multipolygon not near a Pole
- v1 = shape.Vertex(-115.488281, 45.867063, shape.SegmentType.MOVE)
- v2 = shape.Vertex(-91.230469, 36.075742, shape.SegmentType.LINE)
- v3 = shape.Vertex(-95.800781, 54.807017, shape.SegmentType.LINE)
- v4 = shape.Vertex(-108.457031, 39.951859, shape.SegmentType.LINE)
- v5 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
- points_with_self_intersecting_segments = [v1, v2, v3, v4, v5]
- with pytest.raises(AssertionError) as ex:
- validate_multipolygon(shape.MultiPolygon(points_with_self_intersecting_segments))
- assert 'self intersecting' in str(ex.value)
- # should detect self segment intersection of the multipolygon near the South Pole, with the Pole outside the
- # multipolygon
- v1 = shape.Vertex(0.6128286003, -89.8967940441, shape.SegmentType.MOVE)
- v2 = shape.Vertex(210.6391743183, -89.9073892376, shape.SegmentType.LINE)
- v3 = shape.Vertex(90.6405151921, -89.8972874698, shape.SegmentType.LINE)
- v4 = shape.Vertex(270.6114701911, -89.90689353, shape.SegmentType.LINE)
- v5 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
- points_with_self_intersecting_segments = [v1, v2, v3, v4, v5]
- with pytest.raises(AssertionError) as ex:
- validate_multipolygon(shape.MultiPolygon(points_with_self_intersecting_segments))
- assert 'self intersecting' in str(ex.value)
- # should detect self segment intersection of the multipolygon near the South Pole, with the Pole inside the
- # multipolygon
- v1 = shape.Vertex(0.6128286003, -89.8967940441, shape.SegmentType.MOVE)
- v2 = shape.Vertex(130.6391743183, -89.9073892376, shape.SegmentType.LINE)
- v3 = shape.Vertex(90.6405151921, -89.8972874698, shape.SegmentType.LINE)
- v4 = shape.Vertex(270.6114701911, -89.90689353, shape.SegmentType.LINE)
- v5 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
- points_with_self_intersecting_segments = [v1, v2, v3, v4, v5]
- with pytest.raises(AssertionError) as ex:
- validate_multipolygon(shape.MultiPolygon(points_with_self_intersecting_segments))
- assert 'self intersecting' in str(ex.value)
- # should detect self segment intersection of the multipolygon which intersects with meridian = 0
- v1 = shape.Vertex(-7.910156, 13.293411, shape.SegmentType.MOVE)
- v2 = shape.Vertex(4.042969, 7.068185, shape.SegmentType.LINE)
- v3 = shape.Vertex(4.746094, 18.030975, shape.SegmentType.LINE)
- v4 = shape.Vertex(-6.855469, 6.369894, shape.SegmentType.LINE)
- v5 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
- points_with_self_intersecting_segments = [v1, v2, v3, v4, v5]
- with pytest.raises(AssertionError) as ex:
- validate_multipolygon(shape.MultiPolygon(points_with_self_intersecting_segments))
- assert 'self intersecting' in str(ex.value)
-
-
-def test_failures():
- # nothing happens
- validate_multipolygon(None)
-
- test_object = type('', (), {})()
- with pytest.raises(ValueError):
- validate_multipolygon(test_object)
+# def test_open_multipolygon():
+# # should detect that multipolygon is not closed
+# v0 = shape.Vertex(-126.210938, 67.991108, shape.SegmentType.MOVE)
+# v1 = shape.Vertex(-108.984375, 70.480896, shape.SegmentType.LINE)
+# v2 = shape.Vertex(-98.789063, 66.912834, shape.SegmentType.LINE)
+# v3 = shape.Vertex(-75.234375, 60.217991, shape.SegmentType.LINE)
+# v4 = shape.Vertex(-87.890625, 52.241256, shape.SegmentType.LINE)
+# v5 = shape.Vertex(-110.742188, 54.136696, shape.SegmentType.LINE)
+# v6 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
+# v7 = shape.Vertex(24.609375, 62.895218, shape.SegmentType.MOVE)
+# v8 = shape.Vertex(43.593750, 67.322924, shape.SegmentType.LINE)
+# v9 = shape.Vertex(55.898438, 62.734601, shape.SegmentType.LINE)
+# v10 = shape.Vertex(46.757813, 56.145550, shape.SegmentType.LINE)
+# v11 = shape.Vertex(26.015625, 55.354135, shape.SegmentType.LINE)
+# v12 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
+# no_vertices = []
+# too_few_vertices = [v0, v1, v6]
+# two_moves_vertices = [v0, v1, v7, v2, v3, v4, v5, v6]
+# no_move_vertices = [v1, v2, v3, v4, v5, v6]
+# two_closes_vertices = [v0, v1, v2, v3, v4, v5, v7, v8, v9, v10, v11, v12]
+# no_close_vertices = [v0, v1, v2, v3, v4, v5]
+# min_closed_vertices = [v0, v1, v2, v6]
+# closed_vertices = [v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12]
+# rv0 = shape.Vertex(26.015625, 55.354135, shape.SegmentType.MOVE)
+# rv1 = shape.Vertex(46.757813, 56.145550, shape.SegmentType.LINE)
+# rv2 = shape.Vertex(55.898438, 62.734601, shape.SegmentType.LINE)
+# rv3 = shape.Vertex(43.593750, 67.322924, shape.SegmentType.LINE)
+# rv4 = shape.Vertex(24.609375, 62.895218, shape.SegmentType.LINE)
+# rv5 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
+# rv6 = shape.Vertex(-110.742188, 54.136696, shape.SegmentType.MOVE)
+# rv7 = shape.Vertex(-87.890625, 52.241256, shape.SegmentType.LINE)
+# rv8 = shape.Vertex(-75.234375, 60.217991, shape.SegmentType.LINE)
+# rv9 = shape.Vertex(-98.789063, 66.912834, shape.SegmentType.LINE)
+# rv10 = shape.Vertex(-108.984375, 70.480896, shape.SegmentType.LINE)
+# rv11 = shape.Vertex(-126.210938, 67.991108, shape.SegmentType.LINE)
+# rv12 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
+# counter_clockwise_vertices = [rv0, rv1, rv2, rv3, rv4, rv5, rv6, rv7, rv8, rv9, rv10, rv11, rv12]
+# # should detect that the polygons is not clockwise
+# with pytest.raises(AssertionError) as ex:
+# validate_multipolygon(shape.MultiPolygon(counter_clockwise_vertices))
+# assert 'clockwise winding direction' in str(ex.value)
+# # should detect that there are not enough number of vertices to produce a multipolygon
+# with pytest.raises(AssertionError) as ex:
+# validate_multipolygon(shape.MultiPolygon(no_vertices))
+# assert 'invalid polygon: 0 vertices' in str(ex.value)
+# with pytest.raises(AssertionError) as ex:
+# validate_multipolygon(shape.MultiPolygon(too_few_vertices))
+# assert 'invalid polygon: 3 vertices' in str(ex.value)
+# # no close between two 'MOVE'
+# with pytest.raises(AssertionError) as ex:
+# validate_multipolygon(shape.MultiPolygon(two_moves_vertices))
+# assert 'invalid polygon: MOVE vertex when loop open' in str(ex.value)
+# # no 'MOVE' before a 'CLOSE'
+# with pytest.raises(AssertionError) as ex:
+# validate_multipolygon(shape.MultiPolygon(no_move_vertices))
+# assert 'invalid polygon: first vertex is not a MOVE' in str(ex.value)
+# # no 'MOVE' between two 'CLOSE'
+# with pytest.raises(AssertionError) as ex:
+# validate_multipolygon(shape.MultiPolygon(two_closes_vertices))
+# assert 'invalid polygon: MOVE vertex when loop open' in str(ex.value)
+# # no 'CLOSE' after a 'MOVE'
+# with pytest.raises(AssertionError) as ex:
+# validate_multipolygon(shape.MultiPolygon(no_close_vertices))
+# assert 'invalid polygon: last vertex is not a CLOSE' in str(ex.value)
+# # multipolygon default constructor -> too few vertices
+# with pytest.raises(AssertionError):
+# validate_multipolygon(shape.MultiPolygon(None))
+# # should detect that multipolygon is closed
+# validate_multipolygon(shape.MultiPolygon(min_closed_vertices))
+# # should detect that multipolygon is closed
+# validate_multipolygon(shape.MultiPolygon(closed_vertices))
+# # instantiated multipolygon should contain the same vertices
+# p = shape.MultiPolygon(vertices=closed_vertices)
+# validate_multipolygon(p)
+# actual_vertices = p.vertices
+# assert actual_vertices[0].cval1 == closed_vertices[0].cval1
+# assert actual_vertices[0].cval2 == closed_vertices[0].cval2
+# assert actual_vertices[0].type == shape.SegmentType.MOVE
+# assert actual_vertices[1].cval1 == closed_vertices[1].cval1
+# assert actual_vertices[1].cval2 == closed_vertices[1].cval2
+# assert actual_vertices[1].type == shape.SegmentType.LINE
+# assert actual_vertices[2].cval1 == closed_vertices[2].cval1
+# assert actual_vertices[2].cval2 == closed_vertices[2].cval2
+# assert actual_vertices[2].type == shape.SegmentType.LINE
+# assert actual_vertices[3].cval1 == closed_vertices[3].cval1
+# assert actual_vertices[3].cval2 == closed_vertices[3].cval2
+# assert actual_vertices[3].type == shape.SegmentType.LINE
+# assert actual_vertices[4].cval1 == closed_vertices[4].cval1
+# assert actual_vertices[4].cval2 == closed_vertices[4].cval2
+# assert actual_vertices[4].type == shape.SegmentType.LINE
+# assert actual_vertices[5].cval1 == closed_vertices[5].cval1
+# assert actual_vertices[5].cval2 == closed_vertices[5].cval2
+# assert actual_vertices[5].type == shape.SegmentType.LINE
+# assert actual_vertices[6].cval1 == closed_vertices[6].cval1
+# assert actual_vertices[6].cval2 == closed_vertices[6].cval2
+# assert actual_vertices[6].type == shape.SegmentType.CLOSE
+# assert actual_vertices[7].cval1 == closed_vertices[7].cval1
+# assert actual_vertices[7].cval2 == closed_vertices[7].cval2
+# assert actual_vertices[7].type == shape.SegmentType.MOVE
+# assert actual_vertices[8].cval1 == closed_vertices[8].cval1
+# assert actual_vertices[8].cval2 == closed_vertices[8].cval2
+# assert actual_vertices[8].type == shape.SegmentType.LINE
+# assert actual_vertices[9].cval1 == closed_vertices[9].cval1
+# assert actual_vertices[9].cval2 == closed_vertices[9].cval2
+# assert actual_vertices[9].type == shape.SegmentType.LINE
+# assert actual_vertices[10].cval1 == closed_vertices[10].cval1
+# assert actual_vertices[10].cval2 == closed_vertices[10].cval2
+# assert actual_vertices[10].type == shape.SegmentType.LINE
+# assert actual_vertices[11].cval1 == closed_vertices[11].cval1
+# assert actual_vertices[11].cval2 == closed_vertices[11].cval2
+# assert actual_vertices[11].type == shape.SegmentType.LINE
+# assert actual_vertices[12].cval1 == closed_vertices[12].cval1
+# assert actual_vertices[12].cval2 == closed_vertices[12].cval2
+# assert actual_vertices[12].type == shape.SegmentType.CLOSE
+#
+#
+# def test_multipoly_self_intersect():
+# # should detect self segment intersection of the multipolygon not near a Pole
+# v1 = shape.Vertex(-115.488281, 45.867063, shape.SegmentType.MOVE)
+# v2 = shape.Vertex(-91.230469, 36.075742, shape.SegmentType.LINE)
+# v3 = shape.Vertex(-95.800781, 54.807017, shape.SegmentType.LINE)
+# v4 = shape.Vertex(-108.457031, 39.951859, shape.SegmentType.LINE)
+# v5 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
+# points_with_self_intersecting_segments = [v1, v2, v3, v4, v5]
+# with pytest.raises(AssertionError) as ex:
+# validate_multipolygon(shape.MultiPolygon(points_with_self_intersecting_segments))
+# assert 'self intersecting' in str(ex.value)
+# # should detect self segment intersection of the multipolygon near the South Pole, with the Pole outside the
+# # multipolygon
+# v1 = shape.Vertex(0.6128286003, -89.8967940441, shape.SegmentType.MOVE)
+# v2 = shape.Vertex(210.6391743183, -89.9073892376, shape.SegmentType.LINE)
+# v3 = shape.Vertex(90.6405151921, -89.8972874698, shape.SegmentType.LINE)
+# v4 = shape.Vertex(270.6114701911, -89.90689353, shape.SegmentType.LINE)
+# v5 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
+# points_with_self_intersecting_segments = [v1, v2, v3, v4, v5]
+# with pytest.raises(AssertionError) as ex:
+# validate_multipolygon(shape.MultiPolygon(points_with_self_intersecting_segments))
+# assert 'self intersecting' in str(ex.value)
+# # should detect self segment intersection of the multipolygon near the South Pole, with the Pole inside the
+# # multipolygon
+# v1 = shape.Vertex(0.6128286003, -89.8967940441, shape.SegmentType.MOVE)
+# v2 = shape.Vertex(130.6391743183, -89.9073892376, shape.SegmentType.LINE)
+# v3 = shape.Vertex(90.6405151921, -89.8972874698, shape.SegmentType.LINE)
+# v4 = shape.Vertex(270.6114701911, -89.90689353, shape.SegmentType.LINE)
+# v5 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
+# points_with_self_intersecting_segments = [v1, v2, v3, v4, v5]
+# with pytest.raises(AssertionError) as ex:
+# validate_multipolygon(shape.MultiPolygon(points_with_self_intersecting_segments))
+# assert 'self intersecting' in str(ex.value)
+# # should detect self segment intersection of the multipolygon which intersects with meridian = 0
+# v1 = shape.Vertex(-7.910156, 13.293411, shape.SegmentType.MOVE)
+# v2 = shape.Vertex(4.042969, 7.068185, shape.SegmentType.LINE)
+# v3 = shape.Vertex(4.746094, 18.030975, shape.SegmentType.LINE)
+# v4 = shape.Vertex(-6.855469, 6.369894, shape.SegmentType.LINE)
+# v5 = shape.Vertex(0.0, 0.0, shape.SegmentType.CLOSE)
+# points_with_self_intersecting_segments = [v1, v2, v3, v4, v5]
+# with pytest.raises(AssertionError) as ex:
+# validate_multipolygon(shape.MultiPolygon(points_with_self_intersecting_segments))
+# assert 'self intersecting' in str(ex.value)
+#
+#
+# def test_failures():
+# # nothing happens
+# validate_multipolygon(None)
+#
+# test_object = type('', (), {})()
+# with pytest.raises(ValueError):
+# validate_multipolygon(test_object)
diff --git a/caom2utils/caom2utils/tests/test_wcsvalidator.py b/caom2utils/caom2utils/tests/test_wcsvalidator.py
index 5605575a..53099b4f 100644
--- a/caom2utils/caom2utils/tests/test_wcsvalidator.py
+++ b/caom2utils/caom2utils/tests/test_wcsvalidator.py
@@ -2,7 +2,7 @@
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
#
-# (c) 2019. (c) 2019.
+# (c) 2025. (c) 2025.
# Government of Canada Gouvernement du Canada
# National Research Council Conseil national de recherches
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -203,14 +203,14 @@ def test_plane(self):
def test_part(self):
# CAOM2 entity is a Part
pname = "part1"
- product_type = chunk.ProductType.SCIENCE
+ product_type = chunk.DataLinkSemantics.SCIENCE
part = PartTestUtil.get_test_part(pname, product_type)
validate_wcs(part)
def test_artifact(self):
# CAOM2 entity is an Artifact
auri = "uri:foo/bar"
- product_type = chunk.ProductType.SCIENCE
+ product_type = chunk.DataLinkSemantics.SCIENCE
# with valid wcs
artifact = ArtifactTestUtil.get_test_artifact(auri, product_type)
validate_wcs(artifact)
@@ -218,7 +218,7 @@ def test_artifact(self):
def test_artifact_with_null_wcs(self):
# with null wcs
auri = "uri:foo/bar"
- product_type = chunk.ProductType.SCIENCE
+ product_type = chunk.DataLinkSemantics.SCIENCE
artifact = ArtifactTestUtil.get_test_artifact(auri, product_type)
validate_wcs(artifact)
c = artifact.parts['test_part'].chunks[0]
@@ -604,7 +604,7 @@ def get_test_plane(planeID):
uri1 = 'uri:foo/bar1'
uri2 = 'uri:foo/bar2'
uri3 = 'uri:foo/bar3'
- product_type = chunk.ProductType.SCIENCE
+ product_type = chunk.DataLinkSemantics.SCIENCE
a1 = ArtifactTestUtil.get_test_artifact(uri1, product_type)
a2 = ArtifactTestUtil.get_test_artifact(uri2, product_type)
a3 = ArtifactTestUtil.get_test_artifact(uri3, product_type)
@@ -632,9 +632,9 @@ def get_good_test_chunk(ptype):
@staticmethod
def get_test_artifact(uri, ptype):
- # chunk.ProductType.SCIENCE is a common type
+ # chunk.DataLinkSemantics.SCIENCE is a common type
if ptype is None:
- ptype = chunk.ProductType.SCIENCE
+ ptype = chunk.DataLinkSemantics.SCIENCE
test_artifact = artifact.Artifact(uri, ptype, artifact.ReleaseType.DATA)
chunks = TypedList(chunk.Chunk)
chunks.append(ArtifactTestUtil.get_good_test_chunk(ptype))
@@ -652,7 +652,7 @@ def __init__(self):
@staticmethod
def get_test_part(pname, ptype):
- # chunk.ProductType.SCIENCE is a common type
+ # chunk.DataLinkSemantics.SCIENCE is a common type
chunks = TypedList(chunk.Chunk)
chunks.append(ArtifactTestUtil.get_good_test_chunk(ptype))
diff --git a/caom2utils/caom2utils/wcs_util.py b/caom2utils/caom2utils/wcs_util.py
index 64ab1891..9db7cef1 100644
--- a/caom2utils/caom2utils/wcs_util.py
+++ b/caom2utils/caom2utils/wcs_util.py
@@ -343,26 +343,26 @@ def range1d_to_interval(wcs, r):
def _chose_product_type(artifacts):
ret = None
for a in artifacts:
- if chunk.ProductType.SCIENCE == a.product_type:
- return chunk.ProductType.SCIENCE
+ if chunk.DataLinkSemantics.SCIENCE == a.product_type:
+ return chunk.DataLinkSemantics.SCIENCE
- if chunk.ProductType.CALIBRATION == a.product_type:
- return chunk.ProductType.CALIBRATION
+ if chunk.DataLinkSemantics.CALIBRATION == a.product_type:
+ return chunk.DataLinkSemantics.CALIBRATION
for p_key in a.parts:
p = a.parts[p_key]
- if chunk.ProductType.SCIENCE == p.product_type:
- return chunk.ProductType.SCIENCE
+ if chunk.DataLinkSemantics.SCIENCE == p.product_type:
+ return chunk.DataLinkSemantics.SCIENCE
- if chunk.ProductType.CALIBRATION == p.product_type:
- return chunk.ProductType.CALIBRATION
+ if chunk.DataLinkSemantics.CALIBRATION == p.product_type:
+ return chunk.DataLinkSemantics.CALIBRATION
for c in p.chunks:
- if chunk.ProductType.SCIENCE == c.product_type:
- return chunk.ProductType.SCIENCE
+ if chunk.DataLinkSemantics.SCIENCE == c.product_type:
+ return chunk.DataLinkSemantics.SCIENCE
- if chunk.ProductType.CALIBRATION == c.product_type:
- ret = chunk.ProductType.CALIBRATION
+ if chunk.DataLinkSemantics.CALIBRATION == c.product_type:
+ ret = chunk.DataLinkSemantics.CALIBRATION
return ret
@@ -398,9 +398,9 @@ def compute(artifacts):
product_type = CustomAxisUtil._chose_product_type(artifacts)
axis_ctype = CustomAxisUtil._get_ctype(artifacts, product_type)
if axis_ctype is not None:
- c = plane.CustomAxis(axis_ctype)
if product_type is not None:
- c.bounds = CustomAxisUtil.compute_bounds(artifacts, product_type, axis_ctype)
+ bounds = CustomAxisUtil.compute_bounds(artifacts, product_type, axis_ctype)
+ c = plane.CustomAxis(axis_ctype, bounds, [bounds])
if c.dimension is None:
c.dimension = CustomAxisUtil.compute_dimension_from_wcs(
c.bounds, artifacts, product_type, axis_ctype
@@ -466,7 +466,7 @@ def compute_bounds(artifacts, product_type, expected_ctype):
lb = min(lb, sub.lower)
ub = max(ub, sub.upper)
- return shape.Interval(lb, ub, subs)
+ return shape.Interval(lb, ub)
@staticmethod
def compute_dimension_from_wcs(bounds, artifacts, product_type, expected_ctype):