Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9304187
remove obsolete words
lmichel Sep 4, 2025
9aca00a
Remove XML API which has never been used
lmichel Sep 4, 2025
96920de
update docstrings and process the when no TEMPLATES is found
lmichel Sep 4, 2025
c564f49
support the case of TEMPLATES containing multiple INSTANCE (doc not
lmichel Sep 4, 2025
f2cf3a4
adapt the test suite to the new viewer feature (multiple INSTANCE in
lmichel Sep 4, 2025
cf72648
The viewer builds MivotInstances for INSTANCEs children of GLOBALS
lmichel Sep 4, 2025
44aeee5
add missing astropy version checking
lmichel Sep 4, 2025
cf8e572
make SkyCoordBuilder able to process MangoObject instances
lmichel Sep 24, 2025
3009566
minor update to work with the Simbad output conesearch
lmichel Sep 26, 2025
f535b44
make the MIVOT schema validation optional in order to prevent unit tests
lmichel Oct 3, 2025
c7ca9f0
fix a big mistake in the parallax to distance computation
lmichel Oct 3, 2025
50dd934
mapping EpochPostion toSkyCoord moved frep SkyCoordBuilder to here
lmichel Oct 6, 2025
74e9c45
use of the glossary for the skyCoord fied mapping - improve the tyest
lmichel Oct 6, 2025
e7dcf13
mapping EpochPostion to SkyCoord moved from SkyCoordBuilder to the
lmichel Oct 6, 2025
a4c103d
Use of local copies of VOTable schemas to make XML validation working
lmichel Oct 7, 2025
8226621
update doc strings- remove useless methods - make some minor fixes
lmichel Oct 7, 2025
8d73e8f
SkyCoordBuilder can be instantiated from either a dict (for the tests…
lmichel Oct 7, 2025
0674c74
update test data
lmichel Oct 7, 2025
359b041
flake8 - revamp the viewer documentation
lmichel Oct 7, 2025
5c767a2
changelog updated
lmichel Oct 7, 2025
cb982fb
use of the glossary for the skyCoord fied mapping - improve the test
lmichel Oct 6, 2025
e831a02
solve potential conflicts with astropy/main
lmichel Oct 7, 2025
b34808e
(re) merge with remote
lmichel Oct 7, 2025
e884560
Merge branch 'main' into mango-next
lmichel Oct 7, 2025
f7a6d54
stylecheck
lmichel Oct 7, 2025
c166469
Merge branch 'mango-next' of github.com:lmichel/pyvo into mango-next
lmichel Oct 7, 2025
97933f4
add homework with Simbad
lmichel Oct 7, 2025
15a6df7
Correct doc style a little bit
lmichel Oct 8, 2025
beed1d8
Some style improvement by DeepL
lmichel Oct 8, 2025
71cde7b
Brigita's review (2025-10-08)
lmichel Oct 9, 2025
3ee4349
Brigita's review (2025-10-08)
lmichel Oct 9, 2025
d71cdeb
Merge branch 'mango-next' of github.com:lmichel/pyvo into mango-next
lmichel Oct 9, 2025
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
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Enhancements and Fixes

- Add retry option to AsyncTAPJob.fetch_result for transient failures [#696]

- Upgrade of the ``MivotViewer`` API (``xml_viewer`` module removed, support of
multiple mapped objects per row, partial redesign of the public API) -
Improve the gateway between annotations and ``SkyCoord`` objects -
revamp the viewer documentation. [#698]

Deprecations and Removals
-------------------------
Expand Down
192 changes: 110 additions & 82 deletions docs/mivot/example.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
.. _mivot-examples:

************************************************************
MIVOT (``pyvo.mivot``): How to use annotated data - Examples
************************************************************

Photometric properties readout
Photometric Properties Readout
==============================

This example is based on VOTables provided by the ``XTapDB`` service.
Expand All @@ -22,32 +24,32 @@ to tell the server to annotate the queried data.

(*Please read the comment inside the code snippet carefully to fully understand the process*)

.. code-block:: python
.. code-block:: python

import pytest
from pyvo.utils import activate_features
from pyvo.dal import TAPService
from pyvo.mivot.utils.xml_utils import XmlUtils
from pyvo.mivot.utils.dict_utils import DictUtils
from pyvo.mivot.viewer.mivot_viewer import MivotViewer
import pytest
from pyvo.utils import activate_features
from pyvo.dal import TAPService
from pyvo.mivot.utils.xml_utils import XmlUtils
from pyvo.mivot.utils.dict_utils import DictUtils
from pyvo.mivot.viewer.mivot_viewer import MivotViewer

# Enable MIVOT-specific features in the pyvo library
activate_features("MIVOT")
# Enable MIVOT-specific features in the pyvo library
activate_features("MIVOT")

service = TAPService('https://xcatdb.unistra.fr/xtapdb')
result = service.run_sync(
service = TAPService('https://xcatdb.unistra.fr/xtapdb')
result = service.run_sync(
"""
SELECT TOP 5 * FROM "public".mergedentry
""",
format="application/x-votable+xml;content=mivot"
)

# The MIVOT viewer generates the model view of the data
m_viewer = MivotViewer(result, resolve_ref=True)
# The MIVOT viewer generates the model view of the data
m_viewer = MivotViewer(result, resolve_ref=True)

# Print out the Mivot annotations read out of the VOtable
# This statement is just for a pedagogic purpose (access to a private attribute)
XmlUtils.pretty_print(m_viewer._mapping_block)
# Print out the Mivot annotations read out of the VOtable
# This statement is just for a pedagogic purpose (access to a private attribute)
XmlUtils.pretty_print(m_viewer._mapping_block)


In this first step we just queried the service and we built the object that will process the Mivot annotations.
Expand All @@ -65,46 +67,49 @@ The Mivot block printing output is too long to be listed here. However, the scre
At instantiation time, the viewer reads the first data row, which must exist,
in order to construct a Python object that reflects the mapped model.

.. code-block:: python
.. code-block:: python

# Build a Python object matching the TEMPLATES content and
# which leaves are set with the values of the first row
mango_object = m_viewer.dm_instance
# Build a Python object matching the TEMPLATES content and
# which leaves are set with the values of the first row
mango_object = m_viewer.dm_instance

# Print out the content of the Python object
# This statement is just for a pedagogic purpose
DictUtils.print_pretty_json(mango_object.to_dict())
# Print out the content of the Python object
# This statement is just for a pedagogic purpose
DictUtils.print_pretty_json(mango_object.to_dict())

The annotations are consumed by this dynamic Python object which leaves are set with the data of the current row.
You can explore the structure of this object by using the printed dictionary or standard object paths as shown below.

Now, we can iterate through the table data and retrieve an updated Mivot instance for each row.

.. code-block:: python

while m_viewer.next_row_view():
if mango_object.dmtype == "mango:MangoObject":
print(f"Read source {mango_object.identifier.value} {mango_object.dmtype}")
for mango_property in mango_object.propertyDock:
if mango_property.dmtype == "mango:Brightness":
if mango_property.value.value:
mag_value = mango_property.value.value
mag_error = mango_property.error.sigma.value
phot_cal = mango_property.photCal
spectral_location = phot_cal.photometryFilter.spectralLocation
mag_filter = phot_cal.identifier.value
spectral_location = phot_cal.photometryFilter.spectralLocation
mag_wl = spectral_location.value.value
sunit = spectral_location.unitexpression.value

print(f" flux at {mag_wl} {sunit} (filter {mag_filter}) is {mag_value:.2e} +/- {mag_error:.2e}")

Read source 4XMM J054329.3-682106 mango:MangoObject
.. code-block:: python

while m_viewer.next_row_view():
if mango_object.dmtype == "mango:MangoObject":
print(f"Read source {mango_object.identifier.value} {mango_object.dmtype}")
for mango_property in mango_object.propertyDock:
if mango_property.dmtype == "mango:Brightness":
if mango_property.value.value:
mag_value = mango_property.value.value
mag_error = mango_property.error.sigma.value
phot_cal = mango_property.photCal
spectral_location = phot_cal.photometryFilter.spectralLocation
mag_filter = phot_cal.identifier.value
spectral_location = phot_cal.photometryFilter.spectralLocation
mag_wl = spectral_location.value.value
sunit = spectral_location.unitexpression.value

print(f" flux at {mag_wl} {sunit} (filter {mag_filter}) is {mag_value:.2e} +/- {mag_error:.2e}")


.. code-block:: text

Read source 4XMM J054329.3-682106 mango:MangoObject
flux at 0.35 keV (filter XMM/EPIC/EB1) is 8.35e-14 +/- 3.15e-14
flux at 0.75 keV (filter XMM/EPIC/EB2) is 3.26e-15 +/- 5.45e-15
flux at 6.1 keV (filter XMM/EPIC/EB8) is 8.68e-14 +/- 6.64e-14
...
...
...
...

The same code can easily be connected with matplotlib to plot SEDs as shown below (code not provided).

Expand All @@ -113,11 +118,11 @@ The same code can easily be connected with matplotlib to plot SEDs as shown belo
:width: 500
:alt: XMM SED

It is to noted that the current table row keeps available through the Mivot viewer.
It is to be noted that the current table row keeps available through the Mivot viewer.

.. code-block:: python
.. code-block:: python

row = m_viewer.table_row
row = m_viewer.table_row


.. important::
Expand All @@ -129,7 +134,7 @@ It is to noted that the current table row keeps available through the Mivot view

The same client code can be reused in many places with many datasets, provided they are annotated.

EpochPosition property readout
EpochPosition Property Readout
==============================

This example is based on a VOtable resulting on a Vizier cone search.
Expand All @@ -148,48 +153,47 @@ which models a full source's astrometry at a given date.

In the first step below, we run a standard cone search query by using the standard PyVO API.

.. code-block:: python
.. code-block:: python

import pytest
import astropy.units as u
from astropy.coordinates import SkyCoord
from pyvo.dal.scs import SCSService
import pytest
import astropy.units as u
from astropy.coordinates import SkyCoord
from pyvo.dal.scs import SCSService
from pyvo.utils import activate_features
from pyvo.mivot.viewer.mivot_viewer import MivotViewer
from pyvo.mivot.features.sky_coord_builder import SkyCoordBuilder
from pyvo.mivot.utils.dict_utils import DictUtils

from pyvo.utils import activate_features
from pyvo.mivot.viewer.mivot_viewer import MivotViewer
from pyvo.mivot.features.sky_coord_builder import SkyCoordBuilder
from pyvo.mivot.utils.dict_utils import DictUtils
# Enable MIVOT-specific features in the pyvo library
activate_features("MIVOT")

# Enable MIVOT-specific features in the pyvo library
activate_features("MIVOT")
scs_srv = SCSService("https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main")

scs_srv = SCSService("https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main")

query_result = scs_srv.search(
query_result = scs_srv.search(
pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame='icrs'),
radius=0.5)

# The MIVOt viewer generates the model view of the data
m_viewer = MivotViewer(query_result, resolve_ref=True)
# The MIVOt viewer generates the model view of the data
m_viewer = MivotViewer(query_result, resolve_ref=True)

Once the query is finished, we can get a reference to the object that will process the Mivot annotations.

.. code-block:: python
.. code-block:: python

# Build a Python object matching the TEMPLATES content and
# which leaves are set with the values of the first row
mango_property = m_viewer.dm_instance
# Build a Python object matching the TEMPLATES content and
# which leaves are set with the values of the first row
mango_property = m_viewer.dm_instance

# Print out the content of the Python object
# This statement is just for a pedagogic purpose
DictUtils.print_pretty_json(mango_property.to_dict())
# Print out the content of the Python object
# This statement is just for a pedagogic purpose
DictUtils.print_pretty_json(mango_property.to_dict())

The annotations are consumed by this dynamic Python object which leaves are set with the data of the current row.
You can explore the structure of this object by using standard object paths or by browsing the dictionary shown below.

.. code-block:: json

{
{
"dmtype": "mango:EpochPosition",
"longitude": {
"dmtype": "ivoa:RealQuantity",
Expand Down Expand Up @@ -234,19 +238,19 @@ You can explore the structure of this object by using standard object paths or b
}
}
}
}
}


The reader can transform ``EpochPosition`` instances into ``SkyCoord`` instances.
These can then be used for further scientific processing.

.. code-block:: python
.. code-block:: python

while m_viewer.next_row_view():
if mango_property.dmtype == "mango:EpochPosition":
scb = SkyCoordBuilder(mango_property.to_dict())
# do whatever process with the SkyCoord object
print(scb.build_sky_coord())
while m_viewer.next_row_view():
if mango_property.dmtype == "mango:EpochPosition":
scb = SkyCoordBuilder(mango_property.to_dict())
# do whatever process with the SkyCoord object
print(scb.build_sky_coord())

.. important::
Similar to the previous example, this code can be used with any VOTable with data mapped to MANGO.
Expand All @@ -255,5 +259,29 @@ You can explore the structure of this object by using standard object paths or b
It avoids the need for users to build SkyCoord objects by hand from VOTable fields,
which is never an easy task.

Homework
========

Simbad has released (Q3 2025) an annotated version of its Cone Search.
It's a good case to exercise this API.


.. code-block:: python

SERVER = "https://simbad.cds.unistra.fr/cone?"
VERB = 2
RA = 269.452076* u.degree
DEC = 4.6933649* u.degree
SR = 0.1* u.degree
MAXREC = 100

scs_srv = SCSService(SERVER)

query_result = scs_srv.search(
pos=SkyCoord(ra=RA, dec=DEC, frame='icrs'), radius=SR
verbosity=VERB,
RESPONSEFORMAT="mivot",
MAXREC=MAXREC)


The next section provides some tips to use the API documented in the :ref:`annoter page <mivot-annoter>`.
*The next section provides some tips to use the API documented in the* :ref:`annoter page <mivot-annoter>`.
Loading
Loading