def test_limit_scope3(self): """Test that two modifiers of the same type limit the scope of the first modifier.""" doc = nlp("no evidence of CHF, neg for pneumonia") rule = ConTextRule("no evidence of", "DEFINITE_NEGATED_EXISTENCE", "FORWARD") rule2 = ConTextRule("neg for", "DEFINITE_NEGATED_EXISTENCE", "FORWARD") modifier = ConTextModifier(rule, 0, 3, doc) modifier2 = ConTextModifier(rule2, 5, 7, doc) assert modifier.limit_scope(modifier2)
def test_from_dict_error(self): d = dict( literal="reason for examination", category="INDICATION", direction="FORWARD", invalid="this is an invalid key", ) with pytest.raises(ValueError): ConTextRule.from_dict(d)
def test_terminate_limit_scope_backward(self): """Test that a 'TERMINATE' modifier will limit the scope of a 'BACKWARD' modifier. """ doc = nlp("Pt has chf but pneumonia is ruled out") rule = ConTextRule("is ruled out", "NEGATED_EXISTENCE", "BACKWARD") modifier = ConTextModifier(rule, 6, 8, doc) rule2 = ConTextRule("but", "TERMINATE", "TERMINATE") modifier2 = ConTextModifier(rule2, 3, 4, doc) assert modifier.limit_scope(modifier2)
def test_terminate_stops_forward_modifier(self): context = ConTextComponent(nlp, rules=None) rule = ConTextRule("no evidence of", "NEGATED_EXISTENCE", "FORWARD") rule2 = ConTextRule("but", "TERMINATE", "TERMINATE") context.add([rule, rule2]) doc = nlp("No evidence of chf but she has pneumonia.") doc.ents = (Span(doc, 3, 4, "PROBLEM"), Span(doc, 7, 8, "PROBLEM")) context(doc) chf, pneumonia = doc.ents assert len(chf._.modifiers) > 0 assert len(pneumonia._.modifiers) == 0
def test_terminate_stops_backward_modifier(self): context = ConTextComponent(nlp, rules=None) rule = ConTextRule("is ruled out", "NEGATED_EXISTENCE", "BACKWARD") rule2 = ConTextRule("but", "CONJ", "TERMINATE") context.add([rule, rule2]) doc = nlp("Pt has chf but pneumonia is ruled out") doc.ents = (Span(doc, 2, 3, "PROBLEM"), Span(doc, 4, 5, "PROBLEM")) context(doc) chf, pneumonia = doc.ents assert len(chf._.modifiers) == 0 assert len(pneumonia._.modifiers) > 0
def test_prune_false(self): rules = [ ConTextRule("history of", "HISTORICAL", direction="FORWARD"), ConTextRule("no history of", "NEGATED_EXISTENCE", direction="FORWARD"), ] context = ConTextComponent(nlp, rules=None, prune=False) context.add(rules) doc = nlp("No history of afib.") context(doc) assert len(doc._.context_graph.modifiers) == 2
def test_terminate_limit_scope_custom2(self): """Test that a modifier will be explicitly terminated by a modifier with a category in terminated_by.""" doc = nlp("flu is negative, pneumonia is positive.") rule = ConTextRule("negative", "NEGATED_EXISTENCE", direction="BACKWARD") rule2 = ConTextRule("positive", "POSITIVE_EXISTENCE", direction="BACKWARD", terminated_by={"NEGATED_EXISTENCE"}) modifier = ConTextModifier(rule, 2, 3, doc) modifier2 = ConTextModifier(rule2, 6, 7, doc) assert modifier2.limit_scope(modifier)
def test_terminate_limit_scope_custom(self): """Test that a modifier will be explicitly terminated by a modifier with a category in terminated_by.""" doc = nlp("negative for flu, positive for pneumonia.") rule = ConTextRule("negative for", "NEGATED_EXISTENCE", direction="FORWARD", terminated_by={"POSITIVE_EXISTENCE"}) rule2 = ConTextRule("positive for", "POSITIVE_EXISTENCE", direction="FORWARD") modifier = ConTextModifier(rule, 0, 2, doc) modifier2 = ConTextModifier(rule2, 4, 6, doc) assert modifier.limit_scope(modifier2)
def load(): import medspacy nlp = medspacy.load(enable=["sentencizer", "tokenizer"]) # Add components from medspacy.target_matcher import TargetMatcher, TargetRule target_matcher = TargetMatcher(nlp) target_filepath = path.join(RESOURCES_DIR, "target_rules.json") target_rules = TargetRule.from_json(target_filepath) target_matcher.add(target_rules) nlp.add_pipe(target_matcher) from medspacy.context import ConTextComponent, ConTextRule context_filepath = path.join(RESOURCES_DIR, "context_rules.json") context = ConTextComponent(nlp, rules=None, add_attrs=CONTEXT_ATTRS) context_rules = ConTextRule.from_json(context_filepath) context.add(context_rules) nlp.add_pipe(context) from medspacy.section_detection import Sectionizer # TODO: Add radiology section rules sectionizer = Sectionizer(nlp) nlp.add_pipe(sectionizer) clf = DocumentClassifier(nlp) nlp.add_pipe(clf) return nlp
def test_metadata(self): literal = "no evidence of" category = "definite_negated_existence" rule = "forward" meta = {"comment": "This is a comment."} item = ConTextRule(literal, category, rule, metadata=meta) assert item.metadata
def test_rule_value_error(self): """Test that ConTextRule raises a ValueError if an invalid direction is passed in.""" literal = "no evidence of" category = "definite_negated_existence" rule = "asdf" with pytest.raises(ValueError): ConTextRule(literal, category, rule)
def test_context_item_rule_upper(self): """Test that a ConTextRule direction is always upper""" literal = "no evidence of" category = "definite_negated_existence" rule = "forward" item = ConTextRule(literal, category, rule) assert item.direction == "FORWARD"
def test_context_item_category_upper(self): """Test that a ConTextRule category is always upper""" literal = "no evidence of" category = "definite_negated_existence" rule = "forward" item = ConTextRule(literal, category, rule) assert item.category == "DEFINITE_NEGATED_EXISTENCE"
def test_custom_terminate_stops_forward_modifier(self): doc = nlp("negative for flu, positive for pneumonia.") context = ConTextComponent(nlp, rules=None) rule = ConTextRule("negative for", "NEGATED_EXISTENCE", direction="FORWARD", terminated_by={"POSITIVE_EXISTENCE"}) rule2 = ConTextRule("positive for", "POSITIVE_EXISTENCE", direction="FORWARD") context.add([rule, rule2]) doc.ents = (Span(doc, 2, 3, "PROBLEM"), Span(doc, 6, 7)) flu, pneumonia = doc.ents context(doc) assert len(flu._.modifiers) == 1 assert len(pneumonia._.modifiers) == 1
def test_rule_modifier_termination(self): context = ConTextComponent(nlp, rules=None, terminations=None) rule = ConTextRule("no evidence of", "NEGATED_EXISTENCE", "FORWARD", terminated_by={"POSITIVE_EXISTENCE", "UNCERTAIN"}) context.add([rule]) assert rule.terminated_by == {"POSITIVE_EXISTENCE", "UNCERTAIN"}
def test_null_modifier_termination(self): context = ConTextComponent(nlp, rules=None, terminations=None) rule = ConTextRule("no evidence of", "NEGATED_EXISTENCE", "FORWARD", terminated_by=None) context.add([rule]) assert rule.terminated_by == set()
def create_objects(self): doc = nlp( "family history of breast cancer but no diabetes. She has afib.") rule = ConTextRule("family history of", "FAMILY_HISTORY", direction="FORWARD") modifier = ConTextModifier(rule, 0, 3, doc) return doc, rule, modifier
def test_is_historical(self): doc = nlp("History of pneumonia.") context = ConTextComponent(nlp, add_attrs=True, rules=None) rules = [ConTextRule("history of", "HISTORICAL", direction="forward")] context.add(rules) doc.ents = (doc[-2:-1], ) context(doc) assert doc.ents[0]._.is_historical is True
def test_pseudo_modifier(self): rules = [ ConTextRule("negative", "NEGATED_EXISTENCE"), ConTextRule("negative attitude", "PSEUDO_NEGATED_EXISTENCE", direction="PSEUDO"), ] context = ConTextComponent(nlp, rules=None) context.add(rules) doc = nlp("She has a negative attitude about her treatment.") doc.ents = (doc[-2:-1], ) context(doc) assert len(doc.ents[0]._.modifiers) == 0 assert len(doc._.context_graph.modifiers) == 1 assert doc._.context_graph.modifiers[ 0].category == "PSEUDO_NEGATED_EXISTENCE"
def test_to_json(self): import json, os dname = os.path.join(tmpdirname.name, "test_modifiers.json") literal = "no evidence of" category = "definite_negated_existence" rule = "forward" item = ConTextRule(literal, category, rule) ConTextRule.to_json([item], dname) with open(dname) as f: data = json.load(f) assert "context_rules" in data assert len(data["context_rules"]) == 1 item = data["context_rules"][0] for key in ["literal", "category", "direction"]: assert key in item
def test_set_scope_context_window_no_sentences(self): """Test that setting the scope succeeds if sentence boundaries haven't been set but _use_context_window is True.""" doc = nlp.tokenizer( "family history of breast cancer but no diabetes. She has afib.") rule = ConTextRule("family history of", "FAMILY_HISTORY", direction="FORWARD", max_scope=2) modifier = ConTextModifier(rule, 0, 3, doc, _use_context_window=True) assert modifier.scope == doc[3:5]
def context_graph(self): doc = nlp.tokenizer("There is no evidence of pneumonia but there is chf.") doc[0].is_sent_start = True for token in doc[1:]: token.is_sent_start = False item_data1 = ConTextRule( "no evidence of", "DEFINITE_NEGATED_EXISTENCE", "forward" ) tag_object1 = ConTextModifier(item_data1, 2, 5, doc) item_data2 = ConTextRule("evidence of", "DEFINITE_EXISTENCE", "forward") tag_object2 = ConTextModifier(item_data2, 3, 5, doc) item_data3 = ConTextRule("but", "TERMINATE", "TERMINATE") tag_object3 = ConTextModifier(item_data3, 6, 7, doc) graph = ConTextGraph() graph.modifiers = [tag_object1, tag_object2, tag_object3] return doc, graph
def test_overlapping_target(self): """Test that a modifier will not modify a target if it is in the same span as the modifier. """ doc = nlp("Pt presents for r/o of pneumonia.") rule = ConTextRule("r/o", "UNCERTAIN", direction="BIDIRECTIONAL") modifier = ConTextModifier(rule, 3, 4, doc) target = Span(doc, 3, 4, "TEST") assert modifier.modifies(target) is False
def test_is_family(self): doc = nlp("Family history of breast cancer.") context = ConTextComponent(nlp, add_attrs=True, rules=None) rules = [ ConTextRule("family history of", "FAMILY", direction="forward") ] context.add(rules) doc.ents = (doc[-3:-1], ) context(doc) assert doc.ents[0]._.is_family is True
def test_on_modifies_true(self): def on_modifies(target, modifier, span_between): return True rule = ConTextRule("no evidence of", "NEGATED_EXISTENCE", on_modifies=on_modifies) doc = nlp("There is no evidence of pneumonia or chf.") doc.ents = (Span(doc, 5, 6, "CONDITION"), Span(doc, 6, 8, "CONDITION")) mod = ConTextModifier(rule, 2, 5, doc) assert mod.modifies(doc.ents[0]) is True
def test_global_allowed_types2(self): """Check that if the ConTextComponent does not have allowed_types defined and a ConTextRule does, the ConTextRule will not receive the component's value. """ context = ConTextComponent(nlp, rules=None, allowed_types=None) rule = ConTextRule("no evidence of", "NEGATED_EXISTENCE", "FORWARD", allowed_types={"PROBLEM"}) context.add([rule]) assert rule.allowed_types == {"PROBLEM"}
def test_no_limit_scope_same_category_different_allowed_types(self): """Test that a two ConTextModifiers of the same type but with different allowed types does not limits the scope of the modifier object. """ doc = nlp("no history of travel to Puerto Rico, neg for pneumonia") rule = ConTextRule( "no history of", "DEFINITE_NEGATED_EXISTENCE", "FORWARD", allowed_types={"TRAVEL"}, ) rule2 = ConTextRule( "neg for", "DEFINITE_NEGATED_EXISTENCE", "FORWARD", allowed_types={"CONDITION"}, ) modifier = ConTextModifier(rule, 0, 3, doc) modifier2 = ConTextModifier(rule2, 8, 10, doc) assert not modifier.limit_scope(modifier2)
def test_set_scope_fails_no_sentences(self): """Test that setting the scope fails if sentence boundaries haven't been set.""" doc = nlp.tokenizer( "family history of breast cancer but no diabetes. She has afib.") rule = ConTextRule("family history of", "FAMILY_HISTORY", direction="FORWARD") with pytest.raises(ValueError) as exception_info: # This should fail because doc.sents are None ConTextModifier(rule, 0, 3, doc) exception_info.match( "ConText failed because sentence boundaries have not been set")
def test_on_modifies_false(self): def on_modifies(target, modifier, span_between): return False rule = ConTextRule("no evidence of", "NEGATED_EXISTENCE", on_modifies=on_modifies) doc = nlp("There is no evidence of pneumonia or chf.") doc.ents = (doc[5:6], doc[7:8]) modifier = ConTextModifier(rule, 2, 5, doc) assert modifier.modifies(doc.ents[0]) is False
def test_no_types(self): """Test that not specifying allowed_types or excluded_types will modify all targets.""" doc = self.create_target_type_examples() rule = ConTextRule( "no history of travel to", category="DEFINITE_NEGATED_EXISTENCE", direction="FORWARD", ) modifier = ConTextModifier(rule, 0, 5, doc) modifier.set_scope() travel, condition = doc.ents # "puerto rico", "pneumonia" assert modifier.modifies(travel) is True assert modifier.modifies(condition) is True