haoline 0.3.0__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.
Files changed (70) hide show
  1. haoline/.streamlit/config.toml +10 -0
  2. haoline/__init__.py +248 -0
  3. haoline/analyzer.py +935 -0
  4. haoline/cli.py +2712 -0
  5. haoline/compare.py +811 -0
  6. haoline/compare_visualizations.py +1564 -0
  7. haoline/edge_analysis.py +525 -0
  8. haoline/eval/__init__.py +131 -0
  9. haoline/eval/adapters.py +844 -0
  10. haoline/eval/cli.py +390 -0
  11. haoline/eval/comparison.py +542 -0
  12. haoline/eval/deployment.py +633 -0
  13. haoline/eval/schemas.py +833 -0
  14. haoline/examples/__init__.py +15 -0
  15. haoline/examples/basic_inspection.py +74 -0
  16. haoline/examples/compare_models.py +117 -0
  17. haoline/examples/hardware_estimation.py +78 -0
  18. haoline/format_adapters.py +1001 -0
  19. haoline/formats/__init__.py +123 -0
  20. haoline/formats/coreml.py +250 -0
  21. haoline/formats/gguf.py +483 -0
  22. haoline/formats/openvino.py +255 -0
  23. haoline/formats/safetensors.py +273 -0
  24. haoline/formats/tflite.py +369 -0
  25. haoline/hardware.py +2307 -0
  26. haoline/hierarchical_graph.py +462 -0
  27. haoline/html_export.py +1573 -0
  28. haoline/layer_summary.py +769 -0
  29. haoline/llm_summarizer.py +465 -0
  30. haoline/op_icons.py +618 -0
  31. haoline/operational_profiling.py +1492 -0
  32. haoline/patterns.py +1116 -0
  33. haoline/pdf_generator.py +265 -0
  34. haoline/privacy.py +250 -0
  35. haoline/pydantic_models.py +241 -0
  36. haoline/report.py +1923 -0
  37. haoline/report_sections.py +539 -0
  38. haoline/risks.py +521 -0
  39. haoline/schema.py +523 -0
  40. haoline/streamlit_app.py +2024 -0
  41. haoline/tests/__init__.py +4 -0
  42. haoline/tests/conftest.py +123 -0
  43. haoline/tests/test_analyzer.py +868 -0
  44. haoline/tests/test_compare_visualizations.py +293 -0
  45. haoline/tests/test_edge_analysis.py +243 -0
  46. haoline/tests/test_eval.py +604 -0
  47. haoline/tests/test_format_adapters.py +460 -0
  48. haoline/tests/test_hardware.py +237 -0
  49. haoline/tests/test_hardware_recommender.py +90 -0
  50. haoline/tests/test_hierarchical_graph.py +326 -0
  51. haoline/tests/test_html_export.py +180 -0
  52. haoline/tests/test_layer_summary.py +428 -0
  53. haoline/tests/test_llm_patterns.py +540 -0
  54. haoline/tests/test_llm_summarizer.py +339 -0
  55. haoline/tests/test_patterns.py +774 -0
  56. haoline/tests/test_pytorch.py +327 -0
  57. haoline/tests/test_report.py +383 -0
  58. haoline/tests/test_risks.py +398 -0
  59. haoline/tests/test_schema.py +417 -0
  60. haoline/tests/test_tensorflow.py +380 -0
  61. haoline/tests/test_visualizations.py +316 -0
  62. haoline/universal_ir.py +856 -0
  63. haoline/visualizations.py +1086 -0
  64. haoline/visualize_yolo.py +44 -0
  65. haoline/web.py +110 -0
  66. haoline-0.3.0.dist-info/METADATA +471 -0
  67. haoline-0.3.0.dist-info/RECORD +70 -0
  68. haoline-0.3.0.dist-info/WHEEL +4 -0
  69. haoline-0.3.0.dist-info/entry_points.txt +5 -0
  70. haoline-0.3.0.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,123 @@
1
+ # Copyright (c) 2025 HaoLine Contributors
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """
5
+ Format adapters for various model file formats.
6
+
7
+ Each adapter provides read (and optionally write) capabilities
8
+ for a specific model format, extracting metadata and tensor info.
9
+
10
+ Supported formats:
11
+ - GGUF: llama.cpp models (pure Python, no deps)
12
+ - SafeTensors: HuggingFace models (requires safetensors)
13
+ - TFLite: TensorFlow Lite models (pure Python header parsing)
14
+ - CoreML: Apple ML models (requires coremltools)
15
+ - OpenVINO: Intel models (requires openvino)
16
+ """
17
+
18
+ from .coreml import (
19
+ CoreMLInfo,
20
+ CoreMLReader,
21
+ is_coreml_file,
22
+ )
23
+ from .coreml import (
24
+ is_available as coreml_available,
25
+ )
26
+ from .gguf import GGUFInfo, GGUFReader, is_gguf_file
27
+ from .openvino import (
28
+ OpenVINOInfo,
29
+ OpenVINOReader,
30
+ is_openvino_file,
31
+ )
32
+ from .openvino import (
33
+ is_available as openvino_available,
34
+ )
35
+ from .safetensors import (
36
+ SafeTensorsInfo,
37
+ SafeTensorsReader,
38
+ is_safetensors_file,
39
+ )
40
+ from .safetensors import (
41
+ is_available as safetensors_available,
42
+ )
43
+ from .tflite import (
44
+ TFLiteInfo,
45
+ TFLiteReader,
46
+ is_tflite_file,
47
+ )
48
+ from .tflite import (
49
+ is_available as tflite_available,
50
+ )
51
+
52
+ __all__ = [
53
+ # GGUF (Epic 24)
54
+ "GGUFReader",
55
+ "GGUFInfo",
56
+ "is_gguf_file",
57
+ # SafeTensors (Epic 19)
58
+ "SafeTensorsReader",
59
+ "SafeTensorsInfo",
60
+ "is_safetensors_file",
61
+ "safetensors_available",
62
+ # TFLite (Epic 21)
63
+ "TFLiteReader",
64
+ "TFLiteInfo",
65
+ "is_tflite_file",
66
+ "tflite_available",
67
+ # CoreML (Epic 20)
68
+ "CoreMLReader",
69
+ "CoreMLInfo",
70
+ "is_coreml_file",
71
+ "coreml_available",
72
+ # OpenVINO (Epic 23)
73
+ "OpenVINOReader",
74
+ "OpenVINOInfo",
75
+ "is_openvino_file",
76
+ "openvino_available",
77
+ ]
78
+
79
+
80
+ def detect_format(path: str) -> str | None:
81
+ """
82
+ Auto-detect model format from file.
83
+
84
+ Args:
85
+ path: Path to model file.
86
+
87
+ Returns:
88
+ Format name ('gguf', 'safetensors', 'tflite', 'coreml', 'openvino', 'onnx')
89
+ or None if unknown.
90
+ """
91
+ from pathlib import Path as P
92
+
93
+ p = P(path)
94
+
95
+ # Check by extension first
96
+ suffix = p.suffix.lower()
97
+ if suffix == ".onnx":
98
+ return "onnx"
99
+ if suffix == ".gguf":
100
+ return "gguf"
101
+ if suffix == ".safetensors":
102
+ return "safetensors"
103
+ if suffix == ".tflite":
104
+ return "tflite"
105
+ if suffix in (".mlmodel", ".mlpackage"):
106
+ return "coreml"
107
+ if suffix == ".xml":
108
+ if is_openvino_file(path):
109
+ return "openvino"
110
+
111
+ # Check by magic bytes
112
+ if is_gguf_file(path):
113
+ return "gguf"
114
+ if is_safetensors_file(path):
115
+ return "safetensors"
116
+ if is_tflite_file(path):
117
+ return "tflite"
118
+ if is_coreml_file(path):
119
+ return "coreml"
120
+ if is_openvino_file(path):
121
+ return "openvino"
122
+
123
+ return None
@@ -0,0 +1,250 @@
1
+ # Copyright (c) 2025 HaoLine Contributors
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """
5
+ CoreML format reader.
6
+
7
+ CoreML is Apple's machine learning framework for iOS, macOS, watchOS,
8
+ and tvOS. This reader extracts model metadata and layer information.
9
+
10
+ Requires: coremltools (pip install coremltools)
11
+
12
+ Reference: https://coremltools.readme.io/
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass, field
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+
22
+ @dataclass
23
+ class CoreMLLayerInfo:
24
+ """Information about a CoreML layer."""
25
+
26
+ name: str
27
+ type: str
28
+ inputs: list[str] = field(default_factory=list)
29
+ outputs: list[str] = field(default_factory=list)
30
+
31
+
32
+ @dataclass
33
+ class CoreMLInfo:
34
+ """Parsed CoreML model information."""
35
+
36
+ path: Path
37
+ spec_version: int
38
+ description: str
39
+ author: str
40
+ license: str
41
+ layers: list[CoreMLLayerInfo] = field(default_factory=list)
42
+ inputs: list[dict[str, Any]] = field(default_factory=list)
43
+ outputs: list[dict[str, Any]] = field(default_factory=list)
44
+ metadata: dict[str, Any] = field(default_factory=dict)
45
+
46
+ @property
47
+ def layer_count(self) -> int:
48
+ """Number of layers."""
49
+ return len(self.layers)
50
+
51
+ @property
52
+ def layer_type_counts(self) -> dict[str, int]:
53
+ """Count of layers by type."""
54
+ counts: dict[str, int] = {}
55
+ for layer in self.layers:
56
+ counts[layer.type] = counts.get(layer.type, 0) + 1
57
+ return counts
58
+
59
+ @property
60
+ def model_type(self) -> str:
61
+ """Detected model type."""
62
+ return str(self.metadata.get("model_type", "unknown"))
63
+
64
+ def to_dict(self) -> dict[str, Any]:
65
+ """Convert to dictionary for JSON serialization."""
66
+ return {
67
+ "path": str(self.path),
68
+ "spec_version": self.spec_version,
69
+ "description": self.description,
70
+ "author": self.author,
71
+ "license": self.license,
72
+ "model_type": self.model_type,
73
+ "layer_count": self.layer_count,
74
+ "layer_type_counts": self.layer_type_counts,
75
+ "inputs": self.inputs,
76
+ "outputs": self.outputs,
77
+ "metadata": self.metadata,
78
+ }
79
+
80
+
81
+ class CoreMLReader:
82
+ """Reader for CoreML format files (.mlmodel, .mlpackage)."""
83
+
84
+ def __init__(self, path: str | Path):
85
+ """
86
+ Initialize reader with file path.
87
+
88
+ Args:
89
+ path: Path to the CoreML model (.mlmodel or .mlpackage).
90
+
91
+ Raises:
92
+ ImportError: If coremltools is not installed.
93
+ """
94
+ self.path = Path(path)
95
+ if not self.path.exists():
96
+ raise FileNotFoundError(f"CoreML model not found: {self.path}")
97
+
98
+ try:
99
+ import coremltools # noqa: F401
100
+ except ImportError as e:
101
+ raise ImportError("coremltools required. Install with: pip install coremltools") from e
102
+
103
+ def read(self) -> CoreMLInfo:
104
+ """
105
+ Read and parse the CoreML model.
106
+
107
+ Returns:
108
+ CoreMLInfo with model metadata.
109
+ """
110
+ import coremltools as ct
111
+
112
+ # Load model
113
+ model = ct.models.MLModel(str(self.path))
114
+ spec = model.get_spec()
115
+
116
+ # Extract description
117
+ desc = spec.description
118
+ description = desc.metadata.shortDescription or ""
119
+ author = desc.metadata.author or ""
120
+ license_info = desc.metadata.license or ""
121
+
122
+ # Extract inputs
123
+ inputs = []
124
+ for inp in desc.input:
125
+ input_info = {"name": inp.name, "type": self._get_feature_type(inp.type)}
126
+ inputs.append(input_info)
127
+
128
+ # Extract outputs
129
+ outputs = []
130
+ for out in desc.output:
131
+ output_info = {"name": out.name, "type": self._get_feature_type(out.type)}
132
+ outputs.append(output_info)
133
+
134
+ # Extract layers based on model type
135
+ layers = []
136
+ metadata = {}
137
+
138
+ if spec.HasField("neuralNetwork"):
139
+ nn = spec.neuralNetwork
140
+ metadata["model_type"] = "neuralNetwork"
141
+ for layer in nn.layers:
142
+ layers.append(
143
+ CoreMLLayerInfo(
144
+ name=layer.name,
145
+ type=layer.WhichOneof("layer"),
146
+ inputs=list(layer.input),
147
+ outputs=list(layer.output),
148
+ )
149
+ )
150
+ elif spec.HasField("neuralNetworkClassifier"):
151
+ nn = spec.neuralNetworkClassifier
152
+ metadata["model_type"] = "neuralNetworkClassifier"
153
+ for layer in nn.layers:
154
+ layers.append(
155
+ CoreMLLayerInfo(
156
+ name=layer.name,
157
+ type=layer.WhichOneof("layer"),
158
+ inputs=list(layer.input),
159
+ outputs=list(layer.output),
160
+ )
161
+ )
162
+ elif spec.HasField("neuralNetworkRegressor"):
163
+ nn = spec.neuralNetworkRegressor
164
+ metadata["model_type"] = "neuralNetworkRegressor"
165
+ for layer in nn.layers:
166
+ layers.append(
167
+ CoreMLLayerInfo(
168
+ name=layer.name,
169
+ type=layer.WhichOneof("layer"),
170
+ inputs=list(layer.input),
171
+ outputs=list(layer.output),
172
+ )
173
+ )
174
+ elif spec.HasField("mlProgram"):
175
+ metadata["model_type"] = "mlProgram"
176
+ # ML Programs have a different structure
177
+ # Basic info only for now
178
+ elif spec.HasField("pipeline"):
179
+ metadata["model_type"] = "pipeline"
180
+ else:
181
+ metadata["model_type"] = "other"
182
+
183
+ return CoreMLInfo(
184
+ path=self.path,
185
+ spec_version=spec.specificationVersion,
186
+ description=description,
187
+ author=author,
188
+ license=license_info,
189
+ layers=layers,
190
+ inputs=inputs,
191
+ outputs=outputs,
192
+ metadata=metadata,
193
+ )
194
+
195
+ def _get_feature_type(self, feature_type) -> str:
196
+ """Get human-readable feature type."""
197
+ type_name = feature_type.WhichOneof("Type")
198
+ if type_name == "multiArrayType":
199
+ shape = list(feature_type.multiArrayType.shape)
200
+ dtype = feature_type.multiArrayType.dataType
201
+ dtype_names = {0: "INVALID", 65568: "FLOAT32", 65600: "FLOAT64", 131104: "INT32"}
202
+ return f"MultiArray({dtype_names.get(dtype, dtype)}, {shape})"
203
+ elif type_name == "imageType":
204
+ w = feature_type.imageType.width
205
+ h = feature_type.imageType.height
206
+ return f"Image({w}x{h})"
207
+ elif type_name == "dictionaryType":
208
+ return "Dictionary"
209
+ elif type_name == "stringType":
210
+ return "String"
211
+ elif type_name == "int64Type":
212
+ return "Int64"
213
+ elif type_name == "doubleType":
214
+ return "Double"
215
+ else:
216
+ return type_name or "unknown"
217
+
218
+
219
+ def is_coreml_file(path: str | Path) -> bool:
220
+ """
221
+ Check if a file is a CoreML model.
222
+
223
+ Args:
224
+ path: Path to check.
225
+
226
+ Returns:
227
+ True if the file is a CoreML model.
228
+ """
229
+ path = Path(path)
230
+ if not path.exists():
231
+ return False
232
+
233
+ # Check extension
234
+ suffix = path.suffix.lower()
235
+ if suffix == ".mlmodel":
236
+ return True
237
+ if suffix == ".mlpackage" or (path.is_dir() and path.suffix == ".mlpackage"):
238
+ return True
239
+
240
+ return False
241
+
242
+
243
+ def is_available() -> bool:
244
+ """Check if coremltools is available."""
245
+ try:
246
+ import coremltools # noqa: F401
247
+
248
+ return True
249
+ except ImportError:
250
+ return False