Noise stimulus

Hi Chris,

I’m attaching the code to generate the noise stimulus we want. There’s an example notebook with what the masks and the resulting stimulus frames look like. The python code should fairly precisely specify what we want.

I think we decided we wanted to be able to point the stimulus at a directory of unfiltered noise frames and an array with noise frame ordering, so that we could recover exactly the stimulus that was presented on each frame for later analysis.

Let us know what other questions you have as you implement this.

Thanks,
Mark

Attachment: for-chris-181002.zip (1.77 MB)

Hi Mark,

Thanks. It looks like I have everything I need. What’s the timeframe for when you want to have this in MWorks?

(FYI, I sent this to your Gmail address, because the support site seems to lose messages from NIH addresses. Sorry for the inconvenience!)

Chris

Great. There is not a huge rush - next 4 weeks possible to get us something to test? Can we test in a separate build before it goes into MWorks, perhaps as a histedlab plugin?

I also wish NIH email worked better.

Mark

Hi Mark,

I’ve attached a first pass at the “oriented noise” stimulus. I’ve included the source code, the compiled plugin (usable with the current MWorks nightly build), and a demo experiment.

At present, the stimulus generates the “base” noise itself, instead of reading it from files. I’m sharing it now because I’d like you to verify that the implementation of the stimulus is otherwise correct and what you want. (The math replicates your Python code, and the output qualitatively looks like your examples. Still, it’s hard for me to be sure I’ve gotten it exactly right.)

Also, the stimulus doesn’t include an integrated Gaussian mask. Instead, you need to put it in a layer underneath an appropriate mask stimulus. The example experiment demonstrates this.

Cheers,
Chris

Attachments:

Hi all,

Anna and Paul are now included in this discussion.

Also, I failed to be explicit about how to install the compiled plugin: Unzip OrientedNoise.bundle.zip, and then move the .bundle to the directory /Library/Application Support/MWorks/Plugins/Core. After that, you’ll need to restart MWServer.

Cheers,
Chris

Hello Chris,

Thank you for providing the stimulus. We have looked at you’ve provided and we have a few comments:

  1. We noticed that we provided you with an earlier version of our stimulus code that has a bug. I’ve attached the code that should be implemented instead - it’s only a few differences and should be simpler to implement (I’ve also attached a screenshot of the diff between codes for convenience).

  2. We also noticed a potential issue with the contrast. I’ve attached images, but it appears that the mean pixel value across the stimulus is less than that of the background layer. I’ve attached two images where we decrement contrast by adjusting the alpha value. With alpha at 1.0, the stimulus looks correct compared to the background. When alpha is at 0.5, the stimulus appears darker than the background. Would you have any insight into what could be causing this?

  3. Just a small edit for ease of use - can we have the stimulus orientation parameter input changed from radians to degrees?

  4. Last, and slightly unrelated, do you have a status update on converting our current hold-and-detect experiment to .mwel?

Please let me know if you have any questions or would like any other information. Thank you!

-PKL

Attachments:

Hi Paul,

We noticed that we provided you with an earlier version of our stimulus code that has a bug. I’ve attached the code that should be implemented instead

The code that Mark gave me (in for-chris-181002.zip) is identical to what you attached, so this is already what I’ve implemented.

We also noticed a potential issue with the contrast.

Yes, I see what you mean. The issue is that the alpha_multiplier parameter of MWorks’ stimuli only behaves like a contrast setting when the stimulus is drawn directly on top of a 50% gray background. When the stimulus is part of a layer that’s drawn on top of a 50% gray background, the blending works out differently. (Specifically, the stimulus is first blended with the layer’s background, whose red, green, blue, and alpha color components are all zero. Then, the layer is blended with the content under it.)

Luckily, there’s a simple fix: In the noise’s layer, draw a 50% gray rectangle directly beneath the noise. I’ve attached a demo experiment that shows two oriented noise stimuli, both with an alpha of 0.5. The one on the right has a 50% gray rectangle under it, while the one on the left does not. When run, the left stimulus looks like the alpha 0.5 image you sent (i.e. too dark), while the right one looks like it maintains the correct mean luminance.

Just a small edit for ease of use - can we have the stimulus orientation parameter input changed from radians to degrees?

Sure. You mean theta_0, right? Do you also want sigma_theta to be in degrees, or should that stay in radians?

Last, and slightly unrelated, do you have a status update on converting our current hold-and-detect experiment to .mwel?

Sorry, but I failed to grab a copy before Mark removed my access to the repository. If you can add me again, I’ll clone a copy for myself. Or, if you want to email me a tarball of the source, that’d be fine.

Cheers,
Chris

Attachment: oriented_noise_bg_rect.mwel.zip (462 Bytes)

Hello Chris,

  1. We have taken another look at the stimulus code and have fixed the bug. I’ve attached below the new stimulus code and a screenshot of the new diff (two lines to change).

  2. We will take a look at your demo for handling the contrast and let you know if we have trouble.

  3. Yes, we would like degrees for both theta_0 and sigma_theta.

Thank you for your help!

-PKL

Attachments:

Hi Paul,

I don’t understand the math in the new code:

noisefr = np.fft.ifft2(np.fft.ifftshift(ftimagep)).real
noisefr = noisefr - noisefr.mean()
maxamp = np.max(np.abs(noisefr))
minamp = np.min(np.abs(noisefr))
noisefr = (noisefr-minamp) / (maxamp-minamp)

The addition of an ifftshift call makes sense to me. However, putting it in to my code seems to add an angular offset to the noise, along with a visible mesh-like pattern. (See attached image.) I had tried adding it before, and I assumed that these effects were the reason it was omitted.

Also, I don’t understand the normalization code. The last line makes it look like you’re trying to map the range of values in noisefr on to [0,1], which makes sense (and is ultimately what I have to do to display the noise with OpenGL). However, you’re using the minimum and maximum amplitude, as opposed to the signed min and max. Since you’ve translated noisefr to have mean zero, won’t minamp always be zero, in which case you’re just dividing by maxamp as before?

Thanks,
Chris

Attachment: with_ifftshift.jpg (194 KB)

Hi Chris,

I can comment as I looked at this too, and Paul can chime in.

I think there were two problems that are fixed here:

  • inverse shift (prev. omitted)
  • work with real part of inverse transform (prev: abs())
  1. the new code appears to work in our hands, perhaps Paul can attach figure output as we vary orientation. (I think the problem previously was that abs() was hiding the problems due to failture to shift, though you can see problems emerge if you take the stimulus you sent and vary orientation around the whole circle.)

  2. Normalization: What we see is that ifft2() produces pixel values in roughly range [-1, 1]. So we subtract the mean, and find the min (around -1) and max (around +1). Then we find
    (noise-minamp)/(maxamp-minamp)
    which becomes
    (noise+1)/2
    and thus maps onto [0,1].

Mark

Hi Mark,

Normalization: What we see is that ifft2() produces pixel values in roughly range [-1, 1]. So we subtract the mean, and find the min (around -1) and max (around +1). Then we find (noise-minamp)/(maxamp-minamp) which becomes (noise+1)/2 and thus maps onto [0,1]

That makes sense, but that isn’t what your code does. The key part:

maxamp = np.max(np.abs(noisefr))
minamp = np.min(np.abs(noisefr))

You’re taking the min and max of the absolute value of the noise. Hence, maxamp will be around 1, minamp will be around zero, and you get (noise-0)/1. Is that an error, or am I missing something?

Chris

Hi Chris,

Yes, you’re right. That was a bug in my description.

That code should actually return values from [-1,1] (which is what was going on), not [0,1]. Then that frame should be put on a neutral gray screen where zero == gray, as with DriftingGrating.

I fixed that (patch snapshot attached) and regenrated the patches as I vary all the parameters, also attached. This looks correct.
Anything else to resolve?

Thanks,
Mark

Attachments:

Hi Mark,

That code should actually return values from [-1,1] (which is what was going on), not [0,1]. Then that frame should be put on a neutral gray screen where zero == gray, as with DriftingGrating.

Is this different than mapping the values into [0,1], with neutral gray equal to 0.5? I’ve been doing the latter, because the OpenGL color buffer takes values between 0 and 1. If it’s not equivalent, then I don’t understand what you want.

Chris