diff --git a/src/fftools/python/display/FireflyClient.py b/src/fftools/python/display/FireflyClient.py deleted file mode 100644 index fc28ecea3f..0000000000 --- a/src/fftools/python/display/FireflyClient.py +++ /dev/null @@ -1,543 +0,0 @@ -__author__ = 'zhang' - -from ws4py.client.threadedclient import WebSocketClient -import requests -import webbrowser -import json -import time -import socket - - -class FireflyClient(WebSocketClient): - # class variables - # serverEvents = {'name': ['EVT_CONN_EST', 'SvrBackgroundReport', 'WindowResize'], - # 'scope': ['SELF', 'CHANNEL'], - # 'dataType': ['STRING', 'JSON', 'BG_STATUS'], - # 'data': ['channel'] - # } - - fftoolsCmd = '/firefly/sticky/CmdSrv' - myLocalhost = 'localhost:8080' - ALL = 'ALL_EVENTS_ENABLED' - - # for serializing the RangeValues object - modify when new stretch types added - stretchTypeDict={'percent':88,'absolute':90,'zscale':91,'sigma':92} - stretchAlgorithmDict={'linear':44, 'log':45,'loglog':46,'equal':47,'squared':48, 'sqrt':49} - - - #the constructor, define instance variables for the object - def __init__(self, host=myLocalhost, channel=None): - if host.startswith('http://'): - host = host[7:] - self.thisHost = host - url = 'ws://%s/firefly/sticky/firefly/events' % host #web socket url - if channel: - url+= '?channelID=%s' % channel - WebSocketClient.__init__(self, url) - self.urlRoot = 'http://' + host + self.fftoolsCmd - self.urlBW = 'http://' + self.thisHost + '/firefly/firefly.html;wsch=' - self.listeners = {} - self.channel = channel - self.session = requests.Session() - print 'websocket url:%s' % url - self.connect() - - - # def opened(self): - # print ("Opening websocket connection to fftools") - - # def closed(self, code, reason=None): - # print ("Closed down", code, reason) - - - def handleEvent(self, ev): - for callback, eventIDList in self.listeners.items(): - if ev['name'] in eventIDList or FireflyClient.ALL in eventIDList: - callback(ev) - - - #overridde the superclass's method - def received_message(self, m): - ev = json.loads(m.data.decode('utf8')) - eventName = ev['name'] - - if eventName == 'EVT_CONN_EST': - try: - connInfo = ev['data'] - if self.channel is None: - self.channel = connInfo['channel'] - connID = '' - if 'connID' in connInfo: - connID = connInfo['connID'] - seinfo = self.channel - if (len(connID) ) > 0: - seinfo = connID + '_' + seinfo - - # print ("Connection established: " + seinfo) - self.session.cookies['seinfo'] = seinfo - #self.onConnected(self.channel) - except: - print ('from callback exception: ') - print (m) - else: - # print "call calling handleEvnet" - # print sevent - self.handleEvent(ev) - - def sendURLAsGet(self,url): - response= self.session.get(url) - # print response.text - status = json.loads(response.text) - return status[0] - - def isPageConnected(self): - ip=socket.gethostbyname(socket.gethostname()) - url = self.urlRoot + '?cmd=pushAliveCheck&ipAddress=%s' % ip - retval= self.sendURLAsGet(url) - return retval['active'] - - # def onConnected(self, channel): - # #open the browser - # url = 'http://' + self.thisHost + '/fftools/app.html?id=Loader&channelID=' + channel - # webbrowser.open('http://localhost:8080/fftools/app.html?id=Loader&channelID=' + channel) - # webbrowser.open(url) - - - @staticmethod - def createRV(stretchType, lowerValue, upperValue, algorithm, - zscaleContrast=25, zscaleSamples=600, zscaleSamplesPerLine=120): - retval= None - ffc= FireflyClient - st= stretchType.lower() - a= algorithm.lower() - if st in ffc.stretchTypeDict and a in ffc.stretchAlgorithmDict: - retval='%d,%f,%d,%f,%d,%d,%d,%d' % \ - (ffc.stretchTypeDict[st], lowerValue, - ffc.stretchTypeDict[st], upperValue, - ffc.stretchAlgorithmDict[a], - zscaleContrast, zscaleSamples, zscaleSamplesPerLine) - return retval - - - def makePidParam(self,plotId): - return ','.join(plotId) if type(plotId) is list else plotId - - -#----------------------------------------------------------------- -#----------------------------------------------------------------- -# Public API Begins -#----------------------------------------------------------------- -#----------------------------------------------------------------- - - def addListener(self, callback, name=ALL): - """ - Add a function to listen for events on the firefly client - :param callback: set the function to be called when a event happens on the firefly client - :param name: set the name of the events, default to all events - :return: - """ - if callback not in self.listeners.keys(): - self.listeners[callback]= [] - if name not in self.listeners[callback]: - self.listeners[callback].append(name) - - - def removeListener(self, callback, name=ALL): - """ - remove a callback - :param callback: a previous set function that is to be removed - :param name: set the name of the event, default to all events - :return: - """ - if callback in self.listeners.keys(): - if name in self.listeners[callback]: - self.listeners[callback].remove(name) - if len(self.listeners[callback])==0: - self.listeners.pop(callback) - - - def waitForEvents(self): - """ - Pause and do not exit. Wait over events from the server. - This is optional. You should not use this method in ipython notebook - Event will get called anyway. - """ - WebSocketClient.run_forever(self) - - - def getFireflyUrl(self, mode=None, channel=None): - """ - Get URL to Firefly Tools viewer and the channel set. Normally this method - will be call without any parameters. - :param mode: mode maybe one of 'full', or 'minimal'. Defaults to minimal. - :param channel: a different channel string than the default - :return: the full url string - """ - if not channel: - channel = self.channel - - url = 'http://' + self.thisHost + '/firefly/firefly.html?id=Loader&channelID=' - if mode == "full" : - url = self.urlBW - return url + channel - - - def launchBrowser(self, url=None, channel=None, force=False): - """ - Launch a browsers with the Firefly Tools viewer and the channel set. Normally this method - will be call without any parameters. - :param url: the url, overriding the default - :param channel: a different channel than the default - :return: the channel - """ - if not channel: - channel = self.channel - if not url : - url=self.urlBW - doOpen= True if force else not self.isPageConnected() - if doOpen: - webbrowser.open(self.getFireflyUrl(url,channel)) - time.sleep(5) # todo: find something better to do than sleeping - return channel - - - def stayConnected(self): - self.ws.run() - - def disconnect(self): - self.close() - - def uploadFile(self, path, preLoad=True): - """ Upload a file to the Firefly Server - :param path: uploaded file can be fits, region, and various types of table files - """ - url = 'http://' + self.thisHost + '/firefly/sticky/Firefly_FileUpload?preload=%s' % preLoad - files = {'file': open(path, 'rb')} - result = self.session.post(url, files=files) - index = result.text.find('$') - return result.text[index:] - - - def uploadFitsData(self, stream): - """ - Upload a FITS file like object to the Firefly server. The method should allows file like data - to be streamed without using a actual file. - :param stream: a file like object - :return: status - """ - url = 'http://' + self.thisHost + '/fftools/sticky/Firefly_FileUpload?preload=true' - dataPack= {'data' : stream} - result = self.session.post(url, files=dataPack) - index = result.text.find('$') - return result.text[index:] - - def uploadTextData(self, stream): - """ - Upload a Text file like object to the Firefly server. The method should allows file like data - to be streamed without using a actual file. - :param stream: a file like object - :return: status - """ - return self.uploadData(stream,'UNKNOWN') - - - def uploadData(self, stream, dataType): - url = 'http://' + self.thisHost + '/fftools/sticky/Firefly_FileUpload?preload=' - url+= 'true&type=FITS' if dataType=='FITS' else 'false&type=UNKNOWN' - dataPack= {'data' : stream} - result = self.session.post(url, files=dataPack) - index = result.text.find('$') - return result.text[index:] - - - def showFitsOLD(self, fileOnServer=None, plotId=None, additionalParams=None): - """ Show a fits image - :param: fileOnServer: the is the name of the file on the server. If you used uploadFile() - then it is the return value of the method. Otherwise it is a file that - firefly has direct read access to. - :param: plotId: the id you assigned to the plot. This is necessary to further control the plot - :param: additionalParam: dictionary of any valid fits viewer plotting parameters, - see firefly/docs/fits-plotting-parameters.md - :return: status of call - """ - url = self.urlRoot + '?cmd=pushFits' - if additionalParams: - url+= '&' + '&'.join(['%s=%s' % (k, v) for (k, v) in additionalParams.items()]) - if plotId: - url+= '&plotId=%s' % plotId - if fileOnServer: - url+= '&file=%s' % fileOnServer - return self.sendURLAsGet(url) - - def showFits(self, fileOnServer=None, plotId=None, additionalParams=None): - """ Show a fits image - :param: fileOnServer: the is the name of the file on the server. If you used uploadFile() - then it is the return value of the method. Otherwise it is a file that - firefly has direct read access to. - :param: plotId: the id you assigned to the plot. This is necessary to further control the plot - :param: additionalParam: dictionary of any valid fits viewer plotting parameters, - see firefly/docs/fits-plotting-parameters.md - :return: status of call - """ - wpRequest= {'plotGroupId':'groupFromPython', - 'GroupLocked':False} - payload= {'wpRequest': wpRequest, - 'useContextModifications':True, - 'viewerId':'DEFAULT_FITS_VIEWER_ID'} - if plotId: - payload['wpRequest'].update({ 'plotId': plotId}) - if fileOnServer: - payload['wpRequest'].update({ 'file': fileOnServer}) - if additionalParams: - payload['wpRequest'].update(additionalParams) - return self.dispatchRemoteAction(self.channel,'ImagePlotCntlr.PlotImage', payload) - - def dispatchRemoteAction(self, channel, actionType, payload): - - action= {'type':actionType, 'payload':payload} - url = self.urlRoot + '?channelID=' + channel + '&cmd=pushAction&action=' - url+= json.dumps(action) - return self.sendURLAsGet(url) - - def showTable(self, fileOnServer, title=None, pageSize=None, isCatalog=True): - """ - Show a table in Firefly - :param fileOnServer: the is the name of the file on the server. If you used uploadFile() - then it is the return value of the method. Otherwise it is a file that - firefly has direct read access to. - :param title: title on table - :param pageSize: how many rows are shown. - :return: status of call - """ - url = self.urlRoot + "?cmd=pushTable" - - if not isCatalog: - url+= '&tblType=table' - if title: - url+= '&Title=%s' % title - if pageSize: - url+= '&pageSize=%s' % str(pageSize) - url+= "&file=%s" % fileOnServer - return self.sendURLAsGet(url) - - def showXYPlot(self, fileOnServer, additionalParams=None): - """ - Show a table in Firefly - :param fileOnServer: the is the name of the file on the server. If you used uploadFile() - then it is the return value of the method. Otherwise it is a file that - firefly has direct read access to. - :param additionalParams: XY Plot Viewer parameters - :return: status of call - """ - url = self.urlRoot + "?cmd=pushXYPlot" - if additionalParams: - url+= '&' + '&'.join(['%s=%s' % (k, v) for (k, v) in additionalParams.items()]) - url+= '&file=%s' % fileOnServer - return self.sendURLAsGet(url) - - - def addExtension(self, extType, title, plotId, extensionId, image=None): - """ - Add an extension to the plot. Extensions are context menus that allows you to extend - what firefly can so when certain actions happen - :param extType: May be 'AREA_SELECT', 'LINE_SELECT', or 'POINT'. todo: 'CIRCLE_SELECT' - :param title: The title that the user sees - :param plotId: The it of the plot to put the extension on - :param extensionId: The id of the extension - :param image: An url of an icon to display the toolbar instead of title - :return: status of call - """ - url = self.urlRoot + "?cmd=pushExt&plotId=%s&id=%s&extType=%s&Title=%s" % (plotId,extensionId,extType,title) - if image: - url+= "&image=%s" % image - return self.sendURLAsGet(url) - - - def zoom(self, plotId, factor): - """ - Zoom the image - :param plotId: plotId to which this region should be added, parameter may be string or a list of strings. - :param factor: number, zoom factor for the image - :return: - """ - url = self.urlRoot + "?cmd=pushZoom&plotId=%s&zoomFactor=%.3f" % (self.makePidParam(plotId),factor) - return self.sendURLAsGet(url) - - - def pan(self, plotId, x, y): - """ - Pan or scroll the image to center on the image coordinates passed - :param plotId: plotId to which this region should be added, parameter may be string or a list of strings. - :param x: number, new center x position to scroll to - :param y: number, new center y position to scroll to - :return: - """ - url = self.urlRoot + "?cmd=pushPan&plotId=%s&scrollX=%d&scrollY=%d" % (self.makePidParam(plotId),x,y) - return self.sendURLAsGet(url) - - def stretch(self, plotId, serializedRV): - """ - Change the stretch of the image - :param plotId: plotId to which this region should be added, parameter may be string or a list of strings. - :param serializedRV: the range values parameter - :return: - """ - url = self.urlRoot + "?cmd=pushRangeValues&plotId=%s&rangeValues=%s" % (self.makePidParam(plotId),serializedRV) - return self.sendURLAsGet(url) - - - #----------------------------------------------------------------- - # Region Stuff - #----------------------------------------------------------------- - - - def overlayRegion(self, fileOnServer, title=None, regionLayerId=None, plotId=None): - """ - Overlay a region on the loaded FITS images - :param fileOnServer: the is the name of the file on the server. If you used uploadFile() - then it is the return value of the method. Otherwise it is a file that - firefly has direct read access to. - :param title: title of the region file - :param regionLayerId: id of layer to add - :param plotId: plotId to which this region should be added, parameter may be string or a list of strings. - If non the tne region is overlay on all plots - :return: status of call - """ - url = self.urlRoot + "?cmd=pushRegion&file=%s" % fileOnServer - if title: - url+= '&Title=%s' % title - if regionLayerId: - url+= '&id=%s' % regionLayerId - if plotId: - url+= '&plotId=%s' % self.makePidParam(plotId) - return self.sendURLAsGet(url) - - - def removeRegion(self, regionLayerId, plotId=None): - """ - Overlay a region on the loaded FITS images - :param regionLayerId: regionLayer to remove - :return: status of call - """ - url= self.urlRoot +"?cmd=pushRemoveRegion&id=%s" % regionLayerId - if plotId: - url+= '&plotId=%s' % self.makePidParam(plotId) - return self.sendURLAsGet(url) - - - def overlayRegionData(self, regionData, regionLayerId, title=None, plotId=None): - """ - Overlay a region on the loaded FITS images. Note: the plotId is ignored if you have already put this - region id on a plot. In that case it just add the regions to the existing id. - :param regionData: a list of region entries - :param regionLayerId: id of region overlay to create or add too - :param title: title of the region file - :param plotId: plotId to which this region should be added, parameter may be string or a list of strings - If non the tne region is overlay on all plots - :return: status of call - """ - url = self.urlRoot + "?cmd=pushRegionData&id=%s" % regionLayerId - if title: - url+= '&Title=%s' % title - if plotId: - url+= '&plotId=%s' % self.makePidParam(plotId) - response = self.session.post(url, data={'ds9RegionData' : '['+"--STR--".join(regionData)+']'}) - status = json.loads(response.text) - return status[0] - - - - def removeRegionData(self, regionData, regionLayerId): - """ - Remove the specified region entries - :param regionData: a list of region entries - :param regionLayerId: id of region to remove entries from - :return: status of call - """ - url = self.urlRoot + "?cmd=pushRemoveRegionData&id=%s" % regionLayerId - response = self.session.post(url, - data={'ds9RegionData' : '['+"--STR--".join(regionData)+']'}) - status = json.loads(response.text) - return status[0] - - - def addMask(self, maskId,bitNumber,imageNumber,color,plotId,bitDesc=None,fileOnServer=None): - """ - Add a mask layer - :param maskId: id of mask - :param bitNumber: bitNumber of the mask to overlay - :param imageNumber: imageNumber of the mask layer - :param color: color as an html color (eg. #FF0000 (red) #00FF00 (green) - :param plotId: plot id to overlay the mask on - :param bitDesc: (optional) description of the mask layer - :param fileOnServer: (optional) file to get the mask from, if None then get it from the original file - :return: status of call - """ - url = self.urlRoot + "?cmd=pushAddMask" - - params= { - 'id' : maskId, - 'bitNumber' : bitNumber, - 'color' : color, - 'plotId' : plotId, - 'imageNumber' : imageNumber, - } - if bitDesc: - params['bitDesc']= bitDesc - if fileOnServer: - params['fileKey']= fileOnServer - response = self.session.post(url, data=params) - status = json.loads(response.text) - return status[0] - # return self.sendURLAsGet(url) - - - def removeMask(self, maskId): - """ - Remove a mask layer - :param maskId: id of mask - :return: status of call - """ - url = self.urlRoot + "?cmd=pushRemoveMask&id=%s" % maskId - return self.sendURLAsGet(url) - - - #----------------------------------------------------------------- - # Range Values - #----------------------------------------------------------------- - - @staticmethod - def createRangeValuesStandard(algorithm, stretchType='Percent', lowerValue=1, upperValue=99): - """ - :param algorithm: must be 'Linear', 'Log','LogLog','Equal','Squared', 'Sqrt' - :param stretchType: must be 'Percent','Absolute','Sigma' - :param lowerValue: number, lower end of stretch - :param upperValue: number, upper end of stretch - :return: a serialized range values string - """ - retval= FireflyClient.createRV(stretchType,lowerValue,upperValue,algorithm,25,600,120) - ffc= FireflyClient - if not retval: - t= stretchType if stretchType.lower() in ffc.stretchAlgorithmDict else 'percent' - a= algorithm if algorithm.lower() in ffc.stretchAlgorithmDict else 'linear' - retval= FireflyClient.createRV(t,1,99,a) - return retval - - - @staticmethod - def createRangeValuesZScale(algorithm, zscaleContrast=25, zscaleSamples=600, zscaleSamplesPerLine=120): - """ - :param algorithm: must be 'Linear', 'Log','LogLog','Equal','Squared', 'Sqrt' - :param zscaleContrast: zscale contrast - :param zscaleSamples: zscale samples - :param zscaleSamplesPerLine: zscale sample per line - :return: a serialized range values string - """ - retval= FireflyClient.createRV('zscale',1,2,algorithm,zscaleContrast, zscaleSamples, zscaleSamplesPerLine) - if not retval: - a= algorithm if algorithm.lower() in FireflyClient.stretchAlgorithmDict else 'linear' - retval= FireflyClient.createRV('zscale',1,2,a,25,600,120) - return retval - - - diff --git a/src/firefly/java/edu/caltech/ipac/firefly/data/ServerParams.java b/src/firefly/java/edu/caltech/ipac/firefly/data/ServerParams.java index bc94a6be3a..93cf69677f 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/data/ServerParams.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/data/ServerParams.java @@ -144,24 +144,8 @@ public class ServerParams { public static final String JSON_DEEP= "jsonDeep"; public static final String ACTION= "action"; - public static final String VIS_PUSH_CREATE_ID= "createID"; - public static final String VIS_PUSH_FITS= "pushFits"; - public static final String VIS_PUSH_REG= "pushRegion"; - public static final String VIS_PUSH_REMOVE_REG= "pushRemoveRegion"; - public static final String VIS_PUSH_REG_DATA= "pushRegionData"; - public static final String VIS_PUSH_REMOVE_REG_DATA= "pushRemoveRegionData"; - public static final String VIS_PUSH_TABLE= "pushTable"; - public static final String VIS_PUSH_XYPLOT= "pushXYPlot"; - public static final String VIS_PUSH_EXT= "pushExt"; - public static final String VIS_QUERY_ACTION= "queryAction"; - public static final String VIS_PUSH_WPR= "pushWPR"; public static final String VIS_PUSH_ALIVE_CHECK= "pushAliveCheck"; public static final String VIS_PUSH_ALIVE_COUNT= "pushAliveCount"; - public static final String VIS_PUSH_PAN= "pushPan"; - public static final String VIS_PUSH_ZOOM= "pushZoom"; - public static final String VIS_PUSH_RANGE_VALUES= "pushRangeValues"; - public static final String VIS_PUSH_ADD_MASK= "pushAddMask"; - public static final String VIS_PUSH_REMOVE_MASK= "pushRemoveMask"; public static final String VIS_PUSH_ACTION= "pushAction"; diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/ServerCommandAccess.java b/src/firefly/java/edu/caltech/ipac/firefly/server/ServerCommandAccess.java index ee4191e45c..37a04b3e70 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/ServerCommandAccess.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/ServerCommandAccess.java @@ -133,26 +133,9 @@ private static void initCommand() { _cmdMap.put(ServerParams.RESOLVE_NAME, new ResolveServerCommands.ResolveName()); -// _cmdMap.put(ServerParams.VIS_PUSH_CREATE_ID, new PushCommands.GetPushID()); - _cmdMap.put(ServerParams.VIS_PUSH_FITS, new PushCommands.PushFITS()); - _cmdMap.put(ServerParams.VIS_PUSH_REG, new PushCommands.PushRegionFile()); - _cmdMap.put(ServerParams.VIS_PUSH_REMOVE_REG, new PushCommands.PushRemoveRegionFile()); - _cmdMap.put(ServerParams.VIS_PUSH_REG_DATA, new PushCommands.PushRegionData()); - _cmdMap.put(ServerParams.VIS_PUSH_REMOVE_REG_DATA, new PushCommands.PushRemoveRegionData()); - _cmdMap.put(ServerParams.VIS_PUSH_TABLE, new PushCommands.PushTable()); - _cmdMap.put(ServerParams.VIS_PUSH_XYPLOT, new PushCommands.PushXYPlot()); - _cmdMap.put(ServerParams.VIS_PUSH_EXT, new PushCommands.PushExtension()); _cmdMap.put(ServerParams.VIS_PUSH_ALIVE_CHECK, new PushCommands.PushAliveCheck()); _cmdMap.put(ServerParams.VIS_PUSH_ALIVE_COUNT, new PushCommands.PushAliveCount()); - _cmdMap.put(ServerParams.VIS_PUSH_PAN, new PushCommands.PushPan()); - _cmdMap.put(ServerParams.VIS_PUSH_ZOOM, new PushCommands.PushZoom()); - _cmdMap.put(ServerParams.VIS_PUSH_RANGE_VALUES, new PushCommands.PushRangeValues()); - _cmdMap.put(ServerParams.VIS_PUSH_ADD_MASK, new PushCommands.PushAddMask()); - _cmdMap.put(ServerParams.VIS_PUSH_REMOVE_MASK, new PushCommands.PushRemoveMask()); _cmdMap.put(ServerParams.VIS_PUSH_ACTION, new PushCommands.PushAction()); -// _cmdMap.put(ServerParams.VIS_QUERY_ACTION, new PushCommands.QueryAction()); -// _cmdMap.put(ServerParams.VIS_PUSH_WPR, new PushCommands.PushFITS()); - // maybe temporary _cmdMap.put(ServerParams.STATIC_JSON_DATA, new JsonDataCommands.StaticJsonData()); diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/vispush/PushJob.java b/src/firefly/java/edu/caltech/ipac/firefly/server/vispush/PushJob.java index 7da86e8a0f..badeff84b1 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/vispush/PushJob.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/vispush/PushJob.java @@ -33,124 +33,6 @@ private static void fireEvent(String data, Name evName) { } - public static boolean pushFits(WebPlotRequest wpr) { - fireEvent(wpr.toString(), Name.PUSH_WEB_PLOT_REQUEST); - return true; - } - - public static boolean pushExtension(String sreqId, - String plotId, - String extType, - String title, - String image, - String toolTip) { - ServerRequest r = new ServerRequest(sreqId); - r.setParam(ServerParams.EXT_TYPE, extType); - r.setParam(ServerParams.TITLE, title); - r.setParam(ServerParams.PLOT_ID, plotId); - if (image != null) r.setParam(ServerParams.IMAGE, image); - if (toolTip != null) r.setParam(ServerParams.IMAGE, toolTip); - fireEvent(r.toString(), Name.PUSH_FITS_COMMAND_EXT); - return true; - } - - public static boolean pushPan(String plotId, - String xStr, - String yStr) { - ServerRequest r = new ServerRequest(plotId); - r.setParam(ServerParams.SCROLL_X, xStr); - r.setParam(ServerParams.SCROLL_Y, yStr); - fireEvent(r.toString(), Name.PUSH_PAN); - return true; - } - - public static boolean pushZoom(String plotId, String zFactStr) { - ServerRequest r = new ServerRequest(plotId); - r.setParam(ServerParams.ZOOM_FACTOR, zFactStr); - fireEvent(r.toString(), Name.PUSH_ZOOM); - return true; - } - - public static boolean pushRangeValues(String plotId, RangeValues rv) { - ServerRequest r = new ServerRequest(plotId); - r.setParam(ServerParams.RANGE_VALUES, rv.toString()); - fireEvent(r.toString(), Name.PUSH_RANGE_VALUES); - return true; - } - - - public static boolean pushTable(ServerRequest req) { - fireEvent(req.toString(), Name.PUSH_TABLE_FILE); - return true; - } - - public static boolean pushXYPlot(ServerRequest r) { - fireEvent(r.toString(), Name.PUSH_XYPLOT_FILE); - return true; - } - - - - //================================ - //========== Region Stuff - //================================ - - public static boolean pushRegionFile(String fileName, String id, String plotIdAry) { - ServerRequest r = new ServerRequest(id); - r.setParam(ServerParams.FILE, fileName); - if (plotIdAry!=null) r.setParam(ServerParams.PLOT_ID, plotIdAry); - fireEvent(r.toString(), Name.PUSH_REGION_FILE); - return true; - } - - public static boolean pushRemoveRegionFile(String id, String plotIdAry) { - ServerRequest r = new ServerRequest(id); - if (plotIdAry!=null) r.setParam(ServerParams.PLOT_ID, plotIdAry); - fireEvent(r.toString(), Name.PUSH_REMOVE_REGION_FILE); - return true; - } - - public static boolean pushRemoveRegionData(String id, String data) { - ServerRequest r = new ServerRequest(id); - r.setParam(ServerParams.DS9_REGION_DATA, data); - fireEvent(r.toString(), Name.REMOVE_REGION_DATA); - return true; - } - - - public static boolean pushRegionData(String title, String id, String data, String plotIdAry) { - ServerRequest r = new ServerRequest(id); - r.setParam(ServerParams.TITLE, title); - r.setParam(ServerParams.DS9_REGION_DATA, data); - if (plotIdAry!=null) r.setParam(ServerParams.PLOT_ID, plotIdAry); - fireEvent(r.toString(), Name.PUSH_REGION_DATA); - return true; - } - - public static boolean pushAddMask(String maskId, - int bitNumber, - int imageNumber, - String color, - String bitDesc, - String fileKey, - String plotIdStr) { - ServerRequest r = new ServerRequest(maskId); - r.setParam(ServerParams.BIT_NUMBER,bitNumber+""); - r.setParam(ServerParams.IMAGE_NUMBER,imageNumber+""); - r.setParam(ServerParams.COLOR,color); - r.setParam(ServerParams.BIT_DESC,bitDesc); - r.setParam(ServerParams.FILE,fileKey); - r.setParam(ServerParams.PLOT_ID,plotIdStr); - fireEvent(r.toString(), Name.PUSH_ADD_MASK); - return true; - } - - - public static boolean pushRemoveMask(String maskId) { - ServerRequest r = new ServerRequest(maskId); - fireEvent(r.toString(), Name.PUSH_REMOVE_MASK); - return true; - } /** * Get the active count. If 0 keep trying for try time milliseconds. diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PushCommands.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PushCommands.java index 3d17768d5d..4e104a2c2d 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PushCommands.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PushCommands.java @@ -36,278 +36,6 @@ public boolean getCanCreateJson() { } } - public static class PushFITS extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - SrvParam sp= new SrvParam(paramMap); - String file= sp.getOptional(ServerParams.FILE); - String plotID= sp.getOptional(ServerParams.PLOT_ID); - WebPlotRequest wpr; - if (file!=null) { - wpr= WebPlotRequest.makeFilePlotRequest(file); - } - else { - wpr= new WebPlotRequest(); - } - for(Map.Entry entry : paramMap.entrySet()) { - wpr.setParam(entry.getKey(),entry.getValue()[0]); - } - - if (plotID!=null) { - wpr.setPlotId(plotID); - } - boolean success= PushJob.pushFits(wpr); - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - map.put("file",file); - return outJson.toJSONString(); - } - } - - - public static class PushPan extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - SrvParam sp= new SrvParam(paramMap); - String plotID= sp.getRequired(ServerParams.PLOT_ID); - String xStr= sp.getRequired(ServerParams.SCROLL_X); - String yStr= sp.getRequired(ServerParams.SCROLL_Y); - - - boolean success= PushJob.pushPan(plotID,xStr,yStr); - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - return outJson.toJSONString(); - } - } - - public static class PushZoom extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - SrvParam sp= new SrvParam(paramMap); - String plotID= sp.getRequired(ServerParams.PLOT_ID); - String zoomFactStr= sp.getRequired(ServerParams.ZOOM_FACTOR); - - - boolean success= PushJob.pushZoom(plotID,zoomFactStr); - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - return outJson.toJSONString(); - } - } - - public static class PushRangeValues extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - SrvParam sp= new SrvParam(paramMap); - String plotID= sp.getRequired(ServerParams.PLOT_ID); - String rvString= sp.getRequired(ServerParams.RANGE_VALUES); - RangeValues rv= RangeValues.parse(rvString); - - - boolean success= false; - if (rv!=null) { - success= PushJob.pushRangeValues(plotID,rv); - } - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - if (!success) map.put("reason", "could not parse range values string"); - return outJson.toJSONString(); - } - } - - public static class PushExtension extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - SrvParam sp= new SrvParam(paramMap); - String id= sp.getRequired(ServerParams.ID); - String plotId= sp.getRequired(ServerParams.PLOT_ID); - String extType= sp.getRequired(ServerParams.EXT_TYPE); - String title= sp.getRequired(ServerParams.TITLE); - String image= sp.getOptional(ServerParams.IMAGE); - String toolTip= sp.getOptional(ServerParams.TOOL_TIP); - boolean success= PushJob.pushExtension(id, plotId, extType, title, image, toolTip); - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - map.put("id", id); - map.put("plotId", plotId); - return outJson.toJSONString(); - } - - } - - public static class PushTable extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - - SrvParam sp= new SrvParam(paramMap); - String file= sp.getRequired(ServerParams.FILE); - ServerRequest req = new ServerRequest(); - for (String p : paramMap.keySet()) { - req.setParam(p, paramMap.get(p)[0]); - } - req.setParam(ServerParams.SOURCE, file); - boolean success= PushJob.pushTable(req); - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - map.put("file", ""); - return outJson.toJSONString(); - } - - } - - public static class PushXYPlot extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - - SrvParam sp= new SrvParam(paramMap); - String file= sp.getRequired(ServerParams.FILE); - ServerRequest req = new ServerRequest(); - for (String p : paramMap.keySet()) { - req.setParam(p, paramMap.get(p)[0]); - } - req.setParam(ServerParams.SOURCE, file); - boolean success= PushJob.pushXYPlot(req); - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - map.put("file", ""); - return outJson.toJSONString(); - } - - } - - - //======================================================================== - //========= Region Stuff - //======================================================================== - - - public static class PushRegionFile extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - - SrvParam sp= new SrvParam(paramMap); - String file= sp.getRequired(ServerParams.FILE); - String id= sp.getOptional(ServerParams.ID); - String plotIDAry= sp.getOptional(ServerParams.PLOT_ID); - boolean success= PushJob.pushRegionFile(file, id, plotIDAry); - - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - map.put("file",file); - return outJson.toJSONString(); - } - - } - - - public static class PushRemoveRegionFile extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - - SrvParam sp= new SrvParam(paramMap); - String id= sp.getRequired(ServerParams.ID); - String plotId= sp.getOptional(ServerParams.PLOT_ID); - boolean success= PushJob.pushRemoveRegionFile(id, plotId); - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - return outJson.toJSONString(); - } - - } - - public static class PushRegionData extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - - SrvParam sp= new SrvParam(paramMap); - String regData= sp.getRequired(ServerParams.DS9_REGION_DATA); - String id= sp.getRequired(ServerParams.ID); - String title= sp.getOptional(ServerParams.TITLE); - String plotIDAry= sp.getOptional(ServerParams.PLOT_ID); - boolean success= PushJob.pushRegionData(title, id, regData, plotIDAry); - - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - return outJson.toJSONString(); - } - - } - - public static class PushRemoveRegionData extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - SrvParam sp= new SrvParam(paramMap); - String regData= sp.getRequired(ServerParams.DS9_REGION_DATA); - String id= sp.getRequired(ServerParams.ID); - boolean success= PushJob.pushRemoveRegionData(id, regData); - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - return outJson.toJSONString(); - } - } - - - public static class PushAddMask extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - SrvParam sp= new SrvParam(paramMap); - - String maskId= sp.getRequired(ServerParams.ID); - int bitNumber= sp.getRequiredInt(ServerParams.BIT_NUMBER); - int imageNumber= sp.getRequiredInt(ServerParams.IMAGE_NUMBER); - String color= sp.getRequired(ServerParams.COLOR); - String bitDesc= sp.getOptional(ServerParams.BIT_DESC); - String fileKey= sp.getOptional(ServerParams.FILE); - String plotIdStr= sp.getRequired(ServerParams.PLOT_ID); - boolean success= PushJob.pushAddMask(maskId,bitNumber,imageNumber,color,bitDesc,fileKey,plotIdStr); - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - return outJson.toJSONString(); - } - } - - public static class PushRemoveMask extends BaseVisPushCommand { - - public String doCommand(Map paramMap) throws Exception { - SrvParam sp= new SrvParam(paramMap); - String maskId= sp.getRequired(ServerParams.ID); - boolean success= PushJob.pushRemoveMask(maskId); - JSONObject map = new JSONObject(); - JSONArray outJson = new JSONArray(); - outJson.add(map); - map.put("success", success); - return outJson.toJSONString(); - - - } - } - - public static class PushAliveCheck extends BaseVisPushCommand { public String doCommand(Map paramMap) throws Exception { @@ -355,9 +83,4 @@ public String doCommand(Map paramMap) throws Exception { } } - - - - - } diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerOps.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerOps.java index 3c9f4944b4..d42327e0f8 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerOps.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerOps.java @@ -1475,7 +1475,8 @@ public static WebPlotResult getFootprintRegion(String fpInfo) { while ((tmpLine = br.readLine()) != null) { tmpLine = tmpLine.trim(); - if (!tmpLine.startsWith("#") && (tmpLine.contains("tag={" + tag))) + if (!tmpLine.startsWith("#") && ((tmpLine.contains("tag={" + tag)) || + (!tmpLine.contains("tag")))) rAsStrList.add(tmpLine); } if (rAsStrList.size() == 0) { diff --git a/src/firefly/js/core/ExternalAccessCntlr.js b/src/firefly/js/core/ExternalAccessCntlr.js index 1ff3cfa0a6..651aeb49b4 100644 --- a/src/firefly/js/core/ExternalAccessCntlr.js +++ b/src/firefly/js/core/ExternalAccessCntlr.js @@ -14,8 +14,7 @@ import {flux} from '../Firefly.js'; const ALL_MPW= 'AllMpw'; const initState= { - extensionList : [], - remoteChannel : null + extensionList : [] }; export function extensionRoot() { return flux.getState()[EXTERNAL_ACCESS_KEY]; } @@ -64,9 +63,11 @@ function reducer(state=initState, action={}) { case EXTENSION_ACTIVATE : retState= state;// todo something break; + case CHANNEL_ACTIVATE : retState= updateChannel(state,action); break; + } return retState; } diff --git a/src/firefly/js/core/ExternalAccessUtils.js b/src/firefly/js/core/ExternalAccessUtils.js index e472357e49..93f04bae6c 100644 --- a/src/firefly/js/core/ExternalAccessUtils.js +++ b/src/firefly/js/core/ExternalAccessUtils.js @@ -7,6 +7,7 @@ import {flux} from '../Firefly.js'; import ExternalAccessCntlr from './ExternalAccessCntlr.js'; import {reportUserAction} from '../rpc/SearchServicesJson.js'; import { parseImagePt, parseWorldPt, parseScreenPt } from '../visualize/Point.js'; +import {getWsChannel} from './messaging/WebSocketClient.js' const EMPTY_ARRAY=[]; @@ -37,7 +38,8 @@ const activate= function(remoteChannel, extension, resultData) { }; export const getRemoteChannel= function() { - return flux.getState()[ExternalAccessCntlr.EXTERNAL_ACCESS_KEY].remoteChannel ; + //return flux.getState()[ExternalAccessCntlr.EXTERNAL_ACCESS_KEY].remoteChannel ; + return getWsChannel(); }; export const getExtensionList= function(testPlotId) { diff --git a/src/firefly/js/data/ServerParams.js b/src/firefly/js/data/ServerParams.js index 2ff4a38f73..4f8d6fc299 100644 --- a/src/firefly/js/data/ServerParams.js +++ b/src/firefly/js/data/ServerParams.js @@ -130,21 +130,8 @@ export const ServerParams = { JSON_DEEP: 'jsonDeep', ACTION: 'action', - VIS_PUSH_CREATE_ID: 'createID', - VIS_PUSH_FITS: 'pushFits', - VIS_PUSH_REG: 'pushRegion', - VIS_PUSH_REMOVE_REG: 'pushRemoveRegion', - VIS_PUSH_REG_DATA: 'pushRegionData', - VIS_PUSH_REMOVE_REG_DATA: 'pushRemoveRegionData', - VIS_PUSH_TABLE: 'pushTable', - VIS_PUSH_EXT: 'pushExt', - VIS_QUERY_ACTION: 'queryAction', - VIS_PUSH_WPR: 'pushWPR', VIS_PUSH_ALIVE_CHECK: 'pushAliveCheck', VIS_PUSH_ALIVE_COUNT: 'pushAliveCount', - VIS_PUSH_PAN: 'pushPan', - VIS_PUSH_ZOOM: 'pushZoom', - VIS_PUSH_RANGE_VALUES: 'pushRangeValues', VIS_PUSH_ACTION: 'pushAction', diff --git a/src/firefly/js/drawingLayers/FootprintTool.js b/src/firefly/js/drawingLayers/FootprintTool.js index 0ba3c56a07..a1149c9036 100644 --- a/src/firefly/js/drawingLayers/FootprintTool.js +++ b/src/firefly/js/drawingLayers/FootprintTool.js @@ -11,17 +11,17 @@ import {PlotAttribute} from '../visualize/WebPlot.js'; import CsysConverter from '../visualize/CsysConverter.js'; import {primePlot, getDrawLayerById} from '../visualize/PlotViewUtil.js'; import {makeFactoryDef} from '../visualize/draw/DrawLayerFactory.js'; -import {MARKER_DISTANCE, ANGLE_UNIT, ROTATE_BOX, OutlineType, findClosestIndex, makeFootprint, +import {MARKER_DISTANCE, ANGLE_UNIT, ROTATE_BOX, OutlineType, getWorldOrImage, findClosestIndex, makeFootprint, lengthSizeUnit, updateFootprintDrawobjAngle, updateFootprintDrawobjText, updateFootprintTranslate, updateFootprintOutline} from '../visualize/draw/MarkerFootprintObj.js'; -import {markerInterval, getCC, getWorldOrImage, cancelTimeoutProcess,} from './MarkerTool.js'; +import {markerInterval, getCC, cancelTimeoutProcess, initMarkerPos} from './MarkerTool.js'; import {getFootprintToolUIComponent} from './FootprintToolUI.jsx'; import ShapeDataObj, {lengthToScreenPixel} from '../visualize/draw/ShapeDataObj.js'; import {getDrawobjArea} from '../visualize/draw/ShapeHighlight.js'; import {clone} from '../util/WebUtil.js'; import {getDS9Region} from '../rpc/PlotServicesJson.js'; import {FootprintFactory} from '../visualize/draw/FootprintFactory.js'; -import {makeViewPortPt, makeWorldPt, makeImagePt} from '../visualize/Point.js'; +import {makeViewPortPt, makeImagePt} from '../visualize/Point.js'; import {get, set, isArray, has, isNil} from 'lodash'; import Enum from 'enum'; @@ -72,8 +72,7 @@ export function footprintCreateLayerActionCreator(rawAction) { var plot = primePlot(visRoot(), pId); if (plot) { - var {x, y} = plot.attributes[PlotAttribute.FIXED_TARGET]; - var wpt = makeWorldPt(x, y); + var wpt = initMarkerPos(plot); showFootprintByTimer(dispatcher, DrawLayerCntlr.FOOTPRINT_CREATE, wpt, regions, pId, FootprintStatus.attached, footprintInterval, drawLayerId, diff --git a/src/firefly/js/drawingLayers/MarkerTool.js b/src/firefly/js/drawingLayers/MarkerTool.js index 965b46c12f..a1c3100ed7 100644 --- a/src/firefly/js/drawingLayers/MarkerTool.js +++ b/src/firefly/js/drawingLayers/MarkerTool.js @@ -11,7 +11,7 @@ import {PlotAttribute} from '../visualize/WebPlot.js'; import CsysConverter from '../visualize/CsysConverter.js'; import {primePlot, getDrawLayerById} from '../visualize/PlotViewUtil.js'; import {makeFactoryDef} from '../visualize/draw/DrawLayerFactory.js'; -import {makeMarker, findClosestIndex, updateFootprintTranslate, updateMarkerSize, +import {getWorldOrImage, makeMarker, findClosestIndex, updateFootprintTranslate, updateMarkerSize, updateFootprintDrawobjText, updateFootprintOutline, lengthSizeUnit, MARKER_DISTANCE, OutlineType} from '../visualize/draw/MarkerFootprintObj.js'; import {getMarkerToolUIComponent} from './MarkerToolUI.jsx'; @@ -39,8 +39,16 @@ export var getCC = (plotId) => { return CsysConverter.make(plot); }; -export var getWorldOrImage = (pt, cc) => (isWorld(cc) ? cc.getWorldCoords(pt) : cc.getImageCoords(pt)); -var isWorld = (cc) => (cc.projection.isSpecified()); +export var initMarkerPos = (plot, cc) => { + var pos = plot.attributes[PlotAttribute.FIXED_TARGET]; + + if (!cc) cc = CsysConverter.make(plot); + + if (!pos) { + pos = makeViewPortPt(cc.viewPort.dim.width / 2, cc.viewPort.dim.height / 2); + } + return getWorldOrImage(pos, cc); +}; var idCnt=0; @@ -66,7 +74,7 @@ export function markerToolCreateLayerActionCreator(rawAction) { var plot = primePlot(visRoot(), pId); if (plot) { var cc = CsysConverter.make(plot); - var wpt = plot.attributes[PlotAttribute.FIXED_TARGET]; + var wpt = initMarkerPos(plot, cc); var size = lengthToArcsec(MARKER_SIZE, cc, ShapeDataObj.UnitType.PIXEL); showMarkersByTimer(dispatcher, DrawLayerCntlr.MARKER_CREATE, [size, size], wpt, pId, diff --git a/src/firefly/js/drawingLayers/RegionPlot.js b/src/firefly/js/drawingLayers/RegionPlot.js index 2748e7053a..c14d40f01c 100644 --- a/src/firefly/js/drawingLayers/RegionPlot.js +++ b/src/firefly/js/drawingLayers/RegionPlot.js @@ -53,7 +53,18 @@ function creator(initPayload) { var dl = DrawLayer.makeDrawLayer( get(initPayload, 'drawLayerId', `${ID}-${idCnt}`), TYPE_ID, get(initPayload, 'title', 'Region Plot'), options, drawingDef, actionTypes, pairs ); - dl.regions = get(initPayload, 'regions', null); + + dl.regionAry = get(initPayload, 'regionAry', null); + dl.dataFrom = get(initPayload, 'dataFrom', 'ds9'); + + /* + if (regionAry) { + dl.regions = get(initPayload, 'dataFrom', 'ds9') === 'json' ? RegionFactory.parseRegionJson(regionAry) : + RegionFactory.parseRegionDS9(regionAry); + } else { + dl.regions = null; + } + */ dl.highlightedRegion = get(initPayload, 'highlightedRegion', null); idCnt++; @@ -151,11 +162,13 @@ function getLayerChanges(drawLayer, action) { switch (action.type) { case DrawLayerCntlr.MODIFY_CUSTOM_FIELD: - if (changes && changes.regions) { + /* + if (changes && changes.regions) { // this should not happen dd[DataTypes.HIGHLIGHT_DATA] = null; dd[DataTypes.DATA] = null; if (drawLayer.highlightedRegion) drawLayer.highlightedRegion = null; - } else if (changes && changes.highlightedRegion) { + } else */ + if (changes && changes.highlightedRegion) { dd[DataTypes.HIGHLIGHT_DATA] = null; if (drawLayer.highlightedRegion) drawLayer.highlightedRegion.highlight = 0; changes.highlightedRegion.highlight = 1; @@ -187,11 +200,11 @@ function getLayerChanges(drawLayer, action) { } function getDrawData(dataType, plotId, drawLayer, action, lastDataRet) { - const {regions, highlightedRegion} = drawLayer; + const {highlightedRegion, regionAry, dataFrom} = drawLayer; switch (dataType) { case DataTypes.DATA: - return isEmpty(lastDataRet) ? plotAllRegions(regions) : lastDataRet; + return isEmpty(lastDataRet) ? plotAllRegions(regionAry, dataFrom, drawLayer) : lastDataRet; case DataTypes.HIGHLIGHT_DATA: return isEmpty(lastDataRet) ? plotHighlightRegion(highlightedRegion) : lastDataRet; } @@ -199,15 +212,20 @@ function getDrawData(dataType, plotId, drawLayer, action, lastDataRet) { } /** * create DrawingObj for all regions - * @param regionAry + * @param regionAry array of region strings + * @param dataFrom from 'json' (server) or 'ds9' (original ds9 description) + * @param dl drawing layer * @returns {*} */ -function plotAllRegions(regionAry) { +function plotAllRegions(regionAry, dataFrom, dl) { if (!regionAry) { return []; } - return drawRegions(regionAry); + dl.regions = (dataFrom === 'json') ? RegionFactory.parseRegionJson(regionAry) : + RegionFactory.parseRegionDS9(regionAry); + + return drawRegions(dl.regions); } diff --git a/src/firefly/js/visualize/DrawLayerCntlr.js b/src/firefly/js/visualize/DrawLayerCntlr.js index 6792e0cb60..13c1e48810 100644 --- a/src/firefly/js/visualize/DrawLayerCntlr.js +++ b/src/firefly/js/visualize/DrawLayerCntlr.js @@ -249,7 +249,7 @@ export function dispatchDetachLayerFromPlot(id,plotId, detachPlotGroup=false, } -export function dispatchCreateRegionLayer(regionId, layerTitle, fileOnServer ='', regionAry=[], plotId = [], dispatcher = flux.process) { +export function dispatchCreateRegionLayer(regionId, layerTitle, fileOnServer='', regionAry=[], plotId = [], dispatcher = flux.process) { dispatcher({type: REGION_CREATE_LAYER, payload: {regionId, fileOnServer, plotId, layerTitle, regionAry}}); } diff --git a/src/firefly/js/visualize/ImagePlotCntlr.js b/src/firefly/js/visualize/ImagePlotCntlr.js index 95e7ca4f68..11e3986a92 100644 --- a/src/firefly/js/visualize/ImagePlotCntlr.js +++ b/src/firefly/js/visualize/ImagePlotCntlr.js @@ -380,10 +380,11 @@ export function dispatchProcessScroll({plotId,scrollPt, dispatcher= flux.process * * Note - function parameter is a single object * @param {string} plotId + * @param { * @param {function} dispatcher only for special dispatching uses such as remote */ -export function dispatchRecenter({plotId, dispatcher= flux.process}) { - dispatcher({type: RECENTER, payload: {plotId} }); +export function dispatchRecenter({plotId, centerPt, dispatcher= flux.process}) { + dispatcher({type: RECENTER, payload: {plotId, centerPt} }); } /** diff --git a/src/firefly/js/visualize/PlotCmdExtension.js b/src/firefly/js/visualize/PlotCmdExtension.js index 5e0dd40ce1..a27e446d67 100644 --- a/src/firefly/js/visualize/PlotCmdExtension.js +++ b/src/firefly/js/visualize/PlotCmdExtension.js @@ -16,24 +16,9 @@ export const LINE_SELECT= 'LINE_SELECT'; export const POINT= 'POINT'; export const NONE= 'NONE'; -export class PlotCmdExtension { - constructor(id, plotId, extType, imageUrl, title, toolTip, callback=null) { - this.id = id; - this.plotId = plotId; - this.imageUrl = imageUrl; - this.title = title; - this.toolTip = toolTip; - this.extType= extType; - this.callback= callback; - } - - //getId() { return this.id; } - //getImageUrl() { return this.imageUrl; } - //getTitle() { return this.title; } - //getToolTip() { return this.toolTip; } - //getExtType() { return extType; } -} - +export var PlotCmdExtension = ({id, plotId, extType, imageUrl, title, toolTip, callback=null}) => { + return {id, plotId, imageUrl, title, toolTip, extType, callback}; +}; export function makeExtActivateData(ext,pv,dlAry) { var plot= primePlot(pv); @@ -41,7 +26,7 @@ export function makeExtActivateData(ext,pv,dlAry) { var data= { id : ext.id, plotId : pv.plotId, - extType : ext.extType, + type: ext.extType, plotState: PlotState.convertToJSON(plot.plotState) }; diff --git a/src/firefly/js/visualize/draw/DrawOp.js b/src/firefly/js/visualize/draw/DrawOp.js index 0f20366eda..86156c86fd 100644 --- a/src/firefly/js/visualize/draw/DrawOp.js +++ b/src/firefly/js/visualize/draw/DrawOp.js @@ -8,7 +8,6 @@ import SelectBox from './SelectBox.js'; import ShapeDataObj from './ShapeDataObj.js'; import FootprintObj from './FootprintObj.js'; import DirectionArrowDrawObj from './DirectionArrowDrawObj.js'; -import FootprintObj from './FootprintObj.js' import MarkerFootprintObj from './MarkerFootprintObj.js'; export var drawTypes= { diff --git a/src/firefly/js/visualize/draw/MarkerFootprintObj.js b/src/firefly/js/visualize/draw/MarkerFootprintObj.js index a11e3516a5..740e6da186 100644 --- a/src/firefly/js/visualize/draw/MarkerFootprintObj.js +++ b/src/firefly/js/visualize/draw/MarkerFootprintObj.js @@ -4,6 +4,7 @@ */ import Point, {makeImagePt, makeScreenPt, makeViewPortPt, makeWorldPt, SimplePt} from '../Point.js'; +import {CoordinateSys} from '../CoordSys.js'; import ShapeDataObj, {lengthToImagePixel, lengthToScreenPixel, lengthToArcsec, makePoint, drawText, makeTextLocationComposite} from './ShapeDataObj.js'; import {POINT_DATA_OBJ, getPointDataobjArea, makePointDataObj, DrawSymbol} from './PointDataObj.js'; @@ -40,7 +41,8 @@ export const OutlineType = new Enum(['original', 'center', 'plotcenter']); const AllHandle = [MARKER_HANDLE.outline, MARKER_HANDLE.resize, MARKER_HANDLE.rotate]; const AllOutline = [OutlineType.original, OutlineType.center, OutlineType.plotcenter]; -export var getWorldOrImage = (pt, cc) => (cc.projection.isSpecified() ? cc.getWorldCoords(pt) : cc.getImageCoords(pt)); +export var getWorldOrImage = (pt, cc) => (cc.projection.isSpecified() ? + cc.getWorldCoords(pt, CoordinateSys.EQ_J2000) : cc.getImageCoords(pt)); function make(sType, style) { const obj= DrawObj.makeDrawObj(); diff --git a/src/firefly/js/visualize/reducer/HandlePlotChange.js b/src/firefly/js/visualize/reducer/HandlePlotChange.js index 806d46d74a..d9f5aad6c9 100644 --- a/src/firefly/js/visualize/reducer/HandlePlotChange.js +++ b/src/firefly/js/visualize/reducer/HandlePlotChange.js @@ -20,8 +20,9 @@ import {primePlot, findPlotGroup, hasGroupLock, getPlotViewById} from '../PlotViewUtil.js'; -import {makeImagePt} from '../Point.js'; +import {makeImagePt, makeWorldPt} from '../Point.js'; import {UserZoomTypes} from '../ZoomUtil.js'; +import CsysConverter from '../CsysConverter.js' //============ EXPORTS =========== @@ -220,32 +221,44 @@ function updateViewSize(state,action) { } function recenter(state,action) { - const {plotId}= action.payload; + const {plotId, centerPt}= action.payload; var {plotGroupAry,plotViewAry}= state; const pv= getPlotViewById(state,plotId); var plotGroup= findPlotGroup(pv.plotGroupId,plotGroupAry); - plotViewAry= applyToOnePvOrGroup(plotViewAry,plotId,plotGroup,recenterPv); + plotViewAry= applyToOnePvOrGroup(plotViewAry,plotId,plotGroup, recenterPv(centerPt)); return clone(state,{plotViewAry}); } /** - * Center on the FIXED_TARGET attribute or the center of the plot - * @param pv a plot view + * Center on the FIXED_TARGET attribute or the center of the plot or specified center point + * @param centerPt center point * @return {{}} a new plot view */ -function recenterPv(pv) { - const plot= primePlot(pv); - if (!plot) return pv; - var centerImagePt; - var wp= plot.attributes[PlotAttribute.FIXED_TARGET]; - if (wp) { - centerImagePt= CCUtil.getImageCoords(plot,wp); - } - else { - centerImagePt= makeImagePt(plot.dataWidth/2, plot.dataHeight/2); - } - return PlotView.updatePlotViewScrollXY(pv, PlotView.findScrollPtForImagePt(pv,centerImagePt)); + +function recenterPv(centerPt) { + return (pv) => { + const plot = primePlot(pv); + if (!plot) return pv; + var centerImagePt; + + if (centerPt) { + if (centerPt.type === Point.IM_PT) { + centerImagePt = makeImagePt(centerPt.x, centerPt.y); + } else { + centerImagePt = makeWorldPt(centetPt.x, centerPt.y); + } + } else { + var wp = plot.attributes[PlotAttribute.FIXED_TARGET]; + if (wp) { + centerImagePt = CCUtil.getImageCoords(plot, wp); + } + else { + centerImagePt = makeImagePt(plot.dataWidth / 2, plot.dataHeight / 2); + } + } + return PlotView.updatePlotViewScrollXY(pv, PlotView.findScrollPtForImagePt(pv, centerImagePt)); + }; } function makeNewPrimePlot(state,action) { @@ -259,8 +272,7 @@ function makeNewPrimePlot(state,action) { function changeGroupLocking(state,action) { var {plotGroupAry}= state; var {plotId,groupLocked}= action.payload; - const pv= getPlotViewById(state,plotId); - const {plotGroupId} = pv; + const {plotGroupId} = getPlotViewById(state,plotId); const pgIdx= getPlotGroupIdxById(state,plotGroupId); diff --git a/src/firefly/js/visualize/region/RegionFactory.js b/src/firefly/js/visualize/region/RegionFactory.js index 87dd4c5a38..be079585bf 100644 --- a/src/firefly/js/visualize/region/RegionFactory.js +++ b/src/firefly/js/visualize/region/RegionFactory.js @@ -65,7 +65,7 @@ export class RegionFactory { }, []); var globalOptions = Object.assign({}, makeRegionOptions({})); - set(globalOptions, regionPropsList.COORD, 'J2000'); + set(globalOptions, regionPropsList.COORD, 'PHYSICAL'); return regionLines.length > 0 ? regionLines.reduce ( (prev, region, index) => { const rg = RegionFactory.parsePart(region.trim(), index+1, globalOptions, bAllowHeader); diff --git a/src/firefly/js/visualize/region/RegionTask.js b/src/firefly/js/visualize/region/RegionTask.js index e619cc9915..999c825b57 100644 --- a/src/firefly/js/visualize/region/RegionTask.js +++ b/src/firefly/js/visualize/region/RegionTask.js @@ -7,7 +7,7 @@ import {getDS9Region} from '../../rpc/PlotServicesJson.js'; import {getDlAry} from '../DrawLayerCntlr.js'; import {dispatchCreateDrawLayer, dispatchDetachLayerFromPlot, dispatchAttachLayerToPlot} from '../DrawLayerCntlr.js'; -import {getDrawLayerById} from '../PlotViewUtil.js'; +import {getDrawLayerById, getPlotViewIdListInGroup} from '../PlotViewUtil.js'; import RegionPlot from '../../drawingLayers/RegionPlot.js'; import {getPlotViewAry} from '../PlotViewUtil.js'; import {visRoot } from '../ImagePlotCntlr.js'; @@ -23,8 +23,14 @@ var [RegionIdErr, RegionErr, DrawObjErr, JSONErr] = [ 'get region json error']; var getPlotId = (plotId) => { - return (!plotId || (isArray(plotId)&&plotId.length === 0)) ? get(visRoot(), 'activePlotId') : plotId; + //return (!plotId || (isArray(plotId)&&plotId.length === 0)) ? get(visRoot(), 'activePlotId') : plotId; + if (!plotId || (isArray(plotId)&&plotId.length === 0)) { + var pid = get(visRoot(), 'activePlotId'); + return getPlotViewIdListInGroup(visRoot(), pid, false); + } else { + return plotId; + } }; /** @@ -69,26 +75,27 @@ function createRegionLayer(regionAry, title, regionId, plotId, dataFrom = 'ds9') // convert region description array to Region object array if (regionAry && regionAry.length > 0) { - +/* var rgAry = dataFrom === 'json' ? RegionFactory.parseRegionJson(regionAry) : RegionFactory.parseRegionDS9(regionAry); +*/ - if (rgAry && rgAry.length > 0) { + // if (rgAry && rgAry.length > 0) { var dl = getDrawLayerById(getDlAry(), regionId); if (!dl) { - dispatchCreateDrawLayer(regionDrawLayerId, {title, drawLayerId: regionId, regions: rgAry}); + dispatchCreateDrawLayer(regionDrawLayerId, {title, drawLayerId: regionId, regionAry, dataFrom}); } var pId = getPlotId(plotId); if (pId) { dispatchAttachLayerToPlot(regionId, pId, true); } - } else { - reportError(DrawObjErr); - } + // } else { + // reportError(DrawObjErr); + // } } else { - reportError(`${RegionIdErr} for creating region layer`); + reportError(`${RegionErr} for creating region layer`); } } diff --git a/src/firefly/js/visualize/ui/VisCtxToolbarView.jsx b/src/firefly/js/visualize/ui/VisCtxToolbarView.jsx index b9adda83bb..5358dbed13 100644 --- a/src/firefly/js/visualize/ui/VisCtxToolbarView.jsx +++ b/src/firefly/js/visualize/ui/VisCtxToolbarView.jsx @@ -195,14 +195,14 @@ function crop(pv) { function makeExtensionButtons(extensionAry,pv,dlAry) { if (!extensionAry) return false; - return extensionAry.map( (ext,idx) => ( - { + return dispatchExtensionActivate(ext,makeExtActivateData(ext,pv,dlAry))}/> - ) + } ); } diff --git a/src/fftools/python/display/.gitignore b/src/firefly/python/display/.gitignore similarity index 100% rename from src/fftools/python/display/.gitignore rename to src/firefly/python/display/.gitignore diff --git a/src/firefly/python/display/FireflyClient.py b/src/firefly/python/display/FireflyClient.py new file mode 100644 index 0000000000..1b5cb7739b --- /dev/null +++ b/src/firefly/python/display/FireflyClient.py @@ -0,0 +1,648 @@ +from ws4py.client.threadedclient import WebSocketClient +import requests +import webbrowser +import json +import time +import socket +import urlparse + +__author__ = 'zhang' + ' Cindy Wang' + + +class FireflyClient(WebSocketClient): + # class variables + # serverEvents = { + # 'name': ['EVT_CONN_EST', 'SvrBackgroundReport', 'WindowResize'], + # 'scope': ['SELF', 'CHANNEL'], + # 'dataType': ['STRING', 'JSON', 'BG_STATUS'], + # 'data': ['channel'] + # } + + fftools_cmd = '/firefly/sticky/CmdSrv' + my_localhost = 'localhost:8080' + ALL = 'ALL_EVENTS_ENABLED' + + # for serializing the RangeValues object + STRETCH_TYPE_DICT = {'percent': 88, 'maxmin': 89, 'absolute': 90, 'zscale': 91, 'sigma': 92} + STRETCH_ALGORITHM_DICT = {'linear': 44, 'log': 45, 'loglog': 46, 'equal': 47, 'squared': 48, 'sqrt': 49, + 'asinh': 50, 'powerlaw_gamma': 51} + + # extension type + EXTENSION_TYPE = ['AREA_SELECT', 'LINE_SELECT', 'POINT'] + + # actions from Firefly + ACTION_DICT = { + 'ShowFits': 'ImagePlotCntlr.PlotImage', + 'AddExtension': 'ExternalAccessCntlr/extensionAdd', + 'ShowTable': 'table.search', + 'ZoomImage': 'ImagePlotCntlr.ZoomImage', + 'PanImage': 'ImagePlotCntlr.recenter', + 'StretchImage': 'ImagePlotCntlr.StretchChange', + 'CreateRegionLayer': 'DrawLayerCntlr.RegionPlot.createLayer', + 'DeleteRegionLayer': 'DrawLayerCntlr.RegionPlot.deleteLayer', + 'AddRegionData': 'DrawLayerCntlr.RegionPlot.addRegion', + 'RemoveRegionData': 'DrawLayerCntlr.RegionPlot.removeRegion'} + + # id for table, region layer, extension + _item_id = {'Table': 0, 'RegionLayer': 0, 'Extension': 0} + + # the constructor, define instance variables for the object + def __init__(self, host=my_localhost, channel=None): + if host.startswith('http://'): + host = host[7:] + self.this_host = host + url = 'ws://%s/firefly/sticky/firefly/events' % host # web socket url + if channel: + url += '?channelID=%s' % channel + WebSocketClient.__init__(self, url) + self.url_root = 'http://' + host + self.fftools_cmd + self.url_bw = 'http://' + self.this_host + '/firefly/firefly.html;wsch=' + self.listeners = {} + self.channel = channel + self.session = requests.Session() + # print 'websocket url:%s' % url + self.connect() + + # def opened(self): + # print ("Opening websocket connection to fftools") + + # def closed(self, code, reason=None): + # print ("Closed down", code, reason) + + def _handle_event(self, ev): + for callback, eventIDList in self.listeners.items(): + if ev['name'] in eventIDList or FireflyClient.ALL in eventIDList: + callback(ev) + + # overridde the superclass's method + def received_message(self, m): + ev = json.loads(m.data.decode('utf8')) + event_name = ev['name'] + + if event_name == 'EVT_CONN_EST': + try: + conn_info = ev['data'] + if self.channel is None: + self.channel = conn_info['channel'] + conn_id = '' + if 'conn_id' in conn_info: + conn_id = conn_info['conn_id'] + seinfo = self.channel + if (len(conn_id)) > 0: + seinfo = conn_id + '_' + seinfo + + self.session.cookies['seinfo'] = seinfo + except: + print ('from callback exception: ') + print (m) + else: + self._handle_event(ev) + + def _send_url_as_get(self, url): + """ + send URL in 'GET' request + :param url: + :return: + """ + response = self.session.get(url) + # print response.text + status = json.loads(response.text) + return status[0] + + def _send_url_as_post(self, data): + """ + send URL in 'POST' request + :param data + :return: + """ + response = self.session.post(self.url_root, data=data) + status = json.loads(response.text) + return status[0] + + def _is_page_connected(self): + ip = socket.gethostbyname(socket.gethostname()) + url = self.url_root + '?cmd=pushAliveCheck&ipAddress=%s' % ip + retval = self._send_url_as_get(url) + return retval['active'] + + # def onConnected(self, channel): + # #open the browser + # url = 'http://' + self.this_host + '/fftools/app.html?id=Loader&channelID=' + channel + # webbrowser.open('http://localhost:8080/fftools/app.html?id=Loader&channelID=' + channel) + # webbrowser.open(url) + + def _make_pid_param(self, plot_id): + return ','.join(plot_id) if isinstance(plot_id, list) else plot_id + +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +# Public API Begins +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- + + def add_listener(self, callback, name=ALL): + """ + Add a function to listen for events on the firefly client + :param callback: set the function to be called when a event happens on the firefly client + :param name: set the name of the events, default to all events + :return: + """ + if callback not in self.listeners.keys(): + self.listeners[callback] = [] + if name not in self.listeners[callback]: + self.listeners[callback].append(name) + + def remove_listener(self, callback, name=ALL): + """ + remove a callback + :param callback: a previous set function that is to be removed + :param name: set the name of the event, default to all events + :return: + """ + if callback in self.listeners.keys(): + if name in self.listeners[callback]: + self.listeners[callback].remove(name) + if len(self.listeners[callback]) == 0: + self.listeners.pop(callback) + + def wait_for_events(self): + """ + Pause and do not exit. Wait over events from the server. + This is optional. You should not use this method in ipython notebook + Event will get called anyway. + """ + WebSocketClient.run_forever(self) + + def get_firefly_url(self, mode=None, channel=None): + """ + Get URL to Firefly Tools viewer and the channel set. Normally this method + will be call without any parameters. + :param mode: mode maybe one of 'full', or 'minimal'. Defaults to minimal. + :param channel: a different channel string than the default + :return: the full url string + """ + if not channel: + channel = self.channel + + url = 'http://' + self.this_host + '/firefly/firefly.html?id=Loader&channelID=' + if mode == "full": + url = self.url_bw + return url + channel + + def launch_browser(self, url=None, channel=None, force=False): + """ + Launch a browsers with the Firefly Tools viewer and the channel set. Normally this method + will be call without any parameters. + :param url: the url, overriding the default + :param channel: a different channel than the default + :return: the channel + """ + if not channel: + channel = self.channel + if not url: + url = self.url_bw + do_open = True if force else not self._is_page_connected() + if do_open: + webbrowser.open(self.get_firefly_url(url, channel)) + time.sleep(5) # todo: find something better to do than sleeping + return channel + + def stay_connected(self): + self.ws.run() + + def disconnect(self): + self.close() + + def upload_file(self, path, pre_load=True): + """ Upload a file to the Firefly Server + :param path: uploaded file can be fits, region, and various types of table files + """ + url = 'http://' + self.this_host + '/firefly/sticky/Firefly_FileUpload?preload=%s' % pre_load + files = {'file': open(path, 'rb')} + result = self.session.post(url, files=files) + index = result.text.find('$') + return result.text[index:] + + def upload_fits_data(self, stream): + """ + Upload a FITS file like object to the Firefly server. The method should allows file like data + to be streamed without using a actual file. + :param stream: a file like object + :return: status + """ + url = 'http://' + self.this_host + '/firefly/sticky/Firefly_FileUpload?preload=true' + data_pack = {'data': stream} + result = self.session.post(url, files=data_pack) + index = result.text.find('$') + return result.text[index:] + + def upload_text_data(self, stream): + """ + Upload a Text file like object to the Firefly server. The method should allows file like data + to be streamed without using a actual file. + :param stream: a file like object + :return: status + """ + return self.upload_data(stream, 'UNKNOWN') + + def upload_data(self, stream, data_type): + url = 'http://' + self.this_host + '/firefly/sticky/Firefly_FileUpload?preload=' + url += 'true&type=FITS' if data_type == 'FITS' else 'false&type=UNKNOWN' + data_pack = {'data': stream} + result = self.session.post(url, files=data_pack) + index = result.text.find('$') + return result.text[index:] + + def dispatch_remote_action(self, channel, action_type, payload): + """ + dispatch the action to the server by using 'GET' request + :param: channel + :param: actionType + :param: payload: payload for the action + """ + + action = {'type': action_type, 'payload': payload} + url = self.url_root + '?channelID=' + channel + '&cmd=pushAction&action=' + url += json.dumps(action) + return self._send_url_as_get(url) + + def dispatch_remote_action_by_post(self, channel, action_type, payload): + """ + dispatch the action to the server by using 'POST' request + :param: channel + :param: actionType + :param: payload: payload for the action + """ + action = {'type': action_type, 'payload': payload} + data = {'channelID': channel, 'cmd': 'pushAction', 'action': json.dumps(action)} + + return self._send_url_as_post(data) + + # ------------------------- + # dispatch actions + # ------------------------- + + # ---------------------------------------------------------------- + # action on showing fits, tables, XYPlot and adding extension + # ---------------------------------------------------------------- + + def show_fits(self, file_on_server=None, plot_id=None, **additional_params): + """ + Show a fits image + :param: file_on_server: the is the name of the file on the server. If you used upload_file() + then it is the return value of the method. Otherwise it is a file that + firefly has direct read access to. + :param: plot_id: the id you assigned to the plot. This is necessary to further control the plot + :param: additionalParam: dictionary of any valid fits viewer plotting parameters, + see firefly/docs/fits-plotting-parameters.md + :return: status of call + """ + wp_request = {'plotGroupId': 'groupFromPython', + 'GroupLocked': False} + payload = {'wpRequest': wp_request, + 'useContextModifications': True, + 'viewerId': 'DEFAULT_FITS_VIEWER_ID'} + if plot_id: + payload['wpRequest'].update({'plotId': plot_id}) + if file_on_server: + payload['wpRequest'].update({'file': file_on_server}) + if additional_params: + payload['wpRequest'].update(additional_params) + return self.dispatch_remote_action(self.channel, FireflyClient.ACTION_DICT['ShowFits'], payload) + + def show_table(self, file_on_server, tbl_id=None, title=None, page_size=100, is_catalog=True): + """ + Show a table in Firefly + :param file_on_server: the is the name of the file on the server. If you used upload_file() + then it is the return value of the method. Otherwise it is a file that + firefly has direct read access to. + :param tbl_id: table Id + :param title: title on table + :param page_size: how many rows are shown. + :param is_catalog: catalog or table + :return: status of call + """ + + if not tbl_id: + tbl_id = FireflyClient._gen_item_id('Table') + if not title: + title = tbl_id + tbl_type = 'table' if not is_catalog else 'catalog' + + tbl_req = {'startIdx': 0, 'pageSize': page_size, 'source': file_on_server, 'tblType': tbl_type, + 'id': 'IpacTableFromSource', 'tbl_id': tbl_id} + meta_info = {'title': title, 'tbl_id': tbl_id} + tbl_req.update({'META_INFO': meta_info}) + payload = {'request': tbl_req} + + return self.dispatch_remote_action(self.channel, FireflyClient.ACTION_DICT['ShowTable'], payload) + + def show_xyplot(self, file_on_server, **additional_params): + """ + TODO + Show a table in Firefly + :param file_on_server: the is the name of the file on the server. If you used upload_file() + then it is the return value of the method. Otherwise it is a file that + firefly has direct read access to. + :param additional_params: XY Plot Viewer parameters + :return: status of call + """ + url = self.url_root + "?cmd=pushXYPlot" + if additional_params: + url += '&' + '&'.join(['%s=%s' % (k, v) for k, v in additional_params.items()]) + url += '&file=%s' % file_on_server + return self._send_url_as_get(url) + + def add_extension(self, ext_type, plot_id=None, title='', tool_tip='', + extension_id=None, image_src=None): + """ + TODO: callback + Add an extension to the plot. Extensions are context menus that allows you to extend + what firefly can so when certain actions happen + :param ext_type: May be 'AREA_SELECT', 'LINE_SELECT', or 'POINT'. todo: 'CIRCLE_SELECT' + :param title: The title that the user sees + :param plot_id: The it of the plot to put the extension on + :param extension_id: The id of the extension + :param image_src: image source of an icon to display the toolbar instead of title + :return: status of call + """ + + if ext_type not in FireflyClient.EXTENSION_TYPE: + ext_type = 'NONE' + + if not extension_id: + extension_id = FireflyClient._gen_item_id('Extension') + + image_url = FireflyClient.create_image_url(image_src) if image_src else None + + extension = {'id': extension_id, 'plotId': plot_id, 'imageUrl': image_url, + 'title': title, 'extType': ext_type, 'toolTip': tool_tip} + payload = {'extension': extension} + return self.dispatch_remote_action_by_post(self.channel, FireflyClient.ACTION_DICT['AddExtension'], + payload) + + # ---------------------------- + # actions on image + # ---------------------------- + + def set_zoom(self, plot_id, factor=1.0): + """ + Zoom the image + :param plot_id: plotId to which the region is added, parameter may be string or a list of strings. + :param factor: number, zoom factor for the image + :return: + """ + + def zoom_oneplot(plot_id, factor): + payload = {'plotId': plot_id, 'userZoomType': 'LEVEL', 'level': factor, 'actionScope': 'SINGLE'} + return self.dispatch_remote_action(self.channel, FireflyClient.ACTION_DICT['ZoomImage'], payload) + + if isinstance(plot_id, tuple) or isinstance(plot_id, list): + return [zoom_oneplot(x, factor) for x in plot_id] + else: + return zoom_oneplot(plot_id, factor) + + def set_pan(self, plot_id, x=None, y=None, coord='image'): + """ + Pan or scroll the image to center on the image coordinates passed + if no (x, y) is given, the image is recentered at the center of the image + :param plot_id: plot_id to which the region is added, parameter may be string or a list of strings. + :param x: number, new center x position to scroll to + :param y: number, new center y position to scroll to + :param coord: coordinate system, if coord == 'image', then x, y is for image pixel, + : if coord == 'J2000', then x, y is ra, dec on EQ_J2000. + :return: + """ + + payload = {'plotId': plot_id} + if x and y: + if coord.startswith('image'): + payload.update({'centerPt': {'x': x, 'y': y, 'type': 'ImagePt'}}) + else: + payload.update({'centerPt': {'x': x, 'y': y, 'type': 'J2000'}}) + + return self.dispatch_remote_action(self.channel, FireflyClient.ACTION_DICT['PanImage'], payload) + + def set_stretch(self, plot_id, type=None, algorithm=None, **additional_params): + """ + Change the stretch of the image (no band case) + :param type: 'percent','maxmin','absolute','zscale', or 'sigma' + :param algorithm: 'linear', 'log','loglog','equal', 'squared', 'sqrt', 'asinh', 'powerlaw_gamma' + :param plot_id: plotId to which the range is added, parameter may be string or a list of strings. + :param additional_params: + for type is 'zscale', kwargs: zscale_contrast, zscale_samples, zscale_samples_perline + for type is others, kwargs: lower_value, upper_value + :return: status of call + """ + + if type and type.lower() == 'zscale': + serialized_rv = self._create_rangevalues_zscale(algorithm, **additional_params) + else: + serialized_rv = self._create_rangevalues_standard(algorithm, type, **additional_params) + + st_data = [{'band': 'NO_BAND', 'rv': serialized_rv, 'bandVisible': True}] + payload = {'stretchData': st_data, 'plotId': plot_id} + + return self.dispatch_remote_action(self.channel, FireflyClient.ACTION_DICT['StretchImage'], payload) + + # ----------------------------------------------------------------- + # Region Stuff + # ----------------------------------------------------------------- + + def overlay_region_layer(self, file_on_server=None, region_data=None, title=None, + region_layer_id=None, plot_id=None): + """ + Overlay a region layer on the loaded FITS images, the regions are defiend either by a file or + by some text description + :param file_on_server: the is the name of the file on the server. If you used upload_file() + then it is the return value of the method. Otherwise it + is a file that firefly has direct read access to. + :param region_data: region description, either a list of strings or a string + :param title: title of the region file + :param region_layer_id: id of layer to add + :param plot_id: plotId to which this region should be added, parameter may be string or + a list of strings. If None then overlay region on all plots + :return: status of call + """ + + if not region_layer_id: + region_layer_id = FireflyClient._gen_item_id('RegionLayer') + payload = {'regionId': region_layer_id} + + if title: + payload.update({'layerTitle': title}) + if plot_id: + payload.update({'plotId': plot_id}) + + if file_on_server: + payload.update({'fileOnServer': file_on_server}) + return self.dispatch_remote_action(self.channel, + FireflyClient.ACTION_DICT['CreateRegionLayer'], payload) + elif region_data: + payload.update({'regionAry': region_data}) + return self.dispatch_remote_action_by_post( + self.channel, FireflyClient.ACTION_DICT['CreateRegionLayer'], payload) + + def delete_region_layer(self, region_layer_id, plot_id=None): + """ + Delete region layer on the loaded FITS images + :param region_layer_id: regionLayer to remove + :param plot_id: plotId to which the region layer should be removed, if None, + then remove region layer from all plots + :return: status of call + """ + + payload = {'regionId': region_layer_id} + if plot_id: + payload.update({'plotId': plot_id}) + + return self.dispatch_remote_action(self.channel, + FireflyClient.ACTION_DICT['DeleteRegionLayer'], payload) + + def add_region_data(self, region_data, region_layer_id): + """ + Add the specified region entries + :param region_data: a list of region entries + :param region_layer_id: id of region to remove entries from + :return: status of call + """ + payload = {'regionChanges': region_data, 'regionId': region_layer_id} + + return self.dispatch_remote_action_by_post(self.channel, + FireflyClient.ACTION_DICT['AddRegionData'], payload) + + def remove_region_data(self, region_data, region_layer_id): + """ + Remove the specified region entries + :param region_data: a list of region entries + :param region_layer_id: id of region to remove entries from + :return: status of call + """ + payload = {'regionChanges': region_data, 'regionId': region_layer_id} + + return self.dispatch_remote_action_by_post(self.channel, + FireflyClient.ACTION_DICT['RemoveRegionData'], payload) + + def add_mask(self, mask_id, bit_number, image_number, color, plot_id, + bit_desc=None, file_on_server=None): + """ + Add a mask layer + TODO + :param mask_id: id of mask + :param bit_number: bitNumber of the mask to overlay + :param image_number: imageNumber of the mask layer + :param color: color as an html color (eg. #FF0000 (red) #00FF00 (green) + :param plot_id: plot id to overlay the mask on + :param bit_desc: (optional) description of the mask layer + :param file_on_server: (optional) file to get the mask from, + if None then get it from the original file + :return: status of call + """ + url = self.url_root + "?cmd=pushAddMask" + + params = { + 'id': mask_id, + 'bitNumber': bit_number, + 'color': color, + 'plotId': plot_id, + 'imageNumber': image_number} + + if bit_desc: + params['bitDesc'] = bit_desc + if file_on_server: + params['fileKey'] = file_on_server + response = self.session.post(url, data=params) + status = json.loads(response.text) + return status[0] + + def remove_mask(self, mask_id): + """ + TODO + Remove a mask layer + :param mask_id: id of mask + :return: status of call + """ + url = self.url_root + "?cmd=pushRemoveMask&id=%s" % mask_id + return self._send_url_as_get(url) + + # ----------------------------------------------------------------- + # Range Values + # ----------------------------------------------------------------- + + def _create_rv(self, stretch_type, lower_value, upper_value, algorithm, + zscale_contrast=25, zscale_samples=600, zscale_samples_perline=120, + beta_value=0.1, gamma_value=2.0): + retval = None + st = stretch_type.lower() + a = algorithm.lower() + if st in FireflyClient.STRETCH_TYPE_DICT and a in FireflyClient.STRETCH_ALGORITHM_DICT: + retval = '%d,%f,%d,%f,%f,%f,%d,%d,%d,%d' % \ + (FireflyClient.STRETCH_TYPE_DICT[st], lower_value, + FireflyClient.STRETCH_TYPE_DICT[st], upper_value, + beta_value, gamma_value, + FireflyClient.STRETCH_ALGORITHM_DICT[a], + zscale_contrast, zscale_samples, zscale_samples_perline) + return retval + + def _create_rangevalues_standard(self, algorithm, stretch_type='Percent', lower_value=1, upper_value=99): + """ + :param algorithm: must be 'Linear', 'Log','LogLog','Equal','Squared', 'Sqrt' + :param stretch_type: must be 'Percent','Absolute','Sigma' + :param lower_value: number, lower end of stretch + :param upper_value: number, upper end of stretch + :return: a serialized range values string + """ + + retval = self._create_rv(stretch_type, lower_value, upper_value, algorithm) + if not retval: + t = stretch_type if stretch_type.lower() in FireflyClient.STRETCH_ALGORITHM_DICT else 'percent' + a = algorithm if algorithm.lower() in FireflyClient.STRETCH_ALGORITHM_DICT else 'linear' + retval = self._create_rv(t, 1, 99, a) + return retval + + def _create_rangevalues_zscale(self, algorithm, zscale_contrast=25, + zscale_samples=600, zscale_samples_perline=120): + """ + :param algorithm: must be 'Linear', 'Log','LogLog','Equal','Squared', 'Sqrt' + :param zscale_contrast: zscale contrast + :param zscale_samples: zscale samples + :param zscale_samples_perline: zscale sample per line + :return: a serialized range values string + """ + retval = self._create_rv('zscale', 1, 1, algorithm, + zscale_contrast, zscale_samples, zscale_samples_perline) + if not retval: + a = algorithm if algorithm.lower() in FireflyClient.STRETCH_ALGORITHM_DICT else 'linear' + retval = self._create_rv('zscale', 1, 2, a, 25, 600, 120) + return retval + + @classmethod + def _gen_item_id(cls, item): + """ + generate an ID for some entity like 'Table', 'RegionLayer', 'Extension' + :param item: entity type + :return: an ID string + """ + + if item in cls._item_id: + cls._item_id[item] += 1 + return item + '-' + str(cls._item_id[item]) + else: + return None + + @staticmethod + def create_image_url(image_source): + """ + create image url according to image source + :param image_source: an image path or image url + :return: + """ + + def is_url(url): + return urlparse.urlparse(url).scheme != '' + + if not image_source.startswith('data:image') and not is_url(image_source): + data_uri = open(image_source, 'rb').read().encode('base64').replace('\n', '') + return 'data:image/png;base64,%s' % data_uri + + return image_source diff --git a/src/fftools/python/display/__init__.py b/src/firefly/python/display/__init__.py similarity index 100% rename from src/fftools/python/display/__init__.py rename to src/firefly/python/display/__init__.py diff --git a/src/fftools/python/display/webSocketTest.py b/src/firefly/python/display/webSocketTest.py similarity index 100% rename from src/fftools/python/display/webSocketTest.py rename to src/firefly/python/display/webSocketTest.py diff --git a/src/fftools/test/FireflyShowTable.ipynb b/src/firefly/test/python/FireflyShowTable.ipynb similarity index 100% rename from src/fftools/test/FireflyShowTable.ipynb rename to src/firefly/test/python/FireflyShowTable.ipynb diff --git a/src/fftools/test/aCallback.py b/src/firefly/test/python/aCallback.py similarity index 100% rename from src/fftools/test/aCallback.py rename to src/firefly/test/python/aCallback.py diff --git a/src/fftools/test/data/2mass-m31-2412rows.tbl b/src/firefly/test/python/data/2mass-m31-2412rows.tbl similarity index 100% rename from src/fftools/test/data/2mass-m31-2412rows.tbl rename to src/firefly/test/python/data/2mass-m31-2412rows.tbl diff --git a/src/fftools/test/data/c.fits b/src/firefly/test/python/data/c.fits similarity index 100% rename from src/fftools/test/data/c.fits rename to src/firefly/test/python/data/c.fits diff --git a/src/fftools/test/data/c.reg b/src/firefly/test/python/data/c.reg similarity index 97% rename from src/fftools/test/data/c.reg rename to src/firefly/test/python/data/c.reg index 0a4abdfac7..67d1741ac1 100644 --- a/src/fftools/test/data/c.reg +++ b/src/firefly/test/python/data/c.reg @@ -15033,8 +15033,7 @@ point 51.581104 30.989466 # point=box point 51.709601 31.161289 # point=box point 51.959740 31.003145 # point=box point 51.674141 31.094873 # point=box -point - 51.788031 31.164188 # point=box +point 51.788031 31.164188 # point=box point 52.113217 31.277830 # point=box point 51.866949 31.218723 # point=box point 52.015108 31.273577 # point=box diff --git a/src/fftools/test/data/sample.tbl b/src/firefly/test/python/data/sample.tbl similarity index 100% rename from src/fftools/test/data/sample.tbl rename to src/firefly/test/python/data/sample.tbl diff --git a/src/firefly/test/python/data/test5.reg b/src/firefly/test/python/data/test5.reg new file mode 100644 index 0000000000..daea1f69ca --- /dev/null +++ b/src/firefly/test/python/data/test5.reg @@ -0,0 +1,21 @@ +J2000 +point 202.41 47.262 # color=pink text="pt circle 1" point=circle +line(202.414796 47.281999 202.423758 47.247072) # color=orange width=10 select=0 text='line 2' +text 30p 40p # color=pink text={text test 3} edit=0 highlite=0 font="BRAGGADOCIO 10 normal roman" +text 202.437691 47.234228{more text testing 4} # color=purple move=0 +point 202.43 47.270 # point=x 15 color=green text="pt x 5" rotate=0 +point 202.45 47.262 # color=red text="pt boxcircle 6" delete=0 point=boxcircle 10 +physical;circle 80 140 20 # color=red offsetx=25 offsety=2 width=5 edit=0 text="circle 7" +annulus 13h30m16.41s +47d14m43.9s 30p 40p 50p # color=green text="hello" include=0 text="annulus 8" +physical;point 200 140 # color=yellow point=cross 20 text="pt cross 9" offsetx=10 +physical;point 13h29m52.73s, +47d11m40.9s # color=purple point=diamond 15 text="pt diamond 10" +#circle(202.55556, 47.188286 , 20p) # color=blue text="text 11" +box(202.55556, 47.188286 ,20p,40p, 30p, 50p, 0) # color=red width=5 text="boxann 11" +box(202.52556,47.226286,0.0240,0.006,0) # color=green width=5 text="box 11-2" +image;box(100, 150, 50p, 20p, 2r) # color=magenta width=6 text="slanted box 12" +image;box(190.564796, 47.281999, 50p, 20p, 0) # color=red width=6 offsety=-15 text="box 12-1" +j2000;box(47.247072i, 180.445347i, 50p, 20p, 0) # color=blue width=6 text="box 12-3" +physical;-box point 12 12 # text="pt box 13" +polygon(202.564796, 47.281999,202.553758, 47.247072, 202.445347, 47.244296, 202.479687, 47.264027, 202.492153, 47.290841) # color=blue text="polygon 14" width=1 font="helvetica 16 bold" offsety=10 +point 202.45 47.2879 # color=cyan text="pt arrow 15" delete=0 point=arrow 20 + diff --git a/src/firefly/test/python/data/wise-1b-1.fits b/src/firefly/test/python/data/wise-1b-1.fits new file mode 100644 index 0000000000..16332a1ac7 --- /dev/null +++ b/src/firefly/test/python/data/wise-1b-1.fits @@ -0,0 +1,527 @@ +SIMPLE = T / file does conform to FITS standard BITPIX = -32 / number of bits per data pixel NAXIS = 2 / number of data axes NAXIS1 = 197 / length of data axis 1 NAXIS2 = 198 / length of data axis 2 EXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H TELESCOP= 'WISE ' BUNIT = 'DN ' EQUINOX = 2000 RADECSYS= 'FK5 ' FILETYPE= 'intensity image frame' DATE_OBS= '2010-06-11T01:50:36.444' / Observation midpoint UTC WAVELEN = 3.368 / [micron] effective wavelength of band BAND = 1 / Band number SCAN = '05397a ' / Scan ID FRNUM = 207 / Frame bin number in scan FRSETID = '05397a207' / Frame set ID SCANSTRT= '2010-162T01:16:10.309' / Scan start UTC SCANEND = '2010-162T01:58:00.712' / Scan end UTC SCANGRP = '7a ' / Scan ID group FRMTOFF = -4.853 / [sec] Offset applied to packet VTC VTC = 329492922.019448 / Observation midpoint vehicle time code UNIXT = 1276221036.44399 / [sec] Observation midpoint UNIX time EPHEMT = 329493102.628646 / [sec] Observation midpoint TDT secs from J2000 UTC = '2010-162T01:50:36.444' / Observation midpoint UTC date/time DATIME = '2010-06-11T01:50:36.444' / Observation midpoint calendar date/time MJD_OBS = 55358.07681069 / [days] Obs. midpoint Modified Julian Day JD_OBS = 2455358.57681069 / [days] Observation midpoint Julian Day HJD_OBS = 2455358.57714659 / [days] Obs. midpoint Heliocentric Julian Day DTANNEAL= 42893.5599740148 / [sec] Time since last anneal UTANNEAL= '2010-161T13:55:42.884' / UTC of last anneal RAWFILE = 'none ' / Raw input file name; usually none TLMFILE = '/wise/fops/ingest/delivs/10162/10162T103705/WIS_HRP_PKT_FE1A_2010_1&'CONTINUE '62_10_37_05_CAT.bin' TLMSEQNO= 1312 / Delivery image sequence number DELIVID = '10162T103705' / Delivery ID CMPRSRAT= 0.355 / Compression ratio INEVENTS= 'ASCE SCAN' / Active orbit events EVASCE = 2229.44398701191 / [sec] Time since orbit event start EVSCAN = 2066.13498699665 / [sec] Time since orbit event start L0FILE = '/wise/fops/l0/7a/05397a/fr/207/05397a207-w1-int-0.fits.gz' / Level-0 CRPIX1 = 1.715000000000000E+02 / Reference X pixel CRPIX2 = 1.765000000000000E+02 / Reference Y pixel CRVAL1 = 202.50018758348 / [deg] Image center RA CRVAL2 = 47.150443511174 / [deg] Image center Dec CTYPE1 = 'RA---SIN-SIP' / Sin projection with SIP coefficients CTYPE2 = 'DEC--SIN-SIP' / Sin projection with SIP coefficients CD1_1 = 0.000624101473911919 / WCS rotation matrix element CD1_2 = -0.000441940510072229 / WCS rotation matrix element CD2_1 = -0.000444846643317919 / WCS rotation matrix element CD2_2 = -0.000620024288955568 / WCS rotation matrix element WCROTA2 = 144.519513011999 / [deg] CCW rotation of RA at CRPIX1,2 PA = 215.480486988001 / [deg] Rotation of +Y EofN at CRPIX1,2 WCDELT1 = -0.0007664145 / [deg/pix] X-axis scale WCDELT2 = 0.0007614076 / [deg/pix] Y-axis scale PXSCAL1 = -2.7590922 / [arcsec/pixel] X-axis scale PXSCAL2 = 2.74106736 / [arcsec/pixel] Y-axis scale CRDER1 = 3.72050320876857E-06 / [deg] Apriori RA error CRDER2 = 3.80609216390417E-06 / [deg] Apriori Dec error UNCRTPA = 0.000740908423644581 / [deg] Apriori PA error CSDRADEC= 1.53116834576769E-07 / Apriori RA/Dec cross-correlation UNCRTS1 = 2.3E-07 / [deg/pix] Apriori X-axis scale error UNCRTS2 = 2.3E-07 / [deg/pix] Apriori Y-axis scale error RA0 = 202.455129723793 / Level-0 ADCS frame center RA DEC0 = 47.1745747810279 / Level-0 ADCS frame center Dec PA0 = 215.476304042195 / Level-0 ADCS frame rot. of +Y EofN PA0_SC = 54.5236959578049 / Level-0 ADCS raw rot. of S/C +Y EofN ELON0 = 175.130359329397 / Level-0 ADCS frame center ecliptic lon ELAT0 = 50.9128279970785 / Level-0 ADCS frame center ecliptic lat GLON0 = 104.853091109244 / Level-0 ADCS frame center galactic lon GLAT0 = 68.583664669535 / Level-0 ADCS frame center galactic lat L0XFORM = '-1,0,0,1' / Raw->L0 frame transformation L0B2BDX = -9999 / Raw->L0 band x offset L0B2BDY = -9999 / Raw->L0 band y offset L0B2BDPA= -9999 / Raw->L0 band rotation offset SCRATEX = 0.00627063931601822 / [arcmin/sec] Rot. rate about S/C CF X SCRATEY = 3.77133996314466 / [arcmin/sec] Rot. rate about S/C CF Y SCRATEZ = -0.0488001736481421 / [arcmin/sec] Rot. rate about S/C CF Z DISTSRC = 'B10221 ' / Distortion source reference code B2BSRC = 'B10221 ' / Band-to-band delta source reference code EXPTIME = 7.7 / [sec] Frame exposure time TSAMP = 1.1 / [sec] Sample time FRINT = 11 / [sec] Frame-to-frame interval DEBOFF = 1024 / DEB offset DEBTRUNC= 3 / Bits truncated by DEB DEBGAIN = 3.75 / [e-/DEB ADU] DEB gain FEBGAIN = 5.74 / [e-/SUR ADU] FEB gain SUN2SCX = -0.177742616088926 / [AU] Sun-to-S/C vector X component, J2000 SUN2SCY = -0.917176784684527 / [AU] Sun-to-S/C vector Y component, J2000 SUN2SCZ = -0.397587011262086 / [AU] Sun-to-S/C vector Z component, J2000 SCVELX = 0.0203015490884993 / [AU/day] SUN-to-S/C velocity vector X componentSCVELY = -0.00337990414229539 / [AU/day] SUN-to-S/C velocity vector Y componentSCVELZ = 0.00116982618605563 / [AU/day] SUN-to-S/C velocity vector Z componentERTH2SCX= -2.35266746452618E-05 / [AU] Earth-to-S/C vector X component ERTH2SCY= 1.08782650861783E-05 / [AU] Earth-to-S/C vector Y component ERTH2SCZ= 3.8164911026852E-05 / [AU] Earth-to-S/C vector Z component SUNSEP = 93.2860948559483 / [deg] L0 FOV center to Sun angular sep SUNPA = 309.714137354115 / [deg] Direction of Sun, east of north MOONSEP = 102.663420127742 / [deg] L0 FOV center to Moon angular sep MOONPA = 326.322249188059 / [deg] Direction of Moon, east of north MARSSEP = 53.438487760053 / [deg] L0 FOV center to Mars angular sep MARSPA = 245.120362067851 / [deg] Direction of Mars, east of north JUPSEP = 130.073741592429 / [deg] L0 FOV center to Jupiter angular sep JUPPA = 28.669774310394 / [deg] Direction of Jupiter, east of north SATSEP = 48.5755309866161 / [deg] L0 FOV center to Saturn angular sep SATPA = 212.118162951475 / [deg] Direction of Saturn, east of north GEOLON = 228.424145 / [deg] S/C geographic longitude GEOLAT = 55.736005 / [deg] S/C geographic latitude GEOALT = 538.120857 / [km] S/C altitude GCD2SAA = 87.783034 / [deg] Great circle angle to SAA boundary GEO_DT = -0.443830370903015 / [sec] Time since ground track sample SURCOEF1= 0 / Nominal static survey SUR coefficient #1 SURCOEF2= -7 / Nominal static survey SUR coefficient #2 SURCOEF3= -5 / Nominal static survey SUR coefficient #3 SURCOEF4= -3 / Nominal static survey SUR coefficient #4 SURCOEF5= -1 / Nominal static survey SUR coefficient #5 SURCOEF6= 1 / Nominal static survey SUR coefficient #6 SURCOEF7= 3 / Nominal static survey SUR coefficient #7 SURCOEF8= 5 / Nominal static survey SUR coefficient #8 SURCOEF9= 7 / Nominal static survey SUR coefficient #9 ATTEMGEN= 'true ' / electromagnet control enable ATT_ADST= 'point ' / current adcs state ATT_ERRX= 4.188E-06 / attitude error x ATT_ERRY= -3.77455E-06 / attitude error y ATT_ERRZ= -6.91667E-06 / attitude error z ATTUPDMT= 'three_ax_rt_att' / attitude update method ATT_FAQ1= 0.176816 / inertial to control attitude x ATT_FAQ2= -0.31932 / inertial to control attitude y ATT_FAQ3= -0.105542 / inertial to control attitude z ATT_FAQ4= 0.925004 / inertial to control attitude w ATT_FRTX= 1.82058E-06 / [rad/sec] inertial to control rate x ATT_FRTY= 0.00109704 / [rad/sec] inertial to control rate y ATT_FRTZ= -1.42047E-05 / [rad/sec] inertial to control rate z ATT_METH= 'three_ax_rt_att' / attitude determination method ATTCRCFG= 5 / current config set ATTACCFG= 'w1234_sci' / actuator config index GYRODTST= 'good ' / Overall status of the gyro data ICV_VLD = 'valid ' / icv valid IMU_PWR = 'on ' / imu power on MAG_PWR = 'on ' / mag power on MAGFDVST= 'good ' / mag field vector status MNVRDONE= 'true ' / maneuver done MSUN_VST= 'good ' / measured sun vector status MSUN_VX = 0.0665721 / measured sun x MSUN_VY = 0.992196 / measured sun y MSUN_VZ = -0.105431 / measured sun z ORBSTVLD= 'valid ' / orbital state valid RATERRX = 1.81942E-06 / [rad/sec] rate error x RATERRY = 7.67414E-06 / [rad/sec] rate error y RATERRZ = 1.42047E-05 / [rad/sec] rate error z WHL1_SPD= -152.144 / [rad/sec] wheel 1 speed WHL2_SPD= -116.14 / [rad/sec] wheel 2 speed WHL3_SPD= -104.526 / [rad/sec] wheel 3 speed WHL4_SPD= -150.983 / [rad/sec] wheel 4 speed SEL_SENS= 'st1_st2_rs' / selected sensor INSHADOW= 'false ' / in shadow NMSUNUSD= 6 / num sun sensors used ST1_ASCT= 4 / star tracker 1 att star count ST2_ASCT= 4 / star tracker 2 att star count ST1_BGHI= 'ok ' / star tracker 1 background high ST2_BGHI= 'ok ' / star tracker 2 background high ST1STRID= 0 / star tracker 1 star id mode ST2STRID= 0 / star tracker 2 star id mode ST_ATTDQ= 'good ' / star tracker attitude data quality ST_RTEDQ= 'good ' / star tracker rate data quality ST_USED = 'both ' / star tracker used STVLDLCT= 'true ' / st valid last cycle TQROD1ST= 'off ' / torqrod1 current powered status TQROD2ST= 'off ' / torqrod2 current powered status TQROD3ST= 'off ' / torqrod3 current powered status MDSCMODE= 'operate ' / Current spacecraft state APSHIMYT= 100.029 / [K] Aperture Shade Inner Cone (-Y) Temperature APSHIPYT= 101.013 / [K] Aperture Shade Inner Cone (+Y) Temperature APSHOMYT= 190.1 / [K] Aperture Shade Outer Cone (-Y) Temperature APSHOPYT= 184.7 / [K] Aperture Shade Outer Cone (+Y) Temperature B13NUMSA= 9 / Bands 1&3 Number of Samples B1EOFERR= 'no ' / Band 1 End of Frame Error B1SCIFSE= 84 / Band 1 Science Frames Sent B1MLPERR= 'no ' / Band 1 Missing Last Pixel Error B1SATDET= 'enabled ' / Band 1 Saturation Detection Enable B1COEFF1= 0 / Band 1 Coefficient 1 B1COEFF2= -7 / Band 1 Coefficient 2 B1COEFF3= -5 / Band 1 Coefficient 3 B1COEFF4= -3 / Band 1 Coefficient 4 B1COEFF5= -1 / Band 1 Coefficient 5 B1COEFF6= 1 / Band 1 Coefficient 6 B1COEFF7= 3 / Band 1 Coefficient 7 B1COEFF8= 5 / Band 1 Coefficient 8 B1COEFF9= 7 / Band 1 Coefficient 9 B1SUROFF= 1024 / Band 1 SUR Offset B1SSYERR= 'no ' / Band 1 Sample Sync Error B1PSATTH= 16000 / Band 1 Pixel Saturation Threshold B1FPTERR= 'no ' / Band 1 Focal Plane Timeout Error B1TRBITS= 3 / Band 1 Truncated Bits B24NUMSA= 9 / Bands 2&4 Number of Samples B2EOFERR= 'no ' / Band 2 End of Frame Error B2SCIFSE= 84 / Band 2 Science Frames Sent B2MLPERR= 'no ' / Band 2 Missing Last Pixel Error B2SATDET= 'enabled ' / Band 2 Saturation Detection Enable B2COEFF1= 0 / Band 2 Coefficient 1 B2COEFF2= -7 / Band 2 Coefficient 2 B2COEFF3= -5 / Band 2 Coefficient 3 B2COEFF4= -3 / Band 2 Coefficient 4 B2COEFF5= -1 / Band 2 Coefficient 5 B2COEFF6= 1 / Band 2 Coefficient 6 B2COEFF7= 3 / Band 2 Coefficient 7 B2COEFF8= 5 / Band 2 Coefficient 8 B2COEFF9= 7 / Band 2 Coefficient 9 B2SUROFF= 1024 / Band 2 SUR Offset B2SSYERR= 'no ' / Band 2 Sample Sync Error B2PSATTH= 14500 / Band 2 Pixel Saturation Threshold B2FPTERR= 'no ' / Band 2 Focal Plane Timeout Error B2TRBITS= 3 / Band 2 Truncated Bits B3EOFERR= 'no ' / Band 3 End of Frame Error B3SCIFSE= 84 / Band 3 Science Frames Sent B3MLPERR= 'no ' / Band 3 Missing Last Pixel Error B3SATDET= 'enabled ' / Band 3 Saturation Detection Enable B3COEFF1= -4 / Band 3 Coefficient 1 B3COEFF2= -3 / Band 3 Coefficient 2 B3COEFF3= -2 / Band 3 Coefficient 3 B3COEFF4= -1 / Band 3 Coefficient 4 B3COEFF5= 0 / Band 3 Coefficient 5 B3COEFF6= 1 / Band 3 Coefficient 6 B3COEFF7= 2 / Band 3 Coefficient 7 B3COEFF8= 3 / Band 3 Coefficient 8 B3COEFF9= 4 / Band 3 Coefficient 9 B3SUROFF= 1024 / Band 3 SUR Offset B3SSYERR= 'no ' / Band 3 Sample Sync Error B3PSATTH= 16000 / Band 3 Pixel Saturation Threshold B3FPTERR= 'no ' / Band 3 Focal Plane Timeout Error B3TRBITS= 2 / Band 3 Truncated Bits B4EOFERR= 'no ' / Band 4 End of Frame Error B4SCIFSE= 84 / Band 4 Science Frames Sent B4MLPERR= 'no ' / Band 4 Missing Last Pixel Error B4SATDET= 'enabled ' / Band 4 Saturation Detection Enable B4COEFF1= -4 / Band 4 Coefficient 1 B4COEFF2= -3 / Band 4 Coefficient 2 B4COEFF3= -2 / Band 4 Coefficient 3 B4COEFF4= -1 / Band 4 Coefficient 4 B4COEFF5= 0 / Band 4 Coefficient 5 B4COEFF6= 1 / Band 4 Coefficient 6 B4COEFF7= 2 / Band 4 Coefficient 7 B4COEFF8= 3 / Band 4 Coefficient 8 B4COEFF9= 4 / Band 4 Coefficient 9 B4SUROFF= 1024 / Band 4 SUR Offset B4SSYERR= 'no ' / Band 4 Sample Sync Error B4PSATTH= 16000 / Band 4 Pixel Saturation Threshold B4FPTERR= 'no ' / Band 4 Focal Plane Timeout Error B4TRBITS= 2 / Band 4 Truncated Bits B3FPATEM= 7.03142 / [K] Back of Band 3 FPA Temperature B4FPATEM= 7.05634 / [K] Back of Band 4 FPA Temperature CHANAPSA= -4.7791 / [arcmin] Channel A Position [A] CHANAPSB= -1.24239 / [arcmin] Channel A Position [B] CHANBPSA= 0 / [arcmin] Channel B Position [A] CHANBPSB= -0.0012875 / [arcmin] Channel B Position [B] CURAMSAM= 4097 / Current Ramp Samples LFOLERRA= 'no ' / Large Following Error [A] LFOLERRB= 'no ' / Large Following Error [B] B1FEBFT1= 32.1641 / [K] FEB Band 1 FPA Temperature 1 B1FEBFT2= 32.0626 / [K] FEB Band 1 FPA Temperature 2 B1FEBOVO= 1.80394 / [V] FEB Band 1 Offset Voltage B2FEBFT1= 31.9279 / [K] FEB Band 2 FPA Temperature 1 B2FEBFT2= 31.6602 / [K] FEB Band 2 FPA Temperature 2 B2FEBOVO= 1.84315 / [V] FEB Band 2 Offset Voltage B3FEBFT1= 6.82119 / [K] FEB Band 3 FPA Temperature 1 B3FEBFT2= 6.89484 / [K] FEB Band 3 FPA Temperature 2 B3FEBOVO= 1.74511 / [V] FEB Band 3 Offset Voltage B4FEBFT1= 6.82744 / [K] FEB Band 4 FPA Temperature 1 B4FEBFT2= 6.88934 / [K] FEB Band 4 FPA Temperature 2 B4FEBOVO= 1.68629 / [V] FEB Band 4 Offset Voltage ANNHEA_A= 'disabled' / Annealing Heater A Enabled ANNHEAPA= 'off ' / Annealing Heater A Power Indicator ANNHEA_B= 'disabled' / Annealing Heater B Enabled ANNHEAPB= 'off ' / Annealing Heater B Power Indicator ANNHEA_T= 0 / [sec] Annealing Heater Timer IVCSADOT= 63.375 / [K] IVCS Aft Dome Temperature IVCSLHET= 60.9427 / [K] IVCS LHe Cooling Loop Temperature OVCSAFDT= 111.518 / [K] OVCS Aft Dome Temperature OVCSRART= 111.325 / [K] OVCS Radiator Ring Temperature PRITBOTT= 7.08272 / [K] Primary Tank Bottom Temperature PRITTOPT= 7.50056 / [K] Primary Tank Top Temperature SCPOSMON= 'b ' / Scanner selected for position monitoring SCANVELA= 3.65 / [arcmin/sec] Scan Velocity [A] SCANVELB= 3.81 / [arcmin/sec] Scan Velocity [B] SECTBOTT= 11.8115 / [K] Secondary Tank Bottom Temperature SECTTOPT= 11.3923 / [K] Secondary Tank Top Temperature PSEQEN_A= 'disabled' / Position Sequencer Enabled [A] PSEQEN_B= 'enabled ' / Position Sequencer Enabled [B] SERVEN_A= 'disabled' / Servo Enabled [A] SERVEN_B= 'enabled ' / Servo Enabled [B] BAFFRONT= 10.9539 / [K] Baffle Front Temperature BSPLMOUT= 11.1069 / [K] Beamsplitter Mount Temperature TELINTFT= 10.9236 / [K] Telescope Interface Flange Temperature SCAMIRRT= 10.9236 / [K] Scan Mirror Temperature SECMIRRT= 11.0301 / [K] Secondary Mirror Temperature T12FPOIT= 110.551 / [K] Tube 1/2 Fold Point Temperature T23FPOIT= 62.934 / [K] Tube 2/3 Fold Point Temperature SHNDSPTE= 186.8 / [K] Shell near Door Sep. Plane Temperature SHNPVETE= 190.2 / [K] Shell near Primary Vent Temperature SHARDOTE= 192.3 / [K] Shell at Rear Dome Temperature TLCURMOD= 'nominal ' / Current telemetry mode PLPAMPRT= 21.8691 / [K] payload_preamp_redundant temperature PLPAMP_T= 22.5006 / [K] payload_preamp temperature HK_MINDT= -0.443999767303467 / [sec] Minimum H/K sample dt HK_MAXDT= -14.4439997673035 / [sec] Maximum H/K sample dt INGSTREV= '$Id: ingestpipe 7741 2010-04-06 02:02:59Z tim $' / Svn revision L0CREATE= '2010-06-11T15:52:24.266Z' / Level-0 file creation date/time CAL01 = '/wise/fops/cal/ifr/wise-meta-ingest.tbl' CK01 = '/wise/fops/ref/mos/naif/10162/WISE_CK_2010_06_11_03_55_47.bc' CK02 = '/wise/fops/ref/mos/naif/10162/WISE_CK_2010_06_11_11_52_51.bc' CK03 = '/wise/fops/ref/mos/naif/10162/WISE_CK_2010_06_11_12_47_47.bc' CK-PRE01= '/wise/fops/ref/mos/naif/10155/WIS_PGEN_1024_1AwEditedSASF_2010_153_&'CONTINUE '23_11_52.bc' CK-PRE02= '/wise/fops/ref/mos/naif/10160/WIS_PGEN_1024_2AwSSF_2010_160_17_07_0&'CONTINUE '4.bc ' HK01 = '/wise/fops/ref/mos/hk/hk.db' SCLK01 = 'WIS_SCLKSCET.00021.tsc' SPK01 = '/wise/fops/ref/mos/naif/10134/WIS_NAV_SPK_WISEONLY_14May_RadarOD_20&'CONTINUE '10_134_17_55_18.bsp' SPK02 = '/wise/fops/ref/mos/naif/10137/WIS_NAV_SPK_WISEONLY_17May_RadarOD_20&'CONTINUE '10_137_17_44_28.bsp' SPK03 = '/wise/fops/ref/mos/naif/10139/WIS_NAV_SPK_WISEONLY_19May_RadarOD_20&'CONTINUE '10_139_16_56_30.bsp' SPK04 = '/wise/fops/ref/mos/naif/10141/WIS_NAV_SPK_WISEONLY_21May_RadarOD_20&'CONTINUE '10_141_17_32_22.bsp' SPK05 = '/wise/fops/ref/mos/naif/10144/WIS_NAV_SPK_WISEONLY_24May_RadarOD_20&'CONTINUE '10_144_17_45_07.bsp' SPK06 = '/wise/fops/ref/mos/naif/10146/WIS_NAV_SPK_WISEONLY_26May_RadarOD_20&'CONTINUE '10_146_16_49_52.bsp' SPK07 = '/wise/fops/ref/mos/naif/10148/WIS_NAV_SPK_WISETDRS_28May_RadarOD_20&'CONTINUE '10_148_17_40_46.bsp' SPK08 = '/wise/fops/ref/mos/naif/10153/WIS_NAV_SPK_WISEONLY_02Jun_RadarOD_20&'CONTINUE '10_153_16_52_14.bsp' SPK09 = '/wise/fops/ref/mos/naif/10155/WIS_NAV_SPK_WISEONLY_04Jun_RadarOD_20&'CONTINUE '10_155_17_52_43.bsp' SPK10 = '/wise/fops/ref/mos/naif/10158/WIS_NAV_SPK_WISEONLY_07Jun_RadarOD_20&'CONTINUE '10_158_17_38_47.bsp' TLM01 = '/wise/fops/ingest/delivs/10162/10162T103705/WIS_HRP_PKT_FE1A_2010_1&'CONTINUE '62_10_37_05_CAT.bin' TLM02 = '/wise/fops/ingest/delivs/10162/10162T103705/WIS_HRP_PKT_FE1B_2010_1&'CONTINUE '62_10_37_05_CAT.bin' TLM03 = '/wise/fops/ingest/delivs/10162/10162T103705/WIS_HRP_PKT_FE1C_2010_1&'CONTINUE '62_10_37_05_CAT.bin' TLM04 = '/wise/fops/ingest/delivs/10162/10162T103705/WIS_HRP_PKT_FE1D_2010_1&'CONTINUE '62_10_37_05_CAT.bin' TRK01 = '/wise/fops/ref/mos/track/WIS_NAV_WGT_04Jun_RadarOD_2010_155_17_52_4&'CONTINUE '3.txt ' TRK02 = '/wise/fops/ref/mos/track/WIS_NAV_WGT_07Jun_RadarOD_2010_158_17_38_4&'CONTINUE '7.txt ' TRK03 = '/wise/fops/ref/mos/track/WIS_NAV_WGT_09Jun_RadarOD_2010_160_16_20_1&'CONTINUE '4.txt ' LONGSTRN= 'OGIP 1.0' WRELEASE= 'release-v3.5' / WSDS s/w release tag HISTORY instruframecal, v.$Id on 2011-06-01 at 06:45:16 ICALDIR = '/wise/fops/cal/ifr' MODEINT = 11.8750247955322 / [DN] pixel mode within lowest sigma partition A_0_0 = 0.71840158569451 / axis 1 distortion coefficient of v0 A_0_1 = -0.0001339348 / axis 1 distortion coefficient of v1 A_0_2 = -7.865474E-06 / axis 1 distortion coefficient of v2 A_0_3 = -2.197543E-10 / axis 1 distortion coefficient of v3 A_0_4 = -1.650831E-13 / axis 1 distortion coefficient of v4 A_1_0 = -0.0009202705233 / axis 1 distortion coefficient of u1 A_1_1 = 1.319662E-06 / axis 1 distortion coefficient of u.v A_1_2 = 1.40211E-09 / axis 1 distortion coefficient of u.v2 A_1_3 = -4.91699E-13 / axis 1 distortion coefficient of u.v3 A_2_0 = -1.490523E-06 / axis 1 distortion coefficient of u2 A_2_1 = -1.774975E-10 / axis 1 distortion coefficient of u2.v A_2_2 = 1.378454E-12 / axis 1 distortion coefficient of u2.v2 A_3_0 = 3.075193E-09 / axis 1 distortion coefficient of u3 A_3_1 = -4.456145E-13 / axis 1 distortion coefficient of u2.v A_4_0 = 1.268865E-12 / axis 1 distortion coefficient of u4 A_DMAX = 2.075 / [pixel] axis 1 maximum correction A_ORDER = 4 / polynomial order, axis 1, detector to sky AP_0_0 = -0.71893638569451 / axis 1 distortion coefficient of V0 AP_0_1 = 0.0001357412 / axis 1 distortion coefficient of V1 AP_0_2 = 7.867368E-06 / axis 1 distortion coefficient of V2 AP_0_3 = 1.974483E-10 / axis 1 distortion coefficient of V3 AP_0_4 = 2.082039E-13 / axis 1 distortion coefficient of V4 AP_1_0 = 0.0009199584233 / axis 1 distortion coefficient of U1 AP_1_1 = -1.316141E-06 / axis 1 distortion coefficient of U.V AP_1_2 = -1.337672E-09 / axis 1 distortion coefficient of U.V2 AP_1_3 = 5.028983E-13 / axis 1 distortion coefficient of U.V3 AP_2_0 = 1.498243E-06 / axis 1 distortion coefficient of U2 AP_2_1 = 1.688004E-10 / axis 1 distortion coefficient of U2.V AP_2_2 = -1.462359E-12 / axis 1 distortion coefficient of U2.V2 AP_3_0 = -3.081662E-09 / axis 1 distortion coefficient of U3 AP_3_1 = 4.586934E-13 / axis 1 distortion coefficient of U2.V AP_4_0 = -1.281481E-12 / axis 1 distortion coefficient of U4 AP_ORDER= 4 / polynomial order, axis 1, sky to detector B_0_0 = -0.08004934299 / axis 2 distortion coefficient of v0 B_0_1 = 0.0002436832947 / axis 2 distortion coefficient of v1 B_0_2 = 1.053036E-06 / axis 2 distortion coefficient of v2 B_0_3 = -3.251883E-09 / axis 2 distortion coefficient of v3 B_0_4 = -2.838759E-13 / axis 2 distortion coefficient of v4 B_1_0 = -0.000303693200000004 / axis 2 distortion coefficient of u1 B_1_1 = -3.427523E-06 / axis 2 distortion coefficient of u.v B_1_2 = -2.803326E-10 / axis 2 distortion coefficient of u.v2 B_1_3 = 8.58959E-13 / axis 2 distortion coefficient of u.v3 B_2_0 = -3.894091E-08 / axis 2 distortion coefficient of u2 B_2_1 = -4.203492E-11 / axis 2 distortion coefficient of u2.v B_2_2 = -2.228216E-13 / axis 2 distortion coefficient of u2.v2 B_3_0 = 1.011286E-10 / axis 2 distortion coefficient of u3 B_3_1 = 8.828245E-13 / axis 2 distortion coefficient of u2.v B_4_0 = 8.859376E-13 / axis 2 distortion coefficient of u4 B_DMAX = 1.071 / [pixel] axis 2 maximum correction B_ORDER = 4 / polynomial order, axis 2, detector to sky BP_0_0 = 0.07982358299 / axis 2 distortion coefficient of V0 BP_0_1 = -0.0002452664947 / axis 2 distortion coefficient of V1 BP_0_2 = -1.048795E-06 / axis 2 distortion coefficient of V2 BP_0_3 = 3.275245E-09 / axis 2 distortion coefficient of V3 BP_0_4 = 2.705566E-13 / axis 2 distortion coefficient of V4 BP_1_0 = 0.000304400300000004 / axis 2 distortion coefficient of U1 BP_1_1 = 3.427776E-06 / axis 2 distortion coefficient of U.V BP_1_2 = 2.689765E-10 / axis 2 distortion coefficient of U.V2 BP_1_3 = -8.272042E-13 / axis 2 distortion coefficient of U.V3 BP_2_0 = 4.010036E-08 / axis 2 distortion coefficient of U2 BP_2_1 = 5.357017E-11 / axis 2 distortion coefficient of U2.V BP_2_2 = 2.224483E-13 / axis 2 distortion coefficient of U2.V2 BP_3_0 = -1.03665E-10 / axis 2 distortion coefficient of U3 BP_3_1 = -8.917543E-13 / axis 2 distortion coefficient of U2.V BP_4_0 = -8.85461E-13 / axis 2 distortion coefficient of U4 BP_ORDER= 4 / polynomial order, axis 2, sky to detector SKEW = 0. / [deg] Y-axis shear DA_0_0 = 3.38569451E-06 / Diff. abberation distortion delta DAP_0_0 = -3.38569451E-06 / Diff. abberation distortion delta DA_0_1 = -1.80429023E-20 / Diff. abberation distortion delta DAP_0_1 = 1.80429023E-20 / Diff. abberation distortion delta DA_1_0 = -6.36367233E-05 / Diff. abberation distortion delta DAP_1_0 = 6.36367233E-05 / Diff. abberation distortion delta DB_0_0 = -6.133299E-05 / Diff. abberation distortion delta DBP_0_0 = 6.133299E-05 / Diff. abberation distortion delta DB_0_1 = -6.36362053E-05 / Diff. abberation distortion delta DBP_0_1 = 6.36362053E-05 / Diff. abberation distortion delta DB_1_0 = -3.71329006E-18 / Diff. abberation distortion delta DBP_1_0 = 3.71329006E-18 / Diff. abberation distortion delta DIFFABBR= 1 / Distortion correction applied L1ACOSRC= 'v3.5 ' / L1A position source DRA0_1B = 110.313908790091 / [arc-secs] L0 to L1B delta RA DDEC0_1B= -86.8407676951673 / [arc-secs] L0 to L1B delta Dec DPA0_1B = 15.0586048997866 / [arc-secs] L0 to L1B delta PA COMMENT OLD CRVAL1 = 202.50027268534 COMMENT OLD CRVAL2 = 47.1503407675767 COMMENT OLD WCROTA2 = 144.518212612586 COMMENT OLD PA = 215.481787387414 COMMENT OLD CRDER1 = 0.017 COMMENT OLD CRDER2 = 0.017 COMMENT OLD UNCRTPA = 0.017 COMMENT OLD CSDRADEC = 0 COMMENT OLD UNCRTS1 = 3.0E-06 COMMENT OLD UNCRTS2 = 3.0E-06 COMMENT OLD WCDELT1 = -0.0007664145 COMMENT OLD PXSCAL1 = -2.7590922 COMMENT OLD WCDELT2 = 0.0007614076 COMMENT OLD PXSCAL2 = 2.74106736 COMMENT OLD CD1_1 = 0.000624091377399945 COMMENT OLD CD1_2 = -0.000441954582185511 COMMENT OLD CD2_1 = -0.000444860807967267 COMMENT OLD CD2_2 = -0.000620014258402974 COMMENT OLD WCROTA2 = 144.518212612586 COMMENT OLD WCDELT1 = -0.0007664145 COMMENT OLD WCDELT2 = 0.0007614076 COMMENT OLD SKEW = 0. COMMENT OLD PA = 215.481787387414 COMMENT OLD PXSCAL1 = -2.7590922 COMMENT OLD PXSCAL2 = 2.74106736 MAGZP = 20.752 / Relative single exposure photometric zero pointMAGZPUNC= 0.006 / 1-sigma uncertainty in MAGZP (-999=undefined) END AĴA_AEAq +NAxyA)A)AH.@@IlA\AI^A?`A1AnA-%A3C +ABմAToAiYA4\AAQctAR/AHq ARA.A8ԓA`AoAvAYAwA$AXAAo}AH[AxA A?AtAAAADAwAAmARA?`Aw!ASANARAAA5pAAoAA)OA}XAX@AAeAAwAJfAAnAkn9AJA"AB;AaJAhDAvANAAϏ A0AEAB$AdA9ABApAB}AAgAA)AsAL^AAmA_A,A#A:A"A^SAqAA Ax{AYAi#AX?AAkAlAAAWAA9AH@hAoAAF^A%AuAF3A=CA-Ax!@A{AI#A@dA*>A gA AlA7IA'AswA2XAW0AەA"MyAMAm8A[@AoJAn7AVhA ۮA_AJAjZ@N@'AAJAcU@@0A aActA?`AI$2@̇A1yeAV)A)AxAEAGHAAzEAAA:AxAJ iAmA%AnA65A&A2A*NAXKAaLYA?AyA/5A1A`AQtA}A A AxvAZ@AA=AA~.AAKx?AA|A[AQAA8A9AoA]AAtL Al'Ai}A{AAPAAA^A8AxAܷAAVA.bAIA9AA*A>A04AG6Aˣ&ArBkAf[AnAqZA)BPABAXAA$Aϡ|A2BBA-AhB'AA8A,ArAG AAʜASA} AOAAcA]EAtAWAG?AXAYA>A +AwA8AX9APJA6~AAGfAUAA AAMeAtAc3A@҄AAQjA4AagLA4JAt\AA|A\*ALzAY8A:AGˮAn(A.AcAN Aa!A[wAiAAFAQ;AAA$A`~AcAP4AEN@K5@r@B=ANAA1@DAA1A.]@r~@|@!A( AQ: A+(AnA7AYA4dA]AT]ADAA#@ĺAAPAA:iA9A W@{A?A n&@A$4A ALAiUA8@Agn@:A?AyA"'ASAt9A,AAA?`AYm@DA2VA-GALչAoDAړA2A[v-A:yAAnApASAeASDvA?`Ax4A1Aa ABnA@aWA9A;n.A@VHARAbAAG(A?`A:$A,AApAhAzAAAaoAG3AAAjAwAXHA3 A$A`}AҹAMsACAmRAA>AtAdA+AwpAĒAhA^A"AHAA=A&DAMJAVAtA܀AdTA`A,AgA\"AD@AĵAeAz+AqAOA["ARAqA;AH AsA<_^@v!A1 +AA0WUA A3eA?`A5 At̳AWPA_}AA'!AOA|AEA?`A]cA0 A'foAF2vAOA)AP2ADQAb~A$A (@Ab*jA\SA?`A>AY@^QAzA?`A{ABANƖA[@%Ao~Ad#AYDAӠA*cAuA?`A7AA,睿(AA<7A*:A@`AhJSA]AlAS&9AAAXaAbAiA9A]AUAfAnAAA~PAmA>>@NAAK>A2ABAAlA#$@'AF.bAAeAdAOA37AZA?`AD3AR?AA9An*w@ tAhAڏAkiAuAxtA{xAXAAnAAIA AnCDAxA-A6ATAAAAAAςAryAWAGA(A#AA鞑AvAħAŒA-AAfAAAΘA)Ac@AnBg@AAA~AcAHAABqrABAKB uAPBS_A~A&A@4B~AnAAWAB~AGABAPA^A_AHNA~AAAA +kAAəArA\Q +AB@A\LAAA}lAA5AAXA$A?A !A}oAOA]AXP*A>A.?AjAzC=A03Af\AA2 AUVVA"\At @ԶAIoA!nA0+AAA;vAKA_ADA~lWAODA&VAABA?`AFQA GAjA@A?`AVAloANA 8AAi2ZAm~Ag`JATWA?ArAvrARAfA><AZAFA;AcA( ArA6'ArAAACA(A:A+A;AA{qAA+A?`ADQAT:A=EAA/bAAAHAnANA؆AY`AgAEA$-A +A}?AEA19A^=AXԴAvtA_+A$Af'AAkwAA?3A,AmJA5`@A6fAYAA@-AA!AA)$A`A>AA jA,.A~DAߢAMALBApJAεALnAOCAAAczASVA,A#.AAsA|RGA~A?`A~AA3Ah"A/A`AKATAA?`@pA8AIrrA?AvA +A +sA!%{AZWAAAA}0A?bA_ tA \A?AAVb@AL A߭AADlAw`mA?sAA$AASATAAAVAvAAA^AGAAA}AA ANA2AyrBQ,A^AYAAAcADALAQAAuA/"AOAlAgA0A%A AA0>AèPA/AqAAAXAީAPARAΌAA)AAWB +BAB +_AӌALA;B DAӾxAABpAoARxAPAAu&A4AΰA2AA Aq,A\AA#AZxAAHAAI~Au9lA^CAA`AŬAQuA;AZAU ARA6ArAAװbA8~AAyAAO9AA3A6V@zAAA@*Aj.A:q'AmzA+|FAA:AwRAA1LAa%AF~ANقAiGAmA?`Aک@PAxAAAAAA6s@?5A@:A?`AVAAlA +A 9A5'(AB%AAa{AzhyA=AXAҷAWADPAm)5A|yCAAFA?`AuA EA [AQAAxASA:AzA6pcA2A(A?`A)/A-AYMA6A;'nAUhACVA&A@_AG*ATɗAA0AXA~u'ArNxA ,}AjA. AiDAlA[A^HAiz\AAgv AA'AiaA9/A@d AAҠAAUA|RAkAAґB BAAx4AQB6BGB +I AAkA+A|AFApAZAAA&ALAQSATAAA{AArApAvAkOAAĪA.AAsA5A/AOAAĶbAΙA}SB+A?AB&AץAlAf^A^BA}AЍAVA^A}AAbB +AA|ARA̗JA‹ASAA֤A?`AšAA&XBA#AA(ALAAjAA#AbgA-AA6AA%QA:AwtAkAaUAAY%KAFLAG< A!;AZ A A A5AaW%AŃAA4MAL'An|rAxnAFAWA?`A?`APA:i@A"A?`AcA#SAog @_AA|A_p%AFvAAo)WAXA2&AlyGAm0AQ0AL}AWATAn"ALbA{$AP6AA*HA,DA?`A>AbAA30%At@{A6)F@AqAIoA-1A0A AuA/A?`AfAB1AQAo<)A{RA|{lASHArARA,A5A&eAAXAA=AA}4sAlA}A`AvڬAnAA0\AAuACA(AοA+A &A}BOAå,AOAjAASA9A$fA>A?`AayA9'AA)A9]?xA"AGyA8;E@EAQKA+AA5AmxAB@A"A -AAfAgpgAxA9(AEQA?`AqAgVAbA;caA?`AwTA&)@A2+@AKAVPA%8AQhJA%A0A.A-8pASAOAnbAzA!1AMbA@A^AhA(ӰA`A`AbvAASAA$ArDA,A&AamQAvA-AU=gAZ +AFAoA3AqA`AFAp A'AwXwA/APA%FAA!XA AlA*BB"FNB;xAᙃABARAϥA/HA̿AA\A$rB3ZAAڝAQVA9A +AġApAB oA>zAAAԒAAvAϾAcA6AAAšANA.8ATB!A^BaBA.AB&AAA +AAmB6AAkhAAAAڋ*AIA5A^.AmAcvAAAT AԟFA6GAAAPA>AAAZ&=A[AמJAK7Axo_A`AUAAhA7AvJATnOAHAAMAEA`:AA;A>A@AMuLAcAbl/APAsADA2@A1-AaQA{AYAA SA=*A;-AJnAA?`A]IASܢAndA AfAAA@-@@ADAPAªA*hA:!GA-4JAQgAnAAAAnAA6AnAJ@BsCBAlAreA<~AA+lA}\AEsA0pXAN.A@oA AM-A9ACAwAg)A>AYApgAYY*A?`A9A"Am*A"WAtASA/PdAIA/VA"&AS$AsAiAn%AcA{YA|ARAAJlA[<-Ab"A/Ax,A@A91AMAe$AsAIA`:5AẊAhAKyA?`A'@%AG?A|CA&KA?`A?`AXAVH@VlA/ZA[jA7ADA3gA?`A| AاAAGA$AkAzAZ BӉC Az"A^A.A+mAAWVAm&AH*A[ApxA@jAOA A4AbA@&AADyAuAA).fA{AAGA$AAS`AbAwPVAcARA0AAx$BALA$AAFA _A AjAx.A_ +AߒA^ATA͢AyAAAIB +BA54A5AcBA"}AJ AKBA Aש ANA A`dAALA,"A*A{A?AѷAqAA2\AAUAlA~A:ANRB hJAAԜ\A&AZA?AfAlB!q9AJABaBBB aAqB)B7\ABBB(A*xAdAA@'A\AAA;A1AA|]4AښAAbAKAmA3AAYAzA A*JA +AAAj+AAAl]AOAArAfAwAVA{G#Au3A"vAdAb8AXkAny"A(#AWi4AA^T@XA?`AgAu`AKxAAAGY1Af-A-϶AGxAA6AW?AlhA@9-A[AXJAogAAM,t@+AB3A-iA7AtA A?`AuA֒A@ 1AnJAZU8Av7Ai@As AB2cB3A&ASAGA?`AoAwu%@ԘKA A K@E+AE"D@*@5A/KAZABAh{@APAwA:gAARNA76A`dAOVAg$A? A!h_ATJA]AAK$AG!TAwA24AAoJAbAZAANDAA~AA;"A9AmAAAzVA٦AJAbAسAARAGAgA~A5A^A_HA]bA2AڧA߼AAA\A +AAA@A6NAʐjAAyAŖAGAțAAتA/AAĢAAThAfMAOUAޏAA×.A{AA ADAhABKA+B|BTB +B B )BBAjxABmAĐA*BAAvFA|AˊVAz A:AeA8Ab=A-AĜAA AuAl_AoA&A"A!AA7AzAwABAcAFAwrA:A}/AA]wXA@%AAxAhA P@ AssA{+AATIA8AAEQ$A\dA{J+@p@AfABAABB:BANYA@,A-jnAȳA"A?`AAwA:8A8A\FA;@>AY]S@YAr-AD%A}AYA7eA?`@UA?`@ԀA3AA*ݏA/dA9jXAgMA[h>APK!AdAFA AVAA3eA2FAAYjA،A˜AuA!ADAKAAY.AJSAA*ASPATA~ArrAwA1AºAEA'jA>AA*AʚA+/A)A AhRAA*ZAņApAB9AARAA'sB +BI +AԊAH6BB +>A`AKBA;PA&BuUAAHB AXBAAAwAgxB +LAA?B(AԭAA6B}AwZAABA/A"AqBlBB +BAΚAB[BFArAӑhABAA`WA5APA"B *ANAvA8A+gAA.ArAYAmAA+tAA@AcANAjAA\-AB A¡AЧA:vA +AAAGTAUFAA]1A,uA³AOƔAMAjRAmdAMAi/AgmAA4ATAmu=ApMAq-APA2A@AiArAŮAA8A|AAAklAA ArACA}AdAbTHAAAAɽAZAAZAAtAnA1>AAbAQAPA#AAAAAó@AAAͿ&BTAn&ADAAAEAA9AgA/AhA" +A A~zADŽA4Ah,B aAA9NA%B#MBB ACBBLSBB?A;A BhB GBAAPA˖AAAA}&A:BNAjAAЈA~AuaA,A8eA AC8A5A AAADAUAAAEA$AAxAv*AB +AJAbA?`AHAk)AAAAANAWrAQ4NA#VAeTARACA[AEAHUAjCAAuoAAoRA2AQ=Ap~}A,A|IAXUAACAS A?`Au^A(tAm1@AnA=A?`AIMA*9A2&?AZRA"A(xvA+A {A4ADA}TAAY%A|A- A1eA+^ARU(A_=AbApAA0!{A21UA~AsA +AIA]^AGASAr"AA ALAAA_7AA"AfsAA zAAbLAITAYtA@AAhA\AebAAAAA A^AؗAAAtA6DA qAӣAYBAA@B^A\FApAa>A۾BA~$AZA$A̧VBPA}A,AٔBBAnB ,A7B'ApAA`}A;BkABPB nACXB>AQAxHACpAŜAB tAfAAAnA A~B2RB AImB3BBIB"BB#A}AXAu +BB~ABXAɌBAYB A@&AzAMjA_B >DAJAbAizA<AͶZAA؉AA`dArAAJA0AMkA6AMlA\AA\AA(AAAQAoAlA`AiAcA[A=8AAzAN?A^ .A1RA4AH A,AwAAGADX@ +rAqDA"f AktAY_AOTAAqTAAǎA>B 1AEA +AAApB)BrBBzB +FB +BAAb_ASAAA/AhB}BB5fB&ZB#~BB +fB bBrAeB,BW:B ;ARFAAA2BwBjAAJAAnA-A?xAѪAAAAAhAłAAFAmA dAq"AuAfTAuAAXAAAAYApAAA`AAlq&Ar3AMAAA AA?`@AA?`A$ jAAA;AtA]@AʕkB&A\BrAvA B";B0`BAB xBAAzB  B BqA/B BBmB31B+&AA +BuAB {B +RB *BDAp&A&ApAgBBB YBEB:$B,A#AsByBABGB%Am2B>B +B AMBu#BABwqAB~AŴvANAbA͞AV]A=A̧A]AϔAuAiA̛AIHAE`A3ADA^AS>xAAo#A!A\AA9AHAl޻AiAAOXA>A]PiASAiUAȈAAyqgA"A4TADA;=AY>qAbA1"AcAe6A[A{ADHAEA(|AhASAڟAAUQA A@A3An!AwݛA)ABARA{1AAVAAIA6AKAAq}A\_AMyyA$3AhgA,AesA2oAA@ܣA {AA2-A AEAwAIA]@AP]A.AC^A0A\>AsApAEoA$AJAB jBE$BABA ANAAGANAUTAA^AۘA.A6`A۠A^gAqPA +A:]AdXAA݇Ad +A1;AA|AaxA连AUBGFBB/BB gAwB$4IABoAۀAA~BaBzBmA? +B ALBB+B DBB)CBB$_A(BzA0AU@BB &cBPB ?AB +LBAA!UArAꝬA:AAYoA[DA;AjAyA=JAA}{AjA/KA@AfAqRAHA AAAqϕALA?`A4PA'A\5AtN6AvdAmAJA?AaAATSAA_.Ar A?`A7A7A;LnA'ASA HAeAF+A?AҐA%A,A~cEAyAh AAeBAAk@A#, A}GA?`A]|AceAhS=A8AA|A×Ag4@A4AWR8An'AA1A{}AhA@1AN=@Aop;AAAJuqA@A4A}AA; AQA?`AUQA@Y{ATTA:A_)^AArpAXAAAlA3*AB =B,C.xBLA-ArpA AAAAAB}APAAȌA4AAB +/AЖA?B`B&.AqB'B VB [lAbBB3TB2%BBJ_B"aBA0BB +B"BW,B2B<BouBBLB"B*S|BNkB0B]B:B,BABB +ƢB'BCBGB3B" B0B BaBB,B aBBNaB'B BBXAA~A?AADA4ZA AEA AGA*A#A/A{fAA^AJAݾAfAAACA"AzA Av-A?`Ap[AAw6Ap) A*AUAAA4jAS5AYA)MAAmA]ARAUrAX3A]3AMAcAAyvA AWbAAS3WA}uAH@.AAG.AhaPA2A 1A;}A@g@ _A?Ady(A#\=AJ@ A?`Av8AJA@vDA$bAfA"Ar,ABAPA'RANgkAsAZAQ;Ae Au?A_(Aj? A +A>AAjO>AƞABa dB٘bB@AZWAeAsAqA͘AH(A:AA +lBAӰ@A=|AAbAKB8A5AAA:A!AwAAcA:GA~fAծHB7B1\AڱAA%BlB/~BCB' B4BB +)dB/RBxB=BNB%=OBB! BqB0*B6$B=ABOBB B%EB0GB/_XB!£B%SBB3_^BB B B"BtBPBGlB|BBiBW0B@bB@B+6B5B3B.AB ^Bt{BdB%B$B YBBAA9A"B SB-A}B 3MAˍAcAAΔA3A$AZAMÄAMnAnzAa|A0A\AA[[sA?hAN4A,IAA8AAS˭AL8A&AXhA(|A,aAD*A5A~A?`AOAc$AAYAA2A?`AOGAzYyAaAt=AX`A:A AJXAh$~A[AgQ,AY-AKmAGAtӐAqzAAAAjAyUA)iAQ A2A/qAZ]AA,A]AZ1A~A&A*A+A'GA_AAx&A?`@sA5@A{Aj1AJApAǎAA6A?`A^AA.A`A-XA+AC ADAq3AAmA\ȨAAkHAbAeuA@/#AaIA"qA=;ZAAA?`ArALAWVA_XAjAAZA=AADAAwPNAhZAA?`ABKBBoB(B$B"B"B&BJA'LB A:BA呡BEA6B\AH1B&LB}B *AAAԢAA]OAA0A}AA.DA>A^@fAAAt3'AgA*@4A{ AU@"A}aNAq'yA'AiA|A:AuAZ3AIcAzACA"A}AfTARA~1Au=A.a)AzAйAIAfAAZ=AmUA9A?`AIA0 ;AIAJA?`AAHtAՖA{AA;4+A!6AA9AAS0A4AAב:ABAXhANA +CAљA⻲A`ATA8AآAA}AAHAAFADA"B#B/B(B+bB O2B B-rVB5B=rB0HBB6B@BB'qB:;BMBIRSBL;B2;BFMBVBlBJ|BpBbIBh:Bl BdtBfWBSexB\MBV8BbB\BjBU#BO B6I9B[6BrBMBTBS:BCoBPNB$B+BvBP\BVB,3B5;B1B'YB ]BAAEA2MB#-bB5B{BAcAAbAtAxBmAA5AA.AJAzAaJAA_!mAwUA?`A!A`.dA0A^LAGPAArA=@nAFA>A"AA P8AZAvA=fA$Ai AIBAAF^A*AAvAAuhA AҩA_.A7A$A7A|AͬAwlAєB A9DAԳAaAwBArBMAiAAHAQAA9AIA)A%AeAQBjhAAs B nHAAtABB9CrB,ABBIBABB&$7B,1&B BM\BEFB7v\BWB7%B:BPzBBNBBXlB~;B\-By*BT|tB}"dB"BBtsBn]>BkHGA/ĚA~AFA&A{uAV&#AWA%A5A}>VAɪA~AiAA|AǽA AIA?`AA[ArAAAABApAݽA*AFAJA葦BvAAAAl2BAAAATpA@B +A{ABVBB!AABoBJB,B9+BP,Bk +ABQBB6jAVBBCBBQ#BB-BSB/kBg=[BXB0щB^BqBm0B,BBB"BBPB.B>B?uBB~B8BBfBByOBpBv-BsBcjBY/BbBjvBInB#|B4cB!Z(B9BBBB qB4BNBBzB ABAnAA.AAҮfAAV|AA5AghAA&AA}2AAʀAmFAYjAAAktA¶A{[HAPAcWdAyjA`AAA~UAAmwA4AA|ADA8AtAUAAV%3A?NAe\A4UA#qA;dAlAD A9AABA$A0AAm}ABA@4APA'I?AP {A2bHAADA[0A?`A ?JAwAQkA +A{AHAQ`ApAO"AKA;HAn5A}`Aw4A-A=@hAvA{lAqAZhgAl[A4(AAGB ,B&B4AAjB|BCBB!4BޤBB B*-B2?BABWA BQriB`*BM@BclBoBBBBB/B@BoC);CB#BNBBBiBBlBBBPBU BlxBtB|PBs B{w|BSBC:5BCB>$BI}B/5BB%6B BA`ANBOBGA +AAAU AAAAA.AMAA$dASGeA??AAzAuuA{AÚAAyAAiAAx&wAAlx[AKAtA<\}Am}AUuAsAq:AY@ZA[AIkARA~A,Ac A/A?`A_L A/Abr Ar!AbcA;RApAAGfAǷAv>AeAˤgAl`A.A].AxAA5:A-AbA AAFAAf%A,]A鐻AA=AoARB [AA YA@AcAZAAAAz:B.A4AADAA-/AasAHAA*BA\hB)AoBAÊBUBBhAx9ABkB B B,BBb"B7]BDRSBHrBeB7GB~SB[BABdBR[BBBBB~CRICHBAsyeAAA AFAAwAjҫAA)AA`BfAyABAxgAMAAhAdOAYA!ABB .AʴAmAl*B L|A넜A&B_*AtADAS8BSAe A$A@A03ABaB4A49A޳tBtA5AЭB~BzBBB CB!CB!B/BI; BR~B\BBB>~B`BqBBGBΪB2C*>BAEB(BPBzeBBvBZBBBiByB5BaBB\B9YBqB(fBBq/$BgtBgaBMCB^tABP )BFB/JB=B#\tB2B_B# AAXA+(AvAonAaAAAAb@-LAg&AiKApAAAAA7AB1AHAA;@DAۏA+AoA8AbAZ#Az>A4AzFlAUA~AZAMArAcAigA;~&Ah\AEvAo4AζA[Ai%A@A7~AAb-AFzAe @FAA!F`A'@݌=@΅A8ArA5@A AAXAqzAGAA&AA3AAuA\ATB +{AA;A5AA6AǝAa BRABuAzABMAުAҜA_BAAؗ4AAbAAB ANAAKJABBNBEAႈAA_B B zB BgB'B+nB*B?/B8gUBt0BBB@BPGBBBXwB5OBcC4B}C,Bx}B1WBvBB'6BBeB BBή=BmBB=Bd5BTrByBBGB$B?"BlBx&B=IBZtBHB@tBBsB3=BbA}AB B&7AAAA\B B 1A!zA2A.kAJA#AAAAAAA BAAGAATA.A.Ar΁AAaAaAAiA"AIA +~A?`A;zNAeAw(Ao@fACCB˃BߪB>BeBnJBڪ1B'BWB\dBqB4B*BpEBgyBoB;BX BNB)BCmBAB'B BJWB+1AyB6WBA諄B +B*EBEBzAVAA@A AAAAA,ACAAnnA5.A'AZ`AzAAelA§AAAmAW"A2Ao=AńA܄AsA AzAAATA +&A?`A\pAzAjA,UAaʫAv*jAAAEmAA sAQ 8AdAAY^"AI^AqArAv AtAUAq|A>k>AtkAGHAAWG)A{ފA6Ac&9AhA)ACA}\vAUM|B5%/BB4BBHBBA\AyB AړBBB +BAAA˸AxAhAAFA=(A6AgA.AZAAApAAA?A[A(A2jA^AŜA`AmA|AA6AA>PA6ZAiOAAsAA"AABAAU`AjEAvAj.%AaA_ +TA@3A@zAAߗAYAAAAA2twAŃANA7Ae2Akt_A_s`AAm~VAn4RAuzAADGA?`A5AA@̶ AfAL\^A +ASçA-#AAn;A>AABOlA?`AUAsAAHsAxAAoAîA@AoAaAA鞁Ax AARvA#AfAaAHAoBA鴶AB AAKCAA7AП5AAA0ApBAZÅAAeA B BVaAسBA螷AfBHAC\BBBB*BHLB*vB|)BO*BNBBzQBB CiC1'C2C=ICSPCKrCHLCG\CXyCXCVgzCV:}CCĹCH]+CHYC3qC3z\C:(C'C%C!C%Z>C CdCbBaBBBYB;:B>BLzBpBxBigBuBrXBPB+`BdyBCB)eB+ͨB2BGBB$B DB- +BAتAн$AxA=A4PAAAʨA*&A8AFAA1AAAuAAflAAbAJA?`AAuA{AAAcAAJ\AAAAx4A9oA?`ASAA|mA?`AhAeqAq3AA?`AAOAAuEAnABAAS|AFAAA?`A&A2AҸAA_AqAABA5AA҄IAPAI8A8AA1B(?SA|7AAAеAAЇApA{AUAATAA4AA(A\uAAlAA6ApASA9AANAayA6Aw^ANANAA"Aw[@AlQAA;AODA@;vA?`A +=AA@^A!{AA^{AAtfAAXA=,@(AA#iA?`AAKAA4HAvgAL?AlAֻrA0AnAn2AYAXA'As]A?`Ab?1Au2#AXJ@p|AG| @AwKA@AQaALAaAܰAcAkAI AAY`AmAĜA2AA+A~AAJwAGBhANAbA/BeAVA +AůpA8AAvA$AۗA.AMAߙAJABtALpAnAxAT.AAAG*ASAxAAAWBC`3C\CX\CKCFjCIC/iC6nC&C3CxBC cBB^B͆BaBSCBBaBBBBgB8xB=+=B?GB#ϢB^:AkJBBABB +[A2&AB +AƵAs`AЭTALvA¼AAq AVA͓A(1AU7A#A%B#B1B:BVBAAtrAA AAAsmAAAdA0A(BAL-A oAA0ZAஏA AAЩBYAlDBuA A9A^9A^AύAAЀB~B +(AAAAiB!)BdaAlA܈NBAesA.BYAڜBBABBTBVB*BApBc*B2B4BBQCBB15B0B4B_؂BޔBy BuBhC*Cw]C3pCKC\CdwC>JCЭCdCC0C}^CSgCCjC]CXCsMCi (CdbCjPxCo;CNC[YCR2CFOCB\C3IC=+C"[CCCKCyBjFB~BV~BPB)BuB7BrB`edBCBmTBAB7nB%QA%BA&BACaChpCiߖCWWC]CMC:%@C2C,C&C +m]C"B!`BB殺B̻BB1B$BBa'B LByBGBUB_rBhݚBB,̟BB4BBB/B-BRyB`ANB@BfB JBAƄAKzA]AA +A 8A%AAVAPAw`ATAϜAAQMAAUOA7AW.A@A.AlAeAAxAA_AeA4AAzA~AaAvA AROACA|AnAB@0NAA?`AfAvANAZ@AAAA)A@AAA8AAcA5GABl ARBAA9YA~AApA9tAAAJqAP9 A5bAz"AA=AR +AXAA^һAAADwAAWAAAoBANAAهtABB B^AB(AFA=B +AlA]B2(AA$ABAB*KFBH9EJ DuB[AQAÎABoBB mBtB +sB?BAB BNB9BIiB<BbӤB7B?B͝BC%C,ICK|;Cd}C]C{CC+C\C[^C9C1C|CCCLCCzCZCCwoCp2)Cq`CnCz};C^ۡCd_CMCQY>CKVC7= C.C!1C]Cn BlBBBBܩBlBKdA",AhAYAA?`AA(AAAAuZARmA)A?`A@pAAxAW }A^ AA?`A`A\ARAAuA12A(0AIAV]A'AeAM}ADAA0AU{Ak}AdAJ>B9BCIB]AHBwBȩBAJTA~9AnAA#AAnA7:A|AYAuAqAGAzh-AtA2AFA&A[A"ApAAOiA-ABA OACj{A[AsA\AA+NAP$AA?SAO5A&AA2AAmAc A`uAXArAAiAZAcAMA5ACA0SAdA +7OAZuA+AAgAh%AW-AcÄANrAZA|A?gAAgA1KAJyA;A6AacApFSAbѺA~]A,AϛAA~ZApdAAsަAEA[@AAlBAn4ARABAA"ByAAݚNBA}4B@A4AY\BpBKB?_BJݲB-BKByiB?BCZC mC9C;C\ CmO%C}VCĒC(C,C/wC:CC7CʬCCkmCCkCVC~DC,CC,C3CݨCC`CTC`QCDCGC>CݵCB0B!CC |C:eC5kB 9B6lB:B2BtBcB^BBCbBE RBD^B t +B@{B\B7ZB-!B7BB0AcABA1Ay(A^A8AAJAA@AAAP~A%hAAkpAPAAB +B ^AD֝ANAAN̕ARA7A'A9AqAbAAfZA-^@A]OfARVA<;A3A6zYA95A2 A4AEVA[~AAAuAe| +AUAF"AuARAt7A& AfAjaAsAmWAPzA.A*ANfAAvA5JA~CAUA0A6QA1BA6AjAnA`AkxAtiAƮAW-Bc5uB6 BsBWG B> +Bkh`CBJ]6A,B" yB.A7NAAEA++AA`Aˏ(A"AA 8A߲AAFAATAA|_Ad,ALA BȸBAAp Aܑ As]dAAAGA`AYr@AHٲA4AANveAFA:KA X@#A.AbcA3AA,RA(h@G޲A AAn8AOAvOAk/AhAVAlaArA9EABA$AA'3ApA{Qe@ A6>ALAA'OA^Ak~Ar%AHv&AMkRAaA6(AtFA AZLA~A&|A8aATAHA+A\AAAޛAm|AAvDAanAM BpBݷBrB*B*vBtAlAAɚOBB BB6qBBBB"OBmBB[BB;Br2B `B \3B,yBrCC_C(pPC5CSQgC]RCzeCC~CCܺCм"CCGDD DDpDPD/D^CC&CC(CXCPCnaClC\AbAMAFB*AaAFA|AդqA$Ad%AA{A?`A|ӆANfApA<AMAp}AAƈAˈAABB@p{B +B"}B,B B`B% ~B@BYB80B?%6BYBhBnBBq`BТBWCmC$C:CH CPCPQC{!CC)C>CW{C-gCD +{ D D%D4hxD7xD9D,D DCjC8SC6CPC!mCCCnC`QCVCCqCWC4&Ce^CC? B*bBJB%B\B6BBcDBFBK BPBdBK#bBMnB1:B;B'AB4ABB BTB &BA0BA},AYlA`"AA&:AԸ]A4tA-AܜABA^A AxAAnAA۲AoArA%A!AŠAloA0tA}A\A[QAIAA}EAЀA +AWU}AA߫AկAvASAJAAAĒAmA,A=ARAMEw@,A5@}A@VA1ŸAuMAZAU AiAFJA8A4~OAYAhA@=As@[vA\AU>GA#$AWA:AbkAc4AvA}AAqAAFA؂AA9A AAAA|AsB*AyBlA?)BkBBB(B BhqBBA՟BBBBBJB5iB +Y,Bg BQBSB!B<¸B*BxB?LB'aB.vBs0BwBvBv̀BB/JBCC +C&C(OC9`CJiCcTCqrC-\C%C&CԓC:`DVD$D@D_DucDJD\yDmADWCiDB7[D@DCHvCCCMCmCo]CXCSeCi`C=C.C5ŋC"rCzC=BKB>FBfPBBBBB:BPBw_BAB8-BYΤB2>B:0BB(BMAB B%XA8AkBqB/AAnA`A/AAQA$HAɼAn`AA"JAXAbAAAA%~AAkAzCArA2AzA}AtYA(ArArArEAALnYATANkACgAvASJAs_AAsLAMFAAb7Ae xA%.AtAEA>]AaAACA9A@vAb@AAAR@EA.AoAA&`AdrAA\A8AϸA0A(DAWWAi(ZAfF@xAZA8AAAxAA#A;AvAۖA5ANABAנ*ABR)B*B"B#B B +B B)JAUrB0BOBBB!ADAtBBBA!BG_BRB +,BVB*UBB pB,B +YBDtD*JDDDJJDwDDiDJRDCCCjCCgCrCZ1CBǤCFwC1C9CJC8C41C 2BɌBqB?GB"BBJBXiB BjBj9BtBH+BRZNBZB ?B3B2 +BB*ޗBhUB 9ABNB `AA]dB%'6B(#AѧAΟYAAɃAÃAAAAAAfAqSA5Al/AWCbAn%AvKAPAA[Au?A- AnAfA?$A~2ApaAAT"AAEeA\{A/A(AlFAAA-HAMxA~eA0Av+A^A(?A^A,AX~2A'fA/Ad A?`A8oAy*AAKxA(AcALAF0tA; AARiNAK@FA1A$A=GAACAAB BoiBeXIBBZJB^lB)7BtBB3]BBiCYC C&iC+)C'eCH!CT̉CadCC-C8C*^D#D+CDU]DMDEXE(Em7E1EExpD6D|D`.D%DpCCrCRbCВC\CpCZCRF|C=7PC4C8~.CGC3^C>Bf,BlBhB^B.BBB}BzC>Bt6B|LJB;B"zBDjB2BHNB%BAYVB"AAgBOAgXAͲAGAʄAAGHA˽,AAAA}wAA%AA=AjAJAbmAAvcAA;}A?@A'`A`Aw4AkAc5AA|A# AA%RAnAʤABAoARABAG3AAϞRAvAB BݴBQB +BB +BhB%gBAvBQsB"BOB&BBBB&vB-BzBB B'B>P*B8!BBB6B8BJBG*BP B6hB BslBi^BBBkC96BCxC CMC+C/C4CbGCrSC(xCqrD +DrdD]E E#E6FISEE5DD!D88BD CzrCbCIC!~C4CpaCY_!C` CA C;mCeCZBBfBݽBPB@B1BnBNFBxBBqeBBW:2BIn:BbBDB5BPB9XBDBJ^BB*B|B!%B A4AB҃AڨA۲AlAAhAAقA׹A(AAgA$ANA_NxAAvA9bA3AAlAAgAw2AAL5AAiRXAA#AkeA,AYAZAxdAPA]AkIAQAA+A\A| AA*AiA>AknQA ApfAgAcbA[7GALMA?AT=AwAmAAk_ AfGAAAYAcaAKAA(MA?`AVAuAAA}AAWAhAWA|A^A{At}AA^AeBA5A׆HA2AA|B/BA -BB)BBKByBEAFBB B,,BwB BTB3m +B%4$B'BIX4B#WBVBB B$EB)fB/@7B:[;BRB>nBhp@B`JkB]/BB@BBƍ6BkBCCCC"ĆC'C0sCcvC[C!LCCCD&DJDwD3_Er4E\EHECDDED_:D$ʔD +?CCKC#:C;CBCxĚCc CC.CcCNC׺C'szC!C$+C12C^>kCw=`C(CGtC܏DMD,eDMgnDDDńaE aD/DDi_D;YCCCsC# +CCsCo>pCQCDSC.CdBB#Bܝ B[BʨhB=BBWBoBEBUBfBB"ABB!BM=AB"VB :B zA^AԈrAAAkArlAЍAɕB*zBOAlAAXAZAgAXAA?`AVSAAaAAZ A*?}A]otA3;AkAg]AA̎AAADyAgA?`A|.ADuAA}AAd@ubA6AR!AVD|A AueA<%3A2A A4A}AG~AfAsEUA6RiA +A fyApAQ?#A"ATAvdA29SAM ArAkxA0AuAAAAAAw%A +A,A AA![A=AA5PAgAhAc/AFA̾B +A/AB BB7BVBB /BAB@B& +B cnB&AAB29rB-;AB+aB2B)BB%9BDBG|B1[B]!(BJIBVBp#8B_BsjB^LB}BfB2BtBDB3BCACCC[C)RyC4]C9zCItCsC{lC CкCR-D]D&DERD`DvDu+DmE>D[DAMAHAaAfAm/AAٝAC+A]PA[0A9AQiA)AmAhAg!Ao5AxRA;\AA8vA5DAjpA?`Ah@ANAD AAXQAeAQWApAu +AUYAR.AbAF0AyA>9AQRAc AAAA3A:W@µA"bnA5\AXAhA}KA(AA AAA!A>AAAA=xAtrAmAB AeAA@B 4AcA +AB]B pBBDmBBǬB*GBj.B@1BIRBW(BlqB^IB}BXBJBdB!BBBB~BBL2CACCCC$ +C0C>CWkC{eCCCrCgDDD(BD+sD0 +D+MkD.*D!EDD#CC֨kCCC!+CqCdFCJC7 6C,5CNC \B,=BWBɆmBwlByB1B BBdBe6BjBe8BazBY Bi9B.`.BC>B"B9B&pBBFBIXB?E6BYB 8A+AaBNAFAA50AzA ABAALAݰA:A>MA֔AA78A+AACUA8AD\{A*AH{AAAAQAYybAQAAAHA,AλAAAA$AADAQA;0AASMnAAK]#AAl..AzAAA|AgAAFA$A(xA}A7AzA1AzcaA ~3A3AA/A@ ApAAB/B%AȭAaBxAIAލUB nAmAͰ/AA AA'ALA&A8A|ADAUA`AaEAO"A=Ax7AA~AvAIAj&A[~AimB`xA}ABA~A4[AXA[C@@ @˰AFAHAsA`AAA AI-`Ak}A<2AAA K +AM?AL@XAVAAAn3AqA2ABhB!i,BnA|A AAAAaAvAA13AA~A6AGA%BfoBжBBI1B0:BB]B=B/;B5B. BAB B3RB>zB(B1t~BǖBbB[B=SBLAYXA8AA{A^7AggAiA[cAn59Aq$AsAFOA>u@A*ńA-A#ҞAaA&AbA"Bh_BB5ZBTA?AA³AFAŒAAA2AlAܭAiAہA|YA4AAcB>TBѼB{uB"B#+]BB B~AjBmBpB1hA>B8BB/BJB''BCBBXEB*5B6B=B(BUBD~BjqmBuQBnB|k,B8BBB!BڧB'Be1B20BCDC OCMCC.h6C0C%ZCC&B!BBCBBBDAxA<3A AĒA:A"A޽AـAppAAATA,AAtzAA~+AAe|A6"AuUAA$AAA\zA~A3AAZAJAe&A?`Az:AYA7bASZA1A3iAAAT[ATAIAA?A }A5p`AxAGAaA +aAc4Am*A8AY+Aw1A AOAPjA]ƯA<A &AAAC*Aw@A`ßABjAۇ6BKA5AwAhAh2AYAsAFAHA-?AQAĂAA8BBsB 8AjAzAߵBCB +/BVANB gB +B3B7BJB `B [B3+B.B3B_B=nB@SBE=B8B>RBO/BFBQBwB.B%bBBBBBW1BBhBDB>6B$C~CTC#CC"mC(DMC(5cC*PC:C8GCRCBvCHnCcCokCCCiCeCC4MCCC CRjC:FC[CDvFC"aCRFBBeoB&BBBBBBl(BoBCUjBSB5JBUB]hB?cB>PB>pB'}BGB4BblB8BUBfnBBBIBOBּEBҕYB?CC CC&C*C#!#C,(jC%2C1,C?C4CIC\]C]ױCosCfjCrCbCWCACcC.C.kC*CqrCcCU8C1ZC 8C.BBZBBBZB~=BQ$BDLBQVB;_ BU#B,B>=_BSBFRpBOBAGpB 6B7;B;]B,BA|LBtB-DBrBBwBsB#TBg}BF.@B3\B-pB# +BBBB#B B QBBBXA_A AgAhAIADAԯAzAbA(NA=KA=AJAŲAhA5A|A4AAuAapA|tXAA9۝AXAAcA3:AA AڏAcxAY#A ANAZ;'Ayu3AGuAAAAxAAn@&ABXBWB_WBiCBp4B +BOBC=HC,gBߒB5BB,BBBmrBWRBeB-i=BAGBGB!BJ\BM%BFB%B;B1JB3~B8B}B%,BGhB:B`HuBvBBB DBVPBVBBD +BPA?`A_ACA_~A}w0AKAA7AzѸApAA5FAAAAƈA 2AAA-A} AAA,dAAgA!e6A%AAFA'(ADoAcBARA*AJAnAA݊A`AAd4AA}A\EA$AmHAj AAAeB,.ABFAA͆BB9AݔLAB/B n\BAB@MB3B!B15{B4B(;B*KBBB YB&nB~B#oB2 B:B<6B]NBQB3]BAJBjjBWBBx!B:B>BPB{BfBBB BBBB1B^yB8BB;B'"B-AB2"B +hB-HAB!B)B}\AA~AAAA\AAA +AB AoARDA~*AAAvArNAKAWA&AqREAAEAv]VACA/yA2AAPz@AAA~Az AAQNAhFA!AAɄAG֝Aw{AQ@EAAHj(ABDBYnBjIxBrB(BBYBB3BYBcBB˚dBÕB.BZAB=rCC&CFOCeBC `CC%C3C9(C:C2C1CAC; C=C;C*WCȭCFCtMB.BxBeB#SBBBBkBk^B; BNBOtBBBS.B8B4s0B;$B0^B*B;%BVB?@B5BIB;/B0B4BJBM2BC$BNBR:nBtB{ڿBB\9BmMJBB.rBB-JBF +kBvB<1FBG-B5^B0B +A&ANArAhAhAAփAz.A.AJA.yAA>AXA6AAZnA?AúAa8A AAAA~A׾AAuBAB+mB}BBBjB0 B% B +AYB B/BB B,DzBBB0BM%B/IB(BTdB>rBxBIBpLyBxdBB{BЪBuB_B3By1B&B**BiBk2BދkBBO4C(mC m C zCCCIbC/CW2C4C)CvC&CQCCC +hB4BB%OB-B|BG@B3BdBRTBILB_fBJ`DB BvB;B2 CB#BDBVMAB7+B<|B8IB&l B!XB4B ”B+AA{AA3AAHA=AiA&AndAbA$A4AA%/A"ACLAVA~`A2A4ALHAvA4A^'|AAA)ABAAFA-AAGMAJA}gjBk:DOB*A7AAA:; +A'AZAQAeAYAG7AhA+,@A:[!AIuAGTAM•A\AsAaA{A)8An%AA{A7A AAAAAYA%AA"A菬A[AjAlB A׈BJAAoAPA2AƮBB7BiB BBYB([BBB$BmB *B}AtB$B2BlB!FB<B-ӹB/RB@LaBdBNbBU2BMBBeBwHBB|BBB(BByBiBnBB*#BVBB[BB|IBCΥCǽBCwC SC~sC CC/C7C B2BB +BR-B!BBBBBBsuB\7B>ABt,BONB1BB(B~BB(B,iB;B@=BQ\tB4\B*tBdAaA\A?;AU"A9A?`A@VA}@:AyaAb|AAAAAwAAbAد;A\AAAőAZAU|BBAVBvA BA%BB,fhBB9cB, BBB=gLB6BBBABB,BBE BeBž)ByBePBGRB@bB=UB<(B?B +RBm-B|yBBzJBbB4BBnBRB[BBG&B$BBBُBBBCCIBŤBTBBnBB~sBɈBB7KB9BߠBqBBB;B_:BLFBKBAxA[bAbANA'A[A8AA9AeAȜAA?xAfMAATAGA{AGAhAXsA?A={APOA}AA +AaAA*.A`AnkA:A Af7ARA|u%ADZAMkA!A0cAAAD^AG|>AcXA?`AFnA$(AnA7AXAAA`AaAf]A(AA}EA˷AAKwAAJAЏAhAApAhAϿABǢAAŧvAxA[A:BB5ABXB6ByiB3B/AnBAB8PB;4B%B&B o/BWB@KB2B:CB&҄BwKlD@3C|CH{1EUCɛBEBG[BW;7BMC8B\BazBBB.bBBBԠBBtBBBȰBbB~#B B5BBʧKBQBaBwBq6B-B/BBwB@Bn!BB}BEBhl\BEcBc-B/)B/B=!BBq`B-̶BǘBJB5_BDB%@B?~B(iB7B#&B5B܀B/B2bTB]TBXB:iB=HB6B@SBG&wB2qXB5BFmBIB JBcBPjBj_Bb(BuaBgTBe)BIBBr?BԉBvBj|JBHBVBBBBgzB{lBn\Bu7jBfxBvB|pB`B2BV޳B%B7`AB0 *B*BAAHAbvAݮA(HAA!A AuAAOAA8A}AzϖA;(APAxAoAFA`ArAXAA=A}UA$ A^9AVACtAH$bANA.AUBA@A @A)4uAYSA AfeAwWA _A%A!AaAFSAASA.yYAKJ@wAA݉aAIAcAkA AYAAbJALZA +ApA\yAaAAwrAppAD{A;AasADAtAoA]A?AmA2A;xA϶AԮtA<Af>AB gB|BB2B'׸B/^B +B*?B AiB@B%B;8QBB+BŌB:dBIsB~ BjB&B'eB:B9BE0B9aBD BQBXQB~cB҈BBfBBBv BB"BFBB]9Bm2B^wBdBicBQ̔BlIBtBM>BYTBB]BwGB_aBB'B BiBYBdGBr'B#,B5dBCB2pBfAкBA)PAAz`A4-AAt:A:A\Aq&A)AAAAA.Ag:AuNAAA*APA Az AC Ahl"APA?`A4 PA' A@A A|AAAAAEAnd +A@AFA?`AIA"A=A 4AO9 A\A8$AAA,A]*A]AA}APcAAABE!AB +B?>B`BWBHxBTp]BY +BQ>B|#BQBU5BqBND+Bm +By!BdBBk)BBhBBϬBlzByBifBhNBGtB'Q\BɔB A#BuAAKWA۸A^XA\AOlALAvA]AvAAYLAF,A9v{A($A A>AKAA%\AeAXA?0A.AoAf[AcAAAAOAAHAh!(AyA?`Av'A]MAcA#AzOASuAAWA.RA#\AVuAAuA$AnAARA+`A AiAWA~"A0AAޞA!BATA1AA>AmAAXpAVAJBA;AhB +A,BaBpA!BBBBB9BIBrAʾB1 +BBB9 B1*B!jB +>B]BSB@cB6:B +*BTB +B>ApB;5nB4MB4SBgBU)B6xB6)BsB&B B[MBd BX/B\BY>BOQBbbBFB^B?BGB7<@BNB3B3=B(B=BUB³B-] +BB +RBXB#B+"B&CB B!~A牸BBiB=BB= B rBB?SBh;B:B0BGwBW)OBe\ BBBInBvAB\ +B^BP:wBFBX:B8B]B7BXBp>BpB6;BtBmBBs` Bs;BnBeBWBg&B ZBYBBBrB:BIBCB1jB3FB5TB A$AB*AAXAE-AnXAuATAAOA((AAdA%6AkFAHAABNA{AA:AuMAA2"AGA6A!&A:A:UAACAe/ADAAuVAwA(AibAA{VA6AE+A}sABA}yAAD*A0A:AAAdA}sA^A;A4AOAAA00AAĦAGAA>A%AAJABdBB MAtAA@AjaABBAN^A Aʳ~AoB4AAXBlAwhA"BB BIBwBBBBLBHBF{BU B/'BM'BV0B2BBo%B$BS6BElUB<|B]pBc+~B lBB bB_BdABB mB*B3$B"|B35BFiB.TBQBOSVBB6}B?SBOTB>sB'?UB7 B0hBBBB_B@BuBQBRBd`BM +BTByMHBe?BqABBr)Bi=B9fB}BsB~BW BkZByvBsBT5BjfBoBB BB]BsBUPBjцBG1BFB .B-tdBBWAAUAA`A +AA|AÛA~AArAk; AAQG8AA`A5ANAhrA$A /A9A:0ADA -@>A>EABwAAߡAMB~B؁B +AtB AzHB,^AAA&B)BAvCBEBB PB.B RBCBMBmBKB +o'B BB5ԦB*BB +1B[{BBMTB!B}B/B>BZB'`BDjB2jB,B+ 'B$B:jbB-B8?B%jB<+B5B5HB(&|ArB%eBB1nBR"BA$BUB'ABZB) A,B A[AjB#B8B4AA2B8+Bq+BdB7GByB/)hBAΡBA A[BHGAAnAٕB/B +ABA.BB BpA+oBqBABAB-KtB +v`ABoB"O"B 4iB'.B њB +=SA8BTB"uBeB,B [B.BBpB͖B5BHaBByBTB6KaBF_EBDxB-@BB+BxBB$:B:{oBB8vBBBBB !#BKQB8QnA B@B#0BQB(0B(AO0B@yB,T2B.vB;ąBcBm4=B`BqB\IBHB'uB5BP Bm[=BBfB/hBKBNoBT +BZݮBKdBBBz™BJczBvyBpmBcRBp>BaTB9B͢Bz}Bz#aBx+BB%B4#B!$BBy5BBt]BsQBb>BڿBUVBXDB;EB*vB( B A{BAaAAͼ&A˰AAs.AZEYATKA:&AbAdAqcAY?A9A&^A1.1A! A`AFAd#AAX4AA1MA:#M@iAXA\kAHsAKAA}AkArAAfcA0AJ`AK%ABAvA-qAvAؘAvAAAAшAAa,A}AA;AAJAeAAiAn7AנA*ANVAhA`pA:!A\B3AA–AYA HA&BB:AcAAA,dAAAʕABCAmXB!oABPABEA +B)MB2BRA +B5BRBQXB'/B%XB*޶BR*BB BչBhRBBRBk@BB/BBB+ BǤBBaBB#nB$c(B"uBPA +BA"A +BMBBrBB ́BB0A3B`B&JB?BҢA$BB&HB3B$[RB*BJ[B>B6wB:BaB?BxyBJBrB_Bc B?ՇB[YBqBBtdBrޠBjBkBlfBiBg6B[B^w$ByUjBpBqeaBrBstB/B5LB]wBDPB`BxfBiB]XB95BqB('BLBPBBiBNuB9zBa2BEB*vA%BA? +A6AͮAAAAɫA5'A`A$AvgAA{AAA5AvAt)AYaAAw5A_AGAi A{AIA0.A\AAYAmAo@A&@UA\*>AA|@AGA(;AV3AAcA A\AOABAA%A[AZAFARAA"ADZAAATBAA۱ABB>B^AADgA_zAAӦBlB;B +AB'A`B gAAPB & +AABBљB BFhAPvAABWB BABBB .BBoBB DB/nB'шB(B3.BeBB zB B vB-k~B/B*bA߯$BB$lB.AsB8AcB>BB=#BtB B8B,4B0B'B A4AB A BBBBBgB0B>vB?B0BHۊBKBgBBl.B:Bb}2BVmBidBBx3BfBIBq*B9KBiÐBsBBh^BXB9BY|BvpBxlBidBjhBBLBdFBkmBbBpBx4BBwlBsBeENBoXBn=BhBB~BB<=B BPSBZjB@B$@gB B BB AA$A;A +AdAy0ABAq;A]ͻAAAQoAAOAWABA&@pAM4EAb A?xA?`A,Ar Ak]UA^AA˕AAcAA(AgqAG4@ʎAhAA <@AwA*͸@XA|A<AC AALABApAsHAAAqXAEAT_Aӏ.AHACdA~ ATAǜAAoBO6BaAAA4AjA BABzAAA)AcA_=BD@AӆB wA(Aܭ B +Q=B 5BBAWB*9B]B AbBuB*B ~BBB=AmBBB5B|B +'B +B/ AB]BB!`BWBNA^A>BQBB"BNBݖBA7B CBB pA#B?BS3A;ABTAVFA^zA:BB08B +KBB"fB#kB7BBB.ZBKBYBfB%BEvBRBN7BjBl B_PBwBUBBBYBBVBBr.Bt݌BYAVBBBRtBM?B!$YBдB'BtB"A)Aa$A=AųAeJABAxB=BBdHBBBBB{B3BBBBȪB:BBBBBjUjBX BݫBcB)B[BBHBB/BB2B7B6B`3BB?BW)BX8B_%BW=B;yB5B=BB_AzAA!jAAqAAADAvA@AYAAOA7AUAזAA!AqvAAAK`Ae7AlAA)AAA_A@zAR؈APdzA nAaA %AHA&pwB B&BAB Bv2B B3A`A=ADB^B DB PB!1B A\BAB\QB+bBݸAB bBtBOAAuAA5AA"B$BA?AޣB JBB>BTBB 'B6J`B)@zB.hBLBgBJU\BBpBBBCCBCbCB3CC%(CCCBB:BunB YBRB B|BpBBǍBfBBu:B BBaBBB1B~B:\BBHBYBaB BBLBB;B BB(gBBYoBWByxByxB>BY3|BSB)B8*B'JB7ByAٶAAB Q,AA iAkARA#AAFAAՔA}A|AbAB*AcALA?`A,A)AIHAsGA5AceAlVAQAw(A`WA AAPAEyFAhA2A9*A8AtyA0Ad)A\Aj)ARAzA7APAnQlACAAAGAAYpAVA A:AzAiAAAdAAAR;AdzAϢA0A4BVAnBB;A.AxA8AxA2A|A{xA AeeADHAzBMTATAnBB IB3DBd A~B)BB ;B3B)BB0BB B?AHB#VB־BfBBARSApAޡAC{C(CjC4ChCYbC+eC a&CB BǎBɨC'CB4BlBB(hBB&BSBЭBBhBpB +BŹBB_B>BnB&BBaBBX%B/(BBBzKBB\jBBVBrB)BENB?BH׼BRIBfB6B\A`B AAAA[FA5AɼA&A1'AnA+A`|A~AnoUAAotA.A:AvAPAֆAAA^ AAnAnAr$AA^rA?`@5AaAobAnAvA/K@AAcA/vA1ApAA=AUAAAAAAHAA,AAAjApFAe;5A}A|+AAAAʱA6nA`AAAAdA2AAAS$AEAwA B +-AͱAA;pA*BbB AvA&AƢB ]AZyB gB*&A^ABpAAHfBB?B3B|ADpB =AA +AeAqjBAApAլBB%uAAAAnBABAArB#BBBBBDB'*B+BK BF_B|B;BPXBCBB_B8Bb#B;C&AC2C'RCBBC2CgCPiC:ƥC5BCBaC3C%KA:[AVA AAgApAȤUA*AbA̚AvAACA8AmA AA0pBA(QA2DAA-A$AA[AcAO$ALAǁA7Ad:ABBBTA*dBqAB \ABBBqAB KAB!nA凹AABAܡ=B +PBܧBALAy BAAA +AA*B~fBAсA#AAYB!KB +B BBB"UBqBBpB=B0BJ B:[BFZB9ZBF +B&:BBdBBBBCYC'C"C+,C3CB0&C2>CxC OCB%B(B(5C ^CNC}-CBԷFBBB}sBrBĹBBsBt`B@BB\?BגB]BBҢ@ز*AHxGA31A#SAAi"AcAW.A>AlAA%aAxAoBA\AAثXAsATA6wAAIYAApAlRAAAAI74ALAfArAnNAAAA]AA$A$AFVAAl>AA橠A϶gA +A AۥAANA?hA|BvAjBx~B!7ABAABAZAa_AvtA A7AB)A©BAA.TB DABBE,A ABAكBYADANaAwA>Aɳ^AA`AAQ?BB aAA₲Ap"BUB&BB=B" B9KmB4,0BBBkΏBZ=B{Bp-4BBiBZBBBBBBuBwKB-Bq"BBHlBBB^BfB|BBNBDBBRyBBBBBBfgBBHBBJBy3Bdq%BPMB*BsB +lADAB>AښAxAMAbA}tA(JADAADAtAA2@GAfA*_A]AcA{AAmKAĤA+An0AuARyA?`AA0+QAv^/@#ANvAAjAcAceAqX0AtQQAO4A7ɵAGA| MA^Ak A&A1Ae,AA:AA +AA DA:B AAqA)AAtAA4An|5ABAG +A9oẠ4Ay^AAAAAAΉAbB!AHAĆB3BiB-ZBAmsAAcrB BS'BABB'5BzB hBxB,A0B9BA]BkPAŀ(B -mA +BlAA2AռA1ALAlAێAW$A'AA7RA蟭ABeBB"B%eB!? B&CBBB9xBxBiXBjBNC7YC?7XC3C1 C2RC.iCC."#C%BB BCB.BM-BBBBBQBB3BKBDBBCiRC$>C{'BBB9vBtBvBcB Bt B +B-BvyB BBقBBa,BPBCB"DB(AB~AblB'ABLA-gA6BAAHAw|A"BAwA(@A)"A}+gAAAOAwA5Am9`A?`A-oAG@uAG$AjUAX@ApvA-At.IAItAX7ABAVmA~dAMA!A'VAlAA1A5 AAs&AAA;AZwAAAA zAΜB~B'LBdAAmAa*AhAA^A6A0AA^ABAMA2BAAAAAB&PVBA8AATFB ACB5CBABysAԴAA BAۅfBRBbAALAdAjeAɬASA|A2AԲA˂|BVAAA_A ABXB^B B%AIAtB'pB/B>NBCBLBhBHBvBBnBB^B:BB0CBBC C4C&7:C%C2C: >C)IFC'3C;C0#C($C )C2|C? CJC9CP&CNOCYIC?X(CNCcBHBB]BB[8Ba*BޘBmBBXBB;BA8AaBB1ǘA:&AA'hTAsAA AcǡA1KAD4AMAB2A AM2AiARAIAA4hA?`AAAV_/AHPAO7A A?`Aj=AJYA#A?6AAA9AʨAlHAAnHA.A`vAlAsARAVA,A|1PAAAǨAAޖAoZA@AA8 AAǵDAe0AЀA͵ +AߩA;AA!AA3ArABBAAΗaB)C5CoBqCIC C@CC+CCC#5C4CGCCvC#C.}8C1 +CIC?C_C`4>CJCCfCGCBB"TBKBaBlBڜB=B챍BBˍBcBwBB CC%1B jB)TB{BB^B +BHhBSBBsZB<8BBBBUBfwBtYqBrUB^BbWBP~B$B A8B lAfBrBuAABOAvArAnOA 3A;AE"A/AAAA Al8AwAt}:A-LAg@AFEAA7I6AyA9KA#8f@%AV?A#A&G@ACGOAiAABlBB]B$BdBJBBB[BBaGB$!BEuB.B!OB7B7A*AXADAKAJA,AA,yTAvXAV@AX3AAA(VAFAA)cAwAAPiAk"AXAAAdAAABhA9AXA|A%AXB BtBMB*&Br#BEiBXQ1B?;BPBgVBdBkʆBqB3BwQB=BiBMBC 7C9BFCZCB=mC>C"C,hCpC%C?RCi{CC*C /gCCqRC 7BVC'9C(=$C/wCU}CzC0CoC^CcxC6[C(8CCqB%BkBBQCYC!CBBB B#CCBB&BoBB#BGBzBMBw BBoBTBCBBDBBV%2B}BvlB;NcBJB:1B=?KBA?A34A|AJ"AcAK?AAp*AAA.AAMA. {AWjAnfAMAA +:IAO28A;!@gA +JA|A?`AnkA?`AeYAU@گ@A~rA-[A B2ArNA9b@~IA\=Ak\A1A\AAeA#FAfAfA]A"AAA~AA6A%A}AAcAA%AA~H=ARAo%AUAAPA0ArAAAgACFAEDA ,AxAA%+BmRBD+BsA̷AmBA B)AAAAܺAyAIbA%2AnAaAxA ALAь*AA~PAAPAvAAAAAxA9EAͿA4 B@WBBB#BDB'%BAeB]{B:Bh+Bj9Bb}BBxB7BBTBnBBuBjCC2CaALALAAAZMADA`$AMAbA A|wAXCADUAtRABAoAAZA A7+AMAAbAgAAAYASA0AA +AZA·AȰAYA&OA*PAVAA AAEAArAJAQ"AےAKA_%B+Ab"AFA bA.AUoA&Aܟ?AԁjAA{|A`PA%AaAAVA%B+B4o+B0JB_B/c2BBr.0BBGqBleB7 BhB?BzBB#CCKDCC tBgDCC&BCөC%C-CC&aC`BC 0CCwC eC :WC + CACCi*CܩC :CC-OCIiJCoz_CGCCECgm`CC@C8 fCD`C>ˇC+C܃C('NC7cC@wCkGC7YB[vB>B̪~BFB BOBjBB߃BBB)BQJBBnBB,BBBdBBX B_YBd@Bs_B%ǖBPB:B;AAd>AA}A6AyAONAA/AAwAdTAA<DAAiWAX>AlA\Q/A=[A ATA +ݪA?AԆA]<@eAIA^@A,eAJ>kAKnAsA=.A͝B ~AA$A@A-A`A#A AAqAA Aq AAStA"A{"A AAZJA[vAx@AAASA|SAAcA AAA +AAuAxȸAȎzAKAA AzAe9A^|AA|FA9AA`A!HA'AE-A*A A(ASBA1AϛA`AAAƀAtA9A>A*A>Bx"BړBB9{B7BJ Bn^BnB0Bv5BriBh B{BGHB.\BiYB%BBBC`BY/CRCC-kC"1CfCϸCKCaC]vCrGC=CcC>C.C UC!vC(C &C=C:C>vCeɛCd?Cp, C5CuCu"MC2C}iCC|C4"CAzCACJAGHAyAI@0AA>AtANALJ@A[dAA&Ax#@AAAAA^ AA*AsAAEARA.AބA:AAՕA5AaACAA^~AAyAыA+QA$AьA~xAoAzA^{ACnt1C\CXVCBR C<PC(CiBBrMBٙ&BBݿB8PBCB BEB.BAOB 1BBt?ByBB&BǂBBOBzB5 +Bg=\BAbB:hyB1`BEFB>ANA5AAAVA?`AT[A/bA`A`gpAFAs,AATAzAGWAAAͼAO7AA2AzAXxjAeUVA\A9DAAqAFkA9KAA?`AA'A=iAAkdASfAZATA7AA=APA^A^A(AtAAmA A.AA2AʽAAoPAHA=tAh.Ae8A"uAmA~AAv-A>E@AquA]A_<AOAZ+ApBAjAhAA15&AuXAeAWrA`A2AAA8AqAL'AIAFAuA\]AfPA`cA0ABTA\A_AsAZAΈZAjiArRAAfAXKOAőAP]AlZAi1Ag_mAcH.AAAAh4A&AoA !A AkFAoAAA_AoAA AZA{F]AI|APAABAA{IAAtAoAoD[ABaBJB& B+ rB2=9BŘB;.CBJ9BelBgBB(BF8B*mB|BzBˠvBrBiZBC +,sBhByaBBߟtBئBB\BҖB +B/B9B/B>]B"BvBY#B~B㣋BcBCqBBC C-C CwLC'LCC$C) ~C2A)CCACP bCqCYCCKC}C?5yCqUC7 +C$hCBGBBBC,BBCB48B,BSOBBB:BJBPBdB2BZByA1BAA0A AWADAP[AaA AAb +A%*AZJAfAcA(AZ<CCηCqaeC9GCClCFVC +CCBC @C +BBBBmBJBBB.BFB B};BB%lBB.BB{BB߄BRBKJBDPB;amB5UB* B/dBbMA=BBAA_A1QA"A AAA^AANAyAWAA8-An^@AAg5ASAWA?`A4AnA@AAMA~ A|WA*]A\?lAqA ~AAVxA:AmbAAB2lA1$AA@ZAAATA8AhAwA{~AupA*yAQlA,r~A>AW"A;Au +AԢAqAAA/dAIAmAAAkAxaGA{A2AyD9AAFAVAAA~AAo']AoAA/NAIAeAAGBXBYB+BO B2 BCBOBXBñBB_B~BdB +B(GBPBІBzB~BB轑BƅzB2B6BCCBT BXBǴBB5B:B-BˑBvBtBB#VB_BҠB*xB#XBwmB?[BݓfBB{BQCCieCC}CYtCC C#C/C"mC8LCCCTCIYC$C.YC"-XCӱC -|C `B)B헩CCBBy B.BB6BB[BBkBqBnB~BBTBBfQ Bv2B\$B<4BjUB@B$uDA.AAAc=AAAڊAcAyAڄAAAQAՈAgAcAAi}A\z@;A?`A A]A@IA_AYAA̒A$fAfA sAAk#ACAAnP"AyA[A4A|AyAZۍAČAAHAv-A*QA,A-1AkH{A_t!Av~A?ALnA$A;A5AxRAbHAAW.AAèA)AtA)eAA.AͦAAƺAOA8AA|AAASAFAA"9AnAvArA Ab"AA4A|A8Aӂ(AjAX-AAAMAA'ABB[BB2B1nBCBMBmBBBe)Bn B +BnDBjsB#B̳_BBҜrBbBB`.BΉB$/BB޵GBzBBGBݲB܋BbPBaBBBaB߽BBwBBBB2BjB2BʷBgB2B.BBBM;CICgCiCC C, +C+5C3SC> CHMwCKCKC?C@CCBBBJC "C *B{BBۦB5BOBB B BcBB?B?B(BoB?BmB=BvDB+BIB(B:CXB+0A.wB&[BBB \AA$AAAnyAAk4AAH=AP(APAevA!pWABArAGAbAHA@A oAKeAwAu;@ҴAAO1AQfA~HA"A|LAiNA&MAO A<9ADA6xA\HAwGAIOA=AfA0A@A`WAVºA\hAGAAsAAh7wA]A`AlA?`A:Y,A0AA?`AxCA:ABd,ADANA\AvwAAh1A?`AAAAXAA~A^A݊A7AAA.ASAAոAMrAeA AAA*AҩJBYAB ;,BnB%B<B)uBc;gBqqBB_BBB,BGkB2BBB -*AA]AnAjAJ9AASjAtADAA"AxAgɈAsA?`A5Ah^3A)zAA$NAA$A!ApAwA6A A@AH;A` A[A6$AtA؏A"A?`AACA0AeAUWA.jA:A3A\5AA$AAAA"AAZ(AkAe{Af*AߝA1A`(AzA! A*HADAAB +B6AmAy*AFAkA]@AP AvmAAgoAxAAu@ڏAlA ,AAUAZAAAǑ ABAUeA5A-ABX>B.Bk&B.B B+HBCBBAGcA AnFANALNAAbAtA]APiA .lA'rAE*A8=ApAuAEAAz63AԽAA?WALA%xAeAOAUaArAA?`AVAWAUAuAnA7FAAB\A?A Ak AUAAwAAuA|`AAdAoA˞AlyAnAf AAwAGA3AA.A$,AkAAdA8B*B BBJB, Bc]BfvBrvB|NBKB!B,B +rB̨fB4cBNC 6C xC +b BMQB߹B5B#BB߻#B8B.BB9BBĴBBH2B'BB$BwGBleB.Bh6BB+BB^BʂBBbB+B5rB}BBRB`;BܡBB2BmC -B6CTAAd1AcoAjfAC;OAAcAQADAyA\A| A8A]HA2AXA{AA-\A^AFAAnAe9DA pA>dA]GAA|eA Ae{2AbAΘAcAA'AsAfA\A׶AЕA{B B+B'BP,B\BpBabBBBqBoBBBnBAB5CBBOBٸ BtBBBBRBBB BeB\BpBBRQBBB.B@BB(BBڢBVBwB]BBBB BSB̒BBBBBbrBLBJB ByC +zC VC*CEC#C-C2C4qC3C=PC>C-gC#C(yCoCiB( BبPBBB;BپBBB$BB:B>gBiBĴBeBB]BOBnBBh`B8MBYBSB hdB WAQAALA2GAAAA0AupAAa"ATAZAv AiӥA A=SAw&ABB_C>COC?.CDC;C(C|C̱BᨖBǚBB߾B:B B {BBNBB|BbBW BBΑBBBBoBUBB B_B*z5AFZBA`jAAAܗA!AAAȄA8A@A[AXAߪAA!AtAl(AAAZtAvDAc}AMApByCshChAXA@AFAARAiAèA.$A6wAznA@AgGA-jAA~A܆A=a%Aj>bA{ AwAg!PA{\A?`AOAw*A3AXJAAĕAXAPAA|A +aAyAfSA2A)$AjBB8@AqA KAAYTAPA@`A[AtP +A9AVcAˈApӮAA)AaAlA(A?`AVÀAi*BuB ABSB0B}TdBXBkB7YBMpB_BĘB(BBɠ4BJhBCBMBVwC.C=C)gC y!CBQB}B}qBBBB1xBBBڿByBBoJB*BJBBBФB/B2WB#B&B$pBbAvAAAAF AXAAA3zAYAAA(AuA2*ASA]"AnAJ|uA?`AA/AxAf:B"C$:CAG/AA +YAx%AASAAz?AxA-AcA{"AWAzKA?`AgvA AzArA.AAA\HA~A:wAVA!AA\"AFAU@lpAACN=A~AA#AD A?`A۽A?`A{Aq`AYAAĢA8|A($AbAyPAs Aβ6AASA XASA{RAAAιAqAĸALBBqBX۬Bh_PBwBGBBoZCPCBdBBbBPBplBBCsyC- B&BԵBZBcBB^LBBFBʤBtmBBBgBRBB::B*KBbB#B4B%CöC +CCaBBC_CCfC yB6yB_BBJBGB.B)B BBB/=BBݔBzCCC"A%C'ZC:C87C/VC;B CLCIC(C!0CJBIJB̵=BBҀB}BmpBdBBpBBזB4WBrkBL BiBkBB/BW]B{ͷBNBkx)B "B}B D8BAʝAA6bAAmAxAFdA AA Ah[AA#AJDAA?`AݿA$A(BCcA7dA$AAAUAA'FA"/AoOAA{ AU4AU@5AQ|AhAz`A|AIJA9:AADAM)A @xA1BA'thA`\A-HA4AbA'D#AAA9$AvzAlQAzA_A:7A'Aj3DA}1AqqAUABAA~ADAtlAhAbfABBvA HA?AHAQBAYAvB{PB|FBلBN^ BnުBEBBBHC +C0CC~BXBȧBBkBsBB+Bُ1BjB(B5'BtBLBGB;BEBYB'xBHBBB7B~B~B4B B/CCjCChC(pCRC-C +LCOC-C%C(JC!$]C<(BB睧BԚBۣBےOBB>BTBVB͗BBYC;BC6CC{CmC.C+C-C3!C8.3CR~|CPXC-NCx.CBBBBB,B1B~5B}ݧBBaBB|B{AAAvIAAA%AUA.4AWA\B,UBKA?`AHA:AxA~RA]A/OA_(A~AlX>AAgA?`AA%jA(AAAl{AAv'AKA4IA bAOZAAA*lA\:wAAPAXDA2Ac)AfAA{AA^A@A4BAzAEAAH*ASABAA?`AARaA?yA7A:~AeA1TAA0B$BmBKlAA-OAm\AhAkJ|An"ACFAA\AA]AeA00BAŶAAAB-BBA +B8B%B/i>BHALBBB[ B1BJ'CWCC BBBBBBiBB@UB[BBYBB3BMBHkBBBB,BP[BBCKC1Ce?CXCűCFC8aC,C9V2C6CEnrCieC]BcCFICE*5CH`CZ3CiB6C9~fC =-CCCyBVBpHBBcBEBsBaBΖBrBBCiCUCzOCC!C.rC:.C:CL^CZÚCl5aCH +CCQB/BByBmBBP:BkBnPSBBByo2BBe@4Bb7BPBmUBVBy)7BFB>iBHo8B3B)B*.BDB!AClAȠ:A A"AuA:AxA.fApAp'AA_PAԷTC&E 5EׄB_zAJAAvlB IAs(AFAIA;A?`A_AaCAB}AiA2XAB(AYrACeAEAAVAxAd]5A@AAƖAm\AAg AђAA>A/^AƝAA A㍭B&B +BB%rBA BP BB[BOBrcBBձ|CBB=^BmLBBOBBbB BBB !C*CCC4C$/UC0C9CLBBsyBdBqXB[FBLB{B=MBfFBpBIB4BHtBIfB%ܠBQ#B>B6[B .qBAAC2AAIAjA`AAAZA!AcmABcũDhͤDhaB0AaAazAf;_AκAf]A<(A!AuA8A{A&AAG?AI_AuA AxAA7/A^A"ϵAfvA!AfAAG;A]AIؾAXAIyA@ĢAAAgWAcAp +AAAJAnAWxOA@AlAAY\wABqAɹAAuA.ARA5 RAA:]AlA\ AfDA TAAqW$AzAaA?ArAAިAڷ|B AtA$SBB%pRBC`B@B`_BtmBB-B>B淪BĮBBBBmC CĎBB`XBnBBcBFB>BBfB}B^BBȋBԪ~B᜸BC+=B0aCGCI-0CVCGC@CUCfCVpCY1TCt]CCSCy6CbCdCvC ClLC7ZC1XKC3^C=CKC $\BqBuCBBOB]CCC7!C1C +mBFClCcC:5C7rCR3CQDkCME`C35C4&iCܰBṝBֱBWBBBBțBi.Bm BcBBpBaB@BsrBKBB0ATOA^B2A? A` A&+A4zAe2Af՗A}AxkA=6AQ_A,ANIA=A@A.A~ARAWSA{gAb!A?`A4~AAPvAi A7jAE +AAA]ΏA`A]AA/ AA8A`%A/ATAh>FAIFALA?]^A}A?sA?`Al.A: @A)BA.5AuU*AAHAAr[A{AMA]Cv^aCf2 +CtCUCaCOCcC[sCwlCq2C~CxCsChECsCLC;C7tC8CB5C!AurAuAUApA~]|A`AGAU AqqAJAPAA/M AKA6qAa A=AAAAʌtAbA9.A "AAhAxAC/AM=AA\cAxAHA@A6A?`AidBAAAMAAB\ATB#B YB!BbB}BB+#BBWBB͏B>BBBhB۰BBFCCC&YC;jHCV^ACCCxCCqfC]?%Cu2VCZCO>'CQ^ Cl]PC}CCCwCg/C`ClCSCWCj ^CStMCL{CCC*iC/eFC7C:Ce^BC C C'%C CdpCOCCMrC#glC1CFqC|!C fCpCLCC\B3BBγBB'BRBB]FBBTB}C}BBtBGatBhoB0B`#B$xB5gB07fB#TBNIfBJBDBU+B AjB"(8Be^A߁AAAPADzAyAAm AAAdA("A^AquAGPA|NAFCWA?`AfGAa@wA74ANA[A)AʘA)AjAEAgASAkA&ATA?`AAAl4AĈA1S APAA5ʴA8;,ANbAG@:AI(AbAe"AUAWApAJBA*ANA)MAlA|AAK|A|u@dAhAA[A$AGHAQAUA(|AaA|AEA{pAbAAUMA&AAnAABxBB0BEP2BekBB BCC2CB\B]B1BhBBBlBԾBB BgBBB|BzBĜBRBajBlUB⽴BB +COSC%1eC3لCA LC_RCcCqaCRCrl1CVjCYCSC_=CVCmCu"CY:C'CGCCqVCgCxCYCZCb^rC\mCNQCKUCVGC3!|C%^CGC(CACMCO4C CMCPCNCCC$BC]CXC C9KCCUBBadB BBBBh2BBBpB}B B[dB'BROLB]B?6%BVB>nB66B8B*BQB/H=BA.B2ΉB(B B!}B)A(Br"AMAʚAҁAAAֶAmA9AA̞AA1AVAAwRAdA7AfAVARAC|A)LAA|A}pAa2A +As`Ar˳A+KAbAehAA][A{+@A-8ALAPAzdkA)AeWA.A]+A:\AGAHA.ARpA`AK)AxAtAA_O2AwAÞA 0APAzA\mA|A|A?APVB +DBBBXhBaBmk +BB9}B>BhCBMB.BBBBE0BHBBvJBB]B?BlBUBքBB3aBNEB߈BB&Bހ[BC YCcC3=CPC{2Cj[Cf0Cj YCXKECP9CWC@uC"CCJCCeCD6CC +C׵C!CC)> +C3TCPDCeC\|C:6C& CCB(BŠBBZBBe%BhBBl|BQBpBOBG2^BT=BR(BuBd +BJBbB[B7BK>BB&SBI8B(UARAB|AxRAKAA\AAAjAN;AAJAAiA=AWLA8A(mA0ABAdzAASJ*A$A;bAAmATAvAAĎAɒAAuA>BtB-hB/*MBNB BBgB_BB)CBB6B5>BkBͯBBBBBB,BHjBBBնBzBYB`BGB>pBC~C *C8CK*1CgC{ CrFCv[eCmCRCRѝC`;C^)CQwCWxC^C^C͐CCCuOCXiCC=CCpC<+C|ZCoHC}CCCvCYCCV8C7kC:4C"2CLCPCz_CCuhC +φC!C%C CECVCH8&C6+pC$C C +BBB61BtB_B>BNB)B6~BrBlNBjvBz2,BFYvBZmBD2[BqŐBjBaBi BnBYB;aBI@Bd09B@4BkB.$A|BB[BBIAAS%AA(AvATADA_ AAAAR(EA?`AcC17C9\C7XCRiC\CC6C:}/C#jC2BB鬾BpBmBwBcBm5JB}B;BmBiBHB@PBU1B_BavB|BUBg{Bq BxBBBW@B/ DBX6B"vBiNB0BLB !ArA_}AAAAnAAAA_AAEAǒAm^TA;'AA?`AomAA2Ao,AT%A9i +AQWAA9&ADAW*A=DAJXlAJArA'A MZA}%A= Ar2_AM)AJA?+AoAsyAb{>@ؓRAA?`AALA?AӊA$iNARADAAi(A=A}AAsOAcApA&A&A0ǣA!kA-CAMFA?[yAjRqA AqAAvϹAhAL=A-jAAs|AJB wB TBD'EB@#BCdB[ߊBjDOBBiBŞBXB+BBLBޕB=B%CBB"BBcB"BޫBJ.BB BJBdsBJCCzC9)CHCb Ct=Cmv'C[CSYCMCLPCBCMrCA=CEACaACVCmCx?CvCCFCCfCCfDCwC C[CC|C~6CaCkC,CNHCCU*CBC%nfC 0C&C #DC%C!C C-C#@C-C6tCUCSCX|YCPfCFXC4QC/BBBB߰BB?0B,BsHBKʧBXHBllBsBPBZBBmNBc0BLhBu~BfSB{ BAB0DBrBQB\'BWB +sB ABAHA .AAPA>8B A>$AlAmAFAAkAdB@A?AjgAVAahA?`A?`AyGA3'@A@AMږAnA9A"~AqBAAE A?eAnNA.A jADAXA|AdAt$AeAp{A6A(bA AA6AW ALÆAk AaGA2ArK6A>zAlkA&A``AD;AC@]lA *ABuAP&A]FAԀAJA0ABBaLBL|B0KB@QBGADAAB՟AtLA?dAQ4iAA&VA^lAtA\`AAzAAZzAYy3AtאA6AcEAtZA +ATRA+3AtAAC3A1AcmArA,>A[AA LAa\AZAPAAHAAMAAfAǂAMzBF6BBTB9BuB|nB`hBBBtBMBVBa{B :BKpBADB)B|qBBBBBmB. BB$PB BCBC C)cC\ܥCzb CaCCcCJ\`C[1fCX*CThGCNC.qC8%ICLCgeCw\Ct9CwCDC oBB2BBh[B߮B Bh9B^utB_xBv6BnOTBzBW/BOB`BaeBgBh BBXAB!B'/BBriBd@@B;kXBB7:B&A1AAAoA AIA^LA,A؎ApLA[A'$AA AVZA#AN AnnA]\AʄAl7APq=A#AAAThAS0AASTA/AAA/pAA>҃A4 +TAAAA?MA zA\+DAkAUAaeAAy@܋AAqcAsA9AA"AwHAIPA࿺AAU[A_+AAjAo^A_AAjA8AYةA_r9AI*AA"BAbAvA~A{8A A; AWJAݔB`B&B=NBIBs7B\BmZB}6BBJBCF߮CCCCLCM&CXC] CHCU0@CZ2 +CY&5CX-CuIjCCCuA??NAA AAA[AfA|5A=WA)AM_AEA=A*dzA[A?WAOyAMAfA>!AG~AAA\iAQ|~AeCxXCGCsC!C`HC10C +C B+AAAr{A`AA{JAPAzAANAz _AתA _A1eAS@ףAqmA^xAAAQ`AV2*AWAcSAo~A+}AAث@TPA+ADA<*AR} AAgA:A{~AsSAZKAAAHuAzBAA0 +ApA AhAoA A[Ad%!A7AX~$AvAZw@A[IAVxAqdA<(AYA4A AphAyA#A6A A܆AĜnAZAL%AAɰB!B.7B!BB BmVBniBB\nB~+B'BB(BfB!By@BBB}2BR0Bs7BawBBBy*B=BCPCCzC%nC4pC/C6DnC7V9C=ʊC=)GCGCI4CDuGCQCX>*CVOCa Cd9-CgsCsG CCChD6D+D7$DGDFR^DL'DSDU0DS/DHD>VD:D +D4mD!zDDCC#C?C,CznCXCX%bCGyCEk*C(C;ŖC8)CWCx5CuCCڦC^;OCG6,C!ˉCBe B}BGBgBWBB[xBR_Bo܇B|Bo0B/wBsgB~LBuBdBza?BcBBmBβoB|zB4BjBaBB2*B B BA2AUbArAAgAADAPAQAlAAA A +A{A"A8AT )AnA^pAArAA@yiAhAegAlAu)AA&A(A A/3@A&A(A?`A,A(AEAAAuAJAU8A@A AeA\AȺA}A'A AKAwPLAnA8AAPAvuAAEAtA|sA[)ATAiA?ABJ1B8CBQd+AݖA1ALAAAϤA߆JA kBB`BFNB`cyBR=BQ#eBpMBZBXBkpBr B=BsBvBfcB bBBBoBWBBB&BBBC CzaCmC9lC5fC*2C&C3CBC9KCDCA1CCCUCN,CaCmi6CCJCOCfCVD% D;B4D^.Dn'DwD}KDbDD3DskDi2D_DJDD8D!_mD Dc!CsCC4=Cx@CdCVUCLAC< C( #C9 C88KCD0C7ؘC14COrCGʠCeC{޵C!CO>CSC9CBXPB]BT>BB^\BBBt؀BJBMpB|B}Z%Bj BguMBrB2B ^B6BBB?BBByB@BYB_2BRB B B0AlpA +A~Al`AfAAsAVZA\{lAEAkAaA€ABtAAB$A~AAA.AvA2ApcAiLAyAgA%oA.@ADVA?=@/AAlAkAhAMAވAAKAlUWAAiAB*A)AADAu A)A5APhA-ABANVAmȅAAAmAaA#>ABY@BCFCOBxAȹA +AAAA΀B AA|B/iB!pBIBStBj$FBoBxOB_ BBB@B]B3HBxB,BzBy3BFBq4B:BtBMBUBBղUCϞC)C,+CC)C- C.bC)PC-C5C9C9*C>bCDCRC< DC_oCl(CwCC9"CTDHjD(bDMFzDhxDdDbADFDUkD +DDPD{DDkGDMD1PDMrDqCCCuC!CJCFC;C2C9C>%C7|C9C6C>pC]C\7{CCCeCAVC.lC;B=B@BuB8BqLBzRBD@BS%B`RB_BWwKBO`B`BBBd 2B5BBB BFBC.BBB|B@B:!B1BAA\AzA^AǛ4AnAxbAjBAA3hA,rAAkIA8ArmAo]"AmAqA`A`A*AM\A'%Ay@1AA\fA6!AHALA6A#^!AaAAA/A +AAFiAAfAʕAֶAUݑAںABZBΉBpBkBAhA|%fA~I;A0Ai[A`aAAWAAJAU;AA2AzA%tABTRC cCQoB?AyLAAANAIAABrBEzB8 +BW +BVnBlBvRMBB %B*WBBBBaSZBofBrBuBTBvBSBcB<^B&$BtBrBNC +C߿C4OC&"C'/C1C/0C2i:C91C2QC6#C5pCICMLZCIJC[:Cg*C)CcnCCy +DmD=LDadDDZ1DYD4D_D@D%DdDDD.DlDPlD._YD u4DC NCďCgCl?CN\CACASC2?CAZC2C%3C5vC41CJ)DCKBCk)Cz'CsC\C?+C%[CRB湔BB(BgNBvBBu]BZVBM%BIBfBnKBmB<]5B|XJB\]BHRBiFBpXBB%{B 9B1BBB/"BPB#B1B AQBA{A`A.AIAbr)A{mA_AAeqAAA+ZA2XAAdA_AsAL + ApA]AXAAiؔ@G AA?`A!AvA-KAKA oAj.A@P9Av1AA0AtA|AABAA›B2BCCC ΡBAEA&AA9AA}AnA=cAV9A@A~AA AAurA+ABfnBriATAA`3AxAA4AB ABB|B&yBR B~[XBzҰBKBpBCBkB߆BBBB|B$VBBfnB6]B35BBB=B@BC{kC C/q]CCpC(C)FCB7C4kC-CCC7-oC6CByCF[pCPICajCeACkYCwC CjC D+DIDeDD6DāPDDEgEҲE sD!DDD>D]ޔD;,DDAClC5CCpCWdYCICORCHC7C:CExC<CHB;CQnCKVC=N}C=C5LhC6CA C=ҜCLOC_OCnCxCC.)CDPD;DE:De!DĕDDuDޓaEEEF2EfE2DDf"DDeDpED=AeD'3CC[C +CCcC^ CBZOCX xCIrC=yC:_C5QbC5'C1<,CICUCjC{}NCy qCW6C6C(CC%BnB9BunBBRBBBdslBi:KBf|BwjB}B\Bc!BBpBB7BxBIBBBt/HBg}Bo;Ba'BV)B]7BB,[B:A ?AȦAʙ~AAJAoAAZ|A8|A\KAA2AnAi|$Alk%At ALAHPA?`A!>AmA*A~!A.A[AbeA]AcXA%AtERAFJ +AA!A8A9bA)PA~AsAA&AF"lAAWA-BBbCV̞CʐBoBA2A,AAf0A8|A@XAAAғAuAPAAtAA9AܹALAAhA"A`AzTAMAHLAB +BBB(bBp".BhPrBiB8pB,BJB-Bz0By*^Bv!B"B0B*B?BiB,BbBRBnBUBgB,CCBChC#C/CGCi]CWOCHQC:C8C:C;EC5yC<eCL CYCh,8CuˈCqCCC[D D3)9DKD_QDDIDD E 41EF|EkE6'DDϩDbeD}DuDD|DtCCJC%Cr0CdCjvC]C[CNC:hC-C8UC7[5CBCE/C\ CrCmCgC[6YC:;C,C B B(BmFBWBF BBBwVjBypBqBaBiBlBcnBuBgBhBzjB|xBBBETBܭBrBd>B`BKTA>A.A(KACEAb#A?AdPA\AwtA@oApAA][AlAwA FAAA_A*yAa>W@d"A4vA)tA]VA)AAoA͎BJBB=Ay&A4Až2AvWzAovAd`A?²AemA(A] ACOARXAF!jA9Au@kAYA[Aw1A9A`AGAAGTAxAA횶AABB6BSB|eBoYBURB2BCB0BBBVB|NqBgFB-BLBq$4B9CBpBBC)CBBmBCC MC(C:o9CECNCacC= C5C5%C1C>C4OC;;COYCSCjVCts`CCKiCD +BD$%D3DODsPDDDDEE!7EhDDJDDDj8aD9 DWCCDC ++CCbCRCW)CR^CD:C96+C1C2CBWCGkCM4#Cb!CoR8CtCaChkCAGCuCEBBBa,BB%B|7B9BpB})BeBh`BWRBT1]BRB2LBWBvB_QeBBBBgByuBZB_pBZB^BwmBH5BdB +BAcAbA1AmAAA1vA8A`ޛAnA A]AAԠAAJAAWKAc2AUsA\ApuA3A' >A,AAb+A,A'A$AmjAAAl+AAOAToAeA0~nA(/AOIAAA?`AnAX@A3AgAAAAPAAAAAhA>A'BuB/BMtBijBmB{fBBBuBuBBg{Br*fBb&BBB B|CBTzBPC܆CC|BBBA4C CaCCN5CICNCOC>C5C6C4C6/C0 CLCN +1C`CpCxCFCUDPD"D*SD:tCDT6DDTDDDʑRDSDWDDDD_HD-krD SCCCC{bCdoCUCM9nC;C5=C6CPCC?'CIcCH7C^qC_o5CpK{CmCiNCe HCSC6ޘCJC|BώBMOBBtB9IBTBtBV)~B^2BMBH BZ*CBRclBIGBWdB2B{B$BmBXdB5BmBBBwBZfXBz\BAgBmbB BBAtANADAAfAXAJI@ņAL$ANA|hLAAAeAmiA*AζAYAov;AXA?`A%i*A/AÁAt +A]A?7AT>AAN4AT'A# AsA-AA&A5~AA>,AMAzA:>Aq AA_AyADQAJ֭AARA}~AAG*A?`A2{t@rA +sAcvA = +ԠA^AnҎA!0AnAAB +A0A`;CR=CWC=dCE7C0RC0C=s_C6C?UC;FCJMCVMCd9CboC#-CC7D "D̬D'jD>DXDxDDRUDVDy"DD[D]LDJDnDDD&D +CaCQNCCw!CjCHyCBcCKkvC:C6CC6CJcCGrCCaCVC^C}CkwCiՐC^C?0C:gCCoB2BžBB(B~GBB\XBz B|EB#eBJB=B}&B BBBABuSBiLBBTBKSXBYɆBrpBBBB;CCBBEBBCIC-CC=CWECNMC1oC6jC$lCiWCuC0CC4gCE4CNqC_Cd?ACnCCWCpQDXD]D|D/8D;DI/-D\4DpDDXDD| xDdED]>D:UD'r%DeCICCFAt|vAAA7]AtA: AY(AAkAI^AAzAgHAASA BA_sAGLA2)AiAǩA{zA}aAHA>AݿA*6A?`At{A>B{MB4FB|NBeBWBK(KBuztBiCB~19BazBcBSBrB@BvB"BlBSB3B5C8XC"]CE4CK3C=&WC7\mC"VC5C'QC,C#C+|:CBCCCAmCKCeSCCYC;CyDoD DiD%pD(VD7DA:*DLDSD^*D0mD@ +D2eD$xD]CܷMCnCDCCCtC*CDCTC?UCGCB'5CGCQCW/ChLCk!CqC4TCs*CZLnCQrC(C6B6BBBB]BBocBBlrBgBt |B>BgĎBFDBD8BIB.BŪBiBABWB BBVBB~BoBJ#BGB;XBOAoAKAĽAAAkAWAA1_A}AxA AFbAwA^AA AIC`CpC CCECADDD +D'DDUD |D'sD+.D*UD$D)uD(D a CCCCC}QCuC_CN_COCKXC@C9CNCZ-CVKCbpC*'CvYCԸCr`C CnCFmBC8CC +*BBB BBB0[BB^pBd66BduB0BcͨBPa*BBBW~BjXBBt0BwBoBB~Bt{}B*BBB˪Bw*eBaBY@BB%bQAAAAAܗA]AAA3AQAGRAq)AAWPA"QA!A$A*A1@ٔAlNAdA c7AfԲA AD6ARAU AoKAAAIA69AeDAIDAFAXGnAe2A~gAK2A`AldA6A?`AAh`AR@1 AA͔xAGBgAyA%~AR ACCoCC4CC)A2C3leC?+CCCj_C(fhC JC-Z0C, +C!qC: +CA7CYCa2CWCClWC;@D5DtCD 7D D)D fDjDeMDDCGDC;C"C%CCC~KCdbCR4CMrCFYCZ.CB; CKCC!CJCX#oCiC}CC$CC]C|RCLC*tC2BzBB1,B%BoBBgBr|Bk0B_~Bj B4BPTpBt\B|HB}nDB1BBuBBBdBBBB'BNBve4BlwBqTBXBB#B B_AEkA +AA;AARAo~3AAR AP6AAAY]A}y#A;]A_AP AUsApD@A?`A0@AhXgA}8@AA?APA@A/A -AzAeWAltSA&A?Al:AiA0ΐA AVgAW +AsuA~ARAA?oA?+*AUQA2+A7=AHAvwA`AAPDAdA'ATAof.A?AsArAA*ApA% A`AnAԾA:A'A A<0BB٢B0[BJ Bg+BV:BNBBBkuBN`Bk:BpByBUBzB~rBk(BBv;ByBbAB[BTBT"B B@CeCCC7C)CC0C#C3C/{CDdCI tCa|zCC CCCӡCٌCrCYCC +CC6CCقXCҸCCC)CCCbQCpCHICAӍCSԏCPGC=CJJC7ACI%Co^CyC'CCCu CIOCOCj)?CKC(B9B݉B޴BMBTB?BBCܮBk_A1Tv5B8,BfBXEBLELB_QBZBRDvB.B:BgBBqBBR +B?Bz6BBĈBȘBBˌB˱BDC CCjC&LCCuC,#CNCNFC&C!GCC!sC$\C',ZC2CKCKgChBfCGC@CCEC1CIuCIC;CջCm5C oC^CC? +C|CCeCsCoPC[$CEACYICPiCRqC>6\CABbCaNHCYrCnCcCBClCCMCFCa|C>C/2C `IBBB}EB-B$Bo'BeuBwB^@BY^BPVBDBoLB`s BYqBejBcOBiBz BitBB*C @BBBOBQQB BNWB'Bb[B:IqBB?BnAA?AǧADxAA~AxA}yAr=A AM9AKbAkAPG)AAjkAd"AuA`RAaԼA"AT@AhA@srADAADHAAqAnAAqZAAWAAxAbA]rA}^A{A:@8AdzAAAܐA|.A_AMARqhAPAD^A/ABABVAA}@tA?`A?`AIYA=ώAcALAZ:AyAAAAÊA=A6 AtB +=B%eB5 BC&C%C*CAA&Az`lA8+AAAALAAA{yAz^AAC@AKAAFuUA+AZAKfAҙA=5AOA4IdA)FAyAhABA3AAe%AAAvZAKFHA AA*AFAJA+NAkAHrAIS A/AAg:A8AtߩA3AYYAy.A#LA3AAZArGA?`AAϱAAA A3A[A{FA^AjAtASA,hAٞ^BB-|BB!eBB#ZBLSB_Bl0Bo Bn5BWtBS0BJB]BgBLBxBBm+B6BByBBaB`B.BBCC!ICiC6C6PC'CZCCMC+CACtC rC C#C T:C6!C:gC%eCA;BCX CsC+qCECѨ`CCCC;C CCc;C;CCsQCqCe}CW:\CTCTvCSWCYC^G;CY$oCC +CJ CHICKeCj~Cr;CZCbCg jCFrBCO-OCKgC4C^CBoB˦B.B1Bj7BKBt:BWBPXZBRAB;ZBDB-ӹBYrB;BLVB.0B2BWB3B[YBdRB4BYpB|BwSBE}B׊FC%Y,Cm*C TB1BBBBzauBwBi0B]޳B^hBBzSB_aBBdB%BC<[D"B0LB\B٘BRBU@ClC@C)+C-C5C*WCC!&HCHC4[CC VC ePC +vCvHCCC +BCCC-7C1nNCEf=CHCfzCtCv)CCCCCC;CvCiًCb*CgZCnCLCW|CTCLCJP1C]CZCVC\0CHzCIxCcpCd̪CGCRCAzC8MC|CC@VCqC BB$BZB +>BI B-BrBmUB_ZBfBnuB:BTB`B^qBUr BcBudBdcxBMB|BZ{BVC%MCnC +B,BBBB$B6B3rBĂB mB +A(AXB AAVAewA5AB +A2A:AH^AxAaOAH?b A[AAVABAK.AfA/AdzAd@A=@A5AMhAK@|A#+A?A9 AzAU7zA%xAmXAIZAt+oA'_@Ae'A+AnV:A5&ATAmAA MAEl*AAvAΗAb׳AR@AYr"ABAħ A$AAGxA_RAsJGAqVAAAm.AAA+A7%AkAUBB B +:B8&B4XB:BMBQBNBcɜB2BUBPBoWB`B}YBBB7BBfCnB,BB +B׹^BumCC+C +C#-C1C5CDCC +C@CCx9CNCC C].C C)C)uCC'-C;CK?CaCbqCg҃CxtC.CCaCeCZ CjC[C]uCN CWkCHǮCXmCVCR$C`CaGClXCT&C^ʕCg|CiCQ#TCEC: C5p=C.ֵCCXCBaBxbB0BTBjB`qB]!BsOBgBiBV6Byi@BMB^|B_"BOUBfB9KB8BL BIjB`5Bu"BBCBB!lBBQ BijB4B|tVBvB-8B0AABPA‰A*A$A]A‘CA~ 2A6BA:AL+J]A`{dAXAAsDA#AA`SAEZA A).AdAAphA]AaAA,3AXA@lW(A5AZA-A@AEADvVA A8VgAuD)AvAMAL A+A}EAYA8DAPAAtwA)Ax;tAyNUASt\AIgA. +A[8NAA+@־AAA^Aq:A)Ah`AcAA^AкA`AC\AlB!B/PB3D:BBQB-B4BX`BXB_]BjBbnB'BiB)wBBBB(BBBBE%B|BθBvC8C +m{C|C&C CpC$SC8CW-CaCBBK Bj~QBLBXBqBsBB +B(@BtB?nBKB 0%BiA/;AA2AuAA[AOAO8AkA\A3AύAt&AaAA51AtAA A A/"EAoHAAwA[AXA A&AA\ AA$A}AL+A?`A{(A%AeA?`A`8Aw-A0AzpAdACA]AuA:+AAJ(AZA?`A_IAJACxAAAr'9ACOA~DA)!GAMAjA^TAE^AXnAA&A8A~ieAg@AA1AAڹ_BCAǼB!B8BSB#HBGBXpBTBXhB^SmBRB| B[{XB`0Bt!BB3^BB.BBEBO@B!CIC]CCCgCACĺC PC@CC;+C?NBtChrC3C$CNBHCB;C +ݲC C=HCD;C9C cC/C.jCCC={CTCOLCV UCLvcCV>wCmoCPCXH9CT#CUC_[CoICŹCCCkC}Cwl+Cv-CWCABC"&C CC IBE]BjBmBϗ;BˮBB,B(BQBB8BCB0B}@B B>'B% BSBc;BL!B B#B&NA\BA10AAbA(ZAA(AA>AtA3Af +A +'A3A.)AuAA/8AAFAv%-@:AANAw+A?`A?`AFAi Ap@A1A@-ACHC=C9fGC:j>CKCX:nCL]AC^8CTCQCnCz?CC {CC\CiRCdTCbCGGC(7CJCҵC+C e{CCBMBBB'BBZBBjBP"B]Bu9BpBw`&BfBeB\qBc"B\BUB3A`B& B+WBPA4B#AAAӰAqdAoAAA(A^AAAOA0AIYAWsAo@Ai;A0AA~A9AxA[uA7AKHA^A:}A;C@١ALAAYGAFjAv;ADOAHA +qA[0A,A;AUEACAZ*AA(ACAAZAqAp$AyXAKA YA]JANAE@^!A)'@a2Ad%7AAfQAk`VAzA~A}HA(AAHAA~AڸDB2(BB*|B\B.BNB2rBR0BeE@BaBqzfBx(B#BYBzBBn8BwB5BdB}8BBˣBgBBBζC"'C TCC>CBQ:BSCA2BnPB +B CBB'tBCBuCC;CCy`ClC*C&GC4p{C8-ClCMfCOٴC^]FCWtCkyCgqC[>CAC-;C*CKC-?CcCoCC BiB|8BÌ@B]BBBsB}{BBn$]B1wBr٠BCBZB=ъB@BYnBYI(BX0B_hBmFB_r+B`KBVBJBmBmB)$BpBYiBuBQBH3BTBRBjhB:IBA +*B-B=BB~AP.A܆AűAx.AM)A;(AxAA{AyA;AlA'0AA&eAb*AAE@AE *Ae/ AA4AYGA%AR,AlAτaAAֶBnB*BB/BGBN#BoBDBBBAB>,Bz7xBv:BcrB5BBTIB҃C C2B$BDBcwBCqbC#RC:քC)^C'CwC TCHC?C֩CηC T#CdC +lC?JC#CBxB敐BfBCCBbC:B`BFCaC C PCmC)4C(jCBgCC +jCMCE~COC<C=n+CEJCBnCLXCAz;C0'C/C/2CJC BޗC shC/^CBB}BzB[BߎBf2BBBsB[YBn|BWXBdBoʓBLHBd7HBS8 BABNMB[RB]XBK4B`JB2;BW BT,zBu Bm|BBwBJB/j?B5BCB^'BSsBcvBEB.\B!BBB Aє A3&AlAA3A#AydAyaA@iA)AietA\UAhAnAFfAApAKXAAAziA~EVA{OBA׼AgAA A2AGADBBV%B 6ABB:B6ڈBJBcBgR*BBGBz,ByRBÞB/BBByBBBBBB\CAvaCG-CDaCL/C= 0C+C+C'WlCyhCSBXB(B5B*B/CxBB]B:BNBB.BBBpN B]zBX9BBVB`BB`OBKB]B~uHBLBbBZIBkwBiBdcB1Bg9BBJBBQzBFpB\6B0B7~B5B6BvB"B' )B}VA7ZA2AyZAt`AsA)RAf]A{Ad.AA ALAc?rAW[SAAZhA;AaKAYAs@SABUAk"AMA-YAG@AǜA3AGƎAMA0A`DAD[AAj)AA-_A AHxA[7@A4A5H +Ab?ApAAAvmA-ĆAxqA2*AD~A 8hAvA?`AU3KAYACAPF AAPAAoOADA0A^AlA"AAAA5B B B B)BNB4$,Bl^B}|sBBYBkB2BcBBBB)!BB(B0BgBcB!BjBMCIC*CMC=(C*C C:CDC8`Cf +BgNCBC8$CJC vCdCCxBBxBBB3B۳=B^~BҜByBCBB4BC C C8CQ?CSCCXCM,C9C.OC-_C'C6C C9vB*C +CCC BBBֵB.XBBҒCN۴C%CBnB+BvBkxBB9B~BXBpB{~BMrBcFBcQB!BrBuBGBX+NBYdBBO:BUgBp- BB6BQ|BzAb'dAAXH:A 7AAL9AAk|ANADAZAqTKA@AծAےAXAcQAV%A-rAdAGAiAC A3A?`A'/AH|ApnAmA4E9A AbWA&ACA$A]SAdAbAAwAATA]At1ƘdAkP>AsbHAYA +mAABAi~A _ArAJA:AaA٫BA[BB-gBi_BBBIBBB.?BAUB}BD\Bv5BBoBBB=B^B=jBB|CTC;CCCACPrC1pC %C CC6CYBhCCCC H/B.B0BӮB)B.^BrBSBKB/BhSB$BS +BvC/CB3"B CC*xCC_CBdC (C +CLBC[B^QC.BBBB[iBrBBBkB-jCCCBBBKBԩBBBQfBgBB^BuBBxhBRBm"~BL@B}fBcOBKvB^2B^'Bd%4B}BRBa@-B?wBSTBqB(PB2ABk`B5 AMBGA4gA`Au`AAMRAAAAMAƬAA-JARA^AA{EAA-A^~A[JADgAwAXA0DA<@A(~AA;A?`A+ȇAJA9Ar+A~AA>KA oAAADyACeA{~A]A=A FA#ATAASwAUAA[ACSAH{ApcACVG?BAA(^AIo@ئA\A̜AAArAAVuA5lAoAIAA~|A;AAܶAnB 'B BB'32BIaeBQjB BgrBvBtV5Bu BkBYdBBBpB(BxByBBB;BBB"BKBgB>CPC$C0C,C SCC +B/CBWBCWCJ_CD-B-BBBәB3BʢB"B B֊wBBH8BBB݀@BhB0BBؠC8TB``BUCc]BBBWaB#BB2BtBEB;B3B5B^gB[BSdB.B4BDBcnBCBBBBu BBB BihHBqBtB\]B+bB~]B_n@B`B]"BmB^1.Bw/UBeBl&BiWBsBBZqkBZN B_ BzHB:bBd=B>m(B$ B$ 5BvB*-B2AtAޜtAAAÃAkRA +XATA앿A?LADAHA9$BHAՂA-AуH@@APA# +@APgAhAA!-AAA&AbA>AA2A9AA7VA^tSA!A(AA0AArAANA/^A?`A1jA2RAg'A΀AB,NAיAVAf]A|EAXdAz6AKA4AglA,AdApATAtA2aA'AAA*AŊA2AlIAAʃB XAB(BGHB,B*hBToBd[eBv=BwBRBIaBlk$BhCBdOBBkQB\MBlB=BB}B|vBsB0BوBѩB|#BkBɦB뻛BMNBByB+"B"Bc}B7BBqBʖB/B^B,SBCBՎBLBTBv[BBw^BbB_BB^BeBq"BL BWB BuSBYBB5BBnBBuX0Bc(BnB^BBlB[JBMRBJBkBB} B_dBlBCBDB8B2BK7eAҊB=BBfrA|kB"$A֫AAddAm@}ARA,A|NAc6A}A]AVpArA A҈A|֑AAAַvAMAA1B(-MBIcvB_B9}BBwB[Bdi_BlBBwVABz`lBih Bs B^B&HBBOB;fBXkBLBRBpBȞB+B1B/tC +1`C ۉCmCC CC4BABC B(Bu(BeBE{BBGB'BQBă3BʡBMB\B&BB=BUB +QBڧ3B̡BtBBwB BƎ(B\BB}B(B~B9Bo +BBeB5BBYBdBBTBdB=4BPBbOBBjBmBB[BcB;BB,BB{BrB(_Bg(BUByBuB|BcBDMBv#BsqBw]BBpBseBC؆BSGB VB>jBL@B0B!BBB|\BAA2A>A4A!AAAFAnAJ:AY}AQ4AGAQNA֡AvYA;AT]AG#.AuvAA[HcAS A +A4AUGAGbA(SA7AAMAuAHA A'A{VA?`A@9ACB`BBBBB +B]vB^h-BZTBihBuNBn83B<9B=D-B:E-BBRBByAŁA}JAA\>A#:A%PA| AA̰AnAAA\YJAdA^AA AM7AXC$CC QC C CmC CY[C"]BCCeCgCtCDCטC.CCCS;C +tyBBBBB BBAB%BܮBĎ%BB9?C$BBB'BxBHBBdB@B+BBBBBLBLBABB0B%BtBXBB]LBeBqB,B|AB BFB~BrBu*]BTBwBWBBBBŔBŝ&BtBjB\BkcB>ҌBpKBR/B5BB7SBCBOBAB# A暝A9%AAVA|AAAANMAA A0C~A?`AxAA|A<\A$AfAupA.3A7AAzzA+ՆA`AA8A]zA0AA[LApA#ZA&A֦XAېB¡BuB#-LBBBJBiBhB]BBmBA)BWB\BbrB*nBBBB"B>BBОBFBBBNC$OvC CfCC NCCQC:C +aCB3VCCgChCC$3C;C`C-CșCDB;B+JBBר4BBBپB.BܐHBUBXBVFB%BB&BjBBa*BBBOB+B$#BBBBBpB:BBd\UBv|BxlBYBBBαB}BCB@BiB6BΦiB B2B9BkBcV>BAVBMr;BJBm]B5BA|JANDA[@AZOA8xAy?AFA&<@tA#A SARJ{AA@AhAuA$^Ap +`@8A\AAUszAaAو@{ARiANAA& +AfHdAٲAA[~AQAAlBBMB ΖBiBP[BQqBz¦BTc/B}5BgBh.BG:BE3BB>ABtBZB8ALAALAAyAAhA>OA,AAٳAAAuAmAA{GAAHq~As6JA"AAuAfAk\AH@A){AhAAwA_(@ 0AYAARA~^AVzdA~@OAAs@ AOA6A`MA{5AfXA?`A/3AtBA &A!wAhAAiAAfAAiƑA@:ARA=ACiAR@A'A[ALAA4OA@*A\AZA?`AQtA2AAhA4pBAXA)hBA:A^AiA0AcAAvWAvARAlJAYOAAWAyUABAL\AZj.A78A_ +AA ~;A:AN`AOXA0cAbAARA@Ah6A%A6AD!}A5A bA!HA?`AyB@۳HAUACTAA$AhA.ףAnxA2*AAXOAuAo4Ay~AA^AaAymRAA{CAA=BAANtAAg7kA,AAZdA@VA2AiA3AbBlBB8B4DLBmBnMBwE^B$BB|BA>AA"A"GbAs7A!A-AA=wA#Aq^qAy(AʠAOsAMA:8AKA7AAfAzA`AAgaA7A9ÃA7A$A]PBCtB5'BD"EBcBLXBWiBB{_QBd +B QB0B!UB$Bj +4BBJBB>KBwj8BBBPByB8BZBgBCvC +C CC.C CC >CqC +B xC C9^B?C5BkGCBB]BEVBM/BBΐBۺBμBqIB(BBB8BBՃjBBۣB8CFaBBεBzB+BfVBiBBBB(B#EBsBB?BjBi4B BSeBJBkBBBBpBjQB}p B'BtDB~ .BBk#BQBrBN%B5QBF B-iB@_KB6AAtAԠA[ AoA'ALAR9A_AǝAظA1A>BBtBBB BFWBC +B~YC #C+rCQtC~|D&C FBZBBXB§BTBYBV_BUBBBeBBBBBBvvBBBBmxzBqy`B8BxJB3BBaB=VBWBKWB?dZB4UB4kiB#!APA.B8qABFBKB*X,B j7AALAZAAbA|=A.AAuAyvArAzAAw$A\A9AB.C:GAs^A?`A!Amx Aj\Ajc8@]AkGA?`Ae sA~5AjAW>A5;=AE-AApeAWA4AszHA?`AyAvA8$LAo +tAevAABA4_A?*hA^AbA4AfAg@ߑAUAn)AvAAUAv@AABAV-AARAAѥAڊABh8A^|A-A~wAAPApA AͅAAAȑA0AtAB7:B'B6 BC*BGBB+Bs|Bc +BpD?B]EBUBvBIB|BhB.Bp;BBBuBxhBqVBoKB=B~8BHB?BB +BੜB. BдCBtCSC C ]nCpCC#)rC&SC#PCףC mC_CrB BBB)=CC4BBDB`OB]BAkAUmArAxA5A)AtA=6@AvlrAnQAQ,A?`Al9@AA"A7ATA|AXAABAA AkA .AAtEA'xA"I3A?`AktA'LAAAvAQ7AgAQHAFA"A`A6A7As6ALAAjAAvAӺZBBcB+pB"B@ˮBNBS BwBhBSBuBBRmBBQBlPB7Bt6B@BOBnZBBvTbBBuB(BӷBB*B~BBNmBB$CgCCC"C@]CR0C_C1ICsC(C SB^AxAIA퉺A#AZAA0B*A؇AApAAĽ~A.THA[AAѪB]&A7QA~A-kA1A'AAR9A AqA?`@Ae޾AWAAlARAK4A+AXAA9AuF A"*ZA1A>!A<,AtAgsIAPAfAo AC1_A8AzAKAZGPAUAȸApAA&FA#AA[ AͦA$!A?`AQ$ A?`AdkOALOAAdA[AAR#AA:A+AA4A7^AFA!AARB B_BB#B4BB"Bj-BK]BnqBBv*B]BBB B~lB'BtBQZBBBfBB}2BfBB:CMuBOPB,BBBBރ_BЗBC`C/C8kCtjCQCYCdaC&ChVB.CZBtBB6oBBBEgB~IBJ"BKBHBWB1B,JA/A[A4ձA_ A0A?`A+AKcA8A\XAAA AP +AhAXNBWqBBB*/AA3ArAnɹA:>AMOAAXA?`AALAX*AZAA9A AgAaAfACA7AHB$AQAAUNBBrBuBGbBB"B9MB%zB˘AZAAr1BoBB6AlBAB9ABfA(AAAȮA+AA&AVAA2AcA A?:AJAAj+AAAAA9ZA1AgDAdAAhMSAN@ǺRAeW@ A*TAH.A?`A$AUA+AdAAAaA]3FAwAJ aA}AUA8A9CAX^A_oAAnuw@=AAH@APA/A|ANAV[AAbpAX +AŠAAR A/AvATA\&A{A/]AAAXAVmA}@LAAJA A|AA@A}AMWA*BAAAA]A-B&B+B7jdBLBt?9B[BJBm_B\:B |BBBmU4Bb~BTBNB|B^BWBhyBVBLI0B?BK:B)B'LjBY5B#DBZKIB{Bi8BBxBlBBdBB&;C 3CC6-CG۪CCCCvCEC +BBɷBjC vCC/CEBBO{B*B3LBݝBÀB/BB2B.*BB dC#B\B_B߭B3BIBTB+BBuBi:BBv:BqmBwYBaBvBhB?3B4B4BGB)rBۅAA۽BNBB!PBQBB:B"B ɎBcAľA.rA¨AT-AA^AwALAnArA|QAAYA'ApAAAAA>AAv AAʈAtpbAAyA @IdA?AA?`AitA?`@QAH A3AANAc`A +AA>A7kAtALA:H@y*AA +AJ2A`4AN)A)XA`JA>AAcAAA|µAR:%Ag*AmA3ANvAAb6jAAA6BA'A]{nADA)A\A3,yAG@$A?`A{$AȦAA\AVWAD0A@AAOAmA(A5B9AzBB)S2B9BZBAU=B>dBF8BD $BtpBaBlW.Bq B.NBaċB?rBXBlzKB?VBXcBuBR&BQ[B 1B/6By3B)uBVyB/BO,(Bk#BByB;BBB~B^C C#FCNCnYC>C;,CCe3C2eC~C}BBCFC:CFVC B]BיB֙B# BݎBBPBZaBiB(B-B̶BBBaBKB$BmB6Bj}BHBmYB!Bn +B@BfہBABOuB/B2VBgB@^BA܏BKA0AL%AAA%AA BA9AAAnAOA.0AAAMMA͝A vA [AAAZAxATeA'A[kAeA +A_AlA0$AXAXAbA?`A=ZA| A9;9AYA>@LA0?AA]DZA-,oAH"4AAQ AA?`@>A1:A@ACTA`AU(APnA(AE.Z@~gAd~A6 A Al4AHAC?A+gA AQFA!A1rAD/Af@HAHfAAADAC!ABFA_A'VArA];A9AeALdAgAVT9AQlAuARAAAXMAW^ArAMA?XA_9A~eA.]A3AA|`pA?`Aw A`AҐAXA*AjFAb,A2~AA dAbAA$ArBBQB BB(B"BBB]]Bi;B30BBbBd|(ByqBB^fkBJ"BY4nBEB?MB7vB[pBC%&B$CC T6B BܸB%BBٚBCWC*LC C!B%B]BBBB.BB(BޝB,BTLB BDBbBHBBAndBs%BoBbVBoh:BkBD=BY BL6BBjIBDB&TB BBoB|NA pBBA8A;AGAB A'A8B /APA5A=AΨ~AA{YA]B0AɈA>A-AjAAufA#.AXUA`A AuYRAzA\ߢAA0AXAA|AvA8r0AgF9A??A@trAW>dA_PA]vA%!AbA'A8lnA1AnOoA 6A?`AAfǗ@As[AAgASزAY]ABsA@AmA"'AzA ?AT +^A~*AHA?`AZ.A":@ A5A<2A?`AAw8A7A\lA]4AEDA?JA@AlLAAsAxAZAAA1Aq0AvAL?A|&AuA AfvAA`AVAAAˀA7AVALBIB-gB+BIgKB8=BPBCBBmBwNBBaBj!BPMBBcB@=BGB5mB,BFB-z8B(g>B+B1B/B*MBB}B\bCBdTB0BԈLBrB\BՍBA>AAwPA~AAܖA{=yAePAAVA_ vAfA;A-AUASAO aAFA?`AtAGO`A BV@A\AAH'AXfAkA/cAAY7A"sAuA)NzAFAH\oAKA~A'FA^AY2@#@qA_4A"A&@A +wA2AA?`AA #AA>AzABA?`A;An.@AA{ZAAeVAZ9A]A,1A4(A2XA^&AK5A]#gAAtAA]N=AgA) AtA,AktAA +AAAAA?A8B4B BxqBK)B.KBA3BYBpBtNBpRB~4BuBf͙BpVBGB9.B+9BYBB0PBB/BrB?}B+BBݥBlB<[BBvUBB4B B/PBT4BH KB)BEyBGB0Bu5B/SBDCeBc٫B{M6B_BBBkcB`BMDBW,BE)BbB5B96B*B5B% +sB+B&xB;B/ZB1"B+ҫB4B=2BBB_CB~HsBV!~BLBXMBvqB=BԑmB`B$pB}BYBUB+BmBuBHBBBBg@BBDBjBB:B-B~%XBfBtSBBnBSBk +B=BczBKpBYB*ڦB,~B3?/B<B5ʘB&BX6B)bB/:XB?fB%;B$pBB+|BHBAnB nAAƊAg6AAۡAA)vAxA˓A.LAZAAAA^ At\QAA*AAcAdAANAIAlAr$AyA~?AKAkt