Skip to content

Commit 83bf1c6

Browse files
authored
v0.1.2 (#147)
1 parent b2b3619 commit 83bf1c6

16 files changed

Lines changed: 1341 additions & 260 deletions

File tree

Cargo.toml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "usls"
33
edition = "2021"
4-
version = "0.1.1"
4+
version = "0.1.2"
55
rust-version = "1.85"
66
description = "A Rust library integrated with ONNXRuntime, providing a collection of ML models."
77
repository = "https://github.com/jamjamjon/usls"
@@ -12,6 +12,7 @@ exclude = ["assets/*", "examples/*", "runs/*", "benches/*", "tests/*"]
1212

1313

1414
[dependencies]
15+
paste = "1.0.15"
1516
anyhow = { version = "1" }
1617
aksr = { version = "0.0.3" }
1718
ab_glyph = { version = "0.2.29" }
@@ -29,8 +30,6 @@ serde_json = "1.0"
2930
rayon = { version = "1.10.0" }
3031
glob = "0.3.2"
3132
dirs = "6.0.0"
32-
natord = "1.0.9"
33-
geo = "0.31.0"
3433
chrono = "0.4.40"
3534
regex = "1.11.1"
3635
tempfile = "3.19.1"
@@ -46,8 +45,7 @@ ort = { version = "=2.0.0-rc.10", default-features = false, optional = true, fea
4645
] }
4746
hf-hub = { version = "0.4.3", default-features = false, features = ["ureq", "native-tls"] }
4847
tokenizers = { version = "0.21.1" }
49-
paste = "1.0.15"
50-
slsl = { version = "0.0.5", features = ["rayon"] }
48+
slsl = { version = "0.0.6", features = ["rayon"] }
5149

5250

5351
[dev-dependencies]

examples/annotate/main.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ fn main() -> anyhow::Result<()> {
169169
];
170170

171171
let polygons = vec![
172-
Polygon::from_xys(&[
172+
Polygon::try_from(vec![
173173
[13.0, 251.0],
174174
[12.0, 251.0],
175175
[11.0, 251.0],
@@ -348,11 +348,11 @@ fn main() -> anyhow::Result<()> {
348348
[14.0, 253.0],
349349
[13.0, 252.0],
350350
[13.0, 251.0],
351-
])
351+
])?
352352
.with_id(11)
353353
.with_name("stop sign")
354354
.with_confidence(0.5555),
355-
Polygon::from_xys(&[
355+
Polygon::try_from(vec![
356356
[485.0, 149.0],
357357
[484.0, 150.0],
358358
[484.0, 151.0],
@@ -608,7 +608,7 @@ fn main() -> anyhow::Result<()> {
608608
[487.0, 149.0],
609609
[486.0, 149.0],
610610
[485.0, 149.0],
611-
])
611+
])?
612612
.with_id(9)
613613
.with_name("traffic light")
614614
.with_confidence(0.777777),

examples/db/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ fn main() -> Result<()> {
9595
);
9696

9797
for (x, y) in xs.iter().zip(ys.iter()) {
98+
// println!("y: {:?}", y);
9899
annotator.annotate(x, y)?.save(format!(
99100
"{}.jpg",
100101
usls::Dir::Current

src/core/dataloader.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ impl DataLoader {
414414
let a = a.file_name().and_then(|s| s.to_str());
415415
let b = b.file_name().and_then(|s| s.to_str());
416416
match (a, b) {
417-
(Some(a), Some(b)) => natord::compare(a, b),
417+
(Some(a), Some(b)) => crate::natural_compare(a, b),
418418
_ => std::cmp::Ordering::Equal,
419419
}
420420
});
@@ -437,7 +437,7 @@ impl DataLoader {
437437
let a = a.file_name().and_then(|s| s.to_str());
438438
let b = b.file_name().and_then(|s| s.to_str());
439439
match (a, b) {
440-
(Some(a), Some(b)) => natord::compare(a, b),
440+
(Some(a), Some(b)) => crate::natural_compare(a, b),
441441
_ => std::cmp::Ordering::Equal,
442442
}
443443
});

src/core/ort_config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ impl Default for ORTConfig {
3232
graph_opt_level: Default::default(),
3333
num_intra_threads: None,
3434
num_inter_threads: None,
35-
hardware: HardwareConfig::default(),
35+
hardware: HardwareConfig::new(),
3636
}
3737
}
3838
}

src/core/utils.rs

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,230 @@ pub fn timestamp(delimiter: Option<&str>) -> String {
177177
let format = format!("%Y{0}%m{0}%d{0}%H{0}%M{0}%S{0}%f", delimiter);
178178
chrono::Local::now().format(&format).to_string()
179179
}
180+
181+
/// Natural sort comparison for strings (handles embedded numbers correctly)
182+
///
183+
/// Compares strings by breaking them into chunks of digits and non-digits,
184+
/// comparing digit chunks numerically and text chunks lexicographically.
185+
///
186+
/// # Examples
187+
/// ```ignore
188+
/// use usls::natural_compare;
189+
///
190+
/// assert!(natural_compare("file1.txt", "file2.txt") == std::cmp::Ordering::Less);
191+
/// assert!(natural_compare("file2.txt", "file10.txt") == std::cmp::Ordering::Less);
192+
/// assert!(natural_compare("img001.jpg", "img100.jpg") == std::cmp::Ordering::Less);
193+
/// ```
194+
pub fn natural_compare(a: &str, b: &str) -> std::cmp::Ordering {
195+
use std::cmp::Ordering;
196+
197+
let mut a_chars = a.chars().peekable();
198+
let mut b_chars = b.chars().peekable();
199+
200+
loop {
201+
match (a_chars.peek(), b_chars.peek()) {
202+
(None, None) => return Ordering::Equal,
203+
(None, Some(_)) => return Ordering::Less,
204+
(Some(_), None) => return Ordering::Greater,
205+
(Some(&ca), Some(&cb)) => {
206+
// Both are digits - compare numerically
207+
if ca.is_ascii_digit() && cb.is_ascii_digit() {
208+
// Extract full number from both strings
209+
let mut num_a = String::new();
210+
let mut num_b = String::new();
211+
212+
while let Some(&c) = a_chars.peek() {
213+
if c.is_ascii_digit() {
214+
num_a.push(c);
215+
a_chars.next();
216+
} else {
217+
break;
218+
}
219+
}
220+
221+
while let Some(&c) = b_chars.peek() {
222+
if c.is_ascii_digit() {
223+
num_b.push(c);
224+
b_chars.next();
225+
} else {
226+
break;
227+
}
228+
}
229+
230+
// Compare as numbers
231+
// Handle potential parsing errors by falling back to string comparison
232+
match (num_a.parse::<u64>(), num_b.parse::<u64>()) {
233+
(Ok(na), Ok(nb)) => match na.cmp(&nb) {
234+
Ordering::Equal => continue,
235+
other => return other,
236+
},
237+
// If numbers are too large for u64, compare string length first
238+
// then lexicographically (maintains correct ordering for large numbers)
239+
_ => match num_a.len().cmp(&num_b.len()) {
240+
Ordering::Equal => match num_a.cmp(&num_b) {
241+
Ordering::Equal => continue,
242+
other => return other,
243+
},
244+
other => return other,
245+
},
246+
}
247+
} else {
248+
// At least one is not a digit - compare lexicographically
249+
a_chars.next();
250+
b_chars.next();
251+
match ca.cmp(&cb) {
252+
Ordering::Equal => continue,
253+
other => return other,
254+
}
255+
}
256+
}
257+
}
258+
}
259+
}
260+
261+
#[cfg(test)]
262+
mod tests {
263+
use super::*;
264+
use std::cmp::Ordering;
265+
266+
#[test]
267+
fn test_natural_compare_basic() {
268+
assert_eq!(natural_compare("file1.txt", "file2.txt"), Ordering::Less);
269+
assert_eq!(natural_compare("file2.txt", "file10.txt"), Ordering::Less);
270+
assert_eq!(
271+
natural_compare("file10.txt", "file2.txt"),
272+
Ordering::Greater
273+
);
274+
assert_eq!(natural_compare("file5.txt", "file5.txt"), Ordering::Equal);
275+
}
276+
277+
#[test]
278+
fn test_natural_compare_leading_zeros() {
279+
assert_eq!(natural_compare("img001.jpg", "img002.jpg"), Ordering::Less);
280+
assert_eq!(natural_compare("img001.jpg", "img100.jpg"), Ordering::Less);
281+
assert_eq!(natural_compare("img099.jpg", "img100.jpg"), Ordering::Less);
282+
283+
// Leading zeros are preserved in text but compared numerically
284+
assert_eq!(natural_compare("img01.jpg", "img1.jpg"), Ordering::Equal); // 01 == 1
285+
}
286+
287+
#[test]
288+
fn test_natural_compare_version_numbers() {
289+
assert_eq!(natural_compare("v1.0.1", "v1.0.2"), Ordering::Less);
290+
assert_eq!(natural_compare("v1.0.9", "v1.0.10"), Ordering::Less);
291+
assert_eq!(natural_compare("v1.9.0", "v1.10.0"), Ordering::Less);
292+
assert_eq!(natural_compare("v2.0.0", "v1.99.99"), Ordering::Greater);
293+
}
294+
295+
#[test]
296+
fn test_natural_compare_mixed_content() {
297+
assert_eq!(natural_compare("test", "test123"), Ordering::Less);
298+
assert_eq!(natural_compare("test123", "test"), Ordering::Greater);
299+
assert_eq!(natural_compare("abc123def", "abc123def"), Ordering::Equal);
300+
assert_eq!(natural_compare("abc123def", "abc124def"), Ordering::Less);
301+
}
302+
303+
#[test]
304+
fn test_natural_compare_edge_cases() {
305+
// Empty strings
306+
assert_eq!(natural_compare("", ""), Ordering::Equal);
307+
assert_eq!(natural_compare("", "a"), Ordering::Less);
308+
assert_eq!(natural_compare("a", ""), Ordering::Greater);
309+
310+
// Pure numbers
311+
assert_eq!(natural_compare("1", "2"), Ordering::Less);
312+
assert_eq!(natural_compare("9", "10"), Ordering::Less);
313+
assert_eq!(natural_compare("100", "99"), Ordering::Greater);
314+
315+
// Pure text
316+
assert_eq!(natural_compare("apple", "banana"), Ordering::Less);
317+
assert_eq!(natural_compare("zebra", "apple"), Ordering::Greater);
318+
}
319+
320+
#[test]
321+
fn test_natural_compare_complex_filenames() {
322+
// Common image sequence patterns
323+
assert_eq!(
324+
natural_compare("frame_0001.png", "frame_0010.png"),
325+
Ordering::Less
326+
);
327+
assert_eq!(
328+
natural_compare("shot1_take5.mp4", "shot1_take15.mp4"),
329+
Ordering::Less
330+
);
331+
assert_eq!(
332+
natural_compare("scene2_v3.mov", "scene10_v1.mov"),
333+
Ordering::Less
334+
);
335+
336+
// Dataset naming
337+
assert_eq!(
338+
natural_compare("data_batch_1", "data_batch_10"),
339+
Ordering::Less
340+
);
341+
assert_eq!(
342+
natural_compare("train_001.jpg", "train_1000.jpg"),
343+
Ordering::Less
344+
);
345+
}
346+
347+
#[test]
348+
fn test_natural_compare_large_numbers() {
349+
// Numbers larger than u64::MAX
350+
let large_a = "file99999999999999999999.txt";
351+
let large_b = "file100000000000000000000.txt";
352+
assert_eq!(natural_compare(large_a, large_b), Ordering::Less);
353+
354+
// Same length, different values
355+
assert_eq!(
356+
natural_compare(
357+
"file12345678901234567890.txt",
358+
"file12345678901234567891.txt"
359+
),
360+
Ordering::Less
361+
);
362+
}
363+
364+
#[test]
365+
fn test_natural_compare_multiple_numbers() {
366+
// Multiple number sequences in one string
367+
assert_eq!(natural_compare("v1.2.3", "v1.2.10"), Ordering::Less);
368+
assert_eq!(
369+
natural_compare("chapter1_page5", "chapter1_page15"),
370+
Ordering::Less
371+
);
372+
assert_eq!(natural_compare("2024_01_05", "2024_01_15"), Ordering::Less);
373+
assert_eq!(natural_compare("2024_12_31", "2025_01_01"), Ordering::Less);
374+
}
375+
376+
#[test]
377+
fn test_natural_compare_case_sensitivity() {
378+
// Case-sensitive comparison (standard behavior)
379+
assert_eq!(natural_compare("File1.txt", "file1.txt"), Ordering::Less); // 'F' < 'f'
380+
assert_eq!(natural_compare("ABC", "abc"), Ordering::Less);
381+
}
382+
383+
#[test]
384+
fn test_natural_sorting() {
385+
let mut files = vec![
386+
"file10.txt",
387+
"file2.txt",
388+
"file1.txt",
389+
"file20.txt",
390+
"file3.txt",
391+
];
392+
393+
files.sort_by(|a, b| natural_compare(a, b));
394+
395+
assert_eq!(
396+
files,
397+
vec![
398+
"file1.txt",
399+
"file2.txt",
400+
"file3.txt",
401+
"file10.txt",
402+
"file20.txt"
403+
]
404+
);
405+
}
406+
}

src/models/florence2/impl.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,8 @@ impl Florence2 {
315315
image_width,
316316
image_height,
317317
);
318-
y = y.with_polygons(&[Polygon::default()
319-
.with_points(&points)
318+
y = y.with_polygons(&[Polygon::try_from(points)
319+
.unwrap_or_default()
320320
.with_id(0)]);
321321
}
322322
Task::OcrWithRegion => {
@@ -330,9 +330,9 @@ impl Florence2 {
330330
image_width,
331331
image_height,
332332
);
333-
Polygon::default()
333+
Polygon::try_from(points)
334+
.unwrap_or_default()
334335
.with_name(&elem[0])
335-
.with_points(&points)
336336
.with_id(i as _)
337337
})
338338
.collect();

0 commit comments

Comments
 (0)