halib 0.1.61__tar.gz → 0.1.66__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.
- {halib-0.1.61 → halib-0.1.66}/.gitignore +2 -0
- halib-0.1.66/MANIFEST.in +4 -0
- {halib-0.1.61 → halib-0.1.66}/PKG-INFO +46 -6
- {halib-0.1.61 → halib-0.1.66}/README.md +5 -0
- halib-0.1.66/halib/research/metrics.py +121 -0
- {halib-0.1.61 → halib-0.1.66}/halib/research/perfcalc.py +84 -123
- {halib-0.1.61 → halib-0.1.66}/halib/utils/video.py +16 -2
- {halib-0.1.61 → halib-0.1.66}/halib.egg-info/PKG-INFO +46 -6
- {halib-0.1.61 → halib-0.1.66}/halib.egg-info/SOURCES.txt +1 -0
- {halib-0.1.61 → halib-0.1.66}/setup.py +1 -1
- halib-0.1.61/MANIFEST.in +0 -3
- {halib-0.1.61 → halib-0.1.66}/GDriveFolder.txt +0 -0
- {halib-0.1.61 → halib-0.1.66}/LICENSE.txt +0 -0
- {halib-0.1.61 → halib-0.1.66}/guide_publish_pip.pdf +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/__init__.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/common.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/cuda.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/filetype/__init__.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/filetype/csvfile.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/filetype/jsonfile.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/filetype/textfile.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/filetype/videofile.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/filetype/yamlfile.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/online/__init__.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/online/gdrive.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/online/gdrive_mkdir.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/online/gdrive_test.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/online/projectmake.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/research/__init__.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/research/dataset.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/research/perftb.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/research/plot.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/research/torchloader.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/research/wandb_op.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/rich_color.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/system/__init__.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/system/cmd.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/system/filesys.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/utils/__init__.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/utils/dataclass_util.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/utils/dict_op.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/utils/gpu_mon.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/utils/listop.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib/utils/tele_noti.py +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib.egg-info/dependency_links.txt +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib.egg-info/requires.txt +0 -0
- {halib-0.1.61 → halib-0.1.66}/halib.egg-info/top_level.txt +0 -0
- {halib-0.1.61 → halib-0.1.66}/setup.cfg +0 -0
halib-0.1.66/MANIFEST.in
ADDED
@@ -1,20 +1,62 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: halib
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.66
|
4
4
|
Summary: Small library for common tasks
|
5
5
|
Author: Hoang Van Ha
|
6
6
|
Author-email: hoangvanhauit@gmail.com
|
7
|
-
License: UNKNOWN
|
8
|
-
Platform: UNKNOWN
|
9
7
|
Classifier: Programming Language :: Python :: 3
|
10
8
|
Classifier: License :: OSI Approved :: MIT License
|
11
9
|
Classifier: Operating System :: OS Independent
|
12
10
|
Requires-Python: >=3.8
|
13
11
|
Description-Content-Type: text/markdown
|
14
12
|
License-File: LICENSE.txt
|
13
|
+
Requires-Dist: arrow
|
14
|
+
Requires-Dist: click
|
15
|
+
Requires-Dist: enlighten
|
16
|
+
Requires-Dist: kaleido==0.1.*
|
17
|
+
Requires-Dist: loguru
|
18
|
+
Requires-Dist: more-itertools
|
19
|
+
Requires-Dist: moviepy
|
20
|
+
Requires-Dist: networkx
|
21
|
+
Requires-Dist: numpy
|
22
|
+
Requires-Dist: omegaconf
|
23
|
+
Requires-Dist: opencv-python
|
24
|
+
Requires-Dist: pandas
|
25
|
+
Requires-Dist: Pillow
|
26
|
+
Requires-Dist: Pyarrow
|
27
|
+
Requires-Dist: pycurl
|
28
|
+
Requires-Dist: python-telegram-bot
|
29
|
+
Requires-Dist: requests
|
30
|
+
Requires-Dist: rich
|
31
|
+
Requires-Dist: scikit-learn
|
32
|
+
Requires-Dist: matplotlib
|
33
|
+
Requires-Dist: seaborn
|
34
|
+
Requires-Dist: plotly
|
35
|
+
Requires-Dist: pygwalker
|
36
|
+
Requires-Dist: tabulate
|
37
|
+
Requires-Dist: itables
|
38
|
+
Requires-Dist: timebudget
|
39
|
+
Requires-Dist: tqdm
|
40
|
+
Requires-Dist: tube_dl
|
41
|
+
Requires-Dist: wandb
|
42
|
+
Requires-Dist: dataclass-wizard
|
43
|
+
Dynamic: author
|
44
|
+
Dynamic: author-email
|
45
|
+
Dynamic: classifier
|
46
|
+
Dynamic: description
|
47
|
+
Dynamic: description-content-type
|
48
|
+
Dynamic: license-file
|
49
|
+
Dynamic: requires-dist
|
50
|
+
Dynamic: requires-python
|
51
|
+
Dynamic: summary
|
15
52
|
|
16
53
|
Helper package for coding and automation
|
17
54
|
|
55
|
+
**Version 0.1.66**
|
56
|
+
|
57
|
+
+ now use `uv` for venv management
|
58
|
+
+ `research/perfcalc`: support both torchmetrics and custom metrics for performance calculation
|
59
|
+
|
18
60
|
**Version 0.1.61**
|
19
61
|
|
20
62
|
+ add `util/video`: add `VideoUtils` class to handle common video-related tasks
|
@@ -146,5 +188,3 @@ New Features
|
|
146
188
|
New Features
|
147
189
|
|
148
190
|
+ add support to upload local to google drive.
|
149
|
-
|
150
|
-
|
@@ -1,5 +1,10 @@
|
|
1
1
|
Helper package for coding and automation
|
2
2
|
|
3
|
+
**Version 0.1.66**
|
4
|
+
|
5
|
+
+ now use `uv` for venv management
|
6
|
+
+ `research/perfcalc`: support both torchmetrics and custom metrics for performance calculation
|
7
|
+
|
3
8
|
**Version 0.1.61**
|
4
9
|
|
5
10
|
+ add `util/video`: add `VideoUtils` class to handle common video-related tasks
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# -------------------------------
|
2
|
+
# Metrics Backend Interface
|
3
|
+
# -------------------------------
|
4
|
+
import inspect
|
5
|
+
from typing import Dict, Union, List, Any
|
6
|
+
from abc import ABC, abstractmethod
|
7
|
+
|
8
|
+
class MetricsBackend(ABC):
|
9
|
+
"""Interface for pluggable metrics computation backends."""
|
10
|
+
|
11
|
+
def __init__(self, metrics_info: Union[List[str], Dict[str, Any]]):
|
12
|
+
"""
|
13
|
+
Initialize the backend with optional metrics_info.
|
14
|
+
"""
|
15
|
+
self.metric_info = metrics_info
|
16
|
+
self.validate_metrics_info(self.metric_info)
|
17
|
+
|
18
|
+
@property
|
19
|
+
def metric_names(self) -> List[str]:
|
20
|
+
"""
|
21
|
+
Return a list of metric names.
|
22
|
+
If metric_info is a dict, return its keys; if it's a list, return it directly.
|
23
|
+
"""
|
24
|
+
if isinstance(self.metric_info, dict):
|
25
|
+
return list(self.metric_info.keys())
|
26
|
+
elif isinstance(self.metric_info, list):
|
27
|
+
return self.metric_info
|
28
|
+
else:
|
29
|
+
raise TypeError("metric_info must be a list or a dict")
|
30
|
+
|
31
|
+
def validate_metrics_info(self, metrics_info):
|
32
|
+
if isinstance(metrics_info, list):
|
33
|
+
return metrics_info
|
34
|
+
elif isinstance(metrics_info, dict):
|
35
|
+
return {k: v for k, v in metrics_info.items() if isinstance(k, str)}
|
36
|
+
else:
|
37
|
+
raise TypeError(
|
38
|
+
"metrics_info must be a list of strings or a dict with string keys"
|
39
|
+
)
|
40
|
+
|
41
|
+
@abstractmethod
|
42
|
+
def compute_metrics(
|
43
|
+
self, metrics_info: Union[List[str], Dict[str, Any]], metrics_data_dict: Dict[str, Any], *args, **kwargs
|
44
|
+
) -> Dict[str, Any]:
|
45
|
+
pass
|
46
|
+
|
47
|
+
def calc_metrics(
|
48
|
+
self, metrics_data_dict: Dict[str, Any], *args, **kwargs
|
49
|
+
) -> Dict[str, Any]:
|
50
|
+
"""
|
51
|
+
Calculate metrics based on the provided metrics_info and data.
|
52
|
+
This method should be overridden by subclasses to implement specific metric calculations.
|
53
|
+
"""
|
54
|
+
# prevalidate the metrics_data_dict
|
55
|
+
for metric in self.metric_names:
|
56
|
+
if metric not in metrics_data_dict:
|
57
|
+
raise ValueError(f"Metric '{metric}' not found in provided data.")
|
58
|
+
# Call the abstract method to compute metrics
|
59
|
+
return self.compute_metrics(self.metric_info, metrics_data_dict, *args, **kwargs)
|
60
|
+
|
61
|
+
class TorchMetricsBackend(MetricsBackend):
|
62
|
+
"""TorchMetrics-based backend implementation."""
|
63
|
+
|
64
|
+
def __init__(self, metrics_info: Union[List[str], Dict[str, Any]]):
|
65
|
+
try:
|
66
|
+
import torch
|
67
|
+
from torchmetrics import Metric
|
68
|
+
except ImportError:
|
69
|
+
raise ImportError(
|
70
|
+
"TorchMetricsBackend requires torch and torchmetrics to be installed."
|
71
|
+
)
|
72
|
+
self.metric_info = metrics_info
|
73
|
+
self.torch = torch
|
74
|
+
self.Metric = Metric
|
75
|
+
self.validate_metrics_info(metrics_info)
|
76
|
+
|
77
|
+
def validate_metrics_info(self, metrics_info):
|
78
|
+
if not isinstance(metrics_info, dict):
|
79
|
+
raise TypeError(
|
80
|
+
"TorchMetricsBackend requires metrics_info as a dict {name: MetricInstance}"
|
81
|
+
)
|
82
|
+
for k, v in metrics_info.items():
|
83
|
+
if not isinstance(k, str):
|
84
|
+
raise TypeError(f"Key '{k}' is not a string")
|
85
|
+
if not isinstance(v, self.Metric):
|
86
|
+
raise TypeError(f"Value for key '{k}' must be a torchmetrics.Metric")
|
87
|
+
return metrics_info
|
88
|
+
|
89
|
+
def compute_metrics(self, metrics_info, metrics_data_dict, *args, **kwargs):
|
90
|
+
out_dict = {}
|
91
|
+
for metric, metric_instance in metrics_info.items():
|
92
|
+
if metric not in metrics_data_dict:
|
93
|
+
raise ValueError(f"Metric '{metric}' not found in provided data.")
|
94
|
+
|
95
|
+
metric_data = metrics_data_dict[metric]
|
96
|
+
sig = inspect.signature(metric_instance.update)
|
97
|
+
expected_args = list(sig.parameters.values())
|
98
|
+
|
99
|
+
if isinstance(metric_data, dict):
|
100
|
+
args = [metric_data[param.name] for param in expected_args]
|
101
|
+
elif isinstance(metric_data, (list, tuple)):
|
102
|
+
args = metric_data
|
103
|
+
else:
|
104
|
+
raise TypeError(f"Unsupported data format for metric '{metric}'")
|
105
|
+
|
106
|
+
if len(expected_args) == 1:
|
107
|
+
metric_instance.update(args)
|
108
|
+
else:
|
109
|
+
metric_instance.update(*args)
|
110
|
+
|
111
|
+
computed_value = metric_instance.compute()
|
112
|
+
if isinstance(computed_value, self.torch.Tensor):
|
113
|
+
computed_value = (
|
114
|
+
computed_value.item()
|
115
|
+
if computed_value.numel() == 1
|
116
|
+
else computed_value.tolist()
|
117
|
+
)
|
118
|
+
|
119
|
+
|
120
|
+
out_dict[metric] = computed_value
|
121
|
+
return out_dict
|
@@ -8,65 +8,31 @@ from functools import wraps
|
|
8
8
|
from rich.pretty import pprint
|
9
9
|
|
10
10
|
from abc import ABC, abstractmethod
|
11
|
+
from collections import OrderedDict
|
11
12
|
|
12
13
|
from ..filetype import csvfile
|
13
14
|
from ..common import now_str
|
14
15
|
from ..research.perftb import PerfTB
|
16
|
+
from ..research.metrics import *
|
15
17
|
|
16
|
-
# try to import torch, and torchmetrics
|
17
|
-
try:
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
except ImportError:
|
22
|
-
|
18
|
+
# # try to import torch, and torchmetrics
|
19
|
+
# try:
|
20
|
+
# import torch
|
21
|
+
# import torchmetrics
|
22
|
+
# from torchmetrics import Metric
|
23
|
+
# except ImportError:
|
24
|
+
# raise ImportError("Please install torch and torchmetrics to use this module.")
|
23
25
|
|
24
|
-
def validate_torch_metrics(fn):
|
25
|
-
@wraps(fn)
|
26
|
-
def wrapper(self, *args, **kwargs):
|
27
|
-
result = fn(self, *args, **kwargs)
|
28
|
-
|
29
|
-
if not isinstance(result, dict):
|
30
|
-
raise TypeError("torch_metrics() must return a dictionary")
|
31
|
-
|
32
|
-
for k, v in result.items():
|
33
|
-
if not isinstance(k, str):
|
34
|
-
raise TypeError(f"Key '{k}' is not a string")
|
35
|
-
if not isinstance(v, Metric):
|
36
|
-
raise TypeError(
|
37
|
-
f"Value for key '{k}' is not a torchmetrics.Metric (got {type(v).__name__})"
|
38
|
-
)
|
39
|
-
|
40
|
-
return result
|
41
|
-
|
42
|
-
return wrapper
|
43
|
-
def valid_custom_fields(fn):
|
44
|
-
@wraps(fn)
|
45
|
-
def wrapper(self, *args, **kwargs):
|
46
|
-
rs = fn(self, *args, **kwargs)
|
47
|
-
if not isinstance(rs, tuple) or len(rs) != 2:
|
48
|
-
raise ValueError("Function must return a tuple (outdict, custom_fields)")
|
49
|
-
outdict, custom_fields = rs
|
50
|
-
if not isinstance(outdict, dict):
|
51
|
-
raise TypeError("Output must be a dictionary")
|
52
|
-
if not isinstance(custom_fields, list):
|
53
|
-
raise TypeError("Custom fields must be a list")
|
54
|
-
for field in custom_fields:
|
55
|
-
if not isinstance(field, str):
|
56
|
-
raise TypeError(f"Custom field '{field}' is not a string")
|
57
|
-
return outdict, custom_fields
|
58
|
-
|
59
|
-
return wrapper
|
60
26
|
|
61
27
|
REQUIRED_COLS = ["experiment", "dataset"]
|
62
28
|
CSV_FILE_POSTFIX = "__perf"
|
63
29
|
|
64
30
|
class PerfCalc(ABC): # Abstract base class for performance calculation
|
65
31
|
@abstractmethod
|
66
|
-
def
|
32
|
+
def get_experiment_name(self):
|
67
33
|
"""
|
68
|
-
Return
|
69
|
-
|
34
|
+
Return the name of the experiment.
|
35
|
+
This function should be overridden by the subclass if needed.
|
70
36
|
"""
|
71
37
|
pass
|
72
38
|
|
@@ -79,20 +45,22 @@ class PerfCalc(ABC): # Abstract base class for performance calculation
|
|
79
45
|
pass
|
80
46
|
|
81
47
|
@abstractmethod
|
82
|
-
def
|
48
|
+
def get_metric_backend(self) -> MetricsBackend:
|
83
49
|
"""
|
84
|
-
|
85
|
-
|
86
|
-
Must return a dictionary with keys as metric names and values as the data to be used for those metrics.
|
87
|
-
NOTE: that the data (for each metric) must be in the format expected by the torchmetrics instance (for that metric). E.g: {"accuracy": {"preds": [...], "target": [...]}, ...} since torchmetrics expects the data in a specific format.
|
50
|
+
Return a list of metric names to be used for performance calculation OR a dictionaray with keys as metric names and values as metric instances of torchmetrics.Metric. For example: {"accuracy": Accuracy(), "precision": Precision()}
|
51
|
+
|
88
52
|
"""
|
89
53
|
pass
|
90
54
|
|
55
|
+
# ! can be override, but ONLY if torchmetrics are used
|
56
|
+
# Prepare the exp data for torch metrics.
|
91
57
|
@abstractmethod
|
92
|
-
def
|
58
|
+
def prepare_metrics_data_dict(self, metric_names, *args, **kwargs):
|
93
59
|
"""
|
94
|
-
|
60
|
+
Prepare the data for metrics.
|
95
61
|
This function should be overridden by the subclass if needed.
|
62
|
+
Must return a dictionary with keys as metric names and values as the data to be used for those metrics.
|
63
|
+
NOTE: that the data (for each metric) must be in the format expected by the torchmetrics instance (for that metric). E.g: {"accuracy": {"preds": [...], "target": [...]}, ...} since torchmetrics expects the data in a specific format.
|
96
64
|
"""
|
97
65
|
pass
|
98
66
|
|
@@ -102,63 +70,55 @@ class PerfCalc(ABC): # Abstract base class for performance calculation
|
|
102
70
|
"""
|
103
71
|
return outdict, []
|
104
72
|
|
73
|
+
def __valid_calc_custom_fields(self, fun_results):
|
74
|
+
if not isinstance(fun_results, tuple) or len(fun_results) != 2:
|
75
|
+
raise ValueError("Function must return a tuple (outdict, custom_fields)")
|
76
|
+
outdict, custom_fields = fun_results
|
77
|
+
if not isinstance(outdict, dict):
|
78
|
+
raise TypeError("Output must be a dictionary")
|
79
|
+
if not isinstance(custom_fields, list):
|
80
|
+
raise TypeError("Custom fields must be a list")
|
81
|
+
for field in custom_fields:
|
82
|
+
if not isinstance(field, str):
|
83
|
+
raise TypeError(f"Custom field '{field}' is not a string")
|
84
|
+
return outdict, custom_fields
|
85
|
+
|
86
|
+
# ! only need to override this method if torchmetrics are not used
|
87
|
+
def calc_exp_perf_metrics(self, metric_names, *args, **kwargs):
|
88
|
+
metrics_backend = self.get_metric_backend()
|
89
|
+
out_dict = {"dataset": self.get_dataset_name(), "experiment": self.get_experiment_name()}
|
90
|
+
out_dict, custom_fields = self.__valid_calc_custom_fields(self.calc_exp_outdict_custom_fields(
|
91
|
+
outdict=out_dict, *args, **kwargs
|
92
|
+
))
|
93
|
+
metrics_data_dict = self.prepare_metrics_data_dict(
|
94
|
+
metric_names, *args, **kwargs
|
95
|
+
)
|
96
|
+
metric_results = metrics_backend.calc_metrics(
|
97
|
+
metrics_data_dict=metrics_data_dict, *args, **kwargs
|
98
|
+
)
|
99
|
+
metric_results_prefix = {
|
100
|
+
f"metric_{k}": v for k, v in metric_results.items()
|
101
|
+
}
|
102
|
+
out_dict.update(metric_results_prefix)
|
103
|
+
ordered_cols = REQUIRED_COLS + custom_fields + list(metric_results_prefix.keys())
|
104
|
+
out_dict = OrderedDict(
|
105
|
+
(col, out_dict[col]) for col in ordered_cols if col in out_dict
|
106
|
+
)
|
107
|
+
return out_dict
|
108
|
+
|
105
109
|
#! custom kwargs:
|
106
110
|
#! outfile - if provided, will save the output to a CSV file with the given path
|
107
111
|
#! outdir - if provided, will save the output to a CSV file in the given directory with a generated filename
|
108
112
|
#! return_df - if True, will return a DataFrame instead of a dictionary
|
109
113
|
|
110
|
-
def
|
114
|
+
def calc_save_exp_perfs(self, *args, **kwargs):
|
111
115
|
"""
|
112
116
|
Calculate the metrics.
|
113
117
|
This function should be overridden by the subclass if needed.
|
114
118
|
Must return a dictionary with keys as metric names and values as the calculated metrics.
|
115
119
|
"""
|
116
|
-
metric_names =
|
117
|
-
out_dict =
|
118
|
-
out_dict['dataset'] = self.get_dataset_name()
|
119
|
-
out_dict['experiment'] = self.get_experiment_name()
|
120
|
-
out_dict, custom_fields = self.calc_exp_outdict_custom_fields(
|
121
|
-
outdict=out_dict, *args, **kwargs
|
122
|
-
)
|
123
|
-
torch_metrics_dict = self.get_exp_torch_metrics()
|
124
|
-
all_metric_data = self.prepare_exp_data_for_metrics(
|
125
|
-
metric_names, *args, **kwargs
|
126
|
-
)
|
127
|
-
metric_col_names = []
|
128
|
-
for metric in metric_names:
|
129
|
-
if metric not in all_metric_data:
|
130
|
-
raise ValueError(f"Metric '{metric}' not found in provided data.")
|
131
|
-
tmetric = torch_metrics_dict[metric] # torchmetrics instance
|
132
|
-
metric_data = all_metric_data[metric] # should be a dict of args/kwargs
|
133
|
-
# Inspect expected parameters for the metric's update() method
|
134
|
-
sig = inspect.signature(tmetric.update)
|
135
|
-
expected_args = list(sig.parameters.values())
|
136
|
-
# Prepare args in correct order
|
137
|
-
if isinstance(metric_data, dict):
|
138
|
-
# Match dict keys to parameter names
|
139
|
-
args = [metric_data[param.name] for param in expected_args]
|
140
|
-
elif isinstance(metric_data, (list, tuple)):
|
141
|
-
args = metric_data
|
142
|
-
else:
|
143
|
-
raise TypeError(f"Unsupported data format for metric '{metric}'")
|
144
|
-
|
145
|
-
# Call update and compute
|
146
|
-
if len(expected_args) == 1:
|
147
|
-
tmetric.update(args) # pass as single argument
|
148
|
-
else:
|
149
|
-
tmetric.update(*args) # unpack multiple arguments
|
150
|
-
computed_value = tmetric.compute()
|
151
|
-
# ensure the computed value converted to a scala value or list array
|
152
|
-
if isinstance(computed_value, torch.Tensor):
|
153
|
-
if computed_value.numel() == 1:
|
154
|
-
computed_value = computed_value.item()
|
155
|
-
else:
|
156
|
-
computed_value = computed_value.tolist()
|
157
|
-
col_name = f"metric_{metric}" if 'metric_' not in metric else metric
|
158
|
-
metric_col_names.append(col_name)
|
159
|
-
out_dict[col_name] = computed_value
|
160
|
-
|
161
|
-
# check if any kwargs named "outfile"
|
120
|
+
metric_names = self.get_metric_backend().metric_names
|
121
|
+
out_dict = self.calc_exp_perf_metrics(metric_names=metric_names, *args, **kwargs)
|
162
122
|
csv_outfile = kwargs.get("outfile", None)
|
163
123
|
if csv_outfile is not None:
|
164
124
|
filePathNoExt, _ = os.path.splitext(csv_outfile)
|
@@ -171,7 +131,8 @@ class PerfCalc(ABC): # Abstract base class for performance calculation
|
|
171
131
|
|
172
132
|
# convert out_dict to a DataFrame
|
173
133
|
df = pd.DataFrame([out_dict])
|
174
|
-
|
134
|
+
# get the orders of the columns as the orders or the keys in out_dict
|
135
|
+
ordered_cols = list(out_dict.keys())
|
175
136
|
df = df[ordered_cols] # reorder columns
|
176
137
|
|
177
138
|
if csv_outfile:
|
@@ -182,9 +143,17 @@ class PerfCalc(ABC): # Abstract base class for performance calculation
|
|
182
143
|
else:
|
183
144
|
return out_dict, csv_outfile
|
184
145
|
|
146
|
+
@staticmethod
|
147
|
+
def default_exp_csv_filter_fn(exp_file_name: str) -> bool:
|
148
|
+
"""
|
149
|
+
Default filter function for experiments.
|
150
|
+
Returns True if the experiment name does not start with "test_" or "debug_".
|
151
|
+
"""
|
152
|
+
return "__perf.csv" in exp_file_name
|
153
|
+
|
185
154
|
@classmethod
|
186
155
|
def gen_perf_report_for_multip_exps(
|
187
|
-
cls, indir: str,
|
156
|
+
cls, indir: str, exp_csv_filter_fn=default_exp_csv_filter_fn, csv_sep=";"
|
188
157
|
) -> PerfTB:
|
189
158
|
"""
|
190
159
|
Generate a performance report by scanning experiment subdirectories.
|
@@ -221,6 +190,9 @@ class PerfCalc(ABC): # Abstract base class for performance calculation
|
|
221
190
|
assert len(metric_cols) > 0, "No metric columns found in the DataFrame. Ensure that the CSV files contain metric columns starting with 'metric_'."
|
222
191
|
final_cols = REQUIRED_COLS + metric_cols
|
223
192
|
df = df[final_cols]
|
193
|
+
# !hahv debug
|
194
|
+
pprint('------ Final DataFrame Columns ------')
|
195
|
+
csvfile.fn_display_df(df)
|
224
196
|
# ! validate all rows in df before returning
|
225
197
|
# make sure all rows will have at least values for REQUIRED_COLS and at least one metric column
|
226
198
|
for index, row in df.iterrows():
|
@@ -289,12 +261,12 @@ class PerfCalc(ABC): # Abstract base class for performance calculation
|
|
289
261
|
]
|
290
262
|
if len(exp_dirs) == 0:
|
291
263
|
csv_perf_files = glob.glob(
|
292
|
-
os.path.join(indir, f"
|
264
|
+
os.path.join(indir, f"*.csv")
|
293
265
|
)
|
294
266
|
csv_perf_files = [
|
295
267
|
file_item
|
296
268
|
for file_item in csv_perf_files
|
297
|
-
if
|
269
|
+
if exp_csv_filter_fn(file_item)
|
298
270
|
]
|
299
271
|
else:
|
300
272
|
# multiple experiment directories found
|
@@ -302,33 +274,22 @@ class PerfCalc(ABC): # Abstract base class for performance calculation
|
|
302
274
|
for exp_dir in exp_dirs:
|
303
275
|
# pprint(f"Searching in experiment directory: {exp_dir}")
|
304
276
|
matched = glob.glob(
|
305
|
-
os.path.join(exp_dir, f"
|
277
|
+
os.path.join(exp_dir, f"*.csv")
|
306
278
|
)
|
279
|
+
matched = [
|
280
|
+
file_item
|
281
|
+
for file_item in matched
|
282
|
+
if exp_csv_filter_fn(file_item)
|
283
|
+
]
|
307
284
|
csv_perf_files.extend(matched)
|
308
285
|
|
309
286
|
assert (
|
310
287
|
len(csv_perf_files) > 0
|
311
|
-
), f"No CSV files matching pattern '{
|
288
|
+
), f"No CSV files matching pattern '{exp_csv_filter_fn}' found in the experiment directories."
|
312
289
|
|
313
|
-
assert len(csv_perf_files) > 0, f"No CSV files matching pattern '{
|
290
|
+
assert len(csv_perf_files) > 0, f"No CSV files matching pattern '{exp_csv_filter_fn}' found in the experiment directories."
|
314
291
|
|
315
292
|
all_exp_perf_df = get_df_for_all_exp_perf(csv_perf_files, csv_sep=csv_sep)
|
316
293
|
csvfile.fn_display_df(all_exp_perf_df)
|
317
294
|
perf_tb = mk_perftb_report(all_exp_perf_df)
|
318
295
|
return perf_tb
|
319
|
-
|
320
|
-
|
321
|
-
def main():
|
322
|
-
indir = "./zreport/test"
|
323
|
-
report_outfile = "./zreport/all.csv"
|
324
|
-
exp_perf_csv_pattern = "__perf"
|
325
|
-
csv_sep = ";"
|
326
|
-
perftb = PerfCalc.gen_perf_report_for_multip_exps(
|
327
|
-
indir, exp_perf_csv_pattern, csv_sep
|
328
|
-
)
|
329
|
-
perftb.to_csv(report_outfile, sep=csv_sep)
|
330
|
-
inspect(perftb)
|
331
|
-
perftb.plot(save_path="./zreport/test_csv.svg", open_plot=True)
|
332
|
-
|
333
|
-
if __name__ == "__main__":
|
334
|
-
main()
|
@@ -5,15 +5,17 @@ from ..system import filesys as fs
|
|
5
5
|
|
6
6
|
|
7
7
|
class VideoUtils:
|
8
|
+
|
8
9
|
@staticmethod
|
9
|
-
def
|
10
|
+
def _default_meta_extractor(video_path):
|
11
|
+
"""Default video metadata extractor function."""
|
10
12
|
# Open the video file
|
11
13
|
cap = cv2.VideoCapture(video_path)
|
12
14
|
|
13
15
|
# Check if the video was opened successfully
|
14
16
|
if not cap.isOpened():
|
15
17
|
print(f"Error: Could not open video file {video_path}")
|
16
|
-
return None
|
18
|
+
return None
|
17
19
|
|
18
20
|
# Get the frame count
|
19
21
|
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
@@ -23,6 +25,7 @@ class VideoUtils:
|
|
23
25
|
|
24
26
|
# Release the video capture object
|
25
27
|
cap.release()
|
28
|
+
|
26
29
|
meta_dict = {
|
27
30
|
"video_path": video_path,
|
28
31
|
"frame_count": frame_count,
|
@@ -31,6 +34,17 @@ class VideoUtils:
|
|
31
34
|
return meta_dict
|
32
35
|
|
33
36
|
@staticmethod
|
37
|
+
def get_video_meta_dict(video_path, meta_dict_extractor_func=None):
|
38
|
+
assert os.path.exists(video_path), f"Video file {video_path} does not exist"
|
39
|
+
if meta_dict_extractor_func and callable(meta_dict_extractor_func):
|
40
|
+
assert meta_dict_extractor_func.__code__.co_argcount == 1, "meta_dict_extractor_func must take exactly one argument (video_path)"
|
41
|
+
meta_dict = meta_dict_extractor_func(video_path)
|
42
|
+
assert isinstance(meta_dict, dict), "meta_dict_extractor_func must return a dictionary"
|
43
|
+
assert 'video_path' in meta_dict, "meta_dict must contain 'video_path'"
|
44
|
+
else:
|
45
|
+
meta_dict = VideoUtils._default_meta_extractor(video_path=video_path)
|
46
|
+
return meta_dict
|
47
|
+
@staticmethod
|
34
48
|
def get_video_dir_meta_df(video_dir, video_exts=['.mp4', '.avi', '.mov', '.mkv'], search_recursive=False, csv_outfile=None):
|
35
49
|
assert os.path.exists(video_dir), f"Video directory {video_dir} does not exist"
|
36
50
|
video_files = fs.filter_files_by_extension(video_dir, video_exts, recursive=search_recursive)
|
@@ -1,20 +1,62 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: halib
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.66
|
4
4
|
Summary: Small library for common tasks
|
5
5
|
Author: Hoang Van Ha
|
6
6
|
Author-email: hoangvanhauit@gmail.com
|
7
|
-
License: UNKNOWN
|
8
|
-
Platform: UNKNOWN
|
9
7
|
Classifier: Programming Language :: Python :: 3
|
10
8
|
Classifier: License :: OSI Approved :: MIT License
|
11
9
|
Classifier: Operating System :: OS Independent
|
12
10
|
Requires-Python: >=3.8
|
13
11
|
Description-Content-Type: text/markdown
|
14
12
|
License-File: LICENSE.txt
|
13
|
+
Requires-Dist: arrow
|
14
|
+
Requires-Dist: click
|
15
|
+
Requires-Dist: enlighten
|
16
|
+
Requires-Dist: kaleido==0.1.*
|
17
|
+
Requires-Dist: loguru
|
18
|
+
Requires-Dist: more-itertools
|
19
|
+
Requires-Dist: moviepy
|
20
|
+
Requires-Dist: networkx
|
21
|
+
Requires-Dist: numpy
|
22
|
+
Requires-Dist: omegaconf
|
23
|
+
Requires-Dist: opencv-python
|
24
|
+
Requires-Dist: pandas
|
25
|
+
Requires-Dist: Pillow
|
26
|
+
Requires-Dist: Pyarrow
|
27
|
+
Requires-Dist: pycurl
|
28
|
+
Requires-Dist: python-telegram-bot
|
29
|
+
Requires-Dist: requests
|
30
|
+
Requires-Dist: rich
|
31
|
+
Requires-Dist: scikit-learn
|
32
|
+
Requires-Dist: matplotlib
|
33
|
+
Requires-Dist: seaborn
|
34
|
+
Requires-Dist: plotly
|
35
|
+
Requires-Dist: pygwalker
|
36
|
+
Requires-Dist: tabulate
|
37
|
+
Requires-Dist: itables
|
38
|
+
Requires-Dist: timebudget
|
39
|
+
Requires-Dist: tqdm
|
40
|
+
Requires-Dist: tube_dl
|
41
|
+
Requires-Dist: wandb
|
42
|
+
Requires-Dist: dataclass-wizard
|
43
|
+
Dynamic: author
|
44
|
+
Dynamic: author-email
|
45
|
+
Dynamic: classifier
|
46
|
+
Dynamic: description
|
47
|
+
Dynamic: description-content-type
|
48
|
+
Dynamic: license-file
|
49
|
+
Dynamic: requires-dist
|
50
|
+
Dynamic: requires-python
|
51
|
+
Dynamic: summary
|
15
52
|
|
16
53
|
Helper package for coding and automation
|
17
54
|
|
55
|
+
**Version 0.1.66**
|
56
|
+
|
57
|
+
+ now use `uv` for venv management
|
58
|
+
+ `research/perfcalc`: support both torchmetrics and custom metrics for performance calculation
|
59
|
+
|
18
60
|
**Version 0.1.61**
|
19
61
|
|
20
62
|
+ add `util/video`: add `VideoUtils` class to handle common video-related tasks
|
@@ -146,5 +188,3 @@ New Features
|
|
146
188
|
New Features
|
147
189
|
|
148
190
|
+ add support to upload local to google drive.
|
149
|
-
|
150
|
-
|
halib-0.1.61/MANIFEST.in
DELETED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|