def test_apply_changes(self): model = Model(view) leaf_a_label = 'stervormige' leaf_a_field = 'margin' report_leaf_a = LabelNode(leaf_a_field, [], (leaf_a_label, 55), (leaf_a_label, 55)) report_node_1 = Node('mass', children=[report_leaf_a]) report_node_2 = Node('positive_finding', children=[report_node_1]) root = Node('root', children=[report_node_2]) model.tree = root model.create_identifiers(root) json_tree = view.generate_tree(model) # Test label change self.assertEqual(json_tree[4]["text"], leaf_a_label) # pre change leaf_a_id = json_tree[3]["nodeId"] leaf_a_new_label = "circumscribed" model.change(leaf_a_id, "label", leaf_a_new_label) model.apply_back_changes() json_tree = view.generate_tree(model) self.assertEqual(json_tree[4]["text"], leaf_a_new_label) # post change # Test ignore warning self.assertEqual(json_tree[3]["lowConfidence"], True) # pre change self.assertEqual(json_tree[4]["lowConfidence"], False) # false because of edit label_a_id = json_tree[3]["nodeId"] leaf_a_id = json_tree[4]["nodeId"] model.change(label_a_id, "warning", False) json_tree = view.generate_tree(model) self.assertEqual(json_tree[3]["lowConfidence"], False) model.change(leaf_a_id, "warning", True) json_tree = view.generate_tree(model) self.assertEqual(json_tree[4]["lowConfidence"], True)
def test_text(self): node = Node("category") self.assertIsNone(node.text) node = Node("category", ("This is text", 60)) self.assertEqual(node.text, "This is text") node.corr_text = "Text is corrected" self.assertEqual(node.text, "Text is corrected")
def test_other(self): json = [('wat?', 'B-a/O', 1), ('dit', 'I-a/B-b', 1), ('is', 'I-a/I-b', 1), ('een', 'I-a/O', 1), ('test', 'I-a/O', 1)] actual, _, _ = make_tree(["B-a"], json) expected = Node('a', ("wat? dit is een test", 100), children=[ Node('O', ("wat?", 100)), Node('b', ("dit is", 100)), Node('O', ("een test", 100)) ]) self.assertEqual(expected, actual)
def apply_change(self, node: Node): """ Method used to apply a BackChange to a node :param node: the node which needs to be changed """ change = self.back_changes.get(self.tree_identifiers[id(node)], BackChange()) node.corr_text = change.text if isinstance(node, LabelNode): node.corr_label = change.label
def test_identifier_function(self): model = Model(view) node_1_label = 'mass' node_2_label = 'positive_finding' node_3_label = 'negative_finding' root_label = 'root' report_node_1 = Node(node_1_label, children=[]) report_node_2 = Node(node_2_label, children=[report_node_1]) report_node_3 = Node(node_1_label, children=[]) report_node_4 = Node(node_2_label, children=[report_node_3]) report_node_5 = Node(node_1_label, children=[]) report_node_6 = Node(node_3_label, children=[report_node_5]) report_node_7 = Node(node_1_label, children=[]) report_node_8 = Node(node_3_label, children=[report_node_7]) root = Node(root_label, children=[ report_node_2, report_node_4, report_node_6, report_node_8 ]) model.create_identifiers(root) # test if no duplicates identifiers = model.tree_identifiers.values() self.assertEqual(len(identifiers), len(set(identifiers)))
def test_eq(self): leaf1 = Node("field1", text_prediction=("text1", 70), hint="hint1") leaf1a = Node("field1", text_prediction=("text1", 70), hint="hint2") leaf2 = Node("field2", text_prediction=("text1", 70), hint="hint3") self.assertEqual(leaf1, leaf1a) self.assertEqual(leaf2, leaf2) self.assertNotEqual(leaf1, leaf2) leaf = LabelNode("field", ["one", "other"], ("text", 70), ("label", 80), hint="hint") other = LabelNode("field", ["one", "other"], ("text", 70), ("label", 80), hint="hint") self.assertEqual(leaf, other) other = LabelNode("other", ["one", "other"], ("text", 70), ("label", 80), hint="hint") self.assertNotEqual(leaf, other) other = LabelNode("field", ["one", "other"], ("text", 80), ("label", 80), hint="hint") self.assertEqual(leaf, other) other = LabelNode("field", ["one", "other"], ("other", 70), ("label", 80), hint="hint") self.assertNotEqual(leaf, other) other = LabelNode("field", ["two", "other"], ("text", 70), ("label", 80), hint="hint") self.assertNotEqual(leaf, other) other = LabelNode("field", ["other", "one"], ("text", 70), ("label", 80), hint="hint") self.assertNotEqual( leaf, other, "Options in different orders shouldn't be the same") other = LabelNode("field", ["one", "other"], ("text", 70), ("other", 80), hint="hint") self.assertNotEqual(leaf, other) other = LabelNode("field", ["one", "other"], ("text", 70), ("label", 70), hint="hint") self.assertEqual(leaf, other) other = LabelNode("field", ["one", "other"], ("text", 70), ("label", 80), hint="other") self.assertEqual(leaf, other)
def make_tree(base: List[str], items: list) -> Tuple[Node, List[str], float]: """ Make a tree based on a linear list of items :param base: the category of created Node :param items: items left for processing. Will be mutated! :return: the created Node, the text of the node and the confidence """ base_length = len(base) agg_text = [] sum_conf = 0 count = 0 children = [] first = True # loop while items exist and the current label descents from the base (break statement) while items: (text, label_text, conf) = items[0] labels = label_text.split("/") # break if the label has a new base if not first and not has_base(labels, base): break # make sure that only the first B-flag is ignored first = False # the base equals the alternatives, collect all the text and confidences if base_length == len(labels): agg_text.append(text) sum_conf += conf count += 1 items.pop(0) # the new label should not be ignored else: # make a new tree that has a base that goes one label deeper child, child_agg_text, child_sum_conf = make_tree(labels[:base_length + 1], items) children.append(child) agg_text += child_agg_text sum_conf += child_sum_conf category = clean(base[-1]) if base else 'report' if not agg_text: return Node(category, children=children), agg_text, sum_conf pred_text = ' '.join(agg_text) conf = int(sum_conf / len(agg_text) * 100) if category in OPTIONS: return LabelNode(category, OPTIONS[category], text_prediction=(pred_text, conf)), agg_text, sum_conf return Node(category, text_prediction=(pred_text, conf), children=children), agg_text, sum_conf
def test_make_tree(self): json = [('dit', 'B-a', 1), ('is', 'I-a', 1), ('een', 'I-a', 1), ('test', 'I-a', 1)] actual, _, _ = make_tree([], json) expected = Node('a', ("dit is een test", 100)) self.assertIsInstance(actual, Node) self.assertEqual("report", actual.category) self.assertEqual(expected, actual.children[0]) json = [('dit', 'B-a', 1), ('is', 'B-a', 1), ('een', 'B-a', 1), ('test', 'B-a', 1)] actual, _, _ = make_tree([], json) self.assertEqual(4, len(actual.children)) actual, _, _ = make_tree([], []) expected = Node("report") self.assertEqual(expected, actual) json = [('niet', 'O', 1), ('kan', 'B-a', 1), ('niet', 'O', 1), ('dit', 'I-a', 1), ('niet', 'O', 1), ('ook?', 'I-a', 1), ('niet', 'O', 1)] actual, _, _ = make_tree([], json) unexpected = Node('a', ("kan dit ook?", 100)) self.assertNotEqual(unexpected, actual.children[0]) json = [('nested', 'B-a/B-b/B-c', 1), ('attribute', 'I-a/I-b/B-d', 1), ('too', 'I-a/I-e', 1)] actual, _, _ = make_tree(["B-a"], json) expected = Node('a', ("nested attribute too", 100), children=[ Node('b', ("nested attribute", 100), children=[Node('c', ("nested", 100)), Node('d', ("attribute", 100))]), Node('e', ("too", 100)) ]) self.assertEqual(expected, actual)
def test_instance(self): text = Node("field", ("text", 70), hint="hint") label = LabelNode("field", ["one", "other"], ("text", 70), ("label", 80), hint="hint") self.assertIsInstance(text, Node) self.assertNotIsInstance(text, LabelNode) self.assertIsInstance(label, LabelNode) self.assertIsInstance(label, Node)
def __init__(self, view): self.view = view self.environments = {} # Dictionary for environments {name: endpoint} self.environment = None self.text = "" self.colours = {} self.tree = Node("report") self.tree_identifiers = {} self.back_changes = {} self.front_changes = {}
def test_set_change_function(self): model = Model(view) leaf_a_label = 'stervormige' leaf_a_label_confidence = 70 leaf_a_field = 'margin' leaf_a_field_confidence = 55 leaf_b_text = 'laterale bovenkwadrant linkermamma' leaf_b_field = 'location' leaf_b_field_confidence = 92 spec_c_field = 'shape' node_1_label = 'mass' node_2_label = 'positive_finding' root_label = 'root' change1 = 'changed_label1' change2 = 'changed_label2' report_leaf_a = LabelNode(leaf_a_field, [], (leaf_a_label, leaf_a_field_confidence), (leaf_a_label, leaf_a_label_confidence)) report_leaf_b = Node(leaf_b_field, (leaf_b_text, leaf_b_field_confidence)) spec_leaf_c = Node(spec_c_field) report_node_1 = Node( node_1_label, children=[report_leaf_a, report_leaf_b, spec_leaf_c]) report_node_2 = Node(node_2_label, children=[report_node_1]) root = Node(root_label, children=[report_node_2]) model.tree = root model.create_identifiers(root) json_tree = view.generate_tree(model) # Test leaf label change model.change(json_tree[4]['nodeId'], "label", change1) model.apply_back_changes() json_tree = view.generate_tree(model) self.assertEqual(change1, json_tree[4]['text']) model.change(json_tree[4]['nodeId'], "label", change2) model.apply_back_changes() json_tree = view.generate_tree(model) self.assertEqual(change2, json_tree[4]['text'])
def test_add_child(self): child0 = Node("child0") child1 = Node("child1") node = Node("label") node.add_child(child0) node.add_child(child1) self.assertEqual(node.children[0], child0) self.assertEqual(node.children[1], child1)
def hint(self, node: Node, expected): """ Method used recursively to add hints and expectations to nodes in the node structure :param node: The node for which the hints and expectations should be added :param expected: The expected nodes at the current level """ if expected: found = [child.category for child in node] if isinstance(expected.get(node.category), list): for category in expected[node.category]: if category not in found: if category in self.labels: leaf = LabelNode(category, self.labels[category]) else: leaf = Node(category) node.add_child(leaf) for child in node: if child.category in self.hints: child.hint = self.hints[child.category] if expected: next_level = expected.get(node.category) if isinstance(next_level, dict): self.hint(child, next_level)
def get(env_selected): """ Method used to get the right environment :param env_selected: The current selected environment, functions as endpoint :return: Returns the HTTPResponse generated by the selected environment """ data = request.get_json() if data['text'] is None: abort(404, message="Text needed for nlp processing") text = data["text"] if len(text) == 0: ret = jsonpickle.encode(Node("root")) else: ret = jsonpickle.encode(ENVS[env_selected].process(text)) return {"Response": 200, "Data": ret}
def test_hint(self): expected = { # 'a': ['b', 'e', 'f'] ReportNodes are not checked, only ReportLeaves 'a': ['e', 'f'] } labels = {'f': ['l1', 'l2', 'l3']} hints = {'e': "hint"} hinter = Hinter(expected, labels, hints) tree = Node('a', children=[ Node('b', children=[ Node('c', ("nested", 100)), Node('d', ("attribute", 100)) ]), Node('e', ("too", 100)) ]) testtree = Node( 'report', children=[ Node('positive_finding', children=[Node('mass', children=[Node('margin')])]) ]) hinter.hint(testtree, EXPECTED_LEAVES) hinter.hint(tree, expected) self.assertEqual(3, len(tree.children), "make sure the missing LabelNode is added") self.assertIsInstance(tree.children[2], LabelNode) self.assertEqual(labels['f'], tree.children[2].options) self.assertIsNone(tree.children[0].children[0].hint) self.assertIsNone(tree.children[0].children[1].hint) self.assertEqual("hint", tree.children[1].hint)
def make_node(node: Node, identifier: str, parent_id: str, model: Model) -> list: """ Method used to create a json template from a node :param node: The node that needs to be converted into a json representation :param identifier: The identifier of the node :param parent_id: The id of the parent of the node :param model: The current state of the program :return: list of json representations of the node """ change = model.front_changes.get(identifier, FrontChange()) tmp = json_node_template(node, identifier, parent_id, change, False) if node.is_leaf(): value_identifier = identifier + "_value" change = model.front_changes.get(value_identifier, FrontChange()) field = json_node_template(node, value_identifier, identifier, change, True) return [tmp, field] return [tmp]
def set_leaf_colours(node: Node, parent_label: str, colours: Dict[str, str]): """ Method used to create the text object with colour for the leaves :param node: The TextLeaf which needs to get a colour :param parent_label: The label of the parent :param colours: The colour dictionary for the current environment :return: Returns the object needed for the frontend """ if node.is_speculative(): return "" label = parent_label + node.category if node.category == "O": result_type = "other" colour = None else: result_type = "label" colour = colours.get(node.category, FALLBACK_COLOUR) return { "text": node.text, "colour": colour, "type": result_type, "label": label, }
def test_build_json_tree(self): model = Model(view) leaf_a_label = 'stervormige' leaf_a_label_confidence = 70 leaf_a_field = 'margin' leaf_a_field_confidence = 55 leaf_b_text = 'laterale bovenkwadrant linkermamma' leaf_b_field = 'location' leaf_b_field_confidence = 92 spec_c_field = 'shape' node_1_label = 'mass' node_2_label = 'positive_finding' root_label = 'root' report_leaf_a = LabelNode(leaf_a_field, [], (leaf_a_label, leaf_a_field_confidence), (leaf_a_label, leaf_a_label_confidence)) report_leaf_b = Node(leaf_b_field, (leaf_b_text, leaf_b_field_confidence)) spec_leaf_c = Node(spec_c_field) report_node_1 = Node( node_1_label, children=[report_leaf_a, report_leaf_b, spec_leaf_c]) report_node_2 = Node(node_2_label, children=[report_node_1]) root = Node(root_label, children=[report_node_2]) model.tree = root model.create_identifiers(root) json_tree = view.generate_tree(model) # test root self.assertEqual(json_tree[0]["parentNodeId"], None) self.assertEqual(json_tree[0]["template"], "<div class=\"domStyle\"><span>root</span></div>") self.assertEqual(json_tree[0]["text"], root_label) # test finding self.assertEqual(json_tree[1]["parentNodeId"], root_label) self.assertEqual(json_tree[1]["text"], node_2_label) # test category (mass) self.assertEqual(json_tree[2]["parentNodeId"], node_2_label) self.assertEqual(json_tree[2]["text"], node_1_label) # test labelLeaf field self.assertEqual(json_tree[3]["parentNodeId"], node_1_label) self.assertEqual(json_tree[3]["text"], leaf_a_field) # test labelLeaf label with low confidence self.assertEqual(json_tree[4]["parentNodeId"], leaf_a_field) self.assertEqual(json_tree[4]["lowConfidence"], True) self.assertEqual(json_tree[4]["text"], leaf_a_label) # test textLeaf field self.assertEqual(json_tree[5]["parentNodeId"], node_1_label) self.assertEqual(json_tree[5]["text"], leaf_b_field) # test textLeaf text self.assertEqual(json_tree[6]["parentNodeId"], leaf_b_field) self.assertEqual(json_tree[6]["text"], leaf_b_text) # test spec_leaf self.assertEqual(json_tree[7]["parentNodeId"], node_1_label) self.assertEqual(json_tree[7]["speculative"], True) self.assertEqual(json_tree[7]["text"], spec_c_field)
def test_init(self): child = Node("child") node = Node("category", children=[child]) self.assertEqual(node.category, "category") self.assertEqual(node.children[0], child)
def parse(text: str) -> Node: """ Method used to process hersen text :param text: Text that needs processing :return: For now a stub reportnode, as hinternlp is not implemented """ text = "ongeveer 2, 3 cm zichtbaar ependymomas" root = Node("report", (text, 96)) pos1 = Node("positive finding", (text, 96)) mass1 = Node("mass", (text, 96)) size1 = Node("size", ("ongeveer 2, 3 cm", 40), hint="The size of the mass") multifocality1 = Node("Multifocality", ("zichtbaar ependymomas", 80)) hin = ("Multiple tumors in the brain usually indicate metastatic disease (figure)." "Primary brain tumors are typically seen in a single region, but some brain tumors like lymphomas, " "multicentric glioblastomas and gliomatosis cerebri can be multifocal. Some tumors can be multifocal" " as a result of seeding metastases: this can occur in medulloblastomas (PNET-MB), ependymomas, GBMs " "and oligodendrogliomas. Meningiomas and schwannomas can be multiple, especially in neurofibromatosis" " type II") types = ["meningiomas", "ependymomas", "choroid plexus papillomas"] mass1.add_child(size1) neur = LabelNode("Neurofibromatosis II", types, ("zichtbaar ependymomas", 80), ("ependymomas", 80), hint=hin) multifocality1.add_child(neur) mass1.add_child(multifocality1) # speculative child location = Node("location") mass1.add_child(location) pos1.add_child(mass1) root.add_child(pos1) return root
def json_node_template(node: Node, identifier: str, parent_id: str, change: FrontChange, leaf: bool) -> dict: """ Method used to create a json template of any node in the tree :param node: The node for which :param identifier: The identifier of the node :param parent_id: The identifier of the parent :param change: the frontend changes for the node :param leaf: whether the node is a the end of a branch :return: """ alternatives = None low_confidence = False percentage = None if leaf: if isinstance(node, LabelNode): alternatives = node.options text = node.label if not node.is_corrected(): percentage = node.pred_label_conf hint = node.text else: text = node.text hint = None else: text = node.category percentage = node.pred_text_conf hint = node.hint if not text: text = "?" orgtemplate = "<div class=\"domStyle\"><span>{}</span></div>".format(text) template = orgtemplate if percentage: low_confidence = percentage < 75 template = orgtemplate + "<span class=\"confidence\">{}%</span>".format( percentage) if leaf: if node.is_corrected(): template = orgtemplate + "</div></span><span class=\"material-icons\">mode</span>" background_color = COLOUR_DICT[leaf][node.is_speculative()] outline_color = background_color jsonnode = { "nodeId": identifier, "parentNodeId": parent_id, "lowConfidence": low_confidence, "width": 347, "height": 147, "template": template, "alternatives": alternatives, "hint": hint, "text": text, "isCorrected": node.is_corrected(), "speculative": node.is_speculative(), "backgroundColor": background_color, "textColor": "#FFFFFF", "outlineColor": outline_color, "leaf": leaf, } apply_change(jsonnode, change) return jsonnode