{"slug": "ocr-lot-code-and-expiry-date-verification-for-medical-packaging", "title": "OCR Lot Code and Expiry Date Verification for Medical Packaging", "summary": "Medical device recalls hit a four-year high in 2024 with 1,059 events recorded in the US, and roughly 25% trace back to mislabeling of batch numbers and expiry dates on packaging. A new vision AI pipeline using Roboflow Workflows automates the detection and validation of these critical fields by training a localization model to find batch and expiry strips, reading the text with Google Gemini, and running format and expiry validation automatically. The system is designed to catch errors that human inspectors miss at line speed, and it can be adapted for surgical kit pouches, IVD reagent boxes, and implant labels across the medical packaging industry.", "body_md": "*Automatically find batch and expiry strips, read them, and auto-validate lot codes and dates on medical packaging lines with this vision AI pipeline.*\n\nPharmaceutical packaging lines print batch numbers and expiry dates on every pack that leaves the line. A wrong digit, a smudged field, a date that never printed correctly. Those get through manual inspection more often than they should.\n\n[ Medical device recalls hit a four-year high in 2024](https://meddeviceguide.com/blog/medical-device-recall-trends-2024-2026-statistics-root-causes-guide?ref=blog.roboflow.com), with 1,059 events recorded in the US alone. Roughly\n\n[. The stakes are the same across the industry.](https://www.qualityze.com/blogs/medical-device-recall-management-guidelines?ref=blog.roboflow.com)\n\n__25% trace back to mislabeling__Catching these on a packaging line is harder than it sounds. Batch and expiry fields sit in different positions across label layouts, ink density shifts between printhead passes, and some strips run vertically while others run horizontally. A human checker moving at line speed is going to miss some.\n\nThis guide walks through building a print verification pipeline in [ Roboflow Workflows](https://roboflow.com/workflows/build?ref=blog.roboflow.com). You'll train a localization model to find the batch and expiry strip, crop it, read the text with\n\n[, and run format and expiry validation automatically.](https://roboflow.com/model/google-gemini?ref=blog.roboflow.com)\n\n__Google Gemini__This pipeline is not limited to pharmaceutical packaging. Surgical kit pouches, IVD reagent boxes, and implant labels all carry the same fields. Swap the dataset, retrain the localization model on your label layout, and this Workflow carries over unchanged.\n\n## OCR Lot Code and Expiry Date Verification for Medical Packaging\n\nGo to [ Roboflow Universe](https://universe.roboflow.com/?ref=blog.roboflow.com) and search for the\n\n[. Roboflow Universe hosts over 250,000 open source datasets across industries.](https://universe.roboflow.com/college-hxirw/major-project-8anow?ref=blog.roboflow.com)\n\n__major project dataset__This dataset has 1,716 images of pharmaceutical packaging with two annotated classes: name for the product label region and date for the batch and expiry strip.\n\nThe variation covers ink density shifts, vertical and horizontal strip orientations, and partial occlusion from fold overlap. That is what makes your localization model robust before it sees a single production image.\n\nClick **Fork Dataset** to copy it into your workspace with all annotations intact.\n\n### Build the Workflow\n\n[Here is the workflow we will build](https://app.roboflow.com/workflows/embed/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3b3JrZmxvd0lkIjoiblhnWVJVWHdJc1g2OEY5V3NEQTciLCJ3b3Jrc3BhY2VJZCI6Im5JRk5DOGRjbU5OOXZ4d29ybWpoWTdCNjdQZTIiLCJ1c2VySWQiOiJuSUZOQzhkY21OTjl2eHdvcm1qaFk3QjY3UGUyIiwiaWF0IjoxNzgxMDkyNjUzfQ.y4GQ3DMH1BILeNwCaQKX3o05xW1J3KDASm-XANsjy0w?ref=blog.roboflow.com). Before building, here is what each block does and why it is in the chain.\n\n**Image Input:** entry point for every image**Object Detection Model:** locates the batch and expiry strip on the full image**Detections Filter:** passes only date class detections downstream**Dynamic Crop:** isolates the strip as a tight crop**Google Gemini:** extracts batch_number and expiry_date from the crop**Gemini Result Parser:** makes both fields addressable**Python Validation:** checks format and expiry date, returns PASS or FAIL**Bounding Box Visualization:** draws detected regions on the output image\n\n### Step 1: Train the Object Detection Model\n\nAfter forking the dataset, open it in your workspace, Select **Custom Training** to configure your model settings.\n\nChoose [ Roboflow RF-DETR](https://rfdetr.roboflow.com/latest/?ref=blog.roboflow.com) as your model architecture. RF-DETR is Roboflow's real-time object detection model that delivers high accuracy with faster convergence, which makes it a strong fit for label region detection.\n\nAdjust the train/valid/test split. The default 80/10/10 works well here: 1,373 images for training, 172 for validation, and 171 for testing.\n\nClick **Save** to confirm your split. If you want to control how long the model trains, open **Advanced Options** before starting and adjust the number of epochs.\n\nClick **Start Training**. When training finishes, the model card shows [ mAP](https://blog.roboflow.com/mean-average-precision/),\n\n[,](https://blog.roboflow.com/precision-and-recall/#:~:text=Machine%20Learning%20Video-,What%20is%20Precision%3F%20What%20can%20we%20learn%20from%20Precision%3F,-Roboflow%20user%20trained)\n\n__Precision__[, and](https://blog.roboflow.com/precision-and-recall/#:~:text=will%20waste%20water.-,What%20is%20Recall%3F%20What%20does%20Recall%20tell%20us%3F,-Let%27s%20imagine%20we)\n\n__Recall__[. This model achieved 91.2% mAP with 90.3% precision across both classes.](https://blog.roboflow.com/f1-score/)\n\n__F1__With your model trained, you are ready to build the Workflow.\n\n### Step 2: Add the Object Detection Model\n\nGo to **Workflows** from the side panel and create a new Workflow from scratch. To add a block, click the black **+** in the canvas and search for the block by name.\n\nUnder the Image section, connect inputs.image. This tells the block which image to run detection on.\n\nUnder the Model section, paste your model identifier from the training page. You can find it on your model card after training completes. It looks like this: major-project-8anow-oqg51/2.\n\nLeave Confidence Mode on Best (Recommended).\n\nThis block forms the core of the pipeline, as every downstream step relies on the bounding boxes it produces.\n\n### Step 3: Add the Detections Filter\n\nClick + and search for Detections Filter. Under Predictions, connect object_detection_model.predictions.\n\nUnder Operations, click Edit and configure the filter to pass only detections where class equals date. This drops the name detections and sends only the batch and expiry strip downstream to Gemini.\n\nWithout this block, Gemini receives crops from all detected regions, including the product name, which returns no useful batch or expiry data.\n\n### Step 4: Add Dynamic Crop\n\nClick **+** and search for **Dynamic Crop**. Under **Image to Crop**, connect inputs.image. Under **Regions of Interest**, connect detections_filter.predictions.\n\nLeave **Mask Opacity** at 0 and **Background Color** at 0,0,0.\n\nThis block cuts the detected batch and expiry strip out of the full packaging image and passes a tight crop to Gemini, giving it a clean region to read from.\n\n### Step 5: Add Google Gemini\n\nClick **+** and search for **Google Gemini**. Under **Image**, connect dynamic_crop.crops. Set **Task Type** to **Visual Question Answering**.\n\nUnder **Prompt**, paste the following:\n\n```\nExtract batch_number and expiry_date from this cropped label. \nReturn ONLY a valid JSON object with exactly these two fields: \n{\"batch_number\": \"\", \"expiry_date\": \"\"}. \nDo not include markdown, explanation, or extra text.\n```\n\nGemini reads the printed text directly from the crop and returns a clean JSON object with both fields. No OCR model training required.\n\n### Step 6: Add the Gemini Result Parser\n\nClick **+** and search for **Gemini Result Parser**. Under **gemini_json_string**, connect google_gemini.output.\n\nClick **Edit Code** and paste the following:\n\n``` python\ndef run(self, gemini_json_string):\n    import json, re\n    raw = str(gemini_json_string or \"\")\n    clean = re.sub(r\"``` json|```\", \"\", raw).strip()\n    try:\n        data = json.loads(clean)\n    except:\n        m = re.search(r\"\\{.*\\}\", clean, re.DOTALL)\n        data = json.loads(m.group(0)) if m else {}\n    batch = str(data.get(\"batch_number\") or \"\").strip()\n    expiry = str(data.get(\"expiry_date\") or \"\").strip()\n    return {\n        \"batch_number\": batch,\n        \"expiry_date\": expiry,\n        \"parsed_results\": {\"batch_number\": batch, \"expiry_date\": expiry}\n    }\n```\n\nThis strips any markdown formatting that Gemini occasionally adds and returns batch_number and expiry_date as clean addressable fields.\n\n### Step 7: Add the Python Validation Block\n\nClick **+** and search for **Python Block**. Under **parsed_results**, connect gemini_result_parser.parsed_results.\n\n* Python Validation*\n\nClick **Edit Code** and paste the following:\n\n``` python\ndef run(self, parsed_results):\n    import re\n    from datetime import datetime\n    data = parsed_results or {}\n    batch_number = str(data.get(\"batch_number\", \"\") or \"\").strip()\n    expiry_date = str(data.get(\"expiry_date\", \"\") or \"\").strip()\n    batch_valid = bool(re.match(r\"^[A-Za-z0-9\\-\\.\\/]{4,}$\", batch_number))\n    fmt1 = re.match(r\"^(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)\\.\\d{4}$\", expiry_date.upper())\n    fmt2 = re.match(r\"^(0[1-9]|1[0-2])/\\d{4}$\", expiry_date)\n    fmt3 = re.match(r\"^\\d{4}:(0[1-9]|1[0-2])$\", expiry_date)\n    expiry_format_valid = bool(fmt1 or fmt2 or fmt3)\n    expiry_valid = False\n    if expiry_format_valid:\n        try:\n            if fmt1:\n                exp = datetime.strptime(expiry_date.upper(), \"%b.%Y\")\n            elif fmt2:\n                exp = datetime.strptime(expiry_date, \"%m/%Y\")\n            else:\n                exp = datetime.strptime(expiry_date, \"%Y:%m\")\n            exp_end = datetime(exp.year + 1, 1, 1) if exp.month == 12 else datetime(exp.year, exp.month + 1, 1)\n            expiry_valid = exp_end > datetime.today()\n        except Exception:\n            expiry_valid = False\n    status = \"PASS\" if batch_valid and expiry_valid else \"FAIL\"\n    return {\n        \"status\": status,\n        \"results\": {\n            \"batch_number\": batch_number,\n            \"expiry_date\": expiry_date,\n            \"batch_valid\": batch_valid,\n            \"expiry_format_valid\": expiry_format_valid,\n            \"expiry_valid\": expiry_valid,\n            \"status\": status\n        }\n    }\n```\n\nThe block validates the batch number as an alphanumeric string of four or more characters and accepts three expiry date formats: MMM.YYYY, MM/YYYY, and YYYY:MM. If the expiry date is in the past the pack returns FAIL regardless of format. The comparison runs against datetime.today(), so the result always reflects the current date when the Workflow runs.\n\n### Step 8: Add Bounding Box Visualization\n\nClick **+** and search for **Bounding Box Visualization**. Under **Input Image**, connect inputs.image. Under **Predictions**,\n\nconnect object_detection_model.predictions. Leave Color Palette on **DEFAULT**.\n\n### Step 9: Configure the Outputs\n\nClick the **Outputs** block and add three outputs:\n\n- output_image pointed at bounding_box_visualization.image\n- validation_results pointed at python_validation.results\n- predictions pointed at object_detection_model.predictions\n\nClick **Save**. Your Workflow is ready to run.\n\n### Step 10: Test in the Workflow Editor\n\nOpen the Workflow editor and upload a test image from your forked dataset. Click **New Run** to run the pipeline.\n\nYour pipeline is fully wired and running. Head to the Results section to see what comes out.\n\n### Workflow Results\n\n**Test case 1: Clean label, status PASS**\n\nA pharmaceutical packaging box with a clearly printed batch and expiry strip. The localization model detects the region, Gemini extracts both fields, and the validation block confirms the batch number is valid and the expiry date is in the future.\n\nBoth fields present, format correct, expiry date valid. The pack clears verification.\n\n**Test case 2: Expired product, status FAIL**\n\nA sterile medical device pouch with a lot and expiry strip printed on the right edge. The localization model detects the strip, Gemini extracts both fields, and the validation block correctly flags the product as expired.\n\nThe batch number passes validation, but the expiry date is over five years in the past. The pack fails verification before it reaches the next stage.\n\n## Automate OCR Lot Code and Expiry Date Verification with Roboflow Agent\n\nWant more help? You could also just describe the problem you want to solve to Roboflow Agent in plain language and it creates the workflow for you. Watch the video below to see the Agent assemble the pipeline from prompts.\n\n## OCR Lot Code and Expiry Date Verification for Medical Packaging Conclusion\n\nMedical device [manufacturing](https://roboflow.com/industries/manufacturing?ref=blog.roboflow.com) lines run under strict traceability requirements. Surgical kit pouches, IVD reagent boxes, implant labels, and sterile barrier packaging all carry lot numbers and expiry dates. The same Workflow you built handles this without any structural changes.\n\nWhat changes is the dataset. Fork or build a dataset of your specific label layout and retrain the localization model on your date and name regions. Label positions vary across device types so the model needs to learn your layout. Everything downstream in the Workflow stays identical.\n\nFor [MES](https://blog.roboflow.com/manufacturing-execution-system/) or quality system integration, pass validation_results directly to your batch record database at the point of packaging. A FAIL status triggers a line stop before the pack moves to sterilization or final packaging, with no manual review step.\n\nIf your label carries a UDI field, extend the Gemini prompt with no retraining required:\n\nExtract batch_number, expiry_date, and udi from this cropped label.\n\nReturn ONLY a valid JSON object with exactly these three fields:\n\n{\"batch_number\": \"\", \"expiry_date\": \"\", \"udi\": \"\"}.\n\nDo not include markdown, explanation, or extra text.\n\nThe Python validation block then adds a UDI format check alongside the existing batch and expiry logic.\n\n**Further reading:**\n\n**Cite this Post**\n\nUse the following entry to cite this post in your research:\n\n[Mostafa Ibrahim](/author/mostafa/). (Jun 11, 2026).\nOCR Lot Code and Expiry Date Verification for Medical Packaging. Roboflow Blog: https://blog.roboflow.com/ocr-lot-code-and-expiry-date-verification/", "url": "https://wpnews.pro/news/ocr-lot-code-and-expiry-date-verification-for-medical-packaging", "canonical_source": "https://blog.roboflow.com/ocr-lot-code-and-expiry-date-verification/", "published_at": "2026-06-11 20:06:21+00:00", "updated_at": "2026-06-11 21:10:55.372791+00:00", "lang": "en", "topics": ["computer-vision", "artificial-intelligence", "machine-learning", "ai-products", "ai-tools"], "entities": ["Roboflow", "Google Gemini", "Roboflow Workflows"], "alternates": {"html": "https://wpnews.pro/news/ocr-lot-code-and-expiry-date-verification-for-medical-packaging", "markdown": "https://wpnews.pro/news/ocr-lot-code-and-expiry-date-verification-for-medical-packaging.md", "text": "https://wpnews.pro/news/ocr-lot-code-and-expiry-date-verification-for-medical-packaging.txt", "jsonld": "https://wpnews.pro/news/ocr-lot-code-and-expiry-date-verification-for-medical-packaging.jsonld"}}