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:
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==