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!

4
  • 1
    I'd prefer storing them as slice objects, or tuple equivalents. (slice(None), slice(None,20)) or ((None), (None,20)). Commented Mar 29, 2017 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? Commented Mar 29, 2017 at 19:55
  • 1
    True; but a slice object is easier to apply programatically: np.arange(10)[ eval('slice(5,None)') ]. Commented Mar 29, 2017 at 21:02
  • Related: Numpy slicing from variable Commented Jul 26, 2019 at 10:19

4 Answers 4

6

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]])
Sign up to request clarification or add additional context in comments.

2 Comments

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.
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).
2

you can do something like:

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

and to use eval

prescan_area=eval(var1+prescan_area_def)

2 Comments

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 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)
2

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.strip('[]').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%

2 Comments

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 A bit late on my part, but you're right! I had missed a .strip('[]') in the code. Guess I had tested the input without the outer brackets. Fixed now
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)

1 Comment

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!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.