How to Scan Nonstandard and Damaged 1D Barcodes with Dynamsoft Barcode Reader in Python

Start and stop characters are defined by 1D (linear) barcode standards. Sometimes you’ll encounter nonstandard barcodes where these characters differ. This guide takes Code 39 as the example and shows how to decode such barcodes in Python with the Dynamsoft Barcode Reader (via the Dynamsoft Capture Vision Bundle) by supplying a custom template.

What you’ll build: A Python script that decodes nonstandard Code 39 barcodes — including variants with + or - start/stop characters — using the Dynamsoft Capture Vision Bundle and a custom JSON configuration template.

Key Takeaways

  • Dynamsoft Barcode Reader decodes nonstandard 1D barcodes (e.g., Code 39 with + or - start/stop characters) by supplying a JSON template that sets HeadModuleRatio and TailModuleRatio.
  • The BF_NONSTANDARD_BARCODE format flag combined with StandardFormat: BF_CODE_39 lets you define custom symbology variants without modifying application logic.
  • Custom templates are portable JSON files loaded at runtime via init_settings_from_file(), keeping your Python code clean and decoupled from configuration.
  • This technique applies wherever nonstandard 1D barcodes appear — industrial label printers, legacy factory systems, or proprietary labeling workflows.

Common Developer Questions

  • How do I read a Code 39 barcode with non-standard start and stop characters in Python?
  • What are HeadModuleRatio and TailModuleRatio in Dynamsoft Barcode Reader templates?
  • Why does Dynamsoft Barcode Reader return no results when scanning a nonstandard 1D barcode?

Prerequisites

  • Python 3.x
  • A free trial license for Dynamsoft Barcode Reader SDK
  • Install the SDK:

      pip install dynamsoft-capture-vision-bundle
    

Dataset: Standard vs. Nonstandard Code 39 Samples

Standard Code 39 (start/stop *)

Nonstandard Code 39 (start/stop +)

Nonstandard Code 39 (start/stop -)

Step 1: Read a Standard Code 39 Barcode

Here is the code snippet for decoding a standard 1D barcode image:

    from dynamsoft_capture_vision_bundle import *

    license_key = "LICENSE-KEY" # https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform
    cvr_instance = CaptureVisionRouter()
    error_code, error_message = LicenseManager.init_license(license_key)
    result = cvr_instance.capture(filename, EnumPresetTemplate.PT_READ_BARCODES.value)
    
    if result.get_error_code() != EnumErrorCode.EC_OK:
        print("Error:", result.get_error_code(),
                result.get_error_string())
    else:
        items = result.get_items()
        print('Found {} barcodes.'.format(len(items)))
        for item in items:
            format_type = item.get_format_string()
            text = item.get_text()
            print("Barcode Format:", format_type)
            print("Barcode Text:", text)

            location = item.get_location()
            x1 = location.points[0].x
            y1 = location.points[0].y
            x2 = location.points[1].x
            y2 = location.points[1].y
            x3 = location.points[2].x
            y3 = location.points[2].y
            x4 = location.points[3].x
            y4 = location.points[3].y
            print("Location Points:")
            print("({}, {})".format(x1, y1))
            print("({}, {})".format(x2, y2))
            print("({}, {})".format(x3, y3))
            print("({}, {})".format(x4, y4))
            print("-------------------------------------------------")

Step 2: Configure Custom Templates to Decode Nonstandard 1D Barcodes

To decode variants (e.g., Code 39 using + or - as start/stop), provide a custom template that tells the SDK what to expect.

Minimal template for Code 39 with + start/stop Save as template_plus.json:

{
  "BarcodeFormatSpecificationOptions": [
    {
      "BarcodeFormatIds": [
        "BF_DEFAULT",
        "BF_NONSTANDARD_BARCODE"
      ],
      "HeadModuleRatio": "131113131",
      "Name": "bfs_0",
      "StandardFormat": "BF_CODE_39",
      "TailModuleRatio": "131113131"
    }
  ],
  "BarcodeReaderTaskSettingOptions": [
    {
      "BarcodeFormatIds": [
        "BF_DEFAULT",
        "BF_NONSTANDARD_BARCODE"
      ],
      "BarcodeFormatSpecificationNameArray": [
        "bfs_0"
      ],
      "Name": "dbr_task_0",
      "SectionArray": [
        {
          "ImageParameterName": "IP_0",
          "Section": "ST_REGION_PREDETECTION",
          "StageArray": [
            {
              "Stage": "SST_PREDETECT_REGIONS"
            }
          ]
        },
        {
          "ImageParameterName": "IP_0",
          "Section": "ST_BARCODE_LOCALIZATION",
          "StageArray": [
            {
              "Stage": "SST_LOCALIZE_CANDIDATE_BARCODES"
            },
            {
              "Stage": "SST_LOCALIZE_BARCODES"
            }
          ]
        },
        {
          "ImageParameterName": "IP_0",
          "Section": "ST_BARCODE_DECODING",
          "StageArray": [
            {
              "Stage": "SST_RESIST_DEFORMATION"
            },
            {
              "Stage": "SST_COMPLEMENT_BARCODE"
            },
            {
              "Stage": "SST_SCALE_BARCODE_IMAGE"
            },
            {
              "Stage": "SST_DECODE_BARCODES"
            }
          ]
        }
      ]
    }
  ],
  "CaptureVisionTemplates": [
    {
      "ImageROIProcessingNameArray": [
        "roi_default"
      ],
      "Name": "CV_0",
      "Timeout": 1000000
    }
  ],
  "ImageParameterOptions": [
    {
      "ApplicableStages": [
        {
          "Stage": "SST_INPUT_COLOR_IMAGE"
        },
        {
          "Stage": "SST_SCALE_IMAGE"
        },
        {
          "Stage": "SST_CONVERT_TO_GRAYSCALE"
        },
        {
          "Stage": "SST_TRANSFORM_GRAYSCALE"
        },
        {
          "Stage": "SST_ENHANCE_GRAYSCALE"
        },
        {
          "Stage": "SST_BINARIZE_IMAGE"
        },
        {
          "Stage": "SST_DETECT_TEXTURE"
        },
        {
          "Stage": "SST_REMOVE_TEXTURE_FROM_GRAYSCALE"
        },
        {
          "Stage": "SST_BINARIZE_TEXTURE_REMOVED_GRAYSCALE"
        },
        {
          "Stage": "SST_FIND_CONTOURS"
        },
        {
          "Stage": "SST_DETECT_SHORTLINES"
        },
        {
          "Stage": "SST_ASSEMBLE_LINES"
        },
        {
          "Stage": "SST_DETECT_TEXT_ZONES"
        },
        {
          "Stage": "SST_REMOVE_TEXT_ZONES_FROM_BINARY"
        }
      ],
      "Name": "IP_0"
    }
  ],
  "TargetROIDefOptions": [
    {
      "Name": "roi_default",
      "TaskSettingNameArray": [
        "dbr_task_0"
      ]
    }
  ]
}

Minimal template for Code 39 with - start/stop

Save as template_minus.json. Only the tail/head ratios differ:

{
  "BarcodeFormatSpecificationOptions": [
    {
      "BarcodeFormatIds": [
        "BF_DEFAULT",
        "BF_NONSTANDARD_BARCODE"
      ],
      "HeadModuleRatio": "131111313",
      "Name": "bfs_0",
      "StandardFormat": "BF_CODE_39",
      "TailModuleRatio": "131111313"
    }
  ],
  "BarcodeReaderTaskSettingOptions": [
    {
      "BarcodeFormatIds": [
        "BF_DEFAULT",
        "BF_NONSTANDARD_BARCODE"
      ],
      "BarcodeFormatSpecificationNameArray": [
        "bfs_0"
      ],
      "Name": "dbr_task_0",
      "SectionArray": [
        {
          "ImageParameterName": "IP_0",
          "Section": "ST_REGION_PREDETECTION",
          "StageArray": [
            {
              "Stage": "SST_PREDETECT_REGIONS"
            }
          ]
        },
        {
          "ImageParameterName": "IP_0",
          "Section": "ST_BARCODE_LOCALIZATION",
          "StageArray": [
            {
              "Stage": "SST_LOCALIZE_CANDIDATE_BARCODES"
            },
            {
              "Stage": "SST_LOCALIZE_BARCODES"
            }
          ]
        },
        {
          "ImageParameterName": "IP_0",
          "Section": "ST_BARCODE_DECODING",
          "StageArray": [
            {
              "Stage": "SST_RESIST_DEFORMATION"
            },
            {
              "Stage": "SST_COMPLEMENT_BARCODE"
            },
            {
              "Stage": "SST_SCALE_BARCODE_IMAGE"
            },
            {
              "Stage": "SST_DECODE_BARCODES"
            }
          ]
        }
      ]
    }
  ],
  "CaptureVisionTemplates": [
    {
      "ImageROIProcessingNameArray": [
        "roi_default"
      ],
      "Name": "CV_0",
      "Timeout": 1000000
    }
  ],
  "ImageParameterOptions": [
    {
      "ApplicableStages": [
        {
          "Stage": "SST_INPUT_COLOR_IMAGE"
        },
        {
          "Stage": "SST_SCALE_IMAGE"
        },
        {
          "Stage": "SST_CONVERT_TO_GRAYSCALE"
        },
        {
          "Stage": "SST_TRANSFORM_GRAYSCALE"
        },
        {
          "Stage": "SST_ENHANCE_GRAYSCALE"
        },
        {
          "Stage": "SST_BINARIZE_IMAGE"
        },
        {
          "Stage": "SST_DETECT_TEXTURE"
        },
        {
          "Stage": "SST_REMOVE_TEXTURE_FROM_GRAYSCALE"
        },
        {
          "Stage": "SST_BINARIZE_TEXTURE_REMOVED_GRAYSCALE"
        },
        {
          "Stage": "SST_FIND_CONTOURS"
        },
        {
          "Stage": "SST_DETECT_SHORTLINES"
        },
        {
          "Stage": "SST_ASSEMBLE_LINES"
        },
        {
          "Stage": "SST_DETECT_TEXT_ZONES"
        },
        {
          "Stage": "SST_REMOVE_TEXT_ZONES_FROM_BINARY"
        }
      ],
      "Name": "IP_0"
    }
  ],
  "TargetROIDefOptions": [
    {
      "Name": "roi_default",
      "TaskSettingNameArray": [
        "dbr_task_0"
      ]
    }
  ]
}

Key Template Parameters Explained

Parameter Purpose Example Value
StandardFormat Base symbology to extend BF_CODE_39
HeadModuleRatio Module ratio of the start character 131113131 (maps to +)
TailModuleRatio Module ratio of the stop character 131113131 (maps to +)
BF_NONSTANDARD_BARCODE Enables nonstandard barcode recognition Include in BarcodeFormatIds

Notes

  • StandardFormat selects the base symbology (BF_CODE_39).
  • HeadModuleRatio and TailModuleRatio define the start and stop characters. From the Code 39 character table:

      131113131 → +, 131111313 → -.
    

Now modify the Python code to read the nonstandard 1D barcodes.

from dynamsoft_capture_vision_bundle import *

json_file = None
 
if special_character == '+':
    json_file = r"template_plus.json"
 
if special_character == '-':
    json_file = r"template_minus.json"
 
if json_file == None:
    return
 

license_key = "LICENSE-KEY" 
cvr_instance = CaptureVisionRouter()
error_code, error_message = LicenseManager.init_license(license_key)

cvr_instance.init_settings_from_file(json_file) 
result = cvr_instance.capture(filename, "") # The empty string means using the default template

if result.get_error_code() != EnumErrorCode.EC_OK:
    print("Error:", result.get_error_code(),
            result.get_error_string())
else:
    items = result.get_items()
    print('Found {} barcodes.'.format(len(items)))
    for item in items:
        format_type = item.get_format_string()
        text = item.get_text()
        print("Barcode Format:", format_type)
        print("Barcode Text:", text)

        location = item.get_location()
        x1 = location.points[0].x
        y1 = location.points[0].y
        x2 = location.points[1].x
        y2 = location.points[1].y
        x3 = location.points[2].x
        y3 = location.points[2].y
        x4 = location.points[3].x
        y4 = location.points[3].y
        print("Location Points:")
        print("({}, {})".format(x1, y1))
        print("({}, {})".format(x2, y2))
        print("({}, {})".format(x3, y3))
        print("({}, {})".format(x4, y4))
        print("-------------------------------------------------")

nonstandard 1D barcode recognition

Common Issues & Edge Cases

  • No results returned for a nonstandard barcode: Ensure BF_NONSTANDARD_BARCODE is included in BarcodeFormatIds in both the BarcodeFormatSpecificationOptions and the BarcodeReaderTaskSettingOptions. Omitting it from either block causes the SDK to skip nonstandard detection entirely.
  • Wrong HeadModuleRatio / TailModuleRatio: The ratio string must exactly match the bar/space pattern of the custom start/stop character. Use a barcode analysis tool or the Code 39 character table to derive the correct sequence before configuring the template.
  • Template not applied — empty string passed to capture(): When loading a custom template with init_settings_from_file(), pass "" (empty string) or "CV_0" as the template name to capture(). Passing a named built-in preset (e.g., PT_READ_BARCODES) after loading a custom template will override your custom configuration.

Source Code

https://github.com/yushulx/python-barcode-qrcode-sdk/tree/main/examples/official/nonstandard_1D_barcode