delayed_image.sensorchan_spec module

This is an extension of delayed_image.channel_spec, which augments channel information with an associated sensor attribute. Eventually, this will entirely replace the channel spec.

Example

>>> # xdoctest: +REQUIRES(module:lark)
>>> # hack for 3.6
>>> from delayed_image import sensorchan_spec
>>> import delayed_image
>>> delayed_image.SensorChanSpec = sensorchan_spec.SensorChanSpec
>>> self = delayed_image.SensorChanSpec.coerce('sensor0:B1|B8|B8a|B10|B11,sensor1:B11|X.2|Y:2:6,sensor2:r|g|b|disparity|gauss|B8|B11,sensor3:r|g|b|flowx|flowy|distri|B10|B11')
>>> self.normalize()
delayed_image.sensorchan_spec.SENSOR_CHAN_GRAMMAR = '// SENSOR_CHAN_GRAMMAR\n?start: stream\n\n// An identifier can contain spaces\nIDEN: ("_"|"*"|LETTER) ("_"|" "|"-"|"*"|LETTER|DIGIT)*\n\nchan_single : IDEN\nchan_getitem : IDEN "." INT\nchan_getslice_0b : IDEN ":" INT\nchan_getslice_ab : (IDEN "." INT ":" INT) | (IDEN ":" INT ":" INT)\n\n// A channel code can just be an ID, or it can have a getitem\n// style syntax with a scalar or slice as an argument\nchan_code : chan_single | chan_getslice_0b | chan_getslice_ab | chan_getitem\n\n// Fused channels are an ordered sequence of channel codes (without sensors)\nfused : chan_code ("|" chan_code)*\n\n// Channels can be specified in a sequence but must contain parens\nfused_seq : "(" fused ("," fused)* ")"\n\n// Sensors can be specified in a sequence but must contain parens\nsensor_seq : "(" IDEN ("," IDEN)* "):"\n\nsensor_lhs : (IDEN ":") | (sensor_seq)\n\n// A channel only part can be a fused channel or a sequence\nchannel_rhs : fused | fused_seq\n\nsensor_chan : sensor_lhs channel_rhs?\n\nnosensor_chan : channel_rhs\n\nstream_item : sensor_chan | nosensor_chan\n\n// A stream is an unordered sequence of fused channels, that can\n// optionally contain sensor specifications.\n\nstream : stream_item ("," stream_item)*\n\n%import common.DIGIT\n%import common.LETTER\n%import common.INT'

add the concept of an exclusive or operator with left hand priority. The idea is that we can specify a code that will use the one fused channel spec if it is available, but if it is not, it will fall back to the next one. E.G.

WV:((red|green|blue)^(pan))

(L8,S2,WV,WV1):((red|green|blue)^(pan))

Possible Production Rules:

fused : chan_code (“|” chan_code)* fused_seq : “(” fused (“,” fused)* “)”

Maybe also include that on the sensor side?

Use WV:r|g|b if we have it otherwise use S2:r|g|b

(WV^S2)(r|g|b)

Type:

TODO

class delayed_image.sensorchan_spec.SensorSpec(spec)[source]

Bases: NiceRepr

A simple wrapper for sensors in case we want to do anything fancy with them later. For now they are just a string.

class delayed_image.sensorchan_spec.SensorChanSpec(spec: str)[source]

Bases: NiceRepr

The public facing API for the sensor / channel specification

Example

>>> # xdoctest: +REQUIRES(module:lark)
>>> from delayed_image.sensorchan_spec import SensorChanSpec
>>> self = SensorChanSpec('(L8,S2):BGR,WV:BGR,S2:nir,L8:land.0:4')
>>> s1 = self.normalize()
>>> s2 = self.concise()
>>> streams = self.streams()
>>> print(s1)
>>> print(s2)
>>> print('streams = {}'.format(ub.urepr(streams, sv=1, nl=1)))
L8:BGR,S2:BGR,WV:BGR,S2:nir,L8:land.0|land.1|land.2|land.3
(L8,S2,WV):BGR,L8:land:4,S2:nir
streams = [
    L8:BGR,
    S2:BGR,
    WV:BGR,
    S2:nir,
    L8:land.0|land.1|land.2|land.3,
]

Example

>>> # Check with generic sensors
>>> # xdoctest: +REQUIRES(module:lark)
>>> from delayed_image.sensorchan_spec import SensorChanSpec
>>> import delayed_image
>>> self = SensorChanSpec('(*):BGR,*:BGR,*:nir,*:land.0:4')
>>> self.concise().normalize()
>>> s1 = self.normalize()
>>> s2 = self.concise()
>>> print(s1)
>>> print(s2)
*:BGR,*:BGR,*:nir,*:land.0|land.1|land.2|land.3
(*):BGR,*:(nir,land:4)
>>> import delayed_image
>>> c = delayed_image.ChannelSpec.coerce('BGR,BGR,nir,land.0:8')
>>> c1 = c.normalize()
>>> c2 = c.concise()
>>> print(c1)
>>> print(c2)

Example

>>> # Check empty channels
>>> # xdoctest: +REQUIRES(module:lark)
>>> from delayed_image.sensorchan_spec import SensorChanSpec
>>> import delayed_image
>>> print(SensorChanSpec('*:').normalize())
*:
>>> print(SensorChanSpec('sen:').normalize())
sen:
>>> print(SensorChanSpec('sen:').normalize().concise())
sen:
>>> print(SensorChanSpec('sen:').concise().normalize().concise())
sen:
classmethod coerce(data)[source]

Attempt to interpret the data as a channel specification

Returns:

SensorChanSpec

Example

>>> # xdoctest: +REQUIRES(module:lark)
>>> from delayed_image.sensorchan_spec import *  # NOQA
>>> from delayed_image.sensorchan_spec import SensorChanSpec
>>> data = SensorChanSpec.coerce(3)
>>> assert SensorChanSpec.coerce(data).normalize().spec == '*:u0|u1|u2'
>>> data = SensorChanSpec.coerce(3)
>>> assert data.spec == 'u0|u1|u2'
>>> assert SensorChanSpec.coerce(data).spec == 'u0|u1|u2'
>>> data = SensorChanSpec.coerce('u:3')
>>> assert data.normalize().spec == '*:u.0|u.1|u.2'
normalize()[source]
concise()[source]

Example

>>> # xdoctest: +REQUIRES(module:lark)
>>> from delayed_image import SensorChanSpec
>>> a = SensorChanSpec.coerce('Cam1:(red,blue)')
>>> b = SensorChanSpec.coerce('Cam2:(blue,green)')
>>> c = (a + b).concise()
>>> print(c)
(Cam1,Cam2):blue,Cam1:red,Cam2:green
>>> # Note the importance of parenthesis in the previous example
>>> # otherwise channels will be assigned to `*` the generic sensor.
>>> a = SensorChanSpec.coerce('Cam1:red,blue')
>>> b = SensorChanSpec.coerce('Cam2:blue,green')
>>> c = (a + b).concise()
>>> print(c)
(*,Cam2):blue,*:green,Cam1:red
streams()[source]
Returns:

List of sensor-names and fused channel specs

Return type:

List[FusedSensorChanSpec]

late_fuse(*others)[source]

Example

>>> # xdoctest: +REQUIRES(module:lark)
>>> import delayed_image
>>> from delayed_image import sensorchan_spec
>>> import delayed_image
>>> delayed_image.SensorChanSpec = sensorchan_spec.SensorChanSpec  # hack for 3.6
>>> a = delayed_image.SensorChanSpec.coerce('A|B|C,edf')
>>> b = delayed_image.SensorChanSpec.coerce('A12')
>>> c = delayed_image.SensorChanSpec.coerce('')
>>> d = delayed_image.SensorChanSpec.coerce('rgb')
>>> print(a.late_fuse(b).spec)
>>> print((a + b).spec)
>>> print((b + a).spec)
>>> print((a + b + c).spec)
>>> print(sum([a, b, c, d]).spec)
A|B|C,edf,A12
A|B|C,edf,A12
A12,A|B|C,edf
A|B|C,edf,A12
A|B|C,edf,A12,rgb
>>> import delayed_image
>>> a = delayed_image.SensorChanSpec.coerce('A|B|C,edf').normalize()
>>> b = delayed_image.SensorChanSpec.coerce('A12').normalize()
>>> c = delayed_image.SensorChanSpec.coerce('').normalize()
>>> d = delayed_image.SensorChanSpec.coerce('rgb').normalize()
>>> print(a.late_fuse(b).spec)
>>> print((a + b).spec)
>>> print((b + a).spec)
>>> print((a + b + c).spec)
>>> print(sum([a, b, c, d]).spec)
*:A|B|C,*:edf,*:A12
*:A|B|C,*:edf,*:A12
*:A12,*:A|B|C,*:edf
*:A|B|C,*:edf,*:A12,*:
*:A|B|C,*:edf,*:A12,*:,*:rgb
>>> print((a.late_fuse(b)).concise())
>>> print(((a + b)).concise())
>>> print(((b + a)).concise())
>>> print(((a + b + c)).concise())
>>> print((sum([a, b, c, d])).concise())
*:(A|B|C,edf,A12)
*:(A|B|C,edf,A12)
*:(A12,A|B|C,edf)
*:(A|B|C,edf,A12,)
*:(A|B|C,edf,A12,,r|g|b)

Example

>>> # Test multi-arg case
>>> import delayed_image
>>> a = delayed_image.SensorChanSpec.coerce('A|B|C,edf')
>>> b = delayed_image.SensorChanSpec.coerce('A12')
>>> c = delayed_image.SensorChanSpec.coerce('')
>>> d = delayed_image.SensorChanSpec.coerce('rgb')
>>> others = [b, c, d]
>>> print(a.late_fuse(*others).spec)
>>> print(delayed_image.SensorChanSpec.late_fuse(a, b, c, d).spec)
A|B|C,edf,A12,rgb
A|B|C,edf,A12,rgb
matching_sensor(sensor)[source]

Get the components corresponding to a specific sensor

Parameters:

sensor (str) – the name of the sensor to match

Example

>>> # xdoctest: +REQUIRES(module:lark)
>>> import delayed_image
>>> self = delayed_image.SensorChanSpec.coerce('(S1,S2):(a|b|c),S2:c|d|e')
>>> sensor = 'S2'
>>> new = self.matching_sensor(sensor)
>>> print(f'new={new}')
new=S2:a|b|c,S2:c|d|e
>>> print(self.matching_sensor('S1'))
S1:a|b|c
>>> print(self.matching_sensor('S3'))
S3:
property chans

Returns the channel-only spec, ONLY if all of the sensors are the same

Example

>>> # xdoctest: +REQUIRES(module:lark)
>>> import delayed_image
>>> self = delayed_image.SensorChanSpec.coerce('(S1,S2):(a|b|c),S2:c|d|e')
>>> import pytest
>>> with pytest.raises(Exception):
>>>     self.chans
>>> print(self.matching_sensor('S1').chans.spec)
>>> print(self.matching_sensor('S2').chans.spec)
a|b|c
a|b|c,c|d|e
class delayed_image.sensorchan_spec.FusedSensorChanSpec(sensor, chans)[source]

Bases: SensorChanSpec

A single sensor a corresponding fused channels.

property chans
property spec
class delayed_image.sensorchan_spec.SensorChanNode(sensor, chan)[source]

Bases: object

TODO: just replace this with the spec class itself?

property spec
class delayed_image.sensorchan_spec.FusedChanNode(chan)[source]

Bases: object

TODO: just replace this with the spec class itself?

Example

s = FusedChanNode(‘a|b|c.0|c.1|c.2’) c = s.concise() print(s) print(c)

property spec
concise()[source]
class delayed_image.sensorchan_spec.SensorChanTransformer(concise_channels=1, concise_sensors=1)[source]

Bases: Transformer

Given a parsed tree for a sensor-chan spec, can transform it into useful forms.

Todo

Make the classes that hold the underlying data more robust such that they either use the existing channel spec or entirely replace it. (probably the former). Also need to add either a FusedSensorChan node that is restircted to only a single sensor and group of fused channels.

chan_id(items)[source]
chan_single(items)[source]
chan_getitem(items)[source]
chan_getslice_0b(items)[source]
chan_getslice_ab(items)[source]
chan_code(items)[source]
sensor_seq(items)[source]
fused_seq(items)[source]
fused(items)[source]
channel_rhs(items)[source]
sensor_lhs(items)[source]
nosensor_chan(items)[source]
sensor_chan(items)[source]
stream_item(items)[source]
stream(items)[source]
delayed_image.sensorchan_spec.normalize_sensor_chan(spec: str)[source]

Example

>>> # xdoctest: +REQUIRES(module:lark)
>>> from delayed_image.sensorchan_spec import *  # NOQA
>>> spec = 'L8:mat:4,L8:red,S2:red,S2:forest|brush,S2:mat.0|mat.1|mat.2|mat.3'
>>> r1 = normalize_sensor_chan(spec)
>>> spec = 'L8:r|g|b,L8:r|g|b'
>>> r2 = normalize_sensor_chan(spec)
>>> print(f'r1={r1}')
>>> print(f'r2={r2}')
r1=L8:mat.0|mat.1|mat.2|mat.3,L8:red,S2:red,S2:forest|brush,S2:mat.0|mat.1|mat.2|mat.3
r2=L8:r|g|b,L8:r|g|b
delayed_image.sensorchan_spec.concise_sensor_chan(spec: str)[source]

Example

>>> # xdoctest: +REQUIRES(module:lark)
>>> from delayed_image.sensorchan_spec import *  # NOQA
>>> spec = 'L8:mat.0|mat.1|mat.2|mat.3,L8:red,S2:red,S2:forest|brush,S2:mat.0|mat.1|mat.2|mat.3'
>>> concise_spec = concise_sensor_chan(spec)
>>> normed_spec = normalize_sensor_chan(concise_spec)
>>> concise_spec2 = concise_sensor_chan(normed_spec)
>>> assert concise_spec2 == concise_spec
>>> print(concise_spec)
(L8,S2):(mat:4,red),S2:forest|brush
delayed_image.sensorchan_spec.sensorchan_concise_parts(spec: str)[source]
delayed_image.sensorchan_spec.sensorchan_normalized_parts(spec: str)[source]