diff --git a/.github/scripts/check_pyrefly_coverage.py b/.github/scripts/check_pyrefly_coverage.py new file mode 100755 index 00000000..60ba1bf5 --- /dev/null +++ b/.github/scripts/check_pyrefly_coverage.py @@ -0,0 +1,79 @@ +import json +import pprint +import sys +from argparse import ArgumentParser +from difflib import unified_diff +from pathlib import Path + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument( + "--baseline_report_path", + type=str, + required=False, + default=".pyrefly-baseline-report.json", + ) + parser.add_argument( + "--current_report_path", + type=str, + required=False, + default="pyrefly-current-report.json", + ) + args = parser.parse_args() + baseline = json.loads(Path(args.baseline_report_path).read_text()) + current = json.loads(Path(args.current_report_path).read_text()) + + baseline_reports = {} + current_reports = {} + + for report in baseline["module_reports"]: + baseline_reports[report["name"]] = report + + for report in current["module_reports"]: + current_reports[report["name"]] = report + + failures = [] + + for module_name, current_module_report in current_reports.items(): + baseline_module_report = baseline_reports.get(module_name) + + # File does not exist in baseline yet + if baseline_module_report is None: + completeness = current_module_report["coverage"] + + if completeness < 100: + failures.append( + f"New file {module_name} is only " f"{completeness:.1f}% annotated" + ) + continue + + old_n_untyped = baseline_module_report["n_untyped"] + new_n_untyped = current_module_report["n_untyped"] + + if new_n_untyped > old_n_untyped: + dict1_lines = pprint.pformat( + baseline_module_report, sort_dicts=True + ).splitlines() + dict2_lines = pprint.pformat( + current_module_report, sort_dicts=True + ).splitlines() + + diff = unified_diff( + dict1_lines, dict2_lines, fromfile="dict1", tofile="dict2", lineterm="" + ) + + failures.append( + f"{module_name}: Untyped count increased " + f"from {old_n_untyped} to {new_n_untyped}\n" + f"\n{'\n'.join(diff)}" + ) + + if failures: + print("Pyrefly coverage regression detected:") + + for failure in failures: + print(f"- {failure}\n\n") + + sys.exit(1) + + print("No pyrefly coverage regressions detected.") diff --git a/.github/workflows/pyrefly_type_coverage.yaml b/.github/workflows/pyrefly_type_coverage.yaml new file mode 100644 index 00000000..a8a7699b --- /dev/null +++ b/.github/workflows/pyrefly_type_coverage.yaml @@ -0,0 +1,33 @@ +name: Pyrefly Coverage Check + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + check-type-coverage: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pyrefly + + - name: Generate current Pyrefly coverage report + run: | + pyrefly report > pyrefly-current-report.json + + - name: Compare against baseline + run: | + python .github/scripts/check_pyrefly_coverage.py diff --git a/.pyrefly-baseline-report.json b/.pyrefly-baseline-report.json new file mode 100644 index 00000000..68709f58 --- /dev/null +++ b/.pyrefly-baseline-report.json @@ -0,0 +1,3582 @@ +{ + "module_reports": [ + { + "name": "pywt._cwt", + "names": [ + "pywt._cwt.next_fast_len", + "pywt._cwt.cwt" + ], + "line_count": 241, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt._cwt.next_fast_len", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 19, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._cwt.cwt", + "n_typable": 8, + "n_typed": 5, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 28, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 10, + "n_typed": 7, + "n_any": 3, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 70.0, + "n_functions": 2, + "n_methods": 0, + "n_function_params": 8, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._thresholding", + "names": [ + "pywt._thresholding.thresholding_options", + "pywt._thresholding.soft", + "pywt._thresholding.nn_garrote", + "pywt._thresholding.hard", + "pywt._thresholding.greater", + "pywt._thresholding.less", + "pywt._thresholding.threshold", + "pywt._thresholding.threshold_firm" + ], + "line_count": 264, + "symbol_reports": [ + { + "kind": "attr", + "name": "pywt._thresholding.thresholding_options", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 78, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._thresholding.soft", + "n_typable": 4, + "n_typed": 2, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 18, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._thresholding.nn_garrote", + "n_typable": 4, + "n_typed": 2, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 38, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._thresholding.hard", + "n_typable": 4, + "n_typed": 2, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 58, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._thresholding.greater", + "n_typable": 4, + "n_typed": 2, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 64, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._thresholding.less", + "n_typable": 4, + "n_typed": 2, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 71, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._thresholding.threshold", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 88, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._thresholding.threshold_firm", + "n_typable": 4, + "n_typed": 2, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 185, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 29, + "n_typed": 15, + "n_any": 14, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 51.724137931034484, + "n_functions": 7, + "n_methods": 0, + "n_function_params": 22, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 1, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._multidim", + "names": [ + "pywt._multidim.dwt2", + "pywt._multidim.dwtn", + "pywt._multidim.idwt2", + "pywt._multidim.idwtn" + ], + "line_count": 303, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt._multidim.dwt2", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 23, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multidim.dwtn", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 120, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multidim.idwt2", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 76, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multidim.idwtn", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 216, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 20, + "n_typed": 12, + "n_any": 8, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 60.0, + "n_functions": 4, + "n_methods": 0, + "n_function_params": 16, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._extensions._swt", + "names": [ + "pywt._extensions._swt.swt_max_level", + "pywt._extensions._swt.swt", + "pywt._extensions._swt.swt", + "pywt._extensions._swt.swt", + "pywt._extensions._swt.swt_axis", + "pywt._extensions._swt.swt_axis", + "pywt._extensions._swt.swt_axis" + ], + "line_count": 25, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt._extensions._swt.swt_max_level", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 12, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._swt.swt", + "n_typable": 6, + "n_typed": 4, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 13, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._swt.swt", + "n_typable": 6, + "n_typed": 4, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 15, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._swt.swt", + "n_typable": 6, + "n_typed": 4, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 17, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._swt.swt_axis", + "n_typable": 7, + "n_typed": 5, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 19, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._swt.swt_axis", + "n_typable": 7, + "n_typed": 5, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 21, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._swt.swt_axis", + "n_typable": 7, + "n_typed": 5, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 23, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 41, + "n_typed": 29, + "n_any": 12, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 70.73170731707317, + "n_functions": 7, + "n_methods": 0, + "n_function_params": 34, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt", + "names": [ + "pywt.test" + ], + "line_count": 46, + "symbol_reports": [ + { + "kind": "attr", + "name": "pywt.test", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 44, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "coverage": 0.0, + "strict_coverage": 0.0, + "n_functions": 0, + "n_methods": 0, + "n_function_params": 0, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 1, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._wavelet_packets", + "names": [ + "pywt._wavelet_packets.BaseNode.parent", + "pywt._wavelet_packets.BaseNode.wavelet", + "pywt._wavelet_packets.BaseNode.mode", + "pywt._wavelet_packets.BaseNode.level", + "pywt._wavelet_packets.BaseNode.path", + "pywt._wavelet_packets.BaseNode.axes", + "pywt._wavelet_packets.BaseNode.data", + "pywt._wavelet_packets.NodeND.PART_LEN", + "pywt._wavelet_packets.NodeND.PARTS", + "pywt._wavelet_packets.NodeND.ndim", + "pywt._wavelet_packets.NodeND.ndim_transform", + "pywt._wavelet_packets.WaveletPacket.wavelet", + "pywt._wavelet_packets.WaveletPacket.mode", + "pywt._wavelet_packets.WaveletPacket.axes", + "pywt._wavelet_packets.WaveletPacket.data_size", + "pywt._wavelet_packets.WaveletPacket2D.wavelet", + "pywt._wavelet_packets.WaveletPacket2D.mode", + "pywt._wavelet_packets.WaveletPacket2D.axes", + "pywt._wavelet_packets.WaveletPacket2D.data_size", + "pywt._wavelet_packets.WaveletPacketND.wavelet", + "pywt._wavelet_packets.WaveletPacketND.mode", + "pywt._wavelet_packets.WaveletPacketND.axes", + "pywt._wavelet_packets.WaveletPacketND.ndim_transform", + "pywt._wavelet_packets.WaveletPacketND.data_size", + "pywt._wavelet_packets.get_graycode_order", + "pywt._wavelet_packets.BaseNode.__init__", + "pywt._wavelet_packets.BaseNode.path_tuple", + "pywt._wavelet_packets.BaseNode.maxlevel", + "pywt._wavelet_packets.BaseNode.node_name", + "pywt._wavelet_packets.BaseNode.decompose", + "pywt._wavelet_packets.BaseNode.reconstruct", + "pywt._wavelet_packets.BaseNode.get_subnode", + "pywt._wavelet_packets.BaseNode.__getitem__", + "pywt._wavelet_packets.BaseNode.__setitem__", + "pywt._wavelet_packets.BaseNode.__delitem__", + "pywt._wavelet_packets.BaseNode.is_empty", + "pywt._wavelet_packets.BaseNode.has_any_subnode", + "pywt._wavelet_packets.BaseNode.get_leaf_nodes", + "pywt._wavelet_packets.BaseNode.walk", + "pywt._wavelet_packets.BaseNode.walk_depth", + "pywt._wavelet_packets.BaseNode.__str__", + "pywt._wavelet_packets.Node2D.expand_2d_path", + "pywt._wavelet_packets.NodeND.__init__", + "pywt._wavelet_packets.WaveletPacket.__init__", + "pywt._wavelet_packets.WaveletPacket.__reduce__", + "pywt._wavelet_packets.WaveletPacket.reconstruct", + "pywt._wavelet_packets.WaveletPacket.get_level", + "pywt._wavelet_packets.WaveletPacket2D.__init__", + "pywt._wavelet_packets.WaveletPacket2D.__reduce__", + "pywt._wavelet_packets.WaveletPacket2D.reconstruct", + "pywt._wavelet_packets.WaveletPacket2D.get_level", + "pywt._wavelet_packets.WaveletPacketND.__init__", + "pywt._wavelet_packets.WaveletPacketND.reconstruct", + "pywt._wavelet_packets.WaveletPacketND.get_level", + "pywt._wavelet_packets.BaseNode", + "pywt._wavelet_packets.Node", + "pywt._wavelet_packets.Node2D", + "pywt._wavelet_packets.NodeND", + "pywt._wavelet_packets.WaveletPacket", + "pywt._wavelet_packets.WaveletPacket2D", + "pywt._wavelet_packets.WaveletPacketND" + ], + "line_count": 1057, + "symbol_reports": [ + { + "kind": "attr", + "name": "pywt._wavelet_packets.BaseNode.parent", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 61, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.BaseNode.wavelet", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 63, + "column": 18 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.BaseNode.mode", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 64, + "column": 18 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.BaseNode.level", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 65, + "column": 18 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.BaseNode.path", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 67, + "column": 18 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.BaseNode.axes", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 68, + "column": 18 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.BaseNode.data", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 77, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.NodeND.PART_LEN", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 587, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.NodeND.PARTS", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 588, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.NodeND.ndim", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 591, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.NodeND.ndim_transform", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 592, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacket.wavelet", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 714, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacket.mode", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 715, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacket.axes", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 716, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacket.data_size", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 724, + "column": 18 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacket2D.wavelet", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 840, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacket2D.mode", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 841, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacket2D.axes", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 842, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacket2D.data_size", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 850, + "column": 18 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacketND.wavelet", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 994, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacketND.mode", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 995, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacketND.axes", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 996, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacketND.ndim_transform", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 997, + "column": 14 + } + }, + { + "kind": "attr", + "name": "pywt._wavelet_packets.WaveletPacketND.data_size", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 1002, + "column": 18 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.get_graycode_order", + "n_typable": 4, + "n_typed": 4, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 24, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.BaseNode.__init__", + "n_typable": 4, + "n_typed": 3, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 60, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._wavelet_packets.BaseNode.path_tuple", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 116, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._wavelet_packets.BaseNode.maxlevel", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 156, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._wavelet_packets.BaseNode.node_name", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 169, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.BaseNode.decompose", + "n_typable": 1, + "n_typed": 0, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 173, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.BaseNode.reconstruct", + "n_typable": 2, + "n_typed": 1, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 194, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.BaseNode.get_subnode", + "n_typable": 3, + "n_typed": 3, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 215, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.BaseNode.__getitem__", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 236, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.BaseNode.__setitem__", + "n_typable": 3, + "n_typed": 2, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 274, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.BaseNode.__delitem__", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 311, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._wavelet_packets.BaseNode.is_empty", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 330, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._wavelet_packets.BaseNode.has_any_subnode", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 334, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.BaseNode.get_leaf_nodes", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 338, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.BaseNode.walk", + "n_typable": 5, + "n_typed": 2, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 360, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.BaseNode.walk_depth", + "n_typable": 5, + "n_typed": 2, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 387, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.BaseNode.__str__", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 413, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.Node2D.expand_2d_path", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 546, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.NodeND.__init__", + "n_typable": 6, + "n_typed": 5, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 584, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.WaveletPacket.__init__", + "n_typable": 6, + "n_typed": 5, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 708, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.WaveletPacket.__reduce__", + "n_typable": 1, + "n_typed": 0, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 732, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.WaveletPacket.reconstruct", + "n_typable": 2, + "n_typed": 1, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 736, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.WaveletPacket.get_level", + "n_typable": 4, + "n_typed": 4, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 755, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.WaveletPacket2D.__init__", + "n_typable": 6, + "n_typed": 5, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 834, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.WaveletPacket2D.__reduce__", + "n_typable": 1, + "n_typed": 0, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 858, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.WaveletPacket2D.reconstruct", + "n_typable": 2, + "n_typed": 1, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 862, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.WaveletPacket2D.get_level", + "n_typable": 4, + "n_typed": 4, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 881, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.WaveletPacketND.__init__", + "n_typable": 6, + "n_typed": 5, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 966, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.WaveletPacketND.reconstruct", + "n_typable": 2, + "n_typed": 1, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 1010, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._wavelet_packets.WaveletPacketND.get_level", + "n_typable": 3, + "n_typed": 3, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 1029, + "column": 5 + } + }, + { + "kind": "class", + "name": "pywt._wavelet_packets.BaseNode", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 32, + "column": 1 + } + }, + { + "kind": "class", + "name": "pywt._wavelet_packets.Node", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 417, + "column": 1 + } + }, + { + "kind": "class", + "name": "pywt._wavelet_packets.Node2D", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 476, + "column": 1 + } + }, + { + "kind": "class", + "name": "pywt._wavelet_packets.NodeND", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 557, + "column": 1 + } + }, + { + "kind": "class", + "name": "pywt._wavelet_packets.WaveletPacket", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 688, + "column": 1 + } + }, + { + "kind": "class", + "name": "pywt._wavelet_packets.WaveletPacket2D", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 814, + "column": 1 + } + }, + { + "kind": "class", + "name": "pywt._wavelet_packets.WaveletPacketND", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 945, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 108, + "n_typed": 65, + "n_any": 19, + "n_untyped": 24, + "coverage": 77.77777777777779, + "strict_coverage": 60.18518518518518, + "n_functions": 1, + "n_methods": 24, + "n_function_params": 3, + "n_method_params": 51, + "n_classes": 7, + "n_attrs": 24, + "n_properties": 5, + "n_type_ignores": 0 + }, + { + "name": "pywt._mra", + "names": [ + "pywt._mra.mra", + "pywt._mra.imra", + "pywt._mra.mra2", + "pywt._mra.imra2", + "pywt._mra.mran", + "pywt._mra.imran" + ], + "line_count": 437, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt._mra.mra", + "n_typable": 7, + "n_typed": 5, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 22, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._mra.imra", + "n_typable": 2, + "n_typed": 0, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 125, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._mra.mra2", + "n_typable": 7, + "n_typed": 5, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 152, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._mra.imra2", + "n_typable": 2, + "n_typed": 0, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 261, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._mra.mran", + "n_typable": 7, + "n_typed": 5, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 292, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._mra.imran", + "n_typable": 2, + "n_typed": 0, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 408, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 27, + "n_typed": 15, + "n_any": 12, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 55.55555555555556, + "n_functions": 6, + "n_methods": 0, + "n_function_params": 21, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt.data", + "names": [], + "line_count": 3, + "symbol_reports": [], + "type_ignores": [], + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 100.0, + "n_functions": 0, + "n_methods": 0, + "n_function_params": 0, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._swt", + "names": [ + "pywt._swt.swt", + "pywt._swt.swt", + "pywt._swt.swt", + "pywt._swt.iswt", + "pywt._swt.iswtn", + "pywt._swt.swt2", + "pywt._swt.swtn", + "pywt._swt.iswt2" + ], + "line_count": 882, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt._swt.swt", + "n_typable": 8, + "n_typed": 6, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 28, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._swt.swt", + "n_typable": 8, + "n_typed": 6, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 38, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._swt.swt", + "n_typable": 8, + "n_typed": 6, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 49, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._swt.iswt", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 184, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._swt.iswtn", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 717, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._swt.swt2", + "n_typable": 8, + "n_typed": 6, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 291, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._swt.swtn", + "n_typable": 8, + "n_typed": 6, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 585, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._swt.iswt2", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 402, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 55, + "n_typed": 39, + "n_any": 16, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 70.9090909090909, + "n_functions": 8, + "n_methods": 0, + "n_function_params": 47, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._extensions._pywt", + "names": [ + "pywt._extensions._pywt.ModeInt", + "pywt._extensions._pywt.ModeName", + "pywt._extensions._pywt.Mode", + "pywt._extensions._pywt.Modes", + "pywt._extensions._pywt.MODE.MODE_INVALID", + "pywt._extensions._pywt.MODE.MODE_ZEROPAD", + "pywt._extensions._pywt.MODE.MODE_SYMMETRIC", + "pywt._extensions._pywt.MODE.MODE_CONSTANT_EDGE", + "pywt._extensions._pywt.MODE.MODE_SMOOTH", + "pywt._extensions._pywt.MODE.MODE_PERIODIC", + "pywt._extensions._pywt.MODE.MODE_PERIODIZATION", + "pywt._extensions._pywt.MODE.MODE_REFLECT", + "pywt._extensions._pywt.MODE.MODE_ANTISYMMETRIC", + "pywt._extensions._pywt.MODE.MODE_ANTIREFLECT", + "pywt._extensions._pywt.MODE.MODE_MAX", + "pywt._extensions._pywt._Modes.from_object", + "pywt._extensions._pywt.wavelist", + "pywt._extensions._pywt.families", + "pywt._extensions._pywt.Wavelet.__init__", + "pywt._extensions._pywt.Wavelet.__len__", + "pywt._extensions._pywt.Wavelet.name", + "pywt._extensions._pywt.Wavelet.dec_lo", + "pywt._extensions._pywt.Wavelet.dec_hi", + "pywt._extensions._pywt.Wavelet.rec_lo", + "pywt._extensions._pywt.Wavelet.rec_hi", + "pywt._extensions._pywt.Wavelet.rec_len", + "pywt._extensions._pywt.Wavelet.dec_len", + "pywt._extensions._pywt.Wavelet.family_number", + "pywt._extensions._pywt.Wavelet.family_name", + "pywt._extensions._pywt.Wavelet.short_family_name", + "pywt._extensions._pywt.Wavelet.orthogonal", + "pywt._extensions._pywt.Wavelet.orthogonal", + "pywt._extensions._pywt.Wavelet.biorthogonal", + "pywt._extensions._pywt.Wavelet.biorthogonal", + "pywt._extensions._pywt.Wavelet.symmetry", + "pywt._extensions._pywt.Wavelet.vanishing_moments_psi", + "pywt._extensions._pywt.Wavelet.vanishing_moments_phi", + "pywt._extensions._pywt.Wavelet.filter_bank", + "pywt._extensions._pywt.Wavelet.get_filters_coeffs", + "pywt._extensions._pywt.Wavelet.inverse_filter_bank", + "pywt._extensions._pywt.Wavelet.get_reverse_filters_coeffs", + "pywt._extensions._pywt.Wavelet.wavefun", + "pywt._extensions._pywt.ContinuousWavelet.__init__", + "pywt._extensions._pywt.ContinuousWavelet.family_number", + "pywt._extensions._pywt.ContinuousWavelet.family_name", + "pywt._extensions._pywt.ContinuousWavelet.short_family_name", + "pywt._extensions._pywt.ContinuousWavelet.orthogonal", + "pywt._extensions._pywt.ContinuousWavelet.orthogonal", + "pywt._extensions._pywt.ContinuousWavelet.biorthogonal", + "pywt._extensions._pywt.ContinuousWavelet.biorthogonal", + "pywt._extensions._pywt.ContinuousWavelet.complex_cwt", + "pywt._extensions._pywt.ContinuousWavelet.complex_cwt", + "pywt._extensions._pywt.ContinuousWavelet.lower_bound", + "pywt._extensions._pywt.ContinuousWavelet.lower_bound", + "pywt._extensions._pywt.ContinuousWavelet.upper_bound", + "pywt._extensions._pywt.ContinuousWavelet.upper_bound", + "pywt._extensions._pywt.ContinuousWavelet.center_frequency", + "pywt._extensions._pywt.ContinuousWavelet.center_frequency", + "pywt._extensions._pywt.ContinuousWavelet.bandwidth_frequency", + "pywt._extensions._pywt.ContinuousWavelet.bandwidth_frequency", + "pywt._extensions._pywt.ContinuousWavelet.fbsp_order", + "pywt._extensions._pywt.ContinuousWavelet.fbsp_order", + "pywt._extensions._pywt.ContinuousWavelet.symmetry", + "pywt._extensions._pywt.ContinuousWavelet.wavefun", + "pywt._extensions._pywt.DiscreteContinuousWavelet", + "pywt._extensions._pywt.ContinuousWavelet", + "pywt._extensions._pywt.MODE", + "pywt._extensions._pywt.Wavelet", + "pywt._extensions._pywt._Modes" + ], + "line_count": 189, + "symbol_reports": [ + { + "kind": "attr", + "name": "pywt._extensions._pywt.ModeInt", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 46, + "column": 1 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.ModeName", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 48, + "column": 1 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.Mode", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 60, + "column": 1 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.Modes", + "n_typable": 1, + "n_typed": 0, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 77, + "column": 1 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.MODE.MODE_INVALID", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 34, + "column": 5 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.MODE.MODE_ZEROPAD", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 35, + "column": 5 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.MODE.MODE_SYMMETRIC", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 36, + "column": 5 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.MODE.MODE_CONSTANT_EDGE", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 37, + "column": 5 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.MODE.MODE_SMOOTH", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 38, + "column": 5 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.MODE.MODE_PERIODIC", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 39, + "column": 5 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.MODE.MODE_PERIODIZATION", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 40, + "column": 5 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.MODE.MODE_REFLECT", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 41, + "column": 5 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.MODE.MODE_ANTISYMMETRIC", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 42, + "column": 5 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.MODE.MODE_ANTIREFLECT", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 43, + "column": 5 + } + }, + { + "kind": "attr", + "name": "pywt._extensions._pywt.MODE.MODE_MAX", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 44, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._extensions._pywt._Modes.from_object", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 75, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._extensions._pywt.wavelist", + "n_typable": 3, + "n_typed": 3, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 79, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._pywt.families", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 82, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._pywt.Wavelet.__init__", + "n_typable": 3, + "n_typed": 2, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 85, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._extensions._pywt.Wavelet.__len__", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 86, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.name", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 87, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.dec_lo", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 89, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.dec_hi", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 91, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.rec_lo", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 93, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.rec_hi", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 95, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.rec_len", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 97, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.dec_len", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 99, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.family_number", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 101, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.family_name", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 103, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.short_family_name", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 105, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.orthogonal", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 107, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.orthogonal", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 109, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.biorthogonal", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 111, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.biorthogonal", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 113, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.symmetry", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 115, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.vanishing_moments_psi", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 117, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.vanishing_moments_phi", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 119, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.filter_bank", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 121, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._extensions._pywt.Wavelet.get_filters_coeffs", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 125, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.Wavelet.inverse_filter_bank", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 128, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._extensions._pywt.Wavelet.get_reverse_filters_coeffs", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 132, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._extensions._pywt.Wavelet.wavefun", + "n_typable": 2, + "n_typed": 1, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 135, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._extensions._pywt.ContinuousWavelet.__init__", + "n_typable": 3, + "n_typed": 2, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 138, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.family_number", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 141, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.family_name", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 143, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.short_family_name", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 145, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.orthogonal", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 147, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.orthogonal", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 149, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.biorthogonal", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 151, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.biorthogonal", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 153, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.complex_cwt", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 155, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.complex_cwt", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 157, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.lower_bound", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 159, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.lower_bound", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 161, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.upper_bound", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 163, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.upper_bound", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 165, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.center_frequency", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 167, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.center_frequency", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 169, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.bandwidth_frequency", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 171, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.bandwidth_frequency", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 173, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.fbsp_order", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 175, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.fbsp_order", + "n_typable": 2, + "n_typed": 2, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 177, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._extensions._pywt.ContinuousWavelet.symmetry", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 179, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._extensions._pywt.ContinuousWavelet.wavefun", + "n_typable": 3, + "n_typed": 2, + "n_any": 0, + "n_untyped": 1, + "location": { + "line": 181, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._extensions._pywt.DiscreteContinuousWavelet", + "n_typable": 3, + "n_typed": 2, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 183, + "column": 1 + } + }, + { + "kind": "class", + "name": "pywt._extensions._pywt.ContinuousWavelet", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 137, + "column": 1 + } + }, + { + "kind": "class", + "name": "pywt._extensions._pywt.MODE", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 33, + "column": 1 + } + }, + { + "kind": "class", + "name": "pywt._extensions._pywt.Wavelet", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 84, + "column": 1 + } + }, + { + "kind": "class", + "name": "pywt._extensions._pywt._Modes", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 62, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 74, + "n_typed": 68, + "n_any": 3, + "n_untyped": 3, + "coverage": 95.94594594594594, + "strict_coverage": 91.8918918918919, + "n_functions": 3, + "n_methods": 8, + "n_function_params": 5, + "n_method_params": 8, + "n_classes": 4, + "n_attrs": 15, + "n_properties": 39, + "n_type_ignores": 0 + }, + { + "name": "pywt._dwt", + "names": [ + "pywt._dwt.dwt_max_level", + "pywt._dwt.dwt_coeff_len", + "pywt._dwt.dwt", + "pywt._dwt.idwt", + "pywt._dwt.downcoef", + "pywt._dwt.upcoef", + "pywt._dwt.pad" + ], + "line_count": 542, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt._dwt.dwt_max_level", + "n_typable": 3, + "n_typed": 3, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 22, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._dwt.dwt_coeff_len", + "n_typable": 4, + "n_typed": 4, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 87, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._dwt.dwt", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 124, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._dwt.idwt", + "n_typable": 6, + "n_typed": 3, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 194, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._dwt.downcoef", + "n_typable": 6, + "n_typed": 4, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 295, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._dwt.upcoef", + "n_typable": 6, + "n_typed": 4, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 349, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._dwt.pad", + "n_typable": 4, + "n_typed": 1, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 410, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 34, + "n_typed": 22, + "n_any": 12, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 64.70588235294117, + "n_functions": 7, + "n_methods": 0, + "n_function_params": 27, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt.data._wavelab_signals", + "names": [ + "pywt.data._wavelab_signals.demo_signal" + ], + "line_count": 268, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt.data._wavelab_signals.demo_signal", + "n_typable": 3, + "n_typed": 2, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 29, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 3, + "n_typed": 2, + "n_any": 1, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 66.66666666666666, + "n_functions": 1, + "n_methods": 0, + "n_function_params": 2, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._multilevel", + "names": [ + "pywt._multilevel.wavedec", + "pywt._multilevel.waverec", + "pywt._multilevel.wavedec2", + "pywt._multilevel.waverec2", + "pywt._multilevel.wavedecn", + "pywt._multilevel.waverecn", + "pywt._multilevel.coeffs_to_array", + "pywt._multilevel.array_to_coeffs", + "pywt._multilevel.array_to_coeffs", + "pywt._multilevel.array_to_coeffs", + "pywt._multilevel.wavedecn_shapes", + "pywt._multilevel.wavedecn_size", + "pywt._multilevel.dwtn_max_level", + "pywt._multilevel.ravel_coeffs", + "pywt._multilevel.unravel_coeffs", + "pywt._multilevel.unravel_coeffs", + "pywt._multilevel.unravel_coeffs", + "pywt._multilevel.FswavedecnResult.__init__", + "pywt._multilevel.FswavedecnResult.coeffs", + "pywt._multilevel.FswavedecnResult.coeffs", + "pywt._multilevel.FswavedecnResult.coeff_slices", + "pywt._multilevel.FswavedecnResult.ndim", + "pywt._multilevel.FswavedecnResult.ndim_transform", + "pywt._multilevel.FswavedecnResult.axes", + "pywt._multilevel.FswavedecnResult.levels", + "pywt._multilevel.FswavedecnResult.wavelets", + "pywt._multilevel.FswavedecnResult.wavelet_names", + "pywt._multilevel.FswavedecnResult.modes", + "pywt._multilevel.FswavedecnResult.approx", + "pywt._multilevel.FswavedecnResult.approx", + "pywt._multilevel.FswavedecnResult.__getitem__", + "pywt._multilevel.FswavedecnResult.__setitem__", + "pywt._multilevel.FswavedecnResult.detail_keys", + "pywt._multilevel.fswavedecn", + "pywt._multilevel.fswaverecn", + "pywt._multilevel.FswavedecnResult" + ], + "line_count": 1712, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt._multilevel.wavedec", + "n_typable": 6, + "n_typed": 4, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 66, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.waverec", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 135, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.wavedec2", + "n_typable": 6, + "n_typed": 4, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 207, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.waverec2", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 290, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.wavedecn", + "n_typable": 6, + "n_typed": 4, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 404, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.waverecn", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 513, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.coeffs_to_array", + "n_typable": 4, + "n_typed": 2, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 736, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.array_to_coeffs", + "n_typable": 4, + "n_typed": 1, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 867, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.array_to_coeffs", + "n_typable": 4, + "n_typed": 1, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 873, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.array_to_coeffs", + "n_typable": 4, + "n_typed": 1, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 879, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.wavedecn_shapes", + "n_typable": 6, + "n_typed": 6, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 978, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.wavedecn_size", + "n_typable": 2, + "n_typed": 1, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 1042, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.dwtn_max_level", + "n_typable": 4, + "n_typed": 4, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 1091, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.ravel_coeffs", + "n_typable": 3, + "n_typed": 1, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 1138, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.unravel_coeffs", + "n_typable": 5, + "n_typed": 2, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 1230, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.unravel_coeffs", + "n_typable": 5, + "n_typed": 2, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 1237, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.unravel_coeffs", + "n_typable": 5, + "n_typed": 2, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 1244, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.FswavedecnResult.__init__", + "n_typable": 6, + "n_typed": 4, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 1361, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.coeffs", + "n_typable": 1, + "n_typed": 0, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 1381, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.coeffs", + "n_typable": 2, + "n_typed": 1, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 1386, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.coeff_slices", + "n_typable": 1, + "n_typed": 0, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 1393, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.ndim", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 1398, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.ndim_transform", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 1403, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.axes", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 1408, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.levels", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 1413, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.wavelets", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 1418, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.wavelet_names", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 1423, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.modes", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 1428, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.approx", + "n_typable": 1, + "n_typed": 0, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 1441, + "column": 5 + } + }, + { + "kind": "property", + "name": "pywt._multilevel.FswavedecnResult.approx", + "n_typable": 2, + "n_typed": 1, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 1447, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.FswavedecnResult.__getitem__", + "n_typable": 2, + "n_typed": 1, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 1475, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.FswavedecnResult.__setitem__", + "n_typable": 3, + "n_typed": 2, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 1488, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.FswavedecnResult.detail_keys", + "n_typable": 1, + "n_typed": 1, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 1511, + "column": 5 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.fswavedecn", + "n_typable": 6, + "n_typed": 5, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 1524, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._multilevel.fswaverecn", + "n_typable": 2, + "n_typed": 1, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 1643, + "column": 1 + } + }, + { + "kind": "class", + "name": "pywt._multilevel.FswavedecnResult", + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 1341, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 113, + "n_typed": 67, + "n_any": 46, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 59.29203539823009, + "n_functions": 19, + "n_methods": 4, + "n_function_params": 68, + "n_method_params": 8, + "n_classes": 1, + "n_attrs": 0, + "n_properties": 12, + "n_type_ignores": 0 + }, + { + "name": "pywt._extensions._dwt", + "names": [ + "pywt._extensions._dwt.dwt_max_level", + "pywt._extensions._dwt.dwt_coeff_len", + "pywt._extensions._dwt.dwt_single", + "pywt._extensions._dwt.dwt_axis", + "pywt._extensions._dwt.idwt_single", + "pywt._extensions._dwt.idwt_axis", + "pywt._extensions._dwt.upcoef", + "pywt._extensions._dwt.downcoef" + ], + "line_count": 21, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt._extensions._dwt.dwt_max_level", + "n_typable": 3, + "n_typed": 3, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 13, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._dwt.dwt_coeff_len", + "n_typable": 4, + "n_typed": 4, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 14, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._dwt.dwt_single", + "n_typable": 4, + "n_typed": 2, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 15, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._dwt.dwt_axis", + "n_typable": 5, + "n_typed": 3, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 16, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._dwt.idwt_single", + "n_typable": 5, + "n_typed": 2, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 17, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._dwt.idwt_axis", + "n_typable": 6, + "n_typed": 3, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 18, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._dwt.upcoef", + "n_typable": 6, + "n_typed": 4, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 19, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._extensions._dwt.downcoef", + "n_typable": 6, + "n_typed": 4, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 20, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 39, + "n_typed": 25, + "n_any": 14, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 64.1025641025641, + "n_functions": 8, + "n_methods": 0, + "n_function_params": 31, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._doc_utils", + "names": [ + "pywt._doc_utils.wavedec_keys", + "pywt._doc_utils.wavedec2_keys", + "pywt._doc_utils.draw_2d_wp_basis", + "pywt._doc_utils.draw_2d_fswavedecn_basis", + "pywt._doc_utils.boundary_mode_subplot" + ], + "line_count": 188, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt._doc_utils.wavedec_keys", + "n_typable": 2, + "n_typed": 0, + "n_any": 0, + "n_untyped": 2, + "location": { + "line": 13, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._doc_utils.wavedec2_keys", + "n_typable": 2, + "n_typed": 0, + "n_any": 0, + "n_untyped": 2, + "location": { + "line": 26, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._doc_utils.draw_2d_wp_basis", + "n_typable": 7, + "n_typed": 0, + "n_any": 0, + "n_untyped": 7, + "location": { + "line": 88, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._doc_utils.draw_2d_fswavedecn_basis", + "n_typable": 7, + "n_typed": 0, + "n_any": 0, + "n_untyped": 7, + "location": { + "line": 132, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._doc_utils.boundary_mode_subplot", + "n_typable": 5, + "n_typed": 0, + "n_any": 0, + "n_untyped": 5, + "location": { + "line": 154, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 23, + "n_typed": 0, + "n_any": 0, + "n_untyped": 23, + "coverage": 0.0, + "strict_coverage": 0.0, + "n_functions": 5, + "n_methods": 0, + "n_function_params": 18, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._extensions", + "names": [], + "line_count": 1, + "symbol_reports": [], + "type_ignores": [], + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 100.0, + "n_functions": 0, + "n_methods": 0, + "n_function_params": 0, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._functions", + "names": [ + "pywt._functions.intwave", + "pywt._functions.integrate_wavelet", + "pywt._functions.centrfrq", + "pywt._functions.central_frequency", + "pywt._functions.scal2frq", + "pywt._functions.scale2frequency", + "pywt._functions.orthfilt", + "pywt._functions.orthogonal_filter_bank", + "pywt._functions.frequency2scale", + "pywt._functions.qmf" + ], + "line_count": 267, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt._functions.intwave", + "n_typable": 3, + "n_typed": 0, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 35, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._functions.integrate_wavelet", + "n_typable": 3, + "n_typed": 2, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 59, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._functions.centrfrq", + "n_typable": 3, + "n_typed": 0, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 41, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._functions.central_frequency", + "n_typable": 3, + "n_typed": 2, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 122, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._functions.scal2frq", + "n_typable": 3, + "n_typed": 1, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 47, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._functions.scale2frequency", + "n_typable": 4, + "n_typed": 4, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 163, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._functions.orthfilt", + "n_typable": 3, + "n_typed": 0, + "n_any": 3, + "n_untyped": 0, + "location": { + "line": 53, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._functions.orthogonal_filter_bank", + "n_typable": 2, + "n_typed": 0, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 230, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._functions.frequency2scale", + "n_typable": 4, + "n_typed": 4, + "n_any": 0, + "n_untyped": 0, + "location": { + "line": 186, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt._functions.qmf", + "n_typable": 2, + "n_typed": 0, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 207, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 30, + "n_typed": 13, + "n_any": 17, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 43.333333333333336, + "n_functions": 10, + "n_methods": 0, + "n_function_params": 20, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._extensions._cwt", + "names": [ + "pywt._extensions._cwt.cwt_psi_single" + ], + "line_count": 12, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt._extensions._cwt.cwt_psi_single", + "n_typable": 4, + "n_typed": 2, + "n_any": 2, + "n_untyped": 0, + "location": { + "line": 9, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 4, + "n_typed": 2, + "n_any": 2, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 50.0, + "n_functions": 1, + "n_methods": 0, + "n_function_params": 3, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt._utils", + "names": [], + "line_count": 109, + "symbol_reports": [], + "type_ignores": [], + "n_typable": 0, + "n_typed": 0, + "n_any": 0, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 100.0, + "n_functions": 0, + "n_methods": 0, + "n_function_params": 0, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + }, + { + "name": "pywt.data._readers", + "names": [ + "pywt.data._readers.ascent", + "pywt.data._readers.aero", + "pywt.data._readers.camera", + "pywt.data._readers.ecg", + "pywt.data._readers.nino" + ], + "line_count": 213, + "symbol_reports": [ + { + "kind": "function", + "name": "pywt.data._readers.ascent", + "n_typable": 1, + "n_typed": 0, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 10, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt.data._readers.aero", + "n_typable": 1, + "n_typed": 0, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 50, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt.data._readers.camera", + "n_typable": 1, + "n_typed": 0, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 87, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt.data._readers.ecg", + "n_typable": 1, + "n_typed": 0, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 135, + "column": 1 + } + }, + { + "kind": "function", + "name": "pywt.data._readers.nino", + "n_typable": 1, + "n_typed": 0, + "n_any": 1, + "n_untyped": 0, + "location": { + "line": 167, + "column": 1 + } + } + ], + "type_ignores": [], + "n_typable": 5, + "n_typed": 0, + "n_any": 5, + "n_untyped": 0, + "coverage": 100.0, + "strict_coverage": 0.0, + "n_functions": 5, + "n_methods": 0, + "n_function_params": 0, + "n_method_params": 0, + "n_classes": 0, + "n_attrs": 0, + "n_properties": 0, + "n_type_ignores": 0 + } + ], + "summary": { + "n_modules": 20, + "n_typable": 616, + "n_typed": 381, + "n_any": 184, + "n_untyped": 51, + "coverage": 91.72077922077922, + "strict_coverage": 61.850649350649356, + "n_functions": 94, + "n_methods": 36, + "n_function_params": 325, + "n_method_params": 67, + "n_classes": 12, + "n_attrs": 41, + "n_properties": 56, + "n_type_ignores": 0 + } +} diff --git a/pyrefly.toml b/pyrefly.toml new file mode 100644 index 00000000..12334ae8 --- /dev/null +++ b/pyrefly.toml @@ -0,0 +1,24 @@ +# set the directory Pyrefly will search for files to type check +project-includes = ["pywt/**/*.py*"] +project-excludes = ["**/tests/**", "**/*test*", "**/__pycache__", "**/*venv/**", "**/.[!/.]*/**"] +use-ignore-files = true + +search-path = ["."] + +disable-search-path-heuristics = false +# do not include any third-party packages (except those provided by an interpreter) +site-package-path = [] + +ignore-missing-imports = ["*"] + +# manually set the `sys.platform` Pyrefly will assume when type checking +python-platform = "linux" + +min-severity = "warn" + +# a table mapping error codes to an `is-enabled` boolean +[errors] +# disable `bad-assignment` errors +bad-assignment = false +# disable `bad-return` errors +bad-return = false \ No newline at end of file diff --git a/pywt/__init__.py b/pywt/__init__.py index 669b06b6..3f9b87e6 100644 --- a/pywt/__init__.py +++ b/pywt/__init__.py @@ -10,26 +10,33 @@ wavelet packets signal decomposition and reconstruction module. """ -from ._extensions._pywt import * -from ._functions import * -from ._multilevel import * -from ._multidim import * -from ._thresholding import * -from ._wavelet_packets import * -from ._dwt import * -from ._swt import * -from ._cwt import * -from ._mra import * +from ._extensions._pywt import Modes, ContinuousWavelet, families, Wavelet, wavelist, DiscreteContinuousWavelet, _check_dtype +from ._functions import integrate_wavelet, central_frequency, scale2frequency, frequency2scale, qmf, orthogonal_filter_bank, intwave, centrfrq, scal2frq, orthfilt +from ._multilevel import wavedec, waverec, wavedec2, waverec2, wavedecn, waverecn, coeffs_to_array, array_to_coeffs, ravel_coeffs, unravel_coeffs, dwtn_max_level, wavedecn_size, wavedecn_shapes, fswavedecn, fswaverecn, FswavedecnResult +from ._multidim import dwt2, idwt2, dwtn, idwtn +from ._thresholding import threshold, threshold_firm +from ._wavelet_packets import BaseNode, Node, WaveletPacket, Node2D, WaveletPacket2D, NodeND, WaveletPacketND +from ._dwt import dwt, idwt, downcoef, upcoef, dwt_max_level, dwt_coeff_len, pad +from ._swt import swt, swt_max_level, iswt, swt2, iswt2, swtn, iswtn +from ._cwt import cwt +from ._mra import mra, mra2, mran, imra, imra2, imran +from .data import aero, ascent, camera, ecg, nino, demo_signal -from . import data - -__all__ = [s for s in dir() if not s.startswith('_')] -try: - # In Python 2.x the name of the tempvar leaks out of the list - # comprehension. Delete it to not make it show up in the main namespace. - del s -except NameError: - pass +__all__ = ["ContinuousWavelet", "families", "Modes", "Wavelet", "wavelist", + "DiscreteContinuousWavelet", "_check_dtype", "integrate_wavelet", + "central_frequency", "scale2frequency", "frequency2scale", "qmf", + "orthogonal_filter_bank", "intwave", "centrfrq", "scal2frq", + "orthfilt", "wavedec", "waverec", "wavedec2", "waverec2", + "wavedecn", "waverecn", "coeffs_to_array", "array_to_coeffs", + "ravel_coeffs", "unravel_coeffs", "dwtn_max_level", + "wavedecn_size", "wavedecn_shapes", "fswavedecn", "fswaverecn", + "FswavedecnResult", "dwt2", "idwt2", "dwtn", "idwtn", "threshold", + "threshold_firm", "BaseNode", "Node", "WaveletPacket", "Node2D", + "WaveletPacket2D", "NodeND", "WaveletPacketND", "dwt", "idwt", + "downcoef", "upcoef", "dwt_max_level", "dwt_coeff_len", "pad", + "swt", "swt_max_level", "iswt", "swt2", "iswt2", "swtn", "iswtn", + "cwt", "mra", "mra2", "mran", "imra", "imra2", "imran", "aero", + "ascent", "camera", "ecg", "nino", "demo_signal"] from pywt.version import version as __version__ diff --git a/pywt/_cwt.py b/pywt/_cwt.py index 2e52fea6..067a1e54 100644 --- a/pywt/_cwt.py +++ b/pywt/_cwt.py @@ -1,4 +1,5 @@ from math import ceil, floor +from typing import Literal from ._extensions._pywt import ( ContinuousWavelet, @@ -15,7 +16,7 @@ import numpy as np -def next_fast_len(n): +def next_fast_len(n: int) -> int: """Round up size to the nearest power of two. Given a number of samples `n`, returns the next power of two @@ -24,8 +25,16 @@ def next_fast_len(n): return 2**ceil(np.log2(n)) -def cwt(data, scales, wavelet, sampling_period=1., method='conv', axis=-1, - *, precision=12): +def cwt( + data: np.ndarray | list, + scales: np.ndarray | list, + wavelet: Wavelet | str, + sampling_period: float = 1.0, + method: Literal["conv", "fft"] = 'conv', + axis: int = -1, + *, + precision: int = 12 + ) -> tuple[np.ndarray, np.ndarray]: """ One dimensional Continuous Wavelet Transform. diff --git a/pywt/_dwt.py b/pywt/_dwt.py index 151b8787..95f4f54e 100644 --- a/pywt/_dwt.py +++ b/pywt/_dwt.py @@ -1,4 +1,6 @@ +from __future__ import annotations from numbers import Number +from typing import TYPE_CHECKING, Sequence, cast import numpy as np @@ -10,11 +12,14 @@ from ._extensions._pywt import Modes, Wavelet, _check_dtype, wavelist from ._utils import AxisError, _as_wavelet -__all__ = ["dwt", "idwt", "downcoef", "upcoef", "dwt_max_level", - "dwt_coeff_len", "pad"] +if TYPE_CHECKING: + from ._extensions._pywt import Mode -def dwt_max_level(data_len, filter_len): +__all__ = ["dwt", "idwt", "downcoef", "upcoef", "dwt_max_level", "dwt_coeff_len", "pad"] + + +def dwt_max_level(data_len: int, filter_len: int | str | Wavelet) -> int: r""" dwt_max_level(data_len, filter_len) @@ -79,7 +84,7 @@ def dwt_max_level(data_len, filter_len): return _dwt_max_level(data_len, filter_len) -def dwt_coeff_len(data_len, filter_len, mode): +def dwt_coeff_len(data_len: int, filter_len: int, mode: Mode) -> int: """ dwt_coeff_len(data_len, filter_len, mode='symmetric') @@ -116,7 +121,12 @@ def dwt_coeff_len(data_len, filter_len, mode): return _dwt_coeff_len(data_len, filter_len, Modes.from_object(mode)) -def dwt(data, wavelet, mode='symmetric', axis=-1): +def dwt( + data: np.ndarray | list, + wavelet: Wavelet | str, + mode: Mode = "symmetric", + axis: int = -1, +) -> tuple[np.ndarray, np.ndarray]: """ dwt(data, wavelet, mode='symmetric', axis=-1) @@ -181,7 +191,13 @@ def dwt(data, wavelet, mode='symmetric', axis=-1): return (cA, cD) -def idwt(cA, cD, wavelet, mode='symmetric', axis=-1): +def idwt( + cA: np.ndarray | None, + cD: np.ndarray | None, + wavelet: Wavelet | str, + mode: Mode = "symmetric", + axis: int = -1, +) -> np.ndarray: """ idwt(cA, cD, wavelet, mode='symmetric', axis=-1) @@ -255,6 +271,8 @@ def idwt(cA, cD, wavelet, mode='symmetric', axis=-1): elif cD is None: cD = np.zeros_like(cA) + cA = cast(np.ndarray, cA) + cD = cast(np.ndarray, cD) # cA and cD should be same dimension by here ndim = cA.ndim @@ -274,7 +292,13 @@ def idwt(cA, cD, wavelet, mode='symmetric', axis=-1): return rec -def downcoef(part, data, wavelet, mode='symmetric', level=1): +def downcoef( + part: str, + data: np.ndarray | list, + wavelet: Wavelet | str, + mode: Mode | None = "symmetric", + level: int | None = 1, +) -> np.ndarray: """ downcoef(part, data, wavelet, mode='symmetric', level=1) @@ -322,7 +346,13 @@ def downcoef(part, data, wavelet, mode='symmetric', level=1): return np.asarray(_downcoef(part == 'a', data, wavelet, mode, level)) -def upcoef(part, coeffs, wavelet, level=1, take=0): +def upcoef( + part: str, + coeffs: np.ndarray | list, + wavelet: Wavelet | str, + level: int | None = 1, + take: int | None = 0, +) -> np.ndarray: """ upcoef(part, coeffs, wavelet, level=1, take=0) @@ -377,7 +407,9 @@ def upcoef(part, coeffs, wavelet, level=1, take=0): return np.asarray(_upcoef(part == 'a', coeffs, wavelet, level, take)) -def pad(x, pad_widths, mode): +def pad( + x: np.ndarray, pad_widths: Sequence | np.ndarray | int, mode: Mode +) -> np.ndarray: """Extend a 1D signal using a given boundary mode. This function operates like :func:`numpy.pad` but supports all signal diff --git a/pywt/_extensions/_cwt.pyi b/pywt/_extensions/_cwt.pyi new file mode 100644 index 00000000..96101a1b --- /dev/null +++ b/pywt/_extensions/_cwt.pyi @@ -0,0 +1,11 @@ +from typing import TypeVar + +import numpy as np +from ._pywt import ContinuousWavelet +from numpy.typing import NDArray + +_DataT = TypeVar("_DataT", bound=np.float32 | np.float64) + +def cwt_psi_single( + data: NDArray[_DataT], wavelet: ContinuousWavelet, output_len: int +) -> NDArray[_DataT] | tuple[NDArray[_DataT], NDArray[_DataT]]: ... diff --git a/pywt/_extensions/_dwt.pyi b/pywt/_extensions/_dwt.pyi new file mode 100644 index 00000000..2a785f1a --- /dev/null +++ b/pywt/_extensions/_dwt.pyi @@ -0,0 +1,20 @@ +from typing import TypeVar + +import numpy as np +from numpy.typing import NDArray + +from ._pywt import ModeInt, Wavelet + +_CDataT = TypeVar( + "_CDataT", bound=np.float32 | np.float64 | np.complex64 | np.complex128 +) + + +def dwt_max_level(data_len: int, filter_len: int) -> int: ... +def dwt_coeff_len(data_len: int, filter_len: int, mode: ModeInt) -> int: ... +def dwt_single(data: NDArray[_CDataT], wavelet: Wavelet, mode: ModeInt) -> tuple[NDArray[_CDataT], NDArray[_CDataT]]: ... +def dwt_axis(data: NDArray[_CDataT], wavelet: Wavelet, mode: ModeInt, axis: int = 0) -> tuple[NDArray[_CDataT], NDArray[_CDataT]]: ... +def idwt_single(cA: NDArray[_CDataT], cD: NDArray[_CDataT], wavelet: Wavelet, mode: ModeInt) -> NDArray[_CDataT]: ... +def idwt_axis(coefs_a: NDArray[_CDataT], coefs_d: NDArray[_CDataT], wavelet: Wavelet, mode: ModeInt, axis: int = 0) -> NDArray[_CDataT]: ... +def upcoef(do_rec_a: bool, coeffs: NDArray[_CDataT], wavelet: Wavelet, level: int, take: int) -> NDArray[_CDataT]: ... +def downcoef(do_dec_a: bool, data: NDArray[_CDataT], wavelet: Wavelet, mode: ModeInt, level: int) -> NDArray[_CDataT]: ... diff --git a/pywt/_extensions/_pywt.pyi b/pywt/_extensions/_pywt.pyi new file mode 100644 index 00000000..d160bf0e --- /dev/null +++ b/pywt/_extensions/_pywt.pyi @@ -0,0 +1,188 @@ +from enum import IntEnum +from typing import Any, Literal, TypeAlias, TypeVar + +import numpy as np + +_WaveletFamily = Literal[ + "haar", + "db", + "sym", + "coif", + "bior", + "rbio", + "dmey", + "gaus", + "mexh", + "morl", + "cgau", + "shan", + "fbsp", + "cmor", +] + +_Kind: TypeAlias = Literal["all", "continuous", "discrete"] + +_Symmetry = Literal[ + "asymmetric", + "near symmetric", + "symmetric", + "anti-symmetric", + "unknown", +] + +class MODE(IntEnum): + MODE_INVALID = -1 + MODE_ZEROPAD = 0 + MODE_SYMMETRIC = 1 + MODE_CONSTANT_EDGE = 2 + MODE_SMOOTH = 3 + MODE_PERIODIC = 4 + MODE_PERIODIZATION = 5 + MODE_REFLECT = 6 + MODE_ANTISYMMETRIC = 7 + MODE_ANTIREFLECT = 8 + MODE_MAX = 9 + +ModeInt = MODE | Literal[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +ModeName = Literal[ + "zero", + "constant", + "symmetric", + "reflect", + "periodic", + "smooth", + "periodization", + "antisymmetric", + "antireflect", +] + +Mode = MODE | ModeName + +class _Modes: + zero: int + constant: int + symmetric: int + reflect: int + periodic: int + smooth: int + periodization: int + antisymmetric: int + antireflect: int + + modes: list[ModeName] + + def from_object(self, mode: int | str) -> ModeInt: ... + +Modes = _Modes() + +def wavelist( + family: _WaveletFamily | None = None, kind: _Kind = "all" +) -> list[str]: ... +def families(short: bool = True) -> list[str]: ... + +class Wavelet: + def __init__(self, name: str = "", filter_bank: Any = None) -> None: ... + def __len__(self) -> int: ... + @property + def name(self) -> str: ... + @property + def dec_lo(self) -> list[float]: ... + @property + def dec_hi(self) -> list[float]: ... + @property + def rec_lo(self) -> list[float]: ... + @property + def rec_hi(self) -> list[float]: ... + @property + def rec_len(self) -> int: ... + @property + def dec_len(self) -> int: ... + @property + def family_number(self) -> int: ... + @property + def family_name(self) -> str: ... + @property + def short_family_name(self) -> str: ... + @property + def orthogonal(self) -> bool: ... + @orthogonal.setter + def orthogonal(self, value: bool) -> None: ... + @property + def biorthogonal(self) -> bool: ... + @biorthogonal.setter + def biorthogonal(self, value: bool) -> None: ... + @property + def symmetry(self) -> _Symmetry: ... + @property + def vanishing_moments_psi(self) -> int | None: ... + @property + def vanishing_moments_phi(self) -> int | None: ... + @property + def filter_bank( + self, + ) -> tuple[list[float], list[float], list[float], list[float]]: ... + def get_filters_coeffs( + self, + ) -> tuple[list[float], list[float], list[float], list[float]]: ... + @property + def inverse_filter_bank( + self, + ) -> tuple[list[float], list[float], list[float], list[float]]: ... + def get_reverse_filters_coeffs( + self, + ) -> tuple[list[float], list[float], list[float], list[float]]: ... + def wavefun(self, level: int = 8): ... + +class ContinuousWavelet: + def __init__( + self, name: str = "", dtype: type[np.float32 | np.float64] = np.float64 + ) -> None: ... + @property + def family_number(self) -> int: ... + @property + def family_name(self) -> str: ... + @property + def short_family_name(self) -> str: ... + @property + def orthogonal(self) -> bool: ... + @orthogonal.setter + def orthogonal(self, value: bool) -> None: ... + @property + def biorthogonal(self) -> bool: ... + @biorthogonal.setter + def biorthogonal(self, value: bool) -> None: ... + @property + def complex_cwt(self) -> bool: ... + @complex_cwt.setter + def complex_cwt(self, value: bool) -> None: ... + @property + def lower_bound(self) -> float | None: ... + @lower_bound.setter + def lower_bound(self, value: float) -> None: ... + @property + def upper_bound(self) -> float | None: ... + @upper_bound.setter + def upper_bound(self, value: float) -> None: ... + @property + def center_frequency(self) -> float | None: ... + @center_frequency.setter + def center_frequency(self, value: float) -> None: ... + @property + def bandwidth_frequency(self) -> float | None: ... + @bandwidth_frequency.setter + def bandwidth_frequency(self, value: float) -> None: ... + @property + def fbsp_order(self) -> int | None: ... + @fbsp_order.setter + def fbsp_order(self, value: int) -> None: ... + @property + def symmetry(self) -> _Symmetry: ... + def wavefun(self, level: int = 8, length: int | None = None): ... + +def DiscreteContinuousWavelet( + name: str = "", filter_bank: Any = None +) -> Wavelet | ContinuousWavelet: ... +def _check_dtype( + data: Any, +) -> np.float32 | np.float64 | np.complex64 | np.complex128: ... diff --git a/pywt/_extensions/_swt.pyi b/pywt/_extensions/_swt.pyi new file mode 100644 index 00000000..778d5bdf --- /dev/null +++ b/pywt/_extensions/_swt.pyi @@ -0,0 +1,24 @@ +from typing import Literal, TypeVar, overload + +import numpy as np +from numpy.typing import NDArray + +from ._pywt import Wavelet + +_CDataT = TypeVar( + "_CDataT", bound=np.float32 | np.float64 | np.complex64 | np.complex128 +) + +def swt_max_level(input_len: int) -> int: ... +@overload +def swt(data: NDArray[_CDataT], wavelet: Wavelet, level: int, start_level: int, trim_approx: Literal[False] = False) -> list[tuple[NDArray[_CDataT], NDArray[_CDataT]]]: ... +@overload +def swt(data: NDArray[_CDataT], wavelet: Wavelet, level: int, start_level: int, trim_approx: Literal[True]) -> list[NDArray[_CDataT]]: ... +@overload +def swt(data: NDArray[_CDataT], wavelet: Wavelet, level: int, start_level: int, trim_approx: bool = False) -> list[tuple[NDArray[_CDataT], NDArray[_CDataT]]] | list[NDArray[_CDataT]]: ... +@overload +def swt_axis(data: NDArray[_CDataT], wavelet: Wavelet, level: int, start_level: int, axis: int = 0, trim_approx: Literal[False] = False) -> list[tuple[NDArray[_CDataT], NDArray[_CDataT]]]: ... +@overload +def swt_axis(data: NDArray[_CDataT], wavelet: Wavelet, level: int, start_level: int, axis: int = 0, *, trim_approx: Literal[True]) -> list[NDArray[_CDataT]]: ... +@overload +def swt_axis(data: NDArray[_CDataT], wavelet: Wavelet, level: int, start_level: int, axis: int = 0, trim_approx: bool = False) -> list[tuple[NDArray[_CDataT], NDArray[_CDataT]]] | list[NDArray[_CDataT]]: ... diff --git a/pywt/_functions.py b/pywt/_functions.py index b2826d81..fb4e2b86 100644 --- a/pywt/_functions.py +++ b/pywt/_functions.py @@ -8,6 +8,7 @@ """ +from typing import Any import warnings import numpy as np @@ -25,37 +26,37 @@ "be removed in a future version of pywt.") -def _integrate(arr, step): +def _integrate(arr: np.ndarray | list, step: float) -> np.ndarray: integral = np.cumsum(arr) integral *= step return integral -def intwave(*args, **kwargs): +def intwave(*args: Any, **kwargs: Any) -> tuple[np.ndarray, ...]: msg = _DEPRECATION_MSG.format(old='intwave', new='integrate_wavelet') warnings.warn(msg, DeprecationWarning) return integrate_wavelet(*args, **kwargs) -def centrfrq(*args, **kwargs): +def centrfrq(*args: Any, **kwargs: Any) -> np.floating: msg = _DEPRECATION_MSG.format(old='centrfrq', new='central_frequency') warnings.warn(msg, DeprecationWarning) return central_frequency(*args, **kwargs) -def scal2frq(*args, **kwargs): +def scal2frq(*args: Any, **kwargs: Any) -> float: msg = _DEPRECATION_MSG.format(old='scal2frq', new='scale2frequency') warnings.warn(msg, DeprecationWarning) return scale2frequency(*args, **kwargs) -def orthfilt(*args, **kwargs): +def orthfilt(*args: Any, **kwargs: Any) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: msg = _DEPRECATION_MSG.format(old='orthfilt', new='orthogonal_filter_bank') warnings.warn(msg, DeprecationWarning) return orthogonal_filter_bank(*args, **kwargs) -def integrate_wavelet(wavelet, precision=8): +def integrate_wavelet(wavelet: Wavelet | list[Wavelet] | str, precision: int = 8) -> tuple[np.ndarray, ...]: """ Integrate `psi` wavelet function from -Inf to x using the rectangle integration method. @@ -118,7 +119,7 @@ def integrate_wavelet(wavelet, precision=8): return _integrate(psi_d, step), _integrate(psi_r, step), x -def central_frequency(wavelet, precision=8): +def central_frequency(wavelet: Wavelet | str, precision: int = 8) -> np.floating: """ Computes the central frequency of the `psi` wavelet function. @@ -159,7 +160,7 @@ def central_frequency(wavelet, precision=8): return 1.0 / (domain / (index - 1)) -def scale2frequency(wavelet, scale, precision=8): +def scale2frequency(wavelet: Wavelet | str, scale: float, precision: int = 8) -> float: """Convert from CWT "scale" to normalized frequency. Parameters @@ -182,7 +183,7 @@ def scale2frequency(wavelet, scale, precision=8): """ return central_frequency(wavelet, precision=precision) / scale -def frequency2scale(wavelet, freq, precision=8): +def frequency2scale(wavelet: Wavelet | str, freq: float, precision: int = 8) -> float: """Convert from to normalized frequency to CWT "scale". Parameters @@ -203,7 +204,7 @@ def frequency2scale(wavelet, freq, precision=8): """ return central_frequency(wavelet, precision=precision) / freq -def qmf(filt): +def qmf(filt: np.ndarray | list) -> np.ndarray: """ Returns the Quadrature Mirror Filter(QMF). @@ -226,7 +227,9 @@ def qmf(filt): return qm_filter -def orthogonal_filter_bank(scaling_filter): +def orthogonal_filter_bank( + scaling_filter: np.ndarray | list + ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """ Returns the orthogonal filter bank. diff --git a/pywt/_mra.py b/pywt/_mra.py index a41431e3..94913a5b 100644 --- a/pywt/_mra.py +++ b/pywt/_mra.py @@ -1,3 +1,5 @@ +from typing import Literal +from pywt import Wavelet from functools import partial, reduce import numpy as np @@ -17,8 +19,8 @@ __all__ = ["mra", "mra2", "mran", "imra", "imra2", "imran"] -def mra(data, wavelet, level=None, axis=-1, transform='swt', - mode='periodization'): +def mra(data: np.ndarray | list, wavelet: Wavelet | str, level: int | None = None, axis: int = -1, transform: Literal['swt', 'dwt'] = 'swt', + mode: str = 'periodization') -> list[np.ndarray]: """Forward 1D multiresolution analysis. It is a projection onto the wavelet subspaces. @@ -120,7 +122,7 @@ def mra(data, wavelet, level=None, axis=-1, transform='swt', return mra_coeffs -def imra(mra_coeffs): +def imra(mra_coeffs: np.ndarray | list) -> np.ndarray: """Inverse 1D multiresolution analysis via summation. Parameters @@ -147,8 +149,8 @@ def imra(mra_coeffs): return reduce(lambda x, y: x + y, mra_coeffs) -def mra2(data, wavelet, level=None, axes=(-2, -1), transform='swt2', - mode='periodization'): +def mra2(data: np.ndarray | list, wavelet: Wavelet | str, level: int | None = None, axes: tuple[int, int] = (-2, -1), transform: Literal['dwt2', 'swt2'] = 'swt2', + mode: str = 'periodization') -> list: """Forward 2D multiresolution analysis. It is a projection onto wavelet subspaces. @@ -256,7 +258,7 @@ def mra2(data, wavelet, level=None, axes=(-2, -1), transform='swt2', return mra_coeffs -def imra2(mra_coeffs): +def imra2(mra_coeffs: list) -> np.ndarray: """Inverse 2D multiresolution analysis via summation. Parameters @@ -287,8 +289,8 @@ def imra2(mra_coeffs): return rec -def mran(data, wavelet, level=None, axes=None, transform='swtn', - mode='periodization'): +def mran(data: np.ndarray | list, wavelet: Wavelet | str, level: int | None = None, axes: tuple[int, int] | None = None, transform: Literal['dwtn', 'swtn'] = 'swtn', + mode: str = 'periodization') -> list: """Forward nD multiresolution analysis. It is a projection onto the wavelet subspaces. @@ -403,7 +405,7 @@ def mran(data, wavelet, level=None, axes=None, transform='swtn', return mra_coeffs -def imran(mra_coeffs): +def imran(mra_coeffs: list) -> np.ndarray: """Inverse nD multiresolution analysis via summation. Parameters diff --git a/pywt/_multidim.py b/pywt/_multidim.py index 361c2cf2..9ee07925 100644 --- a/pywt/_multidim.py +++ b/pywt/_multidim.py @@ -8,6 +8,8 @@ """ +from typing import Sequence +from pywt import Wavelet from itertools import product import numpy as np @@ -18,7 +20,7 @@ __all__ = ['dwt2', 'idwt2', 'dwtn', 'idwtn'] -def dwt2(data, wavelet, mode='symmetric', axes=(-2, -1)): +def dwt2(data: np.ndarray | list, wavelet: Wavelet | str | tuple[Wavelet], mode: str | tuple[str, str] = 'symmetric', axes: tuple[int, int] = (-2, -1)) -> tuple: """ 2D Discrete Wavelet Transform. @@ -71,7 +73,7 @@ def dwt2(data, wavelet, mode='symmetric', axes=(-2, -1)): return coefs['aa'], (coefs['da'], coefs['ad'], coefs['dd']) -def idwt2(coeffs, wavelet, mode='symmetric', axes=(-2, -1)): +def idwt2(coeffs: tuple, wavelet: Wavelet | str | tuple[Wavelet], mode: str = 'symmetric', axes: tuple[int, int] = (-2, -1)) -> np.ndarray: """ 2-D Inverse Discrete Wavelet Transform. @@ -115,7 +117,7 @@ def idwt2(coeffs, wavelet, mode='symmetric', axes=(-2, -1)): return idwtn(coeffs, wavelet, mode, axes) -def dwtn(data, wavelet, mode='symmetric', axes=None): +def dwtn(data: np.ndarray | list, wavelet: Wavelet | str | tuple[Wavelet], mode: str = 'symmetric', axes: Sequence[int] | None = None) -> dict[str, np.ndarray]: """ Single-level n-dimensional Discrete Wavelet Transform. @@ -185,7 +187,7 @@ def dwtn(data, wavelet, mode='symmetric', axes=None): return dict(coeffs) -def _fix_coeffs(coeffs): +def _fix_coeffs(coeffs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: missing_keys = [k for k, v in coeffs.items() if v is None] if missing_keys: raise ValueError( @@ -211,7 +213,7 @@ def _fix_coeffs(coeffs): return {k: np.asarray(v) for k, v in coeffs.items()} -def idwtn(coeffs, wavelet, mode='symmetric', axes=None): +def idwtn(coeffs: dict[str, np.ndarray], wavelet: Wavelet | str, mode: str = 'symmetric', axes: Sequence[int] | None = None) -> np.ndarray: """ Single-level n-dimensional Inverse Discrete Wavelet Transform. diff --git a/pywt/_multilevel.py b/pywt/_multilevel.py index 0e5ca9cf..cba5ab37 100644 --- a/pywt/_multilevel.py +++ b/pywt/_multilevel.py @@ -9,10 +9,14 @@ """ +from __future__ import annotations + import numbers import warnings +from collections.abc import Mapping, Sequence from copy import copy from itertools import product +from typing import TYPE_CHECKING, Literal, overload import numpy as np @@ -22,13 +26,26 @@ from ._multidim import _fix_coeffs, dwt2, dwtn, idwt2, idwtn from ._utils import AxisError, _as_wavelet, _modes_per_axis, _wavelets_per_axis +if TYPE_CHECKING: + from ._extensions._pywt import Mode, ModeName + __all__ = ['wavedec', 'waverec', 'wavedec2', 'waverec2', 'wavedecn', 'waverecn', 'coeffs_to_array', 'array_to_coeffs', 'ravel_coeffs', 'unravel_coeffs', 'dwtn_max_level', 'wavedecn_size', 'wavedecn_shapes', 'fswavedecn', 'fswaverecn', 'FswavedecnResult'] +# Coefficient layouts returned by wavedec, wavedec2 and wavedecn: the +# approximation array first, followed by the detail coefficients per level. +_WavedecCoeffs = list[np.ndarray] +_Wavedec2Coeffs = list[np.ndarray | tuple[np.ndarray, np.ndarray, np.ndarray]] +_WavedecnCoeffs = list[np.ndarray | dict[str, np.ndarray]] + -def _check_level(sizes, dec_lens, level): +def _check_level( + sizes: int | Sequence[int], + dec_lens: int | Sequence[int], + level: int | None, +) -> int: if np.isscalar(sizes): sizes = (sizes, ) if np.isscalar(dec_lens): @@ -46,7 +63,13 @@ def _check_level(sizes, dec_lens, level): return level -def wavedec(data, wavelet, mode='symmetric', level=None, axis=-1): +def wavedec( + data: np.ndarray | list, + wavelet: Wavelet | str, + mode: Mode = 'symmetric', + level: int | None = None, + axis: int = -1, +) -> _WavedecCoeffs: """ Multilevel 1D Discrete Wavelet Transform of data. @@ -96,7 +119,7 @@ def wavedec(data, wavelet, mode='symmetric', level=None, axis=-1): raise AxisError("Axis greater than data dimensions") level = _check_level(axes_shape, wavelet.dec_len, level) - coeffs_list = [] + coeffs_list: _WavedecCoeffs = [] a = data for i in range(level): @@ -109,7 +132,12 @@ def wavedec(data, wavelet, mode='symmetric', level=None, axis=-1): return coeffs_list -def waverec(coeffs, wavelet, mode='symmetric', axis=-1): +def waverec( + coeffs: Sequence[np.ndarray | None], + wavelet: Wavelet | str, + mode: Mode = 'symmetric', + axis: int = -1, +) -> np.ndarray: """ Multilevel 1D Inverse Discrete Wavelet Transform. @@ -176,7 +204,13 @@ def waverec(coeffs, wavelet, mode='symmetric', axis=-1): return a -def wavedec2(data, wavelet, mode='symmetric', level=None, axes=(-2, -1)): +def wavedec2( + data: np.ndarray | list, + wavelet: Wavelet | str | Sequence[Wavelet | str], + mode: Mode | Sequence[Mode] = 'symmetric', + level: int | None = None, + axes: Sequence[int] = (-2, -1), +) -> _Wavedec2Coeffs: """ Multilevel 2D Discrete Wavelet Transform. @@ -240,7 +274,7 @@ def wavedec2(data, wavelet, mode='symmetric', level=None, axes=(-2, -1)): level = _check_level(axes_sizes, dec_lengths, level) - coeffs_list = [] + coeffs_list: _Wavedec2Coeffs = [] a = data for i in range(level): @@ -253,7 +287,15 @@ def wavedec2(data, wavelet, mode='symmetric', level=None, axes=(-2, -1)): return coeffs_list -def waverec2(coeffs, wavelet, mode='symmetric', axes=(-2, -1)): +def waverec2( + coeffs: Sequence[ + np.ndarray + | tuple[np.ndarray | None, np.ndarray | None, np.ndarray | None] + ], + wavelet: Wavelet | str | Sequence[Wavelet | str], + mode: Mode | Sequence[Mode] = 'symmetric', + axes: Sequence[int] = (-2, -1), +) -> np.ndarray: """ Multilevel 2D Inverse Discrete Wavelet Transform. @@ -337,7 +379,9 @@ def waverec2(coeffs, wavelet, mode='symmetric', axes=(-2, -1)): return a -def _prep_axes_wavedecn(shape, axes): +def _prep_axes_wavedecn( + shape: Sequence[int], axes: int | Sequence[int] | None +) -> tuple[Sequence[int], list[int], int]: if len(shape) < 1: raise ValueError("Expected at least 1D input data.") ndim = len(shape) @@ -357,7 +401,13 @@ def _prep_axes_wavedecn(shape, axes): return axes, axes_shapes, ndim_transform -def wavedecn(data, wavelet, mode='symmetric', level=None, axes=None): +def wavedecn( + data: np.ndarray | list, + wavelet: Wavelet | str | Sequence[Wavelet | str], + mode: Mode | Sequence[Mode] = 'symmetric', + level: int | None = None, + axes: Sequence[int] | None = None, +) -> _WavedecnCoeffs: """ Multilevel nD Discrete Wavelet Transform. @@ -428,7 +478,7 @@ def wavedecn(data, wavelet, mode='symmetric', level=None, axes=None): level = _check_level(axes_shapes, dec_lengths, level) - coeffs_list = [] + coeffs_list: _WavedecnCoeffs = [] a = data for i in range(level): @@ -442,7 +492,9 @@ def wavedecn(data, wavelet, mode='symmetric', level=None, axes=None): return coeffs_list -def _match_coeff_dims(a_coeff, d_coeff_dict): +def _match_coeff_dims( + a_coeff: np.ndarray | None, d_coeff_dict: dict[str, np.ndarray] +) -> np.ndarray | None: # For each axis, compare the approximation coeff shape to one of the # stored detail coeffs and truncate the last element along the axis # if necessary. @@ -458,7 +510,12 @@ def _match_coeff_dims(a_coeff, d_coeff_dict): return a_coeff[tuple(slice(s) for s in d_coeff.shape)] -def waverecn(coeffs, wavelet, mode='symmetric', axes=None): +def waverecn( + coeffs: Sequence[np.ndarray | dict[str, np.ndarray] | None], + wavelet: Wavelet | str | Sequence[Wavelet | str], + mode: Mode | Sequence[Mode] = 'symmetric', + axes: Sequence[int] | None = None, +) -> np.ndarray: """ Multilevel nD Inverse Discrete Wavelet Transform. @@ -578,7 +635,7 @@ def waverecn(coeffs, wavelet, mode='symmetric', axes=None): return a -def _coeffs_wavedec_to_wavedecn(coeffs): +def _coeffs_wavedec_to_wavedecn(coeffs: _WavedecCoeffs) -> _WavedecnCoeffs: """Convert wavedec coefficients to the wavedecn format.""" if len(coeffs) == 0: return coeffs @@ -592,7 +649,7 @@ def _coeffs_wavedec_to_wavedecn(coeffs): return coeffs -def _coeffs_wavedec2_to_wavedecn(coeffs): +def _coeffs_wavedec2_to_wavedecn(coeffs: _Wavedec2Coeffs) -> _WavedecnCoeffs: """Convert wavedec2 coefficients to the wavedecn format.""" if len(coeffs) == 0: return coeffs @@ -609,7 +666,9 @@ def _coeffs_wavedec2_to_wavedecn(coeffs): return coeffs -def _determine_coeff_array_shape(coeffs, axes): +def _determine_coeff_array_shape( + coeffs: _WavedecnCoeffs, axes: Sequence[int] +) -> tuple[tuple[int, ...], bool]: arr_shape = np.asarray(coeffs[0].shape) axes = np.asarray(axes) # axes that were transformed ndim_transform = len(axes) @@ -625,7 +684,10 @@ def _determine_coeff_array_shape(coeffs, axes): return arr_shape, is_tight_packing -def _prepare_coeffs_axes(coeffs, axes): +def _prepare_coeffs_axes( + coeffs: _WavedecCoeffs | _Wavedec2Coeffs | _WavedecnCoeffs, + axes: Sequence[int] | None, +) -> tuple[_WavedecnCoeffs, Sequence[int] | None, int, int | None]: """Helper function to check type of coeffs and axes. This code is used by both coeffs_to_array and ravel_coeffs. @@ -671,7 +733,11 @@ def _prepare_coeffs_axes(coeffs, axes): return coeffs, axes, ndim, ndim_transform -def coeffs_to_array(coeffs, padding=0, axes=None): +def coeffs_to_array( + coeffs: _WavedecCoeffs | _Wavedec2Coeffs | _WavedecnCoeffs, + padding: float | None = 0, + axes: Sequence[int] | None = None, +) -> tuple[np.ndarray, list[tuple[slice, ...] | dict[str, tuple[slice, ...]]]]: """ Arrange a wavelet coefficient list from ``wavedecn`` into a single array. @@ -768,7 +834,7 @@ def coeffs_to_array(coeffs, padding=0, axes=None): coeff_arr[a_slices] = a_coeffs # initialize list of coefficient slices - coeff_slices = [] + coeff_slices: list[tuple[slice, ...] | dict[str, tuple[slice, ...]]] = [] coeff_slices.append(a_slices) # loop over the detail coefficients, adding them to coeff_arr @@ -798,7 +864,29 @@ def coeffs_to_array(coeffs, padding=0, axes=None): return coeff_arr, coeff_slices -def array_to_coeffs(arr, coeff_slices, output_format='wavedecn'): +@overload +def array_to_coeffs( + arr: np.ndarray | list, + coeff_slices: Sequence[tuple[slice, ...] | dict[str, tuple[slice, ...]]], + output_format: Literal['wavedec'], +) -> _WavedecCoeffs: ... +@overload +def array_to_coeffs( + arr: np.ndarray | list, + coeff_slices: Sequence[tuple[slice, ...] | dict[str, tuple[slice, ...]]], + output_format: Literal['wavedec2'], +) -> _Wavedec2Coeffs: ... +@overload +def array_to_coeffs( + arr: np.ndarray | list, + coeff_slices: Sequence[tuple[slice, ...] | dict[str, tuple[slice, ...]]], + output_format: Literal['wavedecn'] = 'wavedecn', +) -> _WavedecnCoeffs: ... +def array_to_coeffs( + arr: np.ndarray | list, + coeff_slices: Sequence[tuple[slice, ...] | dict[str, tuple[slice, ...]]], + output_format: Literal['wavedec', 'wavedec2', 'wavedecn'] = 'wavedecn', +) -> _WavedecCoeffs | _Wavedec2Coeffs | _WavedecnCoeffs: """ Convert a combined array of coefficients back to a list compatible with ``waverecn``. @@ -887,7 +975,13 @@ def array_to_coeffs(arr, coeff_slices, output_format='wavedecn'): return coeffs -def wavedecn_shapes(shape, wavelet, mode='symmetric', level=None, axes=None): +def wavedecn_shapes( + shape: Sequence[int], + wavelet: Wavelet | str | Sequence[Wavelet | str], + mode: Mode | Sequence[Mode] = 'symmetric', + level: int | None = None, + axes: Sequence[int] | None = None, +) -> list[tuple[int, ...] | dict[str, tuple[int, ...]]]: """Subband shapes for a multilevel nD discrete wavelet transform. Parameters @@ -928,10 +1022,10 @@ def wavedecn_shapes(shape, wavelet, mode='symmetric', level=None, axes=None): level = _check_level(min(axes_shapes), max(dec_lengths), level) - shapes = [] + shapes: list[tuple[int, ...] | dict[str, tuple[int, ...]]] = [] for i in range(level): detail_keys = [''.join(c) for c in product('ad', repeat=len(axes))] - new_shapes = {k: list(shape) for k in detail_keys} + new_shapes: dict[str, list[int] | tuple[int, ...]] = {k: list(shape) for k in detail_keys} for axis, wav, mode in zip(axes, wavelets, modes): s = dwt_coeff_len(shape[axis], filter_len=wav.dec_len, mode=mode) for k in detail_keys: @@ -945,7 +1039,13 @@ def wavedecn_shapes(shape, wavelet, mode='symmetric', level=None, axes=None): return shapes -def wavedecn_size(shapes): +def wavedecn_size( + shapes: Sequence[ + tuple[int, ...] + | np.ndarray + | Mapping[str, tuple[int, ...] | np.ndarray] + ], +) -> int: """Compute the total number of wavedecn coefficients. Parameters @@ -972,7 +1072,7 @@ def wavedecn_size(shapes): >>> pywt.wavedecn_size(coeffs) 3087 """ - def _size(x): + def _size(x: tuple[int, ...] | np.ndarray) -> int: """Size corresponding to ``x`` as either a shape tuple or ndarray.""" if isinstance(x, np.ndarray): return x.size @@ -988,7 +1088,11 @@ def _size(x): return ncoeffs -def dwtn_max_level(shape, wavelet, axes=None): +def dwtn_max_level( + shape: Sequence[int], + wavelet: Wavelet | str | Sequence[Wavelet | str], + axes: Sequence[int] | None = None, +) -> int: """Compute the maximum level of decomposition for n-dimensional data. This returns the maximum number of levels of decomposition suitable for use @@ -1031,7 +1135,14 @@ def dwtn_max_level(shape, wavelet, axes=None): return min(max_levels) -def ravel_coeffs(coeffs, axes=None): +def ravel_coeffs( + coeffs: _WavedecCoeffs | _Wavedec2Coeffs | _WavedecnCoeffs, + axes: Sequence[int] | None = None, +) -> tuple[ + np.ndarray, + list[slice | dict[str, slice]], + list[tuple[int, ...] | dict[str, tuple[int, ...]]], +]: """Ravel a set of multilevel wavelet coefficients into a single 1D array. Parameters @@ -1089,8 +1200,8 @@ def ravel_coeffs(coeffs, axes=None): coeff_arr[a_slice] = a_coeffs.ravel() # initialize list of coefficient slices - coeff_slices = [] - coeff_shapes = [] + coeff_slices: list[slice | dict[str, slice]] = [] + coeff_shapes: list[tuple[int, ...] | dict[str, tuple[int, ...]]] = [] coeff_slices.append(a_slice) coeff_shapes.append(coeffs[0].shape) @@ -1116,7 +1227,33 @@ def ravel_coeffs(coeffs, axes=None): return coeff_arr, coeff_slices, coeff_shapes -def unravel_coeffs(arr, coeff_slices, coeff_shapes, output_format='wavedecn'): +@overload +def unravel_coeffs( + arr: np.ndarray | list, + coeff_slices: Sequence[slice | dict[str, slice]], + coeff_shapes: Sequence[tuple[int, ...] | dict[str, tuple[int, ...]]], + output_format: Literal['wavedec', 'swt'], +) -> _WavedecCoeffs: ... +@overload +def unravel_coeffs( + arr: np.ndarray | list, + coeff_slices: Sequence[slice | dict[str, slice]], + coeff_shapes: Sequence[tuple[int, ...] | dict[str, tuple[int, ...]]], + output_format: Literal['wavedec2', 'swt2'], +) -> _Wavedec2Coeffs: ... +@overload +def unravel_coeffs( + arr: np.ndarray | list, + coeff_slices: Sequence[slice | dict[str, slice]], + coeff_shapes: Sequence[tuple[int, ...] | dict[str, tuple[int, ...]]], + output_format: Literal['wavedecn', 'swtn'] = 'wavedecn', +) -> _WavedecnCoeffs: ... +def unravel_coeffs( + arr: np.ndarray | list, + coeff_slices: Sequence[slice | dict[str, slice]], + coeff_shapes: Sequence[tuple[int, ...] | dict[str, tuple[int, ...]]], + output_format: Literal['wavedec', 'wavedec2', 'wavedecn', 'swt', 'swt2', 'swtn'] = 'wavedecn', +) -> _WavedecCoeffs | _Wavedec2Coeffs | _WavedecnCoeffs: """Unravel a raveled array of multilevel wavelet coefficients. Parameters @@ -1191,7 +1328,7 @@ def unravel_coeffs(arr, coeff_slices, coeff_shapes, output_format='wavedecn'): return coeffs -def _check_fswavedecn_axes(data, axes): +def _check_fswavedecn_axes(data: np.ndarray, axes: Sequence[int]) -> None: """Axes checks common to fswavedecn, fswaverecn.""" if len(axes) != len(set(axes)): raise AxisError("The axes passed to fswavedecn must be unique.") @@ -1221,8 +1358,14 @@ class FswavedecnResult: The set of axes over which the transform was performed. """ - def __init__(self, coeffs, coeff_slices, wavelets, mode_enums, - axes): + def __init__( + self, + coeffs: np.ndarray, + coeff_slices: list[list[slice]], + wavelets: Sequence[Wavelet], + mode_enums: Sequence[int], + axes: Sequence[int], + ) -> None: self._coeffs = coeffs self._coeff_slices = coeff_slices self._axes = axes @@ -1236,80 +1379,80 @@ def __init__(self, coeffs, coeff_slices, wavelets, mode_enums, self._mode_enums = mode_enums @property - def coeffs(self): + def coeffs(self) -> np.ndarray: """ndarray: All coefficients stacked into a single array.""" return self._coeffs @coeffs.setter - def coeffs(self, c): + def coeffs(self, c: np.ndarray) -> None: if c.shape != self._coeffs.shape: raise ValueError("new coefficient array must match the existing " "coefficient shape") self._coeffs = c @property - def coeff_slices(self): + def coeff_slices(self) -> list[list[slice]]: """List: List of coefficient slices.""" return self._coeff_slices @property - def ndim(self): + def ndim(self) -> int: """int: Number of data dimensions.""" return self.coeffs.ndim @property - def ndim_transform(self): + def ndim_transform(self) -> int: """int: Number of axes transformed.""" return len(self.axes) @property - def axes(self): + def axes(self) -> Sequence[int]: """List of str: The axes the transform was performed along.""" return self._axes @property - def levels(self): + def levels(self) -> list[int]: """List of int: Levels of decomposition along each transformed axis.""" return [len(s) - 1 for s in self.coeff_slices] @property - def wavelets(self): + def wavelets(self) -> Sequence[Wavelet]: """List of pywt.DiscreteWavelet: wavelet for each transformed axis.""" return self._wavelets @property - def wavelet_names(self): + def wavelet_names(self) -> list[str]: """List of pywt.DiscreteWavelet: wavelet for each transformed axis.""" return [w.name for w in self._wavelets] @property - def modes(self): + def modes(self) -> list[ModeName]: """List of str: The border mode used along each transformed axis.""" names_dict = {getattr(Modes, mode): mode for mode in Modes.modes} return [names_dict[m] for m in self._mode_enums] - def _get_coef_sl(self, levels): + def _get_coef_sl(self, levels: Sequence[int]) -> tuple[slice, ...]: sl = [slice(None), ] * self.ndim for n, (ax, lev) in enumerate(zip(self.axes, levels)): sl[ax] = self.coeff_slices[n][lev] return tuple(sl) @property - def approx(self): + def approx(self) -> np.ndarray: """ndarray: The approximation coefficients.""" sl = self._get_coef_sl((0, )*self.ndim) return self._coeffs[sl] @approx.setter - def approx(self, a): + def approx(self, a: np.ndarray) -> None: sl = self._get_coef_sl((0, )*self.ndim) if self._coeffs[sl].shape != a.shape: raise ValueError( "x does not match the shape of the requested coefficient") self._coeffs[sl] = a - def _validate_index(self, levels): + def _validate_index(self, levels: Sequence[int]) -> None: levels = tuple(levels) if len(levels) != len(self.axes): @@ -1329,7 +1472,7 @@ def _validate_index(self, levels): raise ValueError( "Specified indices exceed the number of transform levels.") - def __getitem__(self, levels): + def __getitem__(self, levels: Sequence[int]) -> np.ndarray: """Retrieve a coefficient subband. Parameters @@ -1342,7 +1485,7 @@ def __getitem__(self, levels): sl = self._get_coef_sl(levels) return self._coeffs[sl] - def __setitem__(self, levels, x): + def __setitem__(self, levels: Sequence[int], x: np.ndarray) -> None: """Assign values to a coefficient subband. Parameters @@ -1365,7 +1508,7 @@ def __setitem__(self, levels, x): f"dtype {current_dtype}") self._coeffs[sl] = x - def detail_keys(self): + def detail_keys(self) -> list[tuple[int, ...]]: """Return a list of all detail coefficient keys. Returns @@ -1378,7 +1521,13 @@ def detail_keys(self): return sorted(keys) -def fswavedecn(data, wavelet, mode='symmetric', levels=None, axes=None): +def fswavedecn( + data: np.ndarray | list, + wavelet: Wavelet | str | Sequence[Wavelet | str], + mode: Mode | Sequence[Mode] = 'symmetric', + levels: int | Sequence[int | None] | None = None, + axes: Sequence[int] | None = None, +) -> FswavedecnResult: """Fully Separable Wavelet Decomposition. This is a variant of the multilevel discrete wavelet transform where all @@ -1491,7 +1640,7 @@ def fswavedecn(data, wavelet, mode='symmetric', levels=None, axes=None): return FswavedecnResult(coeffs_arr, coeff_slices, wavelets, modes, axes) -def fswaverecn(fswavedecn_result): +def fswaverecn(fswavedecn_result: FswavedecnResult) -> np.ndarray: """Fully Separable Inverse Wavelet Reconstruction. Parameters diff --git a/pywt/_swt.py b/pywt/_swt.py index 5588b8f7..27061b79 100644 --- a/pywt/_swt.py +++ b/pywt/_swt.py @@ -1,5 +1,6 @@ import warnings from itertools import product +from typing import Literal, overload import numpy as np @@ -11,12 +12,12 @@ from ._multidim import idwt2, idwtn from ._utils import AxisError, _as_wavelet, _wavelets_per_axis -__all__ = ["swt", "swt_max_level", 'iswt', 'swt2', 'iswt2', 'swtn', 'iswtn'] +__all__ = ["swt", "swt_max_level", "iswt", "swt2", "iswt2", "swtn", "iswtn"] -def _rescale_wavelet_filterbank(wavelet, sf): - wav = Wavelet(wavelet.name + 'r', - [np.asarray(f) * sf for f in wavelet.filter_bank]) + +def _rescale_wavelet_filterbank(wavelet: Wavelet, sf: float) -> Wavelet: + wav = Wavelet(wavelet.name + "r", [np.asarray(f) * sf for f in wavelet.filter_bank]) # copy attributes from the original wavelet wav.orthogonal = wavelet.orthogonal @@ -24,8 +25,46 @@ def _rescale_wavelet_filterbank(wavelet, sf): return wav -def swt(data, wavelet, level=None, start_level=0, axis=-1, - trim_approx=False, norm=False): +@overload +def swt( + data: np.ndarray | list, + wavelet: Wavelet | str, + level: int | None = None, + start_level: int = 0, + axis: int = -1, + trim_approx: Literal[False] = False, + norm: bool = False, +) -> list[tuple[np.ndarray, np.ndarray]]: ... +@overload +def swt( + data: np.ndarray | list, + wavelet: Wavelet | str, + level: int | None = None, + start_level: int = 0, + axis: int = -1, + *, + trim_approx: Literal[True], + norm: bool = False, +) -> list[np.ndarray]: ... +@overload +def swt( + data: np.ndarray | list, + wavelet: Wavelet | str, + level: int | None = None, + start_level: int = 0, + axis: int = -1, + trim_approx: bool = False, + norm: bool = False, +) -> list[tuple[np.ndarray, np.ndarray]] | list[np.ndarray]: ... +def swt( + data: np.ndarray | list, + wavelet: Wavelet | str, + level: int | None = None, + start_level: int = 0, + axis: int = -1, + trim_approx: bool = False, + norm: bool = False, +) -> list[tuple[np.ndarray, np.ndarray]] | list[np.ndarray]: """ Multilevel 1D stationary wavelet transform. @@ -123,8 +162,9 @@ def swt(data, wavelet, level=None, start_level=0, axis=-1, if not wavelet.orthogonal: warnings.warn( "norm=True, but the wavelet is not orthogonal: \n" - "\tThe conditions for energy preservation are not satisfied.") - wavelet = _rescale_wavelet_filterbank(wavelet, 1/np.sqrt(2)) + "\tThe conditions for energy preservation are not satisfied." + ) + wavelet = _rescale_wavelet_filterbank(wavelet, 1 / np.sqrt(2)) if axis < 0: axis = axis + data.ndim @@ -141,7 +181,12 @@ def swt(data, wavelet, level=None, start_level=0, axis=-1, return ret -def iswt(coeffs, wavelet, norm=False, axis=-1): +def iswt( + coeffs: list[tuple[np.ndarray, np.ndarray]], + wavelet: Wavelet | str, + norm: bool | None = False, + axis: int = -1, +) -> np.ndarray: """ Multilevel 1D inverse discrete stationary wavelet transform. @@ -179,9 +224,9 @@ def iswt(coeffs, wavelet, norm=False, axis=-1): if cA.ndim > 1: # convert to swtn coefficient format and call iswtn if trim_approx: - coeffs_nd = [cA] + [{'d': d} for d in coeffs[1:]] + coeffs_nd = [cA] + [{"d": d} for d in coeffs[1:]] else: - coeffs_nd = [{'a': a, 'd': d} for a, d in coeffs] + coeffs_nd = [{"a": a, "d": d} for a, d in coeffs] return iswtn(coeffs_nd, wavelet, axes=(axis,), norm=norm) elif axis != 0 and axis != -1: raise AxisError("Axis greater than data dimensions") @@ -200,9 +245,9 @@ def iswt(coeffs, wavelet, norm=False, axis=-1): wavelet = _as_wavelet(wavelet) if norm: wavelet = _rescale_wavelet_filterbank(wavelet, np.sqrt(2)) - mode = Modes.from_object('periodization') + mode = Modes.from_object("periodization") for j in range(num_levels, 0, -1): - step_size = int(pow(2, j-1)) + step_size = int(pow(2, j - 1)) last_index = step_size if trim_approx: cD = coeffs[-j] @@ -211,7 +256,7 @@ def iswt(coeffs, wavelet, norm=False, axis=-1): cD = np.asarray(cD, dtype=_check_dtype(cD)) if cD.dtype != output.dtype: # upcast to a common dtype (float64 or complex128) - if output.dtype.kind == 'c' or cD.dtype.kind == 'c': + if output.dtype.kind == "c" or cD.dtype.kind == "c": dtype = np.complex128 else: dtype = np.float64 @@ -231,24 +276,27 @@ def iswt(coeffs, wavelet, norm=False, axis=-1): # making sure to use periodic boundary conditions # Note: indexing with an array of ints returns a contiguous # copy as required by idwt_single. - x1 = idwt_single(output[even_indices], - cD[even_indices], - wavelet, mode) - x2 = idwt_single(output[odd_indices], - cD[odd_indices], - wavelet, mode) + x1 = idwt_single(output[even_indices], cD[even_indices], wavelet, mode) + x2 = idwt_single(output[odd_indices], cD[odd_indices], wavelet, mode) # perform a circular shift right x2 = np.roll(x2, 1) # average and insert into the correct indices - output[indices] = (x1 + x2)/2. + output[indices] = (x1 + x2) / 2.0 return output -def swt2(data, wavelet, level, start_level=0, axes=(-2, -1), - trim_approx=False, norm=False): +def swt2( + data: np.ndarray | list, + wavelet: Wavelet | str, + level: int, + start_level: int = 0, + axes: tuple[int, ...] = (-2, -1), + trim_approx: bool = False, + norm: bool = False, +) -> list[tuple[np.ndarray, ...]]: """ Multilevel 2D stationary wavelet transform. @@ -336,8 +384,7 @@ def swt2(data, wavelet, level, start_level=0, axes=(-2, -1), if len(axes) != len(set(axes)): raise ValueError("The axes passed to swt2 must be unique.") if data.ndim < len(np.unique(axes)): - raise ValueError("Input array has fewer dimensions than the specified " - "axes") + raise ValueError("Input array has fewer dimensions than the specified " "axes") coefs = swtn(data, wavelet, level, start_level, axes, trim_approx, norm) ret = [] @@ -346,13 +393,18 @@ def swt2(data, wavelet, level, start_level=0, axes=(-2, -1), coefs = coefs[1:] for c in coefs: if trim_approx: - ret.append((c['da'], c['ad'], c['dd'])) + ret.append((c["da"], c["ad"], c["dd"])) else: - ret.append((c['aa'], (c['da'], c['ad'], c['dd']))) + ret.append((c["aa"], (c["da"], c["ad"], c["dd"]))) return ret -def iswt2(coeffs, wavelet, norm=False, axes=(-2, -1)): +def iswt2( + coeffs: list[tuple[np.ndarray, np.ndarray]], + wavelet: Wavelet | str, + norm: bool | None = False, + axes: tuple[int, ...] = (-2, -1), +) -> np.ndarray: """ Multilevel 2D inverse discrete stationary wavelet transform. @@ -409,11 +461,11 @@ def iswt2(coeffs, wavelet, norm=False, axes=(-2, -1)): if cA.ndim != 2 or axes != (-2, -1): # convert to swtn coefficient format and call iswtn instead if trim_approx: - coeffs_nd = [cA] + [{'da': h, 'ad': v, 'dd': d} - for h, v, d in coeffs[1:]] + coeffs_nd = [cA] + [{"da": h, "ad": v, "dd": d} for h, v, d in coeffs[1:]] else: - coeffs_nd = [{'aa': a, 'da': h, 'ad': v, 'dd': d} - for a, (h, v, d) in coeffs] + coeffs_nd = [ + {"aa": a, "da": h, "ad": v, "dd": d} for a, (h, v, d) in coeffs + ] return iswtn(coeffs_nd, wavelet, axes=axes, norm=norm) if trim_approx: @@ -426,16 +478,16 @@ def iswt2(coeffs, wavelet, norm=False, axes=(-2, -1)): if output.ndim != 2: raise ValueError( "iswt2 only supports 2D arrays. see iswtn for a general " - "n-dimensionsal ISWT") + "n-dimensionsal ISWT" + ) # num_levels, equivalent to the decomposition level, n num_levels = len(coeffs) wavelets = _wavelets_per_axis(wavelet, axes=(0, 1)) if norm: - wavelets = [_rescale_wavelet_filterbank(wav, np.sqrt(2)) - for wav in wavelets] + wavelets = [_rescale_wavelet_filterbank(wav, np.sqrt(2)) for wav in wavelets] for j in range(num_levels): - step_size = int(pow(2, num_levels-j-1)) + step_size = int(pow(2, num_levels - j - 1)) last_index = step_size if trim_approx: (cH, cV, cD) = coeffs[j] @@ -443,13 +495,18 @@ def iswt2(coeffs, wavelet, norm=False, axes=(-2, -1)): _, (cH, cV, cD) = coeffs[j] # We are going to assume cH, cV, and cD are of equal size if (cH.shape != cV.shape) or (cH.shape != cD.shape): - raise RuntimeError( - "Mismatch in shape of intermediate coefficient arrays") + raise RuntimeError("Mismatch in shape of intermediate coefficient arrays") # make sure output shares the common dtype # (conversion of dtype for individual coeffs is handled within idwt2 ) - common_dtype = np.result_type(*( - [dt, ] + [_check_dtype(c) for c in [cH, cV, cD]])) + common_dtype = np.result_type( + *( + [ + dt, + ] + + [_check_dtype(c) for c in [cH, cV, cD]] + ) + ) if output.dtype != common_dtype: output = output.astype(common_dtype) @@ -459,33 +516,61 @@ def iswt2(coeffs, wavelet, norm=False, axes=(-2, -1)): indices_h = slice(first_h, cH.shape[0], step_size) indices_w = slice(first_w, cH.shape[1], step_size) - even_idx_h = slice(first_h, cH.shape[0], 2*step_size) - even_idx_w = slice(first_w, cH.shape[1], 2*step_size) - odd_idx_h = slice(first_h + step_size, cH.shape[0], 2*step_size) - odd_idx_w = slice(first_w + step_size, cH.shape[1], 2*step_size) + even_idx_h = slice(first_h, cH.shape[0], 2 * step_size) + even_idx_w = slice(first_w, cH.shape[1], 2 * step_size) + odd_idx_h = slice(first_h + step_size, cH.shape[0], 2 * step_size) + odd_idx_w = slice(first_w + step_size, cH.shape[1], 2 * step_size) # perform the inverse dwt on the selected indices, # making sure to use periodic boundary conditions - x1 = idwt2((output[even_idx_h, even_idx_w], - (cH[even_idx_h, even_idx_w], + x1 = idwt2( + ( + output[even_idx_h, even_idx_w], + ( + cH[even_idx_h, even_idx_w], cV[even_idx_h, even_idx_w], - cD[even_idx_h, even_idx_w])), - wavelets, 'periodization') - x2 = idwt2((output[even_idx_h, odd_idx_w], - (cH[even_idx_h, odd_idx_w], + cD[even_idx_h, even_idx_w], + ), + ), + wavelets, + "periodization", + ) + x2 = idwt2( + ( + output[even_idx_h, odd_idx_w], + ( + cH[even_idx_h, odd_idx_w], cV[even_idx_h, odd_idx_w], - cD[even_idx_h, odd_idx_w])), - wavelets, 'periodization') - x3 = idwt2((output[odd_idx_h, even_idx_w], - (cH[odd_idx_h, even_idx_w], + cD[even_idx_h, odd_idx_w], + ), + ), + wavelets, + "periodization", + ) + x3 = idwt2( + ( + output[odd_idx_h, even_idx_w], + ( + cH[odd_idx_h, even_idx_w], cV[odd_idx_h, even_idx_w], - cD[odd_idx_h, even_idx_w])), - wavelets, 'periodization') - x4 = idwt2((output[odd_idx_h, odd_idx_w], - (cH[odd_idx_h, odd_idx_w], + cD[odd_idx_h, even_idx_w], + ), + ), + wavelets, + "periodization", + ) + x4 = idwt2( + ( + output[odd_idx_h, odd_idx_w], + ( + cH[odd_idx_h, odd_idx_w], cV[odd_idx_h, odd_idx_w], - cD[odd_idx_h, odd_idx_w])), - wavelets, 'periodization') + cD[odd_idx_h, odd_idx_w], + ), + ), + wavelets, + "periodization", + ) # perform a circular shifts x2 = np.roll(x2, 1, axis=1) @@ -497,8 +582,15 @@ def iswt2(coeffs, wavelet, norm=False, axes=(-2, -1)): return output -def swtn(data, wavelet, level, start_level=0, axes=None, trim_approx=False, - norm=False): +def swtn( + data: np.ndarray | list, + wavelet: Wavelet | str, + level: int, + start_level: int = 0, + axes: tuple[int, ...] | None = None, + trim_approx: bool = False, + norm: bool | None = False, +) -> list[dict[str, np.ndarray]]: """ n-dimensional stationary wavelet transform. @@ -575,7 +667,7 @@ def swtn(data, wavelet, level, start_level=0, axes=None, trim_approx=False, """ data = np.asarray(data) - if data.dtype == np.dtype('object'): + if data.dtype == np.dtype("object"): raise TypeError("Input must be a numeric array-like") if data.ndim < 1: raise ValueError("Input data must be at least 1D") @@ -594,35 +686,40 @@ def swtn(data, wavelet, level, start_level=0, axes=None, trim_approx=False, if not np.all([wav.orthogonal for wav in wavelets]): warnings.warn( "norm=True, but the wavelets used are not orthogonal: \n" - "\tThe conditions for energy preservation are not satisfied.") - wavelets = [_rescale_wavelet_filterbank(wav, 1/np.sqrt(2)) - for wav in wavelets] + "\tThe conditions for energy preservation are not satisfied." + ) + wavelets = [ + _rescale_wavelet_filterbank(wav, 1 / np.sqrt(2)) for wav in wavelets + ] ret = [] for i in range(start_level, start_level + level): - coeffs = [('', data)] + coeffs = [("", data)] for axis, wavelet in zip(axes, wavelets): new_coeffs = [] for subband, x in coeffs: - cA, cD = _swt_axis(x, wavelet, level=1, start_level=i, - axis=axis)[0] - new_coeffs.extend([(subband + 'a', cA), - (subband + 'd', cD)]) + cA, cD = _swt_axis(x, wavelet, level=1, start_level=i, axis=axis)[0] + new_coeffs.extend([(subband + "a", cA), (subband + "d", cD)]) coeffs = new_coeffs coeffs = dict(coeffs) ret.append(coeffs) # data for the next level is the approximation coeffs from this level - data = coeffs['a' * num_axes] + data = coeffs["a" * num_axes] if trim_approx: - coeffs.pop('a' * num_axes) + coeffs.pop("a" * num_axes) if trim_approx: ret.append(data) ret.reverse() return ret -def iswtn(coeffs, wavelet, axes=None, norm=False): +def iswtn( + coeffs: list[dict], + wavelet: Wavelet | str, + axes: tuple[int, ...] | None = None, + norm: bool | None = False, +) -> np.ndarray: """ Multilevel nD inverse discrete stationary wavelet transform. @@ -663,7 +760,7 @@ def iswtn(coeffs, wavelet, axes=None, norm=False): # key length matches the number of axes transformed ndim_transform = max(len(key) for key in coeffs[-1]) trim_approx = not isinstance(coeffs[0], dict) - cA = coeffs[0] if trim_approx else coeffs[0]['a'*ndim_transform] + cA = coeffs[0] if trim_approx else coeffs[0]["a" * ndim_transform] if trim_approx: @@ -680,56 +777,84 @@ def iswtn(coeffs, wavelet, axes=None, norm=False): if len(axes) != len(set(axes)): raise ValueError("The axes passed to swtn must be unique.") if ndim_transform != len(axes): - raise ValueError("The number of axes used in iswtn must match the " - "number of dimensions transformed in swtn.") + raise ValueError( + "The number of axes used in iswtn must match the " + "number of dimensions transformed in swtn." + ) # num_levels, equivalent to the decomposition level, n num_levels = len(coeffs) wavelets = _wavelets_per_axis(wavelet, axes) if norm: - wavelets = [_rescale_wavelet_filterbank(wav, np.sqrt(2)) - for wav in wavelets] + wavelets = [_rescale_wavelet_filterbank(wav, np.sqrt(2)) for wav in wavelets] # initialize various slice objects used in the loops below # these will remain slice(None) only on axes that aren't transformed - indices = [slice(None), ]*ndim - even_indices = [slice(None), ]*ndim - odd_indices = [slice(None), ]*ndim - odd_even_slices = [slice(None), ]*ndim + indices = [ + slice(None), + ] * ndim + even_indices = [ + slice(None), + ] * ndim + odd_indices = [ + slice(None), + ] * ndim + odd_even_slices = [ + slice(None), + ] * ndim for j in range(num_levels): - step_size = int(pow(2, num_levels-j-1)) + step_size = int(pow(2, num_levels - j - 1)) last_index = step_size if not trim_approx: - a = coeffs[j].pop('a'*ndim_transform) # will restore later + a = coeffs[j].pop("a" * ndim_transform) # will restore later details = coeffs[j] # make sure dtype matches the coarsest level approximation coefficients - common_dtype = np.result_type(*( - [dt, ] + [v.dtype for v in details.values()])) + common_dtype = np.result_type( + *( + [ + dt, + ] + + [v.dtype for v in details.values()] + ) + ) if output.dtype != common_dtype: output = output.astype(common_dtype) # We assume all coefficient arrays are of equal size shapes = [v.shape for k, v in details.items()] if len(set(shapes)) != 1: - raise RuntimeError( - "Mismatch in shape of intermediate coefficient arrays") + raise RuntimeError("Mismatch in shape of intermediate coefficient arrays") # shape of a single coefficient array, excluding non-transformed axes coeff_trans_shape = tuple([shapes[0][ax] for ax in axes]) # nested loop over all combinations of axis offsets at this level - for firsts in product(*([range(last_index), ]*ndim_transform)): + for firsts in product( + *( + [ + range(last_index), + ] + * ndim_transform + ) + ): for first, sh, ax in zip(firsts, coeff_trans_shape, axes): indices[ax] = slice(first, sh, step_size) - even_indices[ax] = slice(first, sh, 2*step_size) - odd_indices[ax] = slice(first+step_size, sh, 2*step_size) + even_indices[ax] = slice(first, sh, 2 * step_size) + odd_indices[ax] = slice(first + step_size, sh, 2 * step_size) # nested loop over all combinations of odd/even inidices approx = output.copy() output[tuple(indices)] = 0 ntransforms = 0 - for odds in product(*([(0, 1), ]*ndim_transform)): + for odds in product( + *( + [ + (0, 1), + ] + * ndim_transform + ) + ): for o, ax in zip(odds, axes): if o: odd_even_slices[ax] = odd_indices[ax] @@ -739,12 +864,11 @@ def iswtn(coeffs, wavelet, axes=None, norm=False): details_slice = {} for key, value in details.items(): details_slice[key] = value[tuple(odd_even_slices)] - details_slice['a'*ndim_transform] = approx[ - tuple(odd_even_slices)] + details_slice["a" * ndim_transform] = approx[tuple(odd_even_slices)] # perform the inverse dwt on the selected indices, # making sure to use periodic boundary conditions - x = idwtn(details_slice, wavelets, 'periodization', axes=axes) + x = idwtn(details_slice, wavelets, "periodization", axes=axes) for o, ax in zip(odds, axes): # circular shift along any odd indexed axis if o: @@ -753,5 +877,5 @@ def iswtn(coeffs, wavelet, axes=None, norm=False): ntransforms += 1 output[tuple(indices)] /= ntransforms # normalize if not trim_approx: - coeffs[j]['a'*ndim_transform] = a # restore approx coeffs to dict + coeffs[j]["a" * ndim_transform] = a # restore approx coeffs to dict return output diff --git a/pywt/_thresholding.py b/pywt/_thresholding.py index 10a0127d..03d129ae 100644 --- a/pywt/_thresholding.py +++ b/pywt/_thresholding.py @@ -8,12 +8,14 @@ functions. """ +from typing import Literal + import numpy as np __all__ = ['threshold', 'threshold_firm'] -def soft(data, value, substitute=0): +def soft(data: np.ndarray | list, value: float, substitute: float = 0) -> np.ndarray: data = np.asarray(data) magnitude = np.absolute(data) @@ -33,7 +35,7 @@ def soft(data, value, substitute=0): return np.where(cond, substitute, thresholded) -def nn_garrote(data, value, substitute=0): +def nn_garrote(data: np.ndarray | list, value: float, substitute: float = 0) -> np.ndarray: """Non-negative Garrote.""" data = np.asarray(data) magnitude = np.absolute(data) @@ -53,20 +55,20 @@ def nn_garrote(data, value, substitute=0): return np.where(cond, substitute, thresholded) -def hard(data, value, substitute=0): +def hard(data: np.ndarray | list, value: float, substitute: float = 0) -> np.ndarray: data = np.asarray(data) cond = np.less(np.absolute(data), value) return np.where(cond, substitute, data) -def greater(data, value, substitute=0): +def greater(data: np.ndarray | list, value: float, substitute: float = 0) -> np.ndarray: data = np.asarray(data) if np.iscomplexobj(data): raise ValueError("greater thresholding only supports real data") return np.where(np.less(data, value), substitute, data) -def less(data, value, substitute=0): +def less(data: np.ndarray | list, value: float, substitute: float = 0) -> np.ndarray: data = np.asarray(data) if np.iscomplexobj(data): raise ValueError("less thresholding only supports real data") @@ -83,7 +85,12 @@ def less(data, value, substitute=0): } -def threshold(data, value, mode='soft', substitute=0): +def threshold( + data: np.ndarray | list, + value: float, + mode: Literal['soft', 'hard', 'garrote', 'greater', 'less'] = 'soft', + substitute: float = 0 + ) -> np.ndarray: """ Thresholds the input data depending on the mode argument. @@ -175,7 +182,7 @@ def threshold(data, value, mode='soft', substitute=0): .format(', '.join(keys))) -def threshold_firm(data, value_low, value_high): +def threshold_firm(data: np.ndarray | list, value_low: float, value_high: float) -> np.ndarray: """Firm threshold. The approach is intermediate between soft and hard thresholding [1]_. It diff --git a/pywt/_utils.py b/pywt/_utils.py index 0559724f..83b49fdf 100644 --- a/pywt/_utils.py +++ b/pywt/_utils.py @@ -1,3 +1,4 @@ +from __future__ import annotations # Copyright (c) 2017 The PyWavelets Developers # # See COPYING for license details. @@ -13,8 +14,13 @@ Wavelet, ) +from typing import TYPE_CHECKING -def _as_wavelet(wavelet): +if TYPE_CHECKING: + from ._extensions._pywt import ModeInt + + +def _as_wavelet(wavelet: Wavelet | str) -> Wavelet: """Convert wavelet name to a Wavelet object.""" if not isinstance(wavelet, (ContinuousWavelet, Wavelet)): wavelet = DiscreteContinuousWavelet(wavelet) @@ -27,7 +33,9 @@ def _as_wavelet(wavelet): return wavelet -def _wavelets_per_axis(wavelet, axes): +def _wavelets_per_axis( + wavelet: Wavelet | tuple[Wavelet, ...], axes: list[int] +) -> list[Wavelet]: """Initialize Wavelets for each axis to be transformed. Parameters @@ -63,7 +71,9 @@ def _wavelets_per_axis(wavelet, axes): return wavelets -def _modes_per_axis(modes, axes): +def _modes_per_axis( + modes: str | int | tuple[str | int], axes: list[int] +) -> list[ModeInt]: """Initialize mode for each axis to be transformed. Parameters diff --git a/pywt/_wavelet_packets.py b/pywt/_wavelet_packets.py index 787df443..57a272ab 100644 --- a/pywt/_wavelet_packets.py +++ b/pywt/_wavelet_packets.py @@ -1,3 +1,4 @@ +from __future__ import annotations # Copyright (c) 2006-2012 Filip Wasilewski # Copyright (c) 2012-2016 The PyWavelets Developers # @@ -13,13 +14,14 @@ from itertools import product import numpy as np +from typing import Any, Callable, Iterator, Literal from ._dwt import dwt, dwt_max_level, idwt from ._extensions._pywt import Wavelet, _check_dtype from ._multidim import dwt2, dwtn, idwt2, idwtn -def get_graycode_order(level, x='a', y='d'): +def get_graycode_order(level: int, x: str = 'a', y: str = 'd') -> list[str]: graycode_order = [x, y] for i in range(level - 1): graycode_order = [x + path for path in graycode_order] + \ @@ -55,7 +57,7 @@ class BaseNode: PART_LEN = None PARTS = None - def __init__(self, parent, data, node_name): + def __init__(self, parent: BaseNode | None, data: np.ndarray | None, node_name: str) -> None: self.parent = parent if parent is not None: self.wavelet = parent.wavelet @@ -82,15 +84,15 @@ def __init__(self, parent, data, node_name): self._init_subnodes() - def _init_subnodes(self): + def _init_subnodes(self) -> None: for part in self.PARTS: self._set_node(part, None) - def _create_subnode(self, part, data=None, overwrite=True): + def _create_subnode(self, part: str, data: np.ndarray | None = None, overwrite: bool = True) -> BaseNode: raise NotImplementedError() - def _create_subnode_base(self, node_cls, part, data=None, overwrite=True, - **kwargs): + def _create_subnode_base(self, node_cls: type[BaseNode], part: str, data: np.ndarray | None = None, overwrite: bool = True, + **kwargs: Any) -> BaseNode: self._validate_node_name(part) if not overwrite and self._get_node(part) is not None: return self._get_node(part) @@ -98,21 +100,21 @@ def _create_subnode_base(self, node_cls, part, data=None, overwrite=True, self._set_node(part, node) return node - def _get_node(self, part): + def _get_node(self, part: str) -> BaseNode: return getattr(self, part) - def _set_node(self, part, node): + def _set_node(self, part: str, node: BaseNode | None) -> None: setattr(self, part, node) - def _delete_node(self, part): + def _delete_node(self, part: str) -> None: self._set_node(part, None) - def _validate_node_name(self, part): + def _validate_node_name(self, part: str) -> None: if part not in self.PARTS: raise ValueError("Subnode name must be in [{}], not '{}'.".format(', '.join("'%s'" % p for p in self.PARTS), part)) @property - def path_tuple(self): + def path_tuple(self) -> tuple[str, ...]: """The path to the current node in tuple form. The length of the tuple is equal to the number of decomposition levels. @@ -122,7 +124,7 @@ def path_tuple(self): return tuple([path[(n-1)*self.PART_LEN:n*self.PART_LEN] for n in range(1, nlev+1)]) - def _evaluate_maxlevel(self, evaluate_from='parent'): + def _evaluate_maxlevel(self, evaluate_from: Literal['parent', 'subnodes'] = 'parent') -> int | None: """ Try to find the value of maximum decomposition level if it is not specified explicitly. @@ -152,7 +154,7 @@ def _evaluate_maxlevel(self, evaluate_from='parent'): return None @property - def maxlevel(self): + def maxlevel(self) -> int | None: if self._maxlevel is not None: return self._maxlevel @@ -165,10 +167,10 @@ def maxlevel(self): return self._maxlevel @property - def node_name(self): + def node_name(self) -> str: return self.path[-self.PART_LEN:] - def decompose(self): + def decompose(self) -> Any: """ Decompose node data creating DWT coefficients subnodes. @@ -186,10 +188,10 @@ def decompose(self): else: raise ValueError("Maximum decomposition level reached.") - def _decompose(self): + def _decompose(self) -> Any: raise NotImplementedError() - def reconstruct(self, update=False): + def reconstruct(self, update: bool = False) -> Any: """ Reconstruct node from subnodes. @@ -207,10 +209,10 @@ def reconstruct(self, update=False): return self.data return self._reconstruct(update) - def _reconstruct(self): + def _reconstruct(self, update: bool) -> Any: raise NotImplementedError() # override this in subclasses - def get_subnode(self, part, decompose=True): + def get_subnode(self, part: str, decompose: bool = True) -> BaseNode: """ Returns subnode or None (see `decomposition` flag description). @@ -231,7 +233,7 @@ def get_subnode(self, part, decompose=True): subnode = self._get_node(part) return subnode - def __getitem__(self, path): + def __getitem__(self, path: str | tuple[str, ...]) -> BaseNode: """ Find node represented by the given path. @@ -269,7 +271,7 @@ def __getitem__(self, path): else: raise TypeError(errmsg) - def __setitem__(self, path, data): + def __setitem__(self, path: str, data: np.ndarray | list | BaseNode) -> None: """ Set node or node's data in the decomposition tree. Nodes are identified by string `path`. @@ -306,7 +308,7 @@ def __setitem__(self, path, data): raise TypeError("Invalid path parameter type - expected string but" " got %s." % type(path)) - def __delitem__(self, path): + def __delitem__(self, path: str) -> None: """ Remove node from the tree. @@ -326,14 +328,14 @@ def __delitem__(self, path): parent._delete_node(node.node_name) @property - def is_empty(self): + def is_empty(self) -> bool: return self.data is None @property - def has_any_subnode(self): + def has_any_subnode(self) -> bool: return any(self._get_node(part) is not None for part in self.PARTS) - def get_leaf_nodes(self, decompose=False): + def get_leaf_nodes(self, decompose: bool = False) -> list[BaseNode]: """ Returns leaf nodes. @@ -344,7 +346,7 @@ def get_leaf_nodes(self, decompose=False): """ result = [] - def collect(node): + def collect(node: BaseNode): if node.level == node.maxlevel and not node.is_empty: result.append(node) return False @@ -355,7 +357,7 @@ def collect(node): self.walk(collect, decompose=decompose) return result - def walk(self, func, args=(), kwargs=None, decompose=True): + def walk(self, func: Callable, args: tuple = (), kwargs: dict | None = None, decompose: bool = True) -> None: """ Traverses the decomposition tree and calls ``func(node, *args, **kwargs)`` on every node. If `func` returns True, @@ -382,7 +384,7 @@ def walk(self, func, args=(), kwargs=None, decompose=True): if subnode is not None: subnode.walk(func, args, kwargs, decompose) - def walk_depth(self, func, args=(), kwargs=None, decompose=True): + def walk_depth(self, func: Callable, args: tuple = (), kwargs: dict | None = None, decompose: bool = True) -> None: """ Walk tree and call func on every node starting from the bottom-most nodes. @@ -408,7 +410,7 @@ def walk_depth(self, func, args=(), kwargs=None, decompose=True): subnode.walk_depth(func, args, kwargs, decompose) func(self, *args, **kwargs) - def __str__(self): + def __str__(self) -> str: return self.path + ": " + str(self.data) @@ -425,11 +427,11 @@ class Node(BaseNode): PARTS = A, D PART_LEN = 1 - def _create_subnode(self, part, data=None, overwrite=True): + def _create_subnode(self, part: str, data: np.ndarray | None = None, overwrite: bool = True) -> BaseNode: return self._create_subnode_base(node_cls=Node, part=part, data=data, overwrite=overwrite) - def _decompose(self): + def _decompose(self) -> tuple[BaseNode, BaseNode]: """ See also @@ -449,7 +451,7 @@ def _decompose(self): self._create_subnode(self.D, data_d) return self._get_node(self.A), self._get_node(self.D) - def _reconstruct(self, update): + def _reconstruct(self, update: bool) -> np.ndarray: data_a, data_d = None, None node_a, node_d = self._get_node(self.A), self._get_node(self.D) @@ -487,11 +489,11 @@ class Node2D(BaseNode): PARTS = LL, HL, LH, HH PART_LEN = 1 - def _create_subnode(self, part, data=None, overwrite=True): + def _create_subnode(self, part: str, data: np.ndarray | None = None, overwrite: bool = True) -> BaseNode: return self._create_subnode_base(node_cls=Node2D, part=part, data=data, overwrite=overwrite) - def _decompose(self): + def _decompose(self) -> tuple[BaseNode, BaseNode, BaseNode, BaseNode]: """ See also -------- @@ -509,7 +511,7 @@ def _decompose(self): return (self._get_node(self.LL), self._get_node(self.HL), self._get_node(self.LH), self._get_node(self.HH)) - def _reconstruct(self, update): + def _reconstruct(self, update: bool) -> np.ndarray: data_ll, data_lh, data_hl, data_hh = None, None, None, None node_ll, node_lh, node_hl, node_hh =\ @@ -541,7 +543,7 @@ def _reconstruct(self, update): self.data = rec return rec - def expand_2d_path(self, path): + def expand_2d_path(self, path: str) -> tuple[str, str]: expanded_paths = { self.HH: 'hh', self.HL: 'hl', @@ -579,7 +581,7 @@ class NodeND(BaseNode): The number of dimensions that are to be transformed. """ - def __init__(self, parent, data, node_name, ndim, ndim_transform): + def __init__(self, parent: BaseNode, data: np.ndarray, node_name: str, ndim: int, ndim_transform: int) -> None: super().__init__(parent=parent, data=data, node_name=node_name) self.PART_LEN = ndim_transform @@ -589,33 +591,33 @@ def __init__(self, parent, data, node_name, ndim, ndim_transform): self.ndim = ndim self.ndim_transform = ndim_transform - def _init_subnodes(self): + def _init_subnodes(self) -> None: # need this empty so BaseNode's _init_subnodes isn't called during # __init__. We use a dictionary for PARTS instead for the nd case. pass - def _get_node(self, part): + def _get_node(self, part: str) -> BaseNode: return self.PARTS[part] - def _set_node(self, part, node): + def _set_node(self, part: str, node: BaseNode | None) -> None: if part not in self.PARTS: raise ValueError("invalid part") self.PARTS[part] = node - def _delete_node(self, part): + def _delete_node(self, part: str) -> None: self._set_node(part, None) - def _validate_node_name(self, part): + def _validate_node_name(self, part: str) -> None: if part not in self.PARTS: raise ValueError( "Subnode name must be in [{}], not '{}'.".format(', '.join("'%s'" % p for p in list(self.PARTS.keys())), part)) - def _create_subnode(self, part, data=None, overwrite=True): + def _create_subnode(self, part: str, data: np.ndarray | None = None, overwrite: bool = True) -> BaseNode: return self._create_subnode_base(node_cls=NodeND, part=part, data=data, overwrite=overwrite, ndim=self.ndim, ndim_transform=self.ndim_transform) - def _evaluate_maxlevel(self, evaluate_from='parent'): + def _evaluate_maxlevel(self, evaluate_from: Literal['parent', 'subnodes'] = 'parent') -> int | None: """ Try to find the value of maximum decomposition level if it is not specified explicitly. @@ -643,7 +645,7 @@ def _evaluate_maxlevel(self, evaluate_from='parent'): return level return None - def _decompose(self): + def _decompose(self) -> Iterator[BaseNode]: """ See also -------- @@ -658,7 +660,7 @@ def _decompose(self): self._create_subnode(key, data) return (self._get_node(key) for key in self.PARTS) - def _reconstruct(self, update): + def _reconstruct(self, update: bool) -> np.ndarray: coeffs = {key: None for key in self.PARTS} nnodes = 0 @@ -703,8 +705,8 @@ class WaveletPacket(Node): axis : int, optional The axis to transform. """ - def __init__(self, data, wavelet, mode='symmetric', maxlevel=None, - axis=-1): + def __init__(self, data: np.ndarray | None, wavelet: Wavelet | str, mode: str = 'symmetric', maxlevel: int | None = None, + axis: int = -1) -> None: super().__init__(None, data, "") if not isinstance(wavelet, Wavelet): @@ -727,11 +729,11 @@ def __init__(self, data, wavelet, mode='symmetric', maxlevel=None, self._maxlevel = maxlevel - def __reduce__(self): + def __reduce__(self) -> tuple[type[WaveletPacket], tuple[Any, Wavelet, str, int | None]]: return (WaveletPacket, (self.data, self.wavelet, self.mode, self.maxlevel)) - def reconstruct(self, update=True): + def reconstruct(self, update: bool = True) -> np.ndarray: """ Reconstruct data value using coefficients from subnodes. @@ -750,7 +752,7 @@ def reconstruct(self, update=True): return data return self.data # return original data - def get_level(self, level, order="natural", decompose=True): + def get_level(self, level: int, order: Literal['natural', 'freq'] = "natural", decompose: bool = True) -> list[BaseNode]: """ Returns all nodes on the specified level. @@ -829,8 +831,8 @@ class WaveletPacket2D(Node2D): axes : 2-tuple of ints, optional The axes that will be transformed. """ - def __init__(self, data, wavelet, mode='smooth', maxlevel=None, - axes=(-2, -1)): + def __init__(self, data: np.ndarray, wavelet: Wavelet | str, mode: str = 'smooth', maxlevel: int | None = None, + axes: tuple[int, int] = (-2, -1)) -> None: super().__init__(None, data, "") if not isinstance(wavelet, Wavelet): @@ -853,11 +855,11 @@ def __init__(self, data, wavelet, mode='smooth', maxlevel=None, self.data_size = None self._maxlevel = maxlevel - def __reduce__(self): + def __reduce__(self) -> tuple[type[WaveletPacket2D], tuple[Any, Wavelet, str, int | None]]: return (WaveletPacket2D, (self.data, self.wavelet, self.mode, self.maxlevel)) - def reconstruct(self, update=True): + def reconstruct(self, update: bool = True) -> np.ndarray: """ Reconstruct data using coefficients from subnodes. @@ -876,7 +878,7 @@ def reconstruct(self, update=True): return data return self.data # return original data - def get_level(self, level, order="natural", decompose=True): + def get_level(self, level: int, order: Literal['natural', 'freq'] = "natural", decompose: bool = True) -> list[BaseNode]: """ Returns all nodes from specified level. @@ -961,8 +963,8 @@ class WaveletPacketND(NodeND): The axes to transform. The default value of `None` corresponds to all axes. """ - def __init__(self, data, wavelet, mode='smooth', maxlevel=None, - axes=None): + def __init__(self, data: np.ndarray, wavelet: Wavelet | str, mode: str = 'smooth', maxlevel: int | None = None, + axes: int | tuple[int, ...] | None = None) -> None: if (data is None) and (axes is None): # ndim is required to create a NodeND object raise ValueError("If data is None, axes must be specified") @@ -1005,7 +1007,7 @@ def __init__(self, data, wavelet, mode='smooth', maxlevel=None, self.data_size = None self._maxlevel = maxlevel - def reconstruct(self, update=True): + def reconstruct(self, update: bool = True) -> np.ndarray: """ Reconstruct data using coefficients from subnodes. @@ -1024,7 +1026,7 @@ def reconstruct(self, update=True): return data return self.data # return original data - def get_level(self, level, decompose=True): + def get_level(self, level: int, decompose: bool = True) -> list[BaseNode]: """ Returns all nodes from specified level. diff --git a/pywt/conftest.py b/pywt/conftest.py index da8ae645..5137a8d8 100644 --- a/pywt/conftest.py +++ b/pywt/conftest.py @@ -1,6 +1,96 @@ import pytest +import json +from pathlib import Path +from pytest import fixture +from typing import Iterator def pytest_configure(config): config.addinivalue_line("markers", "slow: Tests that are slow.") + + +@fixture +def baseline_report(tmp_path: Path) -> Iterator[Path]: + baseline_report_path = tmp_path / "pyrefly-test-baseline-report.json" + + mock_content = { + "module_reports": [ + { + "name": "test_file", + "names": [ + "test_file.function1", + "test_file.function2", + ], + "line_count": 100, + "symbol_reports": [ + { + "kind": "function", + "name": "test_file.function1", + "n_typable": 5, + "n_typed": 0, + "n_any": 0, + "n_untyped": 5, + "location": {"line": 20, "column": 1}, + }, + { + "kind": "function", + "name": "test_file.function2", + "n_typable": 5, + "n_typed": 5, + "n_any": 0, + "n_untyped": 0, + "location": {"line": 80, "column": 1}, + }, + ], + "type_ignores": [], + "n_typable": 10, + "n_typed": 5, + "n_any": 0, + "n_untyped": 5, + "coverage": 50.0, # 5/10 + } + ], + "summary": { + "n_modules": 1, + "n_typable": 10, + "n_typed": 5, + "n_any": 0, + "n_untyped": 5, + "coverage": 50.0, # 5/10 + }, + } + + with open(baseline_report_path, "w") as fp: + json.dump(mock_content, fp) + + yield baseline_report_path + + # Cleanup report file + baseline_report_path.unlink() + + +@pytest.fixture(scope="session") +def pyrefly_script_path(request) -> Path: + """ + Path to check_pyrefly_coverage.py script. + """ + pytest_root = Path(request.config.rootdir).resolve() + + print(pytest_root) + + repo_root = pytest_root + while repo_root != repo_root.parent: + if (repo_root / ".github").exists(): + break + repo_root = repo_root.parent + + script_path = repo_root / ".github" / "scripts" / "check_pyrefly_coverage.py" + + if not script_path.exists(): + raise FileNotFoundError( + f"Could not locate check_pyrefly_coverage.py at: {script_path}\n" + f"Current working directory: {Path.cwd()}" + ) + + return script_path diff --git a/pywt/data/_readers.py b/pywt/data/_readers.py index 8c2e7889..3d7ad3a7 100644 --- a/pywt/data/_readers.py +++ b/pywt/data/_readers.py @@ -8,7 +8,7 @@ @functools.cache -def ascent(): +def ascent() -> np.ndarray: """ Get an 8-bit grayscale bit-depth, 512 x 512 derived image for easy use in demos @@ -48,7 +48,7 @@ def ascent(): @functools.cache -def aero(): +def aero() -> np.ndarray: """ Get an 8-bit grayscale bit-depth, 512 x 512 derived image for easy use in demos @@ -85,7 +85,7 @@ def aero(): @functools.cache -def camera(): +def camera() -> np.ndarray: """ Get an 8-bit grayscale bit-depth, 512 x 512 derived image for easy use in demos @@ -133,7 +133,7 @@ def camera(): @functools.cache -def ecg(): +def ecg() -> np.ndarray: """ Get 1024 points of an ECG timeseries. @@ -165,7 +165,7 @@ def ecg(): @functools.cache -def nino(): +def nino() -> tuple[np.ndarray, np.ndarray]: """ This data contains the averaged monthly sea surface temperature in degrees Celsius of the Pacific Ocean, between 0-10 degrees South and 90-80 degrees West, from 1950 to 2016. diff --git a/pywt/data/_wavelab_signals.py b/pywt/data/_wavelab_signals.py index bd91b4ad..e97f3804 100644 --- a/pywt/data/_wavelab_signals.py +++ b/pywt/data/_wavelab_signals.py @@ -26,7 +26,7 @@ 'Riemann'] -def demo_signal(name='Bumps', n=None): +def demo_signal(name: str = 'Bumps', n: int | None = None) -> np.ndarray: """Simple 1D wavelet test functions. This function can generate a number of common 1D test signals used in diff --git a/pywt/tests/test_check_pyrefly_report.py b/pywt/tests/test_check_pyrefly_report.py new file mode 100644 index 00000000..edfab912 --- /dev/null +++ b/pywt/tests/test_check_pyrefly_report.py @@ -0,0 +1,208 @@ +import json +from pathlib import Path +import shutil +import subprocess +import sys + + +def test_no_changes_implies_no_regression( + baseline_report: Path, tmp_path: Path, pyrefly_script_path: Path +): + + current_report = tmp_path / "pyrefly-test-current-report.json" + + shutil.copy(baseline_report, current_report) + + process: subprocess.CompletedProcess = subprocess.run( + [ + sys.executable, + pyrefly_script_path, + "--baseline_report_path", + baseline_report, + "--current_report_path", + current_report, + ], + capture_output=True, + text=True, + ) + assert process.returncode == 0 + assert process.stdout == "No pyrefly coverage regressions detected.\n" + + +def test_increasing_type_coverage_implies_no_regression( + baseline_report: Path, tmp_path: Path, pyrefly_script_path: Path +): + + current_report = tmp_path / "pyrefly-test-current-report.json" + + shutil.copy(baseline_report, current_report) + + data = json.loads(current_report.read_text()) + data["module_reports"][0]["symbol_reports"][0]["n_typed"] += 1 + data["module_reports"][0]["symbol_reports"][0]["n_untyped"] -= 1 + data["module_reports"][0]["n_typed"] += 1 + data["module_reports"][0]["n_untyped"] -= 1 + data["module_reports"][0]["coverage"] += 10 + data["summary"]["coverage"] += 10 + + current_report.write_text(json.dumps(data)) + + process: subprocess.CompletedProcess = subprocess.run( + [ + sys.executable, + pyrefly_script_path, + "--baseline_report_path", + baseline_report, + "--current_report_path", + current_report, + ], + capture_output=True, + text=True, + ) + assert process.returncode == 0 + assert process.stdout == "No pyrefly coverage regressions detected.\n" + + +def test_decreasing_type_coverage_implies_regression( + baseline_report: Path, tmp_path: Path, pyrefly_script_path: Path +): + + current_report = tmp_path / "pyrefly-test-current-report.json" + + shutil.copy(baseline_report, current_report) + + data = json.loads(current_report.read_text()) + data["module_reports"][0]["symbol_reports"][1]["n_typed"] -= 1 + data["module_reports"][0]["symbol_reports"][1]["n_untyped"] += 1 + data["module_reports"][0]["n_typed"] -= 1 + data["module_reports"][0]["n_untyped"] += 1 + data["module_reports"][0]["coverage"] -= 10 + data["summary"]["coverage"] -= 10 + + current_report.write_text(json.dumps(data)) + + process: subprocess.CompletedProcess = subprocess.run( + [ + sys.executable, + pyrefly_script_path, + "--baseline_report_path", + baseline_report, + "--current_report_path", + current_report, + ], + capture_output=True, + text=True, + ) + assert process.returncode == 1 + assert "Pyrefly coverage regression detected" in process.stdout + assert "Untyped count increased from 5 to 6" in process.stdout + + +def test_adding_fully_annotated_file_implies_no_regression( + baseline_report: Path, tmp_path: Path, pyrefly_script_path: Path +): + + current_report = tmp_path / "pyrefly-test-current-report.json" + + shutil.copy(baseline_report, current_report) + + data = json.loads(current_report.read_text()) + data["module_reports"].append( + { + "name": "new_test_file", + "names": [ + "new_test_file.function1", + ], + "line_count": 100, + "symbol_reports": [ + { + "kind": "function", + "name": "test_file.function1", + "n_typable": 5, + "n_typed": 5, + "n_any": 0, + "n_untyped": 0, + "location": {"line": 1, "column": 1}, + }, + ], + "type_ignores": [], + "n_typable": 5, + "n_typed": 5, + "n_any": 0, + "n_untyped": 0, + "coverage": 100.0, + } + ) + data["summary"]["coverage"] = 75 # 10/15 + + current_report.write_text(json.dumps(data)) + + process: subprocess.CompletedProcess = subprocess.run( + [ + sys.executable, + pyrefly_script_path, + "--baseline_report_path", + baseline_report, + "--current_report_path", + current_report, + ], + capture_output=True, + text=True, + ) + assert process.returncode == 0 + assert process.stdout == "No pyrefly coverage regressions detected.\n" + + +def test_adding_partially_annotated_file_implies_regression( + baseline_report: Path, tmp_path: Path, pyrefly_script_path: Path +): + + current_report = tmp_path / "pyrefly-test-current-report.json" + + shutil.copy(baseline_report, current_report) + + data = json.loads(current_report.read_text()) + data["module_reports"].append( + { + "name": "new_test_file", + "names": [ + "new_test_file.function1", + ], + "line_count": 100, + "symbol_reports": [ + { + "kind": "function", + "name": "test_file.function1", + "n_typable": 5, + "n_typed": 4, + "n_any": 0, + "n_untyped": 1, + "location": {"line": 1, "column": 1}, + }, + ], + "type_ignores": [], + "n_typable": 5, + "n_typed": 4, + "n_any": 0, + "n_untyped": 1, + "coverage": 80.0, + } + ) + data["summary"]["coverage"] = 60 # 9/15 + + current_report.write_text(json.dumps(data)) + + process: subprocess.CompletedProcess = subprocess.run( + [ + sys.executable, + pyrefly_script_path, + "--baseline_report_path", + baseline_report, + "--current_report_path", + current_report, + ], + capture_output=True, + text=True, + ) + assert process.returncode == 1 + assert r"New file new_test_file is only 80.0% annotated" in process.stdout