liquidrandom 0.3.2__tar.gz → 0.3.4__tar.gz
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.
- liquidrandom-0.3.4/IMAGE_GEN.md +189 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/PKG-INFO +1 -1
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/pyproject.toml +1 -1
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/categories.py +40 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/generate.py +215 -2
- liquidrandom-0.3.4/seed_generation/image_categories.py +242 -0
- liquidrandom-0.3.4/seed_generation/image_config.py +15 -0
- liquidrandom-0.3.4/seed_generation/image_grounding.py +38 -0
- liquidrandom-0.3.4/seed_generation/image_sampler.py +420 -0
- liquidrandom-0.3.4/seed_generation/image_test_gen.py +243 -0
- liquidrandom-0.3.4/seed_generation/image_viewer.py +285 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/llm.py +2 -1
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/pyproject.toml +1 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/uploader.py +85 -3
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/uv.lock +17 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/validator.py +2 -1
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/__init__.py +48 -2
- liquidrandom-0.3.4/src/liquidrandom/_loader.py +92 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/_registry.py +11 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/__init__.py +10 -0
- liquidrandom-0.3.4/src/liquidrandom/models/aerial.py +61 -0
- liquidrandom-0.3.4/src/liquidrandom/models/indoor.py +60 -0
- liquidrandom-0.3.4/src/liquidrandom/models/navigation.py +58 -0
- liquidrandom-0.3.4/src/liquidrandom/models/people_scene.py +60 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/tool_group.py +4 -2
- liquidrandom-0.3.4/src/liquidrandom/models/warehouse.py +59 -0
- liquidrandom-0.3.4/tests/test_image_models.py +248 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/tests/test_loader.py +7 -1
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/uv.lock +1 -1
- liquidrandom-0.3.2/src/liquidrandom/_loader.py +0 -48
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/.gitignore +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/.python-version +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/CLAUDE.md +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/ISSUES_2.md +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/ISSUES_AND_FIXES.md +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/README.md +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/STEP2.md +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/README.md +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/config.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/dedup.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/generate_hardcoded_parquet.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/hardcoded_instruction_complexities.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/hardcoded_reasoning_patterns.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/inspect_samples.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/run.sh +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/sampler.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/state.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/taxonomy.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/tool_sampler.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/seed_generation/tool_validator.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/_detail.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/coding_task.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/domain.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/emotional_state.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/instruction_complexity.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/job.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/language.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/math_category.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/persona.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/reasoning_pattern.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/scenario.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/science_topic.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/models/writing_style.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/src/liquidrandom/py.typed +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/tests/__init__.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/tests/test_e2e.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/tests/test_models.py +0 -0
- {liquidrandom-0.3.2 → liquidrandom-0.3.4}/tests/test_tool_group_models.py +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# liquidrandom - Image as seed data type project
|
|
2
|
+
|
|
3
|
+
In this project we are using Replicate AI API to generate images as additional seed data type for liquidrandom.
|
|
4
|
+
eg. each sample has an image and text attributes that we can use for the prompting/data generation part,
|
|
5
|
+
eg. an image and text attributes such as "number_of_people": int, "setting": str,
|
|
6
|
+
|
|
7
|
+
Instead of having just one big "image" type we want to actually differentiate between different images groups or categories.
|
|
8
|
+
For instance, "humans" as a category of images that include people in natural everyday situations and settings.
|
|
9
|
+
Another category would be areal images of landscapes, cities, etc, eg. from an airplane, drone or satellite.
|
|
10
|
+
|
|
11
|
+
Your task is to come up with about 5 different categories of images that could be useful for typical on-device vision-language tasks, eg. "animals" is probably not a good category.
|
|
12
|
+
Use the ask-user tool to give me a few options that you think are suitable.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Your second task is then to use these categoreis to generate test images using the Replicate API (REPLICATE_API_KEY is defined, not "TOKEN" but "KEY") with the different models shown below.
|
|
16
|
+
This is important since we want to manually check "human-in-the-loop" is the quality for each pair of category X model is acceptable for our use case. We want to make sure that the images generated are of good quality and relevant to the category they belong to.
|
|
17
|
+
|
|
18
|
+
## Feedback from user about task 2:
|
|
19
|
+
|
|
20
|
+
- Image generation models product per default "too sterile" images with too much focus on aesthetics, which is not ideal for our use case. We want to have more "natural" or "real" images that are more representative of real-world scenarios. Thus, we need to adjust the prompting to include more instructions about this. Two things: it should be "randomized" such that each "real grounding" applied to the image prompt is not the same for each image, and 2, specific imperfections and other types of making it real. Eg. each image has a "depth of field" with blur for aesthetics but we don't want this, i.e. less cinematic.
|
|
21
|
+
- The dashboards and screenshot images are not include enough instructions to make the image an actual screenshot, not a photo of a dashbaord. Make sure in each prompt in this category we include insturction that this is a screenshot.
|
|
22
|
+
- for the "documents" category, the example is good but the images are too "zoomed in". Include explicit instructions in the prompt to make sure the generated images are more "zoomed out" and include the surrounding environment.
|
|
23
|
+
- The warehouse category looks very "bland" (probably since these models are not trained on a lot of back warehouse images). Thus include explictly the instructions on what should be there, eg. objects or people.
|
|
24
|
+
|
|
25
|
+
## Feedback from user about task 2 - Part 2:
|
|
26
|
+
We will keep the following categories:
|
|
27
|
+
- Aerial images
|
|
28
|
+
- Indoor (but also cover partially outdoor and outdoor scenes, eg. entrance, etc)
|
|
29
|
+
- People scenes
|
|
30
|
+
- Warehouse
|
|
31
|
+
- Street, but make it not only "urban" but the theme should be "navigation path", eg. also suburban, rual, even a hiking path in the woods or bike path in a park, etc.), indoor navigation as well, eg. a hallway with a navigation path, etc. In essence, it should "Navigation"
|
|
32
|
+
|
|
33
|
+
We will drop the "documents" and the "dashboards/screenshots" due to high halluciation rate
|
|
34
|
+
|
|
35
|
+
Your third task is then to adjust the seed data generation process to produce around 10k images in each of the categories using the taxonomy tree approach we used in the text-only seed data generation process.
|
|
36
|
+
|
|
37
|
+
Your fourth tasks is to visualize these data, again for human-in-the-loop checking.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
Your fifth tasks is then to adjust the `liquidrandom` public package to support the image seed data types.
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
import replicate
|
|
44
|
+
|
|
45
|
+
input = {
|
|
46
|
+
"prompt": "A striking fashion editorial photograph of a model wearing an elaborate haute couture gown made entirely of living flowers — white peonies, pale pink roses, and trailing jasmine. She stands in a misty English garden at dawn. The dress cascades to the ground and merges with the garden floor. Dewdrops on every petal catch the first light. Her hair is pulled back severely, face serene, skin porcelain. A single monarch butterfly rests on her shoulder. The background is a soft impressionist blur of green hedgerows and pale sky. Shot on Phase One IQ4 150MP, every stamen and pistil razor sharp.",
|
|
47
|
+
"aspect_ratio": "2:3"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
output = replicate.run(
|
|
51
|
+
"bytedance/seedream-5-lite",
|
|
52
|
+
input=input
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# To access the file URLs:
|
|
56
|
+
print(output[0].url)
|
|
57
|
+
#=> "https://replicate.delivery/.../output_0.png"
|
|
58
|
+
|
|
59
|
+
# To write the files to disk:
|
|
60
|
+
for index, item in enumerate(output):
|
|
61
|
+
with open(f"output_{index}.png", "wb") as file:
|
|
62
|
+
file.write(item.read())
|
|
63
|
+
#=> output_0.png written to disk
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
import replicate
|
|
68
|
+
|
|
69
|
+
input = {
|
|
70
|
+
"prompt": "Bookstore window display. A sign displays “New Arrivals This Week”. Below, a shelf tag with the text “Best-Selling Novels Here”. To the side, a colorful poster advertises “Author Meet And Greet on Saturday” with a central portrait of the author. There are four books on the bookshelf, namely “The light between worlds” “When stars are scattered” “The slient patient” “The night circus”",
|
|
71
|
+
"guidance": 4,
|
|
72
|
+
"num_inference_steps": 50
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
output = replicate.run(
|
|
76
|
+
"qwen/qwen-image",
|
|
77
|
+
input=input
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# To access the file URLs:
|
|
81
|
+
print(output[0].url)
|
|
82
|
+
#=> "https://replicate.delivery/.../output_0.webp"
|
|
83
|
+
|
|
84
|
+
# To write the files to disk:
|
|
85
|
+
for index, item in enumerate(output):
|
|
86
|
+
with open(f"output_{index}.webp", "wb") as file:
|
|
87
|
+
file.write(item.read())
|
|
88
|
+
#=> output_0.webp written to disk
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
import replicate
|
|
94
|
+
|
|
95
|
+
input = {
|
|
96
|
+
"size": "4K",
|
|
97
|
+
"prompt": "A warm, nostalgic film-style interior of a cozy café, shot on 35mm-inspired digital photography with soft afternoon sunlight filtering through the front windows. Wooden shelves display neatly arranged ceramics, pastries, and coffee beans. Hand-painted signage on the main interior window reads ‘Seedream 4.5’ in clean, classic lettering, similar to boutique branding. A vintage bicycle with a wicker basket is visible outside the entrance, casting soft shadows on the floor. Rich textures, natural light, warm tones, subtle grain, and calm neighborhood-café ambiance.",
|
|
98
|
+
"aspect_ratio": "16:9"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
output = replicate.run(
|
|
102
|
+
"bytedance/seedream-4.5",
|
|
103
|
+
input=input
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# To access the file URLs:
|
|
107
|
+
print(output[0].url)
|
|
108
|
+
#=> "https://replicate.delivery/.../output_0.jpg"
|
|
109
|
+
|
|
110
|
+
# To write the files to disk:
|
|
111
|
+
for index, item in enumerate(output):
|
|
112
|
+
with open(f"output_{index}.jpg", "wb") as file:
|
|
113
|
+
file.write(item.read())
|
|
114
|
+
#=> output_0.jpg written to disk
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
import replicate
|
|
119
|
+
|
|
120
|
+
input = {
|
|
121
|
+
"prompt": "A close-up portrait of a leopard with distinctive spotted pattern and intense eyes",
|
|
122
|
+
"aspect_ratio": "3:4"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
output = replicate.run(
|
|
126
|
+
"minimax/image-01",
|
|
127
|
+
input=input
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# To access the file URLs:
|
|
131
|
+
print(output[0].url)
|
|
132
|
+
#=> "https://replicate.delivery/.../output_0.jpeg"
|
|
133
|
+
|
|
134
|
+
# To write the files to disk:
|
|
135
|
+
for index, item in enumerate(output):
|
|
136
|
+
with open(f"output_{index}.jpeg", "wb") as file:
|
|
137
|
+
file.write(item.read())
|
|
138
|
+
#=> output_0.jpeg written to disk
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
import replicate
|
|
144
|
+
|
|
145
|
+
input = {
|
|
146
|
+
"prompt": "An illustration of a pink and brown butterfly on a pink background, with the black text “Hunyuan Image 3 on Replicate” below"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
output = replicate.run(
|
|
150
|
+
"tencent/hunyuan-image-3",
|
|
151
|
+
input=input
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# To access the file URLs:
|
|
155
|
+
print(output[0].url)
|
|
156
|
+
#=> "https://replicate.delivery/.../output_0.webp"
|
|
157
|
+
|
|
158
|
+
# To write the files to disk:
|
|
159
|
+
for index, item in enumerate(output):
|
|
160
|
+
with open(f"output_{index}.webp", "wb") as file:
|
|
161
|
+
file.write(item.read())
|
|
162
|
+
#=> output_0.webp written to disk
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
import replicate
|
|
168
|
+
|
|
169
|
+
input = {
|
|
170
|
+
"prompt": "Replace the color of the car to blue",
|
|
171
|
+
"aspect_ratio": "match_input_image",
|
|
172
|
+
"input_images": ["https://replicate.delivery/pbxt/O7kbtH7wgLIItlCyeWjm0fgPpx7OpGsT9VbYyxBXnfieVxQe/woman-by-car.jpg"],
|
|
173
|
+
"output_format": "jpg"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
output = replicate.run(
|
|
177
|
+
"black-forest-labs/flux-2-dev",
|
|
178
|
+
input=input
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# To access the file URL:
|
|
182
|
+
print(output.url)
|
|
183
|
+
#=> "https://replicate.delivery/.../output.jpeg"
|
|
184
|
+
|
|
185
|
+
# To write the file to disk:
|
|
186
|
+
with open("output.jpeg", "wb") as file:
|
|
187
|
+
file.write(output.read())
|
|
188
|
+
#=> output.jpeg written to disk
|
|
189
|
+
```
|
|
@@ -503,4 +503,44 @@ CATEGORY_CONFIGS: dict[str, CategoryConfig] = {
|
|
|
503
503
|
generation_prompt_template="", # Not used; tool_sampler has its own prompts
|
|
504
504
|
specificity_guidance="Each tool group should represent a coherent set of related API functions with inter-tool dependencies.",
|
|
505
505
|
),
|
|
506
|
+
"physical_tool_group": CategoryConfig(
|
|
507
|
+
name="physical_tool_group",
|
|
508
|
+
display_name="Physical-AI Tool Groups",
|
|
509
|
+
fields=[
|
|
510
|
+
FieldSpec("domain", "string", "Tool group domain (e.g. 'Smart Home Lighting Control')"),
|
|
511
|
+
FieldSpec("description", "string", "What this tool group does"),
|
|
512
|
+
FieldSpec("taxonomy_path", "string", "Taxonomy path this group belongs to"),
|
|
513
|
+
FieldSpec("tools_json", "string", "JSON-serialized list of ToolFunction dicts"),
|
|
514
|
+
],
|
|
515
|
+
taxonomy_seed_prompt=(
|
|
516
|
+
"Generate a taxonomy of tool/function calling domains for PHYSICAL and EMBODIED "
|
|
517
|
+
"AI systems (not SaaS or cloud APIs). These represent groups of related tools that "
|
|
518
|
+
"an embodied agent — a voice assistant, robot, vehicle, or wearable AI — would use "
|
|
519
|
+
"together to sense, act on, and reason about the physical world.\n\n"
|
|
520
|
+
"Root categories:\n"
|
|
521
|
+
"- Smart Home Voice Assistants (lighting, climate, security, appliances, media & entertainment)\n"
|
|
522
|
+
"- Mobile Service Robots (indoor delivery, floor cleaning, facility inspection)\n"
|
|
523
|
+
"- Manipulator Robots (arm control, grasping, assembly, pick-and-place, sorting)\n"
|
|
524
|
+
"- Humanoid Robots (bipedal locomotion, whole-body manipulation, human interaction)\n"
|
|
525
|
+
"- Autonomous Vehicles (perception, motion planning, vehicle control, V2X communication)\n"
|
|
526
|
+
"- Sidewalk & Last-Mile Delivery Robots (navigation, obstacle handling, handoff)\n"
|
|
527
|
+
"- Aerial Drones (mission planning, inspection, cinematography, precision agriculture)\n"
|
|
528
|
+
"- Underwater & Marine Robots (ROV/AUV operations, bathymetric mapping)\n"
|
|
529
|
+
"- Industrial & Factory Automation (robotic cells, AMRs, conveyor & PLC control)\n"
|
|
530
|
+
"- Agricultural Robotics (field navigation, harvesting, targeted spraying)\n"
|
|
531
|
+
"- Medical & Surgical Robots (teleoperation, imaging-guided intervention)\n"
|
|
532
|
+
"- Wearable & Personal AI (AR glasses assistants, smart earbuds, fitness devices)\n\n"
|
|
533
|
+
"Each leaf should be a specific embodied tool domain (e.g. 'Quadruped locomotion "
|
|
534
|
+
"gait switching on uneven terrain', not just 'robotics'). Focus on real physical "
|
|
535
|
+
"capabilities: sensing, actuation, state estimation, planning, safety — NOT cloud "
|
|
536
|
+
"services, databases, or web APIs."
|
|
537
|
+
),
|
|
538
|
+
generation_prompt_template="", # Not used; tool_sampler has its own prompts
|
|
539
|
+
specificity_guidance=(
|
|
540
|
+
"Each tool group should represent a coherent set of physical-world capabilities "
|
|
541
|
+
"(sensing + actuation + state) that an embodied AI agent would use together, with "
|
|
542
|
+
"inter-tool state/data dependencies (e.g. a pose returned by a localization tool is "
|
|
543
|
+
"consumed by a navigation tool, or a detected object handle flows into a grasp planner)."
|
|
544
|
+
),
|
|
545
|
+
),
|
|
506
546
|
}
|
|
@@ -26,6 +26,16 @@ from config import (
|
|
|
26
26
|
DEFAULT_TOOL_TAXONOMY_DEPTH,
|
|
27
27
|
STATE_FILE,
|
|
28
28
|
)
|
|
29
|
+
from image_categories import IMAGE_CATEGORY_CONFIGS
|
|
30
|
+
from image_config import (
|
|
31
|
+
DEFAULT_IMAGE_BATCH_SIZE,
|
|
32
|
+
DEFAULT_IMAGE_CONCURRENCY,
|
|
33
|
+
DEFAULT_IMAGE_DEDUP_THRESHOLD,
|
|
34
|
+
DEFAULT_IMAGE_K,
|
|
35
|
+
DEFAULT_IMAGE_N,
|
|
36
|
+
DEFAULT_IMAGE_SAMPLES_PER_LEAF,
|
|
37
|
+
DEFAULT_IMAGE_TAXONOMY_DEPTH,
|
|
38
|
+
)
|
|
29
39
|
from llm import create_client
|
|
30
40
|
from sampler import generate_samples
|
|
31
41
|
from state import RunState
|
|
@@ -349,7 +359,7 @@ def generate_tools(
|
|
|
349
359
|
output_dir: str = typer.Option("output", "--output-dir", help="Output directory"),
|
|
350
360
|
) -> None:
|
|
351
361
|
"""Generate tool group seed data with parameter variations."""
|
|
352
|
-
resolved = categories if categories else ["tool_group"]
|
|
362
|
+
resolved = categories if categories else ["tool_group", "physical_tool_group"]
|
|
353
363
|
for cat in resolved:
|
|
354
364
|
if cat not in CATEGORY_CONFIGS:
|
|
355
365
|
console.print(f"[red]Unknown category: {cat}[/red]")
|
|
@@ -370,15 +380,218 @@ def generate_tools(
|
|
|
370
380
|
)
|
|
371
381
|
|
|
372
382
|
|
|
383
|
+
async def _run_generate_images(
|
|
384
|
+
categories: list[str],
|
|
385
|
+
n: int,
|
|
386
|
+
k: int,
|
|
387
|
+
batch_size: int,
|
|
388
|
+
image_concurrency: int,
|
|
389
|
+
taxonomy_depth: int,
|
|
390
|
+
samples_per_leaf: int,
|
|
391
|
+
dedup_threshold: float,
|
|
392
|
+
output_dir: str,
|
|
393
|
+
resume: bool,
|
|
394
|
+
) -> None:
|
|
395
|
+
from image_sampler import generate_image_samples
|
|
396
|
+
|
|
397
|
+
client = create_client()
|
|
398
|
+
state_path = str(Path(output_dir) / "state.json")
|
|
399
|
+
# Always load existing state to preserve progress from previous runs
|
|
400
|
+
state = RunState.load(state_path)
|
|
401
|
+
|
|
402
|
+
results: dict[str, dict[str, int | float]] = {}
|
|
403
|
+
total_start = time.time()
|
|
404
|
+
|
|
405
|
+
console.print(f"\n[bold]Generating image seed data for {len(categories)} categories[/bold]")
|
|
406
|
+
console.print(f" Target: {n} images per category, {k} prompts per LLM call")
|
|
407
|
+
console.print(f" Taxonomy: depth {taxonomy_depth}, {samples_per_leaf} images/leaf")
|
|
408
|
+
console.print(f" Image concurrency: {image_concurrency}, dedup threshold: {dedup_threshold}")
|
|
409
|
+
console.print()
|
|
410
|
+
|
|
411
|
+
for i, cat_name in enumerate(categories, 1):
|
|
412
|
+
cat_config = IMAGE_CATEGORY_CONFIGS[cat_name]
|
|
413
|
+
cat_state = state.get_category(cat_name)
|
|
414
|
+
cat_start = time.time()
|
|
415
|
+
|
|
416
|
+
console.print(
|
|
417
|
+
f"[bold cyan][{i}/{len(categories)}] {cat_config.display_name}[/bold cyan]"
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
# Phase 1: Taxonomy (reuses existing taxonomy.py)
|
|
421
|
+
if not cat_state.taxonomy_done or not resume:
|
|
422
|
+
try:
|
|
423
|
+
root = await generate_taxonomy(
|
|
424
|
+
client,
|
|
425
|
+
cat_config,
|
|
426
|
+
target_samples=n,
|
|
427
|
+
samples_per_leaf=samples_per_leaf,
|
|
428
|
+
max_depth=taxonomy_depth,
|
|
429
|
+
batch_size=batch_size,
|
|
430
|
+
output_dir=output_dir,
|
|
431
|
+
)
|
|
432
|
+
cat_state.taxonomy_done = True
|
|
433
|
+
state.save(state_path)
|
|
434
|
+
except Exception as e:
|
|
435
|
+
console.print(f" [red]Taxonomy generation failed: {e}[/red]")
|
|
436
|
+
continue
|
|
437
|
+
else:
|
|
438
|
+
from taxonomy import load_taxonomy
|
|
439
|
+
root = load_taxonomy(output_dir, cat_name)
|
|
440
|
+
if root is None:
|
|
441
|
+
console.print(f" [red]No saved taxonomy found, regenerating[/red]")
|
|
442
|
+
cat_state.taxonomy_done = False
|
|
443
|
+
continue
|
|
444
|
+
|
|
445
|
+
# Phase 2+3: Image prompt generation + Replicate image generation
|
|
446
|
+
if not cat_state.generation_done or not resume:
|
|
447
|
+
try:
|
|
448
|
+
total = await generate_image_samples(
|
|
449
|
+
client,
|
|
450
|
+
root,
|
|
451
|
+
cat_config,
|
|
452
|
+
target_samples=n,
|
|
453
|
+
k=k,
|
|
454
|
+
batch_size=batch_size,
|
|
455
|
+
image_concurrency=image_concurrency,
|
|
456
|
+
dedup_threshold=dedup_threshold,
|
|
457
|
+
output_dir=output_dir,
|
|
458
|
+
)
|
|
459
|
+
# Only mark done if we reached the target
|
|
460
|
+
cat_state.generation_done = total >= n
|
|
461
|
+
cat_state.total_samples = total
|
|
462
|
+
state.save(state_path)
|
|
463
|
+
except KeyboardInterrupt:
|
|
464
|
+
console.print("\n [yellow]Interrupted! Saving state...[/yellow]")
|
|
465
|
+
state.save(state_path)
|
|
466
|
+
console.print(
|
|
467
|
+
f" [yellow]Resume with: python generate.py generate-images --resume "
|
|
468
|
+
f"--output-dir {output_dir}[/yellow]"
|
|
469
|
+
)
|
|
470
|
+
raise
|
|
471
|
+
except Exception as e:
|
|
472
|
+
console.print(f" [red]Image generation failed: {e}[/red]")
|
|
473
|
+
state.save(state_path)
|
|
474
|
+
continue
|
|
475
|
+
else:
|
|
476
|
+
total = cat_state.total_samples
|
|
477
|
+
console.print(
|
|
478
|
+
f" [green]Already complete ({total} images)[/green]"
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
elapsed = time.time() - cat_start
|
|
482
|
+
results[cat_name] = {"total": total, "time": elapsed}
|
|
483
|
+
console.print()
|
|
484
|
+
|
|
485
|
+
# Summary table
|
|
486
|
+
total_elapsed = time.time() - total_start
|
|
487
|
+
table = Table(title="Image Generation Summary")
|
|
488
|
+
table.add_column("Category", style="cyan")
|
|
489
|
+
table.add_column("Target", justify="right")
|
|
490
|
+
table.add_column("Generated", justify="right")
|
|
491
|
+
table.add_column("Time", justify="right")
|
|
492
|
+
|
|
493
|
+
for cat_name in categories:
|
|
494
|
+
if cat_name in results:
|
|
495
|
+
r = results[cat_name]
|
|
496
|
+
table.add_row(
|
|
497
|
+
IMAGE_CATEGORY_CONFIGS[cat_name].display_name,
|
|
498
|
+
str(n),
|
|
499
|
+
str(r["total"]),
|
|
500
|
+
f"{r['time']:.1f}s",
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
console.print(table)
|
|
504
|
+
console.print(f"\n[bold]Total time: {total_elapsed:.1f}s[/bold]")
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@app.command(name="generate-images")
|
|
508
|
+
def generate_images(
|
|
509
|
+
n: int = typer.Option(DEFAULT_IMAGE_N, "--n", help="Target images per category"),
|
|
510
|
+
k: int = typer.Option(DEFAULT_IMAGE_K, "--k", help="Prompts per LLM call"),
|
|
511
|
+
batch_size: int = typer.Option(
|
|
512
|
+
DEFAULT_IMAGE_BATCH_SIZE, "--batch-size", help="Concurrent LLM calls"
|
|
513
|
+
),
|
|
514
|
+
image_concurrency: int = typer.Option(
|
|
515
|
+
DEFAULT_IMAGE_CONCURRENCY, "--image-concurrency", help="Concurrent Replicate API calls"
|
|
516
|
+
),
|
|
517
|
+
taxonomy_depth: int = typer.Option(
|
|
518
|
+
DEFAULT_IMAGE_TAXONOMY_DEPTH, "--taxonomy-depth", help="Max taxonomy tree depth"
|
|
519
|
+
),
|
|
520
|
+
samples_per_leaf: int = typer.Option(
|
|
521
|
+
DEFAULT_IMAGE_SAMPLES_PER_LEAF, "--samples-per-leaf", help="Target images per leaf"
|
|
522
|
+
),
|
|
523
|
+
dedup_threshold: float = typer.Option(
|
|
524
|
+
DEFAULT_IMAGE_DEDUP_THRESHOLD, "--dedup-threshold", help="Jaccard similarity threshold"
|
|
525
|
+
),
|
|
526
|
+
categories: Optional[list[str]] = typer.Option(
|
|
527
|
+
None, "--categories", help="Image categories to generate (default: all)"
|
|
528
|
+
),
|
|
529
|
+
resume: bool = typer.Option(False, "--resume", help="Resume from checkpoint"),
|
|
530
|
+
output_dir: str = typer.Option("output", "--output-dir", help="Output directory"),
|
|
531
|
+
) -> None:
|
|
532
|
+
"""Generate image seed data for all or selected image categories."""
|
|
533
|
+
if not categories:
|
|
534
|
+
resolved = list(IMAGE_CATEGORY_CONFIGS.keys())
|
|
535
|
+
else:
|
|
536
|
+
for cat in categories:
|
|
537
|
+
if cat not in IMAGE_CATEGORY_CONFIGS:
|
|
538
|
+
console.print(f"[red]Unknown image category: {cat}[/red]")
|
|
539
|
+
console.print(
|
|
540
|
+
f"Available: {', '.join(sorted(IMAGE_CATEGORY_CONFIGS.keys()))}"
|
|
541
|
+
)
|
|
542
|
+
raise typer.Exit(1)
|
|
543
|
+
resolved = categories
|
|
544
|
+
asyncio.run(
|
|
545
|
+
_run_generate_images(
|
|
546
|
+
categories=resolved,
|
|
547
|
+
n=n,
|
|
548
|
+
k=k,
|
|
549
|
+
batch_size=batch_size,
|
|
550
|
+
image_concurrency=image_concurrency,
|
|
551
|
+
taxonomy_depth=taxonomy_depth,
|
|
552
|
+
samples_per_leaf=samples_per_leaf,
|
|
553
|
+
dedup_threshold=dedup_threshold,
|
|
554
|
+
output_dir=output_dir,
|
|
555
|
+
resume=resume,
|
|
556
|
+
)
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
@app.command(name="review-images")
|
|
561
|
+
def review_images(
|
|
562
|
+
category: str = typer.Argument(help="Image category to review"),
|
|
563
|
+
max_images: int = typer.Option(500, "--max-images", help="Max images to include in gallery"),
|
|
564
|
+
output_dir: str = typer.Option("output", "--output-dir", help="Output directory"),
|
|
565
|
+
) -> None:
|
|
566
|
+
"""Generate an HTML gallery page for reviewing generated images."""
|
|
567
|
+
from image_viewer import generate_review_html
|
|
568
|
+
|
|
569
|
+
if category not in IMAGE_CATEGORY_CONFIGS:
|
|
570
|
+
console.print(f"[red]Unknown image category: {category}[/red]")
|
|
571
|
+
console.print(
|
|
572
|
+
f"Available: {', '.join(sorted(IMAGE_CATEGORY_CONFIGS.keys()))}"
|
|
573
|
+
)
|
|
574
|
+
raise typer.Exit(1)
|
|
575
|
+
path = generate_review_html(output_dir, category, max_images=max_images)
|
|
576
|
+
console.print(f"\n[bold]Open in browser: file://{path.resolve()}[/bold]")
|
|
577
|
+
|
|
578
|
+
|
|
373
579
|
@app.command(name="upload-only")
|
|
374
580
|
def upload_only(
|
|
375
581
|
output_dir: str = typer.Option("output", "--output-dir", help="Output directory"),
|
|
376
582
|
repo_id: str = typer.Option(
|
|
377
583
|
"mlech26l/liquidrandom-data", "--repo-id", help="HuggingFace repo ID"
|
|
378
584
|
),
|
|
585
|
+
skip_images: bool = typer.Option(
|
|
586
|
+
False,
|
|
587
|
+
"--skip-images",
|
|
588
|
+
help="Skip image category consolidation/upload (any existing remote image parquets stay untouched; README will only list text categories)",
|
|
589
|
+
),
|
|
379
590
|
) -> None:
|
|
380
591
|
"""Consolidate samples and upload to HuggingFace."""
|
|
381
|
-
consolidate_and_upload(
|
|
592
|
+
consolidate_and_upload(
|
|
593
|
+
output_dir=output_dir, repo_id=repo_id, skip_images=skip_images
|
|
594
|
+
)
|
|
382
595
|
|
|
383
596
|
|
|
384
597
|
if __name__ == "__main__":
|