6

I have an image array that has an X times Y shape of 2048x2088. The x-axis has two 20 pixel regions, one at the start and one at the end, which are used to calibrate the main image area. To access these regions I can slice the array like so:

prescan_area = img[:, :20]
data_area = img[:, 20:2068]
overscan_area = img[:, 2068:]

My question is how to define these areas in a configuration file in order the generalise this slice for other cameras which may have different prescan and overscan areas and therefore require a different slice.

Ideally, something like the strings below would allow a simple representation in the camera specific configuration file, but I am not sure how to translate these strings into array slices.

prescan_area_def = "[:, :20]"
image_area_def = "[:, 20:2068]"
overscan_area_def = "[:, 2068:]"

Maybe there is something obvious that I am missing?

Thanks!

James McCormac
  • 1,635
  • 2
  • 12
  • 26
  • 1
    I'd prefer storing them as `slice` objects, or tuple equivalents. `(slice(None), slice(None,20))` or `((None), (None,20))`. – hpaulj Mar 29 '17 at 19:53
  • I cannot store slice objects in image headers or configuration files, but I can store strings. If I understand correctly, I'd still have to translate those in string format to a ```slice``` object in my code? – James McCormac Mar 29 '17 at 19:55
  • 1
    True; but a slice object is easier to apply programatically: `np.arange(10)[ eval('slice(5,None)') ]`. – hpaulj Mar 29 '17 at 21:02
  • Related: [Numpy slicing from variable](https://stackoverflow.com/questions/12616821/numpy-slicing-from-variable) – Georgy Jul 26 '19 at 10:19

4 Answers4

5

You can parse the string and use slice. The following generator expression within tuple will create the slice objects for you:

tuple(slice(*(int(i) if i else None for i in part.strip().split(':'))) for part in prescan_area_def.strip('[]').split(','))

Demo:

In [5]: import numpy as np

In [6]: 

In [6]: a = np.arange(20).reshape(4, 5)

In [7]: a
Out[7]: 
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])

In [8]: 

In [8]: prescan_area_def = "[:, :3]"

In [9]: a[:, :3]
Out[9]: 
array([[ 0,  1,  2],
       [ 5,  6,  7],
       [10, 11, 12],
       [15, 16, 17]])

In [10]: indices = tuple(slice(*(int(i) if i else None for i in part.strip().split(':'))) for part in prescan_area_def.strip('[]').split(','))

In [11]: indices
Out[11]: (slice(None, None, None), slice(None, 3, None))

In [12]: a[indices]
Out[12]: 
array([[ 0,  1,  2],
       [ 5,  6,  7],
       [10, 11, 12],
       [15, 16, 17]])
Mazdak
  • 105,000
  • 18
  • 159
  • 188
  • Thanks for your answer. I am all for doing things in a compact manner, but the length of the single line command makes this hard to keep track of. I think the shorter method using eval was what I was looking for. – James McCormac Mar 29 '17 at 17:38
  • 1
    this is the best answer, as it doesn't contain any dangerous code (see references made to the use of `eval` in other comments) and uses the proper access mechanisms (i.e. the `slice` object). – Benjamin Maier Mar 13 '22 at 17:22
3

you can do something like:

var1="img"
prescan_area_def = "[:, :20]"

and to use eval

prescan_area=eval(var1+prescan_area_def)
Binyamin Even
  • 3,318
  • 1
  • 18
  • 45
  • 1
    The other answers were along the lines of what I thought I'd have to do; parse the string into variables an use them to slice the array in the normal way. This is the type of answer I was hoping for. Thanks! – James McCormac Mar 29 '17 at 17:35
  • 1
    @James McCormac and for all future browsers: DO NOT USE THE `eval` SOLUTION. It is bad practice, and it may come back to bite you in the ass (in the form of a severe security vulnerability) – tel Sep 14 '20 at 02:32
1

For the love of all that's holy, DO NOT USE eval TO PARSE SIMPLE INPUT (see detailed rant below*).

Answer for index/slice combo strings, eg "[0, 1:5, :6, 13]"

Expanding on @Kasravnd's excellent (but admittedly possibly confusing) answer, here's a function that will parse an ND string slice that contains both 1D slices and scalar indices:

def parseSlice(sliceStr):
    return tuple((slice(*(int(i) if i else None for i in part.strip().split(':'))) if ':' in part else int(part.strip())) for part in sliceStr.split(','))

In plain English, here's what it does in order:

  1. Break up sliceStr into parts, one part for every , in the original sliceStr

  2. If a part contains a :, treat it as a slice

    i. Break up the part into indexes, one i for every : in the original part

    ii. Convert each i into an int

    iii. Feed the resulting list of i values into the builtin slice function (the * symbol lets you pass a list of args to a function)

  3. Otherwise, treat the part as a scalar index and just convert the part to an int

  4. Finally, wrap the resulting sequence of int and slice objects in a tuple and return it

*The eval rant

There's really only two scenarios where someone would try to use eval to determine a slice from a string. Either a) the user is a raw beginner, and should be directed to use the proper non-string syntax to define their slices or b) the user is trying to parse a string provided by an external source, in which case they kiss any semblance of security goodbye. There is no proven way to sanitize eval, there is no completely safe way to use eval, and eval really is amazingly dangerously.

In short, probably don't actually ever use raw eval calls in your code. DEFINITELY don't use eval in any code you intend to release to the public. This is how severe vulns happen. In reality, 99% of the time there's a better solution than eval-ing anything (as there is to the OP's problem), and 99.9% of the time you can probably get away with using the much safer ast.literal_eval. Yes, there are some legit applications of eval. No, your use case almost certainly isn't among that .01%

tel
  • 13,005
  • 2
  • 44
  • 62
  • I just tried your function with the text example you gave, and it failed with `ValueError: invalid literal for int() with base 10: '[0'` – ProGamerGov Apr 01 '22 at 19:28
0

Here's an approach using regular expressions.

import numpy as np
import re

def slice_from_str(img, s):

    REGEX = r'\[(\d*):(\d*), (\d*):(\d*)\]'

    m = re.findall(REGEX,s)
    if m:
        # convert empty strings to None
        groups = [None if x=='' else int(x) for x in m[0]]
        start_x, end_x, start_y, end_y = groups
        x_slice = slice(start_x, end_x)
        y_slice = slice(start_y, end_y)

        return img[x_slice,y_slice]

    return []

img = np.random.rand(2048,2088)

prescan_area_def = '[:, :20]'
image_area_def = "[:, 20:2068]"
overscan_area_def = "[:, 2068:0]"

slice_from_str(img, prescan_area_def)
slice_from_str(img, image_area_def)
slice_from_str(img, overscan_area_def)
Crispin
  • 2,070
  • 1
  • 13
  • 16
  • 1
    Thanks, I think this is quite a logical way to do it but it is much longer than the answer above using ```eval```, which I have accepted as the answer. Thanks! – James McCormac Mar 29 '17 at 17:36