ipymarkup

NER markup visualization for Jupyter Notebook, similar to DisplaCy NER.

In [1]:
from ipymarkup.demo import show_table
show_table()
LineMarkup BoxMarkup AsciiMarkup
a a aa b bb c c cc
a a aa b bb c c cc
a a a b b c c c
a---- b-- c----
a a aa b bb cc ee d d dd f ff g gg hh
a a aa b bb cc ee d d dd f ff g gg hh
a a a b b c e d d d f f g g h
a---- b-- c e d---- f-- g-- h
a ad a db a a ab b c c cc ff d
a d a b a a aad a b a a a b c c c f ddb a a a bb c c cc ff d
a d a b a a a b c c c f d
a------------   c---- f  
  d----------------------
      b--------          
a ab bb c cc dd ee ff gg h hh i ii a
a b b c c d e f g h h i i aab bb c cc dd ee ff gg h hh i ii a
a b b c c d e f g h h i i a
a--------------------------
  b-- c-- d e f g h-- i--  

Install

ipymarkup supports both Python 2.7+ / 3.4+, non Jupyter functionality should work on 2.7+ / 3.3+, PyPy.

Usage

Imagine there is a text

В мероприятии примут участие не только российские учёные, но и зарубежные исследователи, в том числе, Крис Хелмбрехт - управляющий директор и совладелец креативного агентства Kollektiv (Германия, США), Ннека Угбома - руководитель проекта Mushroom works (Великобритания), Гергей Ковач - политик и лидер субкультурной партии «Dog with two tails» (Венгрия), Георг Жено - немецкий режиссёр, один из создателей экспериментального театра «Театр.doc», Театра им. Йозефа Бойса (Германия).

and spans

[[(102, 116, 'PER'), (186, 194, 'LOC'), (196, 199, 'LOC'), (202, 214, 'PER'), (254, 268, 'LOC'), (271, 283, 'PER'), (324, 342, 'ORG'), (345, 352, 'LOC'), (355, 365, 'PER'), (445, 455, 'ORG'), (456, 468, 'PER'), (470, 478, 'LOC')]

highlighting named entities. To visually check that markup is correct, one may use simple function:

In [2]:
def show_markup(text, spans):
    previous = 0
    result = ''
    for start, stop, type in sorted(spans):
        result += text[previous:start]
        result += '[[' + text[start:stop] + ']]' + type
        previous = stop
    result += text[previous:]
    print(result)
    

text = 'В мероприятии примут участие не только российские учёные, но и зарубежные исследователи, в том числе, Крис Хелмбрехт - управляющий директор и совладелец креативного агентства Kollektiv (Германия, США), Ннека Угбома - руководитель проекта Mushroom works (Великобритания), Гергей Ковач - политик и лидер субкультурной партии «Dog with two tails» (Венгрия), Георг Жено - немецкий режиссёр, один из создателей экспериментального театра «Театр.doc», Театра им. Йозефа Бойса (Германия).'
spans = [(102, 116, 'PER'), (186, 194, 'LOC'), (196, 199, 'LOC'), (202, 214, 'PER'), (254, 268, 'LOC'), (271, 283, 'PER'), (324, 342, 'ORG'), (345, 352, 'LOC'), (355, 365, 'PER'), (445, 455, 'ORG'), (456, 468, 'PER'), (470, 478, 'LOC')]
show_markup(text, spans)
В мероприятии примут участие не только российские учёные, но и зарубежные исследователи, в том числе, [[Крис Хелмбрехт]]PER - управляющий директор и совладелец креативного агентства Kollektiv ([[Германия]]LOC, [[США]]LOC), [[Ннека Угбома]]PER - руководитель проекта Mushroom works ([[Великобритания]]LOC), [[Гергей Ковач]]PER - политик и лидер субкультурной партии «[[Dog with two tails]]ORG» ([[Венгрия]]LOC), [[Георг Жено]]PER - немецкий режиссёр, один из создателей экспериментального театра «Театр.doc», [[Театра им.]]ORG [[Йозефа Бойса]]PER ([[Германия]]LOC).

ipymarkup.show_ascii_markup does just that plus in some cases highlighted entities are easier to spot:

In [3]:
from ipymarkup import show_ascii_markup

show_ascii_markup(text, spans)
В мероприятии примут участие не только российские учёные, но и 
зарубежные исследователи, в том числе, Крис Хелмбрехт - управляющий 
                                       PER-----------               
директор и совладелец креативного агентства Kollektiv (Германия, США),
                                                       LOC-----  LOC  
 Ннека Угбома - руководитель проекта Mushroom works (Великобритания), 
 PER---------                                        LOC-----------   
Гергей Ковач - политик и лидер субкультурной партии «Dog with two 
PER---------                                         ORG----------
tails» (Венгрия), Георг Жено - немецкий режиссёр, один из создателей 
-----   LOC----   PER-------                                         
экспериментального театра «Театр.doc», Театра им. Йозефа Бойса 
                                       ORG------- PER--------- 
(Германия).
 LOC-----  

ipymarkup.show_box_markup in Jupyter outputs same visualization in color:

In [4]:
from ipymarkup import show_box_markup

show_box_markup(text, spans)
В мероприятии примут участие не только российские учёные, но и зарубежные исследователи, в том числе, Крис ХелмбрехтPER - управляющий директор и совладелец креативного агентства Kollektiv (ГерманияLOC, СШАLOC), Ннека УгбомаPER - руководитель проекта Mushroom works (ВеликобританияLOC), Гергей КовачPER - политик и лидер субкультурной партии «Dog with two tailsORG» (ВенгрияLOC), Георг ЖеноPER - немецкий режиссёр, один из создателей экспериментального театра «Театр.doc», Театра им.ORG Йозефа БойсаPER (ГерманияLOC).

To assign specific colors use ipymarkup.palette:

In [5]:
from ipymarkup.palette import palette, BLUE, RED, GREEN

show_box_markup(text, spans, palette=palette(PER=BLUE, ORG=RED, LOC=GREEN))
В мероприятии примут участие не только российские учёные, но и зарубежные исследователи, в том числе, Крис ХелмбрехтPER - управляющий директор и совладелец креативного агентства Kollektiv (ГерманияLOC, СШАLOC), Ннека УгбомаPER - руководитель проекта Mushroom works (ВеликобританияLOC), Гергей КовачPER - политик и лидер субкультурной партии «Dog with two tailsORG» (ВенгрияLOC), Георг ЖеноPER - немецкий режиссёр, один из создателей экспериментального театра «Театр.doc», Театра им.ORG Йозефа БойсаPER (ГерманияLOC).

ipymarkup.show_line_markup is used if spans overlap:

In [6]:
from ipymarkup import show_line_markup

spans = [(102, 200, 'PERSON'), (119, 139, 'PERSONPROPERTY'), (142, 200, 'PERSONPROPERTY'), (153, 200, 'ORGANIZATION'), (186, 194, 'GEO'), (196, 199, 'GEO'), (202, 252, 'PERSON'), (217, 252, 'PERSONPROPERTY'), (254, 268, 'GEO'), (296, 353, 'PERSONPROPERTY'), (302, 353, 'ORGANIZATION'), (345, 352, 'GEO'), (355, 385, 'PERSON'), (368, 385, 'PERSONPROPERTY'), (406, 443, 'ORGANIZATION'), (445, 479, 'ORGANIZATION'), (470, 478, 'GEO')]
show_line_markup(text, spans, palette=palette(BLUE))
В мероприятии примут участие не только российские учёные, но и зарубежные
исследователи, в том числе, Крис Хелмбрехт - PERSONуправляющий директорPERSONPROPERTY и совладелец PERSONPROPERTY
креативного агентства Kollektiv (ORGANIZATIONГерманияGEO, СШАGEO), Ннека Угбома - PERSONруководитель PERSONPROPERTY
проекта Mushroom works (ВеликобританияGEO), Гергей Ковач - политик и лидер PERSONPROPERTY
субкультурной партии «Dog with two tails» (ORGANIZATIONВенгрияGEO), Георг Жено - PERSONнемецкий PERSONPROPERTY
режиссёр, один из создателей экспериментального театра «Театр.doc»ORGANIZATION, Театра им. ORGANIZATION
Йозефа Бойса (ГерманияGEO).

show_ascii_markup also supports overlapping spans:

In [7]:
show_ascii_markup(text, spans)
В мероприятии примут участие не только российские учёные, но и 
зарубежные исследователи, в том числе, Крис Хелмбрехт - управляющий 
                                       PERSON-----------------------
                                                        PERSONPROPER
директор и совладелец креативного агентства Kollektiv (Германия, США),
--------------------------------------------------------------------- 
--------   PERSONPROPERTY-------------------------------------------- 
                      ORGANIZATION----------------------------------- 
                                                       GEO-----  GEO  
 Ннека Угбома - руководитель проекта Mushroom works (Великобритания), 
 PERSON--------------------------------------------  GEO-----------   
                PERSONPROPERTY---------------------                   
Гергей Ковач - политик и лидер субкультурной партии «Dog with two 
                         PERSONPROPERTY---------------------------
                               ORGANIZATION-----------------------
tails» (Венгрия), Георг Жено - немецкий режиссёр, один из создателей 
----------------  PERSON------------------------                     
----------------               PERSONPROPERTY---                     
        GEO----                                                      
экспериментального театра «Театр.doc», Театра им. Йозефа Бойса 
ORGANIZATION-------------------------  ORGANIZATION------------
(Германия).
---------- 
 GEO-----  

API

In [8]:
from ipymarkup import *

Markup

Markup is an instance of Markup class, AsciiMarkup, BoxMarkup, LineMarkup all inherit from it. There are two required arguments text and spans. Elements of spans should be instances of Span.

In [9]:
text = 'a d a b a a a b c c c f d'
spans = [
    Span(0, 13, 'a'),
    Span(2, 25, 'd'),
    Span(6, 15, 'b'),
    Span(16, 21, 'c'),
    Span(22, 23, 'f'),
]
AsciiMarkup(text, spans)
Out[9]:
a d a b a a a b c c c f d
a------------   c---- f  
  d----------------------
      b--------          
In [10]:
LineMarkup(text, spans)
Out[10]:
a ad a db a a ab b c c cc ff d
In [11]:
BoxMarkup(text, spans)
Out[11]:
a d a b a a aad a b a a a b c c c f ddb a a a bb c c cc ff d

markup

Every class has a simplified constructor-function: ascii_markup, box_markup, line_markup. Difference is that spans argument does not need to be list of Spans. It is convenient, user does not need to write code to convert existing spans to Span objects:

In [12]:
class C(object):
    def __init__(self, start, stop, type=None):
        self.start = start
        self.stop = stop
        self.type = type
        
        
text = '0123456789'
spans = [
    (1, 2),  # tuple/list (int, int)
    (3, 4, 'b'), # tuple/list (int, int, str)
    [5, 6],
    C(7, 8),
    C(9, 10, 'c')  # object with start, stop, type attributes
]
box_markup(text, spans)
Out[12]:
0123b456789c
In [13]:
line_markup(text, spans)
Out[13]:
0123b456789c
In [14]:
ascii_markup(text, spans)
Out[14]:
0123456789
 - b - - c

show_markup

Alias to display(ascii_markup(text, spans)) is show_ascii_markup. There are analogous show_box_markup, show_line_markup: Convenient to use them in loops for example:

In [15]:
for _ in range(3):
    show_ascii_markup(text, spans)
    print('===')
0123456789
 - b - - c
===
0123456789
 - b - - c
===
0123456789
 - b - - c
===

Palette

In [16]:
from ipymarkup.palette import *

BoxMarkup, LineMarkup, box_markup, line_markup, show_box_markup, show_line_markup all have optional argument palette instance of Palette. Palette holds a list of colors and cache that maps span types to colors. These is a global PALETTE with six predefined colors:

In [17]:
PALETTE
Out[17]:
Palette([Color('blue'),
         Color('green'),
         Color('red'),
         Color('orange'),
         Color('purple'),
         Color('brown')],
        {'LOC': Color('brown'),
         None: Color('orange'),
         'ORG': Color('blue'),
         'PER': Color('purple'),
         'a': Color('blue'),
         'b': Color('green'),
         'c': Color('red'),
         'd': Color('orange'),
         'e': Color('purple'),
         'f': Color('brown'),
         'g': Color('blue'),
         'h': Color('green'),
         'i': Color('red'),
         'j': Color('orange'),
         'k': Color('purple'),
         'l': Color('brown'),
         'm': Color('blue'),
         'n': Color('green'),
         'o': Color('red')})

Notice cache is already filled. By default colors are assigned to span types in order that they appear during runtime. Every example in previous cells uses and updates PALETTE.cache. Method set explicilty assigns specific colors to given types:

In [18]:
PALETTE.set('a', BLUE)
PALETTE.set('b', GREEN)
PALETTE.set('c', RED)
PALETTE.set('d', ORANGE)
PALETTE.set('e', PURPLE)
PALETTE.set('f', BROWN)
PALETTE.set(None, BLUE)
PALETTE
Out[18]:
Palette([Color('blue'),
         Color('green'),
         Color('red'),
         Color('orange'),
         Color('purple'),
         Color('brown')],
        {'LOC': Color('brown'),
         None: Color('blue'),
         'ORG': Color('blue'),
         'PER': Color('purple'),
         'a': Color('blue'),
         'b': Color('green'),
         'c': Color('red'),
         'd': Color('orange'),
         'e': Color('purple'),
         'f': Color('brown'),
         'g': Color('blue'),
         'h': Color('green'),
         'i': Color('red'),
         'j': Color('orange'),
         'k': Color('purple'),
         'l': Color('brown'),
         'm': Color('blue'),
         'n': Color('green'),
         'o': Color('red')})
In [19]:
text = 'aaaaabbbbbcccccdddddeeeeefffff'
spans = [(0, 5, 'a'), (5, 10, 'b'), (10, 15, 'c'), (15, 20, 'd'), (20, 25, 'e'), (25, 30, 'f'), (5, 15), (20, 30)]

show_box_markup(text, spans, palette=PALETTE)

# PALETTE is global, so no need to explicitly pass it as palette=...
show_line_markup(text, spans)
aaaaaabbbbbbbbbbbcccccccccccddddddeeeeeeeeeeefffffffffff
aaaaaabbbbbbccccccddddddeeeeeeffffff

To create to local instance of Palette it is convenient to use simplified function-constructor palette:

In [20]:
palette('green')
Out[20]:
Palette([Color('green')], {})
In [21]:
palette(BLUE, PER=BLUE, LOC=GREEN)
Out[21]:
Palette([Color('blue')], {'LOC': Color('green'), 'PER': Color('blue')})
In [22]:
# None that palette is empty, spans with type
# different from "LOC-A" and "LOC-R" will raise error
palette({'LOC-A': RED, 'LOC-R': 'green'})
Out[22]:
Palette([], {'LOC-A': Color('red'), 'LOC-R': Color('green')})
In [23]:
show_line_markup(text, spans, palette=palette(BLUE))
aaaaaabbbbbbccccccddddddeeeeeeffffff
In [24]:
show_line_markup(text, spans, palette=palette(BLUE, a=RED, d=GREEN))
aaaaaabbbbbbccccccddddddeeeeeeffffff
In [25]:
show_line_markup(text, spans, palette=palette(BLUE, {None: PURPLE}, a=RED, d=GREEN))
aaaaaabbbbbbccccccddddddeeeeeeffffff

Cookbook

Using ipymarkup outside of Jupyter Notebook

In [26]:
text = 'a d a b a a a b c c c f d'
spans = [
    Span(0, 13, 'a'),
    Span(2, 25, 'd'),
    Span(6, 15, 'b'),
    Span(16, 21, 'c'),
    Span(22, 23, 'f'),
]
markup = BoxMarkup(text, spans)
list(markup.as_html)
Out[26]:
['<div class="tex2jax_ignore" style="white-space: pre-wrap">',
 '',
 '<span style="padding: 2px; border-radius: 4px; border: 1px solid #bbdefb; background: #e3f2fd">',
 'a d a b a a a',
 '<span style="vertical-align: middle; margin-left: 2px; font-size: 0.7em; color: #64b5f6;">',
 'a',
 '</span>',
 '</span>',
 '',
 '<span style="padding: 2px; border-radius: 4px; border: 1px solid #ffe0b2; background: #fff3e0">',
 'd a b a a a b c c c f d',
 '<span style="vertical-align: middle; margin-left: 2px; font-size: 0.7em; color: #ffb74d;">',
 'd',
 '</span>',
 '</span>',
 '',
 '<span style="padding: 2px; border-radius: 4px; border: 1px solid #c8e6c9; background: #e8f5e9">',
 'b a a a b',
 '<span style="vertical-align: middle; margin-left: 2px; font-size: 0.7em; color: #66bb6a;">',
 'b',
 '</span>',
 '</span>',
 ' ',
 '<span style="padding: 2px; border-radius: 4px; border: 1px solid #ffcdd2; background: #ffebee">',
 'c c c',
 '<span style="vertical-align: middle; margin-left: 2px; font-size: 0.7em; color: #e57373;">',
 'c',
 '</span>',
 '</span>',
 ' ',
 '<span style="padding: 2px; border-radius: 4px; border: 1px solid #d7ccc8; background: #efebe9">',
 'f',
 '<span style="vertical-align: middle; margin-left: 2px; font-size: 0.7em; color: #a1887f;">',
 'f',
 '</span>',
 '</span>',
 ' d',
 '</div>']
In [27]:
markup = AsciiMarkup(text, spans)
list(markup.as_ascii)
Out[27]:
['a d a b a a a b c c c f d',
 'a------------   c---- f  ',
 '  d----------------------',
 '      b--------          ']

display vs print

In [28]:
print(markup)
AsciiMarkup('a d a b a a a b c c c f d', [Span(0, 13, 'a'), Span(2, 25, 'd'), Span(6, 15, 'b'), Span(16, 21, 'c'), Span(22, 23, 'f')])
In [29]:
from IPython.display import display

for _ in range(3):
    display(markup)
a d a b a a a b c c c f d
a------------   c---- f  
  d----------------------
      b--------          
a d a b a a a b c c c f d
a------------   c---- f  
  d----------------------
      b--------          
a d a b a a a b c c c f d
a------------   c---- f  
  d----------------------
      b--------