Skip to content

Commit 8b62ec4

Browse files
committed
Added basic entities view
1 parent b974f0d commit 8b62ec4

10 files changed

Lines changed: 135 additions & 18 deletions

File tree

client/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ declare module 'vue' {
1818
CorpusTable: typeof import('./src/components/tables/CorpusTable.vue')['default']
1919
DeleteModal: typeof import('./src/components/modals/DeleteModal.vue')['default']
2020
DifferentTagsetsHelp: typeof import('./src/components/help/DifferentTagsetsHelp.vue')['default']
21+
DocumentSelect: typeof import('./src/components/input/DocumentSelect.vue')['default']
2122
DocumentsHelp: typeof import('./src/components/help/DocumentsHelp.vue')['default']
2223
DocumentsTable: typeof import('./src/components/tables/DocumentsTable.vue')['default']
2324
DownloadButton: typeof import('./src/components/input/DownloadButton.vue')['default']

client/src/api/evaluation.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import type { UUID } from "@/types/corpora"
88
import type {
99
ConfusionWrapper,
1010
DistributionWrapper,
11+
Entity,
1112
Metrics,
1213
TermComparison,
1314
} from "@/types/evaluation"
1415

1516
type ConfusionResponse = AxiosResponse<ConfusionWrapper>
1617
type DistributionResponse = AxiosResponse<DistributionWrapper>
1718
type MetricsResponse = AxiosResponse<Metrics>
19+
type EntitiesResponse = AxiosResponse<Entity[]>
1820

1921
const evaluationPath = (corpus: UUID, hypothesis: string): string =>
2022
`/corpora/${corpus}/jobs/${hypothesis}/evaluation`
@@ -35,6 +37,8 @@ const documentLayerComparisonPath = (
3537
job: string,
3638
document: string,
3739
): string => `/corpora/${corpus}/jobs/${job}/documents/${document}/evaluation`
40+
const entitiesPath = (corpus: UUID, job: string, document: string): string =>
41+
`/corpora/${corpus}/jobs/${job}/documents/${document}/entities`
3842

3943
/**
4044
* Fetch term frequency distribution.
@@ -161,3 +165,11 @@ export function getDocumentLayerComparison(
161165
params: { reference },
162166
})
163167
}
168+
169+
export function getEntities(
170+
corpus: UUID,
171+
job: string,
172+
document: string,
173+
): Promise<EntitiesResponse> {
174+
return axios.get(entitiesPath(corpus, job, document))
175+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<template>
2+
<label for="document-select">Document:</label>
3+
<GSelect v-model="model" :options id="document-select"/>
4+
</template>
5+
6+
<script setup lang="ts">
7+
import stores from "@/stores"
8+
import type { SelectOption } from "@/types/ui/select"
9+
10+
const documentsStore = stores.useDocuments()
11+
12+
const model = defineModel<string>()
13+
14+
const options = computed<SelectOption[]>(() => {
15+
return documentsStore.available.map(doc => ({
16+
value: doc.name,
17+
text: doc.name,
18+
}))
19+
})
20+
</script>

client/src/components/input/JobSelect.vue

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,25 @@ const selectedJob = computed<string>({
4949
5050
// Whether there are documents that have not been tagged yet.
5151
// Not relevant for source layer.
52-
const untaggedDocsExist = computed(() => {
53-
if (!selectedJob.value) return false
54-
if (!jobsStore.jobs) return false
55-
const job = jobsStore.jobs[selectedJob.value]
56-
if (!job) return false
57-
if (job.tagger.id === SOURCE_LAYER) return false
58-
return job.progress.finished < job.progress.total
52+
const untaggedDocsExist = computed<boolean>(() => {
53+
if (!job.value) return false
54+
return job.value.progress.finished < job.value.progress.total
5955
})
6056
// Whether the selected layer is sourceLayer and has missing annotations.
61-
const sourceLayerHasMissingAnnotations = computed(() => {
62-
if (!selectedJob.value) return false
63-
if (!jobsStore.jobs) return false
64-
const job = jobsStore.jobs[selectedJob.value]
65-
if (!job) return false
66-
if (job.tagger.id !== SOURCE_LAYER) return false
67-
return job.progress.finished !== documentsStore.numSourceAnnotations
57+
const sourceLayerHasMissingAnnotations = computed<boolean>(() => {
58+
if (!job.value) return false
59+
return job.value.progress.finished !== documentsStore.numSourceAnnotations
60+
})
61+
62+
const job = computed<Job | undefined>(() => {
63+
if (!selectedJob.value) return undefined
64+
if (!jobsStore.jobs) return undefined
65+
// any job could be any job, even the source layer
66+
const anyJob = jobsStore.jobs[selectedJob.value]
67+
if (!anyJob) return undefined
68+
// if the job is not source layer, return it
69+
if (anyJob.tagger.id !== SOURCE_LAYER) return anyJob
70+
// if the job is source layer, return undefined
71+
return undefined
6872
})
6973
</script>

client/src/router/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import EvaluateView from "@/views/annotate/subviews/evaluate/EvaluateView.vue"
99
import ConfusionView from "@/views/annotate/subviews/evaluate/subviews/ConfusionView.vue"
1010
import DistributionView from "@/views/annotate/subviews/evaluate/subviews/DistributionView.vue"
1111
import DocumentLayerComparisonView from "@/views/annotate/subviews/evaluate/subviews/DocumentLayerComparisonView.vue"
12+
import EntitiesView from "@/views/annotate/subviews/evaluate/subviews/EntitiesView.vue"
1213
import GlobalMetricsView from "@/views/annotate/subviews/evaluate/subviews/GlobalMetricsView.vue"
1314
import GroupedMetricsView from "@/views/annotate/subviews/evaluate/subviews/GroupedMetricsView.vue"
1415
import ExportView from "@/views/annotate/subviews/ExportView.vue"
@@ -94,6 +95,11 @@ const routes = [
9495
path: "document_layer_comparison",
9596
component: DocumentLayerComparisonView,
9697
},
98+
{
99+
meta: { title: "Evaluate" },
100+
path: "entities",
101+
component: EntitiesView,
102+
},
97103
],
98104
},
99105
{

client/src/types/evaluation.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,11 @@ export type TermComparison = {
7171
partialOverlap: boolean
7272
}
7373

74-
export interface Term {
74+
export type Term = {
75+
id: string
76+
offset: number
7577
annotations: Record<string, string>
78+
spaceAfter?: boolean
7679
}
7780

7881
export type WordForm = {
@@ -86,3 +89,8 @@ export type EvaluationEntry = {
8689
count: number
8790
samples: TermComparison[]
8891
}
92+
93+
export type Entity = {
94+
first: string
95+
second: Term[]
96+
}

client/src/views/annotate/subviews/evaluate/EvaluateView.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
{ id: 'grouped_metrics', title: 'Grouped Metrics' },
2828
{ id: 'confusion', title: 'Pos Confusion' },
2929
{ id: 'document_layer_comparison', title: 'Document View' },
30+
{ id: 'entities', title: 'Entities View' },
3031
]">
3132
</GTabs>
3233
</AnnotateTab>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<template>
2+
<GCard title="Entities View">
3+
<template #help>
4+
<p>
5+
Here you can see all the named entities in the selected document.
6+
</p>
7+
</template>
8+
<DocumentSelect v-if="jobSelection.hypothesisJobId" v-model="selectedDoc" />
9+
<p v-else>
10+
Please select a hypothesis job first.
11+
</p>
12+
<GTable compact v-if="items" :title :loading :items :columns>
13+
<template #table-empty-instruction>
14+
No entities found in this document.
15+
</template>
16+
17+
<template #cell-second="data: TableData<Term>">
18+
{{ displayEntity(data.value) }}
19+
</template>
20+
</GTable>
21+
</GCard>
22+
</template>
23+
24+
<script setup lang="ts">
25+
import stores from '@/stores'
26+
import type { Entity, Term } from '@/types/evaluation'
27+
import type { Field, TableData } from '@/types/ui/table'
28+
import * as API from '@/api/evaluation'
29+
30+
const jobSelection = stores.useJobSelection()
31+
const corporaStore = stores.useCorpora()
32+
33+
const selectedDoc = ref<string>()
34+
35+
const items = ref<Entity[]>()
36+
const title = computed<string>(() => {
37+
return `Entities in ${selectedDoc.value}`
38+
})
39+
const columns = ref<Field[]>([
40+
{ key: "first", label: "NER" },
41+
{ key: "second" },
42+
{ key: "third", label: "Count" }
43+
])
44+
45+
const loading = ref<boolean>(false)
46+
47+
watch(() => selectedDoc.value, () => {
48+
loading.value = true
49+
API.getEntities(
50+
corporaStore.activeUUID,
51+
jobSelection.hypothesisJobId,
52+
selectedDoc.value
53+
).then(res => {
54+
items.value = res.data
55+
}).finally(() => {
56+
loading.value = false
57+
})
58+
})
59+
60+
function displayEntity(terms: Term[]): string {
61+
return terms.map(term => term.annotations["token"]).join(' ')
62+
}
63+
</script>

server/src/main/kotlin/org/ivdnt/galahad/web/controller/EvaluationController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,5 +198,5 @@ class EvaluationController(
198198
@PathVariable @Parameter(description = "Corpus UUID") corpus: UUID,
199199
@PathVariable @Parameter(description = "Document name") document: String,
200200
@PathVariable @Parameter(description = "Tagger name or sourceLayer") job: String,
201-
): List<Pair<String, List<Term>>> = evaluationService.getEntities(corpus, document, job)
201+
): List<Triple<String, List<Term>, Int>> = evaluationService.getEntities(corpus, document, job)
202202
}

server/src/main/kotlin/org/ivdnt/galahad/web/service/EvaluationService.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ class EvaluationService(val corpora: CorporaService) {
278278
return cm
279279
}
280280

281-
fun getEntities(corpus: UUID, document: String, job: String): List<Pair<String, List<Term>>> {
281+
fun getEntities(corpus: UUID, document: String, job: String): List<Triple<String, List<Term>, Int>> {
282282
val layer = corpora.readAsReaderOrThrow(corpus, user).jobs.readOrThrow(job).getLayer(document)
283283
return layer.documents.flatMap {
284284
it.paragraphs.flatMap {
@@ -287,6 +287,8 @@ class EvaluationService(val corpora: CorporaService) {
287287
?.map { span -> span.value to span.indices.map { sent.terms[it] } } ?: emptyList()
288288
}
289289
}
290-
}
290+
}.groupBy { it }.mapValues { it.value.size }.map { (key, value) ->
291+
Triple(key.first, key.second, value)
292+
}.sortedByDescending { it.third }
291293
}
292294
}

0 commit comments

Comments
 (0)