Handle inline RGBI frames from SANE coolscan3#471
Conversation
Detect the active, writable coolscan3 infrared option over direct and saned device IDs. Validate the four-sample frame contract before reading, recover complete RGBI rows from python-sane, and fail instead of silently returning RGB-only output.
|
I found this PR as I'm a LS-9000 user and was looking at SANE support in NegPy. It seems the coolscan SANE backend is pretty much abandoned (and was missing quite a few features compared to NikonScan). I'm currently in the process of reverse engineering the entire interface layer by decompiling NikonScan. I'm not sure if my findings should be upstreamed or if we should have some alternative to SANE. |
|
@activexray that is really awesome! I didnt even consider doing a decompile of nikon scan, I just used USBPCap on the wire to capture the info that way. I was working on a blogpost about it, heres my draft https://gist.github.com/rohanpandula/3185bc13fff8e3e6da44eb3b04108af1 |
|
apparently packet capture from SCSI is weirdly difficult from Windows, so it was easier just to load the binaries in Ghidra haha. I wasn't going to focus on any of the post processing code, just getting every single scanner feature implemented. I do, however, have the binaries for ICE, which i've considered reverse engineering as well as I've found others' implementations (like Vuescan) worse. |
|
Very cool work going on here! You might also want to check out this project: https://github.com/kevihiiin/Nikon-Coolscan-RE |
|
I found that repo after spending quite a bit of time doing the same thing haha. Some of their conclusions seems to not be correct, at least for the coolscan 9000. Maybe I PR that repo, but I have yet to complete a scan from rust using only RE'd information. Once I can get a frame back I'll publish my findings. |
|
Yeah, it's a bit over my head, so I can't really evaluate how good the project is. But I'm really happy to see this community effort finally coming to the Coolscan family. I thought I'd share it in case there's anything useful there, and maybe it could lead to some cross-pollination of ideas and save you all from reinventing a couple of wheels ;) |
|
I'm mad enough at the stupid proprietary Vuescan being the only option for scanning with the 9000 on linux (esp considering they've moved to a subscription model). The SANE backend is really quite bad and unmaintained. And all the clients really choke on full-res scans of medium format. So my plan is to write a SANE-less scan client for Nikon scanners that's all GPU-accelerated for the rendering, cross platform, and of course open source. I've honestly made some decent progress with the reverse engineer, i'm just going slow as I only have time on the weekend. |
|
That sounds wonderful! Yeah, I was under the impression that the SANE drivers left quite a lot to be desired. Some good alternatives would be very welcome, though. The quality of ICE in Nikon Scan has always held me back from switching to anything else , but it looks like @rohanpandula might have that covered soon. Exciting times! I'm looking forward to seeing your project :) |
|
Yeah ICE has always seemed better than the alternatives, that's also why I preferred NikonScan. I was going to decompile their algorithm as the patent expired, which it looks like the other person made some progress on. I'll keep all of you posted on my progress! |
|
There are also some potentially low-hanging fruit that Kosma never managed to unlock in the firmware. But it seems like the whole reverse-engineering game has changed quite a bit over the last year or so. For example, a firmware patch that makes physically modding the SA-21 unnecessary, or unlocking multi-sampling on the LS-50. Just throwing it out there :) |

Summary
This PR adds NegPy support for inline 1x RGB+IR frames from SANE's
coolscan3backend. Hardware verification is limited to one LS-5000 ED. Other models are
not claimed as supported by this change.
With the separate SANE patch,
coolscan3exposes infrared as a boolean option.When enabled, it reports an RGB frame but returns four pixel-interleaved
samples: R, G, B, and IR. NegPy detects this contract, enables the option,
reconstructs the RGBI rows from the array returned by python-sane, and passes
the fourth plane to NegPy's existing IR path.
The coolscan3 path returns the same
ScanResult(rgb, ir, ...)object that therest of NegPy already uses, so dust repair and file output stay unchanged.
Why channel repair is needed
python-sane assumes every non-gray frame has three samples per pixel. A
four-sample
SANE_FRAME_RGBstream is therefore grouped into three-channelrows and returned with the wrong shape. If the final group is partial,
python-sane discards it.
NegPy gets the real channel count from SANE's reported pixels, lines, and bytes
per line, then reconstructs complete R, G, B, IR rows from the returned sample
buffer. In the known truncated-tail case, every sample through the penultimate
scanner row is still present. NegPy returns that complete prefix as a zero-copy
four-channel view and omits the one incomplete edge row. It then splits RGB
from IR.
If IR was requested and no fourth channel arrives, the scan fails instead of
silently saving an RGB-only result.
Before reading, NegPy also verifies that SANE reports a terminal color frame,
the requested bit depth, positive geometry, and exactly four samples per row.
An inactive or read-only infrared option is not advertised as usable and fails
before scanner configuration if a caller requests it directly.
Other compatibility fixes in this PR
net:<host>:before backend detection so the same coolscan3 strategyworks through
saned.modeoption on devices such as coolscan3 that do not exposeone.
sanedcoolscan3 IDs as dedicated film scanners whenthey have no generic
sourceor RGBImodeoption.contract despite exposing a similarly named option.
Scope
This PR covers 1x RGBI integration only.
It does not include:
The required SANE change is available for review in draft MR !937:
https://gitlab.com/sane-project/backends/-/merge_requests/937. Until a backend
change lands, users need a patched
coolscan3build in which the infraredoption is active.
That SANE draft asks maintainers whether four interleaved samples should use
the existing
SANE_FRAME_RGBprecedent or a separate grayscale IR frame. That design question remains open for maintainer review.Verification
Physical scanner:
scanimagePNM file: 188,113,751 bytes including its 39-byte headerThe
sanedpath was first checked with SANE's test backend. Local and networkreads were byte-identical, including 16-bit sample order. It has now also run
on the physical LS-5000 through NegPy at 1000 and 4000 dpi. In each live test,
the second capture used the same 1x inline RGBI recovery path covered by this
PR. Both returned a real IR plane and left the scanner responsive.
The exact focused branch passes 1,605 tests, with 4 skipped and 7 deselected.
make format,make lint, andmake typealso pass. The 42 new focused testscover option discovery, frame-contract validation, and channel recovery.
Automated coverage includes:
net:coolscan3 device IDs;truncated;
flatbeds.