LibInsane: General Libinsane principles

| LibInsane 1.0.10 Cross-platform Cross-API Cross-driver Cross-image-scanner Image Scan Library

Loading...
Searching...
No Matches
General Libinsane principles

### API & implementations

Libinsane provides a single API but has many implementations for it. Most of the time, you will only want the implementation "safebet". It is a composition of many other implementations that will provide a consistent behaviour across all platform and devices.

You're advised to get an implementation when your application starts and clean it up just before your application ends.

### Device descriptor

The API allows you to obtain a list of devices connected to the computer (with `implementation.list_devices()`). For each device, it will provide you with a vendor name, a model name, a device type name, and more importantly a device identifier. The device identifier can then be used to access the device (with `implementation.get_device()`).

LibInsane tries to return only devices that are actually configured and online. If the scanner is shut down or disconnected, it is not returned. However this behaviour is (not yet) guaranteed.

Beware that device IDs may change over time. For instance, with Sane drivers, some IDs include the bus and device numbers: `sane:plustek:libusb:002:079`, after disconnecting/reconnecting the scanner, may become `sane:plustek:libusb:002:080`. There is not much Libinsane can do about that. If the scanner ID you're looking for is not available anymore, you may want your application to look for the closest scanner ID it can find.

### Items, devices and sources

Item tree

Libinsane provides an item tree (inspired by WIA2):

- Items can be devices: A device is a scanner device in its entirety.
- Items can be sources: A Source is scanner input. Some scanners have only one (usually a flatbed), some have 2 or more (usually a flatbed and an automatic document feeder).
- Items can have other items as children: The children of device are its sources. All root items are always devices. All their children are always sources.
- Root items (devices) provide children but children (sources) will always return an empty list of children.
- Child items (sources) never have any children.
- On a given device, child items (sources) names are always unique.

All items provide a set of options.

#### Root item / scan device

`implementation.list_devices()` will return you a list of device name and ID. Depending of the environment, this function can take up to 30 seconds.

`implementation.get_device(device_id)` will return a root item (aka a device). When you're done scanning, only this root item will have to be closed. When you close the root item, all the children are closed too and must not be used anymore.

You're advised to get a device only just before wanting to configure and use it or list its options and close it as soon as you're done with it. It's not recommended nor tested to keep a device opened for a long time.

Note that `implementation.get_device()` may take a few seconds.

`root.get_children()` will return all the available sources of the device.

#### Child item / scan source

Usually you cannot scan using the root item. You should always set the options on the child item (source) you want to use and use that child.

To simplify the use of root and children, all the options available on the root item are also made available on all the children.

### Setting the options

Once you have found the child item (source) that you want to use, you have to configure it. Some options are read-only, some options can be modified. Some can have integers as values, some can have strings, etc. Some options have a constraint defining what values are allowed. This constraint can be a range or a list.

Only a small subset of options are guaranteed to be found on any child item (inspired by Sane):

LibInsane offers guarantees only on the options below. All the other options are device-dependant and driver-dependant.

#### Option 'mode'

While option 'mode' is not guaranteed to be present on all scanners, it has been seen on all scanners up to now.

If present, Libinsane will normalize 3 of the possibles values: `Color` (24bits RGB), `Gray` (grayscale), `LineArt` (black & white). Other values may be available depending on the scanner and its driver.

If you set `mode` to `LineArt`, there may also be an option `depth` that you must set to 1. This is (not yet) done automatically by Libinsane.

#### Option 'resolution'

While option `resolution` is not guaranteed to be present on all scanners, it has been seen on all scanners up to now.

Value is guaranteed by Libinsane to be an integer. Unit is always DPI. Constraint is guaranteed by Libinsane to be expressed as a list of integer values.

#### Scan area

Scan area is defined as the value of 4 options:

- `tl-x`: Top left corner of the area that will be scanner. X coordinates.
- `tl-y`: Top left corner of the area that will be scanner. Y coordinates.
- `br-x`: Bottom right corner of the area that will be scanner. X coordinates.
- `br-y`: Bottom right corner of the area that will be scanner. Y coordinates.
- Guarantee: By default, those options will be set so the scanner scans the biggest area possible.
- Not guaranteed: Those options may not be present at all. Their unit is (not yet) guaranteed.

If you choose to set the scan area, be careful to always keep the `tl` coordinates at lower values than the `br` ones. If you don't, you may get an error when setting them.

### Scanning

Once all the options are set, we can finally scan something.

While scanning, your application must not try to list devices nor access any other device nor change the options. If it does, the behaviour of Libinsane and the underlying is undefined.

Using `item.scan_start()` on a child node, you can obtain a scan session. From a scan session, you can obtain one or more pages / scanner images.

- `get_scan_parameters()` provides information regarding the page being scanned.
- `scan_read()` returns the images as raw 24 pixels (even if scanning in black&white or grayscale).
- `end_of_page()` indicates that you reached the end of the current page.
- `end_of_feed()` indicates that all pages have been scanned.
- `cancel()` allows to interrupt the scan session. It can be safely called once the scan session has been finished (`end_of_feed() == True`). Calling it before may or may not work (it may even causes crahses). If you want to cancel a scan while it's running, you're advised to simply let it end. The session cannot be used once this function has been called.

Those functions are intended to be called in the following order:

session = item.scan_start()
while not session.end_of_feed()
params = session.get_scan_parameters()
whole_image = []
while not session.end_of_page():
raw24 = session.scan_read()
whole_image += raw24
whole_image = convert_it_as_you_want(whole_image, params)
whole_image.save()
session.cancel()

Duplex scans are simply provided as 2 pages.

### Choosing a buffer size for `scan_read()`

On GNU/Linux (Sane), `scan_read()` is pretty much a direct call to `sane_read()`, which itself is almost a direct call the backend `read()` function. So during a scan, when you are not calling `scan_read()`, backend may be unable to do anything. If the backend doesn't communicate with the scanner for too long, communication may time out.

On Windows (WIA/TWAIN), data from the scanner are immediately handled by a dedicated thread and stored in a queue. When you call `scan_read()`, you are actually getting data from this queue. If you don't call `scan_read()` often enough, raw data may accumulate in this in-memory queue. This may not be what you want.

Therefore the buffer size depends on what you can process quickly. You don't want to spend too much time processing the data `scan_read()` returns before calling it again.

On the other end, using a buffer too small means calling `scan_read()` much more often, which will decrease performances.

The recommended range for the buffer size is 4kB-128kB. By default 64kB is suggested. If you have to do some heavy processing on the chunks coming from the scanner (for instance to display them), you may want to consider doing it in a separated thread.