Automatically reads player contribution screenshots using OCR and writes the extracted values into the correct rows of the Eden rewards Excel spreadsheet.
- Python 3.10 or later (
py --versionto check) - Install dependencies once:
py -m pip install -r requirements.txt
EasyOCR downloads its model files (~50 MB) on first use. This happens automatically and only once.
py main.py
Processes all screenshots in x[seasonNumber]-screenshots#/ and prints a table of every
player, extracted values, and the Excel row they would be written to. Nothing
is written to the spreadsheet.
It is useful to be able to test this first to check that the OCR is extracting values correctly and that the correct Excel rows are being identified before committing any writes.
To test 10 images as an example
py main.py --limit 10
py main.py --write
Shows the same preview table, then asks:
Write [xxx] rows to the spreadsheet? [y/N]
Type y to confirm. Any row with an ERROR is always skipped regardless.
py main.py --write --yes
py main.py --image "x[seasonNumber]-screenshots#\filename.jpg"
Use this for the ~9 top-ranked players where the small single-digit ranking number cannot be read by OCR. Look at the screenshot, confirm the ranking number yourself, then run:
py main.py --image "x[seasonNumber]-screenshots#\filename.jpg" --set-ranking 1 --write --yes
Replace 1 with the actual ranking shown in the screenshot.
For example, Player1 as rank 1 will have his screenshot read with ranking None and an ERROR status. Running the above command with --set-ranking 1 will override the OCR result and write his data to the correct row in the spreadsheet.
# Player Row Contrib Season Pts Merits Demo Occupy Status
────────────────────────────────────────────────────────────────────────────────────────────────────────
116 Chris 120 1,234,567 5,678,901 2,345,678 1,234,567 987 OK
131 Jenny 135 9,876,543 12,345,678 4,567,890 876,543 1,234 WARN: ...
7 Bungle ? — — — — — ERROR: Ranking not found — needs manual entry (player: Bungle)
| Column | Meaning |
|---|---|
# |
Ranking extracted from screenshot (col B lookup key) |
Player |
Player name with guild tag stripped |
Row |
Excel row number the data will be written to (? = not found) |
| Values | Extracted integers (— = not found/not written) |
Status |
OK / WARN / LOW CONF / ERROR (see below) |
| Status | Description | Written? |
|---|---|---|
OK |
All values extracted with high confidence | Yes |
WARN |
A value was missing or an optional field defaulted to zero | Yes |
LOW CONF |
OCR read a value but confidence score was below 75% | Yes |
ERROR |
Ranking could not be determined — cannot find correct Excel row | No |
LOW CONF values are written but should be spot-checked against the screenshot. In most cases they are correct but just below the confidence threshold. If a LOW CONF value is wrong, it can be corrected manually in Excel after checking.
| Excel Column | Field | Notes |
|---|---|---|
| A | Player name | Guild tag stripped |
| B | Ranking | Also used to find the correct row |
| C | Contribution | |
| D | Season Points | |
| G | Merits | |
| K | Demolition | |
| O | Occupy |
Columns E, F, H, I, J, L, M, N, P–Y are never touched. These contain formulas and manually-entered SS (Screenshot-verified) values.
The spreadsheet must be closed in Excel before running with --write.
screenreader/
├── main.py ← entry point (run this)
├── extractor.py ← OCR logic
├── writer.py ← Excel write logic
├── requirements.txt
├── eden-x[seasonNumber]-rewards-working.xlsx ← spreadsheet (must be in this folder)
└── x[seasonNumber]-screenshots#/ ← drop .jpg screenshots here
The OCR works by reading text positions as fractions of the image dimensions (0.0 = left/top, 1.0 = right/bottom). This means the mapping is resolution-independent — it works whether the screenshot is 1080p or 4K.
However, if screenshots come from a different device or game UI layout (e.g.
a different aspect ratio, zoomed-in UI, or a different language), the
positions of the key text regions may have shifted. All tunable constants are
at the top of extractor.py.
# extractor.py — lines ~50–70
# ─── Header block (Ranking / Contribution info at the top of the screenshot) ─
HEADER_Y_MIN = 0.13 # Y fraction where the header block starts
HEADER_Y_MAX = 0.255 # Y fraction where the header block ends
# Ranking label sits at ~Y 0.214, Contribution at ~Y 0.192
# Must stay below 0.26 to exclude the 'Contribution Type' table header row
HEADER_VAL_X_MIN = 0.58 # Values in the header are right-aligned from here
# ─── Amount column in the contribution table ──────────────────────────────────
AMOUNT_X_MIN = 0.28 # Left edge of the Amount column
AMOUNT_X_MAX = 0.65 # Right edge (Convert Contribution column starts at ~0.75)
# The table has 3 columns: Contribution Type | Amount | Convert Contribution
# Only Amount is extracted. X range must exclude the rightmost column.
# ─── OCR grouping tolerance ───────────────────────────────────────────────────
LINE_Y_TOL = 12 # pixels — items within this vertical distance are
# grouped into the same text line
# ─── Confidence threshold ─────────────────────────────────────────────────────
LOW_CONF_THRESHOLD = 0.75 # OCR confidence below this → flagged as LOW CONF-
Pick one screenshot that represents the new layout. Make sure all screenshots are the same layout and resolution for consistent results. Ideally same device and same in-game UI settings.
-
Run the diagnostic script to see where OCR is finding each text item:
py temp_check.py "x[seasonNumber]-screenshots#\your_image.jpg"
This prints every OCR result with its normalised X/Y position so you can see exactly where each label and value appears.
-
Identify the Y range that contains only the Ranking and Contribution header lines — update
HEADER_Y_MINandHEADER_Y_MAXto bracket them. -
Identify the X range that covers only the Amount column — update
AMOUNT_X_MINandAMOUNT_X_MAX. The key constraint is that the right edge must be left of the "Convert Contribution" column. -
Run
py main.pyagain to verify the values are now extracted correctly.
| Symptom | Likely cause | Fix |
|---|---|---|
| Ranking always None | Header block out of Y range | Widen HEADER_Y_MIN/HEADER_Y_MAX |
| Contribution picks up table row values | Header Y range too wide | Tighten HEADER_Y_MAX to exclude table rows |
| Amount shows "Convert Contribution" number | AMOUNT_X_MAX too large |
Reduce AMOUNT_X_MAX below ~0.65 |
| Values cut off (missing digits) | AMOUNT_X_MIN too large |
Reduce AMOUNT_X_MIN |
| Lines not grouping correctly | Screenshots are lower resolution | Increase LINE_Y_TOL (try 16–20) |
| Too many LOW CONF flags | Expected for this set of screenshots | Lower LOW_CONF_THRESHOLD to e.g. 0.60 |
EasyOCR does not reliably read single-digit ranking numbers at full image scale. The root cause is that rankings 1–9 occupy a very small number of pixels, and the model either misses them entirely or reads the wrong digit.
A crop-and-re-OCR fallback was tested but produced incorrect values (e.g. "1" was read as "3", "7" as "4"). Since a wrong ranking would write data to the wrong player's row, the fallback was removed in favour of accuracy.
Resolution: Use --set-ranking to supply the correct ranking manually
for each affected image. There are typically ~9 such images per season (ranks
1–9). The player name is always shown in the ERROR output so you know which
screenshot to reprocess.
At the start of each new season:
- Update the season multipliers in Excel row 2 (columns Z–AC) if they change.
- Drop the new season's
.jpgscreenshots intox[seasonNumber]-screenshots#/. - Run
py main.pyfor a preview, thenpy main.py --writeto commit. - Handle any ERRORs with
--set-rankingas described above. - The Excel formulas in the protected columns recalculate automatically.
"Permission denied" when writing
The spreadsheet is open in Excel. Close it first, then retry.
"Sheet 'Points Per Person' not found"
The spreadsheet has been renamed or the sheet tab has been renamed. Update
SHEET_NAME in writer.py to match.
"No .jpg files found"
Check that screenshots are in the x[seasonNumber]-screenshots#/ subfolder (note the
# in the folder name) and are .jpg files (not .png or .jpeg).
OCR model download fails
EasyOCR needs internet access the first time it runs. Once the model is
cached locally it works offline.