gaze_recognizers/ner/
detector.rs1use std::fmt;
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4
5use gaze_types::{Detection, Detector};
6
7use super::backend::{load_backend, NerBackend};
8use super::decode;
9use super::error::NerLoadError;
10use super::loader::warn_on_label_vocab_mismatch;
11use super::types::{LabelMap, NerBackendKind, NerOptions, NerSpanResult};
12
13pub struct NerDetector {
17 #[allow(dead_code)]
18 pub(crate) model_dir: PathBuf,
19 pub(crate) backend_kind: NerBackendKind,
20 pub(crate) locale: Option<String>,
21 pub(crate) threshold: f32,
22 pub(crate) backend: Arc<dyn NerBackend>,
23}
24
25impl fmt::Debug for NerDetector {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 f.debug_struct("NerDetector")
28 .field("model_dir", &self.model_dir)
29 .field("backend_kind", &self.backend_kind)
30 .field("locale", &self.locale)
31 .field("threshold", &self.threshold)
32 .finish_non_exhaustive()
33 }
34}
35
36impl NerDetector {
37 pub fn load(model_dir: &Path) -> Result<Self, NerLoadError> {
40 Self::load_with_options(model_dir, NerOptions::default())
41 }
42
43 pub fn load_with_options(model_dir: &Path, options: NerOptions) -> Result<Self, NerLoadError> {
44 let verified = Self::verify_artifacts(model_dir)?;
45 let backend_kind = verified.backend_kind;
46 let model_dir_path = verified.model_dir.clone();
47 let label_count = verified.labels.len();
48 let id2label_len = verified.id2label.len();
49 warn_on_label_vocab_mismatch(&verified.labels, &verified.id2label, model_dir);
50 let backend = load_backend(verified)?;
51
52 tracing::info!(
53 backend = backend_kind.as_str(),
54 labels = label_count,
55 id2label_size = id2label_len,
56 locale = options.locale.as_deref().unwrap_or(""),
57 threshold = options.threshold,
58 model_dir = %model_dir_path.display(),
59 "ner: detector registered"
60 );
61
62 Ok(Self {
63 model_dir: model_dir_path,
64 backend_kind,
65 locale: options.locale,
66 threshold: options.threshold,
67 backend,
68 })
69 }
70
71 pub fn locale(&self) -> Option<&str> {
72 self.locale.as_deref()
73 }
74
75 pub fn backend_kind(&self) -> NerBackendKind {
76 self.backend_kind
77 }
78
79 pub fn merge_bio_spans(
85 labels: &LabelMap,
86 subword_spans: &[(usize, usize)],
87 subword_labels: &[&str],
88 source: &str,
89 ) -> Vec<Detection> {
90 decode::merge_bio_spans(labels, subword_spans, subword_labels, source)
91 }
92
93 pub fn merge_bio_span_results(
94 labels: &LabelMap,
95 subword_spans: &[(usize, usize)],
96 subword_labels: &[&str],
97 subword_scores: &[f32],
98 source: &str,
99 ) -> Vec<NerSpanResult> {
100 decode::merge_bio_span_results(
101 labels,
102 subword_spans,
103 subword_labels,
104 subword_scores,
105 source,
106 )
107 }
108}
109
110impl Detector for NerDetector {
111 fn detect(&self, input: &str) -> Vec<Detection> {
112 match self.backend.detect(input) {
113 Ok(detections) => detections
114 .into_iter()
115 .map(|span| {
116 Detection::new(
117 span.span,
118 span.class,
119 format!("ner/{}", self.backend_kind.as_str()),
120 )
121 })
122 .collect(),
123 Err(err) => {
124 tracing::warn!(backend = self.backend_kind.as_str(), error = %err, "ner: backend detect failed");
125 Vec::new()
126 }
127 }
128 }
129}