The R markdown is available from the pulldown menu for Code at the upper-right, choose “Download Rmd”, or download the Rmd from GitHub.

Cytoscape Ecosytem

Cytoscape is a well-known bioinformatics tool for displaying and exploring biological networks. The Cytoscape Ecosytem extends beyond the desktop software to include web apps (like cytoscape.js), community-contributed collections of networks (NDEx) and apps (AppStore), and the CyREST programmatic interface. Programmatic access and interactive display via R, Python and JS enable a braod range of applications in network anlaysis and visualization, leveraging the Cytoscape Ecosystem.

Installation

RCy3 is a Bioconductor package that connects R to a locally running instance of the Cytoscape desktop software via CyREST.

if(!"RCy3" %in% installed.packages()){
    install.packages("BiocManager")
    BiocManager::install("RCy3")
}
library(RCy3)

The whole point of RCy3 is to connect with Cytoscape. You will need to install and launch Cytoscape:

Confirm your connection with the cytoscapePing funciton:

cytoscapePing()

PART I

Here, we will access NDEx and control Cytoscape using just a few of the hundreds of methods available in RCy3.

Load Network from NDEx

We will import a network from NDEx into the Cytoscape environment. Networks are provided a Universal Unique ID (UUID) by NDEx that can be used to reliably reference a particular network. The import method returns a Cytoscape Session Unique ID (SUID) for the imported network, enabling us to reference it in subsequent steps within a given Cytoscape session.

NETWORK_UUID = 'b1e9a489-bbe7-11ea-aaef-0ac135e8bacf' # UUID provided by NDEx
network_suid <- importNetworkFromNDEx(NETWORK_UUID) 

Copy Visual Style

One of the easiest ways to apply a visual style to a network is to copy that style from another network. This lets you work on the style of your network in Cytoscape using the interactive style editor, then save the network to NDEx for future use as a template.

The name of the template network becomes the name of its style and the set method assigns that style to another network, specified by that network’s SUID.

STYLE_NETWORK_UUID = 'b1c1aa27-bbe7-11ea-aaef-0ac135e8bacf'
style_network_suid <- importNetworkFromNDEx(STYLE_NETWORK_UUID) 
style_network_name <- getNetworkName(style_network_suid)
setVisualStyle(paste0(style_network_name,'-Style'), network=network_suid)
setCurrentNetwork(network_suid)

Apply Layout

We can easily access the layouts available in Cytoscape.

getLayoutNames() # explore available layouts
layoutNetwork('force-directed', network=network_suid)

Cytoscape layouts also have parameters that can be tuned for each network

getLayoutPropertyNames('force-directed') # explore parameter options
layoutNetwork('force-directed defaultSpringCoefficient=1E-5', network=network_suid)

Save Network to NDEx

There is a one-step method to save networks from Cytoscape to NDEx. You can also control whether the network is publicly accessible or private to your account.

Note: By itself, “public” does not mean that users can find the network by searching NDEx. This is so that searches are not cluttered by networks that were convenient to make public but which are not intended for general use. To make a network findable in searches, you need to go to your account on the NDEx site and change that setting. In the context of a tutorial where you access NDEx programmatically, you can see how important this is: no one wants to find the 20,000 networks you accidentally loaded to your account because of a bug in your code.

new_name <- paste0(getNetworkName(network_suid),'-tutorial')
renameNetwork(new_name)
USERNAME <- readline('Enter your NDEx username: ')
PASSWORD <- readline('Enter your NDEx password: ') 
new_network_uuid <- exportNetworkToNDEx(USERNAME, PASSWORD, isPublic=FALSE, network=network_suid)

PART II

In addition to working with networks from NDEx, you can also load networks from local or hosted flatfiles in a variety of formats and annotate them with data, just like you would using the Cytoscape GUI.

Load PPI Network

Tabular data can be read in as a dataframe and then loaded as a network in Cytoscape.

Note: A column named “source” automatically becomes the source node column; “target” becomes the target node column, and “interaction” becomes the edge interactions column. All other columns become edge attributes.

PPI_DATA_URL = 'https://raw.githubusercontent.com/cytoscape/cytoscape-automation/master/for-scripters/Python/data/ap-ms-demo-data.csv'
ppi_data <- read.csv(PPI_DATA_URL, stringsAsFactors = F)
ppi_data
colnames(ppi_data)[1:2] <- c('source','target')
ppi_data
ppi_suid <- createNetworkFromDataFrames(edges=ppi_data, 
                            title = 'AP-MS Demo Data-tutorial',
                            collection = 'AP-MS Demo Data')

Load Expression Data

Now, the network can be annotated with expression data, provided a column in the datset matches a column in the network’s Node Table.

EXPRESSION_DATA_URL = 'https://raw.githubusercontent.com/cytoscape/cytoscape-automation/master/for-scripters/Python/data/annotation-data.csv'
exp_data <- read.csv(EXPRESSION_DATA_URL, stringsAsFactors = F)
exp_data

In this example, the column “GeneSymbol” matches the source nodes in our network in a default “name” column.

loadTableData(exp_data, data.key.column = 'GeneSymbol')

Save Network to NDEx

Again, we can save this network to NDEx as well, including the network and associated expression data.

USERNAME <- readline('Enter your NDEx username: ')
PASSWORD <- readline('Enter your NDEx password: ')
ppi_data_uuid <- exportNetworkToNDEx(USERNAME, PASSWORD, isPublic=FALSE, network=ppi_suid)

PART III

We have demonstrating working with networks and datasets in Cytoscape via scripting. Of course, you can also represent networks at data objects in your scripting environment for bioinformatic analysis.

Retrieve Network from NDEx

In order to retrieve networks from NDEx and represent them in R, we will need to install the ndexr package.

if(!"ndexr" %in% installed.packages()){
    install.packages("BiocManager")
    BiocManager::install("ndexr")
}
library(ndexr)

You can start with NDEx by first establishing a connection.

ndexcon <- ndex_connect()

We will use ndexcon throughout the other ndexr calls. For example, a basic search.

networks <- ndex_find_networks(ndexcon, "Breast Cancer")
print(networks[,c("name","externalId","nodeCount","edgeCount")])

That print statement provides a nifty way to browse the search results. You’ll notice that we got results that hit each of the search terms individually, thus including any pathway with “cancer” in the name. That’s perhaps a bit too broad…

networks <- ndex_find_networks(ndexcon, "BRCA")
print(networks[,c("name","externalId","nodeCount","edgeCount")])

Ok. We can work with this list. Let’s use the first hit. Note: you are going to get different hits as this database changes over time, so proceed with any hit you like.

networkId = networks$externalId[1]
network = ndex_get_network(ndexcon, networkId)
print(network)

The network is an RCX object. Explore the RCX object to see its contents.

str(network)
network$metaData
network$nodes
network$edges

Note: RCX can be coverted to two flavors of igraph objects using rcx_toNGraph and rcx_toRCXgraph. See method documentation for more details.

LS0tCnRpdGxlOiAiQ3l0b3NjYXBlIEVjb3N5c3RlbSBUdXRvcmlhbCIKYXV0aG9yOiAiYnkgQWxleGFuZGVyIFBpY28iCnBhY2thZ2U6IFJDeTMKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogIm5vbmUiCiMgIHBkZl9kb2N1bWVudDoKIyAgICB0b2M6IHRydWUgIAotLS0KYGBge3IsIGVjaG8gPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGV2YWw9RkFMU0UKKQpgYGAKCipUaGUgUiBtYXJrZG93biBpcyBhdmFpbGFibGUgZnJvbSB0aGUgcHVsbGRvd24gbWVudSBmb3IqIENvZGUgKmF0IHRoZSB1cHBlci1yaWdodCwgY2hvb3NlICJEb3dubG9hZCBSbWQiLCBvciBbZG93bmxvYWQgdGhlIFJtZCBmcm9tIEdpdEh1Yl0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2N5dG9zY2FwZS9jeXRvc2NhcGUtYXV0b21hdGlvbi9tYXN0ZXIvZm9yLXNjcmlwdGVycy9SL25vdGVib29rcy9pc21iLW5ldHdvcmstYW5hbHlzaXMtdHV0b3JpYWwuUm1kKS4qCgojIEN5dG9zY2FwZSBFY29zeXRlbQpDeXRvc2NhcGUgaXMgYSB3ZWxsLWtub3duIGJpb2luZm9ybWF0aWNzIHRvb2wgZm9yIGRpc3BsYXlpbmcgYW5kIGV4cGxvcmluZyBiaW9sb2dpY2FsIG5ldHdvcmtzLiBUaGUgQ3l0b3NjYXBlIEVjb3N5dGVtIGV4dGVuZHMgYmV5b25kIHRoZSBkZXNrdG9wIHNvZnR3YXJlIHRvIGluY2x1ZGUgd2ViIGFwcHMgKGxpa2UgY3l0b3NjYXBlLmpzKSwgY29tbXVuaXR5LWNvbnRyaWJ1dGVkIGNvbGxlY3Rpb25zIG9mIG5ldHdvcmtzIChOREV4KSBhbmQgYXBwcyAoQXBwU3RvcmUpLCBhbmQgdGhlIEN5UkVTVCBwcm9ncmFtbWF0aWMgaW50ZXJmYWNlLiBQcm9ncmFtbWF0aWMgYWNjZXNzIGFuZCBpbnRlcmFjdGl2ZSBkaXNwbGF5IHZpYSBSLCBQeXRob24gYW5kIEpTIGVuYWJsZSBhIGJyYW9kIHJhbmdlIG9mIGFwcGxpY2F0aW9ucyBpbiBuZXR3b3JrIGFubGF5c2lzIGFuZCB2aXN1YWxpemF0aW9uLCBsZXZlcmFnaW5nIHRoZSBDeXRvc2NhcGUgRWNvc3lzdGVtLgoKCiMgSW5zdGFsbGF0aW9uCioqUkN5MyoqIGlzIGEgQmlvY29uZHVjdG9yIHBhY2thZ2UgdGhhdCBjb25uZWN0cyBSIHRvIGEgbG9jYWxseSBydW5uaW5nIGluc3RhbmNlIG9mIHRoZSBDeXRvc2NhcGUgZGVza3RvcCBzb2Z0d2FyZSB2aWEgQ3lSRVNULgpgYGB7cn0KaWYoISJSQ3kzIiAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpKXsKICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKICAgIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJSQ3kzIikKfQpsaWJyYXJ5KFJDeTMpCmBgYAoKVGhlIHdob2xlIHBvaW50IG9mIFJDeTMgaXMgdG8gY29ubmVjdCB3aXRoIEN5dG9zY2FwZS4gWW91IHdpbGwgbmVlZCB0byBpbnN0YWxsIGFuZCBsYXVuY2ggQ3l0b3NjYXBlOiAKCiogRG93bmxvYWQgdGhlIGxhdGVzdCBDeXRvc2NhcGUgZnJvbSBodHRwOi8vd3d3LmN5dG9zY2FwZS5vcmcvZG93bmxvYWQucGhwIAoqIENvbXBsZXRlIGluc3RhbGxhdGlvbiB3aXphcmQKKiBMYXVuY2ggQ3l0b3NjYXBlIAoKQ29uZmlybSB5b3VyIGNvbm5lY3Rpb24gd2l0aCB0aGUgKmN5dG9zY2FwZVBpbmcqIGZ1bmNpdG9uOgpgYGB7cn0KY3l0b3NjYXBlUGluZygpCmBgYAoKIyBQQVJUIEkgCkhlcmUsIHdlIHdpbGwgYWNjZXNzIE5ERXggYW5kIGNvbnRyb2wgQ3l0b3NjYXBlIHVzaW5nIGp1c3QgYSBmZXcgb2YgdGhlIGh1bmRyZWRzIG9mIG1ldGhvZHMgYXZhaWxhYmxlIGluIFJDeTMuCgojIyBMb2FkIE5ldHdvcmsgZnJvbSBOREV4CldlIHdpbGwgaW1wb3J0IGEgbmV0d29yayBmcm9tIE5ERXggaW50byB0aGUgQ3l0b3NjYXBlIGVudmlyb25tZW50LiBOZXR3b3JrcyBhcmUgcHJvdmlkZWQgYSBVbml2ZXJzYWwgVW5pcXVlIElEIChVVUlEKSBieSBOREV4IHRoYXQgY2FuIGJlIHVzZWQgdG8gcmVsaWFibHkgcmVmZXJlbmNlIGEgcGFydGljdWxhciBuZXR3b3JrLiBUaGUgaW1wb3J0IG1ldGhvZCByZXR1cm5zIGEgQ3l0b3NjYXBlIFNlc3Npb24gVW5pcXVlIElEIChTVUlEKSBmb3IgdGhlIGltcG9ydGVkIG5ldHdvcmssIGVuYWJsaW5nIHVzIHRvIHJlZmVyZW5jZSBpdCBpbiBzdWJzZXF1ZW50IHN0ZXBzIHdpdGhpbiBhIGdpdmVuIEN5dG9zY2FwZSBzZXNzaW9uLgoKYGBge3J9Ck5FVFdPUktfVVVJRCA9ICdiMWU5YTQ4OS1iYmU3LTExZWEtYWFlZi0wYWMxMzVlOGJhY2YnICMgVVVJRCBwcm92aWRlZCBieSBOREV4Cm5ldHdvcmtfc3VpZCA8LSBpbXBvcnROZXR3b3JrRnJvbU5ERXgoTkVUV09SS19VVUlEKSAKYGBgCgkKIyMgQ29weSBWaXN1YWwgU3R5bGUKT25lIG9mIHRoZSBlYXNpZXN0IHdheXMgdG8gYXBwbHkgYSB2aXN1YWwgc3R5bGUgdG8gYSBuZXR3b3JrIGlzIHRvIGNvcHkgdGhhdCBzdHlsZSBmcm9tIGFub3RoZXIgbmV0d29yay4gVGhpcyBsZXRzIHlvdSB3b3JrIG9uIHRoZSBzdHlsZSBvZiB5b3VyIG5ldHdvcmsgaW4gQ3l0b3NjYXBlIHVzaW5nIHRoZSBpbnRlcmFjdGl2ZSBzdHlsZSBlZGl0b3IsIHRoZW4gc2F2ZSB0aGUgbmV0d29yayB0byBOREV4IGZvciBmdXR1cmUgdXNlIGFzIGEgdGVtcGxhdGUuCgpUaGUgbmFtZSBvZiB0aGUgdGVtcGxhdGUgbmV0d29yayBiZWNvbWVzIHRoZSBuYW1lIG9mIGl0cyBzdHlsZSBhbmQgdGhlICpzZXQqIG1ldGhvZCBhc3NpZ25zIHRoYXQgc3R5bGUgdG8gYW5vdGhlciBuZXR3b3JrLCBzcGVjaWZpZWQgYnkgdGhhdCBuZXR3b3JrJ3MgU1VJRC4KYGBge3J9ClNUWUxFX05FVFdPUktfVVVJRCA9ICdiMWMxYWEyNy1iYmU3LTExZWEtYWFlZi0wYWMxMzVlOGJhY2YnCnN0eWxlX25ldHdvcmtfc3VpZCA8LSBpbXBvcnROZXR3b3JrRnJvbU5ERXgoU1RZTEVfTkVUV09SS19VVUlEKSAKc3R5bGVfbmV0d29ya19uYW1lIDwtIGdldE5ldHdvcmtOYW1lKHN0eWxlX25ldHdvcmtfc3VpZCkKc2V0VmlzdWFsU3R5bGUocGFzdGUwKHN0eWxlX25ldHdvcmtfbmFtZSwnLVN0eWxlJyksIG5ldHdvcms9bmV0d29ya19zdWlkKQpzZXRDdXJyZW50TmV0d29yayhuZXR3b3JrX3N1aWQpCmBgYAoKIyMgQXBwbHkgTGF5b3V0CldlIGNhbiBlYXNpbHkgYWNjZXNzIHRoZSBsYXlvdXRzIGF2YWlsYWJsZSBpbiBDeXRvc2NhcGUuCmBgYHtyfQpnZXRMYXlvdXROYW1lcygpICMgZXhwbG9yZSBhdmFpbGFibGUgbGF5b3V0cwpsYXlvdXROZXR3b3JrKCdmb3JjZS1kaXJlY3RlZCcsIG5ldHdvcms9bmV0d29ya19zdWlkKQpgYGAKCkN5dG9zY2FwZSBsYXlvdXRzIGFsc28gaGF2ZSBwYXJhbWV0ZXJzIHRoYXQgY2FuIGJlIHR1bmVkIGZvciBlYWNoIG5ldHdvcmsKYGBge3J9CmdldExheW91dFByb3BlcnR5TmFtZXMoJ2ZvcmNlLWRpcmVjdGVkJykgIyBleHBsb3JlIHBhcmFtZXRlciBvcHRpb25zCmxheW91dE5ldHdvcmsoJ2ZvcmNlLWRpcmVjdGVkIGRlZmF1bHRTcHJpbmdDb2VmZmljaWVudD0xRS01JywgbmV0d29yaz1uZXR3b3JrX3N1aWQpCmBgYAoKIyMgU2F2ZSBOZXR3b3JrIHRvIE5ERXgKVGhlcmUgaXMgYSBvbmUtc3RlcCBtZXRob2QgdG8gc2F2ZSBuZXR3b3JrcyBmcm9tIEN5dG9zY2FwZSB0byBOREV4LiBZb3UgY2FuIGFsc28gY29udHJvbCB3aGV0aGVyIHRoZSBuZXR3b3JrIGlzIHB1YmxpY2x5IGFjY2Vzc2libGUgb3IgcHJpdmF0ZSB0byB5b3VyIGFjY291bnQuCgoqKk5vdGU6KiogQnkgaXRzZWxmLCAicHVibGljIiBkb2VzIG5vdCBtZWFuIHRoYXQgdXNlcnMgY2FuIGZpbmQgdGhlIG5ldHdvcmsgYnkgc2VhcmNoaW5nIE5ERXguIFRoaXMgaXMgc28gdGhhdCBzZWFyY2hlcyBhcmUgbm90IGNsdXR0ZXJlZCBieSBuZXR3b3JrcyB0aGF0IHdlcmUgY29udmVuaWVudCB0byBtYWtlIHB1YmxpYyBidXQgd2hpY2ggYXJlIG5vdCBpbnRlbmRlZCBmb3IgZ2VuZXJhbCB1c2UuIFRvIG1ha2UgYSBuZXR3b3JrIGZpbmRhYmxlIGluIHNlYXJjaGVzLCB5b3UgbmVlZCB0byBnbyB0byB5b3VyIGFjY291bnQgb24gdGhlIE5ERXggc2l0ZSBhbmQgY2hhbmdlIHRoYXQgc2V0dGluZy4gSW4gdGhlIGNvbnRleHQgb2YgYSB0dXRvcmlhbCB3aGVyZSB5b3UgYWNjZXNzIE5ERXggcHJvZ3JhbW1hdGljYWxseSwgeW91IGNhbiBzZWUgaG93IGltcG9ydGFudCB0aGlzIGlzOiBubyBvbmUgd2FudHMgdG8gZmluZCB0aGUgMjAsMDAwIG5ldHdvcmtzIHlvdSBhY2NpZGVudGFsbHkgbG9hZGVkIHRvIHlvdXIgYWNjb3VudCBiZWNhdXNlIG9mIGEgYnVnIGluIHlvdXIgY29kZS4KCmBgYHtyfQpuZXdfbmFtZSA8LSBwYXN0ZTAoZ2V0TmV0d29ya05hbWUobmV0d29ya19zdWlkKSwnLXR1dG9yaWFsJykKcmVuYW1lTmV0d29yayhuZXdfbmFtZSkKYGBgCgpgYGB7cn0KVVNFUk5BTUUgPC0gcmVhZGxpbmUoJ0VudGVyIHlvdXIgTkRFeCB1c2VybmFtZTogJykKUEFTU1dPUkQgPC0gcmVhZGxpbmUoJ0VudGVyIHlvdXIgTkRFeCBwYXNzd29yZDogJykgCm5ld19uZXR3b3JrX3V1aWQgPC0gZXhwb3J0TmV0d29ya1RvTkRFeChVU0VSTkFNRSwgUEFTU1dPUkQsIGlzUHVibGljPUZBTFNFLCBuZXR3b3JrPW5ldHdvcmtfc3VpZCkKYGBgCgoKIyBQQVJUIElJCkluIGFkZGl0aW9uIHRvIHdvcmtpbmcgd2l0aCBuZXR3b3JrcyBmcm9tIE5ERXgsIHlvdSBjYW4gYWxzbyBsb2FkIG5ldHdvcmtzIGZyb20gbG9jYWwgb3IgaG9zdGVkIGZsYXRmaWxlcyBpbiBhIHZhcmlldHkgb2YgZm9ybWF0cyBhbmQgYW5ub3RhdGUgdGhlbSB3aXRoIGRhdGEsIGp1c3QgbGlrZSB5b3Ugd291bGQgdXNpbmcgdGhlIEN5dG9zY2FwZSBHVUkuIAoKIyMgTG9hZCBQUEkgTmV0d29yawpUYWJ1bGFyIGRhdGEgY2FuIGJlIHJlYWQgaW4gYXMgYSBkYXRhZnJhbWUgYW5kIHRoZW4gbG9hZGVkIGFzIGEgbmV0d29yayBpbiBDeXRvc2NhcGUuIAoKKipOb3RlOioqIEEgY29sdW1uIG5hbWVkICJzb3VyY2UiIGF1dG9tYXRpY2FsbHkgYmVjb21lcyB0aGUgc291cmNlIG5vZGUgY29sdW1uOyAidGFyZ2V0IiBiZWNvbWVzIHRoZSB0YXJnZXQgbm9kZSBjb2x1bW4sIGFuZCAiaW50ZXJhY3Rpb24iIGJlY29tZXMgdGhlIGVkZ2UgaW50ZXJhY3Rpb25zIGNvbHVtbi4gQWxsIG90aGVyIGNvbHVtbnMgYmVjb21lIGVkZ2UgYXR0cmlidXRlcy4KCmBgYHtyfQpQUElfREFUQV9VUkwgPSAnaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2N5dG9zY2FwZS9jeXRvc2NhcGUtYXV0b21hdGlvbi9tYXN0ZXIvZm9yLXNjcmlwdGVycy9QeXRob24vZGF0YS9hcC1tcy1kZW1vLWRhdGEuY3N2JwpwcGlfZGF0YSA8LSByZWFkLmNzdihQUElfREFUQV9VUkwsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpwcGlfZGF0YQpjb2xuYW1lcyhwcGlfZGF0YSlbMToyXSA8LSBjKCdzb3VyY2UnLCd0YXJnZXQnKQpwcGlfZGF0YQpgYGAKYGBge3J9CnBwaV9zdWlkIDwtIGNyZWF0ZU5ldHdvcmtGcm9tRGF0YUZyYW1lcyhlZGdlcz1wcGlfZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICdBUC1NUyBEZW1vIERhdGEtdHV0b3JpYWwnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sbGVjdGlvbiA9ICdBUC1NUyBEZW1vIERhdGEnKQpgYGAKCiMjIExvYWQgRXhwcmVzc2lvbiBEYXRhCk5vdywgdGhlIG5ldHdvcmsgY2FuIGJlIGFubm90YXRlZCB3aXRoIGV4cHJlc3Npb24gZGF0YSwgcHJvdmlkZWQgYSBjb2x1bW4gaW4gdGhlIGRhdHNldCBtYXRjaGVzIGEgY29sdW1uIGluIHRoZSBuZXR3b3JrJ3MgTm9kZSBUYWJsZS4KCmBgYHtyfQpFWFBSRVNTSU9OX0RBVEFfVVJMID0gJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9jeXRvc2NhcGUvY3l0b3NjYXBlLWF1dG9tYXRpb24vbWFzdGVyL2Zvci1zY3JpcHRlcnMvUHl0aG9uL2RhdGEvYW5ub3RhdGlvbi1kYXRhLmNzdicKZXhwX2RhdGEgPC0gcmVhZC5jc3YoRVhQUkVTU0lPTl9EQVRBX1VSTCwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmV4cF9kYXRhCmBgYAoKSW4gdGhpcyBleGFtcGxlLCB0aGUgY29sdW1uICJHZW5lU3ltYm9sIiBtYXRjaGVzIHRoZSBzb3VyY2Ugbm9kZXMgaW4gb3VyIG5ldHdvcmsgaW4gYSBkZWZhdWx0ICJuYW1lIiBjb2x1bW4uCgpgYGB7cn0KbG9hZFRhYmxlRGF0YShleHBfZGF0YSwgZGF0YS5rZXkuY29sdW1uID0gJ0dlbmVTeW1ib2wnKQpgYGAKCiMjIFNhdmUgTmV0d29yayB0byBOREV4CkFnYWluLCB3ZSBjYW4gc2F2ZSB0aGlzIG5ldHdvcmsgdG8gTkRFeCBhcyB3ZWxsLCBpbmNsdWRpbmcgdGhlIG5ldHdvcmsgYW5kIGFzc29jaWF0ZWQgZXhwcmVzc2lvbiBkYXRhLgoKYGBge3J9ClVTRVJOQU1FIDwtIHJlYWRsaW5lKCdFbnRlciB5b3VyIE5ERXggdXNlcm5hbWU6ICcpClBBU1NXT1JEIDwtIHJlYWRsaW5lKCdFbnRlciB5b3VyIE5ERXggcGFzc3dvcmQ6ICcpCnBwaV9kYXRhX3V1aWQgPC0gZXhwb3J0TmV0d29ya1RvTkRFeChVU0VSTkFNRSwgUEFTU1dPUkQsIGlzUHVibGljPUZBTFNFLCBuZXR3b3JrPXBwaV9zdWlkKQpgYGAKCiMgUEFSVCBJSUkKV2UgaGF2ZSBkZW1vbnN0cmF0aW5nIHdvcmtpbmcgd2l0aCBuZXR3b3JrcyBhbmQgZGF0YXNldHMgaW4gQ3l0b3NjYXBlIHZpYSBzY3JpcHRpbmcuIE9mIGNvdXJzZSwgeW91IGNhbiBhbHNvIHJlcHJlc2VudCBuZXR3b3JrcyBhdCBkYXRhIG9iamVjdHMgaW4geW91ciBzY3JpcHRpbmcgZW52aXJvbm1lbnQgZm9yIGJpb2luZm9ybWF0aWMgYW5hbHlzaXMuCgojIyBSZXRyaWV2ZSBOZXR3b3JrIGZyb20gTkRFeApJbiBvcmRlciB0byByZXRyaWV2ZSBuZXR3b3JrcyBmcm9tIE5ERXggYW5kIHJlcHJlc2VudCB0aGVtIGluIFIsIHdlIHdpbGwgbmVlZCB0byBpbnN0YWxsIHRoZSAqbmRleHIqIHBhY2thZ2UuCgpgYGB7cn0KaWYoISJuZGV4ciIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSl7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJCaW9jTWFuYWdlciIpCiAgICBCaW9jTWFuYWdlcjo6aW5zdGFsbCgibmRleHIiKQp9CmxpYnJhcnkobmRleHIpCmBgYAoKWW91IGNhbiBzdGFydCB3aXRoIE5ERXggYnkgZmlyc3QgZXN0YWJsaXNoaW5nIGEgY29ubmVjdGlvbi4KYGBge3J9Cm5kZXhjb24gPC0gbmRleF9jb25uZWN0KCkKYGBgCldlIHdpbGwgdXNlIG5kZXhjb24gdGhyb3VnaG91dCB0aGUgb3RoZXIgbmRleHIgY2FsbHMuIEZvciBleGFtcGxlLCBhIGJhc2ljIHNlYXJjaC4KYGBge3J9Cm5ldHdvcmtzIDwtIG5kZXhfZmluZF9uZXR3b3JrcyhuZGV4Y29uLCAiQnJlYXN0IENhbmNlciIpCnByaW50KG5ldHdvcmtzWyxjKCJuYW1lIiwiZXh0ZXJuYWxJZCIsIm5vZGVDb3VudCIsImVkZ2VDb3VudCIpXSkKYGBgClRoYXQgcHJpbnQgc3RhdGVtZW50IHByb3ZpZGVzIGEgbmlmdHkgd2F5IHRvIGJyb3dzZSB0aGUgc2VhcmNoIHJlc3VsdHMuIFlvdeKAmWxsIG5vdGljZSB0aGF0IHdlIGdvdCByZXN1bHRzIHRoYXQgaGl0IGVhY2ggb2YgdGhlIHNlYXJjaCB0ZXJtcyBpbmRpdmlkdWFsbHksIHRodXMgaW5jbHVkaW5nIGFueSBwYXRod2F5IHdpdGgg4oCcY2FuY2Vy4oCdIGluIHRoZSBuYW1lLiBUaGF04oCZcyBwZXJoYXBzIGEgYml0IHRvbyBicm9hZOKApgpgYGB7cn0KbmV0d29ya3MgPC0gbmRleF9maW5kX25ldHdvcmtzKG5kZXhjb24sICJCUkNBIikKcHJpbnQobmV0d29ya3NbLGMoIm5hbWUiLCJleHRlcm5hbElkIiwibm9kZUNvdW50IiwiZWRnZUNvdW50IildKQpgYGAKT2suIFdlIGNhbiB3b3JrIHdpdGggdGhpcyBsaXN0LiBMZXTigJlzIHVzZSB0aGUgZmlyc3QgaGl0LiBOb3RlOiB5b3UgYXJlIGdvaW5nIHRvIGdldCBkaWZmZXJlbnQgaGl0cyBhcyB0aGlzIGRhdGFiYXNlIGNoYW5nZXMgb3ZlciB0aW1lLCBzbyBwcm9jZWVkIHdpdGggYW55IGhpdCB5b3UgbGlrZS4KYGBge3J9Cm5ldHdvcmtJZCA9IG5ldHdvcmtzJGV4dGVybmFsSWRbMV0KbmV0d29yayA9IG5kZXhfZ2V0X25ldHdvcmsobmRleGNvbiwgbmV0d29ya0lkKQpwcmludChuZXR3b3JrKQpgYGAKVGhlICpuZXR3b3JrKiBpcyBhbiBSQ1ggb2JqZWN0LiBFeHBsb3JlIHRoZSBSQ1ggb2JqZWN0IHRvIHNlZSBpdHMgY29udGVudHMuCmBgYHtyfQpzdHIobmV0d29yaykKbmV0d29yayRtZXRhRGF0YQpuZXR3b3JrJG5vZGVzCm5ldHdvcmskZWRnZXMKYGBgCgoqKk5vdGU6KiogUkNYIGNhbiBiZSBjb3ZlcnRlZCB0byB0d28gZmxhdm9ycyBvZiBpZ3JhcGggb2JqZWN0cyB1c2luZyAqcmN4X3RvTkdyYXBoKiBhbmQgKnJjeF90b1JDWGdyYXBoKi4gU2VlIG1ldGhvZCBkb2N1bWVudGF0aW9uIGZvciBtb3JlIGRldGFpbHMuCg==