a2p2 0.2.14__py3-none-any.whl → 0.7.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- a2p2/__main__.py +40 -1
- a2p2/chara/facility.py +54 -4
- a2p2/chara/gui.py +31 -5
- a2p2/client.py +158 -31
- a2p2/facility.py +14 -1
- a2p2/gui.py +46 -12
- a2p2/instrument.py +3 -0
- a2p2/jmmc/__init__.py +7 -0
- a2p2/jmmc/catalogs.py +129 -0
- a2p2/jmmc/generated_models.py +191 -0
- a2p2/jmmc/models.py +104 -0
- a2p2/jmmc/services.py +16 -0
- a2p2/jmmc/utils.py +130 -0
- a2p2/jmmc/webservices.py +48 -0
- a2p2/ob.py +98 -9
- a2p2/samp.py +20 -0
- a2p2/version.py +210 -131
- a2p2/vlti/conf/GRAVITY_ditTable.json +21 -19
- a2p2/vlti/conf/GRAVITY_rangeTable.json +200 -28
- a2p2/vlti/conf/MATISSE_rangeTable.json +58 -22
- a2p2/vlti/conf/PIONIER_ditTable.json +1 -1
- a2p2/vlti/conf/PIONIER_rangeTable.json +16 -18
- a2p2/vlti/facility.py +160 -43
- a2p2/vlti/gravity.py +243 -311
- a2p2/vlti/gui.py +165 -39
- a2p2/vlti/instrument.py +266 -49
- a2p2/vlti/matisse.py +61 -147
- a2p2/vlti/pionier.py +34 -157
- {a2p2-0.2.14.dist-info → a2p2-0.7.4.dist-info}/METADATA +34 -20
- a2p2-0.7.4.dist-info/RECORD +39 -0
- {a2p2-0.2.14.dist-info → a2p2-0.7.4.dist-info}/WHEEL +1 -1
- {a2p2-0.2.14.dist-info → a2p2-0.7.4.dist-info}/entry_points.txt +0 -1
- a2p2/vlti/confP104/GRAVITY_ditTable.json +0 -122
- a2p2/vlti/confP104/GRAVITY_rangeTable.json +0 -202
- a2p2/vlti/confP104/MATISSE_ditTable.json +0 -2
- a2p2/vlti/confP104/MATISSE_rangeTable.json +0 -202
- a2p2/vlti/confP104/PIONIER_ditTable.json +0 -77
- a2p2/vlti/confP104/PIONIER_rangeTable.json +0 -118
- a2p2/vlti/confP105/GRAVITY_ditTable.json +0 -37
- a2p2/vlti/confP105/GRAVITY_rangeTable.json +0 -42
- a2p2/vlti/confP105/MATISSE_ditTable.json +0 -2
- a2p2/vlti/confP105/MATISSE_rangeTable.json +0 -44
- a2p2/vlti/confP105/PIONIER_ditTable.json +0 -25
- a2p2/vlti/confP105/PIONIER_rangeTable.json +0 -38
- a2p2-0.2.14.dist-info/RECORD +0 -44
- {a2p2-0.2.14.dist-info → a2p2-0.7.4.dist-info}/LICENSE +0 -0
- {a2p2-0.2.14.dist-info → a2p2-0.7.4.dist-info}/top_level.txt +0 -0
a2p2/gui.py
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
__all__ = []
|
4
4
|
|
5
5
|
import sys
|
6
|
+
import logging
|
6
7
|
|
7
8
|
from a2p2 import __release_notes__
|
8
9
|
from a2p2 import __version__
|
@@ -18,10 +19,13 @@ else:
|
|
18
19
|
|
19
20
|
from distutils.version import LooseVersion
|
20
21
|
|
22
|
+
import signal
|
23
|
+
|
21
24
|
HELPTEXT = """This application provides the link between ASPRO (that you should have started) and interferometers facilities.
|
22
25
|
|
23
26
|
"""
|
24
27
|
|
28
|
+
logger = logging.getLogger(__name__)
|
25
29
|
|
26
30
|
class MainWindow():
|
27
31
|
|
@@ -42,6 +46,8 @@ class MainWindow():
|
|
42
46
|
print("Can not set tk scaling !")
|
43
47
|
|
44
48
|
self.window.protocol("WM_DELETE_WINDOW", self._requestAbort)
|
49
|
+
# throws an exception self.window.bind_all('<Control-c>', self._requestAbort) # maybe already bound with WM_DELETE_WINDOW ?
|
50
|
+
signal.signal(signal.SIGINT, self._requestAbortSignal)
|
45
51
|
|
46
52
|
self.notebook = ttk.Notebook(self.window)
|
47
53
|
|
@@ -94,6 +100,10 @@ class MainWindow():
|
|
94
100
|
def _requestAbort(self):
|
95
101
|
self.requestAbort = True
|
96
102
|
|
103
|
+
def _requestAbortSignal(self, arg1,arg2):
|
104
|
+
self.requestAbort = True
|
105
|
+
|
106
|
+
|
97
107
|
def addHelp(self, tabname, txt):
|
98
108
|
frame = Frame(self.helptabs)
|
99
109
|
widget = Text(frame, width=120)
|
@@ -168,12 +178,17 @@ class MainWindow():
|
|
168
178
|
def get_api(self):
|
169
179
|
return self.api
|
170
180
|
|
171
|
-
def addToLog(self, text, displayString=True):
|
181
|
+
def addToLog(self, text, displayString=True, level=logging.INFO):
|
172
182
|
if displayString:
|
173
183
|
self.log_string.set(str(text))
|
174
184
|
self.logtext.insert(END, "\n" + str(text))
|
175
185
|
self.logtext.see(END)
|
176
186
|
self.showFrameToFront()
|
187
|
+
self.refresh()
|
188
|
+
logger.log(level,text)
|
189
|
+
|
190
|
+
def refresh(self):
|
191
|
+
self.window.update()
|
177
192
|
|
178
193
|
def ShowErrorMessage(self, text):
|
179
194
|
if self.a2p2client.fakeAPI:
|
@@ -181,7 +196,7 @@ class MainWindow():
|
|
181
196
|
else:
|
182
197
|
showerror("Error", text)
|
183
198
|
self.addToLog("Error message")
|
184
|
-
self.addToLog(text, False)
|
199
|
+
self.addToLog(text, False, logging.ERROR)
|
185
200
|
|
186
201
|
# do also append to simple error stack (for testing purpose first)
|
187
202
|
self.a2p2client.addError(text)
|
@@ -192,7 +207,7 @@ class MainWindow():
|
|
192
207
|
else:
|
193
208
|
showwarning("Warning", text)
|
194
209
|
self.addToLog("Warning message")
|
195
|
-
self.addToLog(text, False)
|
210
|
+
self.addToLog(text, False, logging.WARNING)
|
196
211
|
|
197
212
|
def ShowInfoMessage(self, text):
|
198
213
|
if self.a2p2client.fakeAPI:
|
@@ -200,7 +215,20 @@ class MainWindow():
|
|
200
215
|
else:
|
201
216
|
showinfo("Info", text)
|
202
217
|
self.addToLog("Info message")
|
203
|
-
self.addToLog(text, False)
|
218
|
+
self.addToLog(text, False, logging.INFO)
|
219
|
+
|
220
|
+
def AskYesNoMessage(self, text):
|
221
|
+
if self.a2p2client.fakeAPI:
|
222
|
+
pass
|
223
|
+
else:
|
224
|
+
self.addToLog(f"Question: {text}", displayString=False)
|
225
|
+
ret = askyesno("", text)
|
226
|
+
self.addToLog(f"Reply: yes={ret}", displayString=False)
|
227
|
+
return ret
|
228
|
+
|
229
|
+
def addProgress(self, offset=0.1):
|
230
|
+
self.setProgress(self.progress_value.get()+offset)
|
231
|
+
|
204
232
|
|
205
233
|
def setProgress(self, perc):
|
206
234
|
if perc > 1:
|
@@ -252,18 +280,24 @@ class FacilityUI(Frame):
|
|
252
280
|
self.facility = facility
|
253
281
|
self.a2p2client = facility.a2p2client
|
254
282
|
|
255
|
-
def addToLog(self,
|
283
|
+
def addToLog(self, *args, **kwargs):
|
256
284
|
""" Wrapper to log message in the common textfield """
|
257
|
-
self.a2p2client.ui.addToLog(
|
285
|
+
self.a2p2client.ui.addToLog(*args, **kwargs)
|
258
286
|
|
259
|
-
def ShowErrorMessage(self,
|
260
|
-
self.a2p2client.ui.ShowErrorMessage(
|
287
|
+
def ShowErrorMessage(self, *args, **kwargs):
|
288
|
+
self.a2p2client.ui.ShowErrorMessage(*args, **kwargs)
|
261
289
|
|
262
|
-
def ShowWarningMessage(self,
|
263
|
-
self.a2p2client.ui.ShowWarningMessage(
|
290
|
+
def ShowWarningMessage(self, *args, **kwargs):
|
291
|
+
self.a2p2client.ui.ShowWarningMessage(*args, **kwargs)
|
264
292
|
|
265
|
-
def ShowInfoMessage(self,
|
266
|
-
self.a2p2client.ui.ShowInfoMessage(
|
293
|
+
def ShowInfoMessage(self, *args, **kwargs):
|
294
|
+
self.a2p2client.ui.ShowInfoMessage(*args, **kwargs)
|
295
|
+
|
296
|
+
def AskYesNoMessage(self,*args, **kwargs):
|
297
|
+
return self.a2p2client.ui.AskYesNoMessage(*args, **kwargs)
|
298
|
+
|
299
|
+
def addProgress(self, offset=0.1):
|
300
|
+
self.a2p2client.ui.addProgress(offset)
|
267
301
|
|
268
302
|
def setProgress(self, perc):
|
269
303
|
""" Wrapper to update progress bar """
|
a2p2/instrument.py
CHANGED
a2p2/jmmc/__init__.py
ADDED
a2p2/jmmc/catalogs.py
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
__all__ = []
|
4
|
+
|
5
|
+
import logging
|
6
|
+
|
7
|
+
from ..client import A2P2ClientPreferences
|
8
|
+
|
9
|
+
from .utils import JmmcAPI
|
10
|
+
from . import PRODLABEL
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class Catalog():
|
16
|
+
""" Get remote access to read and update catalogs exposed through JMMC's API.
|
17
|
+
Credential can be explicitly given for method that require an authentication, else:
|
18
|
+
- a2p2 preferences login will be used if present (see a2p2 -c)
|
19
|
+
- or uses .netrc file
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, catalogName, username=None, password=None, prod=False, apiUrl=None):
|
23
|
+
self.catalogName = catalogName
|
24
|
+
self.prod = prod
|
25
|
+
|
26
|
+
# Manage prod & preprod or user provided access points
|
27
|
+
if apiUrl:
|
28
|
+
self.apiUrl = apiUrl # trust given url as catalogAPI if value is provided
|
29
|
+
elif self.prod:
|
30
|
+
self.apiUrl = "https://oidb.jmmc.fr/restxq/catalogs"
|
31
|
+
else:
|
32
|
+
self.apiUrl = "https://oidb-beta.jmmc.fr/restxq/catalogs"
|
33
|
+
|
34
|
+
self.api = JmmcAPI(self.apiUrl, username, password)
|
35
|
+
|
36
|
+
logger.info(f"Create catalog wrapper to access '{catalogName}' ({PRODLABEL[self.prod]} API at {self.api.rootURL})")
|
37
|
+
|
38
|
+
def list(self):
|
39
|
+
""" Get list of exposed catalogs on API associated to this catalog. """
|
40
|
+
return self.api._get("")
|
41
|
+
|
42
|
+
def metadata(self):
|
43
|
+
""" Get catalog metadata """
|
44
|
+
return self.api._get(f"/meta/{self.catalogName}")
|
45
|
+
|
46
|
+
def pis(self):
|
47
|
+
""" Get PIs from catalog and check for associated JMMC login in OiDB datapi table."""
|
48
|
+
return self.api._get(f"/accounts/{self.catalogName}")["pi"]
|
49
|
+
|
50
|
+
def piname(self, jmmcLogin=None):
|
51
|
+
""" Get the piname associated to the given jmmcLogin.
|
52
|
+
If jmmcLogin parameter is not provided, try to get jmmc.login preferences.
|
53
|
+
Only pi names returned by pis() can be retrieved.
|
54
|
+
|
55
|
+
usage: piname()
|
56
|
+
"""
|
57
|
+
if not jmmcLogin:
|
58
|
+
prefs = A2P2ClientPreferences()
|
59
|
+
jmmcLogin = prefs.getJmmcLogin()
|
60
|
+
if not jmmcLogin:
|
61
|
+
raise Exception(
|
62
|
+
"missing login parameter or jmmc.login preference.")
|
63
|
+
pis = self.pis()
|
64
|
+
for pi in pis:
|
65
|
+
if "login" in pi and pi["login"] == jmmcLogin:
|
66
|
+
return pi["name"]
|
67
|
+
|
68
|
+
def getRow(self, id):
|
69
|
+
""" Get a single catalog record for the given id.
|
70
|
+
|
71
|
+
usage: cat.getRow(42)
|
72
|
+
"""
|
73
|
+
return self.api._get(f"/{self.catalogName}/{id}")
|
74
|
+
|
75
|
+
def updateRow(self, id, values):
|
76
|
+
""" Update record identified by given id and associated values.
|
77
|
+
|
78
|
+
usage: cat.updateRows(42, {"col_a":"a", "col_b":"b" })
|
79
|
+
"""
|
80
|
+
return self.api._put(f"/{self.catalogName}/{id}", values)
|
81
|
+
|
82
|
+
def updateRows(self, values):
|
83
|
+
""" Update multiple rows.
|
84
|
+
Values must contain a list of dictionnary and each entry must contains id key among other columns.
|
85
|
+
|
86
|
+
usage: updateRows([ { "id":42, "col_a":"a" }, { "id":24, "col_b":"b" } ])
|
87
|
+
"""
|
88
|
+
|
89
|
+
# We may check befere sending payload that we always provide an id for every record
|
90
|
+
return self.api._put(f"/{self.catalogName}", values)
|
91
|
+
|
92
|
+
def addRows(self, values):
|
93
|
+
""" Add multiple rows.
|
94
|
+
Values is an array of row to add. id column values will be ignored.
|
95
|
+
|
96
|
+
usage: addCatalogRows([ { "id":42, "col_a":"a" }, { "id":24, "col_b":"b" } ])
|
97
|
+
"""
|
98
|
+
return self.api._post(f"/{self.catalogName}", json=values)
|
99
|
+
|
100
|
+
def getDelegations(self, pi=None):
|
101
|
+
""" Get -all- delegations.
|
102
|
+
A specific pi parameter may be given but requires admin priviledges. Else use current piname will be used by remote service.
|
103
|
+
|
104
|
+
usage: getDelegations()
|
105
|
+
"""
|
106
|
+
res = self.api._get(f"/delegations?pi={pi}")
|
107
|
+
if "delegations" in res:
|
108
|
+
return res["delegations"] # login and pi values not returned
|
109
|
+
|
110
|
+
return []
|
111
|
+
|
112
|
+
def updateDelegations(self, delegations):
|
113
|
+
""" Update delegations according given array of delegations.
|
114
|
+
A specific "pi" key may be given in delegations dict but requires admin privileges. Else current piname will be used by remote service.
|
115
|
+
"action" may be provided with "add" or "remove" value to set proper delegations (default add given delegation).
|
116
|
+
|
117
|
+
usage: addDelegations([{"pi":"mypiname", "catalogs":"mycats", "coi":["alice","bob"]}])
|
118
|
+
|
119
|
+
"""
|
120
|
+
return self.api._put("/delegations", json=delegations)
|
121
|
+
|
122
|
+
def removeDelegations(self, delegations):
|
123
|
+
""" Remove delegations according given array of delegations.
|
124
|
+
A specific pi key may be given but requires admin privileges. Else current piname will be used by remote service.
|
125
|
+
usage: removeDelegations([{}, {}])
|
126
|
+
"""
|
127
|
+
return "TODO"
|
128
|
+
|
129
|
+
|
@@ -0,0 +1,191 @@
|
|
1
|
+
from .models import _model
|
2
|
+
|
3
|
+
def punct( name, flux_weight=1.0, x=0.0, y=0.0, output_mode=None):
|
4
|
+
""" Returns the Fourier transform of a punctual object (Dirac function) at coordinates (X,Y)
|
5
|
+
(milliarcsecond).
|
6
|
+
|
7
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1. """
|
8
|
+
|
9
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "name":name, "type":"punct"}, output_mode)
|
10
|
+
|
11
|
+
def disk( name, flux_weight=1.0, x=0.0, y=0.0, diameter=0.0, output_mode=None):
|
12
|
+
""" Returns the Fourier transform of a normalized uniform disk of diameter DIAMETER
|
13
|
+
(milliarcsecond) and centered at coordinates (X,Y) (milliarcsecond).
|
14
|
+
|
15
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1.
|
16
|
+
|
17
|
+
The function returns an error if DIAMETER is negative. """
|
18
|
+
|
19
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "diameter" : diameter, "name":name, "type":"disk"}, output_mode)
|
20
|
+
|
21
|
+
def elong_disk( name, flux_weight=1.0, x=0.0, y=0.0, minor_axis_diameter=0.0, elong_ratio=1.0, major_axis_pos_angle=0.0, output_mode=None):
|
22
|
+
""" Returns the Fourier transform of a normalized ellipse centered at coordinates (X,Y)
|
23
|
+
(milliarcsecond) with a ratio ELONG_RATIO between the major diameter and the minor one
|
24
|
+
MINOR_AXIS_DIAMETER, turned from the positive vertical semi-axis (i.e. North direction)
|
25
|
+
with angle MAJOR_AXIS_POS_ANGLE, in degrees, towards to the positive horizontal semi-axis
|
26
|
+
(i.e. East direction). (the elongation is along the major_axis)
|
27
|
+
|
28
|
+
For avoiding degenerescence, the domain of variation of MAJOR_AXIS_POS_ANGLE is 180
|
29
|
+
degrees, for ex. from 0 to 180 degrees.
|
30
|
+
|
31
|
+
ELONG_RATIO = major_axis / minor_axis
|
32
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1.
|
33
|
+
|
34
|
+
The function returns an error if MINOR_AXIS_DIAMETER is negative or if ELONG_RATIO is
|
35
|
+
smaller than 1. """
|
36
|
+
|
37
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "minor_axis_diameter" : minor_axis_diameter, "elong_ratio" : elong_ratio, "major_axis_pos_angle" : major_axis_pos_angle, "name":name, "type":"elong_disk"}, output_mode)
|
38
|
+
|
39
|
+
def flatten_disk( name, flux_weight=1.0, x=0.0, y=0.0, major_axis_diameter=0.0, flatten_ratio=1.0, minor_axis_pos_angle=0.0, output_mode=None):
|
40
|
+
""" Returns the Fourier transform of a normalized ellipse centered at coordinates (X,Y)
|
41
|
+
(milliarcsecond) with a ratio FLATTEN_RATIO between the major diameter
|
42
|
+
MAJOR_AXIS_DIAMETER and the minor one, turned from the positive vertical semi-axis
|
43
|
+
(i.e. North direction) with angle MINOR_AXIS_POS_ANGLE, in degrees, towards to the
|
44
|
+
positive horizontal semi-axis (i.e. East direction). (the flattening is along the minor_axis)
|
45
|
+
|
46
|
+
For avoiding degenerescence, the domain of variation of MINOR_AXIS_POS_ANGLE is 180
|
47
|
+
degrees, for ex. from 0 to 180 degrees.
|
48
|
+
|
49
|
+
FLATTEN_RATIO = major_axis / minor_axis
|
50
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1.
|
51
|
+
|
52
|
+
The function returns an error if MAJOR_AXIS_DIAMETER is negative or if FLATTEN_RATIO
|
53
|
+
is smaller than 1. """
|
54
|
+
|
55
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "major_axis_diameter" : major_axis_diameter, "flatten_ratio" : flatten_ratio, "minor_axis_pos_angle" : minor_axis_pos_angle, "name":name, "type":"flatten_disk"}, output_mode)
|
56
|
+
|
57
|
+
def circle( name, flux_weight=1.0, x=0.0, y=0.0, diameter=0.0, output_mode=None):
|
58
|
+
""" Returns the Fourier transform of a normalized uniform circle of diameter DIAMETER
|
59
|
+
(milliarcsecond) and centered at coordinates (X,Y) (milliarcsecond).
|
60
|
+
|
61
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1.
|
62
|
+
|
63
|
+
The function returns an error if DIAMETER is negative. """
|
64
|
+
|
65
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "diameter" : diameter, "name":name, "type":"circle"}, output_mode)
|
66
|
+
|
67
|
+
def ring( name, flux_weight=1.0, x=0.0, y=0.0, diameter=0.0, width=0.0, output_mode=None):
|
68
|
+
""" Returns the Fourier transform of a normalized uniform ring with internal diameter
|
69
|
+
DIAMETER (milliarcsecond) and external diameter DIAMETER + WIDTH centered at coordinates
|
70
|
+
(X,Y) (milliarcsecond).
|
71
|
+
|
72
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1.
|
73
|
+
|
74
|
+
The function returns an error if DIAMETER or WIDTH are negative. """
|
75
|
+
|
76
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "diameter" : diameter, "width" : width, "name":name, "type":"ring"}, output_mode)
|
77
|
+
|
78
|
+
def elong_ring( name, flux_weight=1.0, x=0.0, y=0.0, minor_internal_diameter=0.0, elong_ratio=1.0, width=0.0, major_axis_pos_angle=0.0, output_mode=None):
|
79
|
+
""" Returns the Fourier transform of a normalized uniform elongated ring centered at
|
80
|
+
coordinates (X,Y) (milliarcsecond). The sizes of the function in two orthogonal directions
|
81
|
+
are given by the narrowest internal diameter (MINOR_INTERNAL_DIAMETER) and by the ratio
|
82
|
+
ELONG_RATIO between the widest internal diameter and MINOR_INTERNAL_DIAMETER,
|
83
|
+
in the same way as for an ellipse (the elongation is along the major_axis) :
|
84
|
+
|
85
|
+
ELONG_RATIO = MAJOR_INTERNAL_DIAMETER / MINOR_INTERNAL_DIAMETER.
|
86
|
+
In the direction of MINOR_INTERNAL_DIAMETER, the external diameter is
|
87
|
+
MINOR_INTERNAL_DIAMETER + WIDTH. In the direction of the widest internal diameter,
|
88
|
+
the width is magnified by the ratio ELONG_RATIO, so that the external diameter is
|
89
|
+
the elongated MAJOR_INTERNAL_DIAMETER + WIDTH * ELONG_RATIO.
|
90
|
+
MAJOR_AXIS_POS_ANGLE is measured in degrees, from the positive vertical semi-axis
|
91
|
+
(i.e. North direction) towards to the positive horizontal semi-axis (i.e. East direction).
|
92
|
+
For avoiding degenerescence, the domain of variation of MAJOR_AXIS_POS_ANGLE is 180
|
93
|
+
degrees, for ex. from 0 to 180 degrees.
|
94
|
+
|
95
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1.
|
96
|
+
|
97
|
+
The function returns an error if MINOR_INTERNAL_DIAMETER is negative or if ELONG_RATIO
|
98
|
+
is smaller than 1. """
|
99
|
+
|
100
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "minor_internal_diameter" : minor_internal_diameter, "elong_ratio" : elong_ratio, "width" : width, "major_axis_pos_angle" : major_axis_pos_angle, "name":name, "type":"elong_ring"}, output_mode)
|
101
|
+
|
102
|
+
def flatten_ring( name, flux_weight=1.0, x=0.0, y=0.0, major_internal_diameter=0.0, flatten_ratio=1.0, width=0.0, minor_axis_pos_angle=0.0, output_mode=None):
|
103
|
+
""" Returns the Fourier transform of a normalized uniform flattened ring centered at
|
104
|
+
coordinates (X,Y) (milliarcsecond). The sizes of the function in two orthogonal directions
|
105
|
+
are given by the widest internal diameter (MAJOR_INTERNAL_DIAMETER) and by the ratio
|
106
|
+
FLATTEN_RATIO between MAJOR_INTERNAL_DIAMETER and the narrowest internal diameter,
|
107
|
+
in the same way as for an ellipse (the flattening is along the minor axis) :
|
108
|
+
|
109
|
+
FLATTEN_RATIO = MAJOR_INTERNAL_DIAMETER / MINOR_INTERNAL_DIAMETER.
|
110
|
+
In the direction of MAJOR_INTERNAL_DIAMETER, the external diameter is
|
111
|
+
MAJOR_INTERNAL_DIAMETER + WIDTH. In the direction of the narrowest internal diameter,
|
112
|
+
the width is decreased by the ratio FLATTEN_RATIO, so that the external diameter is
|
113
|
+
the flattened MINOR_INTERNAL_DIAMETER + WIDTH / FLATTEN_RATIO.
|
114
|
+
MINOR_AXIS_POS_ANGLE is measured in degrees, from the positive vertical semi-axis
|
115
|
+
(i.e. North direction) towards to the positive horizontal semi-axis (i.e. East direction).
|
116
|
+
For avoiding degenerescence, the domain of variation of MINOR_AXIS_POS_ANGLE is 180
|
117
|
+
degrees, for ex. from 0 to 180 degrees.
|
118
|
+
|
119
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1.
|
120
|
+
|
121
|
+
The function returns an error if MAJOR_INTERNAL_DIAMETER is negative or if FLATTEN_RATIO
|
122
|
+
is smaller than 1. """
|
123
|
+
|
124
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "major_internal_diameter" : major_internal_diameter, "flatten_ratio" : flatten_ratio, "width" : width, "minor_axis_pos_angle" : minor_axis_pos_angle, "name":name, "type":"flatten_ring"}, output_mode)
|
125
|
+
|
126
|
+
def gaussian( name, flux_weight=1.0, x=0.0, y=0.0, fwhm=0.0, output_mode=None):
|
127
|
+
""" Returns the Fourier transform of a normalized gaussian with given FWHM (milliarcsecond)
|
128
|
+
centered at coordinates (X,Y) (milliarcsecond).
|
129
|
+
|
130
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1.
|
131
|
+
|
132
|
+
The function returns an error if FWHM is negative. """
|
133
|
+
|
134
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "fwhm" : fwhm, "name":name, "type":"gaussian"}, output_mode)
|
135
|
+
|
136
|
+
def elong_gaussian( name, flux_weight=1.0, x=0.0, y=0.0, minor_axis_fwhm=0.0, elong_ratio=1.0, major_axis_pos_angle=0.0, output_mode=None):
|
137
|
+
""" Returns the Fourier transform of a normalized elongated gaussian centered at coordinates
|
138
|
+
(X,Y) (milliarcsecond). The sizes of the function in two orthogonal directions are given by
|
139
|
+
the narrowest FWHM (MINOR_AXIS_FWHM) and by the ratio ELONG_RATIO between the largest
|
140
|
+
FWHM (MAJOR_AXIS_FWHM) and the MINOR_AXIS_FWHM, in the same way as for an ellipse
|
141
|
+
(the elongation is along the major_axis) :
|
142
|
+
|
143
|
+
ELONG_RATIO = MAJOR_AXIS_FWHM / MINOR_AXIS_FWHM.
|
144
|
+
MAJOR_AXIS_POS_ANGLE is measured in degrees, from the positive vertical semi-axis
|
145
|
+
(i.e. North direction) towards to the positive horizontal semi-axis (i.e. East direction).
|
146
|
+
For avoiding degenerescence, the domain of variation of MAJOR_AXIS_POS_ANGLE is 180
|
147
|
+
degrees, for ex. from 0 to 180 degrees.
|
148
|
+
|
149
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1.
|
150
|
+
|
151
|
+
The function returns an error if MINOR_AXIS_FWHM is negative or if ELONG_RATIO
|
152
|
+
is smaller than 1. """
|
153
|
+
|
154
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "minor_axis_fwhm" : minor_axis_fwhm, "elong_ratio" : elong_ratio, "major_axis_pos_angle" : major_axis_pos_angle, "name":name, "type":"elong_gaussian"}, output_mode)
|
155
|
+
|
156
|
+
def flatten_gaussian( name, flux_weight=1.0, x=0.0, y=0.0, major_axis_fwhm=0.0, flatten_ratio=1.0, minor_axis_pos_angle=0.0, output_mode=None):
|
157
|
+
""" Returns the Fourier transform of a normalized flattened gaussian centered at coordinates
|
158
|
+
(X,Y) (milliarcsecond). The sizes of the function in two orthogonal directions are given by
|
159
|
+
the largest FWHM (MAJOR_AXIS_FWHM) and by the ratio FLATTEN_RATIO between the largest
|
160
|
+
FWHM (MAJOR_AXIS_FWHM) and the MINOR_AXIS_FWHM, in the same way as for an ellipse
|
161
|
+
(the flattening is along the minor_axis) :
|
162
|
+
|
163
|
+
FLATTEN_RATIO = MAJOR_AXIS_FWHM / MINOR_AXIS_FWHM.
|
164
|
+
MINOR_AXIS_POS_ANGLE is measured in degrees, from the positive vertical semi-axis
|
165
|
+
(i.e. North direction) towards to the positive horizontal semi-axis (i.e. East direction).
|
166
|
+
For avoiding degenerescence, the domain of variation of MINOR_AXIS_POS_ANGLE is 180
|
167
|
+
degrees, for ex. from 0 to 180 degrees.
|
168
|
+
|
169
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1.
|
170
|
+
|
171
|
+
The function returns an error if MAJOR_AXIS_FWHM is negative or if FLATTEN_RATIO
|
172
|
+
is smaller than 1. """
|
173
|
+
|
174
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "major_axis_fwhm" : major_axis_fwhm, "flatten_ratio" : flatten_ratio, "minor_axis_pos_angle" : minor_axis_pos_angle, "name":name, "type":"flatten_gaussian"}, output_mode)
|
175
|
+
|
176
|
+
def limb_quadratic( name, flux_weight=1.0, x=0.0, y=0.0, diameter=0.0, a1_coeff=0.0, a2_coeff=0.0, output_mode=None):
|
177
|
+
""" Returns the Fourier transform of a center-to-limb darkened disk of diameter DIAMETER
|
178
|
+
(milliarcsecond) centered at coordinates (X,Y) (milliarcsecond).
|
179
|
+
|
180
|
+
The brightness distribution o, if expressed versus mu, the cosine of the azimuth of
|
181
|
+
a surface element of the star, follows a quadratic law of coefficients
|
182
|
+
A1_COEFF, A2_COEFF ([-1,1]), and is normalized for mu = 1 (center of the star).
|
183
|
+
o(mu) = 1 -A1_COEFF(1-mu) - A2_COEFF(1-mu)^2.
|
184
|
+
|
185
|
+
FLUX_WEIGHT is the intensity coefficient. FLUX_WEIGHT=1 means total energy is 1.
|
186
|
+
|
187
|
+
The function returns an error if DIAMETER is negative or if A1_COEFF or A2_coeff is
|
188
|
+
outside bounds [-1,1] """
|
189
|
+
|
190
|
+
return _model({"flux_weight" : flux_weight, "x" : x, "y" : y, "diameter" : diameter, "a1_coeff" : a1_coeff, "a2_coeff" : a2_coeff, "name":name, "type":"limb_quadratic"}, output_mode)
|
191
|
+
|
a2p2/jmmc/models.py
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
__all__ = []
|
4
|
+
|
5
|
+
import json
|
6
|
+
import logging
|
7
|
+
import xml.etree.ElementTree as ET
|
8
|
+
import json
|
9
|
+
|
10
|
+
from .utils import JmmcAPI
|
11
|
+
from a2p2.ob import etree_to_dict
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
#
|
17
|
+
# first release done for SAMP interoperability with Aspro2
|
18
|
+
#
|
19
|
+
|
20
|
+
def modelsFromXml(xmlmodel):
|
21
|
+
""" Returns a python object representing JSON LIKE model(s) from given XML."""
|
22
|
+
# read xml models from Aspro2 and convert them to a JSON Model
|
23
|
+
e = ET.fromstring(xmlmodel)
|
24
|
+
_remove_namespace(e, "http://www.jmmc.fr/jmcs/models/0.1")
|
25
|
+
container = etree_to_dict(e)
|
26
|
+
modellist=container['model']['model']
|
27
|
+
if not isinstance(modellist, list):
|
28
|
+
modellist=[modellist]
|
29
|
+
|
30
|
+
models=[]
|
31
|
+
for m in modellist:
|
32
|
+
model={}
|
33
|
+
for p in m['parameter']:
|
34
|
+
model[p['type']]=p['value']
|
35
|
+
model['name']=m['name']
|
36
|
+
model['type']=m['type']
|
37
|
+
models.append(model)
|
38
|
+
|
39
|
+
return json.dumps(models)
|
40
|
+
|
41
|
+
|
42
|
+
def _remove_namespace(doc, namespace):
|
43
|
+
"""Remove namespace in the passed document in place.
|
44
|
+
from https://homework.nwsnet.de/releases/45be/"""
|
45
|
+
ns = u'{%s}' % namespace
|
46
|
+
nsl = len(ns)
|
47
|
+
for elem in doc.getiterator():
|
48
|
+
if elem.tag.startswith(ns):
|
49
|
+
elem.tag = elem.tag[nsl:]
|
50
|
+
|
51
|
+
|
52
|
+
def _model(models, output_mode=None):
|
53
|
+
""" Returns a serialisation of given model(s) to XML by default or another format given to output_mode value (only jsonlike at present time)."""
|
54
|
+
|
55
|
+
# deserialize if models is a string
|
56
|
+
if isinstance(models, str):
|
57
|
+
models=json.loads(models)
|
58
|
+
|
59
|
+
if output_mode:
|
60
|
+
return models
|
61
|
+
else :
|
62
|
+
return _xml_model(models)
|
63
|
+
|
64
|
+
def _xml_model(models, position=1):
|
65
|
+
""" Rough conversion of dict(s) to xml (Aspro2 namespaces)
|
66
|
+
if name key is not provided, position value will be used to compute suffix(es)"""
|
67
|
+
|
68
|
+
if isinstance(models, list):
|
69
|
+
modellist=[]
|
70
|
+
pos=1
|
71
|
+
for m in models:
|
72
|
+
modellist.append(_xml_model(m, pos))
|
73
|
+
pos+=1 # do not use models.index(m) since same str components would get the same index
|
74
|
+
return "".join(modellist)
|
75
|
+
|
76
|
+
model = models # models is now a single element
|
77
|
+
modeltype=model["type"]
|
78
|
+
if "name" in model.keys():
|
79
|
+
modelname=model["name"]
|
80
|
+
else:
|
81
|
+
modelname=modeltype+str(position)
|
82
|
+
|
83
|
+
|
84
|
+
params=""
|
85
|
+
paramNames=[ k for k in model.keys() if not k in ("type", "name")]
|
86
|
+
# at present time we must use Aspro2's approach with the same xml namespaces
|
87
|
+
# see
|
88
|
+
for p in paramNames:
|
89
|
+
params+=f""" <tm:parameter name="{modelname}_{p}" type="{p}"><value>{model[p]}</value></tm:parameter>\n"""
|
90
|
+
|
91
|
+
return f"""<tm:model name="{modelname}" type="{modeltype}">\n{params}</tm:model>\n"""
|
92
|
+
|
93
|
+
|
94
|
+
class Models():
|
95
|
+
""" Get analytical model's representations (in sync with Aspro2's ones).
|
96
|
+
"""
|
97
|
+
SAMP_UCD_MODEL="meta.code.class;meta.modelled" # use it for colums that would be filled by models below
|
98
|
+
|
99
|
+
# generated code below from an Asprox file that contains a single star with list of all supported models
|
100
|
+
# cd a2p2/jmmc
|
101
|
+
# xml sel -o "from .models import _model" -n -n -b -t -m "//tm:model" -o "def " -v "@type" -o "( name, " -m "tm:parameter" -v "@type" -o "=" -v "value" -o ", " -b -o " output_mode=None):" -n -o ' """ ' -v "desc" -o ' """ ' -n -n -o ' return _model({' -m "tm:parameter" -o '"' -v "@type" -o '" : ' -v "@type" -o ", " -b -o '"name":name, "type":"' -v "@type" -o '"}, output_mode)' -n -n models_template.asprox > generated_models.py
|
102
|
+
# xml sel -t -o " from .generated_models import " -m "//tm:model" -v "@type" -o ", " -b -n models_template.asprox
|
103
|
+
# cd -
|
104
|
+
from .generated_models import punct, disk, elong_disk, flatten_disk, circle, ring, elong_ring, flatten_ring, gaussian, elong_gaussian, flatten_gaussian, limb_quadratic
|
a2p2/jmmc/services.py
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
__all__ = []
|
4
|
+
|
5
|
+
import logging
|
6
|
+
|
7
|
+
from .catalogs import Catalog
|
8
|
+
from .webservices import CallIper
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
# TODO list and give access to whole services and their beta/alpha / pre-prod instances if existing
|
13
|
+
|
14
|
+
oidb_catalog = Catalog("oidb")
|
15
|
+
|
16
|
+
calliper = CallIper()
|