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.
- a2p2/__main__.py +39 -0
- a2p2/chara/facility.py +51 -3
- a2p2/chara/gui.py +29 -5
- a2p2/client.py +144 -34
- a2p2/facility.py +11 -1
- a2p2/gui.py +32 -1
- a2p2/jmmc/__init__.py +7 -0
- a2p2/jmmc/catalogs.py +213 -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 +95 -9
- a2p2/samp.py +17 -0
- a2p2/version.py +209 -137
- 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 +156 -118
- a2p2/vlti/gravity.py +243 -311
- a2p2/vlti/gui.py +162 -40
- a2p2/vlti/instrument.py +264 -49
- a2p2/vlti/matisse.py +61 -147
- a2p2/vlti/pionier.py +34 -157
- {a2p2-0.2.15.dist-info → a2p2-0.7.5.dist-info}/METADATA +37 -21
- a2p2-0.7.5.dist-info/RECORD +39 -0
- {a2p2-0.2.15.dist-info → a2p2-0.7.5.dist-info}/WHEEL +1 -1
- {a2p2-0.2.15.dist-info → a2p2-0.7.5.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.15.dist-info/RECORD +0 -44
- {a2p2-0.2.15.dist-info → a2p2-0.7.5.dist-info/licenses}/LICENSE +0 -0
- {a2p2-0.2.15.dist-info → a2p2-0.7.5.dist-info}/top_level.txt +0 -0
a2p2/jmmc/webservices.py
ADDED
@@ -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
|
-
|
26
|
-
|
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
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
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
|
+
|