From 15f05a6236aa8fe51c8aa29a1993a62e493ee78b Mon Sep 17 00:00:00 2001 From: Chien Yuan Chang Date: Tue, 29 Jul 2025 11:14:44 -0700 Subject: [PATCH] docs: review notebooks/build_person_directory.ipynb --- notebooks/build_person_directory.ipynb | 118 +++++++++++++------------ 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/notebooks/build_person_directory.ipynb b/notebooks/build_person_directory.ipynb index 4840de4..676f790 100644 --- a/notebooks/build_person_directory.ipynb +++ b/notebooks/build_person_directory.ipynb @@ -15,7 +15,7 @@ "source": [ "## Objective\n", "\n", - " This notebook demonstrates how to identify faces in an image against a known set of persons. It begins by building a Person Directory, where each subfolder in a specified directory represents an individual. For each subfolder, a person is created and all face images within it are enrolled to that person.\n", + "This notebook demonstrates how to identify faces in an image by matching them against a known set of persons. It begins by building a Person Directory, where each subfolder in a specified directory represents an individual. For each subfolder, a person entry is created, and all face images within that folder are enrolled to that person.\n", "\n", "| Enrollment | Searching |\n", "| :-: | :-: |\n", @@ -27,15 +27,19 @@ "id": "80a375ca", "metadata": {}, "source": [ - "## Create Azure content understanding face client\n", - "> The [AzureContentUnderstandingFaceClient](../python/content_understanding_face_client.py) is a utility class for interacting with the Content Understanding Face service. Before the official SDK is released, this acts as a lightweight SDK. Set the constants **AZURE_AI_ENDPOINT**, **AZURE_AI_API_VERSION**, and **AZURE_AI_API_KEY** with your Azure AI Service information.\n", + "## Create Azure Content Understanding Face Client\n", + "\n", + "> The [AzureContentUnderstandingFaceClient](../python/content_understanding_face_client.py) is a utility class designed for interacting with the Content Understanding Face service. Before the official SDK is released, this acts as a lightweight SDK.\n", + "\n", + "> Set the constants **AZURE_AI_ENDPOINT**, **AZURE_AI_API_VERSION**, and **AZURE_AI_API_KEY** with your Azure AI Service credentials.\n", "\n", "> ⚠️ Important:\n", "You must update the code below to match your Azure authentication method.\n", - "Look for the `# IMPORTANT` comments and modify those sections accordingly.\n", - "If you skip this step, the sample may not run correctly.\n", + "Check for the `# IMPORTANT` comments and modify those sections accordingly.\n", + "If you skip this step, the sample may not run as expected.\n", "\n", - "> ⚠️ Note: Using a subscription key works, but using a token provider with Azure Active Directory (AAD) is much safer and is highly recommended for production environments." + "> ⚠️ Note:\n", + "Using a subscription key is supported, but using a token provider with Azure Active Directory (AAD) is more secure and strongly recommended for production environments." ] }, { @@ -52,7 +56,7 @@ "from dotenv import find_dotenv, load_dotenv\n", "from azure.identity import DefaultAzureCredential, get_bearer_token_provider\n", "\n", - "# import utility package from python samples root directory\n", + "# Import utility package from Python samples root directory\n", "parent_dir = Path.cwd().parent\n", "sys.path.append(str(parent_dir))\n", "from python.content_understanding_face_client import AzureContentUnderstandingFaceClient\n", @@ -70,7 +74,7 @@ " token_provider=token_provider,\n", " # IMPORTANT: Uncomment this if using subscription key\n", " # subscription_key=os.getenv(\"AZURE_AI_API_KEY\"),\n", - " x_ms_useragent=\"azure-ai-content-understanding-python/build_person_directory\", # This header is used for sample usage telemetry, please comment out this line if you want to opt out.\n", + " x_ms_useragent=\"azure-ai-content-understanding-python/build_person_directory\", # Used for sample usage telemetry; comment out to opt out.\n", ")" ] }, @@ -93,34 +97,34 @@ "import uuid\n", "folder_path = \"../data/face/enrollment_data\" # Replace with the path to your folder containing subfolders of images\n", "\n", - "# Create a person directory\n", + "# Create a person directory with a unique ID\n", "person_directory_id = f\"person_directory_id_{uuid.uuid4().hex[:8]}\"\n", "client.create_person_directory(person_directory_id)\n", "logging.info(f\"Created person directory with ID: {person_directory_id}\")\n", "\n", - "# Iterate through all subfolders in the folder_path\n", + "# Iterate through all subfolders in the enrollment folder\n", "for subfolder_name in os.listdir(folder_path):\n", " subfolder_path = os.path.join(folder_path, subfolder_name)\n", " if os.path.isdir(subfolder_path):\n", " person_name = subfolder_name\n", - " # Add a person for each subfolder\n", + " # Add a person entry for each subfolder\n", " person = client.add_person(person_directory_id, tags={\"name\": person_name})\n", - " logging.info(f\"Created person {person_name} with person_id: {person['personId']}\")\n", + " logging.info(f\"Created person '{person_name}' with person_id: {person['personId']}\")\n", " if person:\n", - " # Iterate through all images in the subfolder\n", + " # Iterate through all image files in the subfolder\n", " for filename in os.listdir(subfolder_path):\n", " if filename.lower().endswith(('.png', '.jpg', '.jpeg')):\n", " image_path = os.path.join(subfolder_path, filename)\n", - " # Convert image to base64\n", + " # Convert image to base64 encoded string\n", " image_data = AzureContentUnderstandingFaceClient.read_file_to_base64(image_path)\n", - " # Add a face to the Person Directory and associate it to the added person\n", + " # Add the face to the Person Directory and associate it with the person\n", " face = client.add_face(person_directory_id, image_data, person['personId'])\n", " if face:\n", - " logging.info(f\"Added face from {filename} with face_id: {face['faceId']} to person_id: {person['personId']}\")\n", + " logging.info(f\"Added face from '{filename}' with face_id: {face['faceId']} to person_id: {person['personId']}\")\n", " else:\n", - " logging.warning(f\"Failed to add face from {filename} to person_id: {person['personId']}\")\n", + " logging.warning(f\"Failed to add face from '{filename}' to person_id: {person['personId']}\")\n", "\n", - "logging.info(\"Done\")" + "logging.info(\"Person directory creation complete.\")" ] }, { @@ -128,8 +132,8 @@ "id": "6a5a058c", "metadata": {}, "source": [ - "### Identifying person\n", - "Detect multiple faces in an image and identify each one by matching it against enrolled persons in the Person Directory." + "### Identifying persons\n", + "Detect multiple faces in an image and identify each by matching against enrolled persons in the Person Directory." ] }, { @@ -144,14 +148,14 @@ "# Detect faces in the test image\n", "image_data = AzureContentUnderstandingFaceClient.read_file_to_base64(test_image_path)\n", "detected_faces = client.detect_faces(data=image_data)\n", - "for face in detected_faces['detectedFaces']:\n", + "for face in detected_faces.get('detectedFaces', []):\n", " identified_persons = client.identify_person(person_directory_id, image_data, face['boundingBox'])\n", " if identified_persons.get(\"personCandidates\"):\n", " person = identified_persons[\"personCandidates\"][0]\n", " name = person.get(\"tags\", {}).get(\"name\", \"Unknown\")\n", - " logging.info(f\"Detected person: {name} with confidence: {person.get('confidence', 0)} at bounding box: {face['boundingBox']}\")\n", + " logging.info(f\"Detected person: {name} with confidence: {person.get('confidence', 0):.2f} at bounding box: {face['boundingBox']}\")\n", "\n", - "logging.info(\"Done\")" + "logging.info(\"Identification complete.\")" ] }, { @@ -160,7 +164,7 @@ "metadata": {}, "source": [ "### Adding and associating a new face\n", - "You can add a new face to the Person Directory and associate it with an existing person." + "You can add a new face image to the Person Directory and associate it with an existing person." ] }, { @@ -170,17 +174,17 @@ "metadata": {}, "outputs": [], "source": [ - "new_face_image_path = \"new_face_image_path\" # The path to the face image you want to add.\n", - "existing_person_id = \"existing_person_id\" # The unique ID of the person to whom the face should be associated.\n", + "new_face_image_path = \"new_face_image_path\" # Path to the new face image to be added.\n", + "existing_person_id = \"existing_person_id\" # Unique ID of the person to associate the face with.\n", "\n", - "# Convert the new face image to base64\n", + "# Convert the new face image to a base64 encoded string\n", "image_data = AzureContentUnderstandingFaceClient.read_file_to_base64(new_face_image_path)\n", - "# Add the new face to the person directory and associate it with the existing person\n", + "# Add the new face to the Person Directory and associate it with the existing person\n", "face = client.add_face(person_directory_id, image_data, existing_person_id)\n", "if face:\n", - " logging.info(f\"Added face from {new_face_image_path} with face_id: {face['faceId']} to person_id: {existing_person_id}\")\n", + " logging.info(f\"Added face from '{new_face_image_path}' with face_id: {face['faceId']} to person_id: {existing_person_id}\")\n", "else:\n", - " logging.warning(f\"Failed to add face from {new_face_image_path} to person_id: {existing_person_id}\")" + " logging.warning(f\"Failed to add face from '{new_face_image_path}' to person_id: {existing_person_id}\")" ] }, { @@ -190,7 +194,7 @@ "source": [ "### Associating a list of already enrolled faces\n", "\n", - "You can associate a list of already enrolled faces in the Person Directory with their respective persons. This is useful if you have existing face IDs to link to specific persons." + "You can associate a list of existing face IDs with their respective persons in the Person Directory. This is useful when linking pre-enrolled faces to specific persons." ] }, { @@ -200,10 +204,10 @@ "metadata": {}, "outputs": [], "source": [ - "existing_person_id = \"existing_person_id\" # The unique ID of the person to whom the face should be associated.\n", - "existing_face_id_list = [\"existing_face_id_1\", \"existing_face_id_2\"] # The list of face IDs to be associated.\n", + "existing_person_id = \"existing_person_id\" # Unique ID of the person to associate faces with.\n", + "existing_face_id_list = [\"existing_face_id_1\", \"existing_face_id_2\"] # List of face IDs to associate.\n", "\n", - "# Associate the existing face IDs with the existing person\n", + "# Associate the listed face IDs with the specified person\n", "client.update_person(person_directory_id, existing_person_id, face_ids=existing_face_id_list)" ] }, @@ -213,7 +217,7 @@ "metadata": {}, "source": [ "### Associating and disassociating a face from a person\n", - "You can associate or disassociate a face from a person in the Person Directory. Associating a face links it to a specific person, while disassociating removes this link." + "You can link a face to a person or remove such a link in the Person Directory. Associating a face connects it to a specific person, while disassociating removes this connection." ] }, { @@ -223,18 +227,18 @@ "metadata": {}, "outputs": [], "source": [ - "existing_face_id = \"existing_face_id\" # The unique ID of the face.\n", + "existing_face_id = \"existing_face_id\" # Unique ID of the face.\n", "\n", - "# Remove the association of the existing face ID from the person\n", - "client.update_face(person_directory_id, existing_face_id, person_id=\"\") # The person_id is set to \"\" to remove the association\n", - "logging.info(f\"Removed association of face_id: {existing_face_id} from the existing person_id\")\n", - "logging.info(client.get_face(person_directory_id, existing_face_id)) # This will return the face information without the person association\n", + "# Remove the association of the face from its person by setting person_id to an empty string\n", + "client.update_face(person_directory_id, existing_face_id, person_id=\"\")\n", + "logging.info(f\"Removed association of face_id: {existing_face_id} from its person.\")\n", + "logging.info(client.get_face(person_directory_id, existing_face_id)) # Returns face information without person association\n", "\n", - "# Associate the existing face ID with a person\n", - "existing_person_id = \"existing_person_id\" # The unique ID of the person to be associated with the face.\n", + "# Associate the face ID with a person\n", + "existing_person_id = \"existing_person_id\" # Unique ID of the person to associate the face with.\n", "client.update_face(person_directory_id, existing_face_id, person_id=existing_person_id)\n", "logging.info(f\"Associated face_id: {existing_face_id} with person_id: {existing_person_id}\")\n", - "logging.info(client.get_face(person_directory_id, existing_face_id)) # This will return the face information with the new person association" + "logging.info(client.get_face(person_directory_id, existing_face_id)) # Returns face information with updated person association" ] }, { @@ -243,7 +247,7 @@ "metadata": {}, "source": [ "### Updating metadata (tags and descriptions)\n", - "You can add or update tags for individual persons, and both descriptions and tags for the Person Directory. These metadata fields help organize, filter, and manage your directory." + "You can add or update tags for individual persons as well as update the description and tags for the entire Person Directory. These metadata fields help organize, filter, and manage your directory effectively." ] }, { @@ -263,11 +267,11 @@ " tags=person_directory_tags\n", ")\n", "logging.info(f\"Updated Person Directory with description: '{person_directory_description}' and tags: {person_directory_tags}\")\n", - "logging.info(client.get_person_directory(person_directory_id)) # This will return the updated person directory information\n", + "logging.info(client.get_person_directory(person_directory_id)) # Returns the updated Person Directory information\n", "\n", "# Update the tags for an individual person\n", - "existing_person_id = \"existing_person_id\" # The unique ID of the person to update.\n", - "person_tags = {\"role\": \"tester\", \"department\": \"engineering\", \"name\": \"\"} # This will remove the name tag from the person.\n", + "existing_person_id = \"existing_person_id\" # Unique ID of the person to update\n", + "person_tags = {\"role\": \"tester\", \"department\": \"engineering\", \"name\": \"\"} # This will remove the 'name' tag from the person\n", "\n", "client.update_person(\n", " person_directory_id,\n", @@ -275,7 +279,7 @@ " tags=person_tags\n", ")\n", "logging.info(f\"Updated person with person_id: {existing_person_id} with tags: {person_tags}\")\n", - "logging.info(client.get_person(person_directory_id, existing_person_id)) # This will return the updated person information" + "logging.info(client.get_person(person_directory_id, existing_person_id)) # Returns the updated person information" ] }, { @@ -284,7 +288,7 @@ "metadata": {}, "source": [ "### Deleting a face\n", - "You can also delete a specific face. Once the face is deleted, the association between the face and its associated person is removed." + "You can delete a specific face from the Person Directory. Once deleted, the association between the face and the person is removed." ] }, { @@ -294,7 +298,7 @@ "metadata": {}, "outputs": [], "source": [ - "existing_face_id = \"existing_face_id\" # The unique ID of the face to delete.\n", + "existing_face_id = \"existing_face_id\" # Unique ID of the face to delete\n", "\n", "client.delete_face(person_directory_id, existing_face_id)\n", "logging.info(f\"Deleted face with face_id: {existing_face_id}\")" @@ -307,7 +311,7 @@ "source": [ "### Deleting a person\n", "\n", - "When a person is deleted from the Person Directory, all the faces associated with that person remain in the Person Directory, but the association between the person and the faces is removed. This means the faces are no longer associated to any person in the Person Directory." + "When a person is deleted from the Person Directory, all faces associated with that person remain in the directory but their association with the person is removed. This means the faces are no longer linked to any person." ] }, { @@ -317,7 +321,7 @@ "metadata": {}, "outputs": [], "source": [ - "existing_person_id = \"existing_person_id\" # The unique ID of the person to delete.\n", + "existing_person_id = \"existing_person_id\" # Unique ID of the person to delete\n", "\n", "client.delete_person(person_directory_id, existing_person_id)\n", "logging.info(f\"Deleted person with person_id: {existing_person_id}\")" @@ -330,7 +334,7 @@ "source": [ "### Deleting a person and their associated faces\n", "\n", - "To completely remove a person and all their associated faces from the Person Directory, you can delete the person along with their face associations. This operation ensures that no residual data related to the person remains in the directory." + "To completely remove a person and all their associated faces from the Person Directory, delete the faces first, then delete the person. This ensures no residual data remains linked to that person." ] }, { @@ -340,15 +344,15 @@ "metadata": {}, "outputs": [], "source": [ - "existing_person_id = \"existing_person_id\" # The unique ID of the person to delete.\n", + "existing_person_id = \"existing_person_id\" # Unique ID of the person to delete\n", "\n", - "# Get the list of face IDs associated with the person\n", + "# Retrieve the list of face IDs associated with the person\n", "response = client.get_person(person_directory_id, existing_person_id)\n", "face_ids = response.get('faceIds', [])\n", "\n", "# Delete each face associated with the person\n", "for face_id in face_ids:\n", - " logging.info(f\"Deleting face with face_id: {face_id} from person_id: {existing_person_id}\")\n", + " logging.info(f\"Deleting face with face_id: {face_id} associated with person_id: {existing_person_id}\")\n", " client.delete_face(person_directory_id, face_id)\n", "\n", "# Delete the person after deleting all associated faces\n", @@ -378,4 +382,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file