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.
Files changed (47) hide show
  1. a2p2/__main__.py +40 -1
  2. a2p2/chara/facility.py +54 -4
  3. a2p2/chara/gui.py +31 -5
  4. a2p2/client.py +158 -31
  5. a2p2/facility.py +14 -1
  6. a2p2/gui.py +46 -12
  7. a2p2/instrument.py +3 -0
  8. a2p2/jmmc/__init__.py +7 -0
  9. a2p2/jmmc/catalogs.py +129 -0
  10. a2p2/jmmc/generated_models.py +191 -0
  11. a2p2/jmmc/models.py +104 -0
  12. a2p2/jmmc/services.py +16 -0
  13. a2p2/jmmc/utils.py +130 -0
  14. a2p2/jmmc/webservices.py +48 -0
  15. a2p2/ob.py +98 -9
  16. a2p2/samp.py +20 -0
  17. a2p2/version.py +210 -131
  18. a2p2/vlti/conf/GRAVITY_ditTable.json +21 -19
  19. a2p2/vlti/conf/GRAVITY_rangeTable.json +200 -28
  20. a2p2/vlti/conf/MATISSE_rangeTable.json +58 -22
  21. a2p2/vlti/conf/PIONIER_ditTable.json +1 -1
  22. a2p2/vlti/conf/PIONIER_rangeTable.json +16 -18
  23. a2p2/vlti/facility.py +160 -43
  24. a2p2/vlti/gravity.py +243 -311
  25. a2p2/vlti/gui.py +165 -39
  26. a2p2/vlti/instrument.py +266 -49
  27. a2p2/vlti/matisse.py +61 -147
  28. a2p2/vlti/pionier.py +34 -157
  29. {a2p2-0.2.14.dist-info → a2p2-0.7.4.dist-info}/METADATA +34 -20
  30. a2p2-0.7.4.dist-info/RECORD +39 -0
  31. {a2p2-0.2.14.dist-info → a2p2-0.7.4.dist-info}/WHEEL +1 -1
  32. {a2p2-0.2.14.dist-info → a2p2-0.7.4.dist-info}/entry_points.txt +0 -1
  33. a2p2/vlti/confP104/GRAVITY_ditTable.json +0 -122
  34. a2p2/vlti/confP104/GRAVITY_rangeTable.json +0 -202
  35. a2p2/vlti/confP104/MATISSE_ditTable.json +0 -2
  36. a2p2/vlti/confP104/MATISSE_rangeTable.json +0 -202
  37. a2p2/vlti/confP104/PIONIER_ditTable.json +0 -77
  38. a2p2/vlti/confP104/PIONIER_rangeTable.json +0 -118
  39. a2p2/vlti/confP105/GRAVITY_ditTable.json +0 -37
  40. a2p2/vlti/confP105/GRAVITY_rangeTable.json +0 -42
  41. a2p2/vlti/confP105/MATISSE_ditTable.json +0 -2
  42. a2p2/vlti/confP105/MATISSE_rangeTable.json +0 -44
  43. a2p2/vlti/confP105/PIONIER_ditTable.json +0 -25
  44. a2p2/vlti/confP105/PIONIER_rangeTable.json +0 -38
  45. a2p2-0.2.14.dist-info/RECORD +0 -44
  46. {a2p2-0.2.14.dist-info → a2p2-0.7.4.dist-info}/LICENSE +0 -0
  47. {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, text, displayString=True):
283
+ def addToLog(self, *args, **kwargs):
256
284
  """ Wrapper to log message in the common textfield """
257
- self.a2p2client.ui.addToLog(text, displayString)
285
+ self.a2p2client.ui.addToLog(*args, **kwargs)
258
286
 
259
- def ShowErrorMessage(self, text):
260
- self.a2p2client.ui.ShowErrorMessage(text)
287
+ def ShowErrorMessage(self, *args, **kwargs):
288
+ self.a2p2client.ui.ShowErrorMessage(*args, **kwargs)
261
289
 
262
- def ShowWarningMessage(self, text):
263
- self.a2p2client.ui.ShowWarningMessage(text)
290
+ def ShowWarningMessage(self, *args, **kwargs):
291
+ self.a2p2client.ui.ShowWarningMessage(*args, **kwargs)
264
292
 
265
- def ShowInfoMessage(self, text):
266
- self.a2p2client.ui.ShowInfoMessage(text)
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
@@ -2,6 +2,9 @@
2
2
 
3
3
  __all__ = []
4
4
 
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
5
8
 
6
9
  class Instrument():
7
10
 
a2p2/jmmc/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ __all__ = ['Catalog', 'Models','CallIper']
2
+
3
+ PRODLABEL = {True: "production", False: "pre-prod"}
4
+
5
+ from .catalogs import Catalog
6
+ from .models import Models
7
+ from .webservices import CallIper
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()