Impariamo a conoscere MCP: Introduzione al Model Context Protocol (MCP)
July 16, 2025CampusSphere: Building the Future of Campus AI with Microsoft’s Agentic Framework
July 16, 2025Azure Custom Vision is an easy-to-use yet powerful cognitive service from Microsoft that empowers developers to build, train, and deploy custom image classification or object detection models – all without being machine learning experts. Through a simple web interface or SDKs, you can upload images, tag them with meaningful labels, and train a model that understands your specific use case with the help of transfer learning.
Once trained, you might realize you need access to those tagged images again – maybe for auditing, retraining on different platforms, migrating data, or simply backing them up.
Unfortunately, the Custom Vision portal doesn’t offer a “Download All Tagged Images” button. Thankfully, Microsoft provides a RESTful Get Tagged Images API, a hidden gem that enables you to recover your labeled dataset in bulk via scripting
However, where there’s a will, there’s a way: by analyzing the API reference, we can craft a simple yet elegant script, it authenticates using your training key, paginates through all tagged images in batches, pulls down their download URLs, and saves them into organized folders by tag. No more manual downloads or tedious GUI quirks. With few lines of code, this solution is reproducible, automatable, and tailor‑made for professional workflows.
Ready to reclaim your data? Up we go!
First, locate the training key and project ID on your Custom Vision project’s settings page.
You will use this as the header in all your API calls.
headers = {
‘Training-Key’: training_key
}
First, identify the classes (tags) in your projects to keep downloaded images organized rather than chaotic. Since your data is already tagged, you can store it neatly in folders using this endpoint:
/customvision/v3.0/training/projects/{project_id}/tags
Remember, this is just the URL tail; the base is your region’s API, e.g., southcentralus.api.cognitive.microsoft.com.
def get_tags(training_key, project_id):
headers = {
‘Training-Key’: training_key
}
url = f”/customvision/v3.0/training/projects/{project_id}/tags”
conn = http.client.HTTPSConnection(‘southcentralus.api.cognitive.microsoft.com’)
conn.request(“GET”, url, “{body}”, headers)
response = conn.getresponse()
data = response.read()
data_json = json.loads(data)
conn.close()
return
The result is a list of project tags, which we’ll use to create folders. Let’s write a function to create these folders locally based on your tag list.
def create_folders(tags):
for tag in tags:
folder = tag
if not os.path.exists(folder):
os.makedirs(folder)
If you have noticed, the API Reference sets a 256-image download limit per request (“take” parameter), requiring manual adjustment of the skip parameter to download all images:
skip_size = (iteration_n – 1) * 256
(e.g., first iteration: skip = 0; second: skip = 256, etc.)
Manual downloading is inefficient, especially with large datasets – my last CV project had over 10k images, which would mean running the script more than 40 times!
The process begins as follows: first, retrieve the list of all tagged photos.
headers = {
‘Training-key’: training_key,
‘Content-Type’: ‘application/json’
}
params = f”take={take}&skip={skip}”
url = f”/customvision/v3.0/training/projects/{project_id}/images/tagged?{params}”
conn = http.client.HTTPSConnection(‘southcentralus.api.cognitive.microsoft.com’)
conn.request(“GET”, url, “”, headers)
response = conn.getresponse()
data = response.read()
data_json = json.loads(data)
This process will return JSON data containing the downloadable image URLs. It is necessary to parse the JSON output.
for item in data_json:
uri = item[‘originalImageUri’]
And to download the image from the URL, you need to:
urllib.request.urlretrieve(uri, output_file)
In this context, “output_file” refers to the designated target file intended to store the resulting data. The approach to naming is straightforward: one may utilise a unique identifier generated by one of the numerous available Python libraries, or alternatively, develop a custom name generator based on parameters such as time and date. In my implementation, I have opted for a simpler method – calculating the total number of files within the target directory (which, in this case, is a tag-specific folder created for each tag) to determine the new file’s name.
len([name for name in os.listdir(tag_name) if os.path.isfile(os.path.join(tag_name, name))])
Each item in the JSON output includes a corresponding tag; please ensure that you retrieve it accordingly.
item[“tags”][0][“tagName”]
If you have read “Clean Code” by Robert C. Martin (a highly recommended resource for software engineers by the way), you will be familiar with the principles of clean code, which advise encapsulating logic within functions. Functions should be small and atomic – each function ought to do one thing and to do it well at the same level of abstraction. Assigning descriptive names to functions reduces the need for comments, making code as readable as well-written prose. With this approach in mind, let us aim to refactor the logic into clear, well-structured functions.
Begin by developing small functions to parse the JSON, generate the output file name, and download images from a URL.
def get_image_uri(item):
return item[‘originalImageUri’]
def get_tag_name(item):
return item[“tags”][0][“tagName”]
def get_file_counter(tag_name):
return len([name for name in os.listdir(tag_name) if os.path.isfile(os.path.join(tag_name, name))])
def get_output_file(tag_name, file_counter):
return os.path.join(tag_name, str(file_counter) + ‘.png’)
def download_image(uri, output_file):
try:
urllib.request.urlretrieve(uri, output_file)
except:
print(“Retry”)
urllib.request.urlretrieve(uri, output_file)
Before proceeding, install the tqdm library to enable process visualization. When working with extensive datasets comprising thousands of training images, the operation may require several hours; utilizing a status bar in your console can provide valuable progress updates. Begin by creating your virtual environment and installing tqdm.
pip install tqdm
Next, invoke all functions that work at the same level of abstraction within a higher-level wrapper function, which can be named process_images.
def process_images(data_json):
“””
Processes the images from the given JSON data.
Parameters:
data_json (list): A list of dictionaries containing image data.
“””
image_counter = 0
for item in data_json:
uri = get_image_uri(item)
tag_name = get_tag_name(item)
file_counter = get_file_counter(tag_name)
output_file = get_output_file(tag_name, file_counter)
download_image(uri, output_file)
image_counter += 1
return image_counter
Combine this with the previous code snippet to retrieve all tagged images, call the newly created function, and name the function download_images.
def download_images(training_key, project_id, take, skip):
headers = {
‘Training-key’: training_key,
‘Content-Type’: ‘application/json’
}
params = f”take={take}&skip={skip}”
url = f”/customvision/v3.0/training/projects/{project_id}/images/tagged?{params}”
conn = http.client.HTTPSConnection(‘southcentralus.api.cognitive.microsoft.com’)
conn.request(“GET”, url, “”, headers)
response = conn.getresponse()
data = response.read()
data_json = json.loads(data)
downloaded_images = process_images(data_json)
conn.close()
return downloaded_images
This function returns the number of downloaded images, which can be used as input for the tqdm progress bar.
with tqdm(total=total_images, desc=”Downloading images”) as pbar:
while skip < total_images:
downloaded_images = download_images(training_key, project_id, take, skip)
skip += take
pbar.update(downloaded_images)
The code is organised and straightforward now. Next, combine all components to create the main function, which will calculate the number of tagged images using the training key and project ID. Set up the main loop, update the “skip” value according to the fixed “take” parameter, and refresh the status bar as needed.
import os
import json
import urllib.request
import urllib.parse
import http.client
from tqdm import tqdm
#from dotenv import load_dotenv
#load_dotenv()
def main():
training_key =
project_id = # Replace with your actual training key and pro or use loadev and then os.getenv which is better for security
if not training_key or not project_id:
raise ValueError(“Environment variables TRAINING_KEY and PROJECT_ID must be set”)
tags = get_tags(training_key, project_id)
create_folders(tags)
take = 250
skip = 0
total_images = get_total_tagged_image_count(training_key, project_id)
with tqdm(total=total_images, desc=”Downloading images”) as pbar:
while skip < total_images:
downloaded_images = download_images(training_key, project_id, take, skip)
skip += take
pbar.update(downloaded_images)
The full code is available here.
import os
import json
import urllib.request
import urllib.parse
import http.client
from tqdm import tqdm
from dotenv import load_dotenv
load_dotenv()
def get_image_uri(item):
“””
Extracts the image URI from the given item.
Parameters:
item (dict): A dictionary containing image data.
Returns:
str: The URI of the image.
“””
return item[‘originalImageUri’]
def get_tag_name(item):
“””
Extracts the tag name from the given item.
Parameters:
item (dict): A dictionary containing image data.
Returns:
str: The tag name of the image.
“””
return item[“tags”][0][“tagName”]
def get_file_counter(tag_name):
“””
Counts the number of files in the directory corresponding to the tag name.
Parameters:
tag_name (str): The name of the tag.
Returns:
int: The number of files in the directory.
“””
return len([name for name in os.listdir(tag_name) if os.path.isfile(os.path.join(tag_name, name))])
def get_output_file(tag_name, file_counter):
“””
Constructs the output file path for the image.
Parameters:
tag_name (str): The name of the tag.
file_counter (int): The current file counter.
Returns:
str: The output file path.
“””
return os.path.join(tag_name, str(file_counter) + ‘.png’)
def download_image(uri, output_file):
“””
Downloads the image from the given URI to the specified output file.
Parameters:
uri (str): The URI of the image.
output_file (str): The path to save the downloaded image.
“””
try:
urllib.request.urlretrieve(uri, output_file)
except:
print(“Retry”)
urllib.request.urlretrieve(uri, output_file)
def process_images(data_json):
“””
Processes the images from the given JSON data.
Parameters:
data_json (list): A list of dictionaries containing image data.
“””
image_counter = 0
for item in data_json:
uri = get_image_uri(item)
tag_name = get_tag_name(item)
file_counter = get_file_counter(tag_name)
output_file = get_output_file(tag_name, file_counter)
download_image(uri, output_file)
image_counter += 1
return image_counter
def get_tags(training_key, project_id):
headers = {
‘Training-Key’: training_key
}
url = f”/customvision/v3.0/training/projects/{project_id}/tags”
conn = http.client.HTTPSConnection(‘southcentralus.api.cognitive.microsoft.com’)
conn.request(“GET”, url, “{body}”, headers)
response = conn.getresponse()
data = response.read()
data_json = json.loads(data)
conn.close()
return
def create_folders(tags):
for tag in tags:
folder = tag
if not os.path.exists(folder):
os.makedirs(folder)
def get_total_tagged_image_count(training_key, project_id):
headers = {
‘Training-key’: training_key,
‘Content-Type’: ‘application/json’
}
url = f”/customvision/v3.4-preview/training/projects/{project_id}/images/tagged/count”
conn = http.client.HTTPSConnection(‘southcentralus.api.cognitive.microsoft.com’)
conn.request(“GET”, url, “”, headers)
response = conn.getresponse()
data = response.read()
data_json = json.loads(data)
conn.close()
return data_json
def download_images(training_key, project_id, take, skip):
headers = {
‘Training-key’: training_key,
‘Content-Type’: ‘application/json’
}
params = f”take={take}&skip={skip}”
url = f”/customvision/v3.0/training/projects/{project_id}/images/tagged?{params}”
conn = http.client.HTTPSConnection(‘southcentralus.api.cognitive.microsoft.com’)
conn.request(“GET”, url, “”, headers)
response = conn.getresponse()
data = response.read()
data_json = json.loads(data)
downloaded_images = process_images(data_json)
conn.close()
return downloaded_images
def main():
training_key = os.getenv(“TRAINING_KEY”)
project_id = os.getenv(“PROJECT_ID”)
if not training_key or not project_id:
raise ValueError(“Environment variables TRAINING_KEY and PROJECT_ID must be set”)
tags = get_tags(training_key, project_id)
create_folders(tags)
take = 250
skip = 0
total_images = get_total_tagged_image_count(training_key, project_id)
with tqdm(total=total_images, desc=”Downloading images”) as pbar:
while skip < total_images:
downloaded_images = download_images(training_key, project_id, take, skip)
skip += take
pbar.update(downloaded_images)
if __name__ == "__main__":
main()
Conclusion
As we’ve seen, downloading tagged images from Azure Custom Vision is entirely achievable with a bit of scripting and the right use of the REST API. By breaking down the process into clear, maintainable functions you can efficiently organize and recover your valuable training data for audits, migration, or further experimentation.
I hope you’ve found this guide useful. With these techniques in hand, managing your machine learning assets becomes simpler and more reliable, freeing you to focus on creating and refining truly remarkable AI solutions. Happy coding.