precon3d 0.1.3__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.
- precon3d/__init__.py +5 -0
- precon3d/_my_typer_cli.py +327 -0
- precon3d/aligner.py +1338 -0
- precon3d/cropper.py +193 -0
- precon3d/custom_types.py +455 -0
- precon3d/czi_info.py +571 -0
- precon3d/downscaler.py +340 -0
- precon3d/factory.py +286 -0
- precon3d/main.py +40 -0
- precon3d/mosaic_utils.py +652 -0
- precon3d/post_stitch_utils.py +914 -0
- precon3d/resampler.py +1052 -0
- precon3d/shading_correction.py +1024 -0
- precon3d/stitcher.py +467 -0
- precon3d/utility.py +919 -0
- precon3d-0.1.3.dist-info/METADATA +49 -0
- precon3d-0.1.3.dist-info/RECORD +22 -0
- precon3d-0.1.3.dist-info/WHEEL +5 -0
- precon3d-0.1.3.dist-info/entry_points.txt +2 -0
- precon3d-0.1.3.dist-info/licenses/LICENSE +23 -0
- precon3d-0.1.3.dist-info/licenses/NOTICE.md +887 -0
- precon3d-0.1.3.dist-info/top_level.txt +1 -0
precon3d/__init__.py
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides a custom command-line interface (CLI) toolkit,
|
|
3
|
+
utilizing the Typer and Click libraries for enhanced user interaction
|
|
4
|
+
and command management.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from enum import StrEnum
|
|
9
|
+
from typing import Final
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich import box
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
LOGO_TEXT: Final = r"""
|
|
19
|
+
+-------------+
|
|
20
|
+
/ /|
|
|
21
|
+
/ / |
|
|
22
|
+
+-------------+ | PRECON3D:
|
|
23
|
+
| slice 1 | | (PRE)pare and re(CON)struct
|
|
24
|
+
| 2 | | serial sectioning montage data
|
|
25
|
+
| 3 | | into 3D volume 🧊
|
|
26
|
+
| ... | +
|
|
27
|
+
| slice n | /
|
|
28
|
+
+-------------+/
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
console = Console()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Color(StrEnum):
|
|
35
|
+
"""Colors for custom typer formmatting
|
|
36
|
+
|
|
37
|
+
full list of colors for customization here:
|
|
38
|
+
https://rich.readthedocs.io/en/stable/appendix/colors.html
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
FRONT_MATTER: str = "dark_sea_green1"
|
|
42
|
+
MODULE: str = "magenta"
|
|
43
|
+
COMMAND: str = "orange1"
|
|
44
|
+
ERROR: str = "red"
|
|
45
|
+
STATEMENT: str = "white"
|
|
46
|
+
CURRENT_LEVEL: str = "yellow"
|
|
47
|
+
HEADER: str = "bold cyan"
|
|
48
|
+
PARAMETER: str = "green"
|
|
49
|
+
TYPE: str = "blue"
|
|
50
|
+
REQUIRED: str = "yellow"
|
|
51
|
+
DEAFULT: str = "magenta"
|
|
52
|
+
SHORT_HELP: str = "white"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class CustomCLIGroup(typer.core.TyperGroup):
|
|
56
|
+
"""
|
|
57
|
+
CustomCLIGroup is an enhanced command group for module-level help
|
|
58
|
+
and error handling in the CLI.
|
|
59
|
+
|
|
60
|
+
This class extends Typer's base group functionality to provide a
|
|
61
|
+
rich, color-coded help and usage display using the Rich library. It
|
|
62
|
+
is designed to improve user interaction by offering:
|
|
63
|
+
|
|
64
|
+
1. A dynamic "Usage:" line that adapts based on the current
|
|
65
|
+
command context.
|
|
66
|
+
2. Display of a short help message (if available) for the current
|
|
67
|
+
command group.
|
|
68
|
+
3. A formatted table listing subcommands (or modules) with
|
|
69
|
+
color-coded entries:
|
|
70
|
+
- Module names are shown in a designated module color.
|
|
71
|
+
- Commands are shown in a designated command color.
|
|
72
|
+
4. Full help documentation (docstring) display when available.
|
|
73
|
+
|
|
74
|
+
Additional Features:
|
|
75
|
+
- When the main command is invoked without additional subcommands,
|
|
76
|
+
an ASCII art logo is printed to brand the CLI.
|
|
77
|
+
- The resolve_command method is overridden to catch cases where a
|
|
78
|
+
user supplies a non-existent command or module. In such cases,
|
|
79
|
+
it prints a descriptive error message, indicates available
|
|
80
|
+
options, and then displays the group help.
|
|
81
|
+
- Overrides several methods (such as format_help, get_help, main)
|
|
82
|
+
to integrate rich formatting and improved error management.
|
|
83
|
+
|
|
84
|
+
Methods:
|
|
85
|
+
list_commands(ctx):
|
|
86
|
+
Returns a sorted list of available command names.
|
|
87
|
+
format_help(ctx, formatter):
|
|
88
|
+
Displays the help message for the current group.
|
|
89
|
+
get_help(ctx):
|
|
90
|
+
Displays help and exits the program.
|
|
91
|
+
show_help(ctx):
|
|
92
|
+
Formats and prints the usage, short help, subcommand table,
|
|
93
|
+
and full help for the group.
|
|
94
|
+
format_exception(ctx, exc):
|
|
95
|
+
Overrides exception formatting (returns an empty string, as
|
|
96
|
+
errors are handled separately).
|
|
97
|
+
resolve_command(ctx, args):
|
|
98
|
+
Attempts to resolve a command; on failure, prints an error
|
|
99
|
+
message, shows available options, and exits.
|
|
100
|
+
main(*args, **kwargs):
|
|
101
|
+
Executes the main command loop with standalone mode disabled
|
|
102
|
+
to allow error bubbling.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def list_commands(self, _ctx):
|
|
106
|
+
"""List all the commands (functions) added to the module"""
|
|
107
|
+
return sorted(self.commands.keys())
|
|
108
|
+
|
|
109
|
+
def format_help(self, ctx, _formatter):
|
|
110
|
+
"""Displays the help message for the current group."""
|
|
111
|
+
self.show_help(ctx)
|
|
112
|
+
|
|
113
|
+
def get_help(self, ctx):
|
|
114
|
+
"""Displays help and exits the program to handle errors."""
|
|
115
|
+
self.show_help(ctx)
|
|
116
|
+
ctx.exit()
|
|
117
|
+
return ""
|
|
118
|
+
|
|
119
|
+
def show_help(self, ctx: click.Context):
|
|
120
|
+
"""Custom show help function displaying table for groups"""
|
|
121
|
+
# 1. Print usage header.
|
|
122
|
+
# Determine the usage line based on the current command path:
|
|
123
|
+
if ctx.command_path.strip() == "precon3d":
|
|
124
|
+
console.print(
|
|
125
|
+
f"[{Color.FRONT_MATTER}]"
|
|
126
|
+
f"{LOGO_TEXT}"
|
|
127
|
+
f"[/{Color.FRONT_MATTER}]"
|
|
128
|
+
)
|
|
129
|
+
# If only "precon3d" is provided, show [MODULE]:
|
|
130
|
+
usage_line = (
|
|
131
|
+
f"[{Color.CURRENT_LEVEL}]precon3d[/{Color.CURRENT_LEVEL}] "
|
|
132
|
+
f"[[{Color.MODULE}]MODULE[/{Color.MODULE}]] "
|
|
133
|
+
f"[[{Color.COMMAND}]COMMAND[/{Color.COMMAND}]] "
|
|
134
|
+
f"[[{Color.PARAMETER}]PARAMETERS[/{Color.PARAMETER}]]"
|
|
135
|
+
)
|
|
136
|
+
column_label = "Module"
|
|
137
|
+
else:
|
|
138
|
+
# Otherwise, just append [COMMAND] [OPTIONS]:
|
|
139
|
+
usage_line = (
|
|
140
|
+
f"[{Color.CURRENT_LEVEL}]{ctx.command_path}[/{Color.CURRENT_LEVEL}] "
|
|
141
|
+
f"[[{Color.COMMAND}]COMMAND[/{Color.COMMAND}]] "
|
|
142
|
+
f"[[{Color.PARAMETER}]PARAMETERS[/{Color.PARAMETER}]]"
|
|
143
|
+
)
|
|
144
|
+
column_label = "Command"
|
|
145
|
+
console.print(f"[bold]Usage:[/] {usage_line}\n")
|
|
146
|
+
|
|
147
|
+
# 2. Print short help for this group if available.
|
|
148
|
+
short_help = getattr(self, "short_help", "")
|
|
149
|
+
if short_help:
|
|
150
|
+
console.print(short_help + "\n", style=f"{Color.SHORT_HELP}")
|
|
151
|
+
# 3. Build a table listing subcommands.
|
|
152
|
+
table = Table(
|
|
153
|
+
show_header=True, header_style=f"{Color.HEADER}", box=None
|
|
154
|
+
)
|
|
155
|
+
table.add_column(column_label, justify="left")
|
|
156
|
+
table.add_column("Short help", justify="left")
|
|
157
|
+
for name, cmd in self.commands.items():
|
|
158
|
+
# Distinguish modules from commands:
|
|
159
|
+
if isinstance(cmd, typer.core.TyperGroup):
|
|
160
|
+
# For modules, display the name in {Color.MODULE}.
|
|
161
|
+
display_name = f"[{Color.MODULE}]{name}[/{Color.MODULE}]"
|
|
162
|
+
else:
|
|
163
|
+
# For function commands, display the name in {Color.COMMAND}.
|
|
164
|
+
display_name = f"[{Color.COMMAND}]{name}[/{Color.COMMAND}]"
|
|
165
|
+
short = getattr(cmd, "short_help", "") or ""
|
|
166
|
+
table.add_row(display_name, short)
|
|
167
|
+
console.print(table)
|
|
168
|
+
|
|
169
|
+
def format_exception(self, _ctx, _exc):
|
|
170
|
+
"""Format exceptions generated by typer"""
|
|
171
|
+
# We override this method but do not print anything here.
|
|
172
|
+
return ""
|
|
173
|
+
|
|
174
|
+
def resolve_command(self, ctx, args):
|
|
175
|
+
"""Attempts to resolve a command; on failure, prints an error
|
|
176
|
+
message, shows available options, and exits."""
|
|
177
|
+
try:
|
|
178
|
+
return super().resolve_command(ctx, args)
|
|
179
|
+
except click.exceptions.UsageError as e:
|
|
180
|
+
# Print error message.
|
|
181
|
+
console.print(f"Error: {e}\n", style=f"{Color.ERROR}")
|
|
182
|
+
# Print a message indicating viable options.
|
|
183
|
+
console.print(
|
|
184
|
+
f"\n[{Color.STATEMENT}]Availble options at this level ([/{Color.STATEMENT}]"
|
|
185
|
+
f"[{Color.CURRENT_LEVEL}]{ctx.command_path}[/{Color.CURRENT_LEVEL}]"
|
|
186
|
+
f"[{Color.STATEMENT}]) are:\n[/{Color.STATEMENT}]"
|
|
187
|
+
)
|
|
188
|
+
# Show this group's help then exit.
|
|
189
|
+
self.show_help(ctx)
|
|
190
|
+
ctx.exit(1)
|
|
191
|
+
return None # This return is never reached but silences pylint.
|
|
192
|
+
|
|
193
|
+
def main(self, *args, **kwargs):
|
|
194
|
+
"""Executes the main command loop with standalone mode disabled
|
|
195
|
+
to allow error bubbling."""
|
|
196
|
+
# Force standalone_mode=False so errors bubble up.
|
|
197
|
+
kwargs.setdefault("standalone_mode", False)
|
|
198
|
+
try:
|
|
199
|
+
return super().main(*args, **kwargs)
|
|
200
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
201
|
+
# catch broad exceptions
|
|
202
|
+
console.print(f"Error: {e}\n", style=f"{Color.ERROR}")
|
|
203
|
+
sys.exit(1)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class CustomCLICommand(click.Command):
|
|
207
|
+
"""
|
|
208
|
+
CustomCLICommand is an enhanced command class for individual CLI
|
|
209
|
+
commands that provides detailed, color-formatted help and error
|
|
210
|
+
handling using the Rich library.
|
|
211
|
+
|
|
212
|
+
This class extends Click's Command functionality to:
|
|
213
|
+
- Print a "Usage:" line with the current command path and its parameters.
|
|
214
|
+
- Display the command's short help message (if provided) in a highlighted style.
|
|
215
|
+
- Create a formatted table of parameters, detailing each parameter's:
|
|
216
|
+
- Name
|
|
217
|
+
- Type
|
|
218
|
+
- Required status
|
|
219
|
+
- Default value
|
|
220
|
+
- Associated short help
|
|
221
|
+
- Output the full detailed help (the command's docstring) when available.
|
|
222
|
+
|
|
223
|
+
Additionally, CustomCLICommand intercepts missing parameter exceptions
|
|
224
|
+
during argument parsing or command invocation. When such exceptions occur,
|
|
225
|
+
it prints an informative error message, displays the help for the command,
|
|
226
|
+
and exits the execution, ensuring that users are guided toward correct usage.
|
|
227
|
+
|
|
228
|
+
Methods:
|
|
229
|
+
- parse_args(ctx, args): Parses command-line arguments and handles missing parameters.
|
|
230
|
+
- invoke(ctx): Invokes the command and handles missing parameters.
|
|
231
|
+
- format_help(ctx, formatter): Displays the help message for the command.
|
|
232
|
+
- get_help(ctx): Displays the help and exits the process.
|
|
233
|
+
- show_help(ctx): Formats and prints:
|
|
234
|
+
- A usage header
|
|
235
|
+
- The command's short help (if available)
|
|
236
|
+
- A detailed table of parameters
|
|
237
|
+
- The full detailed help (if provided)
|
|
238
|
+
- format_exception(ctx, exc): Returns an empty string for higher-level error handling.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def __init__(self, *args, **kwargs):
|
|
242
|
+
kwargs.pop("rich_markup_mode", None)
|
|
243
|
+
kwargs.pop("rich_help_panel", None)
|
|
244
|
+
super().__init__(*args, **kwargs)
|
|
245
|
+
|
|
246
|
+
def parse_args(self, ctx, args):
|
|
247
|
+
try:
|
|
248
|
+
return super().parse_args(ctx, args)
|
|
249
|
+
except click.MissingParameter as e:
|
|
250
|
+
console.print(f"Error: {e}\n", style=f"{Color.ERROR}")
|
|
251
|
+
self.show_help(ctx)
|
|
252
|
+
ctx.exit(1)
|
|
253
|
+
|
|
254
|
+
def invoke(self, ctx):
|
|
255
|
+
try:
|
|
256
|
+
return super().invoke(ctx)
|
|
257
|
+
except click.MissingParameter as e:
|
|
258
|
+
console.print(f"Error: {e}\n", style=f"{Color.ERROR}")
|
|
259
|
+
self.show_help(ctx)
|
|
260
|
+
ctx.exit(1)
|
|
261
|
+
|
|
262
|
+
def format_help(self, ctx, _formatter):
|
|
263
|
+
self.show_help(ctx)
|
|
264
|
+
|
|
265
|
+
def get_help(self, ctx):
|
|
266
|
+
self.show_help(ctx)
|
|
267
|
+
ctx.exit()
|
|
268
|
+
|
|
269
|
+
def show_help(self, ctx: click.Context):
|
|
270
|
+
"""Custom show help function displaying table for commands"""
|
|
271
|
+
# Print usage header for the command.
|
|
272
|
+
console.print(
|
|
273
|
+
f"[bold]Usage:[/] "
|
|
274
|
+
f"[{Color.CURRENT_LEVEL}]{ctx.command_path}[/{Color.CURRENT_LEVEL}] "
|
|
275
|
+
f"[[{Color.PARAMETER}]PARAMETERS[/{Color.PARAMETER}]]\n"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Print short help if available.
|
|
279
|
+
short_help = getattr(self, "short_help", "")
|
|
280
|
+
if short_help:
|
|
281
|
+
console.print(short_help + "\n", style=f"{Color.SHORT_HELP}")
|
|
282
|
+
|
|
283
|
+
# Build table for parameters with additional detail.
|
|
284
|
+
console.print(f"[{Color.HEADER}][bold]Parameters:[/][/{Color.HEADER}]")
|
|
285
|
+
if self.params:
|
|
286
|
+
for param in self.params:
|
|
287
|
+
param_type = (
|
|
288
|
+
"Argument"
|
|
289
|
+
if isinstance(param, click.Argument)
|
|
290
|
+
else "Option"
|
|
291
|
+
)
|
|
292
|
+
required = "Yes" if param.required else "No"
|
|
293
|
+
default = (
|
|
294
|
+
str(param.default) if param.default is not None else ""
|
|
295
|
+
)
|
|
296
|
+
# help_text = getattr(param, "help", "") or ""
|
|
297
|
+
names = (
|
|
298
|
+
", ".join(param.opts)
|
|
299
|
+
if isinstance(param, click.Option)
|
|
300
|
+
else param.name
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Determine the expected type
|
|
304
|
+
if isinstance(param.type, click.Path):
|
|
305
|
+
expected_type = "Path"
|
|
306
|
+
elif isinstance(
|
|
307
|
+
param.type, type
|
|
308
|
+
): # Check if it's a built-in type
|
|
309
|
+
expected_type = param.type.__name__
|
|
310
|
+
else:
|
|
311
|
+
expected_type = str(
|
|
312
|
+
param.type
|
|
313
|
+
) # Fallback to string representation
|
|
314
|
+
|
|
315
|
+
console.print(
|
|
316
|
+
f"[[{Color.PARAMETER}]{names}[/{Color.PARAMETER}]] ({param_type})"
|
|
317
|
+
)
|
|
318
|
+
console.print(f" Required: {required}")
|
|
319
|
+
console.print(f" Default: {default}")
|
|
320
|
+
console.print(f" Type: {expected_type}")
|
|
321
|
+
# console.print(f" Help: {help_text}\n")
|
|
322
|
+
else:
|
|
323
|
+
console.print("No parameters.\n")
|
|
324
|
+
|
|
325
|
+
def format_exception(self, _ctx, _exc):
|
|
326
|
+
"""Format exceptions generated by typer"""
|
|
327
|
+
return ""
|