cudag 0.3.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. cudag/__init__.py +334 -0
  2. cudag/annotation/__init__.py +77 -0
  3. cudag/annotation/codegen.py +648 -0
  4. cudag/annotation/config.py +545 -0
  5. cudag/annotation/loader.py +342 -0
  6. cudag/annotation/scaffold.py +121 -0
  7. cudag/annotation/transcription.py +296 -0
  8. cudag/cli/__init__.py +5 -0
  9. cudag/cli/main.py +315 -0
  10. cudag/cli/new.py +873 -0
  11. cudag/core/__init__.py +364 -0
  12. cudag/core/button.py +137 -0
  13. cudag/core/canvas.py +222 -0
  14. cudag/core/config.py +70 -0
  15. cudag/core/coords.py +233 -0
  16. cudag/core/data_grid.py +804 -0
  17. cudag/core/dataset.py +678 -0
  18. cudag/core/distribution.py +136 -0
  19. cudag/core/drawing.py +75 -0
  20. cudag/core/fonts.py +156 -0
  21. cudag/core/generator.py +163 -0
  22. cudag/core/grid.py +367 -0
  23. cudag/core/grounding_task.py +247 -0
  24. cudag/core/icon.py +207 -0
  25. cudag/core/iconlist_task.py +301 -0
  26. cudag/core/models.py +1251 -0
  27. cudag/core/random.py +130 -0
  28. cudag/core/renderer.py +190 -0
  29. cudag/core/screen.py +402 -0
  30. cudag/core/scroll_task.py +254 -0
  31. cudag/core/scrollable_grid.py +447 -0
  32. cudag/core/state.py +110 -0
  33. cudag/core/task.py +293 -0
  34. cudag/core/taskbar.py +350 -0
  35. cudag/core/text.py +212 -0
  36. cudag/core/utils.py +82 -0
  37. cudag/data/surnames.txt +5000 -0
  38. cudag/modal_apps/__init__.py +4 -0
  39. cudag/modal_apps/archive.py +103 -0
  40. cudag/modal_apps/extract.py +138 -0
  41. cudag/modal_apps/preprocess.py +529 -0
  42. cudag/modal_apps/upload.py +317 -0
  43. cudag/prompts/SYSTEM_PROMPT.txt +104 -0
  44. cudag/prompts/__init__.py +33 -0
  45. cudag/prompts/system.py +43 -0
  46. cudag/prompts/tools.py +382 -0
  47. cudag/py.typed +0 -0
  48. cudag/schemas/filesystem.json +90 -0
  49. cudag/schemas/test_record.schema.json +113 -0
  50. cudag/schemas/train_record.schema.json +90 -0
  51. cudag/server/__init__.py +21 -0
  52. cudag/server/app.py +232 -0
  53. cudag/server/services/__init__.py +9 -0
  54. cudag/server/services/generator.py +128 -0
  55. cudag/templates/scripts/archive.sh +35 -0
  56. cudag/templates/scripts/build.sh +13 -0
  57. cudag/templates/scripts/extract.sh +54 -0
  58. cudag/templates/scripts/generate.sh +116 -0
  59. cudag/templates/scripts/pre-commit.sh +44 -0
  60. cudag/templates/scripts/preprocess.sh +46 -0
  61. cudag/templates/scripts/upload.sh +63 -0
  62. cudag/templates/scripts/verify.py +428 -0
  63. cudag/validation/__init__.py +35 -0
  64. cudag/validation/validate.py +508 -0
  65. cudag-0.3.10.dist-info/METADATA +570 -0
  66. cudag-0.3.10.dist-info/RECORD +69 -0
  67. cudag-0.3.10.dist-info/WHEEL +4 -0
  68. cudag-0.3.10.dist-info/entry_points.txt +2 -0
  69. cudag-0.3.10.dist-info/licenses/LICENSE +66 -0
cudag/core/text.py ADDED
@@ -0,0 +1,212 @@
1
+ # Copyright (c) 2025 Tylt LLC. All rights reserved.
2
+ # CONFIDENTIAL AND PROPRIETARY. Unauthorized use, copying, or distribution
3
+ # is strictly prohibited. For licensing inquiries: hello@claimhawk.app
4
+
5
+ """Text measurement and rendering utilities for CUDAG framework."""
6
+
7
+ from __future__ import annotations
8
+
9
+ from PIL import Image, ImageDraw, ImageFont
10
+
11
+
12
+ def measure_text(
13
+ text: str,
14
+ font: ImageFont.FreeTypeFont | ImageFont.ImageFont,
15
+ ) -> tuple[int, int]:
16
+ """Measure the width and height of text.
17
+
18
+ Args:
19
+ text: Text string to measure.
20
+ font: PIL font to use for measurement.
21
+
22
+ Returns:
23
+ Tuple of (width, height) in pixels.
24
+
25
+ Example:
26
+ >>> font = ImageFont.load_default()
27
+ >>> width, height = measure_text("Hello", font)
28
+ """
29
+ # Create a temporary draw context for measurement
30
+ draw = ImageDraw.Draw(Image.new("RGB", (1, 1)))
31
+ bbox = draw.textbbox((0, 0), text, font=font)
32
+ return bbox[2] - bbox[0], bbox[3] - bbox[1]
33
+
34
+
35
+ def center_text_position(
36
+ text: str,
37
+ font: ImageFont.FreeTypeFont | ImageFont.ImageFont,
38
+ x: int,
39
+ y: int,
40
+ width: int,
41
+ height: int,
42
+ ) -> tuple[int, int]:
43
+ """Calculate position to center text within a bounding box.
44
+
45
+ Args:
46
+ text: Text string to center.
47
+ font: PIL font to use.
48
+ x: Left edge of bounding box.
49
+ y: Top edge of bounding box.
50
+ width: Width of bounding box.
51
+ height: Height of bounding box.
52
+
53
+ Returns:
54
+ Tuple of (x, y) position for the text.
55
+
56
+ Example:
57
+ >>> font = ImageFont.load_default()
58
+ >>> tx, ty = center_text_position("Hello", font, 0, 0, 100, 50)
59
+ """
60
+ text_width, text_height = measure_text(text, font)
61
+ text_x = x + (width - text_width) // 2
62
+ text_y = y + (height - text_height) // 2
63
+ return text_x, text_y
64
+
65
+
66
+ def draw_centered_text(
67
+ draw: ImageDraw.ImageDraw,
68
+ text: str,
69
+ font: ImageFont.FreeTypeFont | ImageFont.ImageFont,
70
+ x: int,
71
+ y: int,
72
+ width: int,
73
+ height: int,
74
+ fill: tuple[int, int, int] | str = (0, 0, 0),
75
+ ) -> None:
76
+ """Draw text centered within a bounding box.
77
+
78
+ Args:
79
+ draw: PIL ImageDraw instance.
80
+ text: Text string to draw.
81
+ font: PIL font to use.
82
+ x: Left edge of bounding box.
83
+ y: Top edge of bounding box.
84
+ width: Width of bounding box.
85
+ height: Height of bounding box.
86
+ fill: Text color (RGB tuple or color name). Default black.
87
+
88
+ Example:
89
+ >>> from PIL import Image, ImageDraw
90
+ >>> img = Image.new("RGB", (200, 100), "white")
91
+ >>> draw = ImageDraw.Draw(img)
92
+ >>> font = ImageFont.load_default()
93
+ >>> draw_centered_text(draw, "Hello", font, 0, 0, 200, 100)
94
+ """
95
+ text_x, text_y = center_text_position(text, font, x, y, width, height)
96
+ draw.text((text_x, text_y), text, font=font, fill=fill)
97
+
98
+
99
+ def wrap_text(
100
+ text: str,
101
+ max_width: int,
102
+ font: ImageFont.FreeTypeFont | ImageFont.ImageFont,
103
+ ) -> list[str]:
104
+ """Wrap text to fit within max_width pixels.
105
+
106
+ Args:
107
+ text: Text string to wrap.
108
+ max_width: Maximum width in pixels for each line.
109
+ font: PIL font to use for measurement.
110
+
111
+ Returns:
112
+ List of wrapped lines.
113
+
114
+ Example:
115
+ >>> font = ImageFont.load_default()
116
+ >>> lines = wrap_text("This is a long sentence", 50, font)
117
+ """
118
+ if not text:
119
+ return [""]
120
+
121
+ words = text.split()
122
+ if not words:
123
+ return [""]
124
+
125
+ lines: list[str] = []
126
+ current_line: list[str] = []
127
+
128
+ # Create a temporary draw context for measurement
129
+ draw = ImageDraw.Draw(Image.new("RGB", (1, 1)))
130
+
131
+ for word in words:
132
+ test_line = " ".join(current_line + [word])
133
+ bbox = draw.textbbox((0, 0), test_line, font=font)
134
+ text_width = bbox[2] - bbox[0]
135
+
136
+ if text_width <= max_width:
137
+ current_line.append(word)
138
+ else:
139
+ if current_line:
140
+ lines.append(" ".join(current_line))
141
+ current_line = [word]
142
+
143
+ if current_line:
144
+ lines.append(" ".join(current_line))
145
+
146
+ return lines if lines else [""]
147
+
148
+
149
+ def truncate_text(
150
+ text: str,
151
+ max_width: int,
152
+ font: ImageFont.FreeTypeFont | ImageFont.ImageFont,
153
+ ellipsis: str = "...",
154
+ ) -> str:
155
+ """Truncate text to fit within max_width pixels, adding ellipsis if needed.
156
+
157
+ Args:
158
+ text: Text string to truncate.
159
+ max_width: Maximum width in pixels.
160
+ font: PIL font to use for measurement.
161
+ ellipsis: String to append when truncating. Default "...".
162
+
163
+ Returns:
164
+ Original text if it fits, otherwise truncated text with ellipsis.
165
+
166
+ Example:
167
+ >>> font = ImageFont.load_default()
168
+ >>> truncate_text("This is a very long text", 50, font)
169
+ 'This is...'
170
+ """
171
+ if not text:
172
+ return text
173
+
174
+ text_width, _ = measure_text(text, font)
175
+ if text_width <= max_width:
176
+ return text
177
+
178
+ # Binary search would be faster, but linear is simpler and text is usually short
179
+ for i in range(len(text), 0, -1):
180
+ truncated = text[:i] + ellipsis
181
+ truncated_width, _ = measure_text(truncated, font)
182
+ if truncated_width <= max_width:
183
+ return truncated
184
+
185
+ # If even ellipsis doesn't fit, return empty
186
+ return ""
187
+
188
+
189
+ def ordinal_suffix(day: int) -> str:
190
+ """Return the ordinal suffix for a day number.
191
+
192
+ Args:
193
+ day: Day of month (1-31)
194
+
195
+ Returns:
196
+ Ordinal suffix ("st", "nd", "rd", or "th")
197
+
198
+ Examples:
199
+ >>> ordinal_suffix(1)
200
+ 'st'
201
+ >>> ordinal_suffix(2)
202
+ 'nd'
203
+ >>> ordinal_suffix(3)
204
+ 'rd'
205
+ >>> ordinal_suffix(11)
206
+ 'th'
207
+ >>> ordinal_suffix(21)
208
+ 'st'
209
+ """
210
+ if 11 <= day <= 13:
211
+ return "th"
212
+ return {1: "st", 2: "nd", 3: "rd"}.get(day % 10, "th")
cudag/core/utils.py ADDED
@@ -0,0 +1,82 @@
1
+ # Copyright (c) 2025 Tylt LLC. All rights reserved.
2
+ # CONFIDENTIAL AND PROPRIETARY. Unauthorized use, copying, or distribution
3
+ # is strictly prohibited. For licensing inquiries: hello@claimhawk.app
4
+
5
+ """Utility functions for CUDAG framework."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+
13
+
14
+ def get_researcher_name(fallback_to_env: bool = True) -> str | None:
15
+ """Get researcher name from .researcher file.
16
+
17
+ Supports formats:
18
+ - "Name: mike" (key-value)
19
+ - "mike" (plain text)
20
+
21
+ The file is searched for in the current working directory.
22
+
23
+ Args:
24
+ fallback_to_env: If True, fall back to USER env var when file missing
25
+ or empty. Defaults to True.
26
+
27
+ Returns:
28
+ Researcher name (lowercased) or None if not found.
29
+
30
+ Example:
31
+ >>> # With .researcher file containing "Name: mike"
32
+ >>> get_researcher_name()
33
+ 'mike'
34
+ >>> # Without .researcher file
35
+ >>> get_researcher_name(fallback_to_env=False)
36
+ None
37
+ """
38
+ researcher_file = Path(".researcher")
39
+ if researcher_file.exists():
40
+ content = researcher_file.read_text().strip()
41
+ for line in content.split("\n"):
42
+ line = line.strip()
43
+ if not line:
44
+ continue
45
+ # Handle "Name: mike" format
46
+ if ":" in line:
47
+ return line.split(":", 1)[1].strip().lower()
48
+ # Handle plain "mike" format
49
+ return line.lower()
50
+ if fallback_to_env:
51
+ user = os.environ.get("USER")
52
+ return user.lower() if user else None
53
+ return None
54
+
55
+
56
+ def check_script_invocation() -> None:
57
+ """Check if generator was invoked from shell script, print warning if not.
58
+
59
+ Generators should be run via ./scripts/generate.sh to ensure the full
60
+ pipeline (generate + upload + preprocess) is executed. Running generator.py
61
+ directly will skip upload and preprocessing.
62
+
63
+ The shell script should set CUDAG_FROM_SCRIPT=1 before calling the generator.
64
+ """
65
+ if os.environ.get("CUDAG_FROM_SCRIPT") != "1":
66
+ print("")
67
+ print("*" * 60)
68
+ print("*" + " " * 58 + "*")
69
+ print("*" + " WARNING: Running generator.py directly!".center(56) + " *")
70
+ print("*" + " " * 58 + "*")
71
+ print("*" + " Use ./scripts/generate.sh for the full pipeline:".center(56) + " *")
72
+ print("*" + " - Dataset generation".center(56) + " *")
73
+ print("*" + " - Upload to Modal".center(56) + " *")
74
+ print("*" + " - Preprocessing".center(56) + " *")
75
+ print("*" + " " * 58 + "*")
76
+ print("*" + " Run: ./scripts/generate.sh".center(56) + " *")
77
+ print("*" + " Or: ./scripts/generate.sh --dry (no upload)".center(56) + " *")
78
+ print("*" + " " * 58 + "*")
79
+ print("*" * 60)
80
+ print("")
81
+ sys.stderr.flush()
82
+ sys.stdout.flush()