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 setsHeadModuleRatioandTailModuleRatio. - The
BF_NONSTANDARD_BARCODEformat flag combined withStandardFormat: BF_CODE_39lets 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
HeadModuleRatioandTailModuleRatioin 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
StandardFormatselects the base symbology (BF_CODE_39).-
HeadModuleRatioandTailModuleRatiodefine 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("-------------------------------------------------")

Common Issues & Edge Cases
- No results returned for a nonstandard barcode: Ensure
BF_NONSTANDARD_BARCODEis included inBarcodeFormatIdsin both theBarcodeFormatSpecificationOptionsand theBarcodeReaderTaskSettingOptions. 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 withinit_settings_from_file(), pass""(empty string) or"CV_0"as the template name tocapture(). Passing a named built-in preset (e.g.,PT_READ_BARCODES) after loading a custom template will override your custom configuration.