mahan_ym commited on
Commit
cc8f276
·
1 Parent(s): b183ae9

Refactor Makefile and update app.py and tools.py for improved functionality and clarity

Browse files
Files changed (4) hide show
  1. Makefile +8 -1
  2. src/app.py +37 -28
  3. src/tools.py +14 -10
  4. src/utils.py +0 -54
Makefile CHANGED
@@ -1,9 +1,16 @@
1
  .PHONY: test deploy_modal run dev
2
 
 
 
 
 
 
 
 
3
  deploy_modal:
4
  modal deploy src/modal_app.py
5
 
6
- test:
7
  uv run test/test_modal.py
8
 
9
  run:
 
1
  .PHONY: test deploy_modal run dev
2
 
3
+ install:
4
+ pip install uv && \
5
+ uv venv && \
6
+ source .venv/bin/activate && \
7
+ uv sync && \
8
+ modal setup
9
+
10
  deploy_modal:
11
  modal deploy src/modal_app.py
12
 
13
+ test_modal:
14
  uv run test/test_modal.py
15
 
16
  run:
src/app.py CHANGED
@@ -1,64 +1,68 @@
1
- import gradio as gr
2
 
3
- from tools import change_color_objects_hsv, change_color_objects_lab
4
 
 
 
 
 
 
5
 
6
- def privacy_preserve_image(input_prompt, input_img):
7
- pass
8
 
 
9
 
10
- title = """
11
- <div style="text-align: center;">
12
- <img src="https://raw.githubusercontent.com/mahan-ym/ImageAlfred/main/src/assets/icons/ImageAlfredIcon.png" alt="Image Alfred Logo" style="width: 200px; height: auto;">
13
- <h1>Image Alfred</h1>
14
- <p>Recolor and Privacy Preserving Image Tools</p>
15
- </div>
16
- """ # noqa: E501
17
 
18
  hsv_df_input = gr.Dataframe(
19
  headers=["Object", "Hue", "Saturation Scale"],
20
  datatype=["str", "number", "number"],
21
- label="Input Data",
22
  type="array",
23
  )
24
 
25
  lab_df_input = gr.Dataframe(
26
  headers=["Object", "New A", "New B"],
27
  datatype=["str", "number", "number"],
28
- label="Input Data",
29
  type="array",
30
  )
31
 
32
  change_color_objects_hsv_tool = gr.Interface(
33
  fn=change_color_objects_hsv,
34
  inputs=[
35
- hsv_df_input,
36
  gr.Image(label="Input Image", type="pil"),
 
37
  ],
38
- outputs=gr.Image(),
39
- title="Image Recolor tool (HSV)",
40
  description="This tool allows you to recolor objects in an image using the HSV color space. You can specify the hue and saturation scale for each object.", # noqa: E501
41
  )
42
 
43
  change_color_objects_lab_tool = gr.Interface(
44
  fn=change_color_objects_lab,
45
  inputs=[
46
- lab_df_input,
47
  gr.Image(label="Input Image", type="pil"),
 
48
  ],
49
- outputs=gr.Image(),
50
- title="Image Recolor tool (LAB)",
51
- description="Recolor an image based on user input using the LAB color space. You can specify the new_a and new_b values for each object.", # noqa: E501
52
  )
53
 
54
  privacy_preserve_tool = gr.Interface(
55
  fn=privacy_preserve_image,
56
  inputs=[
57
- gr.Textbox("user_input"),
58
  gr.Image(label="Input Image", type="pil"),
 
 
 
 
59
  ],
60
- outputs=gr.Image(),
61
- title="Privacy preserving tool",
62
  description="Upload an image and provide a prompt for the object to enforce privacy. The tool will use blurring to obscure the specified objects in the image.", # noqa: E501
63
  )
64
 
@@ -69,14 +73,19 @@ demo = gr.TabbedInterface(
69
  privacy_preserve_tool,
70
  ],
71
  ["Change Color Objects HSV", "Change Color Objects LAB", "Privacy Preserving Tool"],
72
- title="Image Alfred",
73
  theme=gr.themes.Default(
74
  primary_hue="blue",
75
- secondary_hue="blue",
76
- font="Inter",
77
- font_mono="Courier New",
78
  ),
 
79
  )
80
 
 
 
 
 
81
  if __name__ == "__main__":
82
- demo.launch(mcp_server=True)
 
1
+ from pathlib import Path
2
 
3
+ import gradio as gr
4
 
5
+ from tools import (
6
+ change_color_objects_hsv,
7
+ change_color_objects_lab,
8
+ privacy_preserve_image,
9
+ )
10
 
11
+ gr.set_static_paths(paths=[Path.cwd().absolute() / "assets"])
 
12
 
13
+ icon = """<link rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/mahan-ym/ImageAlfred/main/src/assets/icons/ImageAlfredIcon.png">"""
14
 
15
+ title = """Image Alfred - Recolor and Privacy Preserving Image Tools
16
+ <img src="https://raw.githubusercontent.com/mahan-ym/ImageAlfred/main/src/assets/icons/ImageAlfredIcon.png" alt="Image Alfred Logo" style="width: 120px; height: auto; margin: 0 auto;">
17
+ """
 
 
 
 
18
 
19
  hsv_df_input = gr.Dataframe(
20
  headers=["Object", "Hue", "Saturation Scale"],
21
  datatype=["str", "number", "number"],
22
+ label="Target Objects and New Settings",
23
  type="array",
24
  )
25
 
26
  lab_df_input = gr.Dataframe(
27
  headers=["Object", "New A", "New B"],
28
  datatype=["str", "number", "number"],
29
+ label="Target Objects and New Settings",
30
  type="array",
31
  )
32
 
33
  change_color_objects_hsv_tool = gr.Interface(
34
  fn=change_color_objects_hsv,
35
  inputs=[
 
36
  gr.Image(label="Input Image", type="pil"),
37
+ hsv_df_input,
38
  ],
39
+ outputs=gr.Image(label="Output Image"),
40
+ title="Image Recolor Tool (HSV)",
41
  description="This tool allows you to recolor objects in an image using the HSV color space. You can specify the hue and saturation scale for each object.", # noqa: E501
42
  )
43
 
44
  change_color_objects_lab_tool = gr.Interface(
45
  fn=change_color_objects_lab,
46
  inputs=[
 
47
  gr.Image(label="Input Image", type="pil"),
48
+ lab_df_input,
49
  ],
50
+ outputs=gr.Image(label="Output Image"),
51
+ title="Image Recolor Tool (LAB)",
52
+ description="Recolor an image based on user input using the LAB color space. You can specify the new A and new B values for each object.", # noqa: E501
53
  )
54
 
55
  privacy_preserve_tool = gr.Interface(
56
  fn=privacy_preserve_image,
57
  inputs=[
 
58
  gr.Image(label="Input Image", type="pil"),
59
+ gr.Textbox(
60
+ label="Objects to Mask (dot-separated)",
61
+ placeholder="e.g., person. car. license plate",
62
+ ), # noqa: E501
63
  ],
64
+ outputs=gr.Image(label="Output Image"),
65
+ title="Privacy Preserving Tool",
66
  description="Upload an image and provide a prompt for the object to enforce privacy. The tool will use blurring to obscure the specified objects in the image.", # noqa: E501
67
  )
68
 
 
73
  privacy_preserve_tool,
74
  ],
75
  ["Change Color Objects HSV", "Change Color Objects LAB", "Privacy Preserving Tool"],
76
+ title=title,
77
  theme=gr.themes.Default(
78
  primary_hue="blue",
79
+ secondary_hue="green",
80
+ # font="Inter",
81
+ # font_mono="Courier New",
82
  ),
83
+ head=icon,
84
  )
85
 
86
+ # with gr.Blocks(title="Image Alfred", head=test) as demo:
87
+ # gr.HTML(header)
88
+ # tabs_interface.render()
89
+
90
  if __name__ == "__main__":
91
+ demo.launch(mcp_server=True, max_file_size="5mb")
src/tools.py CHANGED
@@ -9,13 +9,16 @@ from utils import upload_image_to_tmpfiles
9
  modal_app_name = "ImageAlfred"
10
 
11
 
12
- def preserve_privacy(input_prompt, input_img):
 
 
 
13
  """
14
  Obscure specified objects in the input image based on the input prompt.
15
 
16
  Args:
 
17
  input_prompt (list): List of [object:str].
18
- input_img (bytes): Input image in bytes format.
19
 
20
  Returns:
21
  bytes: Binary image data of the modified image.
@@ -28,8 +31,8 @@ def preserve_privacy(input_prompt, input_img):
28
 
29
 
30
  def change_color_objects_hsv(
31
- user_input,
32
  input_img,
 
33
  ) -> np.ndarray | Image.Image | str | Path | None:
34
  """Changes the hue and saturation of specified objects in an image.
35
 
@@ -50,8 +53,6 @@ def change_color_objects_hsv(
50
  ValueError: If user_input format is invalid, hue values are outside [0, 179] range, saturation_scale is not positive, or image format is invalid or corrupted.
51
  TypeError: If input_img is not a supported type or modal function returns unexpected type.
52
  """ # noqa: E501
53
- print("Received input image type:", type(input_img))
54
- # source, input_img = validate_image_input(input_img)
55
  print("before processing input:", user_input)
56
 
57
  for item in user_input:
@@ -81,12 +82,15 @@ def change_color_objects_hsv(
81
  raise TypeError(
82
  f"Expected Image.Image from modal remote function, got {type(output_pil)}"
83
  )
84
- img_link = upload_image_to_tmpfiles(output_pil)
85
 
86
  return output_pil
87
 
88
 
89
- def change_color_objects_lab(user_input, input_img):
 
 
 
90
  """Changes the color of specified objects in an image using LAB color space.
91
 
92
  Segments objects based on text prompts and alters their color in the LAB
@@ -106,7 +110,6 @@ def change_color_objects_lab(user_input, input_img):
106
  ValueError: If user_input format is invalid, a/b values are outside [0, 255] range, or image format is invalid or corrupted.
107
  TypeError: If input_img is not a supported type or modal function returns unexpected type.
108
  """ # noqa: E501
109
- print("Received input image type:", type(input_img))
110
  print("before processing input:", user_input)
111
  for item in user_input:
112
  if len(item) != 3:
@@ -133,12 +136,13 @@ def change_color_objects_lab(user_input, input_img):
133
  raise TypeError(
134
  f"Expected Image.Image from modal remote function, got {type(output_pil)}"
135
  )
136
- img_link = upload_image_to_tmpfiles(output_pil)
137
 
138
  return output_pil
139
 
140
 
141
  if __name__ == "__main__":
 
142
  change_color_objects_hsv(
143
- user_input=[["hair", 30, 1.2], ["shirt", 60, 1.0]], input_img=b""
144
  )
 
9
  modal_app_name = "ImageAlfred"
10
 
11
 
12
+ def privacy_preserve_image(
13
+ input_img,
14
+ input_prompt,
15
+ ) -> np.ndarray | Image.Image | str | Path | None:
16
  """
17
  Obscure specified objects in the input image based on the input prompt.
18
 
19
  Args:
20
+ input_img (Image.Image): Input image in bytes format.
21
  input_prompt (list): List of [object:str].
 
22
 
23
  Returns:
24
  bytes: Binary image data of the modified image.
 
31
 
32
 
33
  def change_color_objects_hsv(
 
34
  input_img,
35
+ user_input,
36
  ) -> np.ndarray | Image.Image | str | Path | None:
37
  """Changes the hue and saturation of specified objects in an image.
38
 
 
53
  ValueError: If user_input format is invalid, hue values are outside [0, 179] range, saturation_scale is not positive, or image format is invalid or corrupted.
54
  TypeError: If input_img is not a supported type or modal function returns unexpected type.
55
  """ # noqa: E501
 
 
56
  print("before processing input:", user_input)
57
 
58
  for item in user_input:
 
82
  raise TypeError(
83
  f"Expected Image.Image from modal remote function, got {type(output_pil)}"
84
  )
85
+ # img_link = upload_image_to_tmpfiles(output_pil)
86
 
87
  return output_pil
88
 
89
 
90
+ def change_color_objects_lab(
91
+ input_img,
92
+ user_input,
93
+ ) -> np.ndarray | Image.Image | str | Path | None:
94
  """Changes the color of specified objects in an image using LAB color space.
95
 
96
  Segments objects based on text prompts and alters their color in the LAB
 
110
  ValueError: If user_input format is invalid, a/b values are outside [0, 255] range, or image format is invalid or corrupted.
111
  TypeError: If input_img is not a supported type or modal function returns unexpected type.
112
  """ # noqa: E501
 
113
  print("before processing input:", user_input)
114
  for item in user_input:
115
  if len(item) != 3:
 
136
  raise TypeError(
137
  f"Expected Image.Image from modal remote function, got {type(output_pil)}"
138
  )
139
+ # img_link = upload_image_to_tmpfiles(output_pil)
140
 
141
  return output_pil
142
 
143
 
144
  if __name__ == "__main__":
145
+ image_pil = Image.open("./src/assets/test_image.jpg")
146
  change_color_objects_hsv(
147
+ user_input=[["hair", 30, 1.2], ["shirt", 60, 1.0]], input_img=image_pil
148
  )
src/utils.py CHANGED
@@ -6,59 +6,6 @@ import requests
6
  from PIL import Image
7
 
8
 
9
- class ImageSource(Enum):
10
- """Enum representing different sources of image input"""
11
-
12
- PIL = "PIL"
13
- BASE64 = "base64"
14
- URL = "url"
15
- FILE = "file"
16
-
17
-
18
- def validate_image_input(url_or_data):
19
- """Handle different image input formats for MCP"""
20
- if isinstance(url_or_data, Image.Image):
21
- print("Received input image type: PIL.Image")
22
- return (ImageSource.PIL, url_or_data)
23
-
24
- if isinstance(url_or_data, str):
25
- if url_or_data.startswith("data:image"):
26
- try:
27
- # Handle base64 data URLs
28
- print("Received input image type: base64 data")
29
- header, encoded = url_or_data.split(",", 1)
30
- decoded_bytes = base64.b64decode(encoded)
31
- return (
32
- ImageSource.BASE64,
33
- Image.open(BytesIO(decoded_bytes)).convert("RGB"),
34
- )
35
- except Exception as e:
36
- raise ValueError(f"Invalid base64 data URL: {e}")
37
- elif url_or_data.startswith(("http://", "https://")):
38
- # Handle URLs
39
- try:
40
- response = requests.get(url_or_data, timeout=30)
41
- response.raise_for_status()
42
- print("Received input image type: URL")
43
- return (
44
- ImageSource.URL,
45
- Image.open(BytesIO(response.content)).convert("RGB"),
46
- )
47
- except requests.exceptions.RequestException as e:
48
- raise ValueError(f"Could not download image from URL: {e}")
49
- else:
50
- # Handle file paths
51
- try:
52
- with open(url_or_data, "rb") as f:
53
- return (ImageSource.FILE, Image.open(f).convert("RGB"))
54
- except FileNotFoundError:
55
- raise ValueError(f"File not found: {url_or_data}")
56
- except Exception as e:
57
- raise ValueError(f"Could not read file {url_or_data}: {e}")
58
-
59
- raise ValueError(f"Unsupported image input format: {type(url_or_data)}")
60
-
61
-
62
  def upload_image_to_tmpfiles(image):
63
  """
64
  Upload an image to tmpfiles.org and return the URL.
@@ -75,7 +22,6 @@ def upload_image_to_tmpfiles(image):
75
  raise ValueError(f"Upload failed: Status {response.status_code}")
76
 
77
  data = response.json()
78
- print(f"Response from tmpfiles.org: {data}")
79
  if "data" in data and "url" in data["data"]:
80
  url = data["data"]["url"]
81
  if not url:
 
6
  from PIL import Image
7
 
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  def upload_image_to_tmpfiles(image):
10
  """
11
  Upload an image to tmpfiles.org and return the URL.
 
22
  raise ValueError(f"Upload failed: Status {response.status_code}")
23
 
24
  data = response.json()
 
25
  if "data" in data and "url" in data["data"]:
26
  url = data["data"]["url"]
27
  if not url: