diff --git a/UI/README.md b/UI/README.md new file mode 100644 index 0000000..908e8a2 --- /dev/null +++ b/UI/README.md @@ -0,0 +1,5 @@ +# Versions + +- wcag_validator_ui_pre_multimodel.py : the version used for the 12_2025 user test. Has to work with restServer with only one model (not two) +- wcag_validator_ui.py : the official version + diff --git a/UI/requirements_UI.txt b/UI/requirements_UI.txt index 8bb7be9..87efef4 100644 --- a/UI/requirements_UI.txt +++ b/UI/requirements_UI.txt @@ -1,4 +1,5 @@ gradio==5.49.1 pandas==2.3.3 python-dotenv==1.2.1 -requests==2.32.5 \ No newline at end of file +requests==2.32.5 +gradio-modal==0.0.4 \ No newline at end of file diff --git a/UI/wcag_validator_ui.py b/UI/wcag_validator_ui.py index 9d3ec2e..877db24 100644 --- a/UI/wcag_validator_ui.py +++ b/UI/wcag_validator_ui.py @@ -3,6 +3,7 @@ # python wcag_validator_ui.py import gradio as gr +from gradio_modal import Modal import requests from pathlib import Path import sys @@ -33,7 +34,19 @@ WCAG_VALIDATOR_RESTSERVER_HEADERS = [("Content-Type", "application/json")] def process_dataframe(db_path, url, updated_df, user_state={},llm_response_output={}): - print("Processing dataframe to adjust columns...") + print("Processing dataframe to adjust columns...type:",type(updated_df)) + # accept different input forms from UI (DataFrame, JSON string, or list of dicts) + try: + + if isinstance(updated_df, str): + try: + updated_df = pd.read_json(updated_df, orient="records") + except Exception: + updated_df = pd.read_json(updated_df) + elif isinstance(updated_df, list): + updated_df = pd.DataFrame(updated_df) + except Exception as e: + return f"Error parsing updated data: {str(e)}" for column_rating_name in ["User Assessment for LLM Proposal 1", "User Assessment for LLM Proposal 2"]: # Get the assessment column @@ -41,6 +54,10 @@ def process_dataframe(db_path, url, updated_df, user_state={},llm_response_outpu updated_df[column_rating_name] = updated_df[column_rating_name].astype(int) except ValueError: return "Error: User Assessment for LLM Proposal must be an integer" + except KeyError: + return f"No data Saved because no image selected. Please select at least one image." + except Exception as e: + return f"Error processing User Assessment for LLM Proposal: {str(e)}" if (updated_df[column_rating_name] < 1).any() or ( updated_df[column_rating_name] > 5 @@ -50,7 +67,8 @@ def process_dataframe(db_path, url, updated_df, user_state={},llm_response_outpu dataframe_json = updated_df.to_json(orient="records") connection_db = sqlite3.connect(db_path) json_user_str = json.dumps({"username": user_state["username"]}, ensure_ascii=False) - lm_response_output_str = json.dumps(llm_response_output, ensure_ascii=False) #recuperato dalla chiamata all'llm, ho tutte le info anche sulle immagini + + llm_response_output_str = json.dumps(llm_response_output, ensure_ascii=False) #recuperato dalla chiamata all'llm, ho tutte le info anche sulle immagini try: # insert after everything to keep datetime aligned db_persistence_insert( @@ -59,7 +77,7 @@ def process_dataframe(db_path, url, updated_df, user_state={},llm_response_outpu page_url=url, user=json_user_str, llm_model="", - json_in_str=lm_response_output_str,#dataframe_json, # to improve + json_in_str=llm_response_output_str,#dataframe_json, # to improve json_out_str=dataframe_json, table="wcag_user_assessments", ) @@ -81,7 +99,7 @@ def load_images_from_json(json_input): images = data["images"] info_text = f"Found {len(images)} image(s)" - print(f"Found {len(data['images'])} image(s)") + # Create HTML gallery with checkboxes and assessment forms html = """ @@ -133,7 +151,7 @@ def load_images_from_json(json_input): display: none; margin-top: 15px; padding: 10px; - background: #7896b9; + background: aliceblue; border-radius: 4px; border: 1px solid #2196F3; } @@ -376,8 +394,8 @@ def make_alttext_llm_assessment_api_call( if not selected_images or len(selected_images) == 0: info_text = "No images selected" - print(info_text) - return "LLM assessment not started", pd.DataFrame() + + return "LLM assessment not started", pd.DataFrame(), {} # prepare data for insertion json_in_str = {} @@ -442,7 +460,7 @@ def make_alttext_llm_assessment_api_call( info_dataframe["User Assessment for LLM Proposal 2"] = ( user_assessments_llm_proposal_2 ) - print("info_dataframe after adding user assessments:", info_dataframe) + #print("info_dataframe after adding user assessments:", info_dataframe) except Exception as e: return {"error": str(e)} @@ -493,12 +511,134 @@ def make_image_extraction_api_call( return {"error": str(e)} +def render_alttext_form(df): + """Render a pandas DataFrame (or list/dict) into an editable HTML form.""" + try: + if df is None: + return "" + if isinstance(df, str): + df = pd.read_json(df, orient="records") + if isinstance(df, dict): + df = pd.DataFrame(df) + if isinstance(df, list): + df = pd.DataFrame(df) + + html = """ + + + + + + + + + + + + + + + + + + + """ + + for _, row in df.iterrows(): + imgnum = row.get("Image #", "") + orig = row.get("Original Alt Text", "") + user_ass = row.get("User Assessment", "") + user_prop = row.get("User Proposed Alt Text", "") + llm1_ass = row.get("LLM Assessment 1", "") + llm2_ass = row.get("LLM Assessment 2", "") + llm1_prop = row.get("LLM Proposed Alt Text 1", "") + llm2_prop = row.get("LLM Proposed Alt Text 2", "") + + user_llm1_ass = row.get("User Assessment for LLM Proposal 1", 3) + user_llm2_ass = row.get("User Assessment for LLM Proposal 2", 3) + + html += f""" + + + + + + + + + + + + + + """ + + html += """ + +
Image #Original Alt TextUser AssessmentUser Proposed Alt TextLLM Assessment 1LLM Proposed Alt Text 1User Assessment for LLM Proposal 1LLM Assessment 2LLM Proposed Alt Text 2User Assessment for LLM Proposal 2
{imgnum}{orig}{user_ass}{user_prop}{llm1_ass}{llm1_prop} + + {llm2_ass}{llm2_prop} + +
+ """ + + return gr.update(value=html), html + except Exception as e: + return f"Error rendering form: {str(e)}" + + # ------- Gradio Interface -------# # Create Gradio interface with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo: + + + gr.HTML(""" + + """) + + + llm_response_output = gr.State() + alttext_popup_html_state = gr.State("") + + with Modal(visible=False, allow_user_close=False) as alttext_modal: + gr.Markdown("## Alt Text LLMs Assessment Results") + gr.Markdown("To assess the LLMs outputs, use the dropdowns to indicate how much you agree with the LLM proposed alt text.") + alttext_modal_content = gr.HTML("") + close_modal_btn = gr.Button("Save Your Assessment", variant="secondary",elem_classes=["close-modal-btn"]) + env_path = find_dotenv(filename=".env") if env_path == "": print("env path not found: service starting with the default params values") @@ -539,6 +679,7 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo: login_password = gr.Textbox( label="Password", type="password", placeholder="Enter your password" ) + login_btn = gr.Button("Login", variant="primary") login_msg = gr.Textbox(label="Login Status", interactive=False) @@ -556,8 +697,9 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo: type="password", placeholder="Confirm your password", ) + reg_btn = gr.Button("Register", variant="primary") - reg_msg = gr.Textbox(label="Registration Status", interactive=False) + reg_msg = gr.Textbox(label="Registration Status", interactive=True) with gr.Column(visible=False) as protected_section: @@ -598,7 +740,7 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo: "Extract Images & Alt Texts", variant="primary" ) alttext_api_call_btn = gr.Button( - "Start LLM Assessment", + "Start LLMs Assessment", variant="secondary", interactive=False, ) @@ -608,41 +750,16 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo: with gr.Row(visible=False) as alttext_results_row: - # Use DataFrame for tabular output - alttext_info_output = gr.DataFrame( - headers=[ - "Image #", - "Original Alt Text", - "User Assessment", - "User Proposed Alt Text", - "LLM Assessment 1", - "LLM Proposed Alt Text 1", - "User Assessment for LLM Proposal 1", - "LLM Assessment 2", - "LLM Proposed Alt Text 2", - "User Assessment for LLM Proposal 2", - ], - label="LLM Assessment Results", - wrap=True, # Wrap text in cells - interactive=True, - scale=7, - ) - with gr.Column(): - save_user_assessment_btn = gr.Button( - "Save Your Assessment", - variant="secondary", - interactive=True, - scale=1, - ) - gr.Markdown( - "ℹ Info: to assess the LLM output, only the values ​​for the 'User Assessment for LLM Proposal' column need to be changed." - ) - - llm_response_output=gr.JSON() + # Store the DataFrame in state and render a clear HTML form for user edits + alttext_info_state = gr.State() + alttext_form = gr.HTML(label="Assessment Form") + alttext_form_data = gr.JSON(visible=False) + with gr.Row(): - gallery_html = gr.HTML(label="Image Gallery") + + image_extraction_api_call_btn.click( fn=lambda: ("", "", gr.update(visible=False), gr.Button(interactive=False)), @@ -663,8 +780,6 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo: outputs=[alttext_api_call_btn], ) - # Process selected images with JavaScript - alttext_api_call_btn.click( fn=make_alttext_llm_assessment_api_call, inputs=[ @@ -674,7 +789,7 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo: wcag_rest_server_url_state, user_state, ], - outputs=[image_info_output, alttext_info_output,llm_response_output], + outputs=[image_info_output, alttext_info_state, llm_response_output], js=""" (url_input,gallery_html) => { const checkboxes = document.querySelectorAll('.image-checkbox:checked'); @@ -693,7 +808,6 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo: const imageUrl = checkbox.dataset.imgurl; const originalAlt = document.querySelector('.original-alt[data-index="' + index + '"]').value; const assessment = document.querySelector('input[name="assessment-' + index + '"]:checked').value; - console.log("assessment:",assessment) const newAltText = document.querySelector('.new-alt-text[data-index="' + index + '"]').value; selectedData.push({ @@ -709,15 +823,65 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo: } """, ).then( - fn=lambda: gr.update(visible=True), - inputs=[], - outputs=[alttext_results_row], + fn=render_alttext_form, + inputs=[alttext_info_state], + outputs=[alttext_form,alttext_popup_html_state], + ).then( + fn=lambda html: (gr.update(value=html), Modal(visible=True)), + inputs=[alttext_popup_html_state], + outputs=[alttext_modal_content, alttext_modal], # ← populate + open modal ) + - save_user_assessment_btn.click( + + close_modal_btn.click( #the close button now save fn=process_dataframe, - inputs=[db_path_state, url_input, alttext_info_output, user_state,llm_response_output], + inputs=[db_path_state, url_input, alttext_form_data, user_state,llm_response_output], outputs=[image_info_output], + js=""" + (db_path_state, url_input, alttext_form_html, user_state, llm_response_output) => { + const rows = document.querySelectorAll('.alttext-row'); + const selectedData = []; + rows.forEach(row => { + const imgNum = row.querySelector('.img-num')?.innerText || ''; + const origAlt = row.querySelector('.orig-alt')?.innerText || ''; + const userAssessment = row.querySelector('.user-assessment')?.innerText || '3'; + const userProposed = row.querySelector('.user-proposed')?.innerText || ''; + const user_llm1_ass = row.querySelector('.user_llm1_ass')?.value || '3'; + const user_llm2_ass = row.querySelector('.user_llm2_ass')?.value || '3'; + + selectedData.push({ + "Image #": imgNum, + "Original Alt Text": origAlt, + "User Assessment": parseInt(userAssessment)||3, + "User Proposed Alt Text": userProposed, + "User Assessment for LLM Proposal 1": parseInt(user_llm1_ass), + "User Assessment for LLM Proposal 2": parseInt(user_llm2_ass) + }); + }); + return [db_path_state, url_input, selectedData, user_state, llm_response_output]; + } + """, + ).then( # Close button dismisses the modal + fn=lambda: Modal(visible=False), + inputs=[], + outputs=[alttext_modal], + js=""" + async () => { + const btn = document.querySelector('.close-modal-btn'); + + // Change button text + btn.textContent = 'Saving...'; + + // Fade out + const modal = document.querySelector('.modal-container'); + modal.style.transition = 'opacity 0.4s ease'; + modal.style.opacity = '0'; + + // Wait for fade + await new Promise(resolve => setTimeout(resolve, 400)); + } + """ ) # placed here at the end to give full contents visibility to events diff --git a/UI/wcag_validator_ui_pre_multimodel.py b/UI/wcag_validator_ui_pre_multimodel.py new file mode 100644 index 0000000..60154dc --- /dev/null +++ b/UI/wcag_validator_ui_pre_multimodel.py @@ -0,0 +1,686 @@ +#### To launch the script +# gradio wcag_validator_ui.py +# python wcag_validator_ui.py +# this is the UI version for the 12_2025 student test. The rest Server should work on single model + +import gradio as gr +import requests +from pathlib import Path +import sys +import pandas as pd + +parent_dir = Path(__file__).parent.parent +sys.path.insert(0, str(parent_dir)) +from dotenv import load_dotenv, find_dotenv +from dependences.utils import ( + call_API_urlibrequest, + create_folder, + db_persistence_startup, + db_persistence_insert, + return_from_env_valid, +) +from dependences_ui.utils import * +import logging +import time +import json +import urllib.request +import urllib.parse +import os +import sqlite3 + + +WCAG_VALIDATOR_RESTSERVER_HEADERS = [("Content-Type", "application/json")] + + +def process_dataframe(db_path, url, updated_df, user_state={}): + + print("Processing dataframe to adjust columns...") + column_rating_name = "User Assessment for LLM Proposal" + + # Get the assessment column + try: + updated_df[column_rating_name] = updated_df[column_rating_name].astype(int) + except ValueError: + return "Error: User Assessment for LLM Proposal must be an integer" + + if (updated_df[column_rating_name] < 1).any() or ( + updated_df[column_rating_name] > 5 + ).any(): + return "Error: User Assessment for LLM Proposal must be between 1 and 5" + + dataframe_json = updated_df.to_json(orient="records") + connection_db = sqlite3.connect(db_path) + json_user_str = json.dumps({"username": user_state["username"]}, ensure_ascii=False) + try: + # insert after everything to keep datetime aligned + db_persistence_insert( + connection_db=connection_db, + insert_type="wcag_user_llm_alttext_assessments", + page_url=url, + user=json_user_str, + llm_model="", + json_in_str=dataframe_json, # to improve + json_out_str="done via UI", + table="wcag_user_assessments", + ) + except Exception as e: + print("Error inserting user assessment into database:", str(e)) + finally: + if connection_db: + connection_db.close() + return "User assessment saved successfully!" + + +def load_images_from_json(json_input): + """Extract URLs and alt text from JSON and create HTML gallery""" + try: + data = json_input + + if "images" not in data or not data["images"]: + return "No images found in JSON", "" + + images = data["images"] + info_text = f"Found {len(images)} image(s)" + print(f"Found {len(data['images'])} image(s)") + + # Create HTML gallery with checkboxes and assessment forms + html = """ + + " + + return info_text, html + + except json.JSONDecodeError as e: + return f"Error: Invalid JSON format - {str(e)}", "" + except Exception as e: + return f"Error: {str(e)}", "" + + +def load_llm_assessment_from_json(json_input): + + try: + # Parse JSON input + data = json_input + + if "mllm_validations" not in data or not data["mllm_validations"]: + print("no mllm_validations found") + return pd.DataFrame() + + info_text = f"Assessment done on {len(data['mllm_validations']['mllm_alttext_assessments'])} image(s)\n\n" + print( + f"Assessment done on {len(data['mllm_validations']['mllm_alttext_assessments'])} image(s)" + ) + + data_frame = [] + for idx, img_data in enumerate( + data["mllm_validations"]["mllm_alttext_assessments"], 1 + ): + + original_alt_text_assessment = img_data["mllm_response"].get( + "original_alt_text_assessment", "No description" + ) + new_alt_text = img_data["mllm_response"].get( + "new_alt_text", "No description" + ) + alt_text_original = img_data.get("alt_text", "No alt_text provided") + + data_frame.append( + { + "Original Alt Text": alt_text_original, + "LLM Assessment": original_alt_text_assessment, + "LLM Proposed Alt Text": new_alt_text, + } + ) + + df = pd.DataFrame(data_frame) + return df + + except json.JSONDecodeError as e: + return f"Error: Invalid JSON format - {str(e)}", [] + except Exception as e: + return f"Error: {str(e)}", [] + + +def make_alttext_llm_assessment_api_call( + url, + selected_images_json=[], + db_path=None, + wcag_rest_server_url="http://localhost:8000", + user_state={}, + number_of_images=30, +): + + print( + f"Making API call for llm assessment for {url} to {wcag_rest_server_url}/wcag_alttext_validation" + ) + selected_images = json.loads(selected_images_json) if selected_images_json else [] + # print("selected_images:", selected_images) + + if not selected_images or len(selected_images) == 0: + info_text = "No images selected" + print(info_text) + return "LLM assessment not started", pd.DataFrame() + + # prepare data for insertion + json_in_str = {} + json_out_str = {} + selected_urls = [] + selected_alt_text_original = [] + user_assessments = [] + user_new_alt_texts = [] + selected_image_id = [] + user_assessments_llm_proposal = [] + for img in selected_images: + selected_urls.append(img["image_url"]) + selected_alt_text_original.append(img["original_alt_text"]) + user_assessments.append(img["assessment"]) + user_new_alt_texts.append(img["new_alt_text"]) + selected_image_id.append( + int(img["image_index"]) + 1 + ) # add the id selected (+1 for index alignment) + user_assessments_llm_proposal.append(3) # default value for now + json_in_str["images_urls"] = selected_urls + json_in_str["images_alt_text_original"] = selected_alt_text_original + json_out_str["user_assessments"] = user_assessments + json_out_str["user_new_alt_texts"] = user_new_alt_texts + json_in_str = json.dumps(json_in_str, ensure_ascii=False) + json_out_str = json.dumps(json_out_str, ensure_ascii=False) + json_user_str = json.dumps({"username": user_state["username"]}, ensure_ascii=False) + connection_db = sqlite3.connect(db_path) + # --------- + + try: + + response = call_API_urlibrequest( + data={ + "page_url": url, + "number_of_images": number_of_images, + "context_levels": 5, + "pixel_distance_threshold": 200, + "save_images": "True", + "save_elaboration": "True", + "specific_images_urls": selected_urls, + }, + url=wcag_rest_server_url + "/wcag_alttext_validation", + headers=WCAG_VALIDATOR_RESTSERVER_HEADERS, + ) + # return response + info_dataframe = load_llm_assessment_from_json(response) + + # add the UI ids and other fields to to api response + info_dataframe.insert( + 0, "Image #", selected_image_id + ) # add the UI ids from to api response + info_dataframe.insert(2, "User Assessment", user_assessments) + + info_dataframe.insert(3, "User Proposed Alt Text", user_new_alt_texts) + info_dataframe["User Assessment for LLM Proposal"] = ( + user_assessments_llm_proposal + ) + + except Exception as e: + return {"error": str(e)} + + try: + # insert after everything to keep datetime aligned + db_persistence_insert( + connection_db=connection_db, + insert_type="wcag_user_alttext_assessments", + page_url=url, + user=json_user_str, + llm_model="", + json_in_str=json_in_str, + json_out_str=json_out_str, + table="wcag_user_assessments", + ) + except Exception as e: + print("Error inserting user assessment into database:", str(e)) + finally: + if connection_db: + connection_db.close() + return "LLM assessment completed", info_dataframe + + +def make_image_extraction_api_call( + url, + number_of_images=30, + wcag_rest_server_url="http://localhost:8000", +): + print( + f"Making API call for image_extraction for {url} to {wcag_rest_server_url}/extract_images" + ) + try: + + response = call_API_urlibrequest( + data={ + "page_url": url, + "number_of_images": number_of_images, + }, + url=wcag_rest_server_url + "/extract_images", + headers=WCAG_VALIDATOR_RESTSERVER_HEADERS, + ) + # return response + info_text, gallery_images = load_images_from_json(response) + + return info_text, gallery_images + except Exception as e: + return {"error": str(e)} + + +# ------- Gradio Interface -------# + + +# Create Gradio interface +with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo: + + env_path = find_dotenv(filename=".env") + if env_path == "": + print("env path not found: service starting with the default params values") + _ = load_dotenv(env_path) # read .env file + db_path = return_from_env_valid("DB_PATH", "persistence/wcag_validator_ui.db") + print("db_path:", db_path) + wcag_rest_server_url = return_from_env_valid( + "WCAG_REST_SERVER_URL", "http://localhost:8000" + ) + + default_urls = [ + "https://amazon.com", + "https://ebay.com", + ] + url_list_str = return_from_env_valid("URL_LIST", json.dumps(default_urls)) + url_list = json.loads(url_list_str) + + print("wcag_rest_server_url:", wcag_rest_server_url) + + connection_db = db_persistence_startup( + db_name_and_path=db_path, table="wcag_user_assessments" + ) + print("Database connection reference available:", connection_db) + connection_db.close() + + gr.Markdown("# WCAG AI Validator UI") + + # login section + user_state = gr.State({"logged_in": False, "username": None}) + with gr.Accordion(label="Register & Login", open=True) as register_and_login: + with gr.Column(visible=True) as login_section: + gr.Markdown("## Login / Register") + + with gr.Tab("Login"): + login_username = gr.Textbox( + label="Username", placeholder="Enter your username" + ) + login_password = gr.Textbox( + label="Password", type="password", placeholder="Enter your password" + ) + login_btn = gr.Button("Login", variant="primary") + login_msg = gr.Textbox(label="Login Status", interactive=False) + + with gr.Tab("Register"): + reg_username = gr.Textbox( + label="Username", placeholder="Choose a username" + ) + reg_password = gr.Textbox( + label="Password", + type="password", + placeholder="Choose a password (min 6 characters)", + ) + reg_confirm = gr.Textbox( + label="Confirm Password", + type="password", + placeholder="Confirm your password", + ) + reg_btn = gr.Button("Register", variant="primary") + reg_msg = gr.Textbox(label="Registration Status", interactive=False) + + with gr.Column(visible=False) as protected_section: + + content_display = gr.Textbox( + label="Your account", lines=5, interactive=False + ) + logout_btn = gr.Button("Logout", variant="stop") + + # end login section + + with gr.Tab("Alt Text Assessment", visible=False) as alttext_assessment: + + db_path_state = gr.State(value=db_path) # Store path in State\ + wcag_rest_server_url_state = gr.State(value=wcag_rest_server_url) + with gr.Row(): + with gr.Column(): + + with gr.Row(): + with gr.Column(): + url_input = gr.Dropdown( + url_list, + value=url_list[0], + multiselect=False, + label="Select an URL", + info="Select an URL to load in iframe", + ) + images_number = gr.Slider( + 5, + 100, + value=50, + step=5, + label="Max number of images to retrieve", + visible=False, + ) + with gr.Column(): + + image_extraction_api_call_btn = gr.Button( + "Extract Images & Alt Texts", variant="primary" + ) + alttext_api_call_btn = gr.Button( + "Start LLM Assessment", + variant="secondary", + interactive=False, + ) + image_info_output = gr.Textbox( + label="Activity tracking", lines=1 + ) + + with gr.Row(visible=False) as alttext_results_row: + + # Use DataFrame for tabular output + alttext_info_output = gr.DataFrame( + headers=[ + "Image #", + "Original Alt Text", + "User Assessment", + "User Proposed Alt Text", + "LLM Assessment", + "LLM Proposed Alt Text", + "User Assessment for LLM Proposal", + ], + label="LLM Assessment Results", + wrap=True, # Wrap text in cells + interactive=True, + scale=7, + ) + with gr.Column(): + save_user_assessment_btn = gr.Button( + "Save Your Assessment", + variant="secondary", + interactive=True, + scale=1, + ) + gr.Markdown( + "ℹ Info: to assess the LLM output, only the values ​​for the 'User Assessment for LLM Proposal' column need to be changed." + ) + + with gr.Row(): + + gallery_html = gr.HTML(label="Image Gallery") + + image_extraction_api_call_btn.click( + fn=lambda: ("", "", gr.update(visible=False), gr.Button(interactive=False)), + inputs=[], + outputs=[ + image_info_output, + gallery_html, + alttext_results_row, + alttext_api_call_btn, + ], + ).then( + make_image_extraction_api_call, + inputs=[url_input, images_number, wcag_rest_server_url_state], + outputs=[image_info_output, gallery_html], + ).then( + fn=lambda: gr.Button(interactive=True), + inputs=[], + outputs=[alttext_api_call_btn], + ) + + # Process selected images with JavaScript + + alttext_api_call_btn.click( + fn=make_alttext_llm_assessment_api_call, + inputs=[ + url_input, + gallery_html, + db_path_state, + wcag_rest_server_url_state, + user_state, + ], + outputs=[image_info_output, alttext_info_output], + js=""" + (url_input,gallery_html) => { + const checkboxes = document.querySelectorAll('.image-checkbox:checked'); + if (checkboxes.length === 0) { + alert('Please select at least one image!'); + return [url_input,JSON.stringify([])]; + } + if (checkboxes.length > 3) { + alert('Please select maximum 3 images!'); + return [url_input,JSON.stringify([])]; + } + const selectedData = []; + + checkboxes.forEach(checkbox => { + const index = checkbox.dataset.index; + const imageUrl = checkbox.dataset.imgurl; + const originalAlt = document.querySelector('.original-alt[data-index="' + index + '"]').value; + const assessment = document.querySelector('input[name="assessment-' + index + '"]:checked').value; + console.log("assessment:",assessment) + const newAltText = document.querySelector('.new-alt-text[data-index="' + index + '"]').value; + + selectedData.push({ + image_index: index, + image_url: imageUrl, + original_alt_text: originalAlt, + assessment: parseInt(assessment), + new_alt_text: newAltText + }); + }); + + return [url_input,JSON.stringify(selectedData)]; + } + """, + ).then( + fn=lambda: gr.update(visible=True), + inputs=[], + outputs=[alttext_results_row], + ) + + save_user_assessment_btn.click( + fn=process_dataframe, + inputs=[db_path_state, url_input, alttext_info_output, user_state], + outputs=[image_info_output], + ) + + # placed here at the end to give full contents visibility to events + # Event handlers + login_btn.click( + fn=login_user, + inputs=[login_username, login_password, user_state], + outputs=[ + login_msg, + reg_msg, + user_state, + login_section, + protected_section, + alttext_assessment, + register_and_login, + ], + ).then(fn=protected_content, inputs=[user_state], outputs=[content_display]) + + reg_btn.click( + fn=register_user, + inputs=[reg_username, reg_password, reg_confirm], + outputs=[login_msg, reg_msg, user_state], + ) + + logout_btn.click( + fn=logout_user, + inputs=[user_state], + outputs=[ + login_msg, + user_state, + login_section, + protected_section, + alttext_assessment, + ], + ) + + +if __name__ == "__main__": + demo.launch(server_name="0.0.0.0", server_port=7860) diff --git a/dependences/mllm_management.py b/dependences/mllm_management.py index adbd7be..64366e5 100644 --- a/dependences/mllm_management.py +++ b/dependences/mllm_management.py @@ -445,7 +445,7 @@ class MLLMManager: ### Other utility functions -def parse_mllm_alt_text_response(mllm_response): +def parse_mllm_alt_text_response(mllm_response, model_id=""): """ Parse an MLLM response string and extract key attributes into a JSON object. @@ -467,6 +467,7 @@ def parse_mllm_alt_text_response(mllm_response): "assessment": None, "evaluation_result": None, "new_alt_text": None, + "mllm_model": model_id } # Extract JSON content between ```json and ``` markers @@ -482,6 +483,7 @@ def parse_mllm_alt_text_response(mllm_response): "assessment": None, "evaluation_result": None, "new_alt_text": None, + "mllm_model": model_id } json_str = ( @@ -499,6 +501,7 @@ def parse_mllm_alt_text_response(mllm_response): "assessment": parsed_data.get("Assessment", ""), "evaluation_result": parsed_data.get("EvaluationResult", ""), "new_alt_text": parsed_data.get("New alt-text", ""), + "mllm_model": model_id } return result @@ -510,6 +513,7 @@ def parse_mllm_alt_text_response(mllm_response): "assessment": None, "evaluation_result": None, "new_alt_text": None, + "mllm_model": model_id } except Exception as e: print(f"Error parsing MLLM response: {e}") @@ -518,10 +522,11 @@ def parse_mllm_alt_text_response(mllm_response): "assessment": None, "evaluation_result": None, "new_alt_text": None, + "mllm_model": model_id } -def parse_mllm_standard_response(mllm_response, extra_fields=[]): +def parse_mllm_standard_response(mllm_response, extra_fields=[],model_id=""): try: # Handle NaN or None values @@ -530,6 +535,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]): "assessment": None, "judgment": None, "evaluation_result": None, + "mllm_model": model_id } # Extract JSON content between ```json and ``` markers # json_match = re.search(r"```json\s*(.*?)\s*```", mllm_response, re.DOTALL) @@ -560,6 +566,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]): "assessment": None, "judgment": None, "evaluation_result": None, + "mllm_model": model_id } json_str = ( @@ -580,6 +587,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]): "assessment": parsed_data.get("Assessment", ""), "judgment": parsed_data.get("Judgment", ""), "evaluation_result": parsed_data.get("EvaluationResult", ""), + "mllm_model": model_id } if extra_fields: for field in extra_fields: @@ -590,6 +598,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]): "assessment": None, "judgment": None, "evaluation_result": None, + "mllm_model": model_id } elif isinstance( parsed_data, list @@ -601,6 +610,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]): "assessment": item.get("Assessment", ""), "judgment": item.get("Judgment", ""), "evaluation_result": item.get("EvaluationResult", ""), + "mllm_model": model_id } if extra_fields: for field in extra_fields: @@ -611,6 +621,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]): "assessment": None, "judgment": None, "evaluation_result": None, + "mllm_model": model_id } result.append(item_result) return result @@ -621,6 +632,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]): "assessment": None, "judgment": None, "evaluation_result": None, + "mllm_model": model_id } except Exception as e: print(f"Error parsing MLLM response: {e}") @@ -628,4 +640,5 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]): "assessment": None, "judgment": None, "evaluation_result": None, + "mllm_model": model_id } diff --git a/restserver/routers/routes_wcag_alttext.py b/restserver/routers/routes_wcag_alttext.py index ba76d87..7cf28da 100644 --- a/restserver/routers/routes_wcag_alttext.py +++ b/restserver/routers/routes_wcag_alttext.py @@ -103,21 +103,29 @@ class WCAGAltTextValuationRoutes: extract_context=True, ) - if self.mllm_settings["openai_model"] == "Both": - + from concurrent.futures import ThreadPoolExecutor - def run_model_evaluation(endpoint, api_key, model_id, openai_model, label): + def run_model_evaluation( + endpoint, api_key, model_id, openai_model, label + ): manager = MLLMManager(endpoint, api_key, model_id) - print(f"Using {label} model for alt text evaluation.", manager.end_point) + print( + f"Using {label} model for alt text evaluation.", + manager.end_point, + ) logging.info("mllm_end_point:%s", endpoint) logging.info("mllm_model_id:%s", model_id) - responses = manager.make_alt_text_evaluation(images, openai_model=openai_model) + responses = manager.make_alt_text_evaluation( + images, openai_model=openai_model + ) for i, response in enumerate(responses): - responses[i]["mllm_response"] = parse_mllm_alt_text_response(response["mllm_response"]) + responses[i]["mllm_response"] = parse_mllm_alt_text_response( + response["mllm_response"], model_id=model_id + ) return responses @@ -127,25 +135,27 @@ class WCAGAltTextValuationRoutes: self.mllm_settings["mllm_end_point"]["model_end_point_remote"], self.mllm_settings["mllm_api_key"]["api_key_remote"], self.mllm_settings["mllm_model_id"]["model_id_remote"], - True, "first remote" + True, + "first remote", ) future_local = executor.submit( run_model_evaluation, self.mllm_settings["mllm_end_point"]["model_end_point_local"], self.mllm_settings["mllm_api_key"]["api_key_local"], self.mllm_settings["mllm_model_id"]["model_id_local"], - False, "second local" + False, + "second local", ) mllm_responses_openai = future_openai.result() - mllm_responses_local = future_local.result() + mllm_responses_local = future_local.result() mllm_responses_object = { "mllm_alttext_assessments": { "mllm_alttext_assessments_openai": mllm_responses_openai, "mllm_alttext_assessments_local": mllm_responses_local, } - } + } else: # MLLM settings @@ -170,7 +180,7 @@ class WCAGAltTextValuationRoutes: # Parse MLLM responses for i, response in enumerate(mllm_responses): parsed_resp = parse_mllm_alt_text_response( - response["mllm_response"] + response["mllm_response"], model_id=mllm_model_id ) mllm_responses[i]["mllm_response"] = parsed_resp diff --git a/restserver/routers/routes_wcag_g88.py b/restserver/routers/routes_wcag_g88.py index 32fa89a..abae0ec 100644 --- a/restserver/routers/routes_wcag_g88.py +++ b/restserver/routers/routes_wcag_g88.py @@ -103,8 +103,9 @@ class WCAG_g88ValuationRoutes: ) parsed_mllm_responses = parse_mllm_standard_response( - responses["mllm_response"] + responses["mllm_response"], model_id=model_id ) + return parsed_mllm_responses with ThreadPoolExecutor(max_workers=2) as executor: @@ -156,8 +157,9 @@ class WCAG_g88ValuationRoutes: openai_model=self.mllm_settings["openai_model"], ) parsed_mllm_responses = parse_mllm_standard_response( - mllm_responses["mllm_response"] + mllm_responses["mllm_response"], model_id=mllm_model_id ) + mllm_responses_object = {"mllm_g88_assessments": parsed_mllm_responses} # common: prepare the object to return in the response diff --git a/restserver/routers/routes_wcag_h58.py b/restserver/routers/routes_wcag_h58.py index ca7a537..4c69605 100644 --- a/restserver/routers/routes_wcag_h58.py +++ b/restserver/routers/routes_wcag_h58.py @@ -120,9 +120,9 @@ class WCAG_h58ValuationRoutes: "html", "detected_lang", "declared_lang", - ], + ],model_id=model_id ) - + mllm_respones_flattened.extend(parsed_resp) return mllm_respones_flattened @@ -186,7 +186,9 @@ class WCAG_h58ValuationRoutes: parsed_resp = parse_mllm_standard_response( response["mllm_response"], extra_fields=["tag", "html", "detected_lang", "declared_lang"], + model_id=mllm_model_id ) + mllm_respones_flattened.extend(parsed_resp) diff --git a/scripts/esercitazione_12_2025/analisi_esercitazione_12_2025_ricostruzione_associazioni_nuova_strutturaDB.ipynb b/scripts/esercitazione_12_2025/analisi_esercitazione_12_2025_ricostruzione_associazioni_nuova_strutturaDB.ipynb new file mode 100644 index 0000000..06ddb0d --- /dev/null +++ b/scripts/esercitazione_12_2025/analisi_esercitazione_12_2025_ricostruzione_associazioni_nuova_strutturaDB.ipynb @@ -0,0 +1,907 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 41, + "id": "cc9085ed", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import json" + ] + }, + { + "cell_type": "markdown", + "id": "8d7e45b6", + "metadata": {}, + "source": [ + "# Scopo recuperare i contesti dlle varie immagini per rifare altri scenari" + ] + }, + { + "cell_type": "markdown", + "id": "04e0cf29", + "metadata": {}, + "source": [ + "### partendo dal dump del DB ui fatto da utente (row tipo \"wcag_user_llm_alttext_assessments\")" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "4a5c2ffe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idinsertion_timeinsert_typepage_urluserllm_modeljson_input_datajson_output_data
012026-03-03 09:51:39wcag_user_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/decat...{\"username\": \"nicola\"}NaN{\"images_urls\": [\"https://giove.isti.cnr.it/us...{\"user_assessments\": [3, 3], \"user_new_alt_tex...
122026-03-03 09:51:55wcag_user_llm_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/decat...{\"username\": \"nicola\"}NaN{\"images\": [{\"url\": \"https://giove.isti.cnr.it...[{\"Image #\":21,\"Original Alt Text\":\"{*slate-bl...
232026-03-03 09:53:55wcag_user_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/nike....{\"username\": \"nicola\"}NaN{\"images_urls\": [\"https://giove.isti.cnr.it/us...{\"user_assessments\": [3, 3, 3], \"user_new_alt_...
342026-03-03 09:54:06wcag_user_llm_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/nike....{\"username\": \"nicola\"}NaN{\"images\": [{\"url\": \"https://giove.isti.cnr.it...[{\"Image #\":7,\"Original Alt Text\":\"Nike Club Z...
\n", + "
" + ], + "text/plain": [ + " id insertion_time insert_type \\\n", + "0 1 2026-03-03 09:51:39 wcag_user_alttext_assessments \n", + "1 2 2026-03-03 09:51:55 wcag_user_llm_alttext_assessments \n", + "2 3 2026-03-03 09:53:55 wcag_user_alttext_assessments \n", + "3 4 2026-03-03 09:54:06 wcag_user_llm_alttext_assessments \n", + "\n", + " page_url user \\\n", + "0 https://giove.isti.cnr.it/users/leonardi/decat... {\"username\": \"nicola\"} \n", + "1 https://giove.isti.cnr.it/users/leonardi/decat... {\"username\": \"nicola\"} \n", + "2 https://giove.isti.cnr.it/users/leonardi/nike.... {\"username\": \"nicola\"} \n", + "3 https://giove.isti.cnr.it/users/leonardi/nike.... {\"username\": \"nicola\"} \n", + "\n", + " llm_model json_input_data \\\n", + "0 NaN {\"images_urls\": [\"https://giove.isti.cnr.it/us... \n", + "1 NaN {\"images\": [{\"url\": \"https://giove.isti.cnr.it... \n", + "2 NaN {\"images_urls\": [\"https://giove.isti.cnr.it/us... \n", + "3 NaN {\"images\": [{\"url\": \"https://giove.isti.cnr.it... \n", + "\n", + " json_output_data \n", + "0 {\"user_assessments\": [3, 3], \"user_new_alt_tex... \n", + "1 [{\"Image #\":21,\"Original Alt Text\":\"{*slate-bl... \n", + "2 {\"user_assessments\": [3, 3, 3], \"user_new_alt_... \n", + "3 [{\"Image #\":7,\"Original Alt Text\":\"Nike Club Z... " + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ui_dataframe = pd.read_csv(\"C:\\\\cartella_condivisa\\\\MachineLearning\\\\HIISlab\\\\accessibility\\\\notebook_miei\\\\LLM_accessibility_validator\\\\UI\\\\persistence\\\\wcag_validator_ui.csv\")\n", + "ui_dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "98ae727c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idinsertion_timeinsert_typepage_urluserllm_modeljson_input_datajson_output_data
122026-03-03 09:51:55wcag_user_llm_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/decat...{\"username\": \"nicola\"}NaN{\"images\": [{\"url\": \"https://giove.isti.cnr.it...[{\"Image #\":21,\"Original Alt Text\":\"{*slate-bl...
342026-03-03 09:54:06wcag_user_llm_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/nike....{\"username\": \"nicola\"}NaN{\"images\": [{\"url\": \"https://giove.isti.cnr.it...[{\"Image #\":7,\"Original Alt Text\":\"Nike Club Z...
\n", + "
" + ], + "text/plain": [ + " id insertion_time insert_type \\\n", + "1 2 2026-03-03 09:51:55 wcag_user_llm_alttext_assessments \n", + "3 4 2026-03-03 09:54:06 wcag_user_llm_alttext_assessments \n", + "\n", + " page_url user \\\n", + "1 https://giove.isti.cnr.it/users/leonardi/decat... {\"username\": \"nicola\"} \n", + "3 https://giove.isti.cnr.it/users/leonardi/nike.... {\"username\": \"nicola\"} \n", + "\n", + " llm_model json_input_data \\\n", + "1 NaN {\"images\": [{\"url\": \"https://giove.isti.cnr.it... \n", + "3 NaN {\"images\": [{\"url\": \"https://giove.isti.cnr.it... \n", + "\n", + " json_output_data \n", + "1 [{\"Image #\":21,\"Original Alt Text\":\"{*slate-bl... \n", + "3 [{\"Image #\":7,\"Original Alt Text\":\"Nike Club Z... " + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ui_dataframe[ui_dataframe[\"insert_type\"]==\"wcag_user_llm_alttext_assessments\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "e6c60522", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idinsertion_timeinsert_typepage_urluserllm_modeljson_input_datajson_output_data
122026-03-03 09:51:55wcag_user_llm_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/decat...{\"username\": \"nicola\"}NaN{\"images\": [{\"url\": \"https://giove.isti.cnr.it...[{\"Image #\":21,\"Original Alt Text\":\"{*slate-bl...
342026-03-03 09:54:06wcag_user_llm_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/nike....{\"username\": \"nicola\"}NaN{\"images\": [{\"url\": \"https://giove.isti.cnr.it...[{\"Image #\":7,\"Original Alt Text\":\"Nike Club Z...
\n", + "
" + ], + "text/plain": [ + " id insertion_time insert_type \\\n", + "1 2 2026-03-03 09:51:55 wcag_user_llm_alttext_assessments \n", + "3 4 2026-03-03 09:54:06 wcag_user_llm_alttext_assessments \n", + "\n", + " page_url user \\\n", + "1 https://giove.isti.cnr.it/users/leonardi/decat... {\"username\": \"nicola\"} \n", + "3 https://giove.isti.cnr.it/users/leonardi/nike.... {\"username\": \"nicola\"} \n", + "\n", + " llm_model json_input_data \\\n", + "1 NaN {\"images\": [{\"url\": \"https://giove.isti.cnr.it... \n", + "3 NaN {\"images\": [{\"url\": \"https://giove.isti.cnr.it... \n", + "\n", + " json_output_data \n", + "1 [{\"Image #\":21,\"Original Alt Text\":\"{*slate-bl... \n", + "3 [{\"Image #\":7,\"Original Alt Text\":\"Nike Club Z... " + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "df=ui_dataframe[ui_dataframe[\"insert_type\"]==\"wcag_user_llm_alttext_assessments\"]\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "e9d2ccb4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idinsertion_timeinsert_typepage_urluserllm_modeljson_input_datajson_output_data
\n", + "
" + ], + "text/plain": [ + "Empty DataFrame\n", + "Columns: [id, insertion_time, insert_type, page_url, user, llm_model, json_input_data, json_output_data]\n", + "Index: []" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#verifico match dei numeri con altro notebook\n", + "df[~df['user'].apply(lambda x: json.loads(x).get('username') in ['nicola', 'fabio', 'marco.manca',None])]" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "b5f421ac", + "metadata": {}, + "outputs": [], + "source": [ + "def expand_json_data_backend(df,row_name=\"json_input_data\",row_name2=\"json_output_data\",is_single_model_output = True):\n", + " \"\"\"\n", + " Expands DataFrame by parsing json_input_data and creating structured columns.\n", + " Each row gets expanded with the JSON attributes.\n", + " \"\"\"\n", + " expanded_rows = []\n", + " \n", + " for _, row in df.iterrows():\n", + " # Parse the json_input_data\n", + " \n", + " json_data = json.loads(row[row_name])\n", + " if is_single_model_output:\n", + " # la struttura in out replica le colonne del notebook originale dopo vari merge\n", + " print(\"is_single_model_output is True, so we consider only one model output per row\")\n", + " mllm_validation=json_data[\"mllm_validations\"][\"mllm_alttext_assessments\"][\"mllm_alttext_assessments_openai\"]\n", + " input_data=json_data[\"images\"]\n", + " json_out_data = json.loads(row[row_name2])\n", + " \n", + " print(\"input:\",json_data)\n", + " print(\"output:\",json_out_data)\n", + " # Extract fields from JSON\n", + " count_ele=0\n", + " for ele in input_data:\n", + " #print(\"ele:\", ele)\n", + "\n", + " json_validation_ele=mllm_validation[count_ele][\"mllm_response\"]\n", + " user_assessment_ele=json_out_data[count_ele]\n", + " \n", + " # Create new row with all data FOR EACH ELEMENT\n", + " new_row = {\n", + " 'id': row['id'],\n", + " 'insertion_time': row['insertion_time'],\n", + " 'insert_type': row['insert_type'],\n", + " 'page_url': row['page_url'],\n", + " 'user': row['user'],\n", + " #'llm_model': row['llm_model'],\n", + " 'image_url': ele.get('url'),\n", + " 'original_alt_text': ele.get('alt_text'),\n", + " 'html_context': ele.get('html_context'),\n", + " 'immediate_context': ele.get('immediate_context'),\n", + " 'nearby_context':ele.get('nearby_text'),\n", + " \n", + " 'page_title': ele.get('page_title'),\n", + " 'page_description': ele.get('page_description'),\n", + " 'page_keywords': ele.get('page_keywords'),\n", + " 'page_headings': json.dumps(ele.get('page_headings', [])), # Keep as JSON string\n", + "\n", + " \"llm_assessment\":json_validation_ele[\"original_alt_text_assessment\"],\n", + " \"llm_judgment\":json_validation_ele[\"assessment\"],\n", + " \"llm_evaluation_result\":json_validation_ele[\"evaluation_result\"],\n", + " \"llm_alt_text\":json_validation_ele[\"new_alt_text\"],\n", + " \"llm_model\": json_validation_ele['mllm_model'],\n", + "\n", + " \"user_assessment\": user_assessment_ele[\"User Assessment\"],\n", + " \"user_alt_text\": user_assessment_ele[\"User Proposed Alt Text\"],\n", + " 'user_llm_assessment': user_assessment_ele['User Assessment for LLM Proposal 1'],\n", + "\n", + " }\n", + " \n", + " expanded_rows.append(new_row)\n", + " count_ele=count_ele+1\n", + " else:\n", + " print(\"is_single_model_output is False, so we consider multiple model outputs per row\")\n", + " mllm_validation_1=json_data[\"mllm_validations\"][\"mllm_alttext_assessments\"][\"mllm_alttext_assessments_openai\"]\n", + " mllm_validation_2=json_data[\"mllm_validations\"][\"mllm_alttext_assessments\"][\"mllm_alttext_assessments_local\"]\n", + " input_data=json_data[\"images\"]\n", + " json_out_data = json.loads(row[row_name2])\n", + " \n", + " print(\"input:\",json_data)\n", + " print(\"output:\",json_out_data)\n", + " # Extract fields from JSON\n", + " count_ele=0\n", + " for ele in input_data:\n", + " #print(\"ele:\", ele)\n", + "\n", + " json_validation_ele_1=mllm_validation_1[count_ele][\"mllm_response\"]\n", + " json_validation_ele_2=mllm_validation_2[count_ele][\"mllm_response\"]\n", + " user_assessment_ele=json_out_data[count_ele]\n", + " \n", + " # Create new row with all data FOR EACH ELEMENT\n", + " new_row = {\n", + " 'id': row['id'],\n", + " 'insertion_time': row['insertion_time'],\n", + " 'insert_type': row['insert_type'],\n", + " 'page_url': row['page_url'],\n", + " 'user': row['user'],\n", + " #'llm_model': row['llm_model'],\n", + " 'image_url': ele.get('url'),\n", + " 'original_alt_text': ele.get('alt_text'),\n", + " 'html_context': ele.get('html_context'),\n", + " 'immediate_context': ele.get('immediate_context'),\n", + " 'nearby_context':ele.get('nearby_text'),\n", + " \n", + " 'page_title': ele.get('page_title'),\n", + " 'page_description': ele.get('page_description'),\n", + " 'page_keywords': ele.get('page_keywords'),\n", + " 'page_headings': json.dumps(ele.get('page_headings', [])), # Keep as JSON string\n", + "\n", + " \"llm_assessment_1\":json_validation_ele_1[\"original_alt_text_assessment\"],\n", + " \"llm_judgment_1\":json_validation_ele_1[\"assessment\"],\n", + " \"llm_evaluation_result_1\":json_validation_ele_1[\"evaluation_result\"],\n", + " \"llm_alt_text_1\":json_validation_ele_1[\"new_alt_text\"],\n", + " \"llm_model_1\": json_validation_ele_1['mllm_model'],\n", + "\n", + " \"llm_assessment_2\":json_validation_ele_2[\"original_alt_text_assessment\"],\n", + " \"llm_judgment_2\":json_validation_ele_2[\"assessment\"],\n", + " \"llm_evaluation_result_2\":json_validation_ele_2[\"evaluation_result\"],\n", + " \"llm_alt_text_2\":json_validation_ele_2[\"new_alt_text\"],\n", + " \"llm_model_2\": json_validation_ele_2['mllm_model'],\n", + "\n", + " \"user_assessment\": user_assessment_ele[\"User Assessment\"],\n", + " \"user_alt_text\": user_assessment_ele[\"User Proposed Alt Text\"],\n", + " 'user_llm_assessment_1': user_assessment_ele['User Assessment for LLM Proposal 1'],\n", + " 'user_llm_assessment_2': user_assessment_ele['User Assessment for LLM Proposal 2'],\n", + "\n", + " }\n", + " \n", + " expanded_rows.append(new_row)\n", + " count_ele=count_ele+1\n", + " \n", + " \n", + " # Create new DataFrame from expanded rows\n", + " expanded_df = pd.DataFrame(expanded_rows)\n", + " return expanded_df" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "6df36f4b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "is_single_model_output is True, so we consider only one model output per row\n", + "input: {'images': [{'url': 'https://giove.isti.cnr.it/users/leonardi/decathlon/8493310-product_image-p2865897_526e43f2-1fc4-4646-83ae-d71af4b654a2.jpg', 'alt_text': '{*slate-blue-8493310*}', 'html_context': \": Save 22% : Vendor:

: Simond Men's Travel 500 Merino Wool T-Shirt\", 'immediate_context': 'No immediate context found', 'nearby_text': ' [154px]: Save 22%', 'page_url': 'https://giove.isti.cnr.it/users/leonardi/decathlon.html', 'page_title': \"Men's Outdoor Apparel – Decathlon\", 'page_description': 'Shop our selection of outdoor clothes and gear for men designed by Decathlon. Enjoy free U.S. standard shipping on orders over $49! Free returns up to 90 days!', 'page_keywords': None, 'page_headings': [{'level': 2, 'text': 'Your cart is empty'}, {'level': 2, 'text': 'Your cart'}, {'level': 2, 'text': 'Estimated total'}, {'level': 1, 'text': \"Collection: Men's\"}, {'level': 2, 'text': 'Sort by:'}, {'level': 2, 'text': '111 products'}, {'level': 2, 'text': 'Filter:'}, {'level': 2, 'text': 'Filter and sort'}, {'level': 2, 'text': 'Filter'}, {'level': 2, 'text': '111 products'}, {'level': 3, 'text': \"Simond Men's MT100 Hooded Down Puffer Jacket\"}, {'level': 3, 'text': \"Simond Men's MT100 Hooded Down Puffer Jacket\"}, {'level': 3, 'text': \"Forclaz Men's MT100 Hooded Synthetic Jacket\"}, {'level': 3, 'text': \"Forclaz Men's MT100 Hooded Synthetic Jacket\"}, {'level': 3, 'text': 'Simond Adult MT500 Merino Wool Beanie'}, {'level': 3, 'text': 'Simond Adult MT500 Merino Wool Beanie'}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Fleece\"}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Fleece\"}, {'level': 3, 'text': \"Simond Men's Travel 500 Merino Wool T-Shirt\"}, {'level': 3, 'text': \"Simond Men's Travel 500 Merino Wool T-Shirt\"}, {'level': 3, 'text': \"Simond Men's MT100 Easyfit 70L Backpacking Pack\"}, {'level': 3, 'text': \"Simond Men's MT100 Easyfit 70L Backpacking Pack\"}, {'level': 3, 'text': \"Quechua Men's MH500 Waterproof Hiking Shoes\"}, {'level': 3, 'text': \"Quechua Men's MH500 Waterproof Hiking Shoes\"}, {'level': 3, 'text': \"Simond Men's MT500 Down Puffer Jacket\"}, {'level': 3, 'text': \"Simond Men's MT500 Down Puffer Jacket\"}, {'level': 3, 'text': \"Simond Men's MT100 Easyfit 50 L Backpacking Pack\"}, {'level': 3, 'text': \"Simond Men's MT100 Easyfit 50 L Backpacking Pack\"}, {'level': 3, 'text': \"Simond Forclaz Men's MT500 Hooded Down Puffer Jacket\"}, {'level': 3, 'text': \"Simond Forclaz Men's MT500 Hooded Down Puffer Jacket\"}, {'level': 3, 'text': \"Forclaz Men's MT100 2-in-1 Zip-Off Hiking Pants\"}, {'level': 3, 'text': \"Forclaz Men's MT100 2-in-1 Zip-Off Hiking Pants\"}, {'level': 3, 'text': \"Van Rysel Men's Ultralight Mesh Base Layer\"}, {'level': 3, 'text': \"Van Rysel Men's Ultralight Mesh Base Layer\"}, {'level': 3, 'text': \"Simond Men's MT500 Merino Wool Boxer Briefs\"}, {'level': 3, 'text': \"Simond Men's MT500 Merino Wool Boxer Briefs\"}, {'level': 3, 'text': 'Forclaz MT500 Lightweight Packable Hiking Sandals'}, {'level': 3, 'text': 'Forclaz MT500 Lightweight Packable Hiking Sandals'}, {'level': 3, 'text': \"Quechua Men's MH100 Waterproof Mid Hiking Boots\"}, {'level': 3, 'text': \"Quechua Men's MH100 Waterproof Mid Hiking Boots\"}, {'level': 3, 'text': \"Forclaz Men's Travel 100 Cargo Pants\"}, {'level': 3, 'text': \"Forclaz Men's Travel 100 Cargo Pants\"}, {'level': 3, 'text': \"Quechua Men's MH120 Fleece Hiking Jacket\"}, {'level': 3, 'text': \"Quechua Men's MH120 Fleece Hiking Jacket\"}, {'level': 3, 'text': \"Van Rysel Men's Ultralight Waterproof Cycling Jacket\"}, {'level': 3, 'text': \"Van Rysel Men's Ultralight Waterproof Cycling Jacket\"}, {'level': 3, 'text': 'Forclaz Trek 900 Waterproof Backpacking Hat'}, {'level': 3, 'text': 'Forclaz Trek 900 Waterproof Backpacking Hat'}, {'level': 3, 'text': \"Simond Men's MT500 Air 50+10 L Backpacking Backpack\"}, {'level': 3, 'text': \"Simond Men's MT500 Air 50+10 L Backpacking Backpack\"}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Shoes\"}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Shoes\"}, {'level': 3, 'text': \"Quechua Men's MH500 Hiking Lightweight Waterproof Jacket\"}, {'level': 3, 'text': \"Quechua Men's MH500 Hiking Lightweight Waterproof Jacket\"}, {'level': 3, 'text': \"Forclaz Men's Travel 100 Cargo Shorts\"}, {'level': 3, 'text': \"Forclaz Men's Travel 100 Cargo Shorts\"}, {'level': 3, 'text': \"Forclaz Men's MT500 Merino Wool Base Layer Tights\"}, {'level': 3, 'text': \"Forclaz Men's MT500 Merino Wool Base Layer Tights\"}, {'level': 3, 'text': 'Forclaz MT500 UPF 50+ Hiking Hat'}, {'level': 3, 'text': 'Forclaz MT500 UPF 50+ Hiking Hat'}, {'level': 3, 'text': \"Kiprun Men's Run 100 Dry Running T-Shirt\"}, {'level': 3, 'text': \"Kiprun Men's Run 100 Dry Running T-Shirt\"}, {'level': 3, 'text': \"Quechua Men's MH120 Hiking Fleece\"}, {'level': 3, 'text': \"Quechua Men's MH120 Hiking Fleece\"}, {'level': 3, 'text': \"Forclaz Men's Travel 500 3-in-1 Waterproof Jacket\"}, {'level': 3, 'text': \"Forclaz Men's Travel 500 3-in-1 Waterproof Jacket\"}, {'level': 3, 'text': \"Quechua Men's NH500 Imper Waterproof Over Pants\"}, {'level': 3, 'text': \"Quechua Men's NH500 Imper Waterproof Over Pants\"}, {'level': 3, 'text': \"Wedze Men's BL100 Ski Base Layer Bottoms\"}, {'level': 3, 'text': \"Wedze Men's BL100 Ski Base Layer Bottoms\"}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Pants\"}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Pants\"}, {'level': 3, 'text': \"Quechua Men's NH500 Imper Waterproof Rain Jacket\"}, {'level': 3, 'text': \"Quechua Men's NH500 Imper Waterproof Rain Jacket\"}, {'level': 3, 'text': \"Wedze Men's BL 500 Thermal Ski Base Layer\"}, {'level': 3, 'text': \"Wedze Men's BL 500 Thermal Ski Base Layer\"}, {'level': 3, 'text': \"Wedze Men's BL500 Ski Base Layer Bottoms\"}, {'level': 3, 'text': \"Wedze Men's BL500 Ski Base Layer Bottoms\"}, {'level': 3, 'text': 'Wedze Firstheat Fleece Ski Beanie'}, {'level': 3, 'text': 'Wedze Firstheat Fleece Ski Beanie'}, {'level': 3, 'text': \"Quechua Men's Hiking Synthetic Short-Sleeved T-Shirt MH100\"}, {'level': 3, 'text': \"Quechua Men's Hiking Synthetic Short-Sleeved T-Shirt MH100\"}, {'level': 2, 'text': 'Collapsible content'}, {'level': 3, 'text': \"Learn More About Men's\"}, {'level': 2, 'text': \"About Our Men's Collection\"}, {'level': 3, 'text': 'Tops'}, {'level': 3, 'text': 'Pants'}, {'level': 3, 'text': 'Shoes'}, {'level': 3, 'text': 'Nabaiji'}, {'level': 3, 'text': 'Accessories'}, {'level': 2, 'text': 'The Membership Program'}, {'level': 3, 'text': 'BENEFITS INCLUDE:'}, {'level': 2, 'text': 'Subscribe to our emails'}, {'level': 3, 'text': 'SERVICES'}, {'level': 3, 'text': 'HELP'}, {'level': 3, 'text': 'ABOUT'}, {'level': 1, 'text': 'UNLOCK 10% OFF'}, {'level': 2, 'text': 'Your Cart'}, {'level': 4, 'text': 'Your cart is empty!'}, {'level': 4, 'text': 'Get started with our top sellers'}, {'level': 5, 'text': \"Simond Men's MT100 Hooded Down Puffer Jacket\"}, {'level': 5, 'text': \"Simond Forclaz Men's MT500 Hooded Down Puffer Jacket\"}, {'level': 5, 'text': \"Simond Women's MT100 Hooded Down Puffer Jacket\"}]}, {'url': 'https://giove.isti.cnr.it/users/leonardi/decathlon/FORCLAZ_SAD_H_MT_100_EASYFIT_70L_OCRE_-_8559696_-_001_---_Expires_on_04-06-2035.jpg', 'alt_text': 'Red and gray backpack on a white background', 'html_context': \": Save 30% : Vendor:

: Simond Men's MT100 Easyfit 70L Backpacking Pack\", 'immediate_context': 'No immediate context found', 'nearby_text': ' [154px]: Save 30%', 'page_url': 'https://giove.isti.cnr.it/users/leonardi/decathlon.html', 'page_title': \"Men's Outdoor Apparel – Decathlon\", 'page_description': 'Shop our selection of outdoor clothes and gear for men designed by Decathlon. Enjoy free U.S. standard shipping on orders over $49! Free returns up to 90 days!', 'page_keywords': None, 'page_headings': [{'level': 2, 'text': 'Your cart is empty'}, {'level': 2, 'text': 'Your cart'}, {'level': 2, 'text': 'Estimated total'}, {'level': 1, 'text': \"Collection: Men's\"}, {'level': 2, 'text': 'Sort by:'}, {'level': 2, 'text': '111 products'}, {'level': 2, 'text': 'Filter:'}, {'level': 2, 'text': 'Filter and sort'}, {'level': 2, 'text': 'Filter'}, {'level': 2, 'text': '111 products'}, {'level': 3, 'text': \"Simond Men's MT100 Hooded Down Puffer Jacket\"}, {'level': 3, 'text': \"Simond Men's MT100 Hooded Down Puffer Jacket\"}, {'level': 3, 'text': \"Forclaz Men's MT100 Hooded Synthetic Jacket\"}, {'level': 3, 'text': \"Forclaz Men's MT100 Hooded Synthetic Jacket\"}, {'level': 3, 'text': 'Simond Adult MT500 Merino Wool Beanie'}, {'level': 3, 'text': 'Simond Adult MT500 Merino Wool Beanie'}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Fleece\"}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Fleece\"}, {'level': 3, 'text': \"Simond Men's Travel 500 Merino Wool T-Shirt\"}, {'level': 3, 'text': \"Simond Men's Travel 500 Merino Wool T-Shirt\"}, {'level': 3, 'text': \"Simond Men's MT100 Easyfit 70L Backpacking Pack\"}, {'level': 3, 'text': \"Simond Men's MT100 Easyfit 70L Backpacking Pack\"}, {'level': 3, 'text': \"Quechua Men's MH500 Waterproof Hiking Shoes\"}, {'level': 3, 'text': \"Quechua Men's MH500 Waterproof Hiking Shoes\"}, {'level': 3, 'text': \"Simond Men's MT500 Down Puffer Jacket\"}, {'level': 3, 'text': \"Simond Men's MT500 Down Puffer Jacket\"}, {'level': 3, 'text': \"Simond Men's MT100 Easyfit 50 L Backpacking Pack\"}, {'level': 3, 'text': \"Simond Men's MT100 Easyfit 50 L Backpacking Pack\"}, {'level': 3, 'text': \"Simond Forclaz Men's MT500 Hooded Down Puffer Jacket\"}, {'level': 3, 'text': \"Simond Forclaz Men's MT500 Hooded Down Puffer Jacket\"}, {'level': 3, 'text': \"Forclaz Men's MT100 2-in-1 Zip-Off Hiking Pants\"}, {'level': 3, 'text': \"Forclaz Men's MT100 2-in-1 Zip-Off Hiking Pants\"}, {'level': 3, 'text': \"Van Rysel Men's Ultralight Mesh Base Layer\"}, {'level': 3, 'text': \"Van Rysel Men's Ultralight Mesh Base Layer\"}, {'level': 3, 'text': \"Simond Men's MT500 Merino Wool Boxer Briefs\"}, {'level': 3, 'text': \"Simond Men's MT500 Merino Wool Boxer Briefs\"}, {'level': 3, 'text': 'Forclaz MT500 Lightweight Packable Hiking Sandals'}, {'level': 3, 'text': 'Forclaz MT500 Lightweight Packable Hiking Sandals'}, {'level': 3, 'text': \"Quechua Men's MH100 Waterproof Mid Hiking Boots\"}, {'level': 3, 'text': \"Quechua Men's MH100 Waterproof Mid Hiking Boots\"}, {'level': 3, 'text': \"Forclaz Men's Travel 100 Cargo Pants\"}, {'level': 3, 'text': \"Forclaz Men's Travel 100 Cargo Pants\"}, {'level': 3, 'text': \"Quechua Men's MH120 Fleece Hiking Jacket\"}, {'level': 3, 'text': \"Quechua Men's MH120 Fleece Hiking Jacket\"}, {'level': 3, 'text': \"Van Rysel Men's Ultralight Waterproof Cycling Jacket\"}, {'level': 3, 'text': \"Van Rysel Men's Ultralight Waterproof Cycling Jacket\"}, {'level': 3, 'text': 'Forclaz Trek 900 Waterproof Backpacking Hat'}, {'level': 3, 'text': 'Forclaz Trek 900 Waterproof Backpacking Hat'}, {'level': 3, 'text': \"Simond Men's MT500 Air 50+10 L Backpacking Backpack\"}, {'level': 3, 'text': \"Simond Men's MT500 Air 50+10 L Backpacking Backpack\"}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Shoes\"}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Shoes\"}, {'level': 3, 'text': \"Quechua Men's MH500 Hiking Lightweight Waterproof Jacket\"}, {'level': 3, 'text': \"Quechua Men's MH500 Hiking Lightweight Waterproof Jacket\"}, {'level': 3, 'text': \"Forclaz Men's Travel 100 Cargo Shorts\"}, {'level': 3, 'text': \"Forclaz Men's Travel 100 Cargo Shorts\"}, {'level': 3, 'text': \"Forclaz Men's MT500 Merino Wool Base Layer Tights\"}, {'level': 3, 'text': \"Forclaz Men's MT500 Merino Wool Base Layer Tights\"}, {'level': 3, 'text': 'Forclaz MT500 UPF 50+ Hiking Hat'}, {'level': 3, 'text': 'Forclaz MT500 UPF 50+ Hiking Hat'}, {'level': 3, 'text': \"Kiprun Men's Run 100 Dry Running T-Shirt\"}, {'level': 3, 'text': \"Kiprun Men's Run 100 Dry Running T-Shirt\"}, {'level': 3, 'text': \"Quechua Men's MH120 Hiking Fleece\"}, {'level': 3, 'text': \"Quechua Men's MH120 Hiking Fleece\"}, {'level': 3, 'text': \"Forclaz Men's Travel 500 3-in-1 Waterproof Jacket\"}, {'level': 3, 'text': \"Forclaz Men's Travel 500 3-in-1 Waterproof Jacket\"}, {'level': 3, 'text': \"Quechua Men's NH500 Imper Waterproof Over Pants\"}, {'level': 3, 'text': \"Quechua Men's NH500 Imper Waterproof Over Pants\"}, {'level': 3, 'text': \"Wedze Men's BL100 Ski Base Layer Bottoms\"}, {'level': 3, 'text': \"Wedze Men's BL100 Ski Base Layer Bottoms\"}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Pants\"}, {'level': 3, 'text': \"Quechua Men's MH100 Hiking Pants\"}, {'level': 3, 'text': \"Quechua Men's NH500 Imper Waterproof Rain Jacket\"}, {'level': 3, 'text': \"Quechua Men's NH500 Imper Waterproof Rain Jacket\"}, {'level': 3, 'text': \"Wedze Men's BL 500 Thermal Ski Base Layer\"}, {'level': 3, 'text': \"Wedze Men's BL 500 Thermal Ski Base Layer\"}, {'level': 3, 'text': \"Wedze Men's BL500 Ski Base Layer Bottoms\"}, {'level': 3, 'text': \"Wedze Men's BL500 Ski Base Layer Bottoms\"}, {'level': 3, 'text': 'Wedze Firstheat Fleece Ski Beanie'}, {'level': 3, 'text': 'Wedze Firstheat Fleece Ski Beanie'}, {'level': 3, 'text': \"Quechua Men's Hiking Synthetic Short-Sleeved T-Shirt MH100\"}, {'level': 3, 'text': \"Quechua Men's Hiking Synthetic Short-Sleeved T-Shirt MH100\"}, {'level': 2, 'text': 'Collapsible content'}, {'level': 3, 'text': \"Learn More About Men's\"}, {'level': 2, 'text': \"About Our Men's Collection\"}, {'level': 3, 'text': 'Tops'}, {'level': 3, 'text': 'Pants'}, {'level': 3, 'text': 'Shoes'}, {'level': 3, 'text': 'Nabaiji'}, {'level': 3, 'text': 'Accessories'}, {'level': 2, 'text': 'The Membership Program'}, {'level': 3, 'text': 'BENEFITS INCLUDE:'}, {'level': 2, 'text': 'Subscribe to our emails'}, {'level': 3, 'text': 'SERVICES'}, {'level': 3, 'text': 'HELP'}, {'level': 3, 'text': 'ABOUT'}, {'level': 1, 'text': 'UNLOCK 10% OFF'}, {'level': 2, 'text': 'Your Cart'}, {'level': 4, 'text': 'Your cart is empty!'}, {'level': 4, 'text': 'Get started with our top sellers'}, {'level': 5, 'text': \"Simond Men's MT100 Hooded Down Puffer Jacket\"}, {'level': 5, 'text': \"Simond Forclaz Men's MT500 Hooded Down Puffer Jacket\"}, {'level': 5, 'text': \"Simond Women's MT100 Hooded Down Puffer Jacket\"}]}], 'mllm_validations': {'mllm_alttext_assessments': {'mllm_alttext_assessments_openai': [{'image_url': 'https://giove.isti.cnr.it/users/leonardi/decathlon/8493310-product_image-p2865897_526e43f2-1fc4-4646-83ae-d71af4b654a2.jpg', 'alt_text': '{*slate-blue-8493310*}', 'mllm_response': {'original_alt_text_assessment': '1', 'assessment': 'failure', 'evaluation_result': \"The alt-text 'slate-blue-8493310' is insufficient and doesn't convey meaningful information about the image or its context.\", 'new_alt_text': \"Simond Men's Travel 500 Merino Wool T-Shirt in slate blue.\", 'mllm_model': 'gpt-4o'}}, {'image_url': 'https://giove.isti.cnr.it/users/leonardi/decathlon/FORCLAZ_SAD_H_MT_100_EASYFIT_70L_OCRE_-_8559696_-_001_---_Expires_on_04-06-2035.jpg', 'alt_text': 'Red and gray backpack on a white background', 'mllm_response': {'original_alt_text_assessment': '3', 'assessment': 'warning', 'evaluation_result': \"The current alt-text describes the image's appearance but does not fully convey its purpose, such as highlighting the product's features or sale information.\", 'new_alt_text': \"Simond Men's MT100 Easyfit 70L backpacking pack, save 30%.\", 'mllm_model': 'gpt-4o'}}], 'mllm_alttext_assessments_local': [{'image_url': 'https://giove.isti.cnr.it/users/leonardi/decathlon/8493310-product_image-p2865897_526e43f2-1fc4-4646-83ae-d71af4b654a2.jpg', 'alt_text': '{*slate-blue-8493310*}', 'mllm_response': {'original_alt_text_assessment': '1', 'assessment': 'failure', 'evaluation_result': \"The original alt-text 'slate-blue-8493310' is inappropriate as it does not describe the purpose or function of the image in relation to its surrounding context. The image showcases a man wearing a Simond Men's Travel 500 Merino Wool T-Shirt, and the alt-text should reflect this.\", 'new_alt_text': \"Man wearing Simond Men's Travel 500 Merino Wool T-shirt in slate blue.\", 'mllm_model': 'gemma3:4b-q8_0-wcag'}}, {'image_url': 'https://giove.isti.cnr.it/users/leonardi/decathlon/FORCLAZ_SAD_H_MT_100_EASYFIT_70L_OCRE_-_8559696_-_001_---_Expires_on_04-06-2035.jpg', 'alt_text': 'Red and gray backpack on a white background', 'mllm_response': {'original_alt_text_assessment': '3', 'assessment': 'warning', 'evaluation_result': \"The alt-text 'Red and gray backpack on a white background' provides basic color information but does not sufficiently describe the function or purpose of the image in relation to the product context. The page focuses on backpacking packs, but the alt-text lacks specificity.\", 'new_alt_text': \"Simond Men's MT100 Easyfit 70L Backpacking Pack shown against a white background.\", 'mllm_model': 'gemma3:4b-q8_0-wcag'}}]}}}\n", + "output: [{'Image #': 21, 'Original Alt Text': '{*slate-blue-8493310*}', 'User Assessment': 3, 'User Proposed Alt Text': '{*slate-blue-8493310*}', 'LLM Assessment 1': '1', 'LLM Proposed Alt Text 1': \"Simond Men's Travel 500 Merino Wool T-Shirt in slate blue.\", 'LLM Assessment 2': '1', 'LLM Proposed Alt Text 2': \"Man wearing Simond Men's Travel 500 Merino Wool T-shirt in slate blue.\", 'User Assessment for LLM Proposal 1': 3, 'User Assessment for LLM Proposal 2': 3}, {'Image #': 22, 'Original Alt Text': 'Red and gray backpack on a white background', 'User Assessment': 3, 'User Proposed Alt Text': 'Red and gray backpack on a white background', 'LLM Assessment 1': '3', 'LLM Proposed Alt Text 1': \"Simond Men's MT100 Easyfit 70L backpacking pack, save 30%.\", 'LLM Assessment 2': '3', 'LLM Proposed Alt Text 2': \"Simond Men's MT100 Easyfit 70L Backpacking Pack shown against a white background.\", 'User Assessment for LLM Proposal 1': 3, 'User Assessment for LLM Proposal 2': 3}]\n", + "is_single_model_output is True, so we consider only one model output per row\n", + "input: {'images': [{'url': 'https://giove.isti.cnr.it/users/leonardi/nike/M+NK+CLUB+SSNL+WNTRZD+AOP+HZ.png', 'alt_text': \"Nike Club Zip a metà lunghezza per l'inverno – Uomo\", 'html_context': ': Nike Club', 'immediate_context': 'No immediate context found', 'nearby_text': ' [93px]: Nike Club', 'page_url': 'https://giove.isti.cnr.it/users/leonardi/nike.html', 'page_title': 'Acquista Abbigliamento da Uomo. Nike IT', 'page_description': \"Trova l'abbigliamento da uomo Nike per lo sport, l'allenamento e il tempo libero.\", 'page_keywords': 'Acquista Abbigliamento da Uomo', 'page_headings': [{'level': 1, 'text': 'Abbigliamento Uomo(1418)'}, {'level': 2, 'text': 'Sconto Community'}, {'level': 2, 'text': 'Africa'}, {'level': 4, 'text': 'Egypt'}, {'level': 4, 'text': 'Morocco'}, {'level': 4, 'text': 'Maroc'}, {'level': 4, 'text': 'South Africa'}, {'level': 2, 'text': 'Americas'}, {'level': 4, 'text': 'Argentina'}, {'level': 4, 'text': 'Brasil'}, {'level': 4, 'text': 'Canada'}, {'level': 4, 'text': 'Canada'}, {'level': 4, 'text': 'Chile'}, {'level': 4, 'text': 'Colombia'}, {'level': 4, 'text': 'México'}, {'level': 4, 'text': 'Peru'}, {'level': 4, 'text': 'Puerto Rico'}, {'level': 4, 'text': 'United States'}, {'level': 4, 'text': 'Estados Unidos'}, {'level': 4, 'text': 'Uruguay'}, {'level': 4, 'text': 'Latin America'}, {'level': 2, 'text': 'Asia Pacific'}, {'level': 4, 'text': 'Australia'}, {'level': 4, 'text': '中国大陆'}, {'level': 4, 'text': 'Hong Kong'}, {'level': 4, 'text': '香港'}, {'level': 4, 'text': 'India'}, {'level': 4, 'text': 'Indonesia'}, {'level': 4, 'text': 'Japan'}, {'level': 4, 'text': '日本'}, {'level': 4, 'text': '대한민국'}, {'level': 4, 'text': 'Malaysia'}, {'level': 4, 'text': 'New Zealand'}, {'level': 4, 'text': 'Philippines'}, {'level': 4, 'text': 'Singapore'}, {'level': 4, 'text': '台灣'}, {'level': 4, 'text': 'ไทย'}, {'level': 4, 'text': 'Vietnam'}, {'level': 2, 'text': 'Europe'}, {'level': 4, 'text': 'Österreich'}, {'level': 4, 'text': 'Austria'}, {'level': 4, 'text': 'Belgien'}, {'level': 4, 'text': 'Belgium'}, {'level': 4, 'text': 'Belgique'}, {'level': 4, 'text': 'België'}, {'level': 4, 'text': 'Bulgaria'}, {'level': 4, 'text': 'Croatia'}, {'level': 4, 'text': 'Česká republika'}, {'level': 4, 'text': 'Czech Republic'}, {'level': 4, 'text': 'Danmark'}, {'level': 4, 'text': 'Denmark'}, {'level': 4, 'text': 'Finland'}, {'level': 4, 'text': 'France'}, {'level': 4, 'text': 'Deutschland'}, {'level': 4, 'text': 'Ελλάδα'}, {'level': 4, 'text': 'Hungary'}, {'level': 4, 'text': 'Magyarország'}, {'level': 4, 'text': 'Ireland'}, {'level': 4, 'text': 'Israel'}, {'level': 4, 'text': 'Italia'}, {'level': 4, 'text': 'Luxemburg'}, {'level': 4, 'text': 'Luxembourg'}, {'level': 4, 'text': 'Luxembourg'}, {'level': 4, 'text': 'Netherlands'}, {'level': 4, 'text': 'Nederland'}, {'level': 4, 'text': 'Norway'}, {'level': 4, 'text': 'Norge'}, {'level': 4, 'text': 'Polska'}, {'level': 4, 'text': 'Portugal'}, {'level': 4, 'text': 'Portugal'}, {'level': 4, 'text': 'Romania'}, {'level': 4, 'text': 'Россия'}, {'level': 4, 'text': 'Slovakia'}, {'level': 4, 'text': 'Slovenia'}, {'level': 4, 'text': 'Espanya'}, {'level': 4, 'text': 'España'}, {'level': 4, 'text': 'Sweden'}, {'level': 4, 'text': 'Sverige'}, {'level': 4, 'text': 'Schweiz'}, {'level': 4, 'text': 'Switzerland'}, {'level': 4, 'text': 'Suisse'}, {'level': 4, 'text': 'Svizzera'}, {'level': 4, 'text': 'Türkiye'}, {'level': 4, 'text': 'United Kingdom'}, {'level': 2, 'text': 'Middle East'}, {'level': 4, 'text': 'Saudi Arabia'}, {'level': 4, 'text': 'المملكة العربية السعودية'}, {'level': 4, 'text': 'United Arab Emirates'}, {'level': 4, 'text': 'الإمارات العربية المتحدة'}, {'level': 4, 'text': 'Qatar'}, {'level': 4, 'text': 'دولة قطر'}, {'level': 4, 'text': 'Kuwait'}, {'level': 4, 'text': 'الكويت'}]}, {'url': 'https://giove.isti.cnr.it/users/leonardi/nike/M+NK+WVN+ICON+PNT+STRTFV+SN.png', 'alt_text': 'Nike Icon Pantaloni in tessuto da basket - Uomo', 'html_context': ': Nike Icon', 'immediate_context': 'No immediate context found', 'nearby_text': ' [93px]: Nike Icon', 'page_url': 'https://giove.isti.cnr.it/users/leonardi/nike.html', 'page_title': 'Acquista Abbigliamento da Uomo. Nike IT', 'page_description': \"Trova l'abbigliamento da uomo Nike per lo sport, l'allenamento e il tempo libero.\", 'page_keywords': 'Acquista Abbigliamento da Uomo', 'page_headings': [{'level': 1, 'text': 'Abbigliamento Uomo(1418)'}, {'level': 2, 'text': 'Sconto Community'}, {'level': 2, 'text': 'Africa'}, {'level': 4, 'text': 'Egypt'}, {'level': 4, 'text': 'Morocco'}, {'level': 4, 'text': 'Maroc'}, {'level': 4, 'text': 'South Africa'}, {'level': 2, 'text': 'Americas'}, {'level': 4, 'text': 'Argentina'}, {'level': 4, 'text': 'Brasil'}, {'level': 4, 'text': 'Canada'}, {'level': 4, 'text': 'Canada'}, {'level': 4, 'text': 'Chile'}, {'level': 4, 'text': 'Colombia'}, {'level': 4, 'text': 'México'}, {'level': 4, 'text': 'Peru'}, {'level': 4, 'text': 'Puerto Rico'}, {'level': 4, 'text': 'United States'}, {'level': 4, 'text': 'Estados Unidos'}, {'level': 4, 'text': 'Uruguay'}, {'level': 4, 'text': 'Latin America'}, {'level': 2, 'text': 'Asia Pacific'}, {'level': 4, 'text': 'Australia'}, {'level': 4, 'text': '中国大陆'}, {'level': 4, 'text': 'Hong Kong'}, {'level': 4, 'text': '香港'}, {'level': 4, 'text': 'India'}, {'level': 4, 'text': 'Indonesia'}, {'level': 4, 'text': 'Japan'}, {'level': 4, 'text': '日本'}, {'level': 4, 'text': '대한민국'}, {'level': 4, 'text': 'Malaysia'}, {'level': 4, 'text': 'New Zealand'}, {'level': 4, 'text': 'Philippines'}, {'level': 4, 'text': 'Singapore'}, {'level': 4, 'text': '台灣'}, {'level': 4, 'text': 'ไทย'}, {'level': 4, 'text': 'Vietnam'}, {'level': 2, 'text': 'Europe'}, {'level': 4, 'text': 'Österreich'}, {'level': 4, 'text': 'Austria'}, {'level': 4, 'text': 'Belgien'}, {'level': 4, 'text': 'Belgium'}, {'level': 4, 'text': 'Belgique'}, {'level': 4, 'text': 'België'}, {'level': 4, 'text': 'Bulgaria'}, {'level': 4, 'text': 'Croatia'}, {'level': 4, 'text': 'Česká republika'}, {'level': 4, 'text': 'Czech Republic'}, {'level': 4, 'text': 'Danmark'}, {'level': 4, 'text': 'Denmark'}, {'level': 4, 'text': 'Finland'}, {'level': 4, 'text': 'France'}, {'level': 4, 'text': 'Deutschland'}, {'level': 4, 'text': 'Ελλάδα'}, {'level': 4, 'text': 'Hungary'}, {'level': 4, 'text': 'Magyarország'}, {'level': 4, 'text': 'Ireland'}, {'level': 4, 'text': 'Israel'}, {'level': 4, 'text': 'Italia'}, {'level': 4, 'text': 'Luxemburg'}, {'level': 4, 'text': 'Luxembourg'}, {'level': 4, 'text': 'Luxembourg'}, {'level': 4, 'text': 'Netherlands'}, {'level': 4, 'text': 'Nederland'}, {'level': 4, 'text': 'Norway'}, {'level': 4, 'text': 'Norge'}, {'level': 4, 'text': 'Polska'}, {'level': 4, 'text': 'Portugal'}, {'level': 4, 'text': 'Portugal'}, {'level': 4, 'text': 'Romania'}, {'level': 4, 'text': 'Россия'}, {'level': 4, 'text': 'Slovakia'}, {'level': 4, 'text': 'Slovenia'}, {'level': 4, 'text': 'Espanya'}, {'level': 4, 'text': 'España'}, {'level': 4, 'text': 'Sweden'}, {'level': 4, 'text': 'Sverige'}, {'level': 4, 'text': 'Schweiz'}, {'level': 4, 'text': 'Switzerland'}, {'level': 4, 'text': 'Suisse'}, {'level': 4, 'text': 'Svizzera'}, {'level': 4, 'text': 'Türkiye'}, {'level': 4, 'text': 'United Kingdom'}, {'level': 2, 'text': 'Middle East'}, {'level': 4, 'text': 'Saudi Arabia'}, {'level': 4, 'text': 'المملكة العربية السعودية'}, {'level': 4, 'text': 'United Arab Emirates'}, {'level': 4, 'text': 'الإمارات العربية المتحدة'}, {'level': 4, 'text': 'Qatar'}, {'level': 4, 'text': 'دولة قطر'}, {'level': 4, 'text': 'Kuwait'}, {'level': 4, 'text': 'الكويت'}]}, {'url': 'https://giove.isti.cnr.it/users/leonardi/nike/KB+U+NK+TF+FUND+PANT.png', 'alt_text': 'Kobe Pantaloni da basket Therma-FIT', 'html_context': ': Kobe', 'immediate_context': 'No immediate context found', 'nearby_text': ' [93px]: Kobe', 'page_url': 'https://giove.isti.cnr.it/users/leonardi/nike.html', 'page_title': 'Acquista Abbigliamento da Uomo. Nike IT', 'page_description': \"Trova l'abbigliamento da uomo Nike per lo sport, l'allenamento e il tempo libero.\", 'page_keywords': 'Acquista Abbigliamento da Uomo', 'page_headings': [{'level': 1, 'text': 'Abbigliamento Uomo(1418)'}, {'level': 2, 'text': 'Sconto Community'}, {'level': 2, 'text': 'Africa'}, {'level': 4, 'text': 'Egypt'}, {'level': 4, 'text': 'Morocco'}, {'level': 4, 'text': 'Maroc'}, {'level': 4, 'text': 'South Africa'}, {'level': 2, 'text': 'Americas'}, {'level': 4, 'text': 'Argentina'}, {'level': 4, 'text': 'Brasil'}, {'level': 4, 'text': 'Canada'}, {'level': 4, 'text': 'Canada'}, {'level': 4, 'text': 'Chile'}, {'level': 4, 'text': 'Colombia'}, {'level': 4, 'text': 'México'}, {'level': 4, 'text': 'Peru'}, {'level': 4, 'text': 'Puerto Rico'}, {'level': 4, 'text': 'United States'}, {'level': 4, 'text': 'Estados Unidos'}, {'level': 4, 'text': 'Uruguay'}, {'level': 4, 'text': 'Latin America'}, {'level': 2, 'text': 'Asia Pacific'}, {'level': 4, 'text': 'Australia'}, {'level': 4, 'text': '中国大陆'}, {'level': 4, 'text': 'Hong Kong'}, {'level': 4, 'text': '香港'}, {'level': 4, 'text': 'India'}, {'level': 4, 'text': 'Indonesia'}, {'level': 4, 'text': 'Japan'}, {'level': 4, 'text': '日本'}, {'level': 4, 'text': '대한민국'}, {'level': 4, 'text': 'Malaysia'}, {'level': 4, 'text': 'New Zealand'}, {'level': 4, 'text': 'Philippines'}, {'level': 4, 'text': 'Singapore'}, {'level': 4, 'text': '台灣'}, {'level': 4, 'text': 'ไทย'}, {'level': 4, 'text': 'Vietnam'}, {'level': 2, 'text': 'Europe'}, {'level': 4, 'text': 'Österreich'}, {'level': 4, 'text': 'Austria'}, {'level': 4, 'text': 'Belgien'}, {'level': 4, 'text': 'Belgium'}, {'level': 4, 'text': 'Belgique'}, {'level': 4, 'text': 'België'}, {'level': 4, 'text': 'Bulgaria'}, {'level': 4, 'text': 'Croatia'}, {'level': 4, 'text': 'Česká republika'}, {'level': 4, 'text': 'Czech Republic'}, {'level': 4, 'text': 'Danmark'}, {'level': 4, 'text': 'Denmark'}, {'level': 4, 'text': 'Finland'}, {'level': 4, 'text': 'France'}, {'level': 4, 'text': 'Deutschland'}, {'level': 4, 'text': 'Ελλάδα'}, {'level': 4, 'text': 'Hungary'}, {'level': 4, 'text': 'Magyarország'}, {'level': 4, 'text': 'Ireland'}, {'level': 4, 'text': 'Israel'}, {'level': 4, 'text': 'Italia'}, {'level': 4, 'text': 'Luxemburg'}, {'level': 4, 'text': 'Luxembourg'}, {'level': 4, 'text': 'Luxembourg'}, {'level': 4, 'text': 'Netherlands'}, {'level': 4, 'text': 'Nederland'}, {'level': 4, 'text': 'Norway'}, {'level': 4, 'text': 'Norge'}, {'level': 4, 'text': 'Polska'}, {'level': 4, 'text': 'Portugal'}, {'level': 4, 'text': 'Portugal'}, {'level': 4, 'text': 'Romania'}, {'level': 4, 'text': 'Россия'}, {'level': 4, 'text': 'Slovakia'}, {'level': 4, 'text': 'Slovenia'}, {'level': 4, 'text': 'Espanya'}, {'level': 4, 'text': 'España'}, {'level': 4, 'text': 'Sweden'}, {'level': 4, 'text': 'Sverige'}, {'level': 4, 'text': 'Schweiz'}, {'level': 4, 'text': 'Switzerland'}, {'level': 4, 'text': 'Suisse'}, {'level': 4, 'text': 'Svizzera'}, {'level': 4, 'text': 'Türkiye'}, {'level': 4, 'text': 'United Kingdom'}, {'level': 2, 'text': 'Middle East'}, {'level': 4, 'text': 'Saudi Arabia'}, {'level': 4, 'text': 'المملكة العربية السعودية'}, {'level': 4, 'text': 'United Arab Emirates'}, {'level': 4, 'text': 'الإمارات العربية المتحدة'}, {'level': 4, 'text': 'Qatar'}, {'level': 4, 'text': 'دولة قطر'}, {'level': 4, 'text': 'Kuwait'}, {'level': 4, 'text': 'الكويت'}]}], 'mllm_validations': {'mllm_alttext_assessments': {'mllm_alttext_assessments_openai': [{'image_url': 'https://giove.isti.cnr.it/users/leonardi/nike/M+NK+CLUB+SSNL+WNTRZD+AOP+HZ.png', 'alt_text': \"Nike Club Zip a metà lunghezza per l'inverno – Uomo\", 'mllm_response': {'original_alt_text_assessment': '5', 'assessment': 'success', 'evaluation_result': \"The alt-text describes the product accurately, providing sufficient detail about the item's type, design, and target audience in the context of the webpage.\", 'new_alt_text': \"Nike Club maglia a scacchi con zip a metà lunghezza per l'inverno – Uomo\", 'mllm_model': 'gpt-4o'}}, {'image_url': 'https://giove.isti.cnr.it/users/leonardi/nike/M+NK+WVN+ICON+PNT+STRTFV+SN.png', 'alt_text': 'Nike Icon Pantaloni in tessuto da basket - Uomo', 'mllm_response': {'original_alt_text_assessment': '5', 'assessment': 'success', 'evaluation_result': \"The alt-text clearly describes the product, aligning with its purpose as a clickable link for Nike men's basketball pants.\", 'new_alt_text': 'Nike Icon Pantaloni in tessuto da basket - Uomo', 'mllm_model': 'gpt-4o'}}, {'image_url': 'https://giove.isti.cnr.it/users/leonardi/nike/KB+U+NK+TF+FUND+PANT.png', 'alt_text': 'Kobe Pantaloni da basket Therma-FIT', 'mllm_response': {'original_alt_text_assessment': '3', 'assessment': 'warning', 'evaluation_result': 'The alt-text partially describes the product but fails to clarify its relationship to the surrounding context, such as Nike apparel or the link purpose.', 'new_alt_text': 'Pantaloni da basket Nike Therma-FIT in vendita. Scopri di più.', 'mllm_model': 'gpt-4o'}}], 'mllm_alttext_assessments_local': [{'image_url': 'https://giove.isti.cnr.it/users/leonardi/nike/M+NK+CLUB+SSNL+WNTRZD+AOP+HZ.png', 'alt_text': \"Nike Club Zip a metà lunghezza per l'inverno – Uomo\", 'mllm_response': {'original_alt_text_assessment': '3', 'assessment': 'warning', 'evaluation_result': 'The alt-text is somewhat appropriate as it identifies the product type (Nike Club Zip) and its features (half-length for winter). However, it could be more descriptive by mentioning the plaid pattern or style.', 'new_alt_text': \"Nike Club Zip a mezzo torso con motivo plaid nero e grigio per l'inverno.\", 'mllm_model': 'gemma3:4b-q8_0-wcag'}}, {'image_url': 'https://giove.isti.cnr.it/users/leonardi/nike/M+NK+WVN+ICON+PNT+STRTFV+SN.png', 'alt_text': 'Nike Icon Pantaloni in tessuto da basket - Uomo', 'mllm_response': {'original_alt_text_assessment': '4', 'assessment': 'success', 'evaluation_result': \"The alt-text 'Nike Icon Pantaloni in tessuto da basket - Uomo' is appropriate as it describes the product accurately and aligns with the surrounding context of purchasing men's Nike apparel.\", 'new_alt_text': 'Nike Icon Pantaloni in tessuto da basket neri con logo bianco, abbigliamento sportivo per uomo.', 'mllm_model': 'gemma3:4b-q8_0-wcag'}}, {'image_url': 'https://giove.isti.cnr.it/users/leonardi/nike/KB+U+NK+TF+FUND+PANT.png', 'alt_text': 'Kobe Pantaloni da basket Therma-FIT', 'mllm_response': {'original_alt_text_assessment': '3', 'assessment': 'warning', 'evaluation_result': \"The alt-text 'Kobe Pantaloni da basket Therma-FIT' partially describes the image content but could be more descriptive to align with the page context. It mentions the product name and type, but doesn't convey the full purpose or function of the image.\", 'new_alt_text': 'Modello in abbigliamento sportivo Nike Therma-FIT: pantaloni da basket Kobe e felpa con cappuccio rosa.', 'mllm_model': 'gemma3:4b-q8_0-wcag'}}]}}}\n", + "output: [{'Image #': 7, 'Original Alt Text': \"Nike Club Zip a metà lunghezza per l'inverno – Uomo\", 'User Assessment': 3, 'User Proposed Alt Text': \"Nike Club Zip a metà lunghezza per l'inverno – Uomo\", 'LLM Assessment 1': '5', 'LLM Proposed Alt Text 1': \"Nike Club maglia a scacchi con zip a metà lunghezza per l'inverno – Uomo\", 'LLM Assessment 2': '3', 'LLM Proposed Alt Text 2': \"Nike Club Zip a mezzo torso con motivo plaid nero e grigio per l'inverno.\", 'User Assessment for LLM Proposal 1': 3, 'User Assessment for LLM Proposal 2': 3}, {'Image #': 8, 'Original Alt Text': 'Nike Icon Pantaloni in tessuto da basket - Uomo', 'User Assessment': 3, 'User Proposed Alt Text': 'Nike Icon Pantaloni in tessuto da basket - Uomo', 'LLM Assessment 1': '5', 'LLM Proposed Alt Text 1': 'Nike Icon Pantaloni in tessuto da basket - Uomo', 'LLM Assessment 2': '4', 'LLM Proposed Alt Text 2': 'Nike Icon Pantaloni in tessuto da basket neri con logo bianco, abbigliamento sportivo per uomo.', 'User Assessment for LLM Proposal 1': 3, 'User Assessment for LLM Proposal 2': 3}, {'Image #': 9, 'Original Alt Text': 'Kobe Pantaloni da basket Therma-FIT', 'User Assessment': 3, 'User Proposed Alt Text': 'Kobe Pantaloni da basket Therma-FIT', 'LLM Assessment 1': '3', 'LLM Proposed Alt Text 1': 'Pantaloni da basket Nike Therma-FIT in vendita. Scopri di più.', 'LLM Assessment 2': '3', 'LLM Proposed Alt Text 2': 'Modello in abbigliamento sportivo Nike Therma-FIT: pantaloni da basket Kobe e felpa con cappuccio rosa.', 'User Assessment for LLM Proposal 1': 3, 'User Assessment for LLM Proposal 2': 3}]\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idinsertion_timeinsert_typepage_urluserimage_urloriginal_alt_texthtml_contextimmediate_contextnearby_context...page_keywordspage_headingsllm_assessmentllm_judgmentllm_evaluation_resultllm_alt_textllm_modeluser_assessmentuser_alt_textuser_llm_assessment
022026-03-03 09:51:55wcag_user_llm_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/decat...{\"username\": \"nicola\"}https://giove.isti.cnr.it/users/leonardi/decat...{*slate-blue-8493310*}<span>: Save 22% <span>: Vendor: <h3>: Simond ...No immediate context found<span> [154px]: Save 22%...None[{\"level\": 2, \"text\": \"Your cart is empty\"}, {...1failureThe alt-text 'slate-blue-8493310' is insuffici...Simond Men's Travel 500 Merino Wool T-Shirt in...gpt-4o3{*slate-blue-8493310*}3
122026-03-03 09:51:55wcag_user_llm_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/decat...{\"username\": \"nicola\"}https://giove.isti.cnr.it/users/leonardi/decat...Red and gray backpack on a white background<span>: Save 30% <span>: Vendor: <h3>: Simond ...No immediate context found<span> [154px]: Save 30%...None[{\"level\": 2, \"text\": \"Your cart is empty\"}, {...3warningThe current alt-text describes the image's app...Simond Men's MT100 Easyfit 70L backpacking pac...gpt-4o3Red and gray backpack on a white background3
242026-03-03 09:54:06wcag_user_llm_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/nike....{\"username\": \"nicola\"}https://giove.isti.cnr.it/users/leonardi/nike/...Nike Club Zip a metà lunghezza per l'inverno –...<a>: Nike ClubNo immediate context found<a> [93px]: Nike Club...Acquista Abbigliamento da Uomo[{\"level\": 1, \"text\": \"Abbigliamento Uomo(1418...5successThe alt-text describes the product accurately,...Nike Club maglia a scacchi con zip a metà lung...gpt-4o3Nike Club Zip a metà lunghezza per l'inverno –...3
342026-03-03 09:54:06wcag_user_llm_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/nike....{\"username\": \"nicola\"}https://giove.isti.cnr.it/users/leonardi/nike/...Nike Icon Pantaloni in tessuto da basket - Uomo<a>: Nike IconNo immediate context found<a> [93px]: Nike Icon...Acquista Abbigliamento da Uomo[{\"level\": 1, \"text\": \"Abbigliamento Uomo(1418...5successThe alt-text clearly describes the product, al...Nike Icon Pantaloni in tessuto da basket - Uomogpt-4o3Nike Icon Pantaloni in tessuto da basket - Uomo3
442026-03-03 09:54:06wcag_user_llm_alttext_assessmentshttps://giove.isti.cnr.it/users/leonardi/nike....{\"username\": \"nicola\"}https://giove.isti.cnr.it/users/leonardi/nike/...Kobe Pantaloni da basket Therma-FIT<a>: KobeNo immediate context found<a> [93px]: Kobe...Acquista Abbigliamento da Uomo[{\"level\": 1, \"text\": \"Abbigliamento Uomo(1418...3warningThe alt-text partially describes the product b...Pantaloni da basket Nike Therma-FIT in vendita...gpt-4o3Kobe Pantaloni da basket Therma-FIT3
\n", + "

5 rows × 22 columns

\n", + "
" + ], + "text/plain": [ + " id insertion_time insert_type \\\n", + "0 2 2026-03-03 09:51:55 wcag_user_llm_alttext_assessments \n", + "1 2 2026-03-03 09:51:55 wcag_user_llm_alttext_assessments \n", + "2 4 2026-03-03 09:54:06 wcag_user_llm_alttext_assessments \n", + "3 4 2026-03-03 09:54:06 wcag_user_llm_alttext_assessments \n", + "4 4 2026-03-03 09:54:06 wcag_user_llm_alttext_assessments \n", + "\n", + " page_url user \\\n", + "0 https://giove.isti.cnr.it/users/leonardi/decat... {\"username\": \"nicola\"} \n", + "1 https://giove.isti.cnr.it/users/leonardi/decat... {\"username\": \"nicola\"} \n", + "2 https://giove.isti.cnr.it/users/leonardi/nike.... {\"username\": \"nicola\"} \n", + "3 https://giove.isti.cnr.it/users/leonardi/nike.... {\"username\": \"nicola\"} \n", + "4 https://giove.isti.cnr.it/users/leonardi/nike.... {\"username\": \"nicola\"} \n", + "\n", + " image_url \\\n", + "0 https://giove.isti.cnr.it/users/leonardi/decat... \n", + "1 https://giove.isti.cnr.it/users/leonardi/decat... \n", + "2 https://giove.isti.cnr.it/users/leonardi/nike/... \n", + "3 https://giove.isti.cnr.it/users/leonardi/nike/... \n", + "4 https://giove.isti.cnr.it/users/leonardi/nike/... \n", + "\n", + " original_alt_text \\\n", + "0 {*slate-blue-8493310*} \n", + "1 Red and gray backpack on a white background \n", + "2 Nike Club Zip a metà lunghezza per l'inverno –... \n", + "3 Nike Icon Pantaloni in tessuto da basket - Uomo \n", + "4 Kobe Pantaloni da basket Therma-FIT \n", + "\n", + " html_context \\\n", + "0 : Save 22% : Vendor:

: Simond ... \n", + "1 : Save 30% : Vendor:

: Simond ... \n", + "2 : Nike Club \n", + "3 : Nike Icon \n", + "4 : Kobe \n", + "\n", + " immediate_context nearby_context ... \\\n", + "0 No immediate context found [154px]: Save 22% ... \n", + "1 No immediate context found [154px]: Save 30% ... \n", + "2 No immediate context found [93px]: Nike Club ... \n", + "3 No immediate context found [93px]: Nike Icon ... \n", + "4 No immediate context found [93px]: Kobe ... \n", + "\n", + " page_keywords \\\n", + "0 None \n", + "1 None \n", + "2 Acquista Abbigliamento da Uomo \n", + "3 Acquista Abbigliamento da Uomo \n", + "4 Acquista Abbigliamento da Uomo \n", + "\n", + " page_headings llm_assessment \\\n", + "0 [{\"level\": 2, \"text\": \"Your cart is empty\"}, {... 1 \n", + "1 [{\"level\": 2, \"text\": \"Your cart is empty\"}, {... 3 \n", + "2 [{\"level\": 1, \"text\": \"Abbigliamento Uomo(1418... 5 \n", + "3 [{\"level\": 1, \"text\": \"Abbigliamento Uomo(1418... 5 \n", + "4 [{\"level\": 1, \"text\": \"Abbigliamento Uomo(1418... 3 \n", + "\n", + " llm_judgment llm_evaluation_result \\\n", + "0 failure The alt-text 'slate-blue-8493310' is insuffici... \n", + "1 warning The current alt-text describes the image's app... \n", + "2 success The alt-text describes the product accurately,... \n", + "3 success The alt-text clearly describes the product, al... \n", + "4 warning The alt-text partially describes the product b... \n", + "\n", + " llm_alt_text llm_model \\\n", + "0 Simond Men's Travel 500 Merino Wool T-Shirt in... gpt-4o \n", + "1 Simond Men's MT100 Easyfit 70L backpacking pac... gpt-4o \n", + "2 Nike Club maglia a scacchi con zip a metà lung... gpt-4o \n", + "3 Nike Icon Pantaloni in tessuto da basket - Uomo gpt-4o \n", + "4 Pantaloni da basket Nike Therma-FIT in vendita... gpt-4o \n", + "\n", + " user_assessment user_alt_text \\\n", + "0 3 {*slate-blue-8493310*} \n", + "1 3 Red and gray backpack on a white background \n", + "2 3 Nike Club Zip a metà lunghezza per l'inverno –... \n", + "3 3 Nike Icon Pantaloni in tessuto da basket - Uomo \n", + "4 3 Kobe Pantaloni da basket Therma-FIT \n", + "\n", + " user_llm_assessment \n", + "0 3 \n", + "1 3 \n", + "2 3 \n", + "3 3 \n", + "4 3 \n", + "\n", + "[5 rows x 22 columns]" + ] + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "backend_dataframe_expanded= expand_json_data_backend(df,row_name=\"json_input_data\",is_single_model_output = True)\n", + "#backend_dataframe_expanded= expand_json_data_backend(df,row_name=\"json_input_data\",is_single_model_output = False)\n", + "backend_dataframe_expanded" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "fbd442b5", + "metadata": {}, + "outputs": [], + "source": [ + "backend_dataframe_expanded.to_csv(\"new_management_dataframe_expanded.csv\",sep=\";\",index=False)\n", + "#backend_dataframe_expanded.to_csv(\"new_management_dataframe_expanded_multiple_model_output.csv\",sep=\";\",index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "f3e47bd3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(Index(['id', 'insertion_time', 'insert_type', 'page_url', 'user', 'image_url',\n", + " 'original_alt_text', 'html_context', 'immediate_context',\n", + " 'nearby_context', 'page_title', 'page_description', 'page_keywords',\n", + " 'page_headings', 'llm_assessment', 'llm_judgment',\n", + " 'llm_evaluation_result', 'llm_alt_text', 'llm_model', 'user_assessment',\n", + " 'user_alt_text', 'user_llm_assessment'],\n", + " dtype='object'),\n", + " 22)" + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "backend_dataframe_expanded.columns,len(backend_dataframe_expanded.columns)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "0922243c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "18" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#nell'altro notebook dopo tutti i merge\n", + "len(['page_url','user','image_url','original_alt_text','user_alt_text','llm_alt_text','user_assessment','llm_assessment','user_llm_assessment','llm_model',\n", + " 'html_context', 'immediate_context','nearby_context','page_title',\n", + " 'page_description', 'page_keywords', 'llm_evaluation_result','llm_judgment'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7674fbbe", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "accessibility", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}