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.
- cudag/__init__.py +334 -0
- cudag/annotation/__init__.py +77 -0
- cudag/annotation/codegen.py +648 -0
- cudag/annotation/config.py +545 -0
- cudag/annotation/loader.py +342 -0
- cudag/annotation/scaffold.py +121 -0
- cudag/annotation/transcription.py +296 -0
- cudag/cli/__init__.py +5 -0
- cudag/cli/main.py +315 -0
- cudag/cli/new.py +873 -0
- cudag/core/__init__.py +364 -0
- cudag/core/button.py +137 -0
- cudag/core/canvas.py +222 -0
- cudag/core/config.py +70 -0
- cudag/core/coords.py +233 -0
- cudag/core/data_grid.py +804 -0
- cudag/core/dataset.py +678 -0
- cudag/core/distribution.py +136 -0
- cudag/core/drawing.py +75 -0
- cudag/core/fonts.py +156 -0
- cudag/core/generator.py +163 -0
- cudag/core/grid.py +367 -0
- cudag/core/grounding_task.py +247 -0
- cudag/core/icon.py +207 -0
- cudag/core/iconlist_task.py +301 -0
- cudag/core/models.py +1251 -0
- cudag/core/random.py +130 -0
- cudag/core/renderer.py +190 -0
- cudag/core/screen.py +402 -0
- cudag/core/scroll_task.py +254 -0
- cudag/core/scrollable_grid.py +447 -0
- cudag/core/state.py +110 -0
- cudag/core/task.py +293 -0
- cudag/core/taskbar.py +350 -0
- cudag/core/text.py +212 -0
- cudag/core/utils.py +82 -0
- cudag/data/surnames.txt +5000 -0
- cudag/modal_apps/__init__.py +4 -0
- cudag/modal_apps/archive.py +103 -0
- cudag/modal_apps/extract.py +138 -0
- cudag/modal_apps/preprocess.py +529 -0
- cudag/modal_apps/upload.py +317 -0
- cudag/prompts/SYSTEM_PROMPT.txt +104 -0
- cudag/prompts/__init__.py +33 -0
- cudag/prompts/system.py +43 -0
- cudag/prompts/tools.py +382 -0
- cudag/py.typed +0 -0
- cudag/schemas/filesystem.json +90 -0
- cudag/schemas/test_record.schema.json +113 -0
- cudag/schemas/train_record.schema.json +90 -0
- cudag/server/__init__.py +21 -0
- cudag/server/app.py +232 -0
- cudag/server/services/__init__.py +9 -0
- cudag/server/services/generator.py +128 -0
- cudag/templates/scripts/archive.sh +35 -0
- cudag/templates/scripts/build.sh +13 -0
- cudag/templates/scripts/extract.sh +54 -0
- cudag/templates/scripts/generate.sh +116 -0
- cudag/templates/scripts/pre-commit.sh +44 -0
- cudag/templates/scripts/preprocess.sh +46 -0
- cudag/templates/scripts/upload.sh +63 -0
- cudag/templates/scripts/verify.py +428 -0
- cudag/validation/__init__.py +35 -0
- cudag/validation/validate.py +508 -0
- cudag-0.3.10.dist-info/METADATA +570 -0
- cudag-0.3.10.dist-info/RECORD +69 -0
- cudag-0.3.10.dist-info/WHEEL +4 -0
- cudag-0.3.10.dist-info/entry_points.txt +2 -0
- 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()
|