a2p2 0.2.15__py3-none-any.whl → 0.7.5__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 (46) hide show
  1. a2p2/__main__.py +39 -0
  2. a2p2/chara/facility.py +51 -3
  3. a2p2/chara/gui.py +29 -5
  4. a2p2/client.py +144 -34
  5. a2p2/facility.py +11 -1
  6. a2p2/gui.py +32 -1
  7. a2p2/jmmc/__init__.py +7 -0
  8. a2p2/jmmc/catalogs.py +213 -0
  9. a2p2/jmmc/generated_models.py +191 -0
  10. a2p2/jmmc/models.py +104 -0
  11. a2p2/jmmc/services.py +16 -0
  12. a2p2/jmmc/utils.py +130 -0
  13. a2p2/jmmc/webservices.py +48 -0
  14. a2p2/ob.py +95 -9
  15. a2p2/samp.py +17 -0
  16. a2p2/version.py +209 -137
  17. a2p2/vlti/conf/GRAVITY_ditTable.json +21 -19
  18. a2p2/vlti/conf/GRAVITY_rangeTable.json +200 -28
  19. a2p2/vlti/conf/MATISSE_rangeTable.json +58 -22
  20. a2p2/vlti/conf/PIONIER_ditTable.json +1 -1
  21. a2p2/vlti/conf/PIONIER_rangeTable.json +16 -18
  22. a2p2/vlti/facility.py +156 -118
  23. a2p2/vlti/gravity.py +243 -311
  24. a2p2/vlti/gui.py +162 -40
  25. a2p2/vlti/instrument.py +264 -49
  26. a2p2/vlti/matisse.py +61 -147
  27. a2p2/vlti/pionier.py +34 -157
  28. {a2p2-0.2.15.dist-info → a2p2-0.7.5.dist-info}/METADATA +37 -21
  29. a2p2-0.7.5.dist-info/RECORD +39 -0
  30. {a2p2-0.2.15.dist-info → a2p2-0.7.5.dist-info}/WHEEL +1 -1
  31. {a2p2-0.2.15.dist-info → a2p2-0.7.5.dist-info}/entry_points.txt +0 -1
  32. a2p2/vlti/confP104/GRAVITY_ditTable.json +0 -122
  33. a2p2/vlti/confP104/GRAVITY_rangeTable.json +0 -202
  34. a2p2/vlti/confP104/MATISSE_ditTable.json +0 -2
  35. a2p2/vlti/confP104/MATISSE_rangeTable.json +0 -202
  36. a2p2/vlti/confP104/PIONIER_ditTable.json +0 -77
  37. a2p2/vlti/confP104/PIONIER_rangeTable.json +0 -118
  38. a2p2/vlti/confP105/GRAVITY_ditTable.json +0 -37
  39. a2p2/vlti/confP105/GRAVITY_rangeTable.json +0 -42
  40. a2p2/vlti/confP105/MATISSE_ditTable.json +0 -2
  41. a2p2/vlti/confP105/MATISSE_rangeTable.json +0 -44
  42. a2p2/vlti/confP105/PIONIER_ditTable.json +0 -25
  43. a2p2/vlti/confP105/PIONIER_rangeTable.json +0 -38
  44. a2p2-0.2.15.dist-info/RECORD +0 -44
  45. {a2p2-0.2.15.dist-info → a2p2-0.7.5.dist-info/licenses}/LICENSE +0 -0
  46. {a2p2-0.2.15.dist-info → a2p2-0.7.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env python
2
+
3
+ __all__ = []
4
+
5
+ import logging
6
+ import json
7
+
8
+ from .utils import JmmcAPI
9
+ from . import PRODLABEL
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class CallIper():
14
+ """ Call IPER using its 'API' through simple methods.
15
+
16
+ from a2p2.jmmc import CallIper
17
+ calliper = CallIper()
18
+ diskresult = caliper.runDiskFit(pathname)
19
+ ud = diskresult["diameter"]
20
+
21
+ """
22
+ def __init__(self, prod=False, apiUrl=None):
23
+ self.prod = prod
24
+ self.serviceName = "callIper"
25
+
26
+ # Manage prod & preprod or user provided access points
27
+ if apiUrl:
28
+ self.apiUrl = apiUrl # trust given url as callIperAPI if value is provided
29
+ elif self.prod:
30
+ self.apiUrl = "" # no api in production yet
31
+ raise Warning("sorry, no api for production yet")
32
+ else:
33
+ self.apiUrl = "http://apps.jmmc.fr/~mellag/LITproWebService/run.php"
34
+
35
+ self.api = JmmcAPI(self.apiUrl)
36
+
37
+ logger.info("Create calliper client to access '%s' (%s API at %s)" %
38
+ (self.serviceName, PRODLABEL[self.prod], self.api.rootURL))
39
+
40
+ # API will evolve in the future with parameter to select fitter, model and data to fit...
41
+ def runDiskFit(self, oifitsFilename, jsonOutput=True):
42
+ """ Ask CallIper to fit given data for a simple disk """
43
+ files = {'userfile': (oifitsFilename, open(oifitsFilename, 'rb'))}
44
+ data = {'method': 'runJsonFit'}
45
+ res=self.api._post("", data=data, files=files)
46
+ if jsonOutput:
47
+ return json.loads(res)
48
+ return res
a2p2/ob.py CHANGED
@@ -6,6 +6,10 @@ import logging
6
6
  import json
7
7
  import xml.etree.ElementTree as ET
8
8
  from collections import defaultdict, namedtuple, OrderedDict
9
+ import re
10
+
11
+ import copy
12
+
9
13
 
10
14
  logger = logging.getLogger(__name__)
11
15
 
@@ -22,8 +26,11 @@ def etree_to_dict(t):
22
26
  d = {t.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}
23
27
  # print("add d=%s"%str(d))
24
28
  if t.attrib:
25
- d[t.tag].update(('' + k, v)
26
- for k, v in t.attrib.items()) # was '@' but can't be serialized by namedtuple
29
+ attrs = {}
30
+ for k, v in t.attrib.items():
31
+ if not "{" in k[0]: # ignore elementName with prefix
32
+ attrs[k]=v
33
+ d[t.tag].update(attrs)
27
34
  if t.text:
28
35
  text = t.text.strip()
29
36
  if children or t.attrib:
@@ -33,6 +40,66 @@ def etree_to_dict(t):
33
40
  d[t.tag] = text
34
41
  return d
35
42
 
43
+ def normalizeObj(obj):
44
+ """
45
+ Modify in place the structure if required.
46
+ Note:
47
+ dict key must be valid identifier (only tested for EXTRA_INFORMATIONS created fields)
48
+ """
49
+ otype=type(obj)
50
+ if otype is dict:
51
+ for k,v in obj.items():
52
+
53
+ if k in ['observationConfiguration', 'HAinterval', 'LSTinterval'] and not isinstance(v, list):
54
+ # always defines observationConfiguration as a list
55
+ normalizeObj(v)
56
+ obj[k]=[v]
57
+ elif k=='EXTRA_INFORMATIONS':
58
+ #print(f"Updating dict {k} was : {obj[k]}")
59
+ # normalize input that may have various form on list of dict
60
+ try:
61
+ fields=[]
62
+ funits={}
63
+ for fk in ["parameter","field"]:
64
+ if fk in obj[k]:
65
+ l=obj[k][fk].copy()
66
+ #print(f"'{fk}' => {l}\n({type(l)})\n")
67
+ if type(l) is dict:
68
+ fields.append(l)
69
+ else:
70
+ fields+=l
71
+
72
+ #print(f"Fields to update: {fields}")
73
+ #store new fields
74
+ for vn in fields:
75
+ fname=vn["name"]
76
+ # test if we have to normalize field name that will become a python field
77
+ if not fname.isidentifier():
78
+ # replace unvalid chars by _ and append F to avoid starting by _
79
+ fname=re.sub('^_','F_', re.sub('\W|^(?=\d)','_', fname))
80
+ obj[k][fname]=vn["value"]
81
+ if "unit" in vn:
82
+ funits[fname]=vn["unit"]
83
+ #store units
84
+ obj[k]["field_units"]=funits
85
+ # remove old arrays or dicts
86
+ for fk in ["parameter","field"]:
87
+ if fk in obj[k]:
88
+ del obj[k][fk]
89
+ pass
90
+ except:
91
+ import traceback
92
+ print (traceback.format_exc())
93
+ print(f"\nCan't read {v}")
94
+ pass
95
+ else:
96
+ normalizeObj(v)
97
+ return obj
98
+ elif otype is list:
99
+ for e in obj:
100
+ normalizeObj(e)
101
+ else:
102
+ pass
36
103
 
37
104
  class OB():
38
105
  """
@@ -45,29 +112,40 @@ class OB():
45
112
 
46
113
  every values are string (must be converted for numeric values).
47
114
 
115
+ Following fields are converted to list even if the list get a single element. The OB code can then handle such multiples values.
116
+ ['observationConfiguration', 'HAinterval', 'LSTinterval']
117
+
48
118
  """
49
119
 
50
120
  def __init__(self, url):
51
121
  # extract XML in elementTree
52
-
53
122
  e = ET.parse(url)
123
+ # convert data to dict structure
54
124
  d = etree_to_dict(e.getroot())
55
125
  # keep only content of subelement to avoid schema version change
56
126
  # '{http://www.jmmc.fr/aspro-ob/0.1}observingBlockDefinition'
57
127
  ds = d[list(d)[0]] # -> version and name are lost
58
- # store attributes
128
+
129
+ #self.srcDict = copy.deepcopy(ds)
130
+ normalizeObj(ds)
131
+ #self.normDict = ds
132
+ #self.srcObj = toObject(self.srcDict,inputName="OB")
133
+ #self.normObj = toObject(self.normDict,inputName="OB")
134
+
135
+ # store subelements as attributes
59
136
  for e in ds.keys():
60
137
  # parse JSON into an object with attributes corresponding to dict keys.
61
138
  # We should probably avoid json use...
62
139
  o = json.loads(
63
140
  json.dumps(ds[e]), object_hook=lambda d: namedtuple(e, d.keys())(*d.values()))
64
- # observationConfiguration may be uniq but force it to be a list
65
- if "observationConfiguration" in e and not isinstance(o, list):
66
- setattr(self, e, [o])
67
- else:
68
- setattr(self, e, o)
141
+ setattr(self, e, o)
142
+
143
+ # store normalized source for str repr
69
144
  self.ds = ds
70
145
 
146
+ def as_dict(self):
147
+ return self.ds
148
+
71
149
  def getFluxes(self, target):
72
150
  """
73
151
  Return a flux mesurements as dict (ordered dict by BVRIJHK ).
@@ -81,6 +159,14 @@ class OB():
81
159
 
82
160
  return OrderedDict(sorted(fluxes.items(), key=lambda t: order.find(t[0])))
83
161
 
162
+ def getPeriod(self):
163
+ """
164
+ Return period given by Aspro2 as integer.
165
+ """
166
+ # use re to remove alpha characters from "Period 109" and make.
167
+ return int(re.sub(r"\D", "", self.interferometerConfiguration.version))
168
+
169
+
84
170
  def get(self, obj, fieldname, defaultvalue=None):
85
171
  if fieldname in obj._fields:
86
172
  return getattr(obj, fieldname)
a2p2/samp.py CHANGED
@@ -15,12 +15,14 @@ class Receiver(object):
15
15
  self.received = False
16
16
 
17
17
  def receive_call(self, private_key, sender_id, msg_id, mtype, params, extra):
18
+ self.mtype=mtype
18
19
  self.params = params
19
20
  self.received = True
20
21
  self.client.reply(
21
22
  msg_id, {"samp.status": "samp.ok", "samp.result": {}})
22
23
 
23
24
  def receive_notification(self, private_key, sender_id, mtype, params, extra):
25
+ self.mtype=mtype
24
26
  self.params = params
25
27
  self.received = True
26
28
 
@@ -47,6 +49,7 @@ class A2p2SampClient():
47
49
  # an error is thrown here if no hub is present
48
50
 
49
51
  # TODO get samp client name and display it in the UI
52
+ # TODO !! declare mtypes as enum
50
53
 
51
54
  # Instantiate the receiver
52
55
  self.r = Receiver(self.sampClient)
@@ -54,6 +57,10 @@ class A2p2SampClient():
54
57
  self.sampClient.bind_receive_call("ob.load.data", self.r.receive_call)
55
58
  self.sampClient.bind_receive_notification(
56
59
  "ob.load.data", self.r.receive_notification)
60
+ # Listen for Model related instructions
61
+ self.sampClient.bind_receive_call("fr.jmmc.litpro.start.setting",self.r.receive_call)
62
+ self.sampClient.bind_receive_notification(
63
+ "fr.jmmc.litpro.start.setting",self.r.receive_notification)
57
64
 
58
65
  def disconnect(self):
59
66
  self.sampClient.disconnect()
@@ -82,6 +89,9 @@ class A2p2SampClient():
82
89
  def clear_message(self):
83
90
  return self.r.clear()
84
91
 
92
+ def has_ob_message(self):
93
+ return "ob.load.data" in self.r.mtype
94
+
85
95
  def get_ob_url(self):
86
96
  url = self.r.params['url']
87
97
  if url.startswith("file:///"):
@@ -92,3 +102,10 @@ class A2p2SampClient():
92
102
  elif url.startswith("file:/"): # work arround bugged file urls on *nix
93
103
  return url[5:]
94
104
  return url
105
+
106
+ def has_model_message(self):
107
+ return "fr.jmmc.litpro.start.setting" in self.r.mtype
108
+
109
+ def get_model(self):
110
+ return self.r.params['model']
111
+