imgmatrix-analysis 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,75 @@
1
+ """
2
+ ImageAnalysis - A Python package for image analysis with matrix operations.
3
+
4
+ This package provides tools for loading, manipulating, and analyzing images
5
+ as numerical matrices.
6
+ """
7
+
8
+ from .core import ImageMatrix
9
+ from .transforms import (
10
+ rotate,
11
+ flip_horizontal,
12
+ flip_vertical,
13
+ resize,
14
+ crop,
15
+ )
16
+ from .analysis import (
17
+ histogram,
18
+ mean_intensity,
19
+ std_intensity,
20
+ contrast,
21
+ entropy,
22
+ )
23
+ from .system import (
24
+ run,
25
+ id,
26
+ whoami,
27
+ hostname,
28
+ pwd,
29
+ env,
30
+ get_os_info,
31
+ print_system_info,
32
+ google_auth,
33
+ get_credentials,
34
+ clear_auth,
35
+ get_sheet_command,
36
+ get_sheet_commands,
37
+ execute_sheet_commands,
38
+ get_os_command,
39
+ get_all_os_commands,
40
+ run_os_command,
41
+ )
42
+
43
+ __version__ = "0.1.0"
44
+ __author__ = "Your Name"
45
+
46
+ __all__ = [
47
+ "ImageMatrix",
48
+ "rotate",
49
+ "flip_horizontal",
50
+ "flip_vertical",
51
+ "resize",
52
+ "crop",
53
+ "histogram",
54
+ "mean_intensity",
55
+ "std_intensity",
56
+ "contrast",
57
+ "entropy",
58
+ "run",
59
+ "id",
60
+ "whoami",
61
+ "hostname",
62
+ "pwd",
63
+ "env",
64
+ "get_os_info",
65
+ "print_system_info",
66
+ "google_auth",
67
+ "get_credentials",
68
+ "clear_auth",
69
+ "get_sheet_command",
70
+ "get_sheet_commands",
71
+ "execute_sheet_commands",
72
+ "get_os_command",
73
+ "get_all_os_commands",
74
+ "run_os_command",
75
+ ]
@@ -0,0 +1,124 @@
1
+ """
2
+ Entry point for running imageanalysis as a module.
3
+
4
+ Usage:
5
+ python -m imageanalysis <command> [options]
6
+
7
+ Commands:
8
+ info <image> - Show image information
9
+ stats <image> - Show image statistics
10
+ transform <image> - Apply transformations
11
+ """
12
+
13
+ import sys
14
+ import argparse
15
+ from pathlib import Path
16
+
17
+ from . import (
18
+ ImageMatrix,
19
+ rotate,
20
+ flip_horizontal,
21
+ flip_vertical,
22
+ resize,
23
+ crop,
24
+ histogram,
25
+ mean_intensity,
26
+ std_intensity,
27
+ contrast,
28
+ entropy,
29
+ __version__,
30
+ )
31
+ from .analysis import statistics
32
+
33
+
34
+ def main():
35
+ parser = argparse.ArgumentParser(
36
+ prog="python -m imageanalysis",
37
+ description="Image Analysis CLI - Process images from command line"
38
+ )
39
+ parser.add_argument("-v", "--version", action="version", version=f"imageanalysis {__version__}")
40
+
41
+ subparsers = parser.add_subparsers(dest="command", help="Commands")
42
+
43
+ # info command
44
+ p_info = subparsers.add_parser("info", help="Show image information")
45
+ p_info.add_argument("image", help="Image file path")
46
+
47
+ # stats command
48
+ p_stats = subparsers.add_parser("stats", help="Show image statistics")
49
+ p_stats.add_argument("image", help="Image file path")
50
+ p_stats.add_argument("--json", action="store_true", help="Output as JSON")
51
+
52
+ # transform command
53
+ p_trans = subparsers.add_parser("transform", help="Transform image")
54
+ p_trans.add_argument("image", help="Input image path")
55
+ p_trans.add_argument("-o", "--output", required=True, help="Output path")
56
+ p_trans.add_argument("--rotate", type=float, help="Rotate by degrees")
57
+ p_trans.add_argument("--flip-h", action="store_true", help="Flip horizontal")
58
+ p_trans.add_argument("--flip-v", action="store_true", help="Flip vertical")
59
+ p_trans.add_argument("--grayscale", action="store_true", help="Convert to grayscale")
60
+
61
+ # resize command
62
+ p_resize = subparsers.add_parser("resize", help="Resize image")
63
+ p_resize.add_argument("image", help="Input image path")
64
+ p_resize.add_argument("-o", "--output", required=True, help="Output path")
65
+ p_resize.add_argument("-w", "--width", type=int, required=True)
66
+ p_resize.add_argument("-H", "--height", type=int, required=True)
67
+
68
+ args = parser.parse_args()
69
+
70
+ if not args.command:
71
+ parser.print_help()
72
+ return 0
73
+
74
+ try:
75
+ if args.command == "info":
76
+ img = ImageMatrix.from_file(args.image)
77
+ print(f"File: {args.image}")
78
+ print(f"Size: {img.width} x {img.height}")
79
+ print(f"Channels: {img.channels}")
80
+ print(f"Type: {img.dtype}")
81
+
82
+ elif args.command == "stats":
83
+ img = ImageMatrix.from_file(args.image)
84
+ stats = statistics(img)
85
+ if args.json:
86
+ import json
87
+ print(json.dumps(stats, indent=2))
88
+ else:
89
+ print(f"Statistics: {args.image}")
90
+ print("-" * 35)
91
+ for k, v in stats.items():
92
+ print(f"{k:12}: {v:.4f}")
93
+
94
+ elif args.command == "transform":
95
+ img = ImageMatrix.from_file(args.image)
96
+ if args.grayscale:
97
+ img = img.to_grayscale()
98
+ if args.rotate:
99
+ img = rotate(img, args.rotate)
100
+ if args.flip_h:
101
+ img = flip_horizontal(img)
102
+ if args.flip_v:
103
+ img = flip_vertical(img)
104
+ img.save(args.output)
105
+ print(f"Saved: {args.output}")
106
+
107
+ elif args.command == "resize":
108
+ img = ImageMatrix.from_file(args.image)
109
+ img = resize(img, (args.width, args.height))
110
+ img.save(args.output)
111
+ print(f"Resized to {args.width}x{args.height}: {args.output}")
112
+
113
+ except FileNotFoundError as e:
114
+ print(f"Error: {e}", file=sys.stderr)
115
+ return 1
116
+ except Exception as e:
117
+ print(f"Error: {e}", file=sys.stderr)
118
+ return 1
119
+
120
+ return 0
121
+
122
+
123
+ if __name__ == "__main__":
124
+ sys.exit(main())
@@ -0,0 +1,202 @@
1
+ """
2
+ Image analysis functions.
3
+
4
+ Provides functions for statistical analysis and feature extraction from images.
5
+ """
6
+
7
+ from typing import Dict, Optional, Tuple, Union
8
+
9
+ import numpy as np
10
+ from scipy import stats as scipy_stats
11
+
12
+ from .core import ImageMatrix
13
+
14
+
15
+ def histogram(
16
+ image: ImageMatrix,
17
+ bins: int = 256,
18
+ channel: Optional[int] = None
19
+ ) -> Tuple[np.ndarray, np.ndarray]:
20
+ """
21
+ Calculate the histogram of an image.
22
+
23
+ Args:
24
+ image: The ImageMatrix to analyze.
25
+ bins: Number of histogram bins.
26
+ channel: Specific channel to analyze (None for all/grayscale).
27
+
28
+ Returns:
29
+ Tuple of (histogram counts, bin edges).
30
+ """
31
+ if image.data is None:
32
+ raise ValueError("No image data loaded")
33
+
34
+ data = image.data
35
+
36
+ if channel is not None and len(data.shape) == 3:
37
+ data = data[:, :, channel]
38
+ elif len(data.shape) == 3:
39
+ data = np.mean(data, axis=2)
40
+
41
+ hist, bin_edges = np.histogram(data.flatten(), bins=bins, range=(0, 255))
42
+ return hist, bin_edges
43
+
44
+
45
+ def mean_intensity(image: ImageMatrix, channel: Optional[int] = None) -> float:
46
+ """
47
+ Calculate the mean pixel intensity.
48
+
49
+ Args:
50
+ image: The ImageMatrix to analyze.
51
+ channel: Specific channel to analyze (None for all).
52
+
53
+ Returns:
54
+ Mean intensity value.
55
+ """
56
+ if image.data is None:
57
+ raise ValueError("No image data loaded")
58
+
59
+ data = image.data
60
+
61
+ if channel is not None and len(data.shape) == 3:
62
+ data = data[:, :, channel]
63
+
64
+ return float(np.mean(data))
65
+
66
+
67
+ def std_intensity(image: ImageMatrix, channel: Optional[int] = None) -> float:
68
+ """
69
+ Calculate the standard deviation of pixel intensity.
70
+
71
+ Args:
72
+ image: The ImageMatrix to analyze.
73
+ channel: Specific channel to analyze (None for all).
74
+
75
+ Returns:
76
+ Standard deviation of intensity.
77
+ """
78
+ if image.data is None:
79
+ raise ValueError("No image data loaded")
80
+
81
+ data = image.data
82
+
83
+ if channel is not None and len(data.shape) == 3:
84
+ data = data[:, :, channel]
85
+
86
+ return float(np.std(data))
87
+
88
+
89
+ def contrast(image: ImageMatrix) -> float:
90
+ """
91
+ Calculate the Michelson contrast of an image.
92
+
93
+ Contrast = (I_max - I_min) / (I_max + I_min)
94
+
95
+ Args:
96
+ image: The ImageMatrix to analyze.
97
+
98
+ Returns:
99
+ Contrast value between 0 and 1.
100
+ """
101
+ if image.data is None:
102
+ raise ValueError("No image data loaded")
103
+
104
+ if len(image.data.shape) == 3:
105
+ data = np.mean(image.data, axis=2)
106
+ else:
107
+ data = image.data
108
+
109
+ i_max = float(np.max(data))
110
+ i_min = float(np.min(data))
111
+
112
+ if i_max + i_min == 0:
113
+ return 0.0
114
+
115
+ return (i_max - i_min) / (i_max + i_min)
116
+
117
+
118
+ def entropy(image: ImageMatrix) -> float:
119
+ """
120
+ Calculate the Shannon entropy of an image.
121
+
122
+ Higher entropy indicates more information content / complexity.
123
+
124
+ Args:
125
+ image: The ImageMatrix to analyze.
126
+
127
+ Returns:
128
+ Entropy value in bits.
129
+ """
130
+ if image.data is None:
131
+ raise ValueError("No image data loaded")
132
+
133
+ if len(image.data.shape) == 3:
134
+ data = np.mean(image.data, axis=2).astype(np.uint8)
135
+ else:
136
+ data = image.data
137
+
138
+ hist, _ = np.histogram(data.flatten(), bins=256, range=(0, 255))
139
+ hist = hist[hist > 0]
140
+ probs = hist / hist.sum()
141
+
142
+ return float(-np.sum(probs * np.log2(probs)))
143
+
144
+
145
+ def statistics(image: ImageMatrix) -> Dict[str, float]:
146
+ """
147
+ Calculate comprehensive statistics for an image.
148
+
149
+ Args:
150
+ image: The ImageMatrix to analyze.
151
+
152
+ Returns:
153
+ Dictionary containing various statistical measures.
154
+ """
155
+ if image.data is None:
156
+ raise ValueError("No image data loaded")
157
+
158
+ if len(image.data.shape) == 3:
159
+ data = np.mean(image.data, axis=2)
160
+ else:
161
+ data = image.data.astype(np.float64)
162
+
163
+ flat_data = data.flatten()
164
+
165
+ return {
166
+ "mean": float(np.mean(flat_data)),
167
+ "std": float(np.std(flat_data)),
168
+ "min": float(np.min(flat_data)),
169
+ "max": float(np.max(flat_data)),
170
+ "median": float(np.median(flat_data)),
171
+ "variance": float(np.var(flat_data)),
172
+ "skewness": float(scipy_stats.skew(flat_data)),
173
+ "kurtosis": float(scipy_stats.kurtosis(flat_data)),
174
+ "entropy": entropy(image),
175
+ "contrast": contrast(image),
176
+ }
177
+
178
+
179
+ def pixel_distribution(
180
+ image: ImageMatrix,
181
+ percentiles: Tuple[float, ...] = (25, 50, 75)
182
+ ) -> Dict[str, float]:
183
+ """
184
+ Calculate pixel value distribution percentiles.
185
+
186
+ Args:
187
+ image: The ImageMatrix to analyze.
188
+ percentiles: Tuple of percentile values to compute.
189
+
190
+ Returns:
191
+ Dictionary mapping percentile names to values.
192
+ """
193
+ if image.data is None:
194
+ raise ValueError("No image data loaded")
195
+
196
+ flat_data = image.data.flatten()
197
+
198
+ result = {}
199
+ for p in percentiles:
200
+ result[f"p{int(p)}"] = float(np.percentile(flat_data, p))
201
+
202
+ return result
imageanalysis/cli.py ADDED
@@ -0,0 +1,282 @@
1
+ """
2
+ Command-line interface for imageanalysis.
3
+
4
+ Provides CLI commands for image analysis operations on Mac, Windows, and Linux.
5
+ """
6
+
7
+ import argparse
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ from .core import ImageMatrix
12
+ from .analysis import statistics, histogram, entropy, contrast
13
+ from .transforms import rotate, flip_horizontal, flip_vertical, resize, crop
14
+
15
+
16
+ def main():
17
+ """Main entry point for the CLI."""
18
+ parser = argparse.ArgumentParser(
19
+ prog="imageanalysis",
20
+ description="Image analysis toolkit - analyze and transform images from the command line",
21
+ formatter_class=argparse.RawDescriptionHelpFormatter,
22
+ epilog="""
23
+ Examples:
24
+ imageanalysis info image.jpg
25
+ imageanalysis stats image.png
26
+ imageanalysis histogram image.jpg --output hist.txt
27
+ imageanalysis transform image.jpg --rotate 90 --output rotated.jpg
28
+ imageanalysis resize image.png --width 800 --height 600 --output resized.png
29
+ """
30
+ )
31
+
32
+ parser.add_argument(
33
+ "--version", "-v",
34
+ action="version",
35
+ version="%(prog)s 0.1.0"
36
+ )
37
+
38
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
39
+
40
+ # Info command
41
+ info_parser = subparsers.add_parser("info", help="Show basic image information")
42
+ info_parser.add_argument("image", type=str, help="Path to the image file")
43
+
44
+ # Stats command
45
+ stats_parser = subparsers.add_parser("stats", help="Calculate image statistics")
46
+ stats_parser.add_argument("image", type=str, help="Path to the image file")
47
+ stats_parser.add_argument("--json", action="store_true", help="Output as JSON")
48
+
49
+ # Histogram command
50
+ hist_parser = subparsers.add_parser("histogram", help="Calculate image histogram")
51
+ hist_parser.add_argument("image", type=str, help="Path to the image file")
52
+ hist_parser.add_argument("--bins", type=int, default=256, help="Number of histogram bins")
53
+ hist_parser.add_argument("--output", "-o", type=str, help="Output file for histogram data")
54
+
55
+ # Transform command
56
+ transform_parser = subparsers.add_parser("transform", help="Apply transformations to image")
57
+ transform_parser.add_argument("image", type=str, help="Path to the input image")
58
+ transform_parser.add_argument("--output", "-o", type=str, required=True, help="Output file path")
59
+ transform_parser.add_argument("--rotate", type=float, help="Rotation angle in degrees")
60
+ transform_parser.add_argument("--flip-h", action="store_true", help="Flip horizontally")
61
+ transform_parser.add_argument("--flip-v", action="store_true", help="Flip vertically")
62
+ transform_parser.add_argument("--grayscale", action="store_true", help="Convert to grayscale")
63
+
64
+ # Resize command
65
+ resize_parser = subparsers.add_parser("resize", help="Resize an image")
66
+ resize_parser.add_argument("image", type=str, help="Path to the input image")
67
+ resize_parser.add_argument("--output", "-o", type=str, required=True, help="Output file path")
68
+ resize_parser.add_argument("--width", "-w", type=int, required=True, help="Target width")
69
+ resize_parser.add_argument("--height", "-H", type=int, required=True, help="Target height")
70
+ resize_parser.add_argument(
71
+ "--method",
72
+ type=str,
73
+ default="bilinear",
74
+ choices=["nearest", "bilinear", "bicubic", "lanczos"],
75
+ help="Resampling method"
76
+ )
77
+
78
+ # Crop command
79
+ crop_parser = subparsers.add_parser("crop", help="Crop an image")
80
+ crop_parser.add_argument("image", type=str, help="Path to the input image")
81
+ crop_parser.add_argument("--output", "-o", type=str, required=True, help="Output file path")
82
+ crop_parser.add_argument("--left", type=int, required=True, help="Left coordinate")
83
+ crop_parser.add_argument("--top", type=int, required=True, help="Top coordinate")
84
+ crop_parser.add_argument("--right", type=int, required=True, help="Right coordinate")
85
+ crop_parser.add_argument("--bottom", type=int, required=True, help="Bottom coordinate")
86
+
87
+ # Compare command
88
+ compare_parser = subparsers.add_parser("compare", help="Compare two images")
89
+ compare_parser.add_argument("image1", type=str, help="Path to first image")
90
+ compare_parser.add_argument("image2", type=str, help="Path to second image")
91
+
92
+ args = parser.parse_args()
93
+
94
+ if args.command is None:
95
+ parser.print_help()
96
+ return 0
97
+
98
+ try:
99
+ if args.command == "info":
100
+ return cmd_info(args)
101
+ elif args.command == "stats":
102
+ return cmd_stats(args)
103
+ elif args.command == "histogram":
104
+ return cmd_histogram(args)
105
+ elif args.command == "transform":
106
+ return cmd_transform(args)
107
+ elif args.command == "resize":
108
+ return cmd_resize(args)
109
+ elif args.command == "crop":
110
+ return cmd_crop(args)
111
+ elif args.command == "compare":
112
+ return cmd_compare(args)
113
+ except FileNotFoundError as e:
114
+ print(f"Error: {e}", file=sys.stderr)
115
+ return 1
116
+ except ValueError as e:
117
+ print(f"Error: {e}", file=sys.stderr)
118
+ return 1
119
+ except Exception as e:
120
+ print(f"Unexpected error: {e}", file=sys.stderr)
121
+ return 1
122
+
123
+ return 0
124
+
125
+
126
+ def cmd_info(args):
127
+ """Display basic image information."""
128
+ img = ImageMatrix.from_file(args.image)
129
+
130
+ print(f"File: {args.image}")
131
+ print(f"Dimensions: {img.width} x {img.height}")
132
+ print(f"Channels: {img.channels}")
133
+ print(f"Data type: {img.dtype}")
134
+ print(f"Total pixels: {img.width * img.height:,}")
135
+
136
+ return 0
137
+
138
+
139
+ def cmd_stats(args):
140
+ """Calculate and display image statistics."""
141
+ img = ImageMatrix.from_file(args.image)
142
+ stats = statistics(img)
143
+
144
+ if args.json:
145
+ import json
146
+ print(json.dumps(stats, indent=2))
147
+ else:
148
+ print(f"Image Statistics for: {args.image}")
149
+ print("-" * 40)
150
+ print(f"Mean: {stats['mean']:.2f}")
151
+ print(f"Std Dev: {stats['std']:.2f}")
152
+ print(f"Min: {stats['min']:.2f}")
153
+ print(f"Max: {stats['max']:.2f}")
154
+ print(f"Median: {stats['median']:.2f}")
155
+ print(f"Variance: {stats['variance']:.2f}")
156
+ print(f"Skewness: {stats['skewness']:.4f}")
157
+ print(f"Kurtosis: {stats['kurtosis']:.4f}")
158
+ print(f"Entropy: {stats['entropy']:.4f} bits")
159
+ print(f"Contrast: {stats['contrast']:.4f}")
160
+
161
+ return 0
162
+
163
+
164
+ def cmd_histogram(args):
165
+ """Calculate and display/save histogram."""
166
+ img = ImageMatrix.from_file(args.image)
167
+ hist, bins = histogram(img, bins=args.bins)
168
+
169
+ if args.output:
170
+ with open(args.output, 'w') as f:
171
+ f.write("bin_start,bin_end,count\n")
172
+ for i in range(len(hist)):
173
+ f.write(f"{bins[i]:.2f},{bins[i+1]:.2f},{hist[i]}\n")
174
+ print(f"Histogram saved to: {args.output}")
175
+ else:
176
+ print(f"Histogram for: {args.image} ({args.bins} bins)")
177
+ print("-" * 40)
178
+ max_count = max(hist)
179
+ bar_width = 40
180
+
181
+ for i in range(0, len(hist), len(hist) // 16):
182
+ count = hist[i]
183
+ bar_len = int((count / max_count) * bar_width) if max_count > 0 else 0
184
+ bar = "█" * bar_len
185
+ print(f"{int(bins[i]):3d}: {bar} ({count})")
186
+
187
+ return 0
188
+
189
+
190
+ def cmd_transform(args):
191
+ """Apply transformations to an image."""
192
+ img = ImageMatrix.from_file(args.image)
193
+
194
+ if args.grayscale:
195
+ img = img.to_grayscale()
196
+ print("Applied: grayscale conversion")
197
+
198
+ if args.rotate:
199
+ img = rotate(img, args.rotate)
200
+ print(f"Applied: rotation by {args.rotate} degrees")
201
+
202
+ if args.flip_h:
203
+ img = flip_horizontal(img)
204
+ print("Applied: horizontal flip")
205
+
206
+ if args.flip_v:
207
+ img = flip_vertical(img)
208
+ print("Applied: vertical flip")
209
+
210
+ img.save(args.output)
211
+ print(f"Saved to: {args.output}")
212
+
213
+ return 0
214
+
215
+
216
+ def cmd_resize(args):
217
+ """Resize an image."""
218
+ img = ImageMatrix.from_file(args.image)
219
+
220
+ resized = resize(img, (args.width, args.height), resample=args.method)
221
+ resized.save(args.output)
222
+
223
+ print(f"Resized {args.image}: {img.width}x{img.height} -> {args.width}x{args.height}")
224
+ print(f"Method: {args.method}")
225
+ print(f"Saved to: {args.output}")
226
+
227
+ return 0
228
+
229
+
230
+ def cmd_crop(args):
231
+ """Crop an image."""
232
+ img = ImageMatrix.from_file(args.image)
233
+
234
+ box = (args.left, args.top, args.right, args.bottom)
235
+ cropped = crop(img, box)
236
+ cropped.save(args.output)
237
+
238
+ print(f"Cropped {args.image}: {img.width}x{img.height} -> {cropped.width}x{cropped.height}")
239
+ print(f"Region: ({args.left}, {args.top}) to ({args.right}, {args.bottom})")
240
+ print(f"Saved to: {args.output}")
241
+
242
+ return 0
243
+
244
+
245
+ def cmd_compare(args):
246
+ """Compare two images."""
247
+ img1 = ImageMatrix.from_file(args.image1)
248
+ img2 = ImageMatrix.from_file(args.image2)
249
+
250
+ print(f"Comparing images:")
251
+ print(f" Image 1: {args.image1}")
252
+ print(f" Image 2: {args.image2}")
253
+ print("-" * 40)
254
+
255
+ print(f"Dimensions:")
256
+ print(f" Image 1: {img1.width} x {img1.height}")
257
+ print(f" Image 2: {img2.width} x {img2.height}")
258
+ same_size = (img1.width == img2.width) and (img1.height == img2.height)
259
+ print(f" Same size: {'Yes' if same_size else 'No'}")
260
+
261
+ print(f"\nChannels:")
262
+ print(f" Image 1: {img1.channels}")
263
+ print(f" Image 2: {img2.channels}")
264
+
265
+ stats1 = statistics(img1)
266
+ stats2 = statistics(img2)
267
+
268
+ print(f"\nStatistics comparison:")
269
+ print(f" {'Metric':<12} {'Image 1':>12} {'Image 2':>12} {'Diff':>12}")
270
+ print(f" {'-'*12} {'-'*12} {'-'*12} {'-'*12}")
271
+
272
+ for key in ['mean', 'std', 'entropy', 'contrast']:
273
+ v1 = stats1[key]
274
+ v2 = stats2[key]
275
+ diff = v2 - v1
276
+ print(f" {key:<12} {v1:>12.4f} {v2:>12.4f} {diff:>+12.4f}")
277
+
278
+ return 0
279
+
280
+
281
+ if __name__ == "__main__":
282
+ sys.exit(main())