0

I can't not find the source of the difference in the handling of contrast for version 1.75.01 and 1.82. Here are two images that show what it used to look like (1.75), v 1.75 and what it now looks like: v1.82

Unfortunately, rolling back is not trivial as I run into problems with dependencies (especially PIL v PILLOW). The images are created from a numpy array, and I suspect there is something related to how the numbers are getting handled (?type, rounding) when the conversion from array to image occurs, but I can't find the bug. Any help will be deeply appreciated.

Edited - New Minimal Example

#! /bin/bash

import numpy as np
from psychopy import visual,core

def makeRow (n,c):
    cp = np.tile(c,[n,n,3])
    cm = np.tile(-c,[n,n,3])
    cpm = np.hstack((cp,cm))
    return(cpm)

def makeCB (r1,r2,nr=99):
    #nr is repeat number
    (x,y,z) = r1.shape
    if nr == 99:
        nr = x/2
    else:
        hnr = nr/2
    rr = np.vstack((r1,r2))
    cb=np.tile(rr,[hnr,hnr/2,1])
    return(cb)

def makeTarg(sqsz,targsz,con):
    wr = makeRow(sqsz,1)
    br = makeRow(sqsz,-1)
    cb = makeCB(wr,br,targsz)
    t = cb*con
    return(t)

def main():
    w = visual.Window(size = (400,400),units = "pix", winType = 'pyglet',colorSpace = 'rgb')
    fullCon_np = makeTarg(8,8,1.0)
    fullCon_i  = visual.ImageStim(w, image = fullCon_np,size = fullCon_np.shape[0:2][::-1],pos = (-100,0),colorSpace = 'rgb')
    fullCon_ih = visual.ImageStim(w, image = fullCon_np,size = fullCon_np.shape[0:2][::-1],pos = (-100,0),colorSpace = 'rgb')
    fullCon_iz = visual.ImageStim(w, image = fullCon_np,size = fullCon_np.shape[0:2][::-1],pos = (-100,0),colorSpace = 'rgb')
    fullCon_ih.contrast = 0.5
    fullCon_ih.setPos((-100,100))
    fullCon_iz.setPos((-100,-100))
    fullCon_iz.contrast = 0.1
    partCon_np = makeTarg(8,8,0.1)
    partCon_i  = visual.ImageStim(w, image = partCon_np,pos = (0,0), size = partCon_np.shape[0:2][::-1],colorSpace = 'rgb')
    zeroCon_np = makeTarg(8,8,0.0)
    zeroCon_i  = visual.ImageStim(w, image = zeroCon_np,pos=(100,0), size = zeroCon_np.shape[0:2][::-1],colorSpace = 'rgb')
    fullCon_i.draw()
    partCon_i.draw()
    fullCon_ih.draw()
    fullCon_iz.draw()
    zeroCon_i.draw()
    w.flip()
    core.wait(15)
    core.quit()

if __name__ == "__main__":
    main()

Which yields this:

enter image description here

The three checker-boards along the horizontal have the contrast changed in the array when generated before conversion to the image. The Vertical left shows that changing the image contrast afterwards works fine. The reason I can't use this is that a) I have collected a lot of data with the last version, and b) I want to grade the contrast of those big long bars in the centre programatically by multiplying one array against another, e.g. using a log scale or some other function, and doing the math is easier in numpy.

I still suspect the issue is in the conversion from np.array -> pil.image. The dtype of these array is float64, but even if I coerce to float32 nothing changes. If you examine the array before conversion at half contrast it is filled with 0.5 and -0.5 numbers, but all the negative numbers are getting turned to black and black is being set to zero at the time of conversion by psychopy.tools.imagetools.array2image I think.

brittAnderson
  • 1,428
  • 11
  • 25
  • 1
    To add to this, using ``psychopy.useVersion``, this behavior arose at version 1.81 but the behavior in the 1.80's was even weirder. I can't run versions before that due to some import problems, but you can have a go. In the top of your script, run ``import psychopy`` and on the next line ``psychopy.useVersion(1.76.00´)``. That's how far it can roll back. By the way, that's not a very minimal example since it imports a lot of stuff and defines several variables which are never used. – Jonas Lindeløv Apr 28 '15 at 21:20
  • I have found a work around, but it isn't really an answer to the question of why. You can just take the first "layer" of the NxNx3 array. Apparently, the image conversion will then treat the floats properly, e.g. `partCon_i_FIXED = visual.ImageStim(w, image = partCon_np[:,:,1],size = partCon_np.shape[0:2][::-1],pos = (0,100),colorSpace = 'rgb')`. But this still doesn't look exactly the same as when the full array is used and contrast adjusted afterwards. – brittAnderson Apr 29 '15 at 19:15

2 Answers2

2

OK, yes, the problem was to do with the issue of the scale for the array values. Basically, you've found a corner case that PsychoPy isn't handling correctly (i.e. a bug).

Explanation: PsychoPy has a complex set of transformation rules for handling image/textures; it tries to deduce what you're going to do with this image and whether it should be stored in a way that supports colour manipulations (signed float) or not (can be an unsigned byte). In your case PsychoPy was getting this wrong; the fact that the array was filled with floats made PsychoPy think it could do color transforms, but the fact that it was NxNx3 suggest it shouldn't (we don't want to specify a "color" for something that already has its color specified for every pixel as rgb vals).

Workarounds (any one of these):

  1. Just provide your array as NxN, not NxNx3. This is the right thing to do anyway; it means less for you to compute/store and by providing "intensity" values these can then be recolored on-the-fly. This is roughly what you had discovered already in providing just one slice of your NxNx3 array, but the point is that you could/should only create one slice in the first place.

  2. Use GratingStim, which converts everything to signed floating point values rather than trying to work out what's best (potentially then you'd need to work out the spatial frequency stuff though)

  3. You could add a line to fix it by rescaling your array (*0.5+0.5) but you'd have to set something so that this only occurred for this version (we'll fix it before the next release)

Basically, I'm suggesting you do (1) because that already works for past, present and future versions and is more efficient anyway. But thanks for letting us know - I'll try to make sure we catch this one in future

best wishes Jon

Jon
  • 1,198
  • 7
  • 8
0

The code is too long for me to read through and work out the actual problem.

What might be the problem is the issue of where zero should be. I think for a while numpy arrays were treated as having vals 0:1 whereas the rest of PsychoPy expects values to be -1:1 so it might be that you need to rescale your values with array=array*2-1 to get back to old (bad behaviour). Or check opacity too, which might have a similar issue. If you write a minimal example I'll read/test it properly

Thanks

Jon
  • 1,198
  • 7
  • 8
  • @Jonas You're both right about the minimal example of course. I guess it was "minimal" in terms of the amount of work I required of myself at the end of a frustrating day. I will work on it some more with your suggestions and if problems persist post a better minimal example. – brittAnderson Apr 29 '15 at 13:50