a2p2 0.2.15__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 (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 +129 -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 +205 -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.4.dist-info}/METADATA +34 -20
  29. a2p2-0.7.4.dist-info/RECORD +39 -0
  30. {a2p2-0.2.15.dist-info → a2p2-0.7.4.dist-info}/WHEEL +1 -1
  31. {a2p2-0.2.15.dist-info → a2p2-0.7.4.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.4.dist-info}/LICENSE +0 -0
  46. {a2p2-0.2.15.dist-info → a2p2-0.7.4.dist-info}/top_level.txt +0 -0
a2p2/vlti/instrument.py CHANGED
@@ -12,11 +12,13 @@ import logging
12
12
 
13
13
  import numpy as np
14
14
  from astropy.coordinates import SkyCoord
15
+ import astropy.units as u
15
16
 
16
17
  from a2p2.instrument import Instrument
17
18
 
18
19
  logger = logging.getLogger(__name__)
19
20
 
21
+
20
22
  class VltiInstrument(Instrument):
21
23
 
22
24
  def __init__(self, facility, insname):
@@ -28,13 +30,41 @@ class VltiInstrument(Instrument):
28
30
  self.rangeTable = None
29
31
  self.ditTable = None
30
32
 
33
+ # warnings
34
+ self.warnings = []
35
+ self.errors = []
36
+
37
+ def isScience(self, objtype):
38
+ return "SCI" in objtype
39
+
40
+ def isCalibrator(self, objtype):
41
+ return "SCI" not in objtype
42
+
31
43
  def get(self, obj, fieldname, defaultvalue):
32
44
  if fieldname in obj._fields:
33
45
  return getattr(obj, fieldname)
34
46
  else:
35
47
  return defaultvalue
36
48
 
37
- # must be defined by each instruments
49
+ def getSequence(self, ob):
50
+ # We may look at ob.observationSchedule but at present tuime schedule still is computed on a2p2 side
51
+ sci = []
52
+ cal = []
53
+ for observationConfiguration in ob.observationConfiguration:
54
+ if 'SCIENCE' in observationConfiguration.type:
55
+ sci.append(observationConfiguration)
56
+ else:
57
+ cal.append(observationConfiguration)
58
+ if len(sci)+len(cal) <= 2: # return [CAL] [SCI]
59
+ return cal+sci
60
+ # else return CAL1 SCI CAL2 [SCI CAL3] [SCI CAL4]
61
+ seq = []
62
+ for c in cal:
63
+ seq.append(c)
64
+ seq += sci
65
+ return seq[0:-1]
66
+
67
+ # checkOB() must be defined by each instruments
38
68
  # def checkOB(self, ob, p2container=None):
39
69
  # consider dryMode if p2container is None else check again and submit on p2 side
40
70
 
@@ -44,30 +74,49 @@ class VltiInstrument(Instrument):
44
74
 
45
75
  # create new container
46
76
  obsconflist = ob.observationConfiguration
77
+
78
+ # Aspro2 always send SCI at first position (or CAL if no SCI)
47
79
  folderName = obsconflist[0].SCTarget.name
48
80
  folderName = re.sub('[^A-Za-z0-9]+', '_', folderName.strip())
49
-
50
- # force a top folder in demo.
81
+ # prefix with username to make test folder clearer
51
82
  if p2container.isRoot() and self.facility.isTutorialAccount():
52
- ui.addToLog(
53
- "create pre folder (required for tutorial account) ", False)
54
- folder, _ = api.createFolder(p2container.containerId, folderName)
83
+ folderName += "_" + self.facility.a2p2client.preferences.getP2UserCommentName()
84
+
85
+ # do create a folder or concatenation only for SM or tutorial account
86
+ # should we create a concatenation only if we have multiple objects
87
+ if p2container.isRoot():
88
+ # create a concatenation if service mode (even if we do not have any cal at this stage)
89
+ if p2container.isServiceModeRun():
90
+ # TODO fix bug when we keep the last selection that is a Concatenation. Concatenation can't be nested.
91
+ # use parent container or ask user to choose another container location ?
92
+ folder, _ = api.createConcatenation(
93
+ p2container.containerId, folderName)
94
+ ui.addToLog(f"concatenation '{folderName}' created")
95
+ else:
96
+ folder, _ = api.createFolder(
97
+ p2container.containerId, folderName)
98
+ ui.addToLog(f"folder '{folderName}' created")
99
+
100
+ # update parent tree since we created a new subfolder
101
+ ui.updateTree(p2container.run, p2container.containerId)
102
+
55
103
  p2container.containerId = folder['containerId']
56
104
 
57
- # create a concatenation if service mode
58
- if p2container.isServiceModeRun():
59
- folder, _ = api.createConcatenation(
60
- p2container.containerId, folderName)
61
105
  else:
62
- folder, _ = api.createFolder(p2container.containerId, folderName)
63
- p2container.containerId = folder['containerId']
106
+ ui.addToLog("concatenation or folder not created")
64
107
 
65
108
  ui.addToLog("OB checked / preparing submission...")
66
109
  self.checkOB(ob, p2container)
67
110
 
111
+ # refresh and select last created element tree
112
+ ui.updateTree(p2container.run, p2container.containerId)
113
+ ui.selectTreeItem(p2container.containerId)
114
+ ui.addToLog(
115
+ "OB submitted! Please check logs and fix last details on P2 web.")
116
+
68
117
  def getCoords(self, target, requirePrecision=True):
69
118
  """
70
- Format coordinates from given target to be VLTI compliant.
119
+ Format HMS DMS coordinates from given target to be VLTI compliant.
71
120
  Throws an exception if requirePrecision is true and given inputs have less than 3 (RA) or 2 (DEC) digits.
72
121
  """
73
122
 
@@ -95,36 +144,63 @@ class VltiInstrument(Instrument):
95
144
 
96
145
  def getPMCoords(self, target, defaultPMRA=0.0, defaultPMDEC=0.0):
97
146
  """
98
- Returns PMRA, PMDEC as float values rounded to 4 decimal digits. 0.0 is used as default if not present.
147
+ Returns PMRA, PMDEC in arcsec/year as float values rounded to 4 decimal digits. 0.0 is used as default if not present.
99
148
  """
100
149
  PMRA = self.get(target, "PMRA", defaultPMRA)
101
150
  PMDEC = self.get(target, "PMDEC", defaultPMDEC)
102
151
  return round(float(PMRA) / 1000.0, 4), round(float(PMDEC) / 1000.0, 4)
103
152
 
153
+ def getPARALLAX(self, target, defaultPARALLAX=0.0):
154
+ """
155
+ Returns PARALLAX in arcsec as float value rounded to 4 decimal digits. 0.0 is used as default if not present.
156
+ """
157
+ PARALLAX = self.get(target, "PARALLAX", defaultPARALLAX)
158
+ return round(float(PARALLAX) / 1000.0, 4)
159
+
160
+
104
161
  def getFlux(self, target, flux):
105
162
  """
106
163
  Returns Flux as float values rounded to 3 decimal digits.
107
164
 
108
165
  flux in 'V', 'J', 'H'...
109
166
  """
110
- return round(float(getattr(target, "FLUX_" + flux)), 3)
111
-
112
- def getBaselineCode(self, baseline):
113
- # as of P104
114
- # take care if you change next branch order
115
- if "B2" in baseline:
116
- return "small"
117
- elif "G2" in baseline:
118
- return "medium"
119
- elif "J3" in baseline:
120
- return "large"
121
- elif "K0" in baseline:
122
- return "astrometric"
123
- elif "U" in baseline:
124
- return "UTs"
167
+ try:
168
+ return round(float(getattr(target, "FLUX_" + flux)), 3)
169
+ except:
170
+ raise ValueError(
171
+ f"Missing {flux} flux for target { getattr(target,'name') }") from None
172
+
173
+ def getBaselineCode(self, ob):
174
+ confAltName = ob.get(ob.interferometerConfiguration, "confAltName")
175
+ stations = ob.get(ob.interferometerConfiguration, "stations")
176
+ if confAltName:
177
+ return confAltName
125
178
  else:
126
179
  raise ValueError(
127
- "Can't detect Interferometric Array type from given baseline : %s)" % (baseline))
180
+ "Can't detect alt name of Interferometric Array type from given baseline : %s)" % (stations))
181
+
182
+ def getAcquisitionType(self):
183
+ return self.facility.ui.getAcquisitionType()
184
+ return "onaxis"
185
+ return "offaxis"
186
+ return "wide"
187
+
188
+ def checkIssVltiType(self, acqTSF):
189
+ # TODO improve handling of this keyword using input from Aspro2's OB
190
+
191
+ # use GUI live checkboxes instead of preferences
192
+ vltitypesVars = self.facility.ui.getIssVltitypeVars()
193
+ vltitypes = [v for v in vltitypesVars
194
+ if vltitypesVars[v].get() and self.isInRange(acqTSF.tpl, "ISS.VLTITYPE", v)]
195
+ if vltitypes:
196
+ self.ui.addToLog(
197
+ f"Set template's ISS_VLTITYPE to {vltitypes}")
198
+ acqTSF.ISS_VLTITYPE = vltitypes
199
+ else:
200
+ # TODO throw an warning ?
201
+ self.ui.addToLog(
202
+ f"Warning: no compatible ISS_VLTITYPE selected in the GUI")
203
+ pass
128
204
 
129
205
  def getA2p2Comments(self):
130
206
  return 'Generated by ' + self.facility.a2p2client.preferences.getP2UserCommentName() + \
@@ -149,7 +225,7 @@ class VltiInstrument(Instrument):
149
225
  self.ditTable = json.load(open(f))
150
226
  return self.ditTable
151
227
 
152
- def getDit(self, tel, spec, pol, K, dualFeed=False, showWarning=False):
228
+ def getDit(self, tel, spec, pol, K, dualFeed=False, targetName="", showWarning=False):
153
229
  """
154
230
  finds DIT according to ditTable and K magnitude K
155
231
 
@@ -174,7 +250,7 @@ class VltiInstrument(Instrument):
174
250
  if tel == "UT":
175
251
  dK += ditTable["AT"]['Kut']
176
252
  for i, d in enumerate(dits):
177
- if mags[i] < (K - dK) and (K - dK) <= mags[i + 1]:
253
+ if mags[i] <= (K - dK) and (K - dK) <= mags[i + 1]:
178
254
  return d
179
255
 
180
256
  # handle out of bounds
@@ -183,11 +259,19 @@ class VltiInstrument(Instrument):
183
259
  for i, d in enumerate(dits):
184
260
  kmin = min(kmin, mags[i] + dK)
185
261
  kmax = max(kmax, mags[i + 1] + dK)
186
- if kmin == K:
187
- return minDIT
188
- raise ValueError(
189
- "K mag (%f) is out of ranges [%f,%f]\n for this mode (tel=%s, spec=%s, pol=%s, dualFeed=%s)" % (
190
- K, kmin, kmax, tel, spec, pol, dualFeed))
262
+
263
+ if showWarning:
264
+ self.warnings.append(
265
+ f"K mag ({K}) is out of range [{kmin},{kmax}]\n mode ( ∆K={dK} tel={tel}, spec={spec}, pol={pol}, dualFeed={dualFeed}) \n for target '{targetName}'")
266
+
267
+ # always return a value to avoid unsupported operations
268
+ if K < kmin:
269
+ return min(dits)
270
+ return max(dits)
271
+
272
+ # raise ValueError(
273
+ # "K mag (%f) of '%s' is out of ranges [%f,%f]\n for this mode (tel=%s, spec=%s, pol=%s, dualFeed=%s)" % (
274
+ # K, targetName, kmin, kmax, tel, spec, pol, dualFeed))
191
275
 
192
276
  def getRangeTable(self):
193
277
  if self.rangeTable:
@@ -222,6 +306,14 @@ class VltiInstrument(Instrument):
222
306
  return value >= rangeTable[_tpl][key]['min'] and \
223
307
  value <= rangeTable[_tpl][key]['max']
224
308
  if 'list' in rangeTable[_tpl][key].keys():
309
+ #
310
+ # hack set to check for coordinates convention define by p2
311
+ # vlue will be checked by p2 later...
312
+ if "ra" in rangeTable[_tpl][key]['list']:
313
+ return True
314
+ if "dec" in rangeTable[_tpl][key]['list']:
315
+ return True
316
+
225
317
  # return value in rangeTable[_tpl][key]['list']
226
318
  if type(value) is list:
227
319
  for v in value:
@@ -282,7 +374,7 @@ class VltiInstrument(Instrument):
282
374
  if not key in rangeTable[_tpl].keys():
283
375
  raise ValueError(
284
376
  "unknown keyword '%s' in template '%s'" % (key, tpl))
285
- if 'default' in rangeTable[_tpl][key].keys() :
377
+ if 'default' in rangeTable[_tpl][key].keys():
286
378
  return rangeTable[_tpl][key]['default']
287
379
  return None
288
380
 
@@ -306,13 +398,15 @@ class VltiInstrument(Instrument):
306
398
  res[key] = rangeTable[_tpl][key]["default"]
307
399
  return res
308
400
 
401
+ def getSkySeparation(self, ra1, dec1, ra2, dec2):
402
+ target1 = SkyCoord(ra1, dec1, frame='icrs', unit=(u.hourangle, u.deg))
403
+ target2 = SkyCoord(ra2, dec2, frame='icrs', unit=(u.hourangle, u.deg))
404
+ return target1.separation(target2)
309
405
 
310
- def getSkyDiff(self, ra, dec, ftra, ftdec):
311
- science = SkyCoord(ra, dec, frame='icrs', unit='deg')
312
- ft = SkyCoord(ftra, ftdec, frame='icrs', unit='deg')
313
- ra_offset = (science.ra - ft.ra) * np.cos(ft.dec.to('radian'))
314
- dec_offset = (science.dec - ft.dec)
315
- return [ra_offset.deg * 3600 * 1000, dec_offset.deg * 3600 * 1000] # in mas
406
+ def getSkyOffset(self, ra, dec, originRa, originDec):
407
+ science = SkyCoord(ra, dec, frame='icrs', unit=(u.hourangle, u.deg))
408
+ origin = SkyCoord(originRa, originDec, frame='icrs', unit=(u.hourangle, u.deg))
409
+ return origin.spherical_offsets_to(science)
316
410
 
317
411
  def getSiderealTimeConstraints(self, LSTINTERVAL):
318
412
  # by default, above 40 degree. Will generate a WAIVERABLE ERROR if not.
@@ -332,9 +426,10 @@ class VltiInstrument(Instrument):
332
426
  intervals.append({'from': lstStartSex, 'to': lstEndSex})
333
427
  return intervals
334
428
 
335
- def saveSiderealTimeConstraints(self, api, obId, LSTINTERVAL):
429
+ def saveSiderealTimeConstraints(self, api, ob, LSTINTERVAL):
336
430
  # wait for next Aspro release to only send constraints set by user
337
431
  return
432
+ obId = ob['obId']
338
433
  intervals = self.getSiderealTimeConstraints(LSTINTERVAL)
339
434
  if intervals:
340
435
  sidTCs, stcVersion = api.getSiderealTimeConstraints(obId)
@@ -364,7 +459,35 @@ class VltiInstrument(Instrument):
364
459
 
365
460
  return s
366
461
 
367
- def showP2Response(self, response, ob, obId):
462
+ def formatRangeTable(self):
463
+ rangeTable = self.getRangeTable()
464
+ buffer = ""
465
+ for l in rangeTable.keys():
466
+ buffer += l + "\n"
467
+ for k in rangeTable[l].keys():
468
+ constraint = rangeTable[l][k]
469
+ keys = constraint.keys()
470
+ buffer += ' %30s :' % (k)
471
+ if 'min' in keys and 'max' in keys:
472
+ buffer += ' %f ... %f ' % (
473
+ constraint['min'], constraint['max'])
474
+ elif 'list' in keys:
475
+ buffer += str(constraint['list'])
476
+ elif "spaceseparatedlist" in keys:
477
+ buffer += ' ' + " ".join(constraint['spaceseparatedlist'])
478
+ if 'default' in keys:
479
+ buffer += ' (' + str(constraint['default']) + ')'
480
+ else:
481
+ buffer += ' -no default-'
482
+ buffer += "\n"
483
+ return buffer
484
+
485
+ def showP2Response(self, response, ob,):
486
+ if not ob:
487
+ return
488
+
489
+ obId = ob['obId']
490
+
368
491
  if response['observable']:
369
492
  msg = 'OB ' + \
370
493
  str(obId) + ' submitted successfully on P2\n' + \
@@ -373,9 +496,93 @@ class VltiInstrument(Instrument):
373
496
  msg = 'OB ' + str(obId) + ' submitted successfully on P2\n' + ob[
374
497
  'name'] + ' has WARNING.\n see LOG for details.'
375
498
  self.ui.addToLog('\n')
376
- self.ui.ShowInfoMessage(msg)
499
+ # self.ui.ShowInfoMessage(msg)
500
+ self.ui.addToLog(msg)
377
501
  self.ui.addToLog('\n'.join(response['messages']) + '\n\n')
378
502
 
503
+ def createOB(self, p2container, obTarget, obConstraints, OBJTYPE, instrumentMode, LSTINTERVAL, tsfs):
504
+ """ Creates an OB on P2 and attach a template for every given tsf."""
505
+
506
+ ui = self.ui
507
+ ui.setProgress(0.1)
508
+
509
+ goodName = re.sub('[^A-Za-z0-9]+', '_', obTarget.name)
510
+ OBS_DESCR = '_'.join(
511
+ (OBJTYPE[0:3], goodName, self.getName(), instrumentMode))
512
+ # removed from template name acqTSF.ISS_BASELINE[0]
513
+
514
+ # dev code to debug without interracting with P2
515
+ if False:
516
+ self.ui.addToLog(f"Skip ob creation : {OBS_DESCR}")
517
+ for tsf in tsfs:
518
+ self.ui.addToLog(f"Skip {tsf.getP2Name()} template creation")
519
+ ui.setProgress(1.0)
520
+ return None
521
+ else:
522
+ self.ui.addToLog(f"Creating new ob from p2 : {OBS_DESCR}")
523
+
524
+ api = self.facility.getAPI()
525
+
526
+ ob, obVersion = api.createOB(p2container.containerId, OBS_DESCR)
527
+
528
+ # we use obId to populate OB
529
+ ob['obsDescription']['name'] = OBS_DESCR[0:min(len(OBS_DESCR), 31)]
530
+ ob['obsDescription']['userComments'] = self.getA2p2Comments()
531
+
532
+ # copy target info
533
+ targetInfo = obTarget.getDict()
534
+ for key in targetInfo:
535
+ ob['target'][key] = targetInfo[key]
536
+
537
+ # copy constraints info
538
+ constraints = obConstraints.getDict()
539
+ for k in constraints:
540
+ ob['constraints'][k] = constraints[k]
541
+
542
+ self.ui.addToLog("New OB saved to p2\n%s" % ob, False)
543
+ ob, obVersion = api.saveOB(ob, obVersion)
544
+
545
+ # set time constraints if present
546
+ self.saveSiderealTimeConstraints(api, ob, LSTINTERVAL)
547
+ ui.addProgress()
548
+
549
+ for tsf in tsfs:
550
+ ui.addProgress()
551
+ self.createTemplate(ob,tsf)
552
+
553
+ # verify OB online
554
+ response = self.verifyOB(ob)
555
+ ui.setProgress(1.0)
556
+
557
+ self.showP2Response(response, ob)
558
+
559
+ return ob
560
+
561
+ def createTemplate(self, ob, tsf, templateName=None):
562
+ if not templateName:
563
+ templateName = tsf.getP2Name()
564
+
565
+ if not ob:
566
+ self.ui.addToLog(f"Request for new template ignored '{templateName}'")
567
+ return
568
+ self.ui.addToLog(f"Creating new template '{templateName}'")
569
+ obId = ob['obId']
570
+ api = self.facility.getAPI()
571
+ tpl, tplVersion = api.createTemplate(obId, templateName)
572
+ values = tsf.getDict()
573
+ tpl, tplVersion = api.setTemplateParams(
574
+ obId, tpl, values, tplVersion)
575
+
576
+ def verifyOB(self, ob):
577
+ if not ob:
578
+ self.ui.addToLog(f"Request to verify OB ignored")
579
+ return
580
+
581
+ api = self.facility.getAPI()
582
+ response, _=api.verifyOB(ob['obId'], True)
583
+ return response
584
+
585
+
379
586
 
380
587
  # TemplateSignatureFile
381
588
  # use new style class to get __getattr__ advantage
@@ -412,6 +619,9 @@ class TSF(object):
412
619
  def getDict(self):
413
620
  return self.tsfParams
414
621
 
622
+ def getName(self):
623
+ return self.tpl
624
+
415
625
  def getP2Name(self):
416
626
  return self.tpl[0:-4]
417
627
 
@@ -490,9 +700,14 @@ class FixedDict(object):
490
700
 
491
701
  class OBTarget(FixedDict):
492
702
 
493
- def __init__(self):
703
+ def __init__(self, instrument, scienceTarget):
494
704
  FixedDict.__init__(
495
705
  self, ('name', 'ra', 'dec', 'properMotionRa', 'properMotionDec'))
706
+ # Target name can include any alphanumeric character, and space, dot, plus or minus signs [a-z][A-Z][0-9][.+- ]
707
+ # ( https://www.eso.org/sci/observing/phase2/p2intro/p2-tutorials/p2-ImportTargetList.html )
708
+ self.name = re.sub(r'[^a-zA-Z0-9.\+\- ]+','',scienceTarget.name.strip())
709
+ self.ra, self.dec = instrument.getCoords(scienceTarget)
710
+ self.properMotionRa, self.properMotionDec = instrument.getPMCoords(scienceTarget)
496
711
 
497
712
 
498
713
  class OBConstraints(TSF):
@@ -500,4 +715,4 @@ class OBConstraints(TSF):
500
715
  def __init__(self, instrument):
501
716
  TSF.__init__(self, instrument, "instrumentConstraints.tsf")
502
717
  # WORKARROUND missing param in p2 json but valid and used in our code
503
- instrument.getRangeTable()[self.tpl]["name"]={}
718
+ instrument.getRangeTable()[self.tpl]["name"] = {}