sciveo 0.1.53__tar.gz → 0.1.55__tar.gz
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.
- {sciveo-0.1.53 → sciveo-0.1.55}/PKG-INFO +2 -2
- sciveo-0.1.55/sciveo/media/tools/video_interactive.py +138 -0
- sciveo-0.1.55/sciveo/ml/evaluation/markdown.py +227 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/monitoring/monitor.py +1 -1
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/timers.py +13 -2
- sciveo-0.1.55/sciveo/version.py +2 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo.egg-info/PKG-INFO +2 -2
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo.egg-info/SOURCES.txt +3 -1
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo.egg-info/requires.txt +21 -17
- {sciveo-0.1.53 → sciveo-0.1.55}/setup.py +2 -2
- sciveo-0.1.55/test/test_eval_markdown.py +41 -0
- sciveo-0.1.53/sciveo/ml/images/segmentation.py +0 -304
- sciveo-0.1.53/sciveo/version.py +0 -2
- {sciveo-0.1.53 → sciveo-0.1.55}/README.md +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/api/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/api/base.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/api/predictors.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/api/server.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/api/upload.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/cli.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/common/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/common/configuration.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/common/model.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/common/optimizers.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/common/sampling.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/content/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/content/dataset.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/content/experiment.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/content/project.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/content/runner.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/base.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/encoders/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/encoders/base.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/encoders/normalizer.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/nlp/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/nlp/search.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/time_series/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/time_series/dataset.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/time_series/predictor.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/time_series/trainer.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/ml/time_series/window_generator.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/base.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/job_daemon.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/layouts/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/layouts/base.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/pipeline.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/postprocessors/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/postprocessors/base.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/postprocessors/default.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/audio/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/audio/audio.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/audio/audio_extractor_process.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/aws.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/base.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/file/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/file/archive.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/album.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/album_in_image.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/depth_esimation.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/embeddings.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/filters.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/generators.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/histogram.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/mask.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/object_detection.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/resize.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/segmentation.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/image/watermark.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/media_info.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/nlp/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/nlp/address.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/qr.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/sci/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/sci/base.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/sci/dataset.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/sci/time_series/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/sci/time_series/predictor.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/sci/time_series/trainer.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/tpu_base.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/video/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/video/generators.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/video/motion_detection.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/video/resize.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/video/video_album.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/video/video_frames.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/processors/video/video_resample.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/queues.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/server.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/web/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/pipelines/web/server.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/tools/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/media/tools/nvr.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/base.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/dataset/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/dataset/object_detection.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/evaluation/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/evaluation/object_detection.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/images/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/images/base.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/images/description.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/images/embeddings.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/images/object_detection.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/images/tools.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/images/transformers.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/nlp/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/nlp/embeddings.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/nlp/tokenizers/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/nlp/tokenizers/bpe.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/video/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/ml/video/description.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/monitoring/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/monitoring/start.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/monitoring/watchdog/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/monitoring/watchdog/memory.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/network/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/network/camera.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/network/sniffer.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/network/tools.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/array.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/aws/__init__.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/aws/priority_queue.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/aws/s3.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/common.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/complexity.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/compress.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/configuration.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/crypto.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/daemon.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/formating.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/hardware.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/http.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/logger.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/os.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/queue.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/random.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/remote.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/simple_counter.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo/tools/synchronized.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo.egg-info/dependency_links.txt +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo.egg-info/entry_points.txt +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/sciveo.egg-info/top_level.txt +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/setup.cfg +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/test/test_complexity.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/test/test_compress.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/test/test_configuration.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/test/test_crypto.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/test/test_ml_datasets.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/test/test_monitoring.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/test/test_runner.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/test/test_sampling.py +0 -0
- {sciveo-0.1.53 → sciveo-0.1.55}/test/test_tokenizers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sciveo
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.55
|
|
4
4
|
Description-Content-Type: text/markdown
|
|
5
5
|
Provides-Extra: mon
|
|
6
6
|
Provides-Extra: net
|
|
@@ -8,7 +8,7 @@ Provides-Extra: server
|
|
|
8
8
|
Provides-Extra: media
|
|
9
9
|
Provides-Extra: media-ml
|
|
10
10
|
Provides-Extra: all
|
|
11
|
-
Provides-Extra:
|
|
11
|
+
Provides-Extra: ml
|
|
12
12
|
|
|
13
13
|
# SCIVEO - ML/AI and Scientific tools
|
|
14
14
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Pavlin Georgiev, Softel Labs
|
|
3
|
+
#
|
|
4
|
+
# This is a proprietary file and may not be copied,
|
|
5
|
+
# distributed, or modified without express permission
|
|
6
|
+
# from the owner. For licensing inquiries, please
|
|
7
|
+
# contact pavlin@softel.bg.
|
|
8
|
+
#
|
|
9
|
+
# 2024
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import time
|
|
14
|
+
import cv2
|
|
15
|
+
import threading
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
from sciveo.tools.logger import *
|
|
19
|
+
from sciveo.tools.timers import FPSCounter
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BaseInteractiveVideoPlayer:
|
|
23
|
+
def __init__(self, path_video, tag):
|
|
24
|
+
self.path_video = path_video
|
|
25
|
+
self.file_name = os.path.basename(self.path_video)
|
|
26
|
+
self.file_name_base = filename_without_ext = os.path.splitext(self.file_name)[0]
|
|
27
|
+
self.path_base = os.path.dirname(self.path_video)
|
|
28
|
+
self.tag = tag
|
|
29
|
+
self.frame_tag = f"Video {tag}"
|
|
30
|
+
|
|
31
|
+
self.cap = None
|
|
32
|
+
self.frame = None
|
|
33
|
+
self.frame_id = 0
|
|
34
|
+
self.result = None
|
|
35
|
+
self.progress_lock = threading.Lock()
|
|
36
|
+
self.frame_lock = threading.Lock()
|
|
37
|
+
self.ui_video_progress_tag = "video time progress"
|
|
38
|
+
|
|
39
|
+
self.fps_ui = FPSCounter(tag="ui", period=1, print_period=600)
|
|
40
|
+
self.fps_read = FPSCounter(tag="read", period=1, print_period=600)
|
|
41
|
+
|
|
42
|
+
self.stop_threads = False
|
|
43
|
+
self.threads = [
|
|
44
|
+
threading.Thread(target=self.read, daemon=True)
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
def callback_video_progress(self, x):
|
|
48
|
+
# debug("callback_video_progress", x)
|
|
49
|
+
with self.frame_lock:
|
|
50
|
+
# self.frame_id = cv2.getTrackbarPos(self.ui_video_progress_tag, self.frame_tag)
|
|
51
|
+
self.frame_id = x
|
|
52
|
+
|
|
53
|
+
def ui_controls(self, min_val, max_val):
|
|
54
|
+
cv2.createTrackbar(self.ui_video_progress_tag, self.frame_tag, min_val, max_val, self.callback_video_progress)
|
|
55
|
+
|
|
56
|
+
def process_frame(self, frame):
|
|
57
|
+
return {}
|
|
58
|
+
|
|
59
|
+
def read(self):
|
|
60
|
+
while not self.stop_threads:
|
|
61
|
+
self.fps_read.update()
|
|
62
|
+
with self.progress_lock:
|
|
63
|
+
current_frame_id = self.frame_id
|
|
64
|
+
self.cap.set(cv2.CAP_PROP_POS_FRAMES, int(current_frame_id + 1))
|
|
65
|
+
ret, current_frame = self.cap.read()
|
|
66
|
+
if not ret:
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
current_result = self.process_frame(current_frame)
|
|
70
|
+
|
|
71
|
+
with self.frame_lock:
|
|
72
|
+
self.frame = current_frame.copy()
|
|
73
|
+
self.result = current_result
|
|
74
|
+
|
|
75
|
+
with self.progress_lock:
|
|
76
|
+
self.frame_id += 1
|
|
77
|
+
if self.frame_id >= self.video_frame_count:
|
|
78
|
+
self.frame_id = 0
|
|
79
|
+
cv2.setTrackbarPos(self.ui_video_progress_tag, self.frame_tag, self.frame_id)
|
|
80
|
+
|
|
81
|
+
def draw(self, frame, result):
|
|
82
|
+
cv2.putText(frame, f"R[{result}]", (30, 80), cv2.FONT_HERSHEY_SIMPLEX, 1.3, (0, 255, 255), 2)
|
|
83
|
+
|
|
84
|
+
def run(self, max_display_width=1024):
|
|
85
|
+
cv2.namedWindow(self.frame_tag, cv2.WINDOW_NORMAL)
|
|
86
|
+
|
|
87
|
+
self.cap = cv2.VideoCapture(self.path_video)
|
|
88
|
+
|
|
89
|
+
self.video_fps = self.cap.get(cv2.CAP_PROP_FPS)
|
|
90
|
+
self.video_frame_count = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
91
|
+
self.video_duration_sec = self.video_frame_count / self.video_fps
|
|
92
|
+
debug("run", self.path_video, self.tag, "fps", self.video_fps, "count", self.video_frame_count, "duration", self.video_duration_sec)
|
|
93
|
+
|
|
94
|
+
self.ui_controls(0, self.video_frame_count)
|
|
95
|
+
|
|
96
|
+
for thread in self.threads:
|
|
97
|
+
thread.start()
|
|
98
|
+
|
|
99
|
+
while self.cap.isOpened():
|
|
100
|
+
self.fps_ui.update()
|
|
101
|
+
current_frame = None
|
|
102
|
+
original_current_frame = None
|
|
103
|
+
current_result = None
|
|
104
|
+
with self.frame_lock:
|
|
105
|
+
if self.frame is not None:
|
|
106
|
+
current_frame = self.frame.copy()
|
|
107
|
+
original_current_frame = self.frame.copy()
|
|
108
|
+
current_result = self.result.copy()
|
|
109
|
+
|
|
110
|
+
if current_frame is not None:
|
|
111
|
+
cv2.putText(current_frame, f"FPS read {round(self.fps_read.value, 1)} ui {round(self.fps_ui.value, 1)} T {round(self.frame_id / self.video_fps, 3)}s", (30, 40), cv2.FONT_HERSHEY_SIMPLEX, 1.3, (0, 255, 0), 4)
|
|
112
|
+
self.draw(current_frame, current_result)
|
|
113
|
+
height, width = current_frame.shape[:2]
|
|
114
|
+
if width > max_display_width:
|
|
115
|
+
display_width = max_display_width
|
|
116
|
+
display_height = int(height * (max_display_width / width))
|
|
117
|
+
cv2.resizeWindow(self.frame_tag, display_width, display_height)
|
|
118
|
+
cv2.imshow(self.frame_tag, current_frame)
|
|
119
|
+
|
|
120
|
+
key = cv2.waitKey(1) & 0xFF
|
|
121
|
+
if key == ord("q"):
|
|
122
|
+
self.stop_threads = True
|
|
123
|
+
for thread in self.threads:
|
|
124
|
+
thread.join()
|
|
125
|
+
break
|
|
126
|
+
elif key == ord("p"):
|
|
127
|
+
if original_current_frame is not None:
|
|
128
|
+
frame_path = os.path.join(self.path_base, f"{self.file_name_base}-{self.frame_id}.png")
|
|
129
|
+
cv2.imwrite(frame_path, original_current_frame)
|
|
130
|
+
debug(f"saved frame {self.frame_id} to {frame_path}")
|
|
131
|
+
|
|
132
|
+
self.cap.release()
|
|
133
|
+
cv2.destroyAllWindows()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
video_player = BaseInteractiveVideoPlayer(os.environ["PATH_VIDEO_FILE"], "base video player")
|
|
138
|
+
video_player.run()
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Stanislav Georgiev, Softel Labs
|
|
3
|
+
#
|
|
4
|
+
# This is a proprietary file and may not be copied,
|
|
5
|
+
# distributed, or modified without express permission
|
|
6
|
+
# from the owner. For licensing inquiries, please
|
|
7
|
+
# contact s.georgiev@softel.bg.
|
|
8
|
+
#
|
|
9
|
+
# 2025
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
import difflib
|
|
14
|
+
from collections import defaultdict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EvalMarkdownSimple:
|
|
18
|
+
def __init__(self, md_true: str, md_predicted: str, similarity_threshold=0.8):
|
|
19
|
+
self.md_true = md_true.split("\n")
|
|
20
|
+
self.md_predicted = md_predicted.split("\n")
|
|
21
|
+
self.similarity_threshold = similarity_threshold
|
|
22
|
+
self.results = {"EM": [], "PM": [], "FN": [], "FP": [], "RI": [], "H": [], "FE": []}
|
|
23
|
+
|
|
24
|
+
def _find_best_match(self, text, candidates):
|
|
25
|
+
"""
|
|
26
|
+
Finds the best matching text block from predicted Markdown using similarity comparison.
|
|
27
|
+
|
|
28
|
+
:param text: The labeled Markdown text to match.
|
|
29
|
+
:param candidates: The list of LLM-generated Markdown text blocks.
|
|
30
|
+
:return: (best_match, similarity_score) or (None, 0) if no match found.
|
|
31
|
+
"""
|
|
32
|
+
best_match = None
|
|
33
|
+
best_score = 0
|
|
34
|
+
text_lower = text.lower()
|
|
35
|
+
|
|
36
|
+
for candidate in candidates:
|
|
37
|
+
score = difflib.SequenceMatcher(None, text_lower, candidate.lower()).ratio()
|
|
38
|
+
if score > best_score:
|
|
39
|
+
best_score = score
|
|
40
|
+
best_match = candidate
|
|
41
|
+
|
|
42
|
+
return (best_match, best_score) if best_score >= self.similarity_threshold else (None, 0)
|
|
43
|
+
|
|
44
|
+
def _check_formatting_errors(self, original, predicted):
|
|
45
|
+
"""
|
|
46
|
+
Checks for incorrect Markdown formatting in predicted text.
|
|
47
|
+
|
|
48
|
+
:param original: The manually labeled Markdown text.
|
|
49
|
+
:param predicted: The LLM-generated Markdown text.
|
|
50
|
+
:return: True if formatting errors exist, False otherwise.
|
|
51
|
+
"""
|
|
52
|
+
# Basic check: header formatting, bold/italic differences
|
|
53
|
+
if original.strip("#*`").strip() == predicted.strip("#*`").strip():
|
|
54
|
+
return True
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
def evaluate(self):
|
|
58
|
+
"""
|
|
59
|
+
Evaluates Markdown
|
|
60
|
+
"""
|
|
61
|
+
matched_predicted_blocks = set()
|
|
62
|
+
predicted_idx_map = {block: idx for idx, block in enumerate(self.md_predicted)}
|
|
63
|
+
|
|
64
|
+
for true_text in self.md_true:
|
|
65
|
+
best_match, score = self._find_best_match(true_text, self.md_predicted)
|
|
66
|
+
|
|
67
|
+
if best_match:
|
|
68
|
+
matched_predicted_blocks.add(best_match)
|
|
69
|
+
if score == 1.0:
|
|
70
|
+
self.results["EM"].append((true_text, best_match))
|
|
71
|
+
else:
|
|
72
|
+
self.results["PM"].append((true_text, best_match, score))
|
|
73
|
+
|
|
74
|
+
# Check for formatting errors
|
|
75
|
+
if self._check_formatting_errors(true_text, best_match):
|
|
76
|
+
self.results["FE"].append((true_text, best_match))
|
|
77
|
+
else:
|
|
78
|
+
self.results["FN"].append(true_text)
|
|
79
|
+
|
|
80
|
+
# False Positives (extra predicted blocks that don't match labeled Markdown)
|
|
81
|
+
for pred_text in self.md_predicted:
|
|
82
|
+
if pred_text not in matched_predicted_blocks:
|
|
83
|
+
self.results["FP"].append(pred_text)
|
|
84
|
+
|
|
85
|
+
# Check for hallucinations (predicted content not in labeled text)
|
|
86
|
+
for pred_text in self.results["FP"]:
|
|
87
|
+
best_match, _ = self._find_best_match(pred_text, self.md_true)
|
|
88
|
+
if best_match is None:
|
|
89
|
+
self.results["H"].append(pred_text)
|
|
90
|
+
|
|
91
|
+
# Check for order issues (text found but misordered)
|
|
92
|
+
true_texts = [t for t, _ in self.results["EM"]] + [t for t, _, _ in self.results["PM"]]
|
|
93
|
+
pred_texts = [p for _, p in self.results["EM"]] + [p for _, p, _ in self.results["PM"]]
|
|
94
|
+
|
|
95
|
+
true_indices = [predicted_idx_map[text] for text in pred_texts if text in predicted_idx_map]
|
|
96
|
+
if true_indices != sorted(true_indices):
|
|
97
|
+
self.results["RI"].append(true_indices)
|
|
98
|
+
|
|
99
|
+
return self.results
|
|
100
|
+
|
|
101
|
+
def score(self):
|
|
102
|
+
"""
|
|
103
|
+
Computes an improved similarity score with weighted Partial Matches (PM).
|
|
104
|
+
"""
|
|
105
|
+
TP = len(self.results["EM"])
|
|
106
|
+
PM_weighted = sum(score for _, _, score in self.results["PM"])
|
|
107
|
+
FN = len(self.results["FN"])
|
|
108
|
+
FP = len(self.results["FP"])
|
|
109
|
+
|
|
110
|
+
precision = (TP + PM_weighted) / (TP + PM_weighted + FP) if (TP + PM_weighted + FP) > 0 else 0
|
|
111
|
+
recall = (TP + PM_weighted) / (TP + PM_weighted + FN) if (TP + PM_weighted + FN) > 0 else 0
|
|
112
|
+
f1_score = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
|
|
113
|
+
|
|
114
|
+
return {"Precision": precision, "Recall": recall, "F1 Score": f1_score}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class EvalMarkdown:
|
|
118
|
+
def __init__(self, md_true, md_predicted):
|
|
119
|
+
"""
|
|
120
|
+
Evaluates labeled (true) Markdown against predicted Markdown with section-wise evaluation.
|
|
121
|
+
"""
|
|
122
|
+
self.md_true = self._parse_markdown(md_true.lower())
|
|
123
|
+
self.md_predicted = self._parse_markdown(md_predicted.lower())
|
|
124
|
+
self.results = defaultdict(lambda: {"EM": [], "PM": [], "FN": [], "FP": []})
|
|
125
|
+
|
|
126
|
+
def _parse_markdown(self, markdown_text):
|
|
127
|
+
"""
|
|
128
|
+
Parses Markdown into a dictionary of sections where key = heading, value = list of text blocks.
|
|
129
|
+
"""
|
|
130
|
+
sections = defaultdict(list)
|
|
131
|
+
current_section = "INTRO" # Default section if no heading appears
|
|
132
|
+
|
|
133
|
+
for line in markdown_text.split("\n"):
|
|
134
|
+
heading_match = re.match(r"^(#{1,6})\s+(.+)", line)
|
|
135
|
+
if heading_match:
|
|
136
|
+
current_section = heading_match.group(2).strip() # Extract section title
|
|
137
|
+
else:
|
|
138
|
+
if line.strip():
|
|
139
|
+
sections[current_section].append(line.strip())
|
|
140
|
+
|
|
141
|
+
return sections
|
|
142
|
+
|
|
143
|
+
def _find_best_match(self, text, true_texts):
|
|
144
|
+
"""
|
|
145
|
+
Finds the best match for a given text within a list of true texts.
|
|
146
|
+
Returns (best_match_text, similarity_score).
|
|
147
|
+
"""
|
|
148
|
+
if not true_texts:
|
|
149
|
+
return None, 0
|
|
150
|
+
|
|
151
|
+
from difflib import SequenceMatcher
|
|
152
|
+
best_match, best_score = None, 0
|
|
153
|
+
|
|
154
|
+
for true_text in true_texts:
|
|
155
|
+
score = SequenceMatcher(None, text, true_text).ratio()
|
|
156
|
+
if score > best_score:
|
|
157
|
+
best_match, best_score = true_text, score
|
|
158
|
+
|
|
159
|
+
return best_match, best_score
|
|
160
|
+
|
|
161
|
+
def evaluate(self):
|
|
162
|
+
all_sections = set(self.md_true.keys()).union(set(self.md_predicted.keys()))
|
|
163
|
+
for section in all_sections:
|
|
164
|
+
true_texts = self.md_true.get(section, [])
|
|
165
|
+
pred_texts = self.md_predicted.get(section, [])
|
|
166
|
+
|
|
167
|
+
matched_true = set()
|
|
168
|
+
matched_pred = set()
|
|
169
|
+
|
|
170
|
+
# Exact matches
|
|
171
|
+
for pred_text in pred_texts:
|
|
172
|
+
if pred_text in true_texts:
|
|
173
|
+
self.results[section]["EM"].append((pred_text, pred_text))
|
|
174
|
+
matched_true.add(pred_text)
|
|
175
|
+
matched_pred.add(pred_text)
|
|
176
|
+
|
|
177
|
+
# Partial matches
|
|
178
|
+
for pred_text in pred_texts:
|
|
179
|
+
if pred_text not in matched_pred:
|
|
180
|
+
best_match, score = self._find_best_match(pred_text, true_texts)
|
|
181
|
+
if best_match and score > 0.8: # Accept only good matches
|
|
182
|
+
self.results[section]["PM"].append((best_match, pred_text, score))
|
|
183
|
+
matched_true.add(best_match)
|
|
184
|
+
matched_pred.add(pred_text)
|
|
185
|
+
|
|
186
|
+
# False negatives (missed text from ground truth)
|
|
187
|
+
for true_text in true_texts:
|
|
188
|
+
if true_text not in matched_true:
|
|
189
|
+
self.results[section]["FN"].append(true_text)
|
|
190
|
+
|
|
191
|
+
# False positives (extra predicted text)
|
|
192
|
+
for pred_text in pred_texts:
|
|
193
|
+
if pred_text not in matched_pred:
|
|
194
|
+
self.results[section]["FP"].append(pred_text)
|
|
195
|
+
|
|
196
|
+
return self.results
|
|
197
|
+
|
|
198
|
+
def score(self):
|
|
199
|
+
"""
|
|
200
|
+
Computes section-wise and global similarity scores.
|
|
201
|
+
"""
|
|
202
|
+
section_scores = {}
|
|
203
|
+
global_TP, global_PM, global_FN, global_FP = 0, 0, 0, 0
|
|
204
|
+
|
|
205
|
+
for section, result in self.results.items():
|
|
206
|
+
TP = len(result["EM"])
|
|
207
|
+
PM_weighted = sum(score for _, _, score in result["PM"])
|
|
208
|
+
FN = len(result["FN"])
|
|
209
|
+
FP = len(result["FP"])
|
|
210
|
+
|
|
211
|
+
precision = (TP + PM_weighted) / (TP + PM_weighted + FP) if (TP + PM_weighted + FP) > 0 else 0
|
|
212
|
+
recall = (TP + PM_weighted) / (TP + PM_weighted + FN) if (TP + PM_weighted + FN) > 0 else 0
|
|
213
|
+
f1_score = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
|
|
214
|
+
|
|
215
|
+
section_scores[section] = {"Precision": precision, "Recall": recall, "F1 Score": f1_score}
|
|
216
|
+
|
|
217
|
+
global_TP += TP
|
|
218
|
+
global_PM += PM_weighted
|
|
219
|
+
global_FN += FN
|
|
220
|
+
global_FP += FP
|
|
221
|
+
|
|
222
|
+
# Global precision/recall across all sections
|
|
223
|
+
global_precision = (global_TP + global_PM) / (global_TP + global_PM + global_FP) if (global_TP + global_PM + global_FP) > 0 else 0
|
|
224
|
+
global_recall = (global_TP + global_PM) / (global_TP + global_PM + global_FN) if (global_TP + global_PM + global_FN) > 0 else 0
|
|
225
|
+
global_f1 = (2 * global_precision * global_recall) / (global_precision + global_recall) if (global_precision + global_recall) > 0 else 0
|
|
226
|
+
|
|
227
|
+
return {"Sections": section_scores, "Global": {"Precision": global_precision, "Recall": global_recall, "F1 Score": global_f1}}
|
|
@@ -15,19 +15,30 @@ from sciveo.tools.logger import *
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class FPSCounter:
|
|
18
|
-
def __init__(self, period=
|
|
18
|
+
def __init__(self, period=1, tag="", print_period=1, printer=debug):
|
|
19
19
|
self.period = period
|
|
20
|
+
self.print_period = print_period
|
|
21
|
+
self.printer = printer
|
|
22
|
+
self.print_n = 0
|
|
20
23
|
self.tag = tag
|
|
21
24
|
self.n = 0
|
|
22
25
|
self.t1 = time.time()
|
|
26
|
+
self.value = 0
|
|
27
|
+
|
|
28
|
+
def print(self):
|
|
29
|
+
self.print_n += 1
|
|
30
|
+
if self.print_n > self.print_period:
|
|
31
|
+
self.printer(self.tag, "FPS", self.value)
|
|
32
|
+
self.print_n = 0
|
|
23
33
|
|
|
24
34
|
def update(self):
|
|
25
35
|
self.n += 1
|
|
26
36
|
t2 = time.time()
|
|
27
37
|
if t2 - self.t1 > self.period:
|
|
28
|
-
|
|
38
|
+
self.value = self.n / (t2 - self.t1)
|
|
29
39
|
self.n = 0
|
|
30
40
|
self.t1 = time.time()
|
|
41
|
+
self.print()
|
|
31
42
|
|
|
32
43
|
|
|
33
44
|
class TimerExec:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sciveo
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.55
|
|
4
4
|
Description-Content-Type: text/markdown
|
|
5
5
|
Provides-Extra: mon
|
|
6
6
|
Provides-Extra: net
|
|
@@ -8,7 +8,7 @@ Provides-Extra: server
|
|
|
8
8
|
Provides-Extra: media
|
|
9
9
|
Provides-Extra: media-ml
|
|
10
10
|
Provides-Extra: all
|
|
11
|
-
Provides-Extra:
|
|
11
|
+
Provides-Extra: ml
|
|
12
12
|
|
|
13
13
|
# SCIVEO - ML/AI and Scientific tools
|
|
14
14
|
|
|
@@ -91,18 +91,19 @@ sciveo/media/pipelines/web/__init__.py
|
|
|
91
91
|
sciveo/media/pipelines/web/server.py
|
|
92
92
|
sciveo/media/tools/__init__.py
|
|
93
93
|
sciveo/media/tools/nvr.py
|
|
94
|
+
sciveo/media/tools/video_interactive.py
|
|
94
95
|
sciveo/ml/__init__.py
|
|
95
96
|
sciveo/ml/base.py
|
|
96
97
|
sciveo/ml/dataset/__init__.py
|
|
97
98
|
sciveo/ml/dataset/object_detection.py
|
|
98
99
|
sciveo/ml/evaluation/__init__.py
|
|
100
|
+
sciveo/ml/evaluation/markdown.py
|
|
99
101
|
sciveo/ml/evaluation/object_detection.py
|
|
100
102
|
sciveo/ml/images/__init__.py
|
|
101
103
|
sciveo/ml/images/base.py
|
|
102
104
|
sciveo/ml/images/description.py
|
|
103
105
|
sciveo/ml/images/embeddings.py
|
|
104
106
|
sciveo/ml/images/object_detection.py
|
|
105
|
-
sciveo/ml/images/segmentation.py
|
|
106
107
|
sciveo/ml/images/tools.py
|
|
107
108
|
sciveo/ml/images/transformers.py
|
|
108
109
|
sciveo/ml/nlp/__init__.py
|
|
@@ -146,6 +147,7 @@ test/test_complexity.py
|
|
|
146
147
|
test/test_compress.py
|
|
147
148
|
test/test_configuration.py
|
|
148
149
|
test/test_crypto.py
|
|
150
|
+
test/test_eval_markdown.py
|
|
149
151
|
test/test_ml_datasets.py
|
|
150
152
|
test/test_monitoring.py
|
|
151
153
|
test/test_runner.py
|
|
@@ -5,6 +5,10 @@ requests>=0.0.0
|
|
|
5
5
|
psutil>=0.0.0
|
|
6
6
|
netifaces>=0.0.0
|
|
7
7
|
scapy>=0.0.0
|
|
8
|
+
fastapi>=0.0.0
|
|
9
|
+
uvicorn[standard]>=0.0.0
|
|
10
|
+
flask>=0.0.0
|
|
11
|
+
waitress>=0.0.0
|
|
8
12
|
|
|
9
13
|
[media]
|
|
10
14
|
scikit-learn>=0.0.0
|
|
@@ -25,10 +29,26 @@ ffmpeg-python>=0.0.0
|
|
|
25
29
|
opencv-python-headless>=0.0.0
|
|
26
30
|
opencv-contrib-python-headless>=0.0.0
|
|
27
31
|
|
|
28
|
-
[media-
|
|
32
|
+
[media-ml]
|
|
33
|
+
tensorflow>=0.0.0
|
|
34
|
+
keras>=0.0.0
|
|
35
|
+
torch>=0.0.0
|
|
36
|
+
torchvision>=0.0.0
|
|
37
|
+
diffusers>=0.0.0
|
|
38
|
+
transformers>=0.0.0
|
|
39
|
+
sentence_transformers>=0.0.0
|
|
40
|
+
accelerate>=0.0.0
|
|
41
|
+
annoy>=0.0.0
|
|
42
|
+
ultralytics>=0.0.0
|
|
43
|
+
|
|
44
|
+
[ml]
|
|
29
45
|
psutil>=0.0.0
|
|
30
46
|
netifaces>=0.0.0
|
|
31
47
|
scapy>=0.0.0
|
|
48
|
+
fastapi>=0.0.0
|
|
49
|
+
uvicorn[standard]>=0.0.0
|
|
50
|
+
flask>=0.0.0
|
|
51
|
+
waitress>=0.0.0
|
|
32
52
|
scikit-learn>=0.0.0
|
|
33
53
|
scipy>=0.0.0
|
|
34
54
|
scikit-video>=0.0.0
|
|
@@ -46,22 +66,6 @@ tqdm>=0.0.0
|
|
|
46
66
|
ffmpeg-python>=0.0.0
|
|
47
67
|
opencv-python-headless>=0.0.0
|
|
48
68
|
opencv-contrib-python-headless>=0.0.0
|
|
49
|
-
fastapi>=0.0.0
|
|
50
|
-
uvicorn[standard]>=0.0.0
|
|
51
|
-
flask>=0.0.0
|
|
52
|
-
waitress>=0.0.0
|
|
53
|
-
tensorflow>=0.0.0
|
|
54
|
-
keras>=0.0.0
|
|
55
|
-
torch>=0.0.0
|
|
56
|
-
torchvision>=0.0.0
|
|
57
|
-
diffusers>=0.0.0
|
|
58
|
-
transformers>=0.0.0
|
|
59
|
-
sentence_transformers>=0.0.0
|
|
60
|
-
accelerate>=0.0.0
|
|
61
|
-
annoy>=0.0.0
|
|
62
|
-
ultralytics>=0.0.0
|
|
63
|
-
|
|
64
|
-
[media-ml]
|
|
65
69
|
tensorflow>=0.0.0
|
|
66
70
|
keras>=0.0.0
|
|
67
71
|
torch>=0.0.0
|
|
@@ -39,8 +39,8 @@ extras_require = {
|
|
|
39
39
|
]
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
extras_require['all'] = extras_require['mon'] + extras_require['net']
|
|
43
|
-
extras_require['
|
|
42
|
+
extras_require['all'] = extras_require['mon'] + extras_require['net'] + extras_require['server']
|
|
43
|
+
extras_require['ml'] = extras_require['all'] + extras_require['media'] + extras_require['media-ml']
|
|
44
44
|
|
|
45
45
|
setup(
|
|
46
46
|
name='sciveo',
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Stanislav Georgiev, Softel Labs
|
|
3
|
+
#
|
|
4
|
+
# This is a proprietary file and may not be copied,
|
|
5
|
+
# distributed, or modified without express permission
|
|
6
|
+
# from the owner. For licensing inquiries, please
|
|
7
|
+
# contact s.georgiev@softel.bg.
|
|
8
|
+
#
|
|
9
|
+
# 2025
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
import math
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
from sciveo.tools.logger import *
|
|
16
|
+
from sciveo.ml.evaluation.markdown import *
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestEvalMarkdown(unittest.TestCase):
|
|
20
|
+
def test_1(self):
|
|
21
|
+
md_true = """
|
|
22
|
+
"# Breaking News"
|
|
23
|
+
"A major fire broke out in the city center."
|
|
24
|
+
"Authorities are investigating the cause."
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
md_predicted = """
|
|
28
|
+
"# BREAKING NEWS",
|
|
29
|
+
"A major fire broke out in city center.",
|
|
30
|
+
"Authorities investigate the cause.",
|
|
31
|
+
"Stay tuned for updates."
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
em = EvalMarkdown(md_true, md_predicted)
|
|
35
|
+
results = em.evaluate()
|
|
36
|
+
info(results)
|
|
37
|
+
info("Score", em.score())
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == '__main__':
|
|
41
|
+
unittest.main()
|