LeroyDyer commited on
Commit
b2a4a92
·
verified ·
1 Parent(s): bd0ef65

Upload 2 files

Browse files
Files changed (2) hide show
  1. Spectrograms.py +384 -0
  2. imageTotext-gradio.py +288 -0
Spectrograms.py ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import torch
3
+ import torchaudio
4
+ import librosa
5
+ import librosa.display
6
+ import matplotlib.pyplot as plt
7
+ import soundfile as sf
8
+ from PIL import Image
9
+
10
+
11
+ # Step 1: Encode Audio to Mel-Spectrogram
12
+ def encode_audio_to_mel_spectrogram(audio_file, n_mels=128):
13
+ """
14
+ Encode an audio file to a mel-spectrogram.
15
+
16
+ Parameters:
17
+ - audio_file: Path to the audio file.
18
+ - n_mels: Number of mel bands (default: 128).
19
+
20
+ Returns:
21
+ - mel_spectrogram_db: Mel-spectrogram in dB scale.
22
+ - sample_rate: Sample rate of the audio file.
23
+ """
24
+ y, sample_rate = librosa.load(audio_file, sr=None) # Load audio
25
+ mel_spectrogram = librosa.feature.melspectrogram(y=y, sr=sample_rate, n_mels=n_mels)
26
+ mel_spectrogram_db = librosa.power_to_db(mel_spectrogram, ref=np.max) # Convert to dB
27
+ return mel_spectrogram_db, sample_rate
28
+
29
+ # Improved Step 2: Save Mel-Spectrogram as Image
30
+ def save_mel_spectrogram_image(mel_spectrogram_db, sample_rate, output_image='mel_spectrogram.png', method='matplotlib', figsize=(10, 4), cmap='hot'):
31
+ """
32
+ Save the mel-spectrogram as an image using the specified method.
33
+
34
+ Parameters:
35
+ - mel_spectrogram_db: Mel-spectrogram in dB scale.
36
+ - sample_rate: Sample rate of the audio file.
37
+ - output_image: Path to save the image.
38
+ - method: Method for saving ('matplotlib' or 'custom').
39
+ - figsize: Size of the figure for matplotlib (default: (10, 4)).
40
+ - cmap: Colormap for the spectrogram (default: 'hot').
41
+ """
42
+ if method == 'matplotlib':
43
+ plt.figure(figsize=figsize)
44
+ librosa.display.specshow(mel_spectrogram_db, sr=sample_rate, x_axis='time', y_axis='mel', cmap=cmap)
45
+ plt.colorbar(format='%+2.0f dB')
46
+ plt.title('Mel-Spectrogram')
47
+ plt.savefig(output_image)
48
+ plt.close()
49
+ print(f"Mel-spectrogram image saved using matplotlib as '{output_image}'")
50
+
51
+ elif method == 'custom':
52
+ # Convert dB scale to linear scale for image generation
53
+ mel_spectrogram_linear = librosa.db_to_power(mel_spectrogram_db)
54
+ # Create an image from the mel-spectrogram
55
+ image = image_from_spectrogram(mel_spectrogram_linear[np.newaxis, ...]) # Add channel dimension
56
+ # Save the image
57
+ image.save(output_image)
58
+ print(f"Mel-spectrogram image saved using custom method as '{output_image}'")
59
+
60
+ else:
61
+ raise ValueError("Invalid method. Choose 'matplotlib' or 'custom'.")
62
+
63
+
64
+ # Spectrogram conversion functions
65
+ def image_from_spectrogram(spectrogram: np.ndarray, power: float = 0.25) -> Image.Image:
66
+ """
67
+ Compute a spectrogram image from a spectrogram magnitude array.
68
+
69
+ Args:
70
+ spectrogram: (channels, frequency, time)
71
+ power: A power curve to apply to the spectrogram to preserve contrast
72
+
73
+ Returns:
74
+ image: (frequency, time, channels)
75
+ """
76
+ # Rescale to 0-1
77
+ max_value = np.max(spectrogram)
78
+ data = spectrogram / max_value
79
+
80
+ # Apply the power curve
81
+ data = np.power(data, power)
82
+
83
+ # Rescale to 0-255 and invert
84
+ data = 255 - (data * 255).astype(np.uint8)
85
+
86
+ # Convert to a PIL image
87
+ if data.shape[0] == 1:
88
+ image = Image.fromarray(data[0], mode="L").convert("RGB")
89
+ elif data.shape[0] == 2:
90
+ data = np.array([np.zeros_like(data[0]), data[0], data[1]]).transpose(1, 2, 0)
91
+ image = Image.fromarray(data, mode="RGB")
92
+ else:
93
+ raise NotImplementedError(f"Unsupported number of channels: {data.shape[0]}")
94
+
95
+ # Flip Y
96
+ image = image.transpose(Image.FLIP_TOP_BOTTOM)
97
+ return image
98
+
99
+
100
+ # Step 3: Extract Mel-Spectrogram from Image (Direct Pixel Manipulation)
101
+ def extract_mel_spectrogram_from_image(image_path):
102
+ """
103
+ Extract a mel-spectrogram from a saved image using pixel manipulation.
104
+
105
+ Parameters:
106
+ - image_path: Path to the spectrogram image file.
107
+
108
+ Returns:
109
+ - mel_spectrogram_db: The extracted mel-spectrogram in dB scale.
110
+ """
111
+ img = Image.open(image_path).convert('L') # Open image and convert to grayscale
112
+ img_array = np.array(img) # Convert to NumPy array
113
+ mel_spectrogram_db = img_array / 255.0 * -80 # Scale to dB range
114
+ return mel_spectrogram_db
115
+
116
+ # Alternative Spectrogram Extraction (IFFT Method)
117
+ def extract_spectrogram_with_ifft(mel_spectrogram_db):
118
+ """
119
+ Extracts the audio signal from a mel-spectrogram using the inverse FFT method.
120
+
121
+ Parameters:
122
+ - mel_spectrogram_db: The mel-spectrogram in dB scale.
123
+
124
+ Returns:
125
+ - audio: The reconstructed audio signal.
126
+ """
127
+ # Convert dB mel-spectrogram back to linear scale
128
+ mel_spectrogram = librosa.db_to_power(mel_spectrogram_db)
129
+
130
+ # Inverse mel transformation to get the audio signal
131
+ # Using IFFT (simplified for demonstration; typically requires phase info)
132
+ audio = librosa.feature.inverse.mel_to_audio(mel_spectrogram)
133
+
134
+ return audio
135
+
136
+ # Step 4: Decode Mel-Spectrogram with Griffin-Lim
137
+ def decode_mel_spectrogram_to_audio(mel_spectrogram_db, sample_rate, output_audio='griffin_reconstructed_audio.wav'):
138
+ """
139
+ Decode a mel-spectrogram into audio using Griffin-Lim algorithm.
140
+
141
+ Parameters:
142
+ - mel_spectrogram_db: The mel-spectrogram in dB scale.
143
+ - sample_rate: The sample rate for the audio file.
144
+ - output_audio: Path to save the reconstructed audio file.
145
+ """
146
+ # Convert dB mel-spectrogram back to linear scale
147
+ mel_spectrogram = librosa.db_to_power(mel_spectrogram_db)
148
+ # Perform Griffin-Lim to reconstruct audio
149
+ audio = librosa.griffinlim(mel_spectrogram)
150
+ # Save the generated audio
151
+ sf.write(output_audio, audio, sample_rate)
152
+ print(f"Griffin-Lim reconstructed audio saved as '{output_audio}'")
153
+ return audio
154
+
155
+ # Step 5: Load MelGAN Vocoder
156
+ def load_melgan_vocoder():
157
+ """
158
+ Load a lightweight pre-trained MelGAN vocoder for decoding mel-spectrograms.
159
+ Returns a torch MelGAN vocoder model.
160
+ """
161
+ model = torchaudio.models.MelGAN() # Load MelGAN model
162
+ model.eval() # Ensure the model is in evaluation mode
163
+ return model
164
+
165
+ # Step 6: Decode Mel-Spectrogram with MelGAN
166
+ def decode_mel_spectrogram_with_melgan(mel_spectrogram_db, sample_rate, output_audio='melgan_reconstructed_audio.wav'):
167
+ """
168
+ Decode a mel-spectrogram into audio using MelGAN vocoder.
169
+
170
+ Parameters:
171
+ - mel_spectrogram_db: The mel-spectrogram in dB scale.
172
+ - sample_rate: The sample rate for the audio file.
173
+ - output_audio: Path to save the reconstructed audio file.
174
+
175
+ Returns:
176
+ - audio: The reconstructed audio signal.
177
+ """
178
+ # Convert dB mel-spectrogram back to linear scale
179
+ mel_spectrogram = librosa.db_to_power(mel_spectrogram_db)
180
+ # Convert numpy array to torch tensor and adjust the shape
181
+ mel_spectrogram_tensor = torch.tensor(mel_spectrogram).unsqueeze(0) # Shape: [1, mel_bins, time_frames]
182
+
183
+ # Load the MelGAN vocoder model
184
+ melgan = load_melgan_vocoder()
185
+
186
+ # Pass the mel-spectrogram through MelGAN to generate audio
187
+ with torch.no_grad():
188
+ audio = melgan(mel_spectrogram_tensor).squeeze().numpy() # Squeeze to remove batch dimension
189
+
190
+ # Save the generated audio
191
+ sf.write(output_audio, audio, sample_rate)
192
+ print(f"MelGAN reconstructed audio saved as '{output_audio}'")
193
+ return audio
194
+
195
+ def audio_from_waveform(samples: np.ndarray, sample_rate: int, normalize: bool = False) -> pydub.AudioSegment:
196
+ """
197
+ Convert a numpy array of samples of a waveform to an audio segment.
198
+
199
+ Args:
200
+ samples: (channels, samples) array
201
+ sample_rate: Sample rate of the audio.
202
+ normalize: Flag to normalize volume.
203
+
204
+ Returns:
205
+ pydub.AudioSegment
206
+ """
207
+ # Normalize volume to fit in int16
208
+ if normalize:
209
+ samples *= np.iinfo(np.int16).max / np.max(np.abs(samples))
210
+
211
+ # Transpose and convert to int16
212
+ samples = samples.transpose(1, 0).astype(np.int16)
213
+
214
+ # Write to the bytes of a WAV file
215
+ wav_bytes = io.BytesIO()
216
+ wavfile.write(wav_bytes, sample_rate, samples)
217
+ wav_bytes.seek(0)
218
+
219
+ # Read into pydub
220
+ return pydub.AudioSegment.from_wav(wav_bytes)
221
+
222
+
223
+ def apply_filters(segment: pydub.AudioSegment, compression: bool = False) -> pydub.AudioSegment:
224
+ """
225
+ Apply post-processing filters to the audio segment to compress it and keep at a -10 dBFS level.
226
+
227
+ Args:
228
+ segment: The audio segment to filter.
229
+ compression: Flag to apply dynamic range compression.
230
+
231
+ Returns:
232
+ pydub.AudioSegment
233
+ """
234
+ if compression:
235
+ segment = pydub.effects.normalize(segment, headroom=0.1)
236
+ segment = segment.apply_gain(-10 - segment.dBFS)
237
+ segment = pydub.effects.compress_dynamic_range(
238
+ segment,
239
+ threshold=-20.0,
240
+ ratio=4.0,
241
+ attack=5.0,
242
+ release=50.0,
243
+ )
244
+
245
+ # Apply gain to desired dB level and normalize again
246
+ desired_db = -12
247
+ segment = segment.apply_gain(desired_db - segment.dBFS)
248
+ return pydub.effects.normalize(segment, headroom=0.1)
249
+
250
+
251
+ def stitch_segments(segments: Sequence[pydub.AudioSegment], crossfade_s: float) -> pydub.AudioSegment:
252
+ """
253
+ Stitch together a sequence of audio segments with a crossfade between each segment.
254
+
255
+ Args:
256
+ segments: Sequence of audio segments to stitch.
257
+ crossfade_s: Duration of crossfade in seconds.
258
+
259
+ Returns:
260
+ pydub.AudioSegment
261
+ """
262
+ crossfade_ms = int(crossfade_s * 1000)
263
+ combined_segment = segments[0]
264
+ for segment in segments[1:]:
265
+ combined_segment = combined_segment.append(segment, crossfade=crossfade_ms)
266
+ return combined_segment
267
+
268
+
269
+ def overlay_segments(segments: Sequence[pydub.AudioSegment]) -> pydub.AudioSegment:
270
+ """
271
+ Overlay a sequence of audio segments on top of each other.
272
+
273
+ Args:
274
+ segments: Sequence of audio segments to overlay.
275
+
276
+ Returns:
277
+ pydub.AudioSegment
278
+ """
279
+ assert len(segments) > 0
280
+ output: pydub.AudioSegment = segments[0]
281
+ for segment in segments[1:]:
282
+ output = output.overlay(segment)
283
+ return output
284
+
285
+
286
+
287
+ # Step 7: Full Pipeline for Audio Processing with Customization
288
+ def mel_spectrogram_pipeline(audio_file, output_image='mel_spectrogram.png',
289
+ output_audio_griffin='griffin_reconstructed_audio.wav',
290
+ output_audio_melgan='melgan_reconstructed_audio.wav',
291
+ extraction_method='pixel', # 'pixel' or 'ifft'
292
+ decoding_method='griffin'): # 'griffin' or 'melgan'
293
+ """
294
+ Full pipeline to encode audio to mel-spectrogram, save it as an image, extract the spectrogram from the image,
295
+ and decode it back to audio using the selected methods.
296
+
297
+ Parameters:
298
+ - audio_file: Path to the audio file to be processed.
299
+ - output_image: Path to save the mel-spectrogram image (default: 'mel_spectrogram.png').
300
+ - output_audio_griffin: Path to save the Griffin-Lim reconstructed audio.
301
+ - output_audio_melgan: Path to save the MelGAN reconstructed audio.
302
+ - extraction_method: Method for extraction ('pixel' or 'ifft').
303
+ - decoding_method: Method for decoding ('griffin' or 'melgan').
304
+ """
305
+ # Step 1: Encode (Audio -> Mel-Spectrogram)
306
+ mel_spectrogram_db, sample_rate = encode_audio_to_mel_spectrogram(audio_file)
307
+
308
+ # Step 2: Convert Mel-Spectrogram to Image and save it
309
+ save_mel_spectrogram_image(mel_spectrogram_db, sample_rate, output_image)
310
+
311
+ # Step 3: Extract Mel-Spectrogram from the image based on chosen method
312
+ if extraction_method == 'pixel':
313
+ extracted_mel_spectrogram_db = extract_mel_spectrogram_from_image(output_image)
314
+ elif extraction_method == 'ifft':
315
+ extracted_mel_spectrogram_db = extract_spectrogram_with_ifft(mel_spectrogram_db)
316
+ else:
317
+ raise ValueError("Invalid extraction method. Choose 'pixel' or 'ifft'.")
318
+
319
+ # Step 4: Decode based on the chosen decoding method
320
+ if decoding_method == 'griffin':
321
+ decode_mel_spectrogram_to_audio(extracted_mel_spectrogram_db, sample_rate, output_audio_griffin)
322
+ elif decoding_method == 'melgan':
323
+ decode_mel_spectrogram_with_melgan(extracted_mel_spectrogram_db, sample_rate, output_audio_melgan)
324
+ else:
325
+ raise ValueError("Invalid decoding method. Choose 'griffin' or 'melgan'.")
326
+
327
+
328
+ def process_audio(audio_file, extraction_method, decoding_method):
329
+ # Create temporary files for outputs
330
+ with tempfile.NamedTemporaryFile(suffix=".png") as temp_image, \
331
+ tempfile.NamedTemporaryFile(suffix=".wav") as temp_audio_griffin, \
332
+ tempfile.NamedTemporaryFile(suffix=".wav") as temp_audio_melgan:
333
+
334
+ # Step 1: Encode (Audio -> Mel-Spectrogram)
335
+ mel_spectrogram_db, sample_rate = encode_audio_to_mel_spectrogram(audio_file)
336
+
337
+ # Step 2: Convert Mel-Spectrogram to Image and save it
338
+ save_mel_spectrogram_image(mel_spectrogram_db, sample_rate, temp_image.name)
339
+
340
+ # Step 3: Extract Mel-Spectrogram from the image based on chosen method
341
+ if extraction_method == 'pixel':
342
+ extracted_mel_spectrogram_db = extract_mel_spectrogram_from_image(temp_image.name)
343
+ elif extraction_method == 'ifft':
344
+ extracted_mel_spectrogram_db = extract_spectrogram_with_ifft(mel_spectrogram_db)
345
+
346
+ # Step 4: Decode using both methods
347
+ decode_mel_spectrogram_to_audio(extracted_mel_spectrogram_db, sample_rate, temp_audio_griffin.name)
348
+ decode_mel_spectrogram_with_melgan(extracted_mel_spectrogram_db, sample_rate, temp_audio_melgan.name)
349
+
350
+ # Return results
351
+ return (temp_image.name,
352
+ temp_audio_griffin.name if decoding_method == 'griffin' else temp_audio_melgan.name)
353
+
354
+ # Create Gradio interface
355
+ iface = gr.Interface(
356
+ fn=process_audio,
357
+ inputs=[
358
+ gr.Audio(type="filepath", label="Upload Audio"),
359
+ gr.Radio(["pixel", "ifft"], label="Extraction Method", value="pixel"),
360
+ gr.Radio(["griffin", "melgan"], label="Decoding Method", value="griffin")
361
+ ],
362
+ outputs=[
363
+ gr.Image(type="filepath", label="Mel-Spectrogram"),
364
+ gr.Audio(type="filepath", label="Reconstructed Audio")
365
+ ],
366
+ title="Audio Encoder-Decoder",
367
+ description="Upload an audio file to encode it to a mel-spectrogram and then decode it back to audio."
368
+ )
369
+
370
+ # Launch the app
371
+ iface.launch()
372
+
373
+
374
+ # Example usage(TEST)
375
+ if __name__ == "__main__":
376
+ audio_file_path = 'your_audio_file.wav' # Specify the path to your audio file here
377
+ mel_spectrogram_pipeline(
378
+ audio_file_path,
379
+ output_image='mel_spectrogram.png',
380
+ output_audio_griffin='griffin_reconstructed_audio.wav',
381
+ output_audio_melgan='melgan_reconstructed_audio.wav',
382
+ extraction_method='pixel', # Choose 'pixel' or 'ifft'
383
+ decoding_method='griffin' # Choose 'griffin' or 'melgan'
384
+ )
imageTotext-gradio.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import re
3
+ import uuid
4
+ import os
5
+ import requests
6
+ from PIL import Image
7
+ from PIL import ImageOps
8
+ from io import BytesIO
9
+ from urllib.parse import urlparse
10
+ from pathlib import Path
11
+ from tqdm import tqdm
12
+ import gradio as gr
13
+ from gradio.components import Textbox, Radio, Dataframe
14
+ import torch
15
+ from llava.constants import DEFAULT_IMAGE_TOKEN, IMAGE_TOKEN_INDEX
16
+ from llava.conversation import SeparatorStyle, conv_templates
17
+ from llava.mm_utils import (
18
+ KeywordsStoppingCriteria,
19
+ get_model_name_from_path,
20
+ process_images,
21
+ tokenizer_image_token,
22
+ )
23
+ from llava.model.builder import load_pretrained_model
24
+ from llava.utils import disable_torch_init
25
+
26
+ # Set CUDA device
27
+ os.environ["CUDA_VISIBLE_DEVICES"] = "0"
28
+
29
+ disable_torch_init()
30
+ torch.manual_seed(1234)
31
+
32
+ # Load model and other necessary components
33
+ MODEL = "LeroyDyer/Mixtral_AI_Vision-Instruct_X"
34
+ model_name = get_model_name_from_path(MODEL)
35
+ tokenizer, model, image_processor, context_len = load_pretrained_model(
36
+ model_path=MODEL, model_base=None, model_name=model_name, device="cuda"
37
+ )
38
+
39
+ def get_extension_from_url(url):
40
+ """
41
+ Extract the file extension from the given URL.
42
+ """
43
+ parsed_url = urlparse(url)
44
+ path = Path(parsed_url.path)
45
+ return path.suffix
46
+
47
+ def remove_transparency(image):
48
+ if image.mode in ('RGBA', 'LA') or (image.mode == 'P' and 'transparency' in image.info):
49
+ alpha = image.convert('RGBA').split()[-1]
50
+ bg = Image.new("RGB", image.size, (255, 255, 255))
51
+ bg.paste(image, mask=alpha)
52
+ return bg
53
+ else:
54
+ return image
55
+
56
+ def load_image(image_file):
57
+ if image_file.startswith("http://") or image_file.startswith("https://"):
58
+ response = requests.get(image_file)
59
+ image = Image.open(BytesIO(response.content)).convert("RGB")
60
+ else:
61
+ image = Image.open(image_file).convert("RGB")
62
+ image = remove_transparency(image)
63
+ return image
64
+
65
+ def process_image(image):
66
+ args = {"image_aspect_ratio": "pad"}
67
+ image_tensor = process_images([image], image_processor, args)
68
+ return image_tensor.to(model.device, dtype=torch.float16)
69
+
70
+ def create_prompt(prompt: str):
71
+ conv = conv_templates["llava_v0"].copy()
72
+ roles = conv.roles
73
+ prompt = DEFAULT_IMAGE_TOKEN + "\n" + prompt
74
+ conv.append_message(roles[0], prompt)
75
+ conv.append_message(roles[1], None)
76
+ return conv.get_prompt(), conv
77
+
78
+ def remove_duplicates(string):
79
+ words = string.split()
80
+ unique_words = []
81
+
82
+ for word in words:
83
+ if word not in unique_words:
84
+ unique_words.append(word)
85
+
86
+ return ' '.join(unique_words)
87
+
88
+ def ask_image(image: Image, prompt: str):
89
+ image_tensor = process_image(image)
90
+ prompt, conv = create_prompt(prompt)
91
+ input_ids = (
92
+ tokenizer_image_token(
93
+ prompt,
94
+ tokenizer,
95
+ IMAGE_TOKEN_INDEX,
96
+ return_tensors="pt",
97
+ )
98
+ .unsqueeze(0)
99
+ .to(model.device)
100
+ )
101
+
102
+ stop_str = conv.sep if conv.sep_style != SeparatorStyle.TWO else conv.sep2
103
+ stopping_criteria = KeywordsStoppingCriteria(keywords=[stop_str], tokenizer=tokenizer, input_ids=input_ids)
104
+
105
+ with torch.inference_mode():
106
+ output_ids = model.generate(
107
+ input_ids,
108
+ images=image_tensor,
109
+ do_sample=True,
110
+ temperature=0.2,
111
+ max_new_tokens=2048,
112
+ use_cache=True,
113
+ stopping_criteria=[stopping_criteria],
114
+ )
115
+ generated_caption = tokenizer.decode(output_ids[0, input_ids.shape[1] :], skip_special_tokens=True).strip()
116
+
117
+ # Remove unnecessary phrases from the generated caption
118
+ unnecessary_phrases = [
119
+ "The person is a",
120
+ "The image is",
121
+ "looking directly at the camera",
122
+ "in the image",
123
+ "taking a selfie",
124
+ "posing for a picture",
125
+ "holding a cellphone",
126
+ "is wearing a pair of sunglasses",
127
+ "pulled back in a ponytail",
128
+ "with a large window in the cent",
129
+ "and there are no other people or objects in the scene.",
130
+ " and.",
131
+ "..",
132
+ " is.",
133
+ ]
134
+
135
+ for phrase in unnecessary_phrases:
136
+ generated_caption = generated_caption.replace(phrase, "")
137
+
138
+ # Split the caption into sentences
139
+ sentences = generated_caption.split('. ')
140
+
141
+ # Check if the last sentence is a fragment and remove it if necessary
142
+ min_sentence_length = 3
143
+ if len(sentences) > 1:
144
+ last_sentence = sentences[-1]
145
+ if len(last_sentence.split()) <= min_sentence_length:
146
+ sentences = sentences[:-1]
147
+
148
+ # Keep only the first three sentences and append periods
149
+ sentences = [s.strip() + '.' for s in sentences[:3]]
150
+
151
+ generated_caption = ' '.join(sentences)
152
+
153
+ generated_caption = remove_duplicates(generated_caption) # Remove duplicate words
154
+
155
+ return generated_caption
156
+
157
+
158
+ def fix_generated_caption(generated_caption):
159
+ # Remove unnecessary phrases from the generated caption
160
+ unnecessary_phrases = [
161
+ "The person is",
162
+ "The image is",
163
+ "looking directly at the camera",
164
+ "in the image",
165
+ "taking a selfie",
166
+ "posing for a picture",
167
+ "holding a cellphone",
168
+ "is wearing a pair of sunglasses",
169
+ "pulled back in a ponytail",
170
+ "with a large window in the cent",
171
+ "and there are no other people or objects in the scene.",
172
+ " and.",
173
+ "..",
174
+ " is.",
175
+ ]
176
+
177
+ for phrase in unnecessary_phrases:
178
+ generated_caption = generated_caption.replace(phrase, "")
179
+
180
+ # Split the caption into sentences
181
+ sentences = generated_caption.split('. ')
182
+
183
+ # Check if the last sentence is a fragment and remove it if necessary
184
+ min_sentence_length = 3
185
+ if len(sentences) > 1:
186
+ last_sentence = sentences[-1]
187
+ if len(last_sentence.split()) <= min_sentence_length:
188
+ sentences = sentences[:-1]
189
+
190
+ # Capitalize the first letter of the caption and add "a" at the beginning
191
+ sentences[0] = sentences[0].strip().capitalize()
192
+ sentences[0] = "a " + sentences[0] if not sentences[0].startswith("A ") else sentences[0]
193
+
194
+ generated_caption = '. '.join(sentences)
195
+
196
+ generated_caption = remove_duplicates(generated_caption) # Remove duplicate words
197
+
198
+ return generated_caption
199
+
200
+ def find_image_urls(data, url_pattern=re.compile(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+\.(?:jpg|jpeg|png|webp)')):
201
+ """
202
+ Recursively search for image URLs in a JSON object.
203
+ """
204
+ if isinstance(data, list):
205
+ for item in data:
206
+ for url in find_image_urls(item, url_pattern):
207
+ yield url
208
+ elif isinstance(data, dict):
209
+ for value in data.values():
210
+ for url in find_image_urls(value, url_pattern):
211
+ yield url
212
+ elif isinstance(data, str) and url_pattern.match(data):
213
+ yield data
214
+
215
+ def gradio_interface(directory_path, prompt, exist):
216
+ image_paths = [os.path.join(directory_path, f) for f in os.listdir(directory_path) if f.endswith(('.png', '.jpg', '.jpeg', '.webp'))]
217
+ captions = []
218
+
219
+ # Check for images.json and process it
220
+ json_path = os.path.join(directory_path, 'images.json')
221
+ if os.path.exists(json_path):
222
+ with open(json_path, 'r') as json_file:
223
+ data = json.load(json_file)
224
+ image_urls = list(find_image_urls(data))
225
+ for url in image_urls:
226
+ try:
227
+ # Generate a unique filename for each image with the correct extension
228
+ extension = get_extension_from_url(url) or '.jpg' # Default to .jpg if no extension is found
229
+ unique_filename = str(uuid.uuid4()) + extension
230
+ unique_filepath = os.path.join(directory_path, unique_filename)
231
+ response = requests.get(url)
232
+ with open(unique_filepath, 'wb') as img_file:
233
+ img_file.write(response.content)
234
+ image_paths.append(unique_filepath)
235
+ except Exception as e:
236
+ captions.append((url, f"Error downloading {url}: {e}"))
237
+
238
+ # Process each image path with tqdm progress tracker
239
+ for im_path in tqdm(image_paths, desc="Captioning Images", unit="image"):
240
+ base_name = os.path.splitext(os.path.basename(im_path))[0]
241
+ caption_path = os.path.join(directory_path, base_name + '.caption')
242
+
243
+ # Handling existing files
244
+ if os.path.exists(caption_path) and exist == 'skip':
245
+ captions.append((base_name, "Skipped existing caption"))
246
+ continue
247
+ elif os.path.exists(caption_path) and exist == 'add':
248
+ mode = 'a'
249
+ else:
250
+ mode = 'w'
251
+
252
+ # Image captioning
253
+ try:
254
+ im = load_image(im_path)
255
+ result = ask_image(im, prompt)
256
+
257
+ # Fix the generated caption
258
+ fixed_result = fix_generated_caption(result)
259
+
260
+ # Writing to a text file
261
+ with open(caption_path, mode) as file:
262
+ if mode == 'a':
263
+ file.write("\n")
264
+ file.write(fixed_result) # Write the fixed caption
265
+
266
+ captions.append((base_name, fixed_result))
267
+ except Exception as e:
268
+ captions.append((base_name, f"Error processing {im_path}: {e}"))
269
+
270
+ return captions
271
+
272
+ iface = gr.Interface(
273
+ fn=gradio_interface,
274
+ inputs=[
275
+ Textbox(label="Directory Path"),
276
+ Textbox(default="Describe the persons, The person is appearance like eyes color, hair color, skin color, and the clothes, object position the scene and the situation. Please describe it detailed. Don't explain the artstyle of the image", label="Captioning Prompt"),
277
+ Radio(["skip", "replace", "add"], label="Existing Caption Action", default="skip")
278
+ ],
279
+ outputs=[
280
+ Dataframe(type="pandas", headers=["Image", "Caption"], label="Captions")
281
+ ],
282
+ title="Image Captioning",
283
+ description="Generate captions for images in a specified directory."
284
+ )
285
+
286
+ # Run the Gradio app
287
+ if __name__ == "__main__":
288
+ iface.launch()