class A2p2SampClient(): # TODO watch hub disconnection def __init__(self): self.sampClient = SAMPIntegratedClient( "A2P2 samp relay" ) # TODO get title from main program class instead of HardCoded value def __del__(self): self.disconnect() def connect(self): self.sampClient.connect() # an error is thrown here if no hub is present # TODO get samp client name and display it in the UI # Instantiate the receiver self.r = Receiver(self.sampClient) # Listen for any instructions to load a table self.sampClient.bind_receive_call("ob.load.data", self.r.receive_call) self.sampClient.bind_receive_notification("ob.load.data", self.r.receive_notification) def disconnect(self): self.sampClient.disconnect() def is_connected(self): # Workarround the 'non' reliable is_connected attribute # this helps to reconnect after hub connection lost try: return self.sampClient.is_connected and ( self.sampClient.ping() or self.sampClient.is_connected) except: # consider connection refused exception as not connected state return False def get_status(self): if self.is_connected(): return "connected [%s]" % (self.sampClient.get_public_id()) else: return "not connected" def get_public_id(self): return self.sampClient.get_public_id() def has_message(self): return self.is_connected() and self.r.received def clear_message(self): return self.r.clear() def get_ob_url(self): url = self.r.params['url'] if url.startswith("file:///"): return url[7:] elif url.startswith("file:/"): # work arround bugged file urls return url[5:] return url
class SAMPManager(object): def __init__(self, callback, check_connection_period=1): self.callback = callback self.dirname = os.path.dirname(os.path.abspath(__file__)) self.timer = None self.samp_hub = None self.samp_client = None self.metadata = { 'samp.name': 'iSpec', 'samp.description.text': 'iSpec', 'samp.icon.url': 'file://' + self.dirname + '/images/iSpec.png', } self.samp_client = SAMPIntegratedClient(metadata=self.metadata, addr='localhost') self.check_connection_period = check_connection_period # seconds self.__check_connection() signal.signal(signal.SIGINT, self.__signal_handler) def is_connected(self): working_connection = False if self.samp_client is not None and self.samp_client.is_connected(): try: self.samp_client.ping() working_connection = True except: working_connection = False return working_connection def __check_connection(self): status = "Status: " if not self.is_connected(): if self.samp_client is not None and self.samp_client.is_connected( ): # Destroy old client to completely reset the connection status del self.samp_client self.samp_client = SAMPIntegratedClient(metadata=self.metadata, addr='localhost') logging.info("SAMP Connection lost") status += "Disconnected" self.__connect() else: status += "Connected" pass logging.debug(status) self.timer = threading.Timer(self.check_connection_period, self.__check_connection) self.timer.start() def __connect(self): # Before we start, let's kill off any zombies try: self.shutdown() except: pass # sampy seems to fall over sometimes if 'localhost' isn't specified, even though it shouldn't #self.samp_hub = sampy.SAMPHubServer(addr='localhost') #self.samp_hub.start() try: self.samp_client.connect() logging.info("SAMP Connection established") except Exception: pass else: #samp_client.bindReceiveNotification("*", self.__samp_receive_notification) #samp_client.bindReceiveCall("*", self.__samp_receive_call) self.samp_client.bind_receive_notification( "samp.hub.event.register", self.__samp_receive_notification) self.samp_client.bind_receive_notification( "samp.hub.event.metadata", self.__samp_receive_notification) self.samp_client.bind_receive_notification( "samp.hub.event.subscriptions", self.__samp_receive_notification) self.samp_client.bind_receive_notification( "samp.hub.event.unregister", self.__samp_receive_notification) self.samp_client.bind_receive_notification( "table.load.votable", self.__samp_receive_notification) self.samp_client.bind_receive_notification( "spectrum.load.ssa-generic", self.__samp_receive_notification) self.samp_client.bind_receive_call("table.load.votable", self.__samp_receive_call) self.samp_client.bind_receive_call("spectrum.load.ssa-generic", self.__samp_receive_call) def __samp_receive_notification(self, private_key, sender_id, mtype, params, extra): #print "Notification:", private_key, sender_id, mtype, params, extra if mtype == 'samp.hub.event.register': #print "Registered:", params['id'] pass elif mtype == 'samp.hub.event.metadata': #print "Metadata:", params['id'], "=", params['metadata']['samp.name'] pass elif mtype == 'samp.hub.event.subscriptions': if 'spectrum.load.ssa-generic' in list( params['subscriptions'].keys()): #print params['id'], "supports spectrum" pass if 'table.load.votable' in list(params['subscriptions'].keys()): #print params['id'], "supports votable" pass #print params elif mtype == 'samp.hub.event.unregister': #print params['id'], "unregistered" pass else: # For instance, VOSpec sends load votable/spectrum in form of notification # so we should try to process them also msg_id = None spectrum = self.__samp_receive_and_transform_spectrum( mtype, params) logging.info("Spectrum received via SAMP") self.callback(spectrum, "Received_spectrum") # Function called when a call is received def __samp_receive_call(self, private_key, sender_id, msg_id, mtype, params, extra): #print "Call:", private_key, sender_id, msg_id, mtype, params, extra spectrum = self.__samp_receive_and_transform_spectrum(mtype, params) self.samp_client.ereply(msg_id, sampy.SAMP_STATUS_OK, result={"txt": "printed"}) logging.info("Spectrum received via SAMP") self.callback(spectrum, "Received_spectrum") def __samp_receive_and_transform_spectrum(self, mtype, params): if mtype == 'table.load.votable': #print "Received votable", params['url'] votable = self.__read_votable(params['url']) spectrum = self.__votable_to_spectrum(votable) #print "Spectrum success" elif mtype == 'spectrum.load.ssa-generic': #print "Received spectrum", params['url'], "in format", params['meta']['Access.Format'] if params['meta']['Access.Format'] == 'application/votable': #print "- Votable" votable = self.__read_votable(params['url']) spectrum = self.__votable_to_spectrum(votable) #print "Spectrum success" elif params['meta']['Access.Format'] == 'application/fits': #print "- FITS" spectrum = self.__read_vofits(params['url']) #print "Spectrum success" else: raise Exception("Unknown format") else: raise Exception("Unknown action") return spectrum def __signal_handler(self, signal, frame): print('SIGTERM received (ctrl+c)') self.shutdown() def shutdown(self): if self.timer is not None: self.timer.cancel() if self.samp_client.is_connected(): try: self.samp_client.disconnect() except Exception: pass if self.samp_hub is not None and self.samp_hub._is_running: self.samp_hub.stop() sys.exit(0) def __get_target_client(self, target_client_name): neighbours = samp_client.get_registered_clients() for neighbour in neighbours: metadata = self.samp_client.get_metadata(neighbour) try: if (metadata['samp.name'] == target_client_name): return neighbour except KeyError: continue return None ########### [start] File operations def __votable_to_spectrum(self, votable): spectrum = None # Search for what it is required has_waveobs = False has_flux = False target_table = None waveobs_units = None for resource in votable.resources: for table in resource.tables: if len(table.array) > 0 and len(table.fields) >= 2: for field in table.fields: if field.name in ["wave", "waveobs"]: has_waveobs = True waveobs_units = field.unit elif field.name == "flux": has_flux = True if has_waveobs and has_flux: break if has_waveobs and has_flux: target_table = table break if has_waveobs and has_flux: break if target_table is not None: spectrum = np.recarray((len(target_table.array), ), dtype=[('waveobs', float), ('flux', float), ('err', float)]) if 'waveobs' in list(target_table.array.dtype.fields.keys()): spectrum['waveobs'] = target_table.array['waveobs'] elif 'wave' in list(target_table.array.dtype.fields.keys()): spectrum['waveobs'] = target_table.array['wave'] else: # the first column spectrum['waveobs'] = target_table.array[ target_table.array.dtype.names[0]] if 'flux' in list(target_table.array.dtype.fields.keys()): spectrum['flux'] = target_table.array['flux'] else: # the second column spectrum['flux'] = target_table.array[ target_table.array.dtype.names[1]] if 'err' in list(target_table.array.dtype.fields.keys()): spectrum['err'] = target_table.array['err'] elif 'error' in list(target_table.array.dtype.fields.keys()): spectrum['err'] = target_table.array['error'] elif 'errors' in list(target_table.array.dtype.fields.keys()): spectrum['err'] = target_table.array['errors'] if 'sigma' in list(target_table.array.dtype.fields.keys()): spectrum['err'] = target_table.array['sigma'] elif len(table.fields) >= 3: # the third column if exists spectrum['err'] = target_table.array[ target_table.array.dtype.names[2]] else: spectrum['err'] = 0.0 if target_table is None or spectrum is None: raise Exception("Table not compatible") return spectrum def __read_votable(self, url): votable = None if url.startswith('http://'): u = urllib.request.urlopen(url) tmp = tempfile.NamedTemporaryFile(mode="wt", suffix=".xml", delete=False, encoding='utf-8') tmp.write(u.read()) tmp.close() #print tmp.name votable = parse(tmp.name, pedantic=False) os.remove(tmp.name) elif url.startswith('file://localhost/'): filename = url[17:] if filename[0] != "/": filename = "/" + filename votable = parse(filename, pedantic=False) else: raise Exception("Unkown URL") return votable def __read_vofits(self, url): spectrum = None #print url if url.startswith('http://localhost/'): u = urllib.request.urlopen(url) tmp = tempfile.NamedTemporaryFile(mode="wt", suffix=".xml", delete=False, encoding='utf-8') tmp.write(u.read()) tmp.close() #print tmp.name hdulist = pyfits.open(tmp.name) os.remove(tmp) elif url.startswith('file://localhost/'): filename = url[17:] if filename[0] != "/": filename = "/" + filename hdulist = pyfits.open(filename) else: raise Exception("Unkown URL") # Find hdu containing data target_hdu = None for hdu in hdulist: if hdu.data is not None and len(hdu.data) > 0 and len( list(hdu.data.dtype.fields.keys())) >= 2: target_hdu = hdu spectrum = np.recarray((len(target_hdu.data), ), dtype=[('waveobs', float), ('flux', float), ('err', float)]) # the first column spectrum['waveobs'] = target_hdu.data[target_hdu.data.dtype.names[0]] # the second column spectrum['flux'] = target_hdu.data[target_hdu.data.dtype.names[1]] if len(list(hdu.data.dtype.fields.keys())) >= 3: # the third column if exists spectrum['err'] = target_hdu.data[target_hdu.data.dtype.names[2]] else: spectrum['err'] = 0.0 if target_hdu is None or spectrum is None: raise Exception("FITS not compatible") return spectrum def __spectrum_to_vofits(self, spectrum): t = pyfits.new_table(spectrum) t.header['TTYPE1'] = "WAVELENGTH" t.header['TTYPE2'] = "FLUX" t.header['TTYPE3'] = "SIGMA" fits = pyfits.HDUList(pyfits.PrimaryHDU()) fits.append(t) return fits def __spectrum_to_votable(self, spectrum): # Create a new VOTable file... votable = VOTableFile() # ...with one resource... resource = Resource() votable.resources.append(resource) # ... with one table table = Table(votable) resource.tables.append(table) # Define some fields waveobs = Field(votable, name="waveobs", datatype="double", unit="nm", ucd="em.wl") flux = Field(votable, name="flux", datatype="double", unit="Jy", ucd="phot.flux") err = Field(votable, name="err", datatype="double", ucd="stat.error;phot.flux") table.fields.extend([waveobs, flux, err]) table.groups.extend([Group([flux, err])]) #import ipdb #ipdb.set_trace() # Now, use those field definitions to create the numpy record arrays, with # the given number of rows table.create_arrays(len(spectrum)) # Now table.array can be filled with data table.array['waveobs'] = spectrum['waveobs'] table.array['flux'] = spectrum['flux'] table.array['err'] = spectrum['err'] #votable.set_all_tables_format('binary') # VOSpec does not understand binary format return votable ########### [end] File operations def get_subscribers(self): ids = [] names = [] as_tables = [] try: if self.samp_client.is_connected(): subscribers1 = self.samp_client.get_subscribed_clients( "table.load.votable") subscribers2 = self.samp_client.get_subscribed_clients( "spectrum.load.ssa-generic") subscribers = dict( list(subscribers1.items()) + list(subscribers2.items())) for subscriber in list(subscribers.keys()): metadata = self.samp_client.get_metadata(subscriber) name = "" if 'samp.name' in metadata: name = metadata['samp.name'] ids.append(subscriber) names.append(name) # Prefered form: "spectrum.load.ssa-generic" if subscriber in list(subscribers2.keys()): as_tables.append(False) else: as_tables.append(True) # If there are repeated names, add the identifier as a prefix uniq_names, uniq_names_index = np.unique(names, return_index=True) if len(names) != len(uniq_names): for i in np.arange(len(names)): # Only add a preffix to the repeated application names if i not in uniq_names_index: names[i] += " [" + ids[i] + "]" except Exception: pass return (ids, names, as_tables) # Broadcast a table file to TOPCAT def broadcast_spectrum(self, spectrum, spectrum_name, target_client, target_client_is_name=False, as_table=True, as_fits=False): if as_fits: suffix = ".fits" else: suffix = ".xml" tmp = tempfile.NamedTemporaryFile(mode="wt", suffix=suffix, delete=False, encoding='utf-8') tmp.close() if not as_fits: votable = self.__spectrum_to_votable(spectrum) votable.to_xml(tmp.name) else: fits = self.__spectrum_to_vofits(spectrum) fits.writeto(tmp.name, clobber=True) if as_table: mtype = 'table.load.votable' else: mtype = 'spectrum.load.ssa-generic' metadata = { 'samp.mtype': mtype, 'samp.params': { 'name': spectrum_name, 'table-id': spectrum_name, 'url': 'file://localhost/' + tmp.name } } if target_client_is_name: target_client_name = target_client target_client = self.__get_target_client(target_client_name) response = False if target_client is not None: try: response = self.samp_client.notify(target_client, metadata) logging.info("Spectrum sent via SAMP") except Exception: pass # Do not delete or the target client will not have time to read it #os.remove(tmp.name) return response