Building an AI Image Generator with OpenAI A developer has published a tutorial on building a web application that uses OpenAI's Images API to turn drawings into photorealistic images. The app is built with Python and Anvil, and the tutorial walks through creating the UI and integrating the API. In this tutorial, we're going to build a web application that uses OpenAI's Images API https://platform.openai.com/docs/api-reference/images to turn drawings into photorealistic images. We'll build the entire app using Python with Anvil. When the app is finished, we'll be able to upload an image and click a button to call the Images API. When the model has finished generating a new image, it will be displayed on the screen, and we can download it. To build the app, we will: For this tutorial, you will need basic Python knowledge and an OpenAI account with API credits. If you'd prefer to follow the video version of this tutorial, you can find that here: https://www.youtube.com/watch?v=imJ YaSHxOk https://www.youtube.com/watch?v=imJ YaSHxOk Let's get started Log in https://anvil.works/login?utm source=crosspost:dev.to:/learn/tutorials/open-ai to Anvil and click 'Create a new app'. Choose the New M3 theme and select 'Blank Panel Form'. If you can't find the New M3 theme, you may need to open the "Advanced" dropdown First, rename the Form to "MainForm" by right-clicking on it in the App Browser, then rename the app. Click on the name at the top of the screen and give it a name like "OpenAI Image Generator". We're now looking at the Form Editor https://anvil.works/docs/editor/form-editor?utm source=crosspost:dev.to:/learn/tutorials/open-ai , where we can drag and drop components from the Toolbox https://anvil.works/docs/editor/form-editor toolbox?utm source=crosspost:dev.to:/learn/tutorials/open-ai to build our app's UI. Let's start by adding a Card to the Form to hold our images and buttons. Drop a ColumnPanel https://anvil.works/docs/ui/components/containers columnpanel?utm source=crosspost:dev.to:/learn/tutorials/open-ai inside the Card to make it easier to lay out the components. We need a button to upload an image - that's what the FileLoader component https://anvil.works/docs/ui/app-themes/material-3/components fileloader is for. Drag and drop a FileLoader into the ColumnPanel. Centre-align the FileLoader using the floating Object Palette https://anvil.works/docs/editor/form-editor object-palette . From the Properties Panel https://anvil.works/docs/editor/form-editor properties-panel , change its appearance property to filled . We only want to upload images, so set the file types property to image/ . We need an Image component https://anvil.works/docs/ui/components/basic image to display the uploaded image. Drag and drop an Image component above the FileLoader. Change its name to uploaded img . We don't want it visible until an image is uploaded, so click the eye icon on the Object Palette to make it invisible. Also set its display mode property to fill width . Add another Image component next to the first one to display our generated image. Name it output img and make it invisible too. We now need a button that, when clicked, will call the OpenAI API. Add a Button component https://anvil.works/docs/ui/app-themes/material-3/components button to the page and name it generate button . Centre it, change the text to "Turn into photo", and make it invisible to start. We'll make the Button visible once a file is uploaded. Let's write some code that will make the uploaded img and generate button components visible when an image is uploaded. Select the FileLoader and click on change event from the Object Palette. This opens the code view and automatically creates a method that runs when a file is uploaded. Add the following code: @handle "file loader 1", "change" def self.file loader 1 change self, file, event args : """This method is called when a new file is loaded into this FileLoader""" if file: self.uploaded img.source = file self.uploaded img.visible = True self.generate button.visible = True Let's test out our UI and the code we just wrote. At the top right of the Anvil Editor, click the green Run button. Upload an image and you should see it appear along with the "Turn into photo" button. We're going to use the Images API https://platform.openai.com/docs/api-reference/images from OpenAI to turn our drawings into photorealistic images. In order to use this API, we need to get an API key from OpenAI. If you don't already have an OpenAI account, create one at platform.openai.com https://platform.openai.com . Once logged in, go to the Billing page and add credits to your account. The minimum amount that you can add is typically $5, which is plenty for building and testing this app. You'll need to verify your account to use the Images API. In the "General" tab, look for a "Verify Organization" button. If you don't see the verification button immediately, you may need to wait a few days for it to appear. Once verified, it will say "Organization verified". Navigate to "API keys" and click "Create new secret key". Give it a name and copy the key that appears. You won't be able to see the key again after navigating away. We can now store the key securely in our Anvil app. Back in the app, click the blue '+' button in the Sidebar Menu and choose "App Secrets". Click "Create new secret" and name it OPEN AI API KEY . Click "Set value" and paste in your API key from OpenAI. This key is now encrypted and stored securely. We can now set up our backend to call the API and get a generated image. openai pacakage First, we need to install the OpenAI Python package in our app's server environment. In the Sidebar Menu https://anvil.works/docs/editor sidebar-menu , navigate to Settings and select "Python versions". Switch the base package to "Machine Learning", and in the packages section, add openai . You can leave the version box blank. Back in the App Browser, click "Add Server Module" to add a server environment to your app. This is a Python environment running on Anvil's secure cloud servers. Let's first copy and paste the code from the Images API documentation that can be found here https://platform.openai.com/docs/guides/image-generation?api=image edit-images . Add your API key that's stored in App Secrets to the OpenAI client: client = OpenAI api key=anvil.secrets.get secret 'OPEN AI API KEY' We'll need to modify this code so that it works in Anvil but for now, your server code should look like this: python import anvil.secrets import anvil.server import base64 from openai import OpenAI client = OpenAI api key=anvil.secrets.get secret 'OPEN AI API KEY' prompt = """ Generate a photorealistic image of a gift basket on a white background labeled 'Relax & Unwind' with a ribbon and handwriting-like font, containing all the items in the reference pictures. """ result = client.images.edit model="gpt-image-1", image= open "body-lotion.png", "rb" , open "bath-bomb.png", "rb" , open "incense-kit.png", "rb" , open "soap.png", "rb" , , prompt=prompt image base64 = result.data 0 .b64 json image bytes = base64.b64decode image base64 An Anvil server module doesn’t run top to bottom like a normal Python script. Any code we write here will run when it’s called, so we need to turn this code into a function. We’ll then call that function when the generate button is clicked. Create a function called generate image that takes in input img as an argument. Indent the code we copied into this function and change the prompt to say "Turn the drawing into a photorealistic image". Your server code should now look something like this: python import anvil.secrets import anvil.server import base64 from openai import OpenAI client = OpenAI api key=anvil.secrets.get secret 'OPEN AI API KEY' def generate image input img : result = client.images.edit model="gpt-image-1", image= open "body-lotion.png", "rb" , open "bath-bomb.png", "rb" , open "incense-kit.png", "rb" , open "soap.png", "rb" , , prompt="Turn the drawing into a photorealistic image" image base64 = result.data 0 .b64 json image bytes = base64.b64decode image base64 Save the image to a file with open "gift-basket.png", "wb" as f: f.write image bytes We need to pass in a file path for our input image into the OpenAI API. We can get a temporary file path using anvil.media.TempFile https://anvil.works/docs/working-with-files/media/files on disk media-object-to-temporary-file . OpenAI checks the MIME type of images based on the file extension, not the actual content, so we also need to append the proper file extension to our temporary file path. Add the following import statements to your server code: python import anvil.media import mimetypes import os Then, inside generate image , we'll get the MIME type of input img and use mimetypes to find the corresponding file extension. We then need to create a TempFile and rename the filepath so that it includes this extension: python def generate image input img : get the MIME type and extension from the input img mime type = input img.content type ext = mimetypes.guess extension mime type create a temporary file path with anvil.media.TempFile input img as tmp path: add the extension to the temporary path new path = tmp path + ext result = client.images.edit model="gpt-image-1", image= pass the path to the model open new path, "rb" , , prompt="Turn the drawing into a photorealistic image" image base64 = result.data 0 .b64 json image bytes = base64.b64decode image base64 Save the image to a file with open "gift-basket.png", "wb" as f: f.write image bytes When the model finishes generating an image, we can create an Anvil Media Object https://anvil.works/docs/working-with-files/media instead of writing to a file. Replace the with statement at the end of the server function with: output img = anvil.BlobMedia content type="text/jpeg", content=image bytes, name="ai-image.jpg" The name argument will be the name of your file when downloaded. You can change this name to anything you'd like. Your server code should now look like this: python import anvil.secrets import anvil.server import base64 from openai import OpenAI import anvil.media import mimetypes import os client = OpenAI api key=anvil.secrets.get secret 'OPEN AI API KEY' def generate image input img : get the MIME type and extension from the input img mime type = input img.content type ext = mimetypes.guess extension mime type create a temporary file path with anvil.media.TempFile input img as tmp path: add the extension to the temporary path new path = tmp path + ext result = client.images.edit model="gpt-image-1", image= pass the path to the model open new path, "rb" , , prompt="Turn the drawing into a photorealistic image" image base64 = result.data 0 .b64 json image bytes = base64.b64decode image base64 Save the image to a file output img = anvil.BlobMedia content type="text/jpeg", content=image bytes, name="ai-image.jpg" The image generation request may take some time, so we don't want our server to hang while waiting. To prevent this, we can run the function in the background using Background Tasks https://anvil.works/docs/background-tasks?utm source=crosspost:dev.to:/learn/tutorials/open-ai . To turn the function into a background task, we just need to decorate it with @anvil.server.background task . python @anvil.server.background task def generate image input img : ... We want to be able to launch the background task when the generate button is clicked. To do that, we need to create a client-callable function that launches the background task. In the ServerModule, add the following function python @anvil.server.callable def launch bg task input img : task = anvil.server.launch background task 'generate image', input img return task The @anvil.server.callable decorator makes this function callable from our frontend code. anvil.server.launch background task returns a Task object https://anvil.works/docs/background-tasks/communicating-back task-object , which we can use to check when the background task is finished and get the return value. We can't return a Media object directly from a background task https://anvil.works/docs/server/background-tasks/communicating-back communicating-back-to-the-main-program , so we'll store the generated image in a Data Table. Choose Data from the Sidebar Menu and click "Add Table" to create a new Data Table https://anvil.works/docs/data-tables?utm source=crosspost:dev.to:/learn/tutorials/open-ai . Call this table tasks and add the following columns: image Media column - for the generated image task id Text column - for the background task ID When the image has finished generating, we need to add a row to the Data Table and return the row. Add the following lines of code to the bottom of the generate image function: task id = anvil.server.context.background task id row = app tables.tasks.add row image=output img, task id=task id return row Your finished ServerModule should now look something like this: python import anvil.secrets import anvil.server import base64 from openai import OpenAI import anvil.media import mimetypes import os client = OpenAI api key=anvil.secrets.get secret 'OPEN AI API KEY' @anvil.server.callable def launch bg task input img : task = anvil.server.launch background task 'generate image', input img return task @anvil.server.background task def generate image input img : get the MIME type and extension from the input img mime type = input img.content type ext = mimetypes.guess extension mime type create a temporary file path with anvil.media.TempFile input img as tmp path: add the extension to the temporary path new path = tmp path + ext result = client.images.edit model="gpt-image-1", image= pass the path to the model open new path, "rb" , , prompt="Turn the drawing into a photorealistic image" image base64 = result.data 0 .b64 json image bytes = base64.b64decode image base64 Save the image to a file output img = anvil.BlobMedia content type="text/jpeg", content=image bytes, name="ai-image.jpg" task id = anvil.server.context.background task id row = app tables.tasks.add row image=output img, task id=task id return row Now that our server code is finished, we can launch the background task from the generate button . Switch back to the Design view of MainForm and select the generate button . Click on click event from the Object Palette to set up a function that will run https://anvil.works/docs/client/events?utm source=crosspost:dev.to:/learn/tutorials/open-ai when the Button is clicked. From this function, we want to call launch bg task , passing in the uploaded file. We also want to disable the generate button while the background task is running: python @handle "generate button", "click" def generate button click self, event args : """This method is called when the button is clicked""" self.task = anvil.server.call 'launch bg task', self.file loader 1.file self.generate button.enabled = False Switch back to Design view, and add a LinearProgressIndicator component https://anvil.works/docs/ui/app-themes/material-3/components linearprogressindicator underneath the Image components. We'll use this to indicate to the user that the image is being generated. Click on the eye icon from the Object Palette to make the component invisible to start. We'll make it visible while the image is being generated. Update the generate button click event to make the linear progress indicator visible when clicked: python @handle "generate button", "click" def generate button click self, event args : """This method is called when the button is clicked""" self.task = anvil.server.call 'launch bg task', self.file loader 1.file self.generate button.enabled = False indicate that the task is running self.linear progress indicator 1.visible = True We need to poll the server to check if the background task has finished running and our image has been generated. Drag and drop a Timer component https://anvil.works/docs/ui/components/basic timer onto the Form. It will appear at the top because it's an invisible component. Timers have an interval property that determines how frequently they raise a tick event. We can then write a function that runs every time the Timer "ticks". From the Properties Panel, set the Timer's interval to 0. We don't want it to start ticking until we tell it to. From the Object Palette, set up a tick event handler for the Timer. In here, we want to check if the background task has finished, and if so, we'll update the UI accordingly. We can also use with anvil.server.no loading indicator to stop Anvil's loading spinner from appearing every time we poll the server. Your timer 1 tick function should look like this: python @handle "timer 1", "tick" def timer 1 tick self, event args : """This method is called Every interval seconds. Does not trigger if interval is 0.""" with anvil.server.no loading indicator: check if the background task has finished if self.task.is completed : get the image from the Data Table row self.generated img = self.task.get return value 'image' display the generated image self.output img.source = self.generated image self.linear progress indicator 1.visible = False self.output img.visible = True self.generate button.enabled = True stop the timer from ticking self.timer 1.interval = 0 Finally, we can add the ability to download the generated image. Back in Design view, add a Button to the page and name it download button . Centre align the Button and make it invisible. Change it's appearance to tonal and set its icon to mi:downlaod . Set up a click event handler for the download button , and inside that function, call anvil.media.download self.generated image . python @handle "download button", "click" def download button click self, event args : """This method is called when the component is clicked.""" anvil.media.download self.generated img At the top of the Form code, make sure to import anvil.media : python import anvil.media It's now time to test out the app Click the green Run button, upload a drawing and click "Turn into photo". Wait for the AI to generate a photorealistic image and then try downloading it. To publish your app to the web, click "Publish" at the top right of the editor, then "Publish this app". You'll get a URL you can share with others. We built a complete web app that: We did it all in Python and deployed it instantly to the web There's a lot you can do to extend this app: If you're new here, welcome Anvil https://anvil.works?utm source=crosspost:dev.to:/learn/tutorials/open-ai is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks - just build it all in Python . Yes - Python that runs in the browser https://anvil.works/docs/client/python?utm source=crosspost:dev.to:/learn/tutorials/open-ai . Python that runs on the server https://anvil.works/docs/server?utm source=crosspost:dev.to:/learn/tutorials/open-ai . Python that builds your UI https://anvil.works/docs/client?utm source=crosspost:dev.to:/learn/tutorials/open-ai . A drag-and-drop UI editor https://anvil.works/docs/client/adding-ui-elements?utm source=crosspost:dev.to:/learn/tutorials/open-ai . We even have a built-in Python database https://anvil.works/docs/data-tables?utm source=crosspost:dev.to:/learn/tutorials/open-ai , in case you don't have your own. Why not have a play with the app builder? It's free Click here to get started: