elismasilva commited on
Commit
acbc3d4
·
verified ·
1 Parent(s): cac86a3

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -10,7 +10,7 @@ app_file: space.py
10
  ---
11
 
12
  # `gradio_imagemeta`
13
- <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.4%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_imagemeta"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_imagemeta'>Component GitHub Code</a></span></p>
14
 
15
  Image Preview with Metadata for Gradio Interface
16
 
@@ -41,10 +41,9 @@ import gradio as gr
41
  from gradio_imagemeta import ImageMeta
42
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
43
  from gradio_propertysheet import PropertySheet
44
- from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
45
  from pathlib import Path
46
 
47
-
48
  output_dir = Path("outputs")
49
  output_dir.mkdir(exist_ok=True)
50
 
@@ -62,101 +61,74 @@ class ImageSettings:
62
  @dataclass
63
  class PropertyConfig:
64
  """Root configuration for image properties, including nested image settings."""
65
- image_settings: ImageSettings = field(default_factory=ImageSettings)
66
  description: str = field(default="", metadata={"label": "Description"})
67
 
68
- def infer_type(s: str):
69
- """
70
- Infers and converts a string to the most likely data type.
71
-
72
- It attempts conversions in the following order:
73
- 1. Integer
74
- 2. Float
75
- 3. Boolean (case-insensitive 'true' or 'false')
76
- If all conversions fail, it returns the original string.
77
-
78
- Args:
79
- s: The input string to be converted.
80
-
81
- Returns:
82
- The converted value (int, float, bool) or the original string.
83
- """
84
- if not isinstance(s, str):
85
- # If the input is not a string, return it as is.
86
- return s
87
-
88
- # 1. Try to convert to an integer
89
- try:
90
- return int(s)
91
- except ValueError:
92
- # Not an integer, continue...
93
- pass
94
-
95
- # 2. Try to convert to a float
96
- try:
97
- return float(s)
98
- except ValueError:
99
- # Not a float, continue...
100
- pass
101
-
102
- # 3. Check for a boolean value
103
- # This explicit check is important because bool('False') evaluates to True.
104
- s_lower = s.lower()
105
- if s_lower == 'true':
106
- return True
107
- if s_lower == 'false':
108
- return False
109
-
110
- # 4. If nothing else worked, return the original string
111
- return s
112
-
113
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
114
  """
115
- Processes image metadata and maps it to output components.
116
-
117
- Args:
118
- image_data: ImageMeta object containing image data and metadata, or None.
119
-
120
- Returns:
121
- A list of values for output components (Textbox, Slider, or PropertySheet instances).
122
  """
123
- if not image_data:
124
- return [gr.Textbox(value="") for _ in output_fields]
125
 
126
  metadata = extract_metadata(image_data, only_custom_metadata=True)
127
- dataclass_fields = build_dataclass_fields(PropertyConfig)
128
- raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
129
-
130
- output_values = [gr.skip()] * len(output_fields)
131
- for i, (component, value) in enumerate(zip(output_fields, raw_values)):
132
- if hasattr(component, 'root_label'):
133
- output_values[i] = create_dataclass_instance(PropertyConfig, value)
134
- else:
135
- output_values[i] = gr.update(value=infer_type(value))
 
 
136
 
137
- return output_values
 
 
 
 
 
138
 
139
- def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
 
 
 
 
 
 
 
 
140
  """
141
- Saves an image with updated metadata to a file.
142
-
143
- Args:
144
- image_data: Input image data (e.g., file path or PIL Image).
145
- *inputs: Variable number of input values from UI components (Textbox, Slider).
146
-
147
- Returns:
148
- The file path of the saved image, or None if no image is provided.
149
  """
150
  if not image_data:
151
  return None
152
-
153
- params = list(inputs)
154
- image_params = dict(zip(input_fields.keys(), params))
155
- metadata = {label: image_params.get(label, "") for label in image_params.keys()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
  new_filepath = output_dir / "image_with_meta.png"
158
-
159
- add_metadata(image_data, new_filepath, metadata)
160
 
161
  return str(new_filepath)
162
 
@@ -263,7 +235,7 @@ with gr.Blocks() as demo:
263
  )
264
  save_button.click(
265
  save_image_with_metadata,
266
- inputs=[img_custom, *input_fields.values()],
267
  outputs=[saved_file_output]
268
  )
269
 
 
10
  ---
11
 
12
  # `gradio_imagemeta`
13
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.6%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_imagemeta"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_imagemeta'>Component GitHub Code</a></span></p>
14
 
15
  Image Preview with Metadata for Gradio Interface
16
 
 
41
  from gradio_imagemeta import ImageMeta
42
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
43
  from gradio_propertysheet import PropertySheet
44
+ from gradio_propertysheet.helpers import flatten_dataclass_with_labels
45
  from pathlib import Path
46
 
 
47
  output_dir = Path("outputs")
48
  output_dir.mkdir(exist_ok=True)
49
 
 
61
  @dataclass
62
  class PropertyConfig:
63
  """Root configuration for image properties, including nested image settings."""
64
+ image_settings: ImageSettings = field(default_factory=ImageSettings, metadata={"label": "Image Settings"})
65
  description: str = field(default="", metadata={"label": "Description"})
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
68
  """
69
+ Processes image metadata by calling the agnostic `transfer_metadata` helper.
 
 
 
 
 
 
70
  """
71
+ if not image_data or not hasattr(image_data, 'path'):
72
+ return [gr.skip()] * len(output_fields)
73
 
74
  metadata = extract_metadata(image_data, only_custom_metadata=True)
75
+ if not metadata:
76
+ return [gr.skip()] * len(output_fields)
77
+
78
+ # --- UI-Specific Configuration ---
79
+ # Define the map that tells the helper how to process the PropertySheet.
80
+ sheet_map = {
81
+ id(property_sheet): {
82
+ "type": PropertyConfig,
83
+ "prefixes": [] # No prefixes needed for this simple case
84
+ }
85
+ }
86
 
87
+ # Call the agnostic helper function to do the heavy lifting.
88
+ return transfer_metadata(
89
+ output_fields=output_fields,
90
+ metadata=metadata,
91
+ propertysheet_map=sheet_map
92
+ )
93
 
94
+ def save_image_with_metadata(
95
+ image_data: Any,
96
+ sheet_state: PropertyConfig,
97
+ model: str,
98
+ f_number: str,
99
+ iso: str,
100
+ s_churn_val: float,
101
+ description: str
102
+ ) -> str | None:
103
  """
104
+ Saves an image with updated metadata, merging data from the PropertySheet
105
+ and individual UI components.
106
+ This example deals with the PropertySheet component and the individual gradio components.
107
+ Since they have the same labels here, we'll simply replace the metadata with each other's values.
 
 
 
 
108
  """
109
  if not image_data:
110
  return None
111
+
112
+
113
+ metadata = flatten_dataclass_with_labels(sheet_state)
114
+ individual_component_values = {
115
+ "Model": model,
116
+ "FNumber": f_number,
117
+ "ISOSpeedRatings": iso,
118
+ "Schurn": s_churn_val,
119
+ "Description": description
120
+ }
121
+
122
+ metadata["Image Settings - Model"] = individual_component_values["Model"]
123
+ metadata["Image Settings - FNumber"] = individual_component_values["FNumber"]
124
+ metadata["Image Settings - ISOSpeedRatings"] = individual_component_values["ISOSpeedRatings"]
125
+ metadata["Image Settings - Schurn"] = individual_component_values["Schurn"]
126
+ metadata["Description"] = individual_component_values["Description"]
127
+
128
+ final_metadata = {str(key): value for key, value in metadata.items()}
129
 
130
  new_filepath = output_dir / "image_with_meta.png"
131
+ add_metadata(image_data, new_filepath, final_metadata)
 
132
 
133
  return str(new_filepath)
134
 
 
235
  )
236
  save_button.click(
237
  save_image_with_metadata,
238
+ inputs=[img_custom, property_sheet, *input_fields.values()],
239
  outputs=[saved_file_output]
240
  )
241
 
app.py CHANGED
@@ -4,10 +4,9 @@ import gradio as gr
4
  from gradio_imagemeta import ImageMeta
5
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
6
  from gradio_propertysheet import PropertySheet
7
- from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
8
  from pathlib import Path
9
 
10
-
11
  output_dir = Path("outputs")
12
  output_dir.mkdir(exist_ok=True)
13
 
@@ -25,101 +24,74 @@ class ImageSettings:
25
  @dataclass
26
  class PropertyConfig:
27
  """Root configuration for image properties, including nested image settings."""
28
- image_settings: ImageSettings = field(default_factory=ImageSettings)
29
  description: str = field(default="", metadata={"label": "Description"})
30
 
31
- def infer_type(s: str):
32
- """
33
- Infers and converts a string to the most likely data type.
34
-
35
- It attempts conversions in the following order:
36
- 1. Integer
37
- 2. Float
38
- 3. Boolean (case-insensitive 'true' or 'false')
39
- If all conversions fail, it returns the original string.
40
-
41
- Args:
42
- s: The input string to be converted.
43
-
44
- Returns:
45
- The converted value (int, float, bool) or the original string.
46
- """
47
- if not isinstance(s, str):
48
- # If the input is not a string, return it as is.
49
- return s
50
-
51
- # 1. Try to convert to an integer
52
- try:
53
- return int(s)
54
- except ValueError:
55
- # Not an integer, continue...
56
- pass
57
-
58
- # 2. Try to convert to a float
59
- try:
60
- return float(s)
61
- except ValueError:
62
- # Not a float, continue...
63
- pass
64
-
65
- # 3. Check for a boolean value
66
- # This explicit check is important because bool('False') evaluates to True.
67
- s_lower = s.lower()
68
- if s_lower == 'true':
69
- return True
70
- if s_lower == 'false':
71
- return False
72
-
73
- # 4. If nothing else worked, return the original string
74
- return s
75
-
76
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
77
  """
78
- Processes image metadata and maps it to output components.
79
-
80
- Args:
81
- image_data: ImageMeta object containing image data and metadata, or None.
82
-
83
- Returns:
84
- A list of values for output components (Textbox, Slider, or PropertySheet instances).
85
  """
86
- if not image_data:
87
- return [gr.Textbox(value="") for _ in output_fields]
88
 
89
  metadata = extract_metadata(image_data, only_custom_metadata=True)
90
- dataclass_fields = build_dataclass_fields(PropertyConfig)
91
- raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
92
-
93
- output_values = [gr.skip()] * len(output_fields)
94
- for i, (component, value) in enumerate(zip(output_fields, raw_values)):
95
- if hasattr(component, 'root_label'):
96
- output_values[i] = create_dataclass_instance(PropertyConfig, value)
97
- else:
98
- output_values[i] = gr.update(value=infer_type(value))
 
 
99
 
100
- return output_values
 
 
 
 
 
101
 
102
- def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
 
 
 
 
 
 
 
 
103
  """
104
- Saves an image with updated metadata to a file.
105
-
106
- Args:
107
- image_data: Input image data (e.g., file path or PIL Image).
108
- *inputs: Variable number of input values from UI components (Textbox, Slider).
109
-
110
- Returns:
111
- The file path of the saved image, or None if no image is provided.
112
  """
113
  if not image_data:
114
  return None
115
-
116
- params = list(inputs)
117
- image_params = dict(zip(input_fields.keys(), params))
118
- metadata = {label: image_params.get(label, "") for label in image_params.keys()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
  new_filepath = output_dir / "image_with_meta.png"
121
-
122
- add_metadata(image_data, new_filepath, metadata)
123
 
124
  return str(new_filepath)
125
 
@@ -226,7 +198,7 @@ with gr.Blocks() as demo:
226
  )
227
  save_button.click(
228
  save_image_with_metadata,
229
- inputs=[img_custom, *input_fields.values()],
230
  outputs=[saved_file_output]
231
  )
232
 
 
4
  from gradio_imagemeta import ImageMeta
5
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
6
  from gradio_propertysheet import PropertySheet
7
+ from gradio_propertysheet.helpers import flatten_dataclass_with_labels
8
  from pathlib import Path
9
 
 
10
  output_dir = Path("outputs")
11
  output_dir.mkdir(exist_ok=True)
12
 
 
24
  @dataclass
25
  class PropertyConfig:
26
  """Root configuration for image properties, including nested image settings."""
27
+ image_settings: ImageSettings = field(default_factory=ImageSettings, metadata={"label": "Image Settings"})
28
  description: str = field(default="", metadata={"label": "Description"})
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
31
  """
32
+ Processes image metadata by calling the agnostic `transfer_metadata` helper.
 
 
 
 
 
 
33
  """
34
+ if not image_data or not hasattr(image_data, 'path'):
35
+ return [gr.skip()] * len(output_fields)
36
 
37
  metadata = extract_metadata(image_data, only_custom_metadata=True)
38
+ if not metadata:
39
+ return [gr.skip()] * len(output_fields)
40
+
41
+ # --- UI-Specific Configuration ---
42
+ # Define the map that tells the helper how to process the PropertySheet.
43
+ sheet_map = {
44
+ id(property_sheet): {
45
+ "type": PropertyConfig,
46
+ "prefixes": [] # No prefixes needed for this simple case
47
+ }
48
+ }
49
 
50
+ # Call the agnostic helper function to do the heavy lifting.
51
+ return transfer_metadata(
52
+ output_fields=output_fields,
53
+ metadata=metadata,
54
+ propertysheet_map=sheet_map
55
+ )
56
 
57
+ def save_image_with_metadata(
58
+ image_data: Any,
59
+ sheet_state: PropertyConfig,
60
+ model: str,
61
+ f_number: str,
62
+ iso: str,
63
+ s_churn_val: float,
64
+ description: str
65
+ ) -> str | None:
66
  """
67
+ Saves an image with updated metadata, merging data from the PropertySheet
68
+ and individual UI components.
69
+ This example deals with the PropertySheet component and the individual gradio components.
70
+ Since they have the same labels here, we'll simply replace the metadata with each other's values.
 
 
 
 
71
  """
72
  if not image_data:
73
  return None
74
+
75
+
76
+ metadata = flatten_dataclass_with_labels(sheet_state)
77
+ individual_component_values = {
78
+ "Model": model,
79
+ "FNumber": f_number,
80
+ "ISOSpeedRatings": iso,
81
+ "Schurn": s_churn_val,
82
+ "Description": description
83
+ }
84
+
85
+ metadata["Image Settings - Model"] = individual_component_values["Model"]
86
+ metadata["Image Settings - FNumber"] = individual_component_values["FNumber"]
87
+ metadata["Image Settings - ISOSpeedRatings"] = individual_component_values["ISOSpeedRatings"]
88
+ metadata["Image Settings - Schurn"] = individual_component_values["Schurn"]
89
+ metadata["Description"] = individual_component_values["Description"]
90
+
91
+ final_metadata = {str(key): value for key, value in metadata.items()}
92
 
93
  new_filepath = output_dir / "image_with_meta.png"
94
+ add_metadata(image_data, new_filepath, final_metadata)
 
95
 
96
  return str(new_filepath)
97
 
 
198
  )
199
  save_button.click(
200
  save_image_with_metadata,
201
+ inputs=[img_custom, property_sheet, *input_fields.values()],
202
  outputs=[saved_file_output]
203
  )
204
 
space.py CHANGED
@@ -44,10 +44,9 @@ import gradio as gr
44
  from gradio_imagemeta import ImageMeta
45
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
46
  from gradio_propertysheet import PropertySheet
47
- from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
48
  from pathlib import Path
49
 
50
-
51
  output_dir = Path("outputs")
52
  output_dir.mkdir(exist_ok=True)
53
 
@@ -65,101 +64,74 @@ class ImageSettings:
65
  @dataclass
66
  class PropertyConfig:
67
  \"\"\"Root configuration for image properties, including nested image settings.\"\"\"
68
- image_settings: ImageSettings = field(default_factory=ImageSettings)
69
  description: str = field(default="", metadata={"label": "Description"})
70
 
71
- def infer_type(s: str):
72
- \"\"\"
73
- Infers and converts a string to the most likely data type.
74
-
75
- It attempts conversions in the following order:
76
- 1. Integer
77
- 2. Float
78
- 3. Boolean (case-insensitive 'true' or 'false')
79
- If all conversions fail, it returns the original string.
80
-
81
- Args:
82
- s: The input string to be converted.
83
-
84
- Returns:
85
- The converted value (int, float, bool) or the original string.
86
- \"\"\"
87
- if not isinstance(s, str):
88
- # If the input is not a string, return it as is.
89
- return s
90
-
91
- # 1. Try to convert to an integer
92
- try:
93
- return int(s)
94
- except ValueError:
95
- # Not an integer, continue...
96
- pass
97
-
98
- # 2. Try to convert to a float
99
- try:
100
- return float(s)
101
- except ValueError:
102
- # Not a float, continue...
103
- pass
104
-
105
- # 3. Check for a boolean value
106
- # This explicit check is important because bool('False') evaluates to True.
107
- s_lower = s.lower()
108
- if s_lower == 'true':
109
- return True
110
- if s_lower == 'false':
111
- return False
112
-
113
- # 4. If nothing else worked, return the original string
114
- return s
115
-
116
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
117
  \"\"\"
118
- Processes image metadata and maps it to output components.
119
-
120
- Args:
121
- image_data: ImageMeta object containing image data and metadata, or None.
122
-
123
- Returns:
124
- A list of values for output components (Textbox, Slider, or PropertySheet instances).
125
  \"\"\"
126
- if not image_data:
127
- return [gr.Textbox(value="") for _ in output_fields]
128
 
129
  metadata = extract_metadata(image_data, only_custom_metadata=True)
130
- dataclass_fields = build_dataclass_fields(PropertyConfig)
131
- raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
132
-
133
- output_values = [gr.skip()] * len(output_fields)
134
- for i, (component, value) in enumerate(zip(output_fields, raw_values)):
135
- if hasattr(component, 'root_label'):
136
- output_values[i] = create_dataclass_instance(PropertyConfig, value)
137
- else:
138
- output_values[i] = gr.update(value=infer_type(value))
 
 
139
 
140
- return output_values
 
 
 
 
 
141
 
142
- def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
 
 
 
 
 
 
 
 
143
  \"\"\"
144
- Saves an image with updated metadata to a file.
145
-
146
- Args:
147
- image_data: Input image data (e.g., file path or PIL Image).
148
- *inputs: Variable number of input values from UI components (Textbox, Slider).
149
-
150
- Returns:
151
- The file path of the saved image, or None if no image is provided.
152
  \"\"\"
153
  if not image_data:
154
  return None
155
-
156
- params = list(inputs)
157
- image_params = dict(zip(input_fields.keys(), params))
158
- metadata = {label: image_params.get(label, "") for label in image_params.keys()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
  new_filepath = output_dir / "image_with_meta.png"
161
-
162
- add_metadata(image_data, new_filepath, metadata)
163
 
164
  return str(new_filepath)
165
 
@@ -266,7 +238,7 @@ with gr.Blocks() as demo:
266
  )
267
  save_button.click(
268
  save_image_with_metadata,
269
- inputs=[img_custom, *input_fields.values()],
270
  outputs=[saved_file_output]
271
  )
272
 
 
44
  from gradio_imagemeta import ImageMeta
45
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
46
  from gradio_propertysheet import PropertySheet
47
+ from gradio_propertysheet.helpers import flatten_dataclass_with_labels
48
  from pathlib import Path
49
 
 
50
  output_dir = Path("outputs")
51
  output_dir.mkdir(exist_ok=True)
52
 
 
64
  @dataclass
65
  class PropertyConfig:
66
  \"\"\"Root configuration for image properties, including nested image settings.\"\"\"
67
+ image_settings: ImageSettings = field(default_factory=ImageSettings, metadata={"label": "Image Settings"})
68
  description: str = field(default="", metadata={"label": "Description"})
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
71
  \"\"\"
72
+ Processes image metadata by calling the agnostic `transfer_metadata` helper.
 
 
 
 
 
 
73
  \"\"\"
74
+ if not image_data or not hasattr(image_data, 'path'):
75
+ return [gr.skip()] * len(output_fields)
76
 
77
  metadata = extract_metadata(image_data, only_custom_metadata=True)
78
+ if not metadata:
79
+ return [gr.skip()] * len(output_fields)
80
+
81
+ # --- UI-Specific Configuration ---
82
+ # Define the map that tells the helper how to process the PropertySheet.
83
+ sheet_map = {
84
+ id(property_sheet): {
85
+ "type": PropertyConfig,
86
+ "prefixes": [] # No prefixes needed for this simple case
87
+ }
88
+ }
89
 
90
+ # Call the agnostic helper function to do the heavy lifting.
91
+ return transfer_metadata(
92
+ output_fields=output_fields,
93
+ metadata=metadata,
94
+ propertysheet_map=sheet_map
95
+ )
96
 
97
+ def save_image_with_metadata(
98
+ image_data: Any,
99
+ sheet_state: PropertyConfig,
100
+ model: str,
101
+ f_number: str,
102
+ iso: str,
103
+ s_churn_val: float,
104
+ description: str
105
+ ) -> str | None:
106
  \"\"\"
107
+ Saves an image with updated metadata, merging data from the PropertySheet
108
+ and individual UI components.
109
+ This example deals with the PropertySheet component and the individual gradio components.
110
+ Since they have the same labels here, we'll simply replace the metadata with each other's values.
 
 
 
 
111
  \"\"\"
112
  if not image_data:
113
  return None
114
+
115
+
116
+ metadata = flatten_dataclass_with_labels(sheet_state)
117
+ individual_component_values = {
118
+ "Model": model,
119
+ "FNumber": f_number,
120
+ "ISOSpeedRatings": iso,
121
+ "Schurn": s_churn_val,
122
+ "Description": description
123
+ }
124
+
125
+ metadata["Image Settings - Model"] = individual_component_values["Model"]
126
+ metadata["Image Settings - FNumber"] = individual_component_values["FNumber"]
127
+ metadata["Image Settings - ISOSpeedRatings"] = individual_component_values["ISOSpeedRatings"]
128
+ metadata["Image Settings - Schurn"] = individual_component_values["Schurn"]
129
+ metadata["Description"] = individual_component_values["Description"]
130
+
131
+ final_metadata = {str(key): value for key, value in metadata.items()}
132
 
133
  new_filepath = output_dir / "image_with_meta.png"
134
+ add_metadata(image_data, new_filepath, final_metadata)
 
135
 
136
  return str(new_filepath)
137
 
 
238
  )
239
  save_button.click(
240
  save_image_with_metadata,
241
+ inputs=[img_custom, property_sheet, *input_fields.values()],
242
  outputs=[saved_file_output]
243
  )
244
 
src/README.md CHANGED
@@ -10,7 +10,7 @@ app_file: space.py
10
  ---
11
 
12
  # `gradio_imagemeta`
13
- <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.4%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_imagemeta"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_imagemeta'>Component GitHub Code</a></span></p>
14
 
15
  Image Preview with Metadata for Gradio Interface
16
 
@@ -41,10 +41,9 @@ import gradio as gr
41
  from gradio_imagemeta import ImageMeta
42
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
43
  from gradio_propertysheet import PropertySheet
44
- from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
45
  from pathlib import Path
46
 
47
-
48
  output_dir = Path("outputs")
49
  output_dir.mkdir(exist_ok=True)
50
 
@@ -62,101 +61,74 @@ class ImageSettings:
62
  @dataclass
63
  class PropertyConfig:
64
  """Root configuration for image properties, including nested image settings."""
65
- image_settings: ImageSettings = field(default_factory=ImageSettings)
66
  description: str = field(default="", metadata={"label": "Description"})
67
 
68
- def infer_type(s: str):
69
- """
70
- Infers and converts a string to the most likely data type.
71
-
72
- It attempts conversions in the following order:
73
- 1. Integer
74
- 2. Float
75
- 3. Boolean (case-insensitive 'true' or 'false')
76
- If all conversions fail, it returns the original string.
77
-
78
- Args:
79
- s: The input string to be converted.
80
-
81
- Returns:
82
- The converted value (int, float, bool) or the original string.
83
- """
84
- if not isinstance(s, str):
85
- # If the input is not a string, return it as is.
86
- return s
87
-
88
- # 1. Try to convert to an integer
89
- try:
90
- return int(s)
91
- except ValueError:
92
- # Not an integer, continue...
93
- pass
94
-
95
- # 2. Try to convert to a float
96
- try:
97
- return float(s)
98
- except ValueError:
99
- # Not a float, continue...
100
- pass
101
-
102
- # 3. Check for a boolean value
103
- # This explicit check is important because bool('False') evaluates to True.
104
- s_lower = s.lower()
105
- if s_lower == 'true':
106
- return True
107
- if s_lower == 'false':
108
- return False
109
-
110
- # 4. If nothing else worked, return the original string
111
- return s
112
-
113
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
114
  """
115
- Processes image metadata and maps it to output components.
116
-
117
- Args:
118
- image_data: ImageMeta object containing image data and metadata, or None.
119
-
120
- Returns:
121
- A list of values for output components (Textbox, Slider, or PropertySheet instances).
122
  """
123
- if not image_data:
124
- return [gr.Textbox(value="") for _ in output_fields]
125
 
126
  metadata = extract_metadata(image_data, only_custom_metadata=True)
127
- dataclass_fields = build_dataclass_fields(PropertyConfig)
128
- raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
129
-
130
- output_values = [gr.skip()] * len(output_fields)
131
- for i, (component, value) in enumerate(zip(output_fields, raw_values)):
132
- if hasattr(component, 'root_label'):
133
- output_values[i] = create_dataclass_instance(PropertyConfig, value)
134
- else:
135
- output_values[i] = gr.update(value=infer_type(value))
 
 
136
 
137
- return output_values
 
 
 
 
 
138
 
139
- def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
 
 
 
 
 
 
 
 
140
  """
141
- Saves an image with updated metadata to a file.
142
-
143
- Args:
144
- image_data: Input image data (e.g., file path or PIL Image).
145
- *inputs: Variable number of input values from UI components (Textbox, Slider).
146
-
147
- Returns:
148
- The file path of the saved image, or None if no image is provided.
149
  """
150
  if not image_data:
151
  return None
152
-
153
- params = list(inputs)
154
- image_params = dict(zip(input_fields.keys(), params))
155
- metadata = {label: image_params.get(label, "") for label in image_params.keys()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
  new_filepath = output_dir / "image_with_meta.png"
158
-
159
- add_metadata(image_data, new_filepath, metadata)
160
 
161
  return str(new_filepath)
162
 
@@ -263,7 +235,7 @@ with gr.Blocks() as demo:
263
  )
264
  save_button.click(
265
  save_image_with_metadata,
266
- inputs=[img_custom, *input_fields.values()],
267
  outputs=[saved_file_output]
268
  )
269
 
 
10
  ---
11
 
12
  # `gradio_imagemeta`
13
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.6%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_imagemeta"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_imagemeta'>Component GitHub Code</a></span></p>
14
 
15
  Image Preview with Metadata for Gradio Interface
16
 
 
41
  from gradio_imagemeta import ImageMeta
42
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
43
  from gradio_propertysheet import PropertySheet
44
+ from gradio_propertysheet.helpers import flatten_dataclass_with_labels
45
  from pathlib import Path
46
 
 
47
  output_dir = Path("outputs")
48
  output_dir.mkdir(exist_ok=True)
49
 
 
61
  @dataclass
62
  class PropertyConfig:
63
  """Root configuration for image properties, including nested image settings."""
64
+ image_settings: ImageSettings = field(default_factory=ImageSettings, metadata={"label": "Image Settings"})
65
  description: str = field(default="", metadata={"label": "Description"})
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
68
  """
69
+ Processes image metadata by calling the agnostic `transfer_metadata` helper.
 
 
 
 
 
 
70
  """
71
+ if not image_data or not hasattr(image_data, 'path'):
72
+ return [gr.skip()] * len(output_fields)
73
 
74
  metadata = extract_metadata(image_data, only_custom_metadata=True)
75
+ if not metadata:
76
+ return [gr.skip()] * len(output_fields)
77
+
78
+ # --- UI-Specific Configuration ---
79
+ # Define the map that tells the helper how to process the PropertySheet.
80
+ sheet_map = {
81
+ id(property_sheet): {
82
+ "type": PropertyConfig,
83
+ "prefixes": [] # No prefixes needed for this simple case
84
+ }
85
+ }
86
 
87
+ # Call the agnostic helper function to do the heavy lifting.
88
+ return transfer_metadata(
89
+ output_fields=output_fields,
90
+ metadata=metadata,
91
+ propertysheet_map=sheet_map
92
+ )
93
 
94
+ def save_image_with_metadata(
95
+ image_data: Any,
96
+ sheet_state: PropertyConfig,
97
+ model: str,
98
+ f_number: str,
99
+ iso: str,
100
+ s_churn_val: float,
101
+ description: str
102
+ ) -> str | None:
103
  """
104
+ Saves an image with updated metadata, merging data from the PropertySheet
105
+ and individual UI components.
106
+ This example deals with the PropertySheet component and the individual gradio components.
107
+ Since they have the same labels here, we'll simply replace the metadata with each other's values.
 
 
 
 
108
  """
109
  if not image_data:
110
  return None
111
+
112
+
113
+ metadata = flatten_dataclass_with_labels(sheet_state)
114
+ individual_component_values = {
115
+ "Model": model,
116
+ "FNumber": f_number,
117
+ "ISOSpeedRatings": iso,
118
+ "Schurn": s_churn_val,
119
+ "Description": description
120
+ }
121
+
122
+ metadata["Image Settings - Model"] = individual_component_values["Model"]
123
+ metadata["Image Settings - FNumber"] = individual_component_values["FNumber"]
124
+ metadata["Image Settings - ISOSpeedRatings"] = individual_component_values["ISOSpeedRatings"]
125
+ metadata["Image Settings - Schurn"] = individual_component_values["Schurn"]
126
+ metadata["Description"] = individual_component_values["Description"]
127
+
128
+ final_metadata = {str(key): value for key, value in metadata.items()}
129
 
130
  new_filepath = output_dir / "image_with_meta.png"
131
+ add_metadata(image_data, new_filepath, final_metadata)
 
132
 
133
  return str(new_filepath)
134
 
 
235
  )
236
  save_button.click(
237
  save_image_with_metadata,
238
+ inputs=[img_custom, property_sheet, *input_fields.values()],
239
  outputs=[saved_file_output]
240
  )
241
 
src/backend/gradio_imagemeta/helpers.py CHANGED
@@ -1,10 +1,57 @@
1
- from dataclasses import fields
2
  import os
3
  from pathlib import Path
4
- from typing import Any, Dict, List
5
  from PIL import Image, PngImagePlugin, ExifTags
6
  import numpy as np
7
  from gradio import image_utils
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  def extract_metadata(image_data: str | Path | Image.Image | np.ndarray | None, only_custom_metadata: bool = True) -> Dict[str, Any]:
10
  """
@@ -143,46 +190,99 @@ def add_metadata(image_data: str | Path | Image.Image | np.ndarray, save_path: s
143
  return True
144
  except Exception:
145
  return False
146
-
147
- def transfer_metadata(output_fields: List[Any], metadata: Dict[str, Any], dataclass_fields: Dict[str, str]) -> List[Any]:
 
 
 
 
148
  """
149
- Maps metadata to a list of output components based on their labels.
 
 
 
 
 
 
150
 
151
  Args:
152
- output_fields: List of components (e.g., Textbox, PropertySheet).
153
- metadata: Dictionary of extracted image metadata.
154
- dataclass_fields: Dictionary mapping component labels (e.g., 'Model') to field paths (e.g., 'image_settings.model' or 'description').
 
 
 
 
 
 
155
 
156
  Returns:
157
- List of values (strings for Textbox, nested dictionary for PropertySheet) in the same order as output_fields.
 
158
  """
 
 
 
159
  output_values = [None] * len(output_fields)
160
- for i, component in enumerate(output_fields):
161
- label = getattr(component, 'label', None)
162
-
163
- # Check if the component is a PropertySheet via root_label attribute
164
- is_property_sheet = hasattr(component, 'root_label')
165
- if is_property_sheet:
166
- # Create nested dictionary for PropertySheet
167
- updated_config = {}
168
- for dataclass_label, field_path in dataclass_fields.items():
169
- value = str(metadata.get(dataclass_label, 'None'))
170
- value = None if value == 'None' else value
171
- # Split field_path into parts (e.g., 'image_settings.model' -> ['image_settings', 'model'])
172
- parts = field_path.split('.')
173
- # Build nested structure in dictionary
174
- current = updated_config
175
- for part in parts[:-1]:
176
- if part not in current:
177
- current[part] = {}
178
- current = current[part]
179
- # Assign value to final field
180
- current[parts[-1]] = value
181
- output_values[i] = updated_config
182
- else:
183
- # For other components (e.g., Textbox), assign raw value
184
- value = str(metadata.get(label, None)) if label else None
185
- value = None if value == 'None' else value
186
- output_values[i] = value
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  return output_values
 
1
+ from dataclasses import fields, is_dataclass
2
  import os
3
  from pathlib import Path
4
+ from typing import Any, Dict, List, Optional
5
  from PIL import Image, PngImagePlugin, ExifTags
6
  import numpy as np
7
  from gradio import image_utils
8
+ from gradio_propertysheet.helpers import build_path_to_metadata_key_map
9
+
10
+ def infer_type(s: str):
11
+ """
12
+ Infers and converts a string to the most likely data type.
13
+
14
+ It attempts conversions in the following order:
15
+ 1. Integer
16
+ 2. Float
17
+ 3. Boolean (case-insensitive 'true' or 'false')
18
+ If all conversions fail, it returns the original string.
19
+
20
+ Args:
21
+ s: The input string to be converted.
22
+
23
+ Returns:
24
+ The converted value (int, float, bool) or the original string.
25
+ """
26
+ if not isinstance(s, str):
27
+ # If the input is not a string, return it as is.
28
+ return s
29
+
30
+ # 1. Try to convert to an integer
31
+ try:
32
+ return int(s)
33
+ except ValueError:
34
+ # Not an integer, continue...
35
+ pass
36
+
37
+ # 2. Try to convert to a float
38
+ try:
39
+ return float(s)
40
+ except ValueError:
41
+ # Not a float, continue...
42
+ pass
43
+
44
+ # 3. Check for a boolean value
45
+ # This explicit check is important because bool('False') evaluates to True.
46
+ s_lower = s.lower()
47
+ if s_lower == 'true':
48
+ return True
49
+ if s_lower == 'false':
50
+ return False
51
+
52
+ # 4. If nothing else worked, return the original string
53
+ return s
54
+
55
 
56
  def extract_metadata(image_data: str | Path | Image.Image | np.ndarray | None, only_custom_metadata: bool = True) -> Dict[str, Any]:
57
  """
 
190
  return True
191
  except Exception:
192
  return False
193
+
194
+ def transfer_metadata(
195
+ output_fields: List[Any],
196
+ metadata: Dict[str, Any],
197
+ propertysheet_map: Optional[Dict[int, Dict[str, Any]]] = None
198
+ ) -> List[Any]:
199
  """
200
+ Maps a flat metadata dictionary to a list of Gradio UI components, including
201
+ complex, nested PropertySheets.
202
+
203
+ This function is UI-agnostic. It populates standard components based on their
204
+ labels. For PropertySheet components, it uses a provided map to understand
205
+ which dataclass type to construct and which metadata keys to use for building
206
+ the necessary prefixes to find the correct values.
207
 
208
  Args:
209
+ output_fields (List[Any]): The list of Gradio components to be updated.
210
+ metadata (Dict[str, Any]): The flat dictionary of metadata extracted from an image.
211
+ propertysheet_map (Optional[Dict[int, Dict[str, Any]]]):
212
+ A dictionary mapping the `id()` of each PropertySheet component to its
213
+ configuration. The configuration dictionary should contain:
214
+ - "type" (Type): The dataclass type to construct (e.g., `PropertyConfig`).
215
+ - "prefixes" (List[str]): A list of keys from the `metadata` dictionary
216
+ whose values should be used to build the metadata key prefix.
217
+ Example: `{"type": MyDataClass, "prefixes": ["Restorer", "Image Restore Engine"]}`
218
 
219
  Returns:
220
+ List[Any]: A list of `gr.update` objects or `gr.skip()` values, ready to be
221
+ returned by a Gradio event listener function.
222
  """
223
+ if propertysheet_map is None:
224
+ propertysheet_map = {}
225
+
226
  output_values = [None] * len(output_fields)
227
+ component_to_index = {id(comp): i for i, comp in enumerate(output_fields)}
228
+
229
+ base_label_map = {}
230
+ for key, value in metadata.items():
231
+ base_label = key.rsplit(' - ', 1)[-1]
232
+ base_label_map[base_label] = value
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
+ for component in output_fields:
235
+ comp_id = id(component)
236
+ output_index = component_to_index.get(comp_id)
237
+ if output_index is None:
238
+ continue
239
+
240
+ # --- Logic for PropertySheets ---
241
+ if comp_id in propertysheet_map:
242
+ sheet_info = propertysheet_map[comp_id]
243
+ dc_type = sheet_info.get("type")
244
+ prefix_keys = sheet_info.get("prefixes", [])
245
+
246
+ # Build the list of actual prefix strings by looking up their values in the metadata
247
+ prefix_values = [metadata.get(key, "") for key in prefix_keys]
248
+ prefix_values = [p for p in prefix_values if p]
249
+
250
+ if not dc_type or not is_dataclass(dc_type):
251
+ continue
252
+
253
+ # Build the map from the dataclass structure to the expected metadata keys
254
+ path_to_key_map = build_path_to_metadata_key_map(dc_type, prefix_values)
255
+
256
+ # Get the base instance to start populating
257
+ instance_to_populate = getattr(component, '_dataclass_value', None)
258
+ if not is_dataclass(instance_to_populate):
259
+ instance_to_populate = dc_type() # Create a new instance if the current one is invalid
260
+
261
+ # Populate the instance by iterating through the path map
262
+ for path, metadata_key in path_to_key_map.items():
263
+ if metadata_key in metadata:
264
+ value_from_meta = metadata[metadata_key]
265
+
266
+ parts = path.split('.')
267
+ obj_to_set = instance_to_populate
268
+ try:
269
+ for part in parts[:-1]:
270
+ obj_to_set = getattr(obj_to_set, part)
271
+
272
+ final_field_name = parts[-1]
273
+ converted_value = infer_type(value_from_meta)
274
+ setattr(obj_to_set, final_field_name, converted_value)
275
+ except (AttributeError, KeyError, ValueError, TypeError) as e:
276
+ print(f"Warning (transfer_metadata): Could not set value for path '{path}'. Error: {e}")
277
+
278
+ output_values[output_index] = value=instance_to_populate
279
+
280
+ # --- Logic for Standard Gradio Components ---
281
+ else:
282
+ label = getattr(component, 'label', None)
283
+ if label and label in base_label_map:
284
+ value = base_label_map[label]
285
+ value = None if value == 'None' else value
286
+ output_values[output_index] = infer_type(value)
287
+
288
  return output_values
src/demo/app.py CHANGED
@@ -4,10 +4,9 @@ import gradio as gr
4
  from gradio_imagemeta import ImageMeta
5
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
6
  from gradio_propertysheet import PropertySheet
7
- from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
8
  from pathlib import Path
9
 
10
-
11
  output_dir = Path("outputs")
12
  output_dir.mkdir(exist_ok=True)
13
 
@@ -25,101 +24,74 @@ class ImageSettings:
25
  @dataclass
26
  class PropertyConfig:
27
  """Root configuration for image properties, including nested image settings."""
28
- image_settings: ImageSettings = field(default_factory=ImageSettings)
29
  description: str = field(default="", metadata={"label": "Description"})
30
 
31
- def infer_type(s: str):
32
- """
33
- Infers and converts a string to the most likely data type.
34
-
35
- It attempts conversions in the following order:
36
- 1. Integer
37
- 2. Float
38
- 3. Boolean (case-insensitive 'true' or 'false')
39
- If all conversions fail, it returns the original string.
40
-
41
- Args:
42
- s: The input string to be converted.
43
-
44
- Returns:
45
- The converted value (int, float, bool) or the original string.
46
- """
47
- if not isinstance(s, str):
48
- # If the input is not a string, return it as is.
49
- return s
50
-
51
- # 1. Try to convert to an integer
52
- try:
53
- return int(s)
54
- except ValueError:
55
- # Not an integer, continue...
56
- pass
57
-
58
- # 2. Try to convert to a float
59
- try:
60
- return float(s)
61
- except ValueError:
62
- # Not a float, continue...
63
- pass
64
-
65
- # 3. Check for a boolean value
66
- # This explicit check is important because bool('False') evaluates to True.
67
- s_lower = s.lower()
68
- if s_lower == 'true':
69
- return True
70
- if s_lower == 'false':
71
- return False
72
-
73
- # 4. If nothing else worked, return the original string
74
- return s
75
-
76
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
77
  """
78
- Processes image metadata and maps it to output components.
79
-
80
- Args:
81
- image_data: ImageMeta object containing image data and metadata, or None.
82
-
83
- Returns:
84
- A list of values for output components (Textbox, Slider, or PropertySheet instances).
85
  """
86
- if not image_data:
87
- return [gr.Textbox(value="") for _ in output_fields]
88
 
89
  metadata = extract_metadata(image_data, only_custom_metadata=True)
90
- dataclass_fields = build_dataclass_fields(PropertyConfig)
91
- raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
92
-
93
- output_values = [gr.skip()] * len(output_fields)
94
- for i, (component, value) in enumerate(zip(output_fields, raw_values)):
95
- if hasattr(component, 'root_label'):
96
- output_values[i] = create_dataclass_instance(PropertyConfig, value)
97
- else:
98
- output_values[i] = gr.update(value=infer_type(value))
 
 
99
 
100
- return output_values
 
 
 
 
 
101
 
102
- def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
 
 
 
 
 
 
 
 
103
  """
104
- Saves an image with updated metadata to a file.
105
-
106
- Args:
107
- image_data: Input image data (e.g., file path or PIL Image).
108
- *inputs: Variable number of input values from UI components (Textbox, Slider).
109
-
110
- Returns:
111
- The file path of the saved image, or None if no image is provided.
112
  """
113
  if not image_data:
114
  return None
115
-
116
- params = list(inputs)
117
- image_params = dict(zip(input_fields.keys(), params))
118
- metadata = {label: image_params.get(label, "") for label in image_params.keys()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
  new_filepath = output_dir / "image_with_meta.png"
121
-
122
- add_metadata(image_data, new_filepath, metadata)
123
 
124
  return str(new_filepath)
125
 
@@ -226,7 +198,7 @@ with gr.Blocks() as demo:
226
  )
227
  save_button.click(
228
  save_image_with_metadata,
229
- inputs=[img_custom, *input_fields.values()],
230
  outputs=[saved_file_output]
231
  )
232
 
 
4
  from gradio_imagemeta import ImageMeta
5
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
6
  from gradio_propertysheet import PropertySheet
7
+ from gradio_propertysheet.helpers import flatten_dataclass_with_labels
8
  from pathlib import Path
9
 
 
10
  output_dir = Path("outputs")
11
  output_dir.mkdir(exist_ok=True)
12
 
 
24
  @dataclass
25
  class PropertyConfig:
26
  """Root configuration for image properties, including nested image settings."""
27
+ image_settings: ImageSettings = field(default_factory=ImageSettings, metadata={"label": "Image Settings"})
28
  description: str = field(default="", metadata={"label": "Description"})
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
31
  """
32
+ Processes image metadata by calling the agnostic `transfer_metadata` helper.
 
 
 
 
 
 
33
  """
34
+ if not image_data or not hasattr(image_data, 'path'):
35
+ return [gr.skip()] * len(output_fields)
36
 
37
  metadata = extract_metadata(image_data, only_custom_metadata=True)
38
+ if not metadata:
39
+ return [gr.skip()] * len(output_fields)
40
+
41
+ # --- UI-Specific Configuration ---
42
+ # Define the map that tells the helper how to process the PropertySheet.
43
+ sheet_map = {
44
+ id(property_sheet): {
45
+ "type": PropertyConfig,
46
+ "prefixes": [] # No prefixes needed for this simple case
47
+ }
48
+ }
49
 
50
+ # Call the agnostic helper function to do the heavy lifting.
51
+ return transfer_metadata(
52
+ output_fields=output_fields,
53
+ metadata=metadata,
54
+ propertysheet_map=sheet_map
55
+ )
56
 
57
+ def save_image_with_metadata(
58
+ image_data: Any,
59
+ sheet_state: PropertyConfig,
60
+ model: str,
61
+ f_number: str,
62
+ iso: str,
63
+ s_churn_val: float,
64
+ description: str
65
+ ) -> str | None:
66
  """
67
+ Saves an image with updated metadata, merging data from the PropertySheet
68
+ and individual UI components.
69
+ This example deals with the PropertySheet component and the individual gradio components.
70
+ Since they have the same labels here, we'll simply replace the metadata with each other's values.
 
 
 
 
71
  """
72
  if not image_data:
73
  return None
74
+
75
+
76
+ metadata = flatten_dataclass_with_labels(sheet_state)
77
+ individual_component_values = {
78
+ "Model": model,
79
+ "FNumber": f_number,
80
+ "ISOSpeedRatings": iso,
81
+ "Schurn": s_churn_val,
82
+ "Description": description
83
+ }
84
+
85
+ metadata["Image Settings - Model"] = individual_component_values["Model"]
86
+ metadata["Image Settings - FNumber"] = individual_component_values["FNumber"]
87
+ metadata["Image Settings - ISOSpeedRatings"] = individual_component_values["ISOSpeedRatings"]
88
+ metadata["Image Settings - Schurn"] = individual_component_values["Schurn"]
89
+ metadata["Description"] = individual_component_values["Description"]
90
+
91
+ final_metadata = {str(key): value for key, value in metadata.items()}
92
 
93
  new_filepath = output_dir / "image_with_meta.png"
94
+ add_metadata(image_data, new_filepath, final_metadata)
 
95
 
96
  return str(new_filepath)
97
 
 
198
  )
199
  save_button.click(
200
  save_image_with_metadata,
201
+ inputs=[img_custom, property_sheet, *input_fields.values()],
202
  outputs=[saved_file_output]
203
  )
204
 
src/demo/space.py CHANGED
@@ -44,10 +44,9 @@ import gradio as gr
44
  from gradio_imagemeta import ImageMeta
45
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
46
  from gradio_propertysheet import PropertySheet
47
- from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
48
  from pathlib import Path
49
 
50
-
51
  output_dir = Path("outputs")
52
  output_dir.mkdir(exist_ok=True)
53
 
@@ -65,101 +64,74 @@ class ImageSettings:
65
  @dataclass
66
  class PropertyConfig:
67
  \"\"\"Root configuration for image properties, including nested image settings.\"\"\"
68
- image_settings: ImageSettings = field(default_factory=ImageSettings)
69
  description: str = field(default="", metadata={"label": "Description"})
70
 
71
- def infer_type(s: str):
72
- \"\"\"
73
- Infers and converts a string to the most likely data type.
74
-
75
- It attempts conversions in the following order:
76
- 1. Integer
77
- 2. Float
78
- 3. Boolean (case-insensitive 'true' or 'false')
79
- If all conversions fail, it returns the original string.
80
-
81
- Args:
82
- s: The input string to be converted.
83
-
84
- Returns:
85
- The converted value (int, float, bool) or the original string.
86
- \"\"\"
87
- if not isinstance(s, str):
88
- # If the input is not a string, return it as is.
89
- return s
90
-
91
- # 1. Try to convert to an integer
92
- try:
93
- return int(s)
94
- except ValueError:
95
- # Not an integer, continue...
96
- pass
97
-
98
- # 2. Try to convert to a float
99
- try:
100
- return float(s)
101
- except ValueError:
102
- # Not a float, continue...
103
- pass
104
-
105
- # 3. Check for a boolean value
106
- # This explicit check is important because bool('False') evaluates to True.
107
- s_lower = s.lower()
108
- if s_lower == 'true':
109
- return True
110
- if s_lower == 'false':
111
- return False
112
-
113
- # 4. If nothing else worked, return the original string
114
- return s
115
-
116
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
117
  \"\"\"
118
- Processes image metadata and maps it to output components.
119
-
120
- Args:
121
- image_data: ImageMeta object containing image data and metadata, or None.
122
-
123
- Returns:
124
- A list of values for output components (Textbox, Slider, or PropertySheet instances).
125
  \"\"\"
126
- if not image_data:
127
- return [gr.Textbox(value="") for _ in output_fields]
128
 
129
  metadata = extract_metadata(image_data, only_custom_metadata=True)
130
- dataclass_fields = build_dataclass_fields(PropertyConfig)
131
- raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
132
-
133
- output_values = [gr.skip()] * len(output_fields)
134
- for i, (component, value) in enumerate(zip(output_fields, raw_values)):
135
- if hasattr(component, 'root_label'):
136
- output_values[i] = create_dataclass_instance(PropertyConfig, value)
137
- else:
138
- output_values[i] = gr.update(value=infer_type(value))
 
 
139
 
140
- return output_values
 
 
 
 
 
141
 
142
- def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
 
 
 
 
 
 
 
 
143
  \"\"\"
144
- Saves an image with updated metadata to a file.
145
-
146
- Args:
147
- image_data: Input image data (e.g., file path or PIL Image).
148
- *inputs: Variable number of input values from UI components (Textbox, Slider).
149
-
150
- Returns:
151
- The file path of the saved image, or None if no image is provided.
152
  \"\"\"
153
  if not image_data:
154
  return None
155
-
156
- params = list(inputs)
157
- image_params = dict(zip(input_fields.keys(), params))
158
- metadata = {label: image_params.get(label, "") for label in image_params.keys()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
  new_filepath = output_dir / "image_with_meta.png"
161
-
162
- add_metadata(image_data, new_filepath, metadata)
163
 
164
  return str(new_filepath)
165
 
@@ -266,7 +238,7 @@ with gr.Blocks() as demo:
266
  )
267
  save_button.click(
268
  save_image_with_metadata,
269
- inputs=[img_custom, *input_fields.values()],
270
  outputs=[saved_file_output]
271
  )
272
 
 
44
  from gradio_imagemeta import ImageMeta
45
  from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
46
  from gradio_propertysheet import PropertySheet
47
+ from gradio_propertysheet.helpers import flatten_dataclass_with_labels
48
  from pathlib import Path
49
 
 
50
  output_dir = Path("outputs")
51
  output_dir.mkdir(exist_ok=True)
52
 
 
64
  @dataclass
65
  class PropertyConfig:
66
  \"\"\"Root configuration for image properties, including nested image settings.\"\"\"
67
+ image_settings: ImageSettings = field(default_factory=ImageSettings, metadata={"label": "Image Settings"})
68
  description: str = field(default="", metadata={"label": "Description"})
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
71
  \"\"\"
72
+ Processes image metadata by calling the agnostic `transfer_metadata` helper.
 
 
 
 
 
 
73
  \"\"\"
74
+ if not image_data or not hasattr(image_data, 'path'):
75
+ return [gr.skip()] * len(output_fields)
76
 
77
  metadata = extract_metadata(image_data, only_custom_metadata=True)
78
+ if not metadata:
79
+ return [gr.skip()] * len(output_fields)
80
+
81
+ # --- UI-Specific Configuration ---
82
+ # Define the map that tells the helper how to process the PropertySheet.
83
+ sheet_map = {
84
+ id(property_sheet): {
85
+ "type": PropertyConfig,
86
+ "prefixes": [] # No prefixes needed for this simple case
87
+ }
88
+ }
89
 
90
+ # Call the agnostic helper function to do the heavy lifting.
91
+ return transfer_metadata(
92
+ output_fields=output_fields,
93
+ metadata=metadata,
94
+ propertysheet_map=sheet_map
95
+ )
96
 
97
+ def save_image_with_metadata(
98
+ image_data: Any,
99
+ sheet_state: PropertyConfig,
100
+ model: str,
101
+ f_number: str,
102
+ iso: str,
103
+ s_churn_val: float,
104
+ description: str
105
+ ) -> str | None:
106
  \"\"\"
107
+ Saves an image with updated metadata, merging data from the PropertySheet
108
+ and individual UI components.
109
+ This example deals with the PropertySheet component and the individual gradio components.
110
+ Since they have the same labels here, we'll simply replace the metadata with each other's values.
 
 
 
 
111
  \"\"\"
112
  if not image_data:
113
  return None
114
+
115
+
116
+ metadata = flatten_dataclass_with_labels(sheet_state)
117
+ individual_component_values = {
118
+ "Model": model,
119
+ "FNumber": f_number,
120
+ "ISOSpeedRatings": iso,
121
+ "Schurn": s_churn_val,
122
+ "Description": description
123
+ }
124
+
125
+ metadata["Image Settings - Model"] = individual_component_values["Model"]
126
+ metadata["Image Settings - FNumber"] = individual_component_values["FNumber"]
127
+ metadata["Image Settings - ISOSpeedRatings"] = individual_component_values["ISOSpeedRatings"]
128
+ metadata["Image Settings - Schurn"] = individual_component_values["Schurn"]
129
+ metadata["Description"] = individual_component_values["Description"]
130
+
131
+ final_metadata = {str(key): value for key, value in metadata.items()}
132
 
133
  new_filepath = output_dir / "image_with_meta.png"
134
+ add_metadata(image_data, new_filepath, final_metadata)
 
135
 
136
  return str(new_filepath)
137
 
 
238
  )
239
  save_button.click(
240
  save_image_with_metadata,
241
+ inputs=[img_custom, property_sheet, *input_fields.values()],
242
  outputs=[saved_file_output]
243
  )
244
 
src/examples/image_with_meta.png CHANGED

Git LFS Details

  • SHA256: 21d3f57b5657cbdbfcc65be66db9793ec5592cf7d375468c1dd4677147dad769
  • Pointer size: 132 Bytes
  • Size of remote file: 5.79 MB

Git LFS Details

  • SHA256: 0ee3f0bc8525aaa355a495f9cfadf00306261971d389b0291233e514c12f1d40
  • Pointer size: 132 Bytes
  • Size of remote file: 5.79 MB
src/pyproject.toml CHANGED
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
8
 
9
  [project]
10
  name = "gradio_imagemeta"
11
- version = "0.0.4"
12
  description = "Image Preview with Metadata for Gradio Interface"
13
  readme = "README.md"
14
  license = "apache-2.0"
 
8
 
9
  [project]
10
  name = "gradio_imagemeta"
11
+ version = "0.0.6"
12
  description = "Image Preview with Metadata for Gradio Interface"
13
  readme = "README.md"
14
  license = "apache-2.0"