halib 0.1.91__py3-none-any.whl → 0.1.99__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.
halib/common.py CHANGED
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import re
2
3
  import rich
3
4
  import arrow
@@ -9,6 +10,8 @@ from rich import print
9
10
  from rich.panel import Panel
10
11
  from rich.console import Console
11
12
  from rich.pretty import pprint, Pretty
13
+ from pathlib import PureWindowsPath
14
+
12
15
 
13
16
  console = Console()
14
17
 
@@ -91,18 +94,58 @@ class ConsoleLog:
91
94
  print(f"Exception message: {exc_value}")
92
95
 
93
96
 
94
- def pprint_local_path(local_path, tag=""):
95
- # Create a file URI
96
- file_path = Path(local_path).resolve()
97
- is_file = file_path.is_file()
98
- is_dir = file_path.is_dir()
99
- type_str = "📄" if is_file else "📁" if is_dir else "❓"
100
- file_uri = file_path.as_uri()
97
+ def linux_to_wins_path(path: str) -> str:
98
+ """
99
+ Convert a Linux-style WSL path (/mnt/c/... or /mnt/d/...) to a Windows-style path (C:\...).
100
+ """
101
+ # Handle only /mnt/<drive>/... style
102
+ if (
103
+ path.startswith("/mnt/")
104
+ and len(path) > 6
105
+ and path[5].isalpha()
106
+ and path[6] == "/"
107
+ ):
108
+ drive = path[5].upper() # Extract drive letter
109
+ win_path = f"{drive}:{path[6:]}" # Replace "/mnt/c/" with "C:/"
110
+ else:
111
+ win_path = path # Return unchanged if not a WSL-style path
112
+ # Normalize to Windows-style backslashes
113
+ return str(PureWindowsPath(win_path))
114
+
115
+
116
+ def pprint_local_path(
117
+ local_path: str, get_wins_path: bool = False, tag: str = ""
118
+ ) -> str:
119
+ """
120
+ Pretty-print a local path with emoji and clickable file:// URI.
121
+
122
+ Args:
123
+ local_path: Path to file or directory (Linux or Windows style).
124
+ get_wins_path: If True on Linux, convert WSL-style path to Windows style before printing.
125
+ tag: Optional console log tag.
126
+
127
+ Returns:
128
+ The file URI string.
129
+ """
130
+ p = Path(local_path).resolve()
131
+ type_str = "📄" if p.is_file() else "📁" if p.is_dir() else "❓"
132
+
133
+ if get_wins_path and os.name == "posix":
134
+ # Try WSL → Windows conversion
135
+ converted = linux_to_wins_path(str(p))
136
+ if converted != str(p): # Conversion happened
137
+ file_uri = str(PureWindowsPath(converted).as_uri())
138
+ else:
139
+ file_uri = p.as_uri()
140
+ else:
141
+ file_uri = p.as_uri()
142
+
101
143
  content_str = f"{type_str} [link={file_uri}]{file_uri}[/link]"
102
- if isinstance(tag, str) and len(tag) > 0:
144
+
145
+ if tag:
103
146
  with ConsoleLog(tag):
104
147
  console.print(content_str)
105
148
  else:
106
- # If tag is not provided, just print the link
107
- console.print(f"{content_str}")
149
+ console.print(content_str)
150
+
108
151
  return file_uri
@@ -0,0 +1,63 @@
1
+ from contextlib import contextmanager
2
+ from pathlib import Path
3
+
4
+ import ipynbname
5
+
6
+ from ..common import console, now_str
7
+
8
+
9
+ @contextmanager
10
+ def gen_ipynb_name(
11
+ filename,
12
+ add_time_stamp=False,
13
+ nb_prefix="nb__",
14
+ separator="__",
15
+ ):
16
+ """
17
+ Context manager that prefixes the filename with the notebook name.
18
+ Output: NotebookName_OriginalName.ext
19
+ """
20
+ try:
21
+ nb_name = ipynbname.name()
22
+ except FileNotFoundError:
23
+ nb_name = "script" # Fallback
24
+
25
+ p = Path(filename)
26
+
27
+ # --- FIX START ---
28
+
29
+ # 1. Get the parts separately
30
+ original_stem = p.stem # "test" (no extension)
31
+ extension = p.suffix # ".csv"
32
+
33
+ now_string = now_str() if add_time_stamp else ""
34
+
35
+ # 2. Construct the base name (Notebook + Separator + OriginalName)
36
+ base_name = f"{nb_prefix}{nb_name}{separator}{original_stem}"
37
+
38
+ # 3. Append timestamp if needed
39
+ if now_string:
40
+ base_name = f"{base_name}{separator}{now_string}"
41
+
42
+ # 4. Add the extension at the VERY END
43
+ new_filename = f"{base_name}{extension}"
44
+
45
+ # --- FIX END ---
46
+
47
+ final_path = p.parent / new_filename
48
+
49
+ # Assuming you use 'rich' console based on your snippet
50
+ # console.rule()
51
+ # print(f"📝 Saving as: {final_path}")
52
+
53
+ yield str(final_path)
54
+
55
+
56
+ if __name__ == "__main__":
57
+ # --- Usage Example ---
58
+ # Assume Notebook Name is: "MyThesisWork"
59
+ filename = "results.csv"
60
+ with gen_ipynb_name(filename) as filename_ipynb:
61
+ # filename_ipynb is now: "MyThesisWork_results.csv"
62
+ print(f"File to save: {filename_ipynb}")
63
+ # df.to_csv(filename_ipynb)
@@ -6,6 +6,8 @@ from omegaconf import OmegaConf
6
6
  from rich.console import Console
7
7
  from argparse import ArgumentParser
8
8
 
9
+ from ..research.mics import *
10
+
9
11
  console = Console()
10
12
 
11
13
 
@@ -51,6 +53,27 @@ def load_yaml(yaml_file, to_dict=False, log_info=False):
51
53
  else:
52
54
  return omgconf
53
55
 
56
+ def load_yaml_with_PC_abbr(
57
+ yaml_file, pc_abbr_to_working_disk=DEFAULT_ABBR_WORKING_DISK
58
+ ):
59
+ # current PC abbreviation
60
+ pc_abbr = get_PC_abbr_name()
61
+
62
+ # current plaftform: windows or linux
63
+ current_platform = platform.system().lower()
64
+
65
+ assert pc_abbr in pc_abbr_to_working_disk, f"The is no mapping for {pc_abbr} to <working_disk>"
66
+
67
+ # working disk
68
+ working_disk = pc_abbr_to_working_disk.get(pc_abbr)
69
+
70
+ # load yaml file
71
+ data_dict = load_yaml(yaml_file=yaml_file, to_dict=True)
72
+
73
+ # Normalize paths in the loaded data
74
+ data_dict = normalize_paths(data_dict, working_disk, current_platform)
75
+ return data_dict
76
+
54
77
 
55
78
  def parse_args():
56
79
  parser = ArgumentParser(description="desc text")
halib/research/dataset.py CHANGED
@@ -4,18 +4,17 @@
4
4
 
5
5
  from argparse import ArgumentParser
6
6
 
7
- from rich import inspect
8
- from common import console, seed_everything, ConsoleLog
9
- from sklearn.model_selection import StratifiedShuffleSplit, ShuffleSplit
10
- from tqdm import tqdm
11
7
  import os
12
8
  import click
13
- from torchvision.datasets import ImageFolder
14
9
  import shutil
10
+ from tqdm import tqdm
11
+ from rich import inspect
15
12
  from rich.pretty import pprint
16
- from system import filesys as fs
17
- import glob
13
+ from torchvision.datasets import ImageFolder
14
+ from sklearn.model_selection import StratifiedShuffleSplit, ShuffleSplit
18
15
 
16
+ from ..system import filesys as fs
17
+ from ..common import console, seed_everything, ConsoleLog
19
18
 
20
19
  def parse_args():
21
20
  parser = ArgumentParser(description="desc text")
@@ -0,0 +1,34 @@
1
+ from halib import *
2
+ from flops import _calculate_flops_for_model
3
+
4
+ from halib import *
5
+ from argparse import ArgumentParser
6
+
7
+
8
+ def main():
9
+ csv_file = "./results-imagenet.csv"
10
+ df = pd.read_csv(csv_file)
11
+ # make param_count column as float
12
+ # df['param_count'] = df['param_count'].astype(float)
13
+ df['param_count'] = pd.to_numeric(df['param_count'], errors='coerce').fillna(99999).astype(float)
14
+ df = df[df['param_count'] < 5.0] # filter models with param_count < 20M
15
+
16
+ dict_ls = []
17
+
18
+ for index, row in tqdm(df.iterrows()):
19
+ console.rule(f"Row {index+1}/{len(df)}")
20
+ model = row['model']
21
+ num_class = 2
22
+ _, _, mflops = _calculate_flops_for_model(model, num_class)
23
+ dict_ls.append({'model': model, 'param_count': row['param_count'], 'mflops': mflops})
24
+
25
+ # Create a DataFrame from the list of dictionaries
26
+ result_df = pd.DataFrame(dict_ls)
27
+
28
+ final_df = pd.merge(df, result_df, on=['model', 'param_count'])
29
+ final_df.sort_values(by='mflops', inplace=True, ascending=True)
30
+ csvfile.fn_display_df(final_df)
31
+
32
+
33
+ if __name__ == "__main__":
34
+ main()
@@ -0,0 +1,156 @@
1
+ import os
2
+ import sys
3
+ import torch
4
+ import timm
5
+ from argparse import ArgumentParser
6
+ from fvcore.nn import FlopCountAnalysis
7
+ from halib import *
8
+ from halib.filetype import csvfile
9
+ from curriculum.utils.config import *
10
+ from curriculum.utils.model_helper import *
11
+
12
+
13
+ # ---------------------------------------------------------------------
14
+ # Argument Parser
15
+ # ---------------------------------------------------------------------
16
+ def parse_args():
17
+ parser = ArgumentParser(description="Calculate FLOPs for TIMM or trained models")
18
+
19
+ # Option 1: Direct TIMM model
20
+ parser.add_argument(
21
+ "--model_name", type=str, help="TIMM model name (e.g., efficientnet_b0)"
22
+ )
23
+ parser.add_argument(
24
+ "--num_classes", type=int, default=1000, help="Number of output classes"
25
+ )
26
+
27
+ # Option 2: Experiment directory
28
+ parser.add_argument(
29
+ "--indir",
30
+ type=str,
31
+ default=None,
32
+ help="Directory containing trained experiment (with .yaml and .pth)",
33
+ )
34
+ parser.add_argument(
35
+ "-o", "--o", action="store_true", help="Open output CSV after saving"
36
+ )
37
+ return parser.parse_args()
38
+
39
+
40
+ # ---------------------------------------------------------------------
41
+ # Helper Functions
42
+ # ---------------------------------------------------------------------
43
+ def _get_list_of_proc_dirs(indir):
44
+ assert os.path.exists(indir), f"Input directory {indir} does not exist."
45
+ pth_files = [f for f in os.listdir(indir) if f.endswith(".pth")]
46
+ if len(pth_files) > 0:
47
+ return [indir]
48
+ return [
49
+ os.path.join(indir, f)
50
+ for f in os.listdir(indir)
51
+ if os.path.isdir(os.path.join(indir, f))
52
+ ]
53
+
54
+
55
+ def _calculate_flops_for_model(model_name, num_classes):
56
+ """Calculate FLOPs for a plain TIMM model."""
57
+ try:
58
+ model = timm.create_model(model_name, pretrained=False, num_classes=num_classes)
59
+ input_size = timm.data.resolve_data_config(model.default_cfg)["input_size"]
60
+ dummy_input = torch.randn(1, *input_size)
61
+ model.eval() # ! set to eval mode to avoid some warnings or errors
62
+ flops = FlopCountAnalysis(model, dummy_input)
63
+ gflops = flops.total() / 1e9
64
+ mflops = flops.total() / 1e6
65
+ print(f"\nModel: **{model_name}**, Classes: {num_classes}")
66
+ print(f"Input size: {input_size}, FLOPs: **{gflops:.3f} GFLOPs**, **{mflops:.3f} MFLOPs**\n")
67
+ return model_name, gflops, mflops
68
+ except Exception as e:
69
+ print(f"[Error] Could not calculate FLOPs for {model_name}: {e}")
70
+ return model_name, -1, -1
71
+
72
+
73
+ def _calculate_flops_for_experiment(exp_dir):
74
+ """Calculate FLOPs for a trained experiment directory."""
75
+ yaml_files = [f for f in os.listdir(exp_dir) if f.endswith(".yaml")]
76
+ pth_files = [f for f in os.listdir(exp_dir) if f.endswith(".pth")]
77
+
78
+ assert (
79
+ len(yaml_files) == 1
80
+ ), f"Expected 1 YAML file in {exp_dir}, found {len(yaml_files)}"
81
+ assert (
82
+ len(pth_files) == 1
83
+ ), f"Expected 1 PTH file in {exp_dir}, found {len(pth_files)}"
84
+
85
+ exp_cfg_yaml = os.path.join(exp_dir, yaml_files[0])
86
+ cfg = ExpConfig.from_yaml(exp_cfg_yaml)
87
+ ds_label_list = cfg.dataset.get_label_list()
88
+
89
+ try:
90
+ model = build_model(
91
+ cfg.model.name, num_classes=len(ds_label_list), pretrained=True
92
+ )
93
+ model_weights_path = os.path.join(exp_dir, pth_files[0])
94
+ model.load_state_dict(torch.load(model_weights_path, map_location="cpu"))
95
+ model.eval()
96
+
97
+ input_size = timm.data.resolve_data_config(model.default_cfg)["input_size"]
98
+ dummy_input = torch.randn(1, *input_size)
99
+ flops = FlopCountAnalysis(model, dummy_input)
100
+ gflops = flops.total() / 1e9
101
+ mflops = flops.total() / 1e6
102
+
103
+ return str(cfg), cfg.model.name, gflops, mflops
104
+ except Exception as e:
105
+ console.print(f"[red] Error processing {exp_dir}: {e}[/red]")
106
+ return str(cfg), cfg.model.name, -1, -1
107
+
108
+
109
+ # ---------------------------------------------------------------------
110
+ # Main Entry
111
+ # ---------------------------------------------------------------------
112
+ def main():
113
+ args = parse_args()
114
+
115
+ # Case 1: Direct TIMM model input
116
+ if args.model_name:
117
+ _calculate_flops_for_model(args.model_name, args.num_classes)
118
+ return
119
+
120
+ # Case 2: Experiment directory input
121
+ if args.indir is None:
122
+ print("[Error] Either --model_name or --indir must be specified.")
123
+ return
124
+
125
+ proc_dirs = _get_list_of_proc_dirs(args.indir)
126
+ pprint(proc_dirs)
127
+
128
+ dfmk = csvfile.DFCreator()
129
+ TABLE_NAME = "model_flops_results"
130
+ dfmk.create_table(TABLE_NAME, ["exp_name", "model_name", "gflops", "mflops"])
131
+
132
+ console.rule(f"Calculating FLOPs for models in {len(proc_dirs)} dir(s)...")
133
+ rows = []
134
+ for exp_dir in tqdm(proc_dirs):
135
+ dir_name = os.path.basename(exp_dir)
136
+ console.rule(f"{dir_name}")
137
+ exp_name, model_name, gflops, mflops = _calculate_flops_for_experiment(exp_dir)
138
+ rows.append([exp_name, model_name, gflops, mflops])
139
+
140
+ dfmk.insert_rows(TABLE_NAME, rows)
141
+ dfmk.fill_table_from_row_pool(TABLE_NAME)
142
+
143
+ outfile = f"zout/zreport/{now_str()}_model_flops_results.csv"
144
+ dfmk[TABLE_NAME].to_csv(outfile, sep=";", index=False)
145
+ csvfile.fn_display_df(dfmk[TABLE_NAME])
146
+
147
+ if args.o:
148
+ os.system(f"start {outfile}")
149
+
150
+
151
+ # ---------------------------------------------------------------------
152
+ # Script Entry
153
+ # ---------------------------------------------------------------------
154
+ if __name__ == "__main__":
155
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
156
+ main()
halib/research/mics.py CHANGED
@@ -1,5 +1,9 @@
1
+ from ..common import *
2
+ from ..filetype import csvfile
3
+ import pandas as pd
1
4
  import platform
2
5
 
6
+
3
7
  PC_NAME_TO_ABBR = {
4
8
  "DESKTOP-JQD9K01": "MainPC",
5
9
  "DESKTOP-5IRHU87": "MSI_Laptop",
@@ -8,9 +12,57 @@ PC_NAME_TO_ABBR = {
8
12
  "DESKTOP-QNS3DNF": "1GPU_SV"
9
13
  }
10
14
 
15
+ DEFAULT_ABBR_WORKING_DISK = {
16
+ "MainPC": "E:",
17
+ "MSI_Laptop": "D:",
18
+ "4090_SV": "E:",
19
+ "4GPU_SV": "D:",
20
+ }
21
+
22
+ def list_PCs(show=True):
23
+ df = pd.DataFrame(list(PC_NAME_TO_ABBR.items()), columns=["PC Name", "Abbreviation"])
24
+ if show:
25
+ csvfile.fn_display_df(df)
26
+ return df
27
+
11
28
  def get_PC_name():
12
29
  return platform.node()
13
30
 
14
31
  def get_PC_abbr_name():
15
32
  pc_name = get_PC_name()
16
33
  return PC_NAME_TO_ABBR.get(pc_name, "Unknown")
34
+
35
+ # ! This funcction search for full paths in the obj and normalize them according to the current platform and working disk
36
+ # ! E.g: "E:/zdataset/DFire", but working_disk: "D:", current_platform: "windows" => "D:/zdataset/DFire"
37
+ # ! E.g: "E:/zdataset/DFire", but working_disk: "D:", current_platform: "linux" => "/mnt/d/zdataset/DFire"
38
+ def normalize_paths(obj, working_disk, current_platform):
39
+ if isinstance(obj, dict):
40
+ for key, value in obj.items():
41
+ obj[key] = normalize_paths(value, working_disk, current_platform)
42
+ return obj
43
+ elif isinstance(obj, list):
44
+ for i, item in enumerate(obj):
45
+ obj[i] = normalize_paths(item, working_disk, current_platform)
46
+ return obj
47
+ elif isinstance(obj, str):
48
+ # Normalize backslashes to forward slashes for consistency
49
+ obj = obj.replace("\\", "/")
50
+ # Regex for Windows-style path: e.g., "E:/zdataset/DFire"
51
+ win_match = re.match(r"^([A-Z]):/(.*)$", obj)
52
+ # Regex for Linux-style path: e.g., "/mnt/e/zdataset/DFire"
53
+ lin_match = re.match(r"^/mnt/([a-z])/(.*)$", obj)
54
+ if win_match or lin_match:
55
+ rest = win_match.group(2) if win_match else lin_match.group(2)
56
+ if current_platform == "windows":
57
+ # working_disk is like "D:", so "D:/" + rest
58
+ new_path = working_disk + "/" + rest
59
+ elif current_platform == "linux":
60
+ # Extract drive letter from working_disk (e.g., "D:" -> "d")
61
+ drive_letter = working_disk[0].lower()
62
+ new_path = "/mnt/" + drive_letter + "/" + rest
63
+ else:
64
+ # Unknown platform, return original
65
+ return obj
66
+ return new_path
67
+ # For non-strings or non-path strings, return as is
68
+ return obj
@@ -227,9 +227,9 @@ class PerfCalc(ABC): # Abstract base class for performance calculation
227
227
  ), "No metric columns found in the DataFrame. Ensure that the CSV files contain metric columns starting with 'metric_'."
228
228
  final_cols = sticky_cols + metric_cols
229
229
  df = df[final_cols]
230
- # !hahv debug
231
- pprint("------ Final DataFrame Columns ------")
232
- csvfile.fn_display_df(df)
230
+ # # !hahv debug
231
+ # pprint("------ Final DataFrame Columns ------")
232
+ # csvfile.fn_display_df(df)
233
233
  # ! validate all rows in df before returning
234
234
  # make sure all rows will have at least values for REQUIRED_COLS and at least one metric column
235
235
  for index, row in df.iterrows():
halib/research/perftb.py CHANGED
@@ -308,7 +308,8 @@ class PerfTB:
308
308
  if save_path:
309
309
  export_success = False
310
310
  try:
311
- fig.write_image(save_path, engine="kaleido")
311
+ # fig.write_image(save_path, engine="kaleido")
312
+ fig.write_image(save_path, engine="kaleido", width=width, height=height * len(metric_list))
312
313
  export_success = True
313
314
  # pprint(f"Saved: {os.path.abspath(save_path)}")
314
315
  except Exception as e: