wuenlp_tools.visualize.scenes

  1from __future__ import annotations
  2
  3from collections import defaultdict
  4from pathlib import Path
  5from typing import Optional, Type
  6
  7from loguru import logger
  8from wuenlp.impl.UIMANLPStructs import UIMADocument, UIMASentence, UIMAToken, UIMAScene
  9import plotly.graph_objs as go
 10import plotly.io as pio
 11
 12import textwrap
 13
 14from wuenlp.impl.uima import UIMASpan
 15
 16from wuenlp_tools.pipeline import PipelineStep, PipelineProcessor
 17from wuenlp_tools.utils.scenes import SCENE_TYPES, NONSCENE_TYPES
 18
 19
 20def _sentence_within_nonscene(sentence):
 21    overlapping_scenes = sentence.overlapping(UIMAScene)
 22    for scene in overlapping_scenes:
 23        scene_type_lower = scene.scene_type.lower()
 24        assert scene_type_lower in SCENE_TYPES, f"SceneType is {scene.scene_type}"
 25        assert NONSCENE_TYPES.issubset(
 26            SCENE_TYPES), f"Nonscene types {NONSCENE_TYPES - SCENE_TYPES} not in valid scene types"
 27        if scene_type_lower in NONSCENE_TYPES:
 28            return True
 29
 30
 31def visualise_document(doc: UIMADocument, title: Optional[str] = None,
 32                       tolerance: int = 3):
 33    if title is None and doc.path is not None:
 34        title = doc.path.name
 35
 36    document_annotation = doc.document_annotation
 37
 38    def _get_scene_index(scene):
 39        try:
 40            return scene.covered(UIMASentence)[0].position_within(document_annotation)
 41        except IndexError:
 42            return None
 43
 44    gold_borders = [_get_scene_index(scene) for scene in doc.scenes]
 45    gold_borders = [border for border in gold_borders if border is not None]
 46    pred_borders = [_get_scene_index(scene) for scene in doc.system_scenes]
 47    pred_borders = [border for border in pred_borders if border is not None]
 48    sentences = doc.sentences
 49    end = len(sentences)
 50
 51    fps, fns, tps = 0, 0, 0
 52
 53    fig = go.Figure(layout_xaxis_range=[-1, end + 1], layout_yaxis_range=[-1, 2])
 54
 55    wrong_linebreak = "\n"
 56
 57    text = [
 58        f"{textwrap.fill(sentence.text, 50).replace(wrong_linebreak, '<br>')} <br> " for i, sentence in
 59        enumerate(sentences)
 60    ]
 61
 62    x_scene = [x for x in range(end) if not _sentence_within_nonscene(sentences[x])]
 63    x_nonscene = [x for x in range(end) if _sentence_within_nonscene(sentences[x])]
 64
 65    for y in (0, 1):
 66        fig.add_trace(
 67            go.Scatter(x=x_scene, y=[y] * len(x_scene), text=[t for i, t in enumerate(text) if i in x_scene],
 68                       mode="markers",
 69                       marker=dict(symbol=0, color="blue", size=5, opacity=1)))
 70        fig.add_trace(
 71            go.Scatter(x=x_nonscene, y=[y] * len(x_nonscene), text=[t for i, t in enumerate(text) if i in x_nonscene],
 72                       mode="markers",
 73                       marker=dict(symbol=0, color="yellow", size=5, opacity=1)))
 74
 75    for y, borders in enumerate((gold_borders, pred_borders)):
 76        fig.add_shape(
 77            type="line",
 78            x0=min(borders),
 79            y0=y,
 80            x1=end,
 81            y1=y,
 82            line=dict(
 83                color="black"
 84            ),
 85        ),
 86
 87    matched_borders = []
 88    for border in pred_borders:
 89        fig.add_shape(
 90            type="line",
 91            x0=border,
 92            y0=0.9,
 93            x1=border,
 94            y1=1.1,
 95            line=dict(
 96                color="black"
 97            ),
 98        )
 99        # find closest gold border
100        closest = min(gold_borders, key=lambda x: abs(x - border))
101        fig.add_shape(
102            type="line",
103            x0=border,
104            y0=1,
105            x1=closest,
106            y1=0,
107            line=dict(
108                color="red" if abs(border - closest) > tolerance else "green"
109            ),
110        )
111        if abs(border - closest) <= tolerance:
112            tps += 1
113            matched_borders.append(closest)
114            fig.add_shape(
115                type="line",
116                x0=border,
117                y0=0.9,
118                x1=border,
119                y1=1.1,
120                line=dict(
121                    color="green"
122                ),
123            )
124        else:
125            fps += 1
126            fig.add_shape(
127                type="line",
128                x0=border,
129                y0=0.9,
130                x1=border,
131                y1=1.1,
132                line=dict(
133                    color="red"
134                ),
135            )
136
137    for border in gold_borders:
138        fig.add_shape(
139            type="line",
140            x0=border,
141            y0=-0.1,
142            x1=border,
143            y1=0.1,
144            line=dict(
145                color="black"
146            ),
147        )
148
149        if border not in matched_borders:
150            fns += 1
151            fig.add_shape(
152                type="line",
153                x0=border,
154                y0=-0.1,
155                x1=border,
156                y1=0.1,
157                line=dict(
158                    color="red"
159                )
160            )
161
162    fig.update_layout(
163        title=f"{title}",
164        xaxis_title="Sentence Offset",
165        height=300,
166        width=1000,
167        showlegend=False,
168        yaxis=dict(
169            range=[-0.2, 1.2]  # Set y-axis to range from 0 to 60
170        )
171    )
172
173    labelalias = {0: "Gold", 1: "Predicted", -1: "", -0.5: "", 0.5: "", 1.5: "", 2: ""}
174
175    fig.update_yaxes(fixedrange=True, labelalias=labelalias)
176
177    fig.show()
178
179    print(f"TP: {tps}, FP: {fps}, FN: {fns}")
180    return doc
181
182
183class SceneVisualiseProcessor(PipelineProcessor):
184
185    def __call__(self, doc: UIMADocument, unit_type: Type[UIMASpan] | None, overwrite: bool = False,
186                 **kwargs) -> UIMADocument:
187        return visualise_document(doc)
188
189
190SceneVisualiser = PipelineStep(
191    name="SceneVisualiser",
192    processor=SceneVisualiseProcessor(),
193    unit_type=UIMAScene,
194)
def visualise_document( doc: wuenlp.impl.uima.UIMANLPStructs.UIMADocument, title: Optional[str] = None, tolerance: int = 3):
 32def visualise_document(doc: UIMADocument, title: Optional[str] = None,
 33                       tolerance: int = 3):
 34    if title is None and doc.path is not None:
 35        title = doc.path.name
 36
 37    document_annotation = doc.document_annotation
 38
 39    def _get_scene_index(scene):
 40        try:
 41            return scene.covered(UIMASentence)[0].position_within(document_annotation)
 42        except IndexError:
 43            return None
 44
 45    gold_borders = [_get_scene_index(scene) for scene in doc.scenes]
 46    gold_borders = [border for border in gold_borders if border is not None]
 47    pred_borders = [_get_scene_index(scene) for scene in doc.system_scenes]
 48    pred_borders = [border for border in pred_borders if border is not None]
 49    sentences = doc.sentences
 50    end = len(sentences)
 51
 52    fps, fns, tps = 0, 0, 0
 53
 54    fig = go.Figure(layout_xaxis_range=[-1, end + 1], layout_yaxis_range=[-1, 2])
 55
 56    wrong_linebreak = "\n"
 57
 58    text = [
 59        f"{textwrap.fill(sentence.text, 50).replace(wrong_linebreak, '<br>')} <br> " for i, sentence in
 60        enumerate(sentences)
 61    ]
 62
 63    x_scene = [x for x in range(end) if not _sentence_within_nonscene(sentences[x])]
 64    x_nonscene = [x for x in range(end) if _sentence_within_nonscene(sentences[x])]
 65
 66    for y in (0, 1):
 67        fig.add_trace(
 68            go.Scatter(x=x_scene, y=[y] * len(x_scene), text=[t for i, t in enumerate(text) if i in x_scene],
 69                       mode="markers",
 70                       marker=dict(symbol=0, color="blue", size=5, opacity=1)))
 71        fig.add_trace(
 72            go.Scatter(x=x_nonscene, y=[y] * len(x_nonscene), text=[t for i, t in enumerate(text) if i in x_nonscene],
 73                       mode="markers",
 74                       marker=dict(symbol=0, color="yellow", size=5, opacity=1)))
 75
 76    for y, borders in enumerate((gold_borders, pred_borders)):
 77        fig.add_shape(
 78            type="line",
 79            x0=min(borders),
 80            y0=y,
 81            x1=end,
 82            y1=y,
 83            line=dict(
 84                color="black"
 85            ),
 86        ),
 87
 88    matched_borders = []
 89    for border in pred_borders:
 90        fig.add_shape(
 91            type="line",
 92            x0=border,
 93            y0=0.9,
 94            x1=border,
 95            y1=1.1,
 96            line=dict(
 97                color="black"
 98            ),
 99        )
100        # find closest gold border
101        closest = min(gold_borders, key=lambda x: abs(x - border))
102        fig.add_shape(
103            type="line",
104            x0=border,
105            y0=1,
106            x1=closest,
107            y1=0,
108            line=dict(
109                color="red" if abs(border - closest) > tolerance else "green"
110            ),
111        )
112        if abs(border - closest) <= tolerance:
113            tps += 1
114            matched_borders.append(closest)
115            fig.add_shape(
116                type="line",
117                x0=border,
118                y0=0.9,
119                x1=border,
120                y1=1.1,
121                line=dict(
122                    color="green"
123                ),
124            )
125        else:
126            fps += 1
127            fig.add_shape(
128                type="line",
129                x0=border,
130                y0=0.9,
131                x1=border,
132                y1=1.1,
133                line=dict(
134                    color="red"
135                ),
136            )
137
138    for border in gold_borders:
139        fig.add_shape(
140            type="line",
141            x0=border,
142            y0=-0.1,
143            x1=border,
144            y1=0.1,
145            line=dict(
146                color="black"
147            ),
148        )
149
150        if border not in matched_borders:
151            fns += 1
152            fig.add_shape(
153                type="line",
154                x0=border,
155                y0=-0.1,
156                x1=border,
157                y1=0.1,
158                line=dict(
159                    color="red"
160                )
161            )
162
163    fig.update_layout(
164        title=f"{title}",
165        xaxis_title="Sentence Offset",
166        height=300,
167        width=1000,
168        showlegend=False,
169        yaxis=dict(
170            range=[-0.2, 1.2]  # Set y-axis to range from 0 to 60
171        )
172    )
173
174    labelalias = {0: "Gold", 1: "Predicted", -1: "", -0.5: "", 0.5: "", 1.5: "", 2: ""}
175
176    fig.update_yaxes(fixedrange=True, labelalias=labelalias)
177
178    fig.show()
179
180    print(f"TP: {tps}, FP: {fps}, FN: {fns}")
181    return doc
184class SceneVisualiseProcessor(PipelineProcessor):
185
186    def __call__(self, doc: UIMADocument, unit_type: Type[UIMASpan] | None, overwrite: bool = False,
187                 **kwargs) -> UIMADocument:
188        return visualise_document(doc)

Base class for protocol classes.

Protocol classes are defined as::

class Proto(Protocol):
    def meth(self) -> int:
        ...

Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing).

For example::

class C:
    def meth(self) -> int:
        return 0

def func(x: Proto) -> int:
    return x.meth()

func(C())  # Passes static type check

See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as::

class GenProto[T](Protocol):
    def meth(self) -> T:
        ...
SceneVisualiser = PipelineStep('SceneVisualiser', processor=SceneVisualiseProcessor)

Pipeline step SceneVisualiser (SceneVisualiseProcessor).

unit type UIMAScene.