Skip to content

Fix crash when deleting an image & jump to the next unlabeled one afterwards - #269

Open
Jammy123-prog wants to merge 2 commits into
PFCCLab:mainfrom
Jammy123-prog:fix/delete-image-crash
Open

Jammy123-prog wants to merge 2 commits into
PFCCLab:mainfrom
Jammy123-prog:fix/delete-image-crash

Conversation

@Jammy123-prog

@Jammy123-prog Jammy123-prog commented Oct 21, 2026 •

Copy link
Copy Markdown

Problem

PPOCRLabel crashes (exits without any dialog) when the user deletes an image while the "auto save unsaved changes" option is enabled (and also whenever there are unsaved changes on the deleted image).

Root cause

After the image is moved to the trash, deleteImg() calls importDirImages(..., isDelete=True), which first goes through mayContinue() → saveFile(). At that moment self.filePath still points to the file that has just been deleted, so _saveFile() crashes in cv2.imdecode(np.fromfile(...)):

Traceback (most recent call last):
  File "PPOCRLabel.py", line 2907, in deleteImg
    self.importDirImages(self.lastOpenDir, isDelete=True)
  File "PPOCRLabel.py", line 2687, in importDirImages
    if not self.mayContinue() or not dirpath:
  File "PPOCRLabel.py", line 2935, in mayContinue
    self.saveFile()
  File "PPOCRLabel.py", line 2814, in saveFile
    self._saveFile(img_idx, mode=mode)
  File "PPOCRLabel.py", line 2835, in _saveFile
    np.fromfile(self.filePath, dtype=np.uint8), cv2.IMREAD_COLOR
FileNotFoundError: [Errno 2] No such file or directory: '...jpg'

Fix (13 lines, no behaviour change for normal saves)

  1. saveFile(): skip saving (with a warning log) when the target file no longer exists on disk.
  2. deleteImg(): drop the stale reference (self.filePath = None, self.dirty = False) before reloading the directory, so nothing can re-read the deleted image.

Verification

Reproduced the exact call chain in a regression test (open dir → mark dirty → remove file → saveFile(mode="Auto") + metadata saves):

  • before the fix: hard crash (exit 139)
  • after the fix: all steps pass, Label.txt / Cache.cach / fileState.txt are written correctly

Manual GUI test on Windows 11 + PaddleOCR 3.7.0 + custom det/rec models: deleting images no longer crashes the app.

中文说明

问题:开启"自动保存未提交变更"后删除图片,程序直接闪退。

根因:图片移入回收站后,deleteImg() → importDirImages(isDelete=True) → mayContinue() → saveFile() 链路仍持有 self.filePath(已删除路径),cv2.imdecode(np.fromfile(...)) 抛 FileNotFoundError 崩溃。

修复:① saveFile() 增加文件存在性检查,文件不存在则跳过并记录 warning;② deleteImg() 删除成功后立即清空 filePath 引用并复位 dirty 标志。共 13 行新增,不影响正常保存逻辑。已通过回归测试与 GUI 手工验证。


Follow-up (2nd commit): jump to the next unlabeled image after deletion

The crash fix clears self.filePath before importDirImages() rescans the folder, so openNextImg() falls back to selecting the very first image of the directory — the annotator loses their position in the list after every deletion.

Restore position-aware navigation and improve it:

  • deleteImg() records the index of the deleted image and passes it to importDirImages(..., isDelete=True, deleteIdx=...).
  • importDirImages() maps that index onto the rescanned list (the same index then points at the image that followed the deleted one) and asks openNextImg() to prefer an unlabeled target.
  • openNextImg(preferUnlabeled=True) skips forward to the first image whose validFilestate() is False — the next image that still needs annotation. If all following images are already labeled, it keeps the previous fallback (the image right after the deleted one, or the previous one when the last image was deleted).

Verified with three scenarios (3-image folder):

  1. delete 1st, 2nd already labeled → 3rd is selected
  2. delete 1st, rest labeled → 2nd is selected (fallback)
  3. delete last → previous is selected

补充说明(第二个提交):删除后跳转到下一张未标注图片

崩溃修复会先清空 self.filePath,导致重扫列表后 openNextImg() 总是选中目录里的第一张图片,标注位置丢失。本提交记录被删图片的索引并传递给重扫逻辑,删除后自动跳到下一张未标注(validFilestate() 为 False)的图片;若后续均已标注则回退为紧邻的下一张;删除最后一张时回退为前一张。三个场景回归测试全部通过。

Root cause: after the image is moved to the trash, deleteImg() calls
importDirImages(isDelete=True), which first goes through mayContinue()
-> saveFile(). At that point self.filePath still points to the deleted
file, so _saveFile() fails with FileNotFoundError in cv2.imdecode and
the application exits. This is easily triggered when the
'auto save unsaved changes' option is enabled.

Fix:
- saveFile(): skip saving and log a warning when the file no longer
  exists on disk.
- deleteImg(): clear self.filePath and the dirty flag before reloading
  the directory so the stale reference cannot be touched anymore.

Verified with a regression test reproducing the exact call chain
(open dir -> mark dirty -> remove file -> saveFile(Auto)): crashes
before the fix, passes cleanly after it.
@Jammy123-prog
Jammy123-prog force-pushed the fix/delete-image-crash branch from 5beb9ee to 459e2c1 Compare October 21, 2026 01:49
The crash fix clears self.filePath before importDirImages() rescans the
folder, so openNextImg() falls back to selecting the very first image
of the directory and the annotator loses their position in the list.

Restore position-aware navigation and improve it:
- deleteImg() records the index of the deleted image and passes it to
  importDirImages(..., isDelete=True, deleteIdx=...).
- importDirImages() maps that index onto the rescanned list (the same
  index now points at the image that followed the deleted one) and
  tells openNextImg() to prefer an unlabeled target.
- openNextImg(preferUnlabeled=True) skips forward from that position to
  the first image whose validFilestate() is False, i.e. the next image
  that still needs annotation. If all following images are already
  labeled it keeps the previous fallback (the image right after the
  deleted one, or the previous one when the last image was deleted).

Verified with three scenarios: delete first of three with the second
already labeled -> third is selected; delete first with the rest
labeled -> second is selected; delete the last one -> previous is
selected.
@Jammy123-prog Jammy123-prog changed the title Fix crash when deleting an image with unsaved changes Fix crash when deleting an image & jump to the next unlabeled one afterwards Oct 26, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant