Skip to content

Commit 2ff9a4f

Browse files
authored
Merge pull request #306 from korpling/feature/transitive-coverage
Add support for coverage edges between spans and segmentation nodes
2 parents 781dd89 + bc93aad commit 2ff9a4f

File tree

10 files changed

+552
-64
lines changed

10 files changed

+552
-64
lines changed

.github/workflows/release_capi.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ on:
22
release:
33
types: [published]
44
workflow_run:
5-
workflows:
5+
workflows:
66
- Release
7-
types:
7+
types:
88
- completed
99
pull_request:
1010
types: [labeled]
@@ -20,6 +20,7 @@ jobs:
2020
uses: pozetroninc/[email protected]
2121
with:
2222
repository: ${{ github.repository }}
23+
token: ${{ secrets.GITHUB_TOKEN }}
2324
- uses: actions/checkout@v2
2425
- uses: actions-rs/[email protected]
2526
with:
@@ -47,6 +48,7 @@ jobs:
4748
uses: pozetroninc/[email protected]
4849
with:
4950
repository: ${{ github.repository }}
51+
token: ${{ secrets.GITHUB_TOKEN }}
5052
- uses: actions/checkout@v2
5153
- uses: actions-rs/[email protected]
5254
with:
@@ -74,6 +76,7 @@ jobs:
7476
uses: pozetroninc/[email protected]
7577
with:
7678
repository: ${{ github.repository }}
79+
token: ${{ secrets.GITHUB_TOKEN }}
7780
- uses: actions/checkout@v2
7881
- uses: actions-rs/[email protected]
7982
with:

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
55

66
## [Unreleased]
77

8+
### Added
9+
10+
- Added support for coverage edges between span nodes an segmentation nodes when
11+
calculating the AQL model index.
12+
13+
### Fixed
14+
15+
- Do not use recursion to calculate the indirect coverage edges in the model
16+
index, since this could fail for deeply nested structures.
17+
818
## [3.3.3] - 2024-07-12
919

1020
### Fixed

cli/src/bin/annis.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ impl AnnisRunner {
175175
let readline = rl.readline(&prompt);
176176
match readline {
177177
Ok(line) => {
178-
rl.add_history_entry(&line.clone());
178+
rl.add_history_entry(line.clone());
179179
if !self.exec(&line) {
180180
break;
181181
}

graphannis/src/annis/db/aql/model.rs

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -111,61 +111,50 @@ pub struct AQLGlobalStatistics {
111111
fn calculate_inherited_coverage_edges(
112112
graph: &mut AnnotationGraph,
113113
n: NodeID,
114-
all_cov_components: &[AnnotationComponent],
115-
all_dom_gs: &[Arc<dyn GraphStorage>],
114+
other_cov_gs: &[Arc<dyn GraphStorage>],
115+
all_text_coverage_components: &[AnnotationComponent],
116+
inherited_cov_component: &AnnotationComponent,
116117
) -> std::result::Result<FxHashSet<NodeID>, ComponentTypeError> {
117-
let mut directly_covered_token = FxHashSet::default();
118-
119-
for c in all_cov_components.iter() {
120-
if let Some(gs) = graph.get_graphstorage_as_ref(c) {
121-
let out: Result<Vec<u64>, graphannis_core::errors::GraphAnnisCoreError> =
122-
gs.get_outgoing_edges(n).collect();
123-
directly_covered_token.extend(out?);
124-
}
125-
}
126-
127-
if directly_covered_token.is_empty() {
128-
let has_token_anno = graph
129-
.get_node_annos()
130-
.get_value_for_item(&n, &TOKEN_KEY)?
131-
.is_some();
132-
if has_token_anno {
133-
// Even if technically a token does not cover itself, if we need to abort the recursion
134-
// with the basic case
135-
directly_covered_token.insert(n);
118+
// Iterate over all all nodes that are somehow covered (by coverage or
119+
// dominance edges) starting from the given node.
120+
let all_text_cov_components_gs: Vec<_> = all_text_coverage_components
121+
.iter()
122+
.filter_map(|c| graph.get_graphstorage_as_ref(c))
123+
.map(|gs| gs.as_edgecontainer())
124+
.collect();
125+
126+
let all_text_cov_components_combined = UnionEdgeContainer::new(all_text_cov_components_gs);
127+
128+
let mut covered_token = FxHashSet::default();
129+
{
130+
let tok_helper = TokenHelper::new(graph)?;
131+
for step in CycleSafeDFS::new(&all_text_cov_components_combined, n, 1, usize::MAX) {
132+
let step = step?;
133+
if tok_helper.is_token(step.node)? {
134+
covered_token.insert(step.node);
135+
}
136136
}
137-
}
137+
};
138138

139-
let mut indirectly_covered_token = FxHashSet::default();
140-
// recursivly get the covered token from all children connected by a dominance relation
141-
for dom_gs in all_dom_gs {
142-
for out in dom_gs.get_outgoing_edges(n) {
143-
let out = out?;
144-
indirectly_covered_token.extend(calculate_inherited_coverage_edges(
145-
graph,
146-
out,
147-
all_cov_components,
148-
all_dom_gs,
149-
)?);
139+
// Connect all non-token nodes to the covered token nodes if no such direct coverage already exists
140+
let mut direct_coverage_targets = FxHashSet::default();
141+
for gs in other_cov_gs.iter() {
142+
for target in gs.get_outgoing_edges(n) {
143+
direct_coverage_targets.insert(target?);
150144
}
151145
}
146+
let inherited_gs_cov = graph.get_or_create_writable(inherited_cov_component)?;
152147

153-
if let Ok(gs_cov) = graph.get_or_create_writable(&AnnotationComponent::new(
154-
AnnotationComponentType::Coverage,
155-
ANNIS_NS.into(),
156-
"inherited-coverage".into(),
157-
)) {
158-
// Ignore all already directly covered token when creating the inherited coverage edges
159-
for t in indirectly_covered_token.difference(&directly_covered_token) {
160-
gs_cov.add_edge(Edge {
148+
for target in &covered_token {
149+
if n != *target && !direct_coverage_targets.contains(target) {
150+
inherited_gs_cov.add_edge(Edge {
161151
source: n,
162-
target: *t,
152+
target: *target,
163153
})?;
164154
}
165155
}
166156

167-
directly_covered_token.extend(indirectly_covered_token);
168-
Ok(directly_covered_token)
157+
Ok(covered_token)
169158
}
170159

171160
pub struct AQLUpdateGraphIndex {
@@ -274,19 +263,37 @@ impl AQLUpdateGraphIndex {
274263
) -> std::result::Result<(), ComponentTypeError> {
275264
self.clear_left_right_token(graph)?;
276265

277-
let all_cov_components =
278-
graph.get_all_components(Some(AnnotationComponentType::Coverage), None);
279-
let all_dom_gs: Vec<Arc<dyn GraphStorage>> = graph
280-
.get_all_components(Some(AnnotationComponentType::Dominance), Some(""))
266+
let inherited_cov_component = AnnotationComponent::new(
267+
AnnotationComponentType::Coverage,
268+
ANNIS_NS.into(),
269+
"inherited-coverage".into(),
270+
);
271+
let all_cov_components: Vec<_> = graph
272+
.get_all_components(Some(AnnotationComponentType::Coverage), None)
281273
.into_iter()
282-
.filter_map(|c| graph.get_graphstorage(&c))
274+
.filter(|c| c != &inherited_cov_component)
283275
.collect();
284276

277+
let all_cov_gs: Vec<_> = all_cov_components
278+
.iter()
279+
.filter_map(|c| graph.get_graphstorage(c))
280+
.collect();
281+
282+
let all_dom_components =
283+
graph.get_all_components(Some(AnnotationComponentType::Dominance), None);
284+
let all_text_coverage_components: Vec<AnnotationComponent> =
285+
[all_cov_components, all_dom_components].concat();
286+
285287
// go over each node and calculate the left-most and right-most token
286288
for invalid in self.invalid_nodes.iter()? {
287289
let (n, _) = invalid?;
288-
let covered_token =
289-
calculate_inherited_coverage_edges(graph, n, &all_cov_components, &all_dom_gs)?;
290+
let covered_token = calculate_inherited_coverage_edges(
291+
graph,
292+
n,
293+
&all_cov_gs,
294+
&all_text_coverage_components,
295+
&inherited_cov_component,
296+
)?;
290297
self.calculate_token_alignment(
291298
graph,
292299
n,

0 commit comments

Comments
 (0)