import spaces import os import gradio as gr from time import sleep from signal import SIGTERM from psutil import process_iter from settings import GRAND3D_Settings from utils import list_dirs import open3d as o3d from copy import deepcopy import numpy as np import re from bs4 import BeautifulSoup import trimesh.transformations as tf import logging # The following line sets the root logger level as well. # It's equivalent to both previous statements combined: logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) from session import Session from model import load_model_and_dataloader, get_model_response # Load model and tokenizer once at the start model_path = "checkpoints/merged_weights_grounded_obj_ref" model_base = None load_8bit = False load_4bit = False load_bf16 = True scene_to_obj_mapping = "data/predicted_scene_data_update_5.json" # scene_to_obj_mapping = "data/scanrefer_ground_truth_scene_graph.json" max_new_tokens = 5000 obj_context_feature_type = "text" tokenizer, model, data_loader = load_model_and_dataloader( model_path=model_path, model_base=model_base, load_8bit=load_8bit, load_4bit=load_4bit, load_bf16=load_bf16, scene_to_obj_mapping=scene_to_obj_mapping, device_map="cpu", ) # Huggingface Zero-GPU has to use .to(device) to set the device, otherwise it will fail model.to("cuda") # Huggingface Zero-GPU requires explicit device placement def get_chatbot_response(user_chat_input, scene_id): # Get the response from the model prompt, response = get_model_response( model=model, tokenizer=tokenizer, data_loader=data_loader, scene_id=scene_id, user_input=user_chat_input, max_new_tokens=max_new_tokens, temperature=0.2, top_p=0.9, ) return scene_id, prompt, response # def get_chatbot_response(user_chat_input): # # Get the response from the chatbot # scene_id = "scene0643_00" # scene_graph = """ # Object-centric context: : {'category': 'door', 'centroid': '[0.35, 1.99, 1.11]', 'extent': '[0.68, 0.65, 2.11]'}; : {'category': 'ceiling', 'centroid': '[1.04, -1.39, 2.68]', 'extent': '[0.18, 0.90, 0.05]'}; : {'category': 'ceiling', 'centroid': '[0.77, 2.09, 2.65]', 'extent': '[0.94, 0.86, 0.11]'}; : {'category': 'trash can', 'centroid': '[-0.61, -2.16, 0.21]', 'extent': '[0.42, 0.36, 0.41]'}; : {'category': 'chair', 'centroid': '[0.35, -1.35, 0.50]', 'extent': '[0.46, 0.47, 0.94]'}; : {'category': 'trash can', 'centroid': '[-0.22, -2.13, 0.24]', 'extent': '[0.40, 0.28, 0.39]'}; : {'category': 'cabinet', 'centroid': '[-1.24, 0.00, 0.58]', 'extent': '[0.61, 0.57, 0.79]'}; : {'category': 'cup', 'centroid': '[0.62, 0.23, 0.77]', 'extent': '[0.14, 0.14, 0.08]'}; : {'category': 'window', 'centroid': '[-0.35, -2.87, 1.13]', 'extent': '[2.05, 0.60, 1.07]'}; : {'category': 'file cabinet', 'centroid': '[0.40, -1.97, 0.39]', 'extent': '[0.40, 0.66, 0.73]'}; : {'category': 'monitor', 'centroid': '[0.92, -1.51, 0.97]', 'extent': '[0.25, 0.57, 0.47]'}; : {'category': 'chair', 'centroid': '[0.34, 0.59, 0.43]', 'extent': '[0.65, 0.64, 0.94]'}; : {'category': 'desk', 'centroid': '[0.64, 0.75, 0.57]', 'extent': '[0.76, 1.60, 0.82]'}; : {'category': 'chair', 'centroid': '[0.55, -0.33, 0.48]', 'extent': '[0.60, 0.60, 0.87]'}; : {'category': 'office chair', 'centroid': '[-0.28, 1.56, 0.46]', 'extent': '[0.67, 0.55, 1.02]'}; : {'category': 'office chair', 'centroid': '[-0.86, -1.53, 0.43]', 'extent': '[0.54, 0.64, 0.97]'}; : {'category': 'chair', 'centroid': '[-0.28, 1.56, 0.46]', 'extent': '[0.67, 0.55, 1.02]'}; : {'category': 'monitor', 'centroid': '[0.98, 0.56, 1.05]', 'extent': '[0.21, 0.60, 0.54]'}; : {'category': 'doorframe', 'centroid': '[-0.17, 2.42, 1.01]', 'extent': '[0.16, 0.18, 1.70]'}; : {'category': 'chair', 'centroid': '[-0.86, -1.53, 0.43]', 'extent': '[0.54, 0.64, 0.97]'}; : {'category': 'bookshelf', 'centroid': '[0.93, 2.00, 1.34]', 'extent': '[0.73, 0.99, 2.60]'}; : {'category': 'office chair', 'centroid': '[0.35, -1.35, 0.50]', 'extent': '[0.46, 0.47, 0.94]'}; : {'category': 'desk', 'centroid': '[-1.23, 1.60, 0.70]', 'extent': '[0.80, 2.01, 0.51]'}; : {'category': 'book', 'centroid': '[0.91, 1.31, 0.89]', 'extent': '[0.34, 0.32, 0.30]'}; : {'category': 'desk', 'centroid': '[-1.24, -1.12, 0.54]', 'extent': '[0.79, 1.88, 0.85]'}; : {'category': 'desk', 'centroid': '[0.63, -1.51, 0.53]', 'extent': '[0.81, 1.97, 0.85]'}; : {'category': 'calendar', 'centroid': '[-1.72, -0.44, 1.40]', 'extent': '[0.07, 0.88, 0.83]'}; : {'category': 'office chair', 'centroid': '[0.34, 0.59, 0.43]', 'extent': '[0.65, 0.64, 0.94]'}; : {'category': 'file cabinet', 'centroid': '[-1.02, -0.76, 0.47]', 'extent': '[0.58, 0.75, 0.81]'}; : {'category': 'cup', 'centroid': '[-1.26, -1.65, 0.78]', 'extent': '[0.10, 0.12, 0.04]'}; : {'category': 'keyboard', 'centroid': '[0.55, 0.84, 0.73]', 'extent': '[0.22, 0.15, 0.03]'} # """ # response = """ # a

brown wooden office desk

[] on the left to the

gray shelf

[].
These sentences refer to

the brown wooden office desk

[].
# """ # return scene_id, scene_graph, response # Resetting to blank def reset_textbox(): return gr.update(value="") # to set a component as visible=False def set_visible_false(): return gr.update(visible=False) # to set a component as visible=True def set_visible_true(): return gr.update(visible=True) def change_scene_or_system_prompt(dropdown_scene_selection: str): # reset model_3d, chatbot_for_display, chat_counter, server_status_code new_session_state = Session.create_for_scene(dropdown_scene_selection) file_name = f"{dropdown_scene_selection}.obj" print(os.path.join(GRAND3D_Settings.data_path, dropdown_scene_selection, file_name)) return ( new_session_state, os.path.join(GRAND3D_Settings.data_path, dropdown_scene_selection, file_name), None, new_session_state.chat_history_for_display, ) def cylinder_frame(p0, p1): """Calculate the transformation matrix to position a unit cylinder between two points.""" direction = np.asarray(p1) - np.asarray(p0) length = np.linalg.norm(direction) direction /= length # Computing rotation matrix using Rodrigues' formula rot_axis = np.cross([0, 0, 1], direction) rot_angle = np.arccos(np.dot([0, 0, 1], direction)) rot_matrix = o3d.geometry.get_rotation_matrix_from_axis_angle(rot_axis * rot_angle) # Translation translation = (np.asarray(p0) + np.asarray(p1)) / 2 transformation = np.eye(4) transformation[:3, :3] = rot_matrix transformation[:3, 3] = translation scaling = np.eye(4) scaling[2, 2] = length transformation = np.matmul(transformation, scaling) return transformation def create_cylinder_mesh(p0, p1, color, radius=0.04, resolution=20, split=1): """Create a colored cylinder mesh between two points p0 and p1.""" cylinder = o3d.geometry.TriangleMesh.create_cylinder( radius=radius, height=1, resolution=resolution, split=split ) transformation = cylinder_frame(p0, p1) cylinder.transform(transformation) # Apply color cylinder.paint_uniform_color(color) return cylinder def prettify_mesh_for_gradio(mesh): # Define the transformation matrix T = np.array([[0, -1, 0, 0], [0, 0, 1, 0], [-1, 0, 0, 0], [0, 0, 0, 1]]) # Apply the transformation mesh.transform(T) mesh.scale(10.0, center=mesh.get_center()) bright_factor = 1 # Adjust this factor to get the desired brightness mesh.vertex_colors = o3d.utility.Vector3dVector( np.clip(np.asarray(mesh.vertex_colors) * bright_factor, 0, 1) ) return mesh def create_bbox(center, extents, color=[1, 0, 0], radius=0.02): """Create a colored bounding box with given center, extents, and line thickness.""" # ... [The same code as before to define corners and lines] ... print(extents) print(type(extents)) extents = extents.replace("[", "").replace("]", "") center = center.replace("[", "").replace("]", "") extents = [float(x.strip()) for x in extents.split(",")] center = [float(x.strip()) for x in center.split(",")] angle = -np.pi / 2 # 90 degrees axis = [1, 0, 0] # Rotate around x-axis R = tf.rotation_matrix(angle, axis) center_homogeneous = np.append(center, 1) extents_homogeneous = np.append(extents, 1) # Apply the rotation to the center and extents rotated_center = np.dot(R, center_homogeneous)[:3] rotated_extents = np.dot(R, extents_homogeneous)[:3] sx, sy, sz = rotated_extents x_corners = [sx / 2, sx / 2, -sx / 2, -sx / 2, sx / 2, sx / 2, -sx / 2, -sx / 2] y_corners = [sy / 2, -sy / 2, -sy / 2, sy / 2, sy / 2, -sy / 2, -sy / 2, sy / 2] z_corners = [sz / 2, sz / 2, sz / 2, sz / 2, -sz / 2, -sz / 2, -sz / 2, -sz / 2] corners_3d = np.vstack([x_corners, y_corners, z_corners]) corners_3d[0, :] = corners_3d[0, :] + float(rotated_center[0]) corners_3d[1, :] = corners_3d[1, :] + float(rotated_center[1]) corners_3d[2, :] = corners_3d[2, :] + float(rotated_center[2]) corners_3d = np.transpose(corners_3d) lines = [ [0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7], ] cylinders = [] for line in lines: p0, p1 = corners_3d[line[0]], corners_3d[line[1]] cylinders.append(create_cylinder_mesh(p0, p1, color, radius)) return cylinders def highlight_clusters_in_mesh( centroids_extents_detailed, centroids_extends_refer, mesh, output_dir, output_file_name="highlighted_mesh.obj", ): print("*" * 50) # Visualize the highlighted points by drawing 3D bounding boxes overlay on a mesh old_mesh = deepcopy(mesh) output_path = os.path.join(output_dir, "mesh_vis") if not os.path.exists(output_path): os.makedirs(output_path) # Create a combined mesh to hold both the original and the bounding boxes combined_mesh = o3d.geometry.TriangleMesh() combined_mesh += old_mesh # Draw bounding boxes for each centroid and extent for center, extent in centroids_extents_detailed: print("center: ", center) print("extent: ", extent) bbox = create_bbox( center, extent, color=[1, 1, 0] ) # yellow color for all boxes for b in bbox: combined_mesh += b for center, extent in centroids_extends_refer: bbox = create_bbox(center, extent, color=[0, 1, 0]) for b in bbox: combined_mesh += b # Save the combined mesh output_file_path = os.path.join(output_path, output_file_name) o3d.io.write_triangle_mesh( output_file_path, combined_mesh, write_vertex_colors=True ) print("*" * 50) return output_file_path def extract_objects(text): return re.findall(r"", text) # Parse the scene graph into a dictionary def parse_scene_graph(scene_graph): scene_dict = {} matches = re.findall(r": (\{.*?\})", scene_graph) for match in matches: obj_id = f"" obj_data = eval(match[1]) scene_dict[obj_id] = obj_data return scene_dict def get_centroids_extents(obj_list, scene_dict): centroids_extents = [] for obj in obj_list: if obj in scene_dict: centroid = scene_dict[obj]["centroid"] extent = scene_dict[obj]["extent"] centroids_extents.append((centroid, extent)) return centroids_extents @spaces.GPU def language_model_forward( session_state, user_chat_input, top_p, temperature, dropdown_scene ): session_state = Session.create_for_scene(dropdown_scene) session_state.chat_history_for_display.append( (user_chat_input, None) ) # append in a tuple format, first is user input, second is assistant response yield session_state, None, session_state.chat_history_for_display # Load in a 3D model file_name = f"{session_state.scene}.obj" original_model_path = os.path.join( GRAND3D_Settings.data_path, session_state.scene, file_name ) print("original_model_path: ", original_model_path) # Load the GLB mesh mesh = o3d.io.read_triangle_mesh(original_model_path) # get chatbot response scene_id, scene_graph, response = get_chatbot_response( user_chat_input, session_state.scene ) assert scene_id == session_state.scene # Ensure the scene ID matches # use scene_graph and response to get centroids and extents # Parse the scene graph into a dictionary scene_dict = parse_scene_graph(scene_graph) print("Model Input: " + str(scene_dict)) print("=" * 50) print("Model Response: " + response) # Parse the response to get detailed and refer expression groundings soup = BeautifulSoup(response, "html.parser") detailed_grounding_html = str(soup.find("detailed_grounding")) refer_expression_grounding_html = str(soup.find("refer_expression_grounding")) # Extract objects from both sections detailed_objects = extract_objects(detailed_grounding_html) refer_objects = extract_objects(refer_expression_grounding_html) # Extract objects from both sections print("detailed_objects: ", detailed_objects) print("refer_objects: ", refer_objects) # Perform set subtraction to get remaining objects remaining_objects = list(set(detailed_objects) - set(refer_objects)) print("remaining_objects: ", remaining_objects) centroids_extents_detailed = get_centroids_extents(remaining_objects, scene_dict) print("centroids_extents_detailed: ", centroids_extents_detailed) centroids_extents_refer = get_centroids_extents(refer_objects, scene_dict) print("centroids_extents_refer: ", centroids_extents_refer) # Define your centroids and extents here (example data) # Highlight clusters in the mesh and save it session_output_dir = session_state.get_session_output_dir() highlighted_model_path = highlight_clusters_in_mesh( centroids_extents_detailed, centroids_extents_refer, mesh, session_output_dir, output_file_name="highlighted_model.obj", ) # Update the chat history with the response last_turn = session_state.chat_history_for_display[ -1 ] # first is user input, second is assistant response last_turn = (last_turn[0], response) session_state.chat_history_for_display[-1] = last_turn session_state.save() # save the session state yield session_state, highlighted_model_path, session_state.chat_history_for_display title = """

🏠💬 3D-GRAND: Towards Better Grounding and Less Hallucination for 3D-LLMs 🚀

[Project Page] [3D-GRAND Data] [3D-POPE Data]

""" # Modifying existing Gradio Theme # theme = gr.themes.Soft( # primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.pink # ) with gr.Blocks(theme=gr.themes.Soft()) as demo: session_state = gr.State(Session.create) gr.HTML(title) with gr.Column(): with gr.Row(): with gr.Column(scale=5): dropdown_scene = gr.Dropdown( choices=list_dirs(GRAND3D_Settings.data_path), value=GRAND3D_Settings.default_scene, interactive=True, label="Select a scene", ) model_3d = gr.Model3D( value=os.path.join( GRAND3D_Settings.data_path, GRAND3D_Settings.default_scene, f"{GRAND3D_Settings.default_scene}.obj", ), clear_color=[0.0, 0.0, 0.0, 0.0], label="3D Model", camera_position=(-50, 65, 6), zoom_speed=10.0, ) gr.HTML( """
👆 SCROLL or DRAG on the 3D Model to zoom in/out and rotate. Press CTRL and DRAG to pan.
""" ) gr.HTML( """
👇 When grounding finishes, the grounding result will be displayed below.
""" ) model_3d_grounding_result = gr.Model3D( clear_color=[0.0, 0.0, 0.0, 0.0], label="Grounding Result", camera_position=(-50, 65, 6), zoom_speed=10.0, ) gr.HTML( """
= Predicted Target  
= Grounded Chain-of-Thought  
""" ) with gr.Column(scale=5): chat_history_for_display = gr.Chatbot( value=[(None, GRAND3D_Settings.INITIAL_MSG_FOR_DISPLAY)], label="Chat Assistant", height=510, render_markdown=False, sanitize_html=False, ) with gr.Row(): with gr.Column(scale=8): user_chat_input = gr.Textbox( placeholder="I want to find the chair near the table", show_label=False, ) with gr.Column(scale=1, min_width=0): send_button = gr.Button("Send", variant="primary") with gr.Column(scale=1, min_width=0): clear_button = gr.Button("Clear") with gr.Row(): with gr.Accordion(label="Examples for user message:", open=True): gr.Examples( examples=[ ["The TV on the drawer, opposing the bed."], ["the desk next to the window"], ], inputs=user_chat_input, ) with gr.Accordion("Parameters", open=False, visible=False): top_p = gr.Slider( minimum=0, maximum=1.0, value=1.0, step=0.05, interactive=True, label="Top-p (nucleus sampling)", ) temperature = gr.Slider( minimum=0, maximum=5.0, value=1.0, step=0.1, interactive=True, label="Temperature", ) # gr.Markdown("### Terms of Service") # gr.HTML( # """By using this service, users are required to agree to the following terms: # The service is a research preview intended for non-commercial use only. # The service may collect user dialogue data for future research.""" # ) # Event handling dropdown_scene.change( fn=change_scene_or_system_prompt, inputs=[dropdown_scene], outputs=[ session_state, model_3d, model_3d_grounding_result, chat_history_for_display, ], ) clear_button.click( fn=change_scene_or_system_prompt, inputs=[dropdown_scene], outputs=[ session_state, model_3d, model_3d_grounding_result, chat_history_for_display, ], ) user_chat_input.submit( fn=language_model_forward, inputs=[session_state, user_chat_input, top_p, temperature, dropdown_scene], outputs=[session_state, model_3d_grounding_result, chat_history_for_display], ) send_button.click( fn=language_model_forward, inputs=[session_state, user_chat_input, top_p, temperature, dropdown_scene], outputs=[session_state, model_3d_grounding_result, chat_history_for_display], ) send_button.click(reset_textbox, [], [user_chat_input]) user_chat_input.submit(reset_textbox, [], [user_chat_input]) sleep_time = 2 port = 7011 for x in range(1, 10): # try 8 times try: # put your logic here gr.close_all() demo.queue( max_size=20, ).launch( # debug=True, # server_name="0.0.0.0", # server_port=port, share=True ) except OSError: for proc in process_iter(): for conns in proc.connections(kind="inet"): if conns.laddr.port == port: proc.send_signal(SIGTERM) # or SIGKILL print(f"Retrying {x} time...") pass sleep(sleep_time) # wait for 2 seconds before trying to fetch the data again sleep_time *= 2 # exponential backoff