Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mivot writer #627

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
89d8dae
add a clean ns option for the pretty printing
lmichel Nov 24, 2024
5fddfcd
layout changed after a read/write cycle
lmichel Nov 24, 2024
89c6fe3
local copy of the MIVOT XML schema
lmichel Nov 24, 2024
cf7fc92
annotation builder: new class
lmichel Nov 24, 2024
db49366
instance builder: new class
lmichel Nov 24, 2024
4803139
test suite for the annotations builders
lmichel Nov 24, 2024
34aacff
API for writing MIVOT annotations
lmichel Nov 27, 2024
1b6197b
test suite for the MIVOT writer
lmichel Nov 27, 2024
f367116
Doc updated with the split into 2 documents, one for the reader and one
lmichel Nov 28, 2024
7a9cff4
Changelog updated
lmichel Nov 28, 2024
c8b8549
code style
lmichel Nov 28, 2024
3ad6f43
style corrections
lmichel Nov 30, 2024
79c01b8
add PR number in change log
lmichel Nov 30, 2024
9e20fd3
removed
lmichel Nov 30, 2024
55cc826
add PR number
lmichel Nov 30, 2024
a622b32
resolve conflicts
lmichel Nov 30, 2024
be59549
add writer to api index
lmichel Nov 30, 2024
c68dfaa
Merge branch 'main' into mivot-writer
lmichel Nov 30, 2024
f9917ed
fix CI
lmichel Dec 1, 2024
c7cfe7b
Merge branch 'mivot-writer' of github.com:lmichel/pyvo into mivot-writer
lmichel Dec 1, 2024
7e0c918
fix use of mapping exception
lmichel Dec 1, 2024
4b40de5
add package init
lmichel Dec 1, 2024
c0068aa
fix call to MivotInstance dict
lmichel Dec 1, 2024
e9e273e
typo
lmichel Dec 2, 2024
28ee3ed
test MivotViewer.to_dict() output
lmichel Dec 2, 2024
a86c8f2
Fix CI errors
lmichel Dec 1, 2024
d74d094
add missing doctring for clean_ns parameter of pretty_string()
lmichel Dec 2, 2024
150acba
merge after rebase
lmichel Dec 2, 2024
9462516
fix test output in rest file
lmichel Dec 2, 2024
9277c03
flake8
lmichel Dec 2, 2024
b2efc78
Fix CI errors
lmichel Dec 1, 2024
2dee869
Merge branch 'mivot-writer' of github.com:lmichel/pyvo into mivot-writer
lmichel Dec 3, 2024
d36398b
restructuring documentation : toc updated, exposed API
lmichel Dec 13, 2024
1b5f460
Fix code blocks in docstring that were tagged as sphinx reference (one
lmichel Dec 13, 2024
514b48b
remove narrative doc from source files
lmichel Dec 14, 2024
b79706c
replace abbreviation with explicit name (clean_ns)
lmichel Dec 14, 2024
4932477
add explicit definition of the exposed namespaces (__all__)
lmichel Dec 14, 2024
b8021e9
add license
lmichel Dec 14, 2024
0303a85
Make optional parameters mandatory keywords.
lmichel Dec 14, 2024
3d9dec5
import viewer explicitely because pyvo.mivot.viewer is no longer the
lmichel Dec 15, 2024
bff20a5
XML typography more homogeneous, parameter model_url of add_model made
lmichel Dec 15, 2024
bab58f2
fix CI errors
lmichel Dec 15, 2024
d470071
DOC: fix headings
bsipocz Dec 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Enhancements and Fixes

- MIVOT module: the model references in the dictionaries that are used to build ``MivotInstance``
objects are made more consistent [#551]

- RegTAP constraints involving tables other than rr.resource are now
done via subqueries for less duplication of interfaces. [#562, #572]

Expand All @@ -62,7 +62,10 @@ Enhancements and Fixes

- Updated getdatalink to be consistent with iter_datalinks. [#613]


- Extending the MIVOT module with the ability to build annotations component by component
bsipocz marked this conversation as resolved.
Show resolved Hide resolved
and put them into a VOTable. [#627]


Deprecations and Removals
-------------------------

Expand Down
199 changes: 18 additions & 181 deletions docs/mivot/index.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
.. _index:

********************
MIVOT (`pyvo.mivot`)
********************

This module contains the new feature of annotations in VOTable.
This module contains the new feature handling model annotations in VOTable.
Astropy version >= 6.0 is required.

Introduction
Expand Down Expand Up @@ -42,8 +44,8 @@ Some of the examples have been provided by a special end-point of the Vizier con
(https://cdsarc.cds.unistra.fr/beta/viz-bin/mivotconesearch) that maps query results to this model.

.. image:: _images/mangoEpochPosition.png
:width: 500
:alt: EpochPropagation class used to validate this api.
:width: 500
:alt: EpochPropagation class used to validate this api.

It is to be noted that the Vizier service does not annotate errors at the time of writing (Q1 2024)

Expand All @@ -56,185 +58,19 @@ which allows to get (and set) Mivot blocks from/into VOTables as an XML element
epoch propagation use case:

- ``JOIN`` features are not supported.
- ``TEMPLATES`` with more than one ``INSANCE`` not supported.

Integrated Readout
------------------
The ``ModelViewer`` module manages access to data mapped to a model through dynamically
generated objects (``MivotInstance``class).
The example below shows how a VOTable, resulting from a cone-search query which data are mapped
to the ``EpochPosition`` class, can be consumed.

.. doctest-remote-data::
>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord
>>> from pyvo.dal.scs import SCSService
>>> from pyvo.utils.prototype import activate_features
>>> from pyvo.mivot.version_checker import check_astropy_version
>>> from pyvo.mivot.viewer.mivot_viewer import MivotViewer
>>> activate_features("MIVOT")
>>> if check_astropy_version() is False:
... pytest.skip("MIVOT test skipped because of the astropy version.")
>>> scs_srv = SCSService("https://cdsarc.cds.unistra.fr/beta/viz-bin/mivotconesearch/I/239/hip_main")
>>> m_viewer = MivotViewer(
... scs_srv.search(
... pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame='icrs'),
... radius=0.05
... )
... )
>>> mivot_instance = m_viewer.dm_instance
>>> print(mivot_instance.dmtype)
mango:EpochPosition
>>> print(mivot_instance.coordSys.spaceRefFrame.value)
ICRS
>>> while m_viewer.next():
... print(f"position: {mivot_instance.latitude.value} {mivot_instance.longitude.value}")
position: 59.94033461 52.26722684


In this example, the data readout is totally managed by the ``MivotViewer`` instance.
The ``astropy.io.votable`` API is encapsulated in this module.

Model leaves (class attributes) are complex types that provide additional information:

- ``value``: attribute value
- ``dmtype``: attribute type such as defined in the Mivot annotations
- ``unit``: attribute unit such as defined in the Mivot annotations
- ``ref``: identifier of the table column mapped on the attribute

The model view on a data row can also be passed as a Python dictionary
using the ``to_dict()`` method of ``MivotInstance``.

.. code-block:: python
:caption: Working with a model view as a dictionary
(the JSON layout has been squashed for display purpose)

from pyvo.mivot import MivotViewer
from pyvo.mivot.utils.dict_utils import DictUtils

m_viewer = MivotViewer(path_to_votable)
mivot_instance = m_viewer.dm_instance
mivot_object_dict = mivot_object.to_dict()

DictUtils.print_pretty_json(mivot_object_dict)
{
"dmtype": "mango:EpochPosition",
"longitude": {"value": 359.94372764, "unit": "deg"},
"latitude": {"value": -0.28005255, "unit": "deg"},
"pmLongitude": {"value": -5.14, "unit": "mas/yr"},
"pmLatitude": {"value": -25.43, "unit": "mas/yr"},
"epoch": {"value": 1991.25, "unit": "year"},
"coordSys": {
"dmtype": "coords:SpaceSys",
"dmid": "ICRS",
"dmrole": "coords:Coordinate.coordSys",
"spaceRefFrame": {"value": "ICRS"},
},
}

- It is recommended to use a copy of the
dictionary as it will be rebuilt each time the ``to_dict()`` method is invoked.
- The default representation of ``MivotInstance`` instances is made with a pretty
string serialization of this dictionary (method ``__repr__()``).
- An extended version of the object dictionary e.g. with information about where
the values were picked from from, is available using the method ``to_hk_dict()``.

Per-Row Readout
---------------

The annotation schema can also be applied to table rows read outside of the ``MivotViewer``
with the `astropy.io.votable` API:

.. code-block:: python
:caption: Accessing the model view of Astropy table rows

votable = parse(path_to_votable)
table = votable.resources[0].tables[0]
# init the viewer
mivot_viewer = MivotViewer(votable, resource_number=0)
mivot_object = mivot_viewer.dm_instance
# and feed it with the table row
read = []
for rec in table.array:
mivot_object.update(rec)
read.append(mivot_object.longitude.value)
# show that the model retrieve the correct data values
assert rec["RAICRS"] == mivot_object.longitude.value
assert rec["DEICRS"] == mivot_object.latitude.value

In this case, it is up to the user to ensure that the read data rows are those mapped by the Mivot annotations.

Get a SkyCoord Instance Directly From the Annotations
-----------------------------------------------------

Once you get a ``MivotInstance`` representing the last row read, you can use it to create an ``astropy.SkyCoord`` object.

.. code-block:: python
:caption: Accessing the model view of Astropy table rows

from pyvo.mivot import MivotViewer

m_viewer = MivotViewer(path_to_votable)
mivot_instance = m_viewer.dm_instance
print(mivot_instance.get_SkyCoord())
<SkyCoord (ICRS): (ra, dec) in deg(52.26722684, 59.94033461)
(pm_ra_cosdec, pm_dec) in mas / yr(-0.82, -1.85)>

This feature works under the condition that the annotations contain a valid instance of ``mango:EPochPosition``, otherwise
a ``NoMatchingDMTypeError`` is thrown.
Although not a standard at the time of writing, the class structure supported by this implementation must match the figure above.


For XML Hackers
---------------

The model instances can also be serialized as XML elements that can be parsed with XPath queries.

.. code-block:: python
:caption: Accessing the XML view of the mapped model instances

with MivotViewer(path_to_votable) as mivot_viewer:
while mivot_viewer.next():
xml_view = mivot_viewer.xml_view
# do whatever you want with this XML element

It to be noted that ``mivot_viewer.xml_view`` is a shortcut
for ``mivot_viewer.xml_view.view`` where ``mivot_viewer.xml_view``
is is an instance of ``pyvo.mivot.viewer.XmlViewer``.
This object provides many functions facilitating the XML parsing.

Class Generation in a Nutshell
------------------------------

MIVOT reconstructs model structures with 3 elements:

- ``INSTANCE`` for the objects
- ``ATTRIBUTE`` for the attributes
- ``COLLECTION`` for the elements with a cardinality greater than 1

The role played by each of these elements in the model hierarchy is defined
by its ``@dmrole`` XML attribute. Types of both ``INSTANCE`` and ``ATTRIBUTE`` are defined by
their ``@dmtype`` XML attributes.

``MivotInstance`` classes are built by following MIVOT annotation structure:

- ``INSTANCE`` are represented by Python classes
- ``ATTRIBUTE`` are represented by Python class fields
- ``COLLECTION`` are represented by Python lists ([])

``@dmrole`` and ``@dmtype`` cannot be used as Python keywords as such, because they are built from VO-DML
identifiers, which have the following structure: ``model:a.b``.

- Only the last part of the path is kept for attribute names.
- For class names, forbidden characters (``:`` or ``.``) are replaced with ``_``.
- Original ``@dmtype`` are kept as attributes of generated Python objects.
- The structure of the ``MivotInstance`` objects can be inferred from the mapped model in 2 different ways:
- ``TEMPLATES`` with more than one ``INSTANCE`` not supported.


Using the MIVOT package
=======================

The ``pyvo.mivot`` module can be used to either read or build annotations.

.. toctree::

viewer
writer

- 1. From the MIVOT instance property ``MivotInstance.to_dict()`` a shown above.
This is a pure Python dictionary but its access can be slow because it is generated
on the fly each time the property is invoked.
- 2. From the internal class dictionary ``MivotInstance.__dict__``
(see the Python `data model <https://docs.python.org/3/reference/datamodel.html>`_).

Reference/API
=============
Expand All @@ -244,3 +80,4 @@ Reference/API
.. automodapi:: pyvo.mivot.seekers
.. automodapi:: pyvo.mivot.features
.. automodapi:: pyvo.mivot.utils
.. automodapi:: pyvo.mivot.writer
Loading
Loading