-
Notifications
You must be signed in to change notification settings - Fork 20
feat(scene-engine): add text-guided scene editing #515
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
MuziWong
wants to merge
29
commits into
main
Choose a base branch
from
muzi/add_edit_mode
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
4038b31
Align the doc, test, and client with the newest .env setup (image seg…
MuziWong e435426
Add image generation client + update .env + update doc + test files
MuziWong 7af223f
Reformat the code, add scene edit basic framework, notice that the do…
MuziWong 23a6557
Basic design of scene graph
MuziWong c21e399
i
MuziWong dd7cbaf
Fixed the prompt of scne understanding: delete the location in descri…
MuziWong 2dffcfe
modify the prompt of scene understanding, to avoid a very long descri…
MuziWong c3d2203
finish the llm understand scene edit
MuziWong e2177c0
finish the scene edit plan -> updated scene graph
MuziWong 809203a
move client try catch outside the understand_scene
MuziWong 15ec06d
finish part of the scene edit: from user prompt to simreadyed asset, …
MuziWong 0bf17da
finish basic simready real world scale + semantic-based rotation
MuziWong 36c98e4
Moved the table support infos into simready.
MuziWong c6b0cc4
Finished scene edit
MuziWong 8550f14
fix some bug before pr
MuziWong 455d585
fix a real-world size bug, using z-up mesh
MuziWong 4976b47
update image-to-scene scene understanding: let vlm give orientation s…
MuziWong 7fdffdc
fix big-font problem in asset_masks_with_ids; add scene graph based c…
MuziWong a7ff6b1
run black
MuziWong 387646e
run black to tests/
MuziWong 06f0b77
replace hard-code bottle-z-up with VLM auto-analyze
MuziWong b27cdb6
make assets group layout optimizer more rubust
MuziWong 4365da4
delete the hard-coded spatial check in assets' names and descriptionsa
MuziWong b3f6e63
replace jiange's pca heuristic method with VLM operate rotation tools…
MuziWong bc92aa5
ignore the fixed 2d aabb overlap when doing the on-table optimization
MuziWong 361b69d
finished the table as root layout optimization (but still needs to be…
MuziWong 4f6aabb
finish a simple on-relationship optimizer
MuziWong e78ba19
add the heuristic attaches in collision optimization
MuziWong d55581c
replace the assets gravity settler with gravity settler
MuziWong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
172 changes: 172 additions & 0 deletions
172
embodichain/gen_sim/scene_engine/clients/image_generation.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| # ---------------------------------------------------------------------------- | ||
| # Copyright (c) 2021-2026 DexForce Technology Co., Ltd. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # ---------------------------------------------------------------------------- | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| import requests | ||
|
|
||
| from embodichain.gen_sim.scene_engine.configs.environment import ( | ||
| read_scene_engine_env_values, | ||
| ) | ||
|
|
||
|
|
||
| class ImageGenerationClient: | ||
| """Manage the Image Generation Server connection.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| *, | ||
| base_url: str, | ||
| timeout_s: int, | ||
| max_attempts: int, | ||
| health_path: str, | ||
| generate_image_by_prompt_path: str, | ||
| session: requests.Session | None = None, | ||
| ) -> None: | ||
| self._base_url = base_url.rstrip("/") | ||
| self._timeout_s = timeout_s | ||
| self._max_attempts = max_attempts | ||
| self._health_path = health_path | ||
| self._generate_image_by_prompt_path = generate_image_by_prompt_path | ||
| self._session = session or requests.Session() | ||
|
|
||
| @classmethod | ||
| def from_dotenv(cls) -> "ImageGenerationClient": | ||
| """Create a client from its required ``gen_sim/.env`` settings.""" | ||
| return cls(**_load_dotenv_config()) | ||
|
|
||
| def check_health(self) -> None: | ||
| last_error: requests.RequestException | RuntimeError | None = None | ||
| for _ in range(self._max_attempts): | ||
| try: | ||
| response = self._session.get( | ||
| self._url(self._health_path), | ||
| timeout=10, # Use a shorter timeout for avoiding long waits. | ||
| ) | ||
| response.raise_for_status() | ||
| response_data = response.json() | ||
| if ( | ||
| not isinstance(response_data, dict) | ||
| or response_data.get("ok") is not True | ||
| ): | ||
| raise RuntimeError( | ||
| "Image Generation Server health response does not contain ok=true." | ||
| ) | ||
| return | ||
| except (requests.RequestException, ValueError, RuntimeError) as exc: | ||
| last_error = exc | ||
|
|
||
| assert last_error is not None | ||
| raise RuntimeError( | ||
| "Image Generation Server health check failed after " | ||
| f"{self._max_attempts} attempts." | ||
| ) from last_error | ||
|
|
||
| def close(self) -> None: | ||
| self._session.close() | ||
|
|
||
| def generate_image_by_prompt( | ||
| self, | ||
| *, | ||
| prompt: str, | ||
| output_path: str | Path, | ||
| ) -> Path: | ||
| """Generate one PNG image from ``prompt`` and save it to ``output_path``.""" | ||
| prompt = prompt.strip() | ||
| if not prompt: | ||
| raise ValueError("Image generation prompt must not be empty.") | ||
|
|
||
| resolved_output_path = Path(output_path).expanduser().resolve() | ||
| resolved_output_path.parent.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| last_error: Exception | None = None | ||
| for _ in range(self._max_attempts): | ||
| try: | ||
| response = self._session.post( | ||
| self._url(self._generate_image_by_prompt_path), | ||
| json={"prompt": prompt}, | ||
| timeout=self._timeout_s, | ||
| ) | ||
| response.raise_for_status() | ||
| content_type = response.headers.get("content-type", "").split(";")[0] | ||
| if content_type != "image/png": | ||
| raise RuntimeError( | ||
| "Image Generation Server response is not a PNG image." | ||
| ) | ||
| resolved_output_path.write_bytes(response.content) | ||
| return resolved_output_path | ||
| except (requests.RequestException, RuntimeError) as exc: | ||
| last_error = exc | ||
|
|
||
| assert last_error is not None | ||
| raise RuntimeError( | ||
| "Image Generation Server request failed after " | ||
| f"{self._max_attempts} attempts." | ||
| ) from last_error | ||
|
|
||
| def _url(self, path: str) -> str: | ||
| return f"{self._base_url}/{path.lstrip('/')}" | ||
|
|
||
|
|
||
| def _load_dotenv_config() -> dict[str, Any]: | ||
| values = read_scene_engine_env_values( | ||
| "SCENE_ENGINE_IMAGE_GENERATION_BASE_URL", | ||
| "SCENE_ENGINE_IMAGE_GENERATION_TIMEOUT_S", | ||
| "SCENE_ENGINE_IMAGE_GENERATION_MAX_ATTEMPTS", | ||
| "SCENE_ENGINE_IMAGE_GENERATION_HEALTH_PATH", | ||
| "SCENE_ENGINE_IMAGE_GENERATION_BY_PROMPT_PATH", | ||
| ) | ||
| try: | ||
| timeout_s = int(values["SCENE_ENGINE_IMAGE_GENERATION_TIMEOUT_S"]) | ||
| except (TypeError, ValueError) as exc: | ||
| raise ValueError( | ||
| "SCENE_ENGINE_IMAGE_GENERATION_TIMEOUT_S must be an integer." | ||
| ) from exc | ||
| if timeout_s < 1: | ||
| raise ValueError("SCENE_ENGINE_IMAGE_GENERATION_TIMEOUT_S must be at least 1.") | ||
|
|
||
| try: | ||
| max_attempts = int(values["SCENE_ENGINE_IMAGE_GENERATION_MAX_ATTEMPTS"]) | ||
| except (TypeError, ValueError) as exc: | ||
| raise ValueError( | ||
| "SCENE_ENGINE_IMAGE_GENERATION_MAX_ATTEMPTS must be an integer." | ||
| ) from exc | ||
| if max_attempts < 1: | ||
| raise ValueError( | ||
| "SCENE_ENGINE_IMAGE_GENERATION_MAX_ATTEMPTS must be at least 1." | ||
| ) | ||
|
|
||
| string_keys = ( | ||
| "SCENE_ENGINE_IMAGE_GENERATION_BASE_URL", | ||
| "SCENE_ENGINE_IMAGE_GENERATION_HEALTH_PATH", | ||
| "SCENE_ENGINE_IMAGE_GENERATION_BY_PROMPT_PATH", | ||
| ) | ||
| for key in string_keys: | ||
| if not values[key].strip(): | ||
| raise ValueError(f"Scene Engine .env key {key} must be a non-empty string.") | ||
|
|
||
| return { | ||
| "base_url": values["SCENE_ENGINE_IMAGE_GENERATION_BASE_URL"].strip(), | ||
| "timeout_s": timeout_s, | ||
| "max_attempts": max_attempts, | ||
| "health_path": values["SCENE_ENGINE_IMAGE_GENERATION_HEALTH_PATH"].strip(), | ||
| "generate_image_by_prompt_path": values[ | ||
| "SCENE_ENGINE_IMAGE_GENERATION_BY_PROMPT_PATH" | ||
| ].strip(), | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new public module does not define
__all__, leaving its intended API ambiguous and allowing wildcard imports to expose incidental imported names.Context Used: CLAUDE.md (source)
Prompt To Fix With AI
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!