├── .gitignore ├── LICENSE ├── README.md ├── bin └── push-tag.sh ├── examples ├── 01_out-of-the-box.py ├── 02_custom.py ├── 03_visualize-ner-manual.py ├── 04_visualize-ner-extra-options.py └── 05_visualize-spans.py ├── requirements.txt ├── setup.cfg ├── setup.py └── spacy_streamlit ├── __init__.py ├── util.py └── visualizer.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .prettierrc 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # celery beat schedule file 98 | celerybeat-schedule 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | .dmypy.json 125 | dmypy.json 126 | 127 | # Pyre type checker 128 | .pyre/ 129 | 130 | # Pycharm project files 131 | *.idea 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ExplosionAI GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # spacy-streamlit: spaCy building blocks for Streamlit apps 4 | 5 | This package contains utilities for visualizing [spaCy](https://spacy.io) models 6 | and building interactive spaCy-powered apps with 7 | [Streamlit](https://streamlit.io). It includes various building blocks you can 8 | use in your own Streamlit app, like visualizers for **syntactic dependencies**, 9 | **named entities**, **text classification**, **semantic similarity** via word 10 | vectors, token attributes, and more. 11 | 12 | [![Current Release Version](https://img.shields.io/github/release/explosion/spacy-streamlit.svg?style=flat-square&logo=github&include_prereleases)](https://github.com/explosion/spacy-streamlit/releases) 13 | [![pypi Version](https://img.shields.io/pypi/v/spacy-streamlit.svg?style=flat-square&logo=pypi&logoColor=white)](https://pypi.org/project/spacy-streamlit/) 14 | 15 | 16 | 17 | ## 🚀 Quickstart 18 | 19 | You can install `spacy-streamlit` from pip: 20 | 21 | ```bash 22 | pip install spacy-streamlit 23 | ``` 24 | 25 | The package includes **building blocks** that call into Streamlit and set up all 26 | the required elements for you. You can either use the individual components 27 | directly and combine them with other elements in your app, or call the 28 | `visualize` function to embed the whole visualizer. 29 | 30 | Download the English model from spaCy to get started. 31 | 32 | ```bash 33 | python -m spacy download en_core_web_sm 34 | ``` 35 | 36 | Then put the following example code in a file. 37 | 38 | ```python 39 | # streamlit_app.py 40 | import spacy_streamlit 41 | 42 | models = ["en_core_web_sm", "en_core_web_md"] 43 | default_text = "Sundar Pichai is the CEO of Google." 44 | spacy_streamlit.visualize(models, default_text) 45 | ``` 46 | 47 | You can then run your app with `streamlit run streamlit_app.py`. The app should 48 | pop up in your web browser. 😀 49 | 50 | #### 📦 Example: [`01_out-of-the-box.py`](examples/01_out-of-the-box.py) 51 | 52 | Use the embedded visualizer with custom settings out-of-the-box. 53 | 54 | ```bash 55 | streamlit run https://raw.githubusercontent.com/explosion/spacy-streamlit/master/examples/01_out-of-the-box.py 56 | ``` 57 | 58 | #### 👑 Example: [`02_custom.py`](examples/02_custom.py) 59 | 60 | Use individual components in your existing app. 61 | 62 | ```bash 63 | streamlit run https://raw.githubusercontent.com/explosion/spacy-streamlit/master/examples/02_custom.py 64 | ``` 65 | 66 | ## 🎛 API 67 | 68 | ### Visualizer components 69 | 70 | These functions can be used in your Streamlit app. They call into `streamlit` 71 | under the hood and set up the required elements. 72 | 73 | #### function `visualize` 74 | 75 | Embed the full visualizer with selected components. 76 | 77 | ```python 78 | import spacy_streamlit 79 | 80 | models = ["en_core_web_sm", "/path/to/model"] 81 | default_text = "Sundar Pichai is the CEO of Google." 82 | visualizers = ["ner", "textcat"] 83 | spacy_streamlit.visualize(models, default_text, visualizers) 84 | ``` 85 | 86 | | Argument | Type | Description | 87 | | ------------------------ | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 88 | | `models` | List[str] / Dict[str, str] | Names of loadable spaCy models (paths or package names). The models become selectable via a dropdown. Can either be a list of names or the names mapped to descriptions to display in the dropdown. | 89 | | `default_text` | str | Default text to analyze on load. Defaults to `""`. | 90 | | `default_model` | Optional[str] | Optional name of default model. If not set, the first model in the list of `models` is used. | 91 | | `visualizers` | List[str] | Names of visualizers to show. Defaults to `["parser", "ner", "textcat", "similarity", "tokens"]`. | 92 | | `ner_labels` | Optional[List[str]] | NER labels to include. If not set, all labels present in the `"ner"` pipeline component will be used. | 93 | | `ner_attrs` | List[str] | Span attributes shown in table of named entities. See [`visualizer.py`](spacy_streamlit/visualizer.py) for defaults. | 94 | | `token_attrs` | List[str] | Token attributes to show in token visualizer. See [`visualizer.py`](spacy_streamlit/visualizer.py) for defaults. | 95 | | `similarity_texts` | Tuple[str, str] | The default texts to compare in the similarity visualizer. Defaults to `("apple", "orange")`. | 96 | | `show_json_doc` | bool | Show button to toggle JSON representation of the `Doc`. Defaults to `True`. | 97 | | `show_meta` | bool | Show button to toggle `meta.json` of the current pipeline. Defaults to `True`. | 98 | | `show_config` | bool | Show button to toggle `config.cfg` of the current pipeline. Defaults to `True`. | 99 | | `show_visualizer_select` | bool | Show sidebar dropdown to select visualizers to display (based on enabled visualizers). Defaults to `False`. | 100 | | `sidebar_title` | Optional[str] | Title shown in the sidebar. Defaults to `None`. | 101 | | `sidebar_description` | Optional[str] | Description shown in the sidebar. Accepts Markdown-formatted text. | 102 | | `show_logo` | bool | Show the spaCy logo in the sidebar. Defaults to `True`. | 103 | | `color` | Optional[str] | Experimental: Primary color to use for some of the main UI elements (`None` to disable hack). Defaults to `"#09A3D5"`. | 104 | | `get_default_text` | Callable[[Language], str] | Optional callable that takes the currently loaded `nlp` object and returns the default text. Can be used to provide language-specific default texts. If the function returns `None`, the value of `default_text` is used, if available. Defaults to `None`. | 105 | 106 | #### function `visualize_parser` 107 | 108 | Visualize the dependency parse and part-of-speech tags using spaCy's 109 | [`displacy` visualizer](https://spacy.io/usage/visualizers). 110 | 111 | ```python 112 | import spacy 113 | from spacy_streamlit import visualize_parser 114 | 115 | nlp = spacy.load("en_core_web_sm") 116 | doc = nlp("This is a text") 117 | visualize_parser(doc) 118 | ``` 119 | 120 | | Argument | Type | Description | 121 | | ------------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | 122 | | `doc` | `Doc` | The spaCy `Doc` object to visualize. | 123 | | _keyword-only_ | | | 124 | | `title` | Optional[str] | Title of the visualizer block. | 125 | | `key` | Optional[str] | Key used for the streamlit component for selecting labels. | 126 | | `manual` | bool | Flag signifying whether the doc argument is a Doc object or a List of Dicts containing parse information. | 127 | | `displacy_options` | Optional[Dict] | Dictionary of options to be passed to the displacy render method for generating the HTML to be rendered. See: https://spacy.io/api/top-level#options-dep | 128 | 129 | #### function `visualize_ner` 130 | 131 | Visualize the named entities in a `Doc` using spaCy's 132 | [`displacy` visualizer](https://spacy.io/usage/visualizers). 133 | 134 | ```python 135 | import spacy 136 | from spacy_streamlit import visualize_ner 137 | 138 | nlp = spacy.load("en_core_web_sm") 139 | doc = nlp("Sundar Pichai is the CEO of Google.") 140 | visualize_ner(doc, labels=nlp.get_pipe("ner").labels) 141 | ``` 142 | 143 | | Argument | Type | Description | 144 | | ------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 145 | | `doc` | `Doc` | The spaCy `Doc` object to visualize. | 146 | | _keyword-only_ | | | 147 | | `labels` | Sequence[str] | The labels to show in the labels dropdown. | 148 | | `attrs` | List[str] | The span attributes to show in entity table. | 149 | | `show_table` | bool | Whether to show a table of entities and their attributes. Defaults to `True`. | 150 | | `title` | Optional[str] | Title of the visualizer block. | 151 | | `colors` | Dict[str,str] | Dictionary of colors for the entity spans to visualize, with keys as labels and corresponding colors as the values. This argument will be deprecated soon. In future the colors arg need to be passed in the `displacy_options` arg with the key "colors".) | 152 | | `key` | Optional[str] | Key used for the streamlit component for selecting labels. | 153 | | `manual` | bool | Flag signifying whether the doc argument is a Doc object or a List of Dicts containing entity span | 154 | | information. | 155 | | `displacy_options` | Optional[Dict] | Dictionary of options to be passed to the displacy render method for generating the HTML to be rendered. See https://spacy.io/api/top-level#displacy_options-ent. | 156 | 157 | 158 | #### function `visualize_spans` 159 | 160 | Visualize spans in a `Doc` using spaCy's 161 | [`displacy` visualizer](https://spacy.io/usage/visualizers). 162 | 163 | ```python 164 | import spacy 165 | from spacy_streamlit import visualize_spans 166 | 167 | nlp = spacy.load("en_core_web_sm") 168 | doc = nlp("Sundar Pichai is the CEO of Google.") 169 | span = doc[4:7] # CEO of Google 170 | span.label_ = "CEO" 171 | doc.spans["job_role"] = [span] 172 | visualize_spans(doc, spans_key="job_role", displacy_options={"colors": {"CEO": "#09a3d5"}}) 173 | ``` 174 | 175 | | Argument | Type | Description | 176 | | ------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 177 | | `doc` | `Doc` | The spaCy `Doc` object to visualize. | 178 | | _keyword-only_ | | | 179 | | `spans_key` | Sequence[str] | Which spans key to render spans from. Default is "sc". | 180 | | `attrs` | List[str] | The attributes on the entity Span to be labeled. Attributes are displayed only when the `show_table` argument is True. | 181 | | `show_table` | bool | Whether to show a table of spans and their attributes. Defaults to `True`. | 182 | | `title` | Optional[str] | Title of the visualizer block. | 183 | | `manual` | bool | Flag signifying whether the doc argument is a Doc object or a List of Dicts containing entity span information. | 184 | | `displacy_options` | Optional[Dict] | Dictionary of options to be passed to the displacy render method for generating the HTML to be rendered. See https://spacy.io/api/top-level#displacy_options-span. | 185 | 186 | 187 | #### function `visualize_textcat` 188 | 189 | Visualize text categories predicted by a trained text classifier. 190 | 191 | ```python 192 | import spacy 193 | from spacy_streamlit import visualize_textcat 194 | 195 | nlp = spacy.load("./my_textcat_model") 196 | doc = nlp("This is a text about a topic") 197 | visualize_textcat(doc) 198 | ``` 199 | 200 | | Argument | Type | Description | 201 | | -------------- | ------------- | ------------------------------------ | 202 | | `doc` | `Doc` | The spaCy `Doc` object to visualize. | 203 | | _keyword-only_ | | | 204 | | `title` | Optional[str] | Title of the visualizer block. | 205 | 206 | #### `visualize_similarity` 207 | 208 | Visualize semantic similarity using the model's word vectors. Will show a 209 | warning if no vectors are present in the model. 210 | 211 | ```python 212 | import spacy 213 | from spacy_streamlit import visualize_similarity 214 | 215 | nlp = spacy.load("en_core_web_lg") 216 | visualize_similarity(nlp, ("pizza", "fries")) 217 | ``` 218 | 219 | | Argument | Type | Description | 220 | | --------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | 221 | | `nlp` | `Language` | The loaded `nlp` object with vectors. | 222 | | `default_texts` | Tuple[str, str] | The default texts to compare on load. Defaults to `("apple", "orange")`. | 223 | | _keyword-only_ | | | 224 | | `threshold` | float | Threshold for what's considered "similar". If the similarity score is greater than the threshold, the result is shown as similar. Defaults to `0.5`. | 225 | | `title` | Optional[str] | Title of the visualizer block. | 226 | 227 | #### function `visualize_tokens` 228 | 229 | Visualize the tokens in a `Doc` and their attributes. 230 | 231 | ```python 232 | import spacy 233 | from spacy_streamlit import visualize_tokens 234 | 235 | nlp = spacy.load("en_core_web_sm") 236 | doc = nlp("This is a text") 237 | visualize_tokens(doc, attrs=["text", "pos_", "dep_", "ent_type_"]) 238 | ``` 239 | 240 | | Argument | Type | Description | 241 | | -------------- | ------------- | -------------------------------------------------------------------------------------------------------- | 242 | | `doc` | `Doc` | The spaCy `Doc` object to visualize. | 243 | | _keyword-only_ | | | 244 | | `attrs` | List[str] | The names of token attributes to use. See [`visualizer.py`](spacy_streamlit/visualizer.py) for defaults. | 245 | | `title` | Optional[str] | Title of the visualizer block. | 246 | 247 | ### Cached helpers 248 | 249 | These helpers attempt to cache loaded models and created `Doc` objects. 250 | 251 | #### function `process_text` 252 | 253 | Process a text with a model of a given name and create a `Doc` object. Calls 254 | into the `load_model` helper to load the model. 255 | 256 | ```python 257 | import streamlit as st 258 | from spacy_streamlit import process_text 259 | 260 | spacy_model = st.sidebar.selectbox("Model name", ["en_core_web_sm", "en_core_web_md"]) 261 | text = st.text_area("Text to analyze", "This is a text") 262 | doc = process_text(spacy_model, text) 263 | ``` 264 | 265 | | Argument | Type | Description | 266 | | ------------ | ----- | ------------------------------------------------------- | 267 | | `model_name` | str | Loadable spaCy model name. Can be path or package name. | 268 | | `text` | str | The text to process. | 269 | | **RETURNS** | `Doc` | The processed document. | 270 | 271 | #### function `load_model` 272 | 273 | Load a spaCy model from a path or installed package and return a loaded `nlp` 274 | object. 275 | 276 | ```python 277 | import streamlit as st 278 | from spacy_streamlit import load_model 279 | 280 | spacy_model = st.sidebar.selectbox("Model name", ["en_core_web_sm", "en_core_web_md"]) 281 | nlp = load_model(spacy_model) 282 | ``` 283 | 284 | | Argument | Type | Description | 285 | | ----------- | ---------- | ------------------------------------------------------- | 286 | | `name` | str | Loadable spaCy model name. Can be path or package name. | 287 | | **RETURNS** | `Language` | The loaded `nlp` object. | 288 | -------------------------------------------------------------------------------- /bin/push-tag.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Insist repository is clean 6 | git diff-index --quiet HEAD 7 | 8 | git checkout $1 9 | git pull origin $1 10 | git push origin $1 11 | 12 | version=$(grep "version = " setup.cfg) 13 | version=${version/version = } 14 | version=${version/\'/} 15 | version=${version/\'/} 16 | version=${version/\"/} 17 | version=${version/\"/} 18 | git tag "v$version" 19 | git push origin "v$version" 20 | -------------------------------------------------------------------------------- /examples/01_out-of-the-box.py: -------------------------------------------------------------------------------- 1 | """ 2 | Very basic out-of-the-box example using the full visualizer. This file can be 3 | run using the "streamlit run" command. 4 | 5 | Prerequisites: 6 | python -m spacy download en_core_web_sm 7 | python -m spacy download en_core_web_md 8 | """ 9 | import spacy_streamlit 10 | 11 | models = ["en_core_web_sm", "en_core_web_md"] 12 | default_text = "Sundar Pichai is the CEO of Google." 13 | spacy_streamlit.visualize(models, default_text) 14 | -------------------------------------------------------------------------------- /examples/02_custom.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example using the components provided by spacy-streamlit in an existing app. 3 | 4 | Prerequisites: 5 | python -m spacy download en_core_web_sm 6 | """ 7 | import spacy_streamlit 8 | import streamlit as st 9 | 10 | DEFAULT_TEXT = """Google was founded in September 1998 by Larry Page and Sergey Brin while they were Ph.D. students at Stanford University in California. Together they own about 14 percent of its shares and control 56 percent of the stockholder voting power through supervoting stock. They incorporated Google as a California privately held company on September 4, 1998, in California. Google was then reincorporated in Delaware on October 22, 2002.""" 11 | 12 | spacy_model = "en_core_web_sm" 13 | 14 | st.title("My cool app") 15 | text = st.text_area("Text to analyze", DEFAULT_TEXT, height=200) 16 | doc = spacy_streamlit.process_text(spacy_model, text) 17 | 18 | spacy_streamlit.visualize_ner( 19 | doc, 20 | labels=["PERSON", "DATE", "GPE"], 21 | show_table=False, 22 | title="Persons, dates and locations", 23 | ) 24 | st.text(f"Analyzed using spaCy model {spacy_model}") 25 | -------------------------------------------------------------------------------- /examples/03_visualize-ner-manual.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of using manual=True for visualize_ner. 3 | """ 4 | import spacy_streamlit 5 | import streamlit as st 6 | 7 | st.title("My cool app") 8 | 9 | doc = [{ 10 | "text": "But Google is starting from behind.", 11 | "ents": [{"start": 4, "end": 10, "label": "ORG"}], 12 | "title": None 13 | }] 14 | 15 | spacy_streamlit.visualize_ner( 16 | doc, 17 | labels=["ORG"], 18 | show_table=False, 19 | title="Manual visualization of organisations", 20 | manual=True 21 | ) 22 | -------------------------------------------------------------------------------- /examples/04_visualize-ner-extra-options.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of using displacy_options for visualize_ner. 3 | """ 4 | import spacy 5 | 6 | import spacy_streamlit 7 | 8 | nlp = spacy.blank("en") 9 | text = "But Google is starting from behind." 10 | doc = nlp.make_doc(text) 11 | ent = doc.char_span(4, 10, label="ORG", kb_id="Q95") 12 | doc.ents = [ent] 13 | 14 | spacy_streamlit.visualize_ner( 15 | doc, 16 | labels=["ORG"], 17 | show_table=False, 18 | title="Custom Colors NER Visualization", 19 | displacy_options={ 20 | "colors": {"ORG": "#EEE"}, 21 | "kb_url_template": "https://www.wikidata.org/wiki/{}" 22 | }, 23 | key="Custom Colors" 24 | ) 25 | 26 | spacy_streamlit.visualize_ner( 27 | doc, 28 | labels=["ORG"], 29 | show_table=False, 30 | title="Default Colors NER Visualization", 31 | displacy_options={ 32 | "kb_url_template": "https://www.wikidata.org/wiki/{}" 33 | }, 34 | key="Default Colors" 35 | ) 36 | -------------------------------------------------------------------------------- /examples/05_visualize-spans.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of using `visualize_spans` with a non-default spans_key 3 | """ 4 | import spacy_streamlit 5 | import streamlit as st 6 | 7 | import spacy 8 | from spacy_streamlit import visualize_spans 9 | 10 | nlp = spacy.load("en_core_web_sm") 11 | doc = nlp("Sundar Pichai is the CEO of Google.") 12 | span = doc[4:7] # CEO of Google 13 | span.label_ = "CEO" 14 | doc.spans["job_role"] = [span] 15 | visualize_spans( 16 | doc, spans_key="job_role", displacy_options={"colors": {"CEO": "#09a3d5"}} 17 | ) 18 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit>=1.18.0 2 | spacy>=3.0.0,<4.0.0 3 | pandas 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | version = 1.0.6 3 | description = Visualize spaCy with streamlit 4 | url = https://github.com/explosion/spacy-streamlit 5 | author = Explosion 6 | author_email = contact@explosion.ai 7 | license = MIT 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | 11 | [options] 12 | zip_safe = true 13 | python_requires = >=3.6 14 | install_requires = 15 | streamlit>=1.18.0 16 | spacy>=3.0.0,<4.0.0 17 | pandas 18 | 19 | [flake8] 20 | ignore = E203, E266, E501, E731, W503, E741 21 | max-line-length = 80 22 | select = B,C,E,F,W,T4,B9 23 | exclude = 24 | spacy_streamlit/__init__.py 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | if __name__ == "__main__": 5 | from setuptools import setup, find_packages 6 | 7 | setup(name="spacy_streamlit", packages=find_packages()) 8 | -------------------------------------------------------------------------------- /spacy_streamlit/__init__.py: -------------------------------------------------------------------------------- 1 | from .visualizer import visualize, visualize_parser, visualize_ner, visualize_spans 2 | from .visualizer import visualize_textcat, visualize_similarity, visualize_tokens 3 | from .util import load_model, process_text 4 | -------------------------------------------------------------------------------- /spacy_streamlit/util.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import spacy 3 | import base64 4 | 5 | 6 | @st.cache_resource 7 | def load_model(name: str) -> spacy.language.Language: 8 | """Load a spaCy model.""" 9 | return spacy.load(name) 10 | 11 | 12 | @st.cache_data 13 | def process_text(model_name: str, text: str) -> spacy.tokens.Doc: 14 | """Process a text and create a Doc object.""" 15 | nlp = load_model(model_name) 16 | return nlp(text) 17 | 18 | 19 | def get_svg(svg: str, style: str = "", wrap: bool = True): 20 | """Convert an SVG to a base64-encoded image.""" 21 | b64 = base64.b64encode(svg.encode("utf-8")).decode("utf-8") 22 | html = f'' 23 | return get_html(html) if wrap else html 24 | 25 | 26 | def get_html(html: str): 27 | """Convert HTML so it can be rendered.""" 28 | WRAPPER = """
{}
""" 29 | # Newlines seem to mess with the rendering 30 | html = html.replace("\n", " ") 31 | return WRAPPER.format(html) 32 | 33 | 34 | LOGO_SVG = """ """ 35 | 36 | LOGO = get_svg(LOGO_SVG, wrap=False, style="max-width: 100%; margin-bottom: 25px") 37 | -------------------------------------------------------------------------------- /spacy_streamlit/visualizer.py: -------------------------------------------------------------------------------- 1 | from typing import List, Sequence, Tuple, Optional, Dict, Union, Callable 2 | from packaging.version import Version 3 | import streamlit as st 4 | import spacy 5 | from spacy.language import Language 6 | from spacy import displacy 7 | import pandas as pd 8 | 9 | from .util import load_model, process_text, get_svg, get_html, LOGO 10 | 11 | SPACY_VERSION = Version(spacy.__version__) 12 | 13 | # fmt: off 14 | NER_ATTRS = ["text", "label_", "start", "end", "start_char", "end_char"] 15 | TOKEN_ATTRS = ["idx", "text", "lemma_", "pos_", "tag_", "dep_", "head", "morph", 16 | "ent_type_", "ent_iob_", "shape_", "is_alpha", "is_ascii", 17 | "is_digit", "is_punct", "like_num", "is_sent_start"] 18 | # Currently these attrs are the same, but they might differ in the future. 19 | SPAN_ATTRS = NER_ATTRS 20 | 21 | # fmt: on 22 | FOOTER = """♥ Built with [`spacy-streamlit`](https://github.com/explosion/spacy-streamlit)""" 23 | 24 | 25 | def visualize( 26 | models: Union[List[str], Dict[str, str]], 27 | default_text: str = "", 28 | default_model: Optional[str] = None, 29 | visualizers: List[str] = ["parser", "ner", "textcat", "similarity", "tokens"], 30 | ner_labels: Optional[List[str]] = None, 31 | ner_attrs: List[str] = NER_ATTRS, 32 | similarity_texts: Tuple[str, str] = ("apple", "orange"), 33 | token_attrs: List[str] = TOKEN_ATTRS, 34 | show_json_doc: bool = True, 35 | show_meta: bool = True, 36 | show_config: bool = True, 37 | show_visualizer_select: bool = False, 38 | show_pipeline_info: bool = True, 39 | sidebar_title: Optional[str] = None, 40 | sidebar_description: Optional[str] = None, 41 | show_logo: bool = True, 42 | color: Optional[str] = "#09A3D5", 43 | key: Optional[str] = None, 44 | get_default_text: Callable[[Language], str] = None, 45 | ) -> None: 46 | """Embed the full visualizer with selected components.""" 47 | 48 | if st.config.get_option("theme.primaryColor") != color: 49 | st.config.set_option("theme.primaryColor", color) 50 | 51 | # Necessary to apply theming 52 | st.experimental_rerun() 53 | 54 | if show_logo: 55 | st.sidebar.markdown(LOGO, unsafe_allow_html=True) 56 | if sidebar_title: 57 | st.sidebar.title(sidebar_title) 58 | if sidebar_description: 59 | st.sidebar.markdown(sidebar_description) 60 | 61 | # Allow both dict of model name / description as well as list of names 62 | model_names = models 63 | format_func = str 64 | if isinstance(models, dict): 65 | format_func = lambda name: models.get(name, name) 66 | model_names = list(models.keys()) 67 | 68 | default_model_index = ( 69 | model_names.index(default_model) 70 | if default_model is not None and default_model in model_names 71 | else 0 72 | ) 73 | spacy_model = st.sidebar.selectbox( 74 | "Model", 75 | model_names, 76 | index=default_model_index, 77 | key=f"{key}_visualize_models", 78 | format_func=format_func, 79 | ) 80 | model_load_state = st.info(f"Loading model '{spacy_model}'...") 81 | nlp = load_model(spacy_model) 82 | model_load_state.empty() 83 | 84 | if show_pipeline_info: 85 | st.sidebar.subheader("Pipeline info") 86 | desc = f"""

{spacy_model}: v{nlp.meta['version']}. {nlp.meta.get("description", "")}

""" 87 | st.sidebar.markdown(desc, unsafe_allow_html=True) 88 | 89 | if show_visualizer_select: 90 | active_visualizers = st.sidebar.multiselect( 91 | "Visualizers", 92 | options=visualizers, 93 | default=list(visualizers), 94 | key=f"{key}_viz_select", 95 | ) 96 | else: 97 | active_visualizers = visualizers 98 | 99 | default_text = ( 100 | get_default_text(nlp) if get_default_text is not None else default_text 101 | ) 102 | text = st.text_area("Text to analyze", default_text, key=f"{key}_visualize_text") 103 | doc = process_text(spacy_model, text) 104 | 105 | if "parser" in visualizers and "parser" in active_visualizers: 106 | visualize_parser(doc, key=key) 107 | if "ner" in visualizers and "ner" in active_visualizers: 108 | ner_labels = ner_labels or nlp.get_pipe("ner").labels 109 | visualize_ner(doc, labels=ner_labels, attrs=ner_attrs, key=key) 110 | if "textcat" in visualizers and "textcat" in active_visualizers: 111 | visualize_textcat(doc) 112 | if "similarity" in visualizers and "similarity" in active_visualizers: 113 | visualize_similarity(nlp, default_texts=similarity_texts, key=key) 114 | if "tokens" in visualizers and "tokens" in active_visualizers: 115 | visualize_tokens(doc, attrs=token_attrs, key=key) 116 | 117 | if show_json_doc or show_meta or show_config: 118 | st.header("Pipeline information") 119 | if show_json_doc: 120 | json_doc_exp = st.expander("JSON Doc") 121 | json_doc_exp.json(doc.to_json()) 122 | 123 | if show_meta: 124 | meta_exp = st.expander("Pipeline meta.json") 125 | meta_exp.json(nlp.meta) 126 | 127 | if show_config: 128 | config_exp = st.expander("Pipeline config.cfg") 129 | config_exp.code(nlp.config.to_str()) 130 | 131 | st.sidebar.markdown( 132 | FOOTER, 133 | unsafe_allow_html=True, 134 | ) 135 | 136 | 137 | def visualize_parser( 138 | doc: Union[spacy.tokens.Doc, List[Dict[str, str]]], 139 | *, 140 | title: Optional[str] = "Dependency Parse & Part-of-speech tags", 141 | key: Optional[str] = None, 142 | manual: bool = False, 143 | displacy_options: Optional[Dict] = None, 144 | ) -> None: 145 | """Visualizer for dependency parses. 146 | 147 | doc (Doc, List): The document to visualize. 148 | key (str): Key used for the streamlit component for selecting labels. 149 | title (str): The title displayed at the top of the parser visualization. 150 | manual (bool): Flag signifying whether the doc argument is a Doc object or a List of Dicts containing parse information. 151 | displacy_options (Dict): Dictionary of options to be passed to the displacy render method for generating the HTML to be rendered. 152 | See: https://spacy.io/api/top-level#options-dep 153 | """ 154 | if displacy_options is None: 155 | displacy_options = dict() 156 | if title: 157 | st.header(title) 158 | if manual: 159 | # In manual mode, collapse_phrases and collapse_punct are passed as options to 160 | # displacy.parse_deps(doc) and the resulting data is retokenized to be correct, 161 | # so we already have these options configured at the time we use this data. 162 | cols = st.columns(1) 163 | split_sents = False 164 | options = { 165 | "compact": cols[0].checkbox("Compact mode", key=f"{key}_parser_compact"), 166 | } 167 | else: 168 | cols = st.columns(4) 169 | split_sents = cols[0].checkbox( 170 | "Split sentences", value=True, key=f"{key}_parser_split_sents" 171 | ) 172 | options = { 173 | "collapse_punct": cols[1].checkbox( 174 | "Collapse punct", value=True, key=f"{key}_parser_collapse_punct" 175 | ), 176 | "collapse_phrases": cols[2].checkbox( 177 | "Collapse phrases", key=f"{key}_parser_collapse_phrases" 178 | ), 179 | "compact": cols[3].checkbox("Compact mode", key=f"{key}_parser_compact"), 180 | } 181 | docs = [span.as_doc() for span in doc.sents] if split_sents else [doc] 182 | # add selected options to options provided by user 183 | # `options` from `displacy_options` are overwritten by user provided 184 | # options from the checkboxes 185 | displacy_options = {**displacy_options, **options} 186 | for sent in docs: 187 | html = displacy.render( 188 | sent, options=displacy_options, style="dep", manual=manual 189 | ) 190 | # Double newlines seem to mess with the rendering 191 | html = html.replace("\n\n", "\n") 192 | if split_sents and len(docs) > 1: 193 | st.markdown(f"> {sent.text}") 194 | st.write(get_svg(html), unsafe_allow_html=True) 195 | 196 | 197 | def visualize_ner( 198 | doc: Union[spacy.tokens.Doc, List[Dict[str, str]]], 199 | *, 200 | labels: Sequence[str] = tuple(), 201 | attrs: List[str] = NER_ATTRS, 202 | show_table: bool = True, 203 | title: Optional[str] = "Named Entities", 204 | colors: Dict[str, str] = {}, 205 | key: Optional[str] = None, 206 | manual: bool = False, 207 | displacy_options: Optional[Dict] = None, 208 | ): 209 | """ 210 | Visualizer for named entities. 211 | 212 | doc (Doc, List): The document to visualize. 213 | labels (list): The entity labels to visualize. 214 | attrs (list): The attributes on the entity Span to be labeled. Attributes are displayed only when the show_table 215 | argument is True. 216 | show_table (bool): Flag signifying whether to show a table with accompanying entity attributes. 217 | title (str): The title displayed at the top of the NER visualization. 218 | colors (Dict): Dictionary of colors for the entity spans to visualize, with keys as labels and corresponding colors 219 | as the values. This argument will be deprecated soon. In future the colors arg need to be passed in the displacy_options arg 220 | with the key "colors". 221 | key (str): Key used for the streamlit component for selecting labels. 222 | manual (bool): Flag signifying whether the doc argument is a Doc object or a List of Dicts containing entity span 223 | information. 224 | displacy_options (Dict): Dictionary of options to be passed to the displacy render method for generating the HTML to be rendered. 225 | See https://spacy.io/api/top-level#displacy_options-ent. 226 | """ 227 | if not displacy_options: 228 | displacy_options = dict() 229 | if colors: 230 | displacy_options["colors"] = colors 231 | 232 | if title: 233 | st.header(title) 234 | 235 | if manual: 236 | if show_table: 237 | st.warning( 238 | "When the parameter 'manual' is set to True, the parameter 'show_table' must be set to False." 239 | ) 240 | if not isinstance(doc, list): 241 | st.warning( 242 | "When the parameter 'manual' is set to True, the parameter 'doc' must be of type 'list', not 'spacy.tokens.Doc'." 243 | ) 244 | else: 245 | labels = labels or list({ent.label_ for ent in doc.ents}) 246 | 247 | if not labels: 248 | st.warning("The parameter 'labels' should not be empty or None.") 249 | else: 250 | exp = st.expander("Select entity labels") 251 | label_select = exp.multiselect( 252 | "Entity labels", 253 | options=labels, 254 | default=list(labels), 255 | key=f"{key}_ner_label_select", 256 | ) 257 | 258 | displacy_options["ents"] = label_select 259 | html = displacy.render( 260 | doc, 261 | style="ent", 262 | options=displacy_options, 263 | manual=manual, 264 | ) 265 | style = "" 266 | st.write(f"{style}{get_html(html)}", unsafe_allow_html=True) 267 | if show_table: 268 | data = [ 269 | [str(getattr(ent, attr)) for attr in attrs] 270 | for ent in doc.ents 271 | if ent.label_ in label_select 272 | ] 273 | if data: 274 | df = pd.DataFrame(data, columns=attrs) 275 | st.dataframe(df) 276 | 277 | 278 | def visualize_spans( 279 | doc: Union[spacy.tokens.Doc, Dict[str, str]], 280 | *, 281 | spans_key: str = "sc", 282 | attrs: List[str] = SPAN_ATTRS, 283 | show_table: bool = True, 284 | title: Optional[str] = "Spans", 285 | manual: bool = False, 286 | displacy_options: Optional[Dict] = None, 287 | ): 288 | """ 289 | Visualizer for spans. 290 | 291 | doc (Doc, Dict): The document to visualize. 292 | spans_key (str): Which spans key to render spans from. Default is "sc". 293 | attrs (list): The attributes on the entity Span to be labeled. Attributes are displayed only when the show_table 294 | argument is True. 295 | show_table (bool): Flag signifying whether to show a table with accompanying span attributes. 296 | title (str): The title displayed at the top of the Spans visualization. 297 | manual (bool): Flag signifying whether the doc argument is a Doc object or a List of Dicts containing span information. 298 | displacy_options (Dict): Dictionary of options to be passed to the displacy render method for generating the HTML to be rendered. 299 | See https://spacy.io/api/top-level#displacy_options-span 300 | """ 301 | if SPACY_VERSION < Version("3.3.0"): 302 | raise ValueError( 303 | f"'visualize_spans' requires spacy>=3.3.0. You have spacy=={spacy.__version__}" 304 | ) 305 | if not displacy_options: 306 | displacy_options = dict() 307 | displacy_options["spans_key"] = spans_key 308 | 309 | if title: 310 | st.header(title) 311 | 312 | if manual: 313 | if show_table: 314 | st.warning( 315 | "When the parameter 'manual' is set to True, the parameter 'show_table' must be set to False." 316 | ) 317 | if not isinstance(doc, dict): 318 | st.warning( 319 | "When the parameter 'manual' is set to True, the parameter 'doc' must be of type 'Dict', not 'spacy.tokens.Doc'." 320 | ) 321 | html = displacy.render( 322 | doc, 323 | style="span", 324 | options=displacy_options, 325 | manual=manual, 326 | ) 327 | st.write(f"{get_html(html)}", unsafe_allow_html=True) 328 | 329 | if show_table: 330 | data = [ 331 | [str(getattr(span, attr)) for attr in attrs] 332 | for span in doc.spans[spans_key] 333 | ] 334 | if data: 335 | df = pd.DataFrame(data, columns=attrs) 336 | st.dataframe(df) 337 | 338 | 339 | def visualize_textcat( 340 | doc: spacy.tokens.Doc, *, title: Optional[str] = "Text Classification" 341 | ) -> None: 342 | """Visualizer for text categories.""" 343 | if title: 344 | st.header(title) 345 | st.markdown(f"> {doc.text}") 346 | df = pd.DataFrame(doc.cats.items(), columns=("Label", "Score")) 347 | st.dataframe(df) 348 | 349 | 350 | def visualize_similarity( 351 | nlp: spacy.language.Language, 352 | default_texts: Tuple[str, str] = ("apple", "orange"), 353 | *, 354 | threshold: float = 0.5, 355 | title: Optional[str] = "Vectors & Similarity", 356 | key: Optional[str] = None, 357 | ) -> None: 358 | """Visualizer for semantic similarity using word vectors.""" 359 | meta = nlp.meta.get("vectors", {}) 360 | if title: 361 | st.header(title) 362 | if not meta.get("width", 0): 363 | st.warning("No vectors available in the model.") 364 | else: 365 | cols = st.columns(2) 366 | text1 = cols[0].text_input( 367 | "Text or word 1", default_texts[0], key=f"{key}_similarity_text1" 368 | ) 369 | text2 = cols[1].text_input( 370 | "Text or word 2", default_texts[1], key=f"{key}_similarity_text2" 371 | ) 372 | doc1 = nlp.make_doc(text1) 373 | doc2 = nlp.make_doc(text2) 374 | similarity = doc1.similarity(doc2) 375 | similarity_text = f"**Score:** `{similarity}`" 376 | if similarity > threshold: 377 | st.success(similarity_text) 378 | else: 379 | st.error(similarity_text) 380 | 381 | exp = st.expander("Vector information") 382 | exp.code(meta) 383 | 384 | 385 | def visualize_tokens( 386 | doc: spacy.tokens.Doc, 387 | *, 388 | attrs: List[str] = TOKEN_ATTRS, 389 | title: Optional[str] = "Token attributes", 390 | key: Optional[str] = None, 391 | ) -> None: 392 | """Visualizer for token attributes.""" 393 | if title: 394 | st.header(title) 395 | exp = st.expander("Select token attributes") 396 | selected = exp.multiselect( 397 | "Token attributes", 398 | options=attrs, 399 | default=list(attrs), 400 | key=f"{key}_tokens_attr_select", 401 | ) 402 | data = [[str(getattr(token, attr)) for attr in selected] for token in doc] 403 | df = pd.DataFrame(data, columns=selected) 404 | st.dataframe(df) 405 | --------------------------------------------------------------------------------