pumaguard 20.post157__py3-none-any.whl → 20.post159__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pumaguard/presets.py CHANGED
@@ -34,6 +34,19 @@ def get_xdg_config_home() -> Path:
34
34
  return Path.home() / ".config"
35
35
 
36
36
 
37
+ def get_xdg_data_home() -> Path:
38
+ """
39
+ Get the XDG data home directory according to XDG Base Directory spec.
40
+
41
+ Returns:
42
+ Path to XDG_DATA_HOME (defaults to ~/.local/share if not set)
43
+ """
44
+ xdg_data = os.environ.get("XDG_DATA_HOME")
45
+ if xdg_data:
46
+ return Path(xdg_data)
47
+ return Path.home() / ".local" / "share"
48
+
49
+
37
50
  def get_default_settings_file() -> str:
38
51
  """
39
52
  Get the default settings file path using XDG standards.
@@ -128,6 +141,31 @@ class Preset:
128
141
  else:
129
142
  self.tf_compat = "2.17"
130
143
 
144
+ # Classification product directories (XDG data location by default)
145
+ data_root = get_xdg_data_home() / "pumaguard"
146
+ self.classification_root_dir = str(data_root / "classified")
147
+ self.classified_puma_dir = str(
148
+ Path(self.classification_root_dir) / "puma"
149
+ )
150
+ self.classified_other_dir = str(
151
+ Path(self.classification_root_dir) / "other"
152
+ )
153
+ self.intermediate_dir = str(
154
+ Path(self.classification_root_dir) / "intermediate"
155
+ )
156
+
157
+ # Ensure directories exist
158
+ for d in [
159
+ self.classification_root_dir,
160
+ self.classified_puma_dir,
161
+ self.classified_other_dir,
162
+ self.intermediate_dir,
163
+ ]:
164
+ try:
165
+ Path(d).mkdir(parents=True, exist_ok=True)
166
+ except OSError as exc: # pragma: no cover (rare failure)
167
+ logger.error("Could not create directory %s: %s", d, exc)
168
+
131
169
  def load(self, filename: str):
132
170
  """
133
171
  Load settings from YAML file.
@@ -38,6 +38,6 @@ _flutter.buildConfig = {"engineRevision":"13e658725ddaa270601426d1485636157e38c3
38
38
 
39
39
  _flutter.loader.load({
40
40
  serviceWorkerSettings: {
41
- serviceWorkerVersion: "3370573590"
41
+ serviceWorkerVersion: "2968416529"
42
42
  }
43
43
  });
@@ -34,7 +34,7 @@ const RESOURCES = {"flutter.js": "24bc71911b75b5f8135c949e27a2984e",
34
34
  "canvaskit/skwasm.js.symbols": "3a4aadf4e8141f284bd524976b1d6bdc",
35
35
  "favicon.png": "5dcef449791fa27946b3d35ad8803796",
36
36
  "main.dart.wasm": "b7e85784e7cc42b937c6ddffca885d19",
37
- "flutter_bootstrap.js": "bf49cbb2b275f239c83088ec2fb67975",
37
+ "flutter_bootstrap.js": "80cc003c2d58f860e024eb2075e631d5",
38
38
  "version.json": "d3ae24eea88b92d3d617cfcd1d6d057d",
39
39
  "main.dart.js": "8d76816e4e65d2d9504d5fd4013ba7cf"};
40
40
  // The application shell files that are downloaded before a service worker can
pumaguard/server.py CHANGED
@@ -6,11 +6,15 @@ that the new images show pumas.
6
6
  import argparse
7
7
  import logging
8
8
  import os
9
+ import shutil
9
10
  import signal
10
11
  import subprocess
11
12
  import sys
12
13
  import threading
13
14
  import time
15
+ from pathlib import (
16
+ Path,
17
+ )
14
18
 
15
19
  from PIL import (
16
20
  Image,
@@ -257,7 +261,9 @@ class FolderObserver:
257
261
  return
258
262
  logger.debug("Classifying: %s", filepath)
259
263
  prediction = classify_image_two_stage(
260
- presets=self.presets, image_path=filepath
264
+ presets=self.presets,
265
+ image_path=filepath,
266
+ intermediate_dir=self.presets.intermediate_dir,
261
267
  )
262
268
  logger.info("Chance of puma in %s: %.2f%%", filepath, prediction * 100)
263
269
  if prediction > 0.5:
@@ -267,6 +273,25 @@ class FolderObserver:
267
273
  self.presets.sound_path, self.presets.deterrent_sound_file
268
274
  )
269
275
  playsound(sound_file_path)
276
+ # Move original file into classification folder
277
+ try:
278
+ dest_root = (
279
+ self.presets.classified_puma_dir
280
+ if prediction > 0.5
281
+ else self.presets.classified_other_dir
282
+ )
283
+ Path(dest_root).mkdir(parents=True, exist_ok=True)
284
+ dest_path = Path(dest_root) / Path(filepath).name
285
+ shutil.move(filepath, dest_path)
286
+ logger.info(
287
+ "Moved %s to classification folder %s", filepath, dest_path
288
+ )
289
+ except Exception as exc: # pylint: disable=broad-except
290
+ logger.error(
291
+ "Failed to move %s into classification folder: %s",
292
+ filepath,
293
+ exc,
294
+ )
270
295
  lock.release()
271
296
  logger.debug("Exiting (%s)", me.name)
272
297
 
pumaguard/utils.py CHANGED
@@ -410,7 +410,10 @@ def cache_model_two_stage(
410
410
 
411
411
 
412
412
  def classify_image_two_stage(
413
- presets: Preset, image_path: str, print_progress: bool = True
413
+ presets: Preset,
414
+ image_path: str,
415
+ print_progress: bool = True,
416
+ intermediate_dir: str | None = None,
414
417
  ) -> float:
415
418
  """
416
419
  Classify the image using two-stage approach: YOLO detection + EfficientNet
@@ -420,9 +423,16 @@ def classify_image_two_stage(
420
423
  presets (Preset): An instance of the Preset class containing settings.
421
424
  image_path (str): The file path to the image to be classified.
422
425
 
426
+ Args:
427
+ presets (Preset): Settings preset.
428
+ image_path (str): Path to image file.
429
+ print_progress (bool): Whether to print model download progress.
430
+ intermediate_dir (str | None): If provided, store visualization and
431
+ CSV summaries inside this directory instead of CWD.
432
+
423
433
  Returns:
424
- float: Maximum puma probability from all detections (0.0 if no
425
- detections)
434
+ float: Maximum puma probability from all detections
435
+ (0.0 if no detections)
426
436
  """
427
437
 
428
438
  def expand_box(xyxy, crop_expand, width, height):
@@ -577,7 +587,10 @@ def classify_image_two_stage(
577
587
  axc.set_title(f"det {idx} — {det_probs[idx]:.3f} → {lbl}")
578
588
  idx += 1
579
589
 
590
+ # Determine output paths
580
591
  out_png = f"{image_file.stem}_viz.png"
592
+ if intermediate_dir:
593
+ out_png = str(Path(intermediate_dir) / out_png)
581
594
  plt.tight_layout()
582
595
  plt.savefig(out_png, dpi=160)
583
596
  plt.close(fig)
@@ -616,8 +629,17 @@ def classify_image_two_stage(
616
629
  )
617
630
 
618
631
  # Write CSV outputs
632
+ # Per-image CSV names (avoid overwriting)
619
633
  det_csv = "test_detections_predictions.csv"
620
634
  img_csv = "test_image_summary.csv"
635
+ if intermediate_dir:
636
+ det_csv = str(
637
+ Path(intermediate_dir)
638
+ / f"{image_file.stem}_detections_predictions.csv"
639
+ )
640
+ img_csv = str(
641
+ Path(intermediate_dir) / f"{image_file.stem}_image_summary.csv"
642
+ )
621
643
 
622
644
  # Write detection predictions CSV
623
645
  if all_rows:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pumaguard
3
- Version: 20.post157
3
+ Version: 20.post159
4
4
  Author-email: Nicolas Bock <nicolasbock@gmail.com>
5
5
  Project-URL: Homepage, http://pumaguard.rtfd.io/
6
6
  Project-URL: Repository, https://github.com/PEEC-Nature-Youth-Group/pumaguard
@@ -5,11 +5,11 @@ pumaguard/main.py,sha256=_dxt9P_SNIoFJA6HqU9KGeC9519c7CN4CA8_D-gPyEQ,6284
5
5
  pumaguard/model-registry.yaml,sha256=V-pTaqJrk_jJo568okBDaVhs51qTHttSqKe6PqdX6Bc,10318
6
6
  pumaguard/model_cli.py,sha256=nzDv0lXSvRKpLxs579tiHInJPPV-AFO4jzeLk5t2GaA,1394
7
7
  pumaguard/model_downloader.py,sha256=dymLuHAeIETALNouBG2KlLxUcxZNJ1lOQv5v9-V7_lk,12078
8
- pumaguard/presets.py,sha256=x14iLg-TQ8oL13PYaF9A9fl-Qw-LleD4D23X7nIPX8U,24483
9
- pumaguard/server.py,sha256=e8R5g0VzcnjpH3gU1nPUZUXcDaQVbRuVY3lipljahzI,12293
8
+ pumaguard/presets.py,sha256=T8-GsqWrOCnwwT6Fu2WWCf46pyhudQcgps4XqtGtEgE,25795
9
+ pumaguard/server.py,sha256=UcD7KYviWvetZy-wU8KcH6mTLR2Kj-s8bxDm2byCj1I,13168
10
10
  pumaguard/sound.py,sha256=dBylHyuS8ro9tFJH5y3s6Bn2kSGnfrOyGeGYZlgYTsU,369
11
11
  pumaguard/stats.py,sha256=ZwocfnFCQ-ky7me-YTTrEoJqsIHOWAgSzeoJHItsIU4,927
12
- pumaguard/utils.py,sha256=gC4UsHORhm13l9cQeE2bsU0D0x3pcLBJ0SLbeJ4KahI,18956
12
+ pumaguard/utils.py,sha256=w1EgOLSZGyjq_b49hvVZhBESy-lVP0yRtNHe-sXBoIU,19735
13
13
  pumaguard/verify.py,sha256=vfw3PRzDt1uuH5FKV9F5vb1PH7KQ6AEgVNhJ6jck_hQ,5513
14
14
  pumaguard/web_ui.py,sha256=E_cEV3cA1srlwMXc3eFQW6UiNE1BKd0oJlKIoduARSI,34761
15
15
  pumaguard/completions/pumaguard-classify-completions.sh,sha256=5QySg-2Jdinj15qpUYa5UzHbTgYzi2gmPVYYyyXny4c,1353
@@ -19,8 +19,8 @@ pumaguard/completions/pumaguard-train-completions.sh,sha256=lI8LG-QrncvhUqCeKtfr
19
19
  pumaguard/pumaguard-ui/.last_build_id,sha256=2z9bkdHZlfzXSpHBHs18Lo4GUD8oTSkZDZxqAeZW6KE,32
20
20
  pumaguard/pumaguard-ui/favicon.png,sha256=erJSX0uGtl0-THA1ihfloar29Df5nLzARtrXPVm7kBU,917
21
21
  pumaguard/pumaguard-ui/flutter.js,sha256=7V1ZIKmGiouT15CpquQWWmKWJyjUq77FoU9gDXPFO9M,9412
22
- pumaguard/pumaguard-ui/flutter_bootstrap.js,sha256=W3_JVqt6kt-z9ZzdKicFFOw-8uiRaL-Ca_X4KaRBPEc,9858
23
- pumaguard/pumaguard-ui/flutter_service_worker.js,sha256=sXnvixBj7TMPqXyzAijqWX9Wpj_EDDuP5vNnOyHuVsg,8435
22
+ pumaguard/pumaguard-ui/flutter_bootstrap.js,sha256=9b5W1CTbXpXNCEPNFJFB80dD4NSyvDMAS4ygtFd8Cp4,9858
23
+ pumaguard/pumaguard-ui/flutter_service_worker.js,sha256=AdfZnbMTG01__3IF0v_fh-D9rTHOtL8IyQan5s-HiLU,8435
24
24
  pumaguard/pumaguard-ui/index.html,sha256=901-ZY0WysVAZWPwj2xGatoezwm9TX9IV_jpMrlsaXg,1205
25
25
  pumaguard/pumaguard-ui/main.dart.js,sha256=fFz7OvGMIyrSvIBcp1jK3P3x1xCh3i3anBpMHuaVgDo,2613988
26
26
  pumaguard/pumaguard-ui/main.dart.mjs,sha256=u8D9JVFd7gNLKK-L2s_IjAfLkymktS1DXV25sXo4BsQ,33768
@@ -51,7 +51,7 @@ pumaguard/pumaguard-ui/icons/Icon-192.png,sha256=Pc6ZB3YC9wQhwcayokC8m4PWTYZoHUX
51
51
  pumaguard/pumaguard-ui/icons/Icon-512.png,sha256=usyyBa5F8LQhvhZXJZtJQ6xAyVCUq4d_O8vhLNVE3L4,8252
52
52
  pumaguard/pumaguard-ui/icons/Icon-maskable-192.png,sha256=0shC4iqfTsnZlrIzc6kFyI2aIDsiDFwVGIWtYh-XS1w,5594
53
53
  pumaguard/pumaguard-ui/icons/Icon-maskable-512.png,sha256=au4Gzcq2sq73Sxc0xHePRCHS2hALD_nlKyG1UkAgKSk,20998
54
- pumaguard-20.post157.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
54
+ pumaguard-20.post159.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
55
55
  pumaguard-sounds/cougar_call.mp3,sha256=jdPzi7Qneect3ez2G6XAeHWtetU5vSOSB6pceuB26Wc,129048
56
56
  pumaguard-sounds/cougarsounds.wav,sha256=hwVmmQ75dkOP3qd07YAvVOSm1neYtxLSzxw3Ulvs2cM,96346
57
57
  pumaguard-sounds/dark-engine-logo-141942.mp3,sha256=Vw-qyLTMPJZvsgQcZtH0DpGcP1dd7nJq-9BnHuNPGug,372819
@@ -69,8 +69,8 @@ pumaguard-sounds/mixkit-vintage-telephone-ringtone-1356.wav,sha256=zWWY2uFF0-l7P
69
69
  pumaguard-sounds/pumaguard-warning.mp3,sha256=wcCfHsulPo5P5s8MjpQAG2NYHQDsRpjqoMig1-o_MDI,232249
70
70
  pumaguard-sounds/short-round-110940.mp3,sha256=vdskGD94SeH1UJyJyR0Ek_7xGXPIZfnPdoBvxGnUt98,450816
71
71
  pumaguard-ui/ios/Flutter/ephemeral/flutter_lldb_helper.py,sha256=Bc_jl3_e5ZPvrSBJpPYtN05VxpztyKq-7lVms3rLg4Q,1276
72
- pumaguard-20.post157.dist-info/METADATA,sha256=ACEPHCv3lm21RctPD-e4fuxcW0aUAr_SjYGw9HJej38,7751
73
- pumaguard-20.post157.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
74
- pumaguard-20.post157.dist-info/entry_points.txt,sha256=rmCdBTPWrbJQvPPwABSVobXE9D7hrKsITGZ6nvCrko8,127
75
- pumaguard-20.post157.dist-info/top_level.txt,sha256=B-PzS4agkQNhOYbLLIrMVOyMD_pl5F-yujPBm5zYYjY,40
76
- pumaguard-20.post157.dist-info/RECORD,,
72
+ pumaguard-20.post159.dist-info/METADATA,sha256=xuIjZT7SYWmnOnjMFB4dfHWkYG0SeVKkVpTsy0DvrjE,7751
73
+ pumaguard-20.post159.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
74
+ pumaguard-20.post159.dist-info/entry_points.txt,sha256=rmCdBTPWrbJQvPPwABSVobXE9D7hrKsITGZ6nvCrko8,127
75
+ pumaguard-20.post159.dist-info/top_level.txt,sha256=B-PzS4agkQNhOYbLLIrMVOyMD_pl5F-yujPBm5zYYjY,40
76
+ pumaguard-20.post159.dist-info/RECORD,,