class SAMPManager(): __client = None __received = False __params = {} __hub = None __server_name = "frastro server" __server_description = "frastro Web client" __server_description_html = "<h1>frastro Web client</h1>" __author = "Camilo E. Jimenez-Angel" __instritution = "IAC" __email_contact = "*****@*****.**" __url_icon = "http://localhost:8000/static/img/frastro_icon.jpg" def __init__(self, server_name=""): # Instantiate the client and connect to the hub self.__server_name = server_name if server_name != "" else self.__server_name self.connect() def starHubServer(self, web_profile=True): if self.__hub is None: self.__hub = SAMPHubServer(web_profile=web_profile, label=self.__server_name) self.__hub.start() def receive_call(self, private_key, sender_id, msg_id, mtype, params, extra): self.__params = params self.__received = True self.__client.reply(msg_id, { "samp.status": "samp.ok", "samp.result": {} }) def receive_notification(self, private_key, sender_id, mtype, params, extra): self.__params = params self.__received = True def bind_to_server(self): # Listen for any instructions to load a table self.__client.bind_receive_call("table.load.votable", self.receive_call) self.__client.bind_receive_notification("table.load.votable", self.receive_notification) def sampMetadata(self): meta = { "samp.name": self.__server_name, "samp.description.text": self.__server_description, "samp.description.html": self.__server_description_html, "samp.icon.url": self.__url_icon, "author.affiliation": self.__instritution, "author.name": self.__author, "author.email": self.__email_contact } return meta def getRegisteredClients(self): clients = self.__client.get_registered_clients() run_clients = {} for client in clients: client_data = self.getMetadata(client) run_clients[client] = client_data return run_clients def getMetadata(self, client_id): return self.__client.get_metadata(client_id) def sendTable(self, message, recipient="all"): if recipient == "all": self.__client.notify_all(message) else: self.__client.notify(recipient, message) def connect(self): self.__client = SAMPIntegratedClient(name=self.__server_name, metadata=self.sampMetadata()) self.__client.connect() self.bind_to_server() def sendImage(self, params, id=""): message = {} message["samp.mtype"] = "image.load.fits" message["samp.params"] = params if id == "" or id == "all": self.__client.notify_all(message) else: self.__client.notify(recipient_id=id, message=message) def sendMessage(self, params, id=""): message = {} message["samp.mtype"] = "table.load.votable" message["samp.params"] = params if id == "" or id == "all": self.__client.notify_all(message) else: self.__client.notify(recipient_id=id, message=message) def disconnect(self): self.__client.disconnect() def __del__(self): self.disconnect()
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
class SAMPState(State): status = CallbackProperty('Not connected to SAMP Hub') connected = CallbackProperty(False) clients = CallbackProperty([]) highlight_is_selection = CallbackProperty(False) def __init__(self): super(SAMPState, self).__init__() self.hub = SAMPHubServer() self.client = SAMPIntegratedClient() self.add_callback('connected', self.on_connected) def start_samp(self): if not self.client.is_connected: try: self.client.connect() except SAMPHubError: try: self.hub.start() self.client.connect() except Exception: self.connected = False self.status = 'Could not connect to Hub' else: self.connected = True self.status = 'Connected to (glue) SAMP Hub' except: self.connected = False self.status = 'Could not connect to Hub' else: self.connected = True self.status = 'Connected to SAMP Hub' def stop_samp(self): if self.client.is_connected: self.client.disconnect() if self.hub.is_running: self.hub.stop() self.connected = False self.status = 'Not connected to SAMP Hub' def on_connected(self, *args): if self.connected: metadata = { 'author.email': '*****@*****.**', 'author.name': 'Thomas Robitaille', 'home.page': 'http://www.glueviz.org', 'samp.description.text': 'Multi-dimensional linked data exploration', 'samp.documentation.url': 'http://www.glueviz.org', 'samp.icon.url': 'file://' + ICON_PATH, 'samp.name': 'glueviz', 'glue.version': glue_version } self.client.declare_metadata(metadata) self.on_client_change() def on_client_change(self): clients = [] for client in self.client.get_registered_clients(): metadata = self.client.get_metadata(client) clients.append((client, metadata.get('samp.name', client))) self.clients = clients def send_data(self, layer=None, client=None): filename = tempfile.mktemp() message = {} message["samp.params"] = {} if isinstance(layer, Data): if layer.ndim == 1: table = data_to_astropy_table(layer) table.write(filename, format='votable') message["samp.mtype"] = "table.load.votable" if 'samp-table-id' not in layer.meta: layer.meta['samp-table-id'] = layer.label message["samp.params"]['table-id'] = layer.meta[ 'samp-table-id'] elif layer.ndim == 2: fits_writer(filename, layer) message["samp.mtype"] = "image.load.fits" if 'samp-image-id' not in layer.meta: layer.meta['samp-image-id'] = layer.label message["samp.params"]['image-id'] = layer.meta[ 'samp-image-id'] else: return message["samp.params"]['name'] = layer.label message["samp.params"]['url'] = 'file://' + os.path.abspath( filename) else: message['samp.mtype'] = 'table.select.rowList' if layer.ndim == 1: message["samp.params"]['table-id'] = layer.data.meta[ 'samp-table-id'] message["samp.params"]['row-list'] = np.nonzero( layer.to_mask())[0].astype(str).tolist() else: return if client is None: self.client.notify_all(message) else: # Make sure client is subscribed otherwise an exception is raised subscriptions = self.client.get_subscriptions(client) for mtype in subscriptions: if fnmatch(message['samp.mtype'], mtype): self.client.notify(client, message) return else: return