From 190666a067fcee3bfd7b6f327b53c2dfa266010e Mon Sep 17 00:00:00 2001 From: David Rotermund <54365609+davrot@users.noreply.github.com> Date: Fri, 28 Jul 2023 15:42:20 +0200 Subject: [PATCH] Add files via upload --- RenderStimuli/CPPExtensions/Makefile | 43 ++ RenderStimuli/CPPExtensions/PyTCopyCPU.cpp | 15 + RenderStimuli/CPPExtensions/TCopyCPU.cpp | 196 +++++++ RenderStimuli/CPPExtensions/TCopyCPU.h | 52 ++ RenderStimuli/CPPExtensions/test.py | 78 +++ RenderStimuli/contours.py | 603 +++++++++++++++++++++ RenderStimuli/meanGabors.py | 73 +++ RenderStimuli/render.py | 269 +++++++++ RenderStimuli/renderLess.py | 299 ++++++++++ RenderStimuli/test_readgaborfield.py | 24 + RenderStimuli/z.mat | Bin 0 -> 190776 bytes 11 files changed, 1652 insertions(+) create mode 100644 RenderStimuli/CPPExtensions/Makefile create mode 100644 RenderStimuli/CPPExtensions/PyTCopyCPU.cpp create mode 100644 RenderStimuli/CPPExtensions/TCopyCPU.cpp create mode 100644 RenderStimuli/CPPExtensions/TCopyCPU.h create mode 100644 RenderStimuli/CPPExtensions/test.py create mode 100644 RenderStimuli/contours.py create mode 100644 RenderStimuli/meanGabors.py create mode 100644 RenderStimuli/render.py create mode 100644 RenderStimuli/renderLess.py create mode 100644 RenderStimuli/test_readgaborfield.py create mode 100644 RenderStimuli/z.mat diff --git a/RenderStimuli/CPPExtensions/Makefile b/RenderStimuli/CPPExtensions/Makefile new file mode 100644 index 0000000..7b61abf --- /dev/null +++ b/RenderStimuli/CPPExtensions/Makefile @@ -0,0 +1,43 @@ +PYBIN=/home/kk/P3.10/bin/ +CC=/usr/lib64/ccache/clang++ +NVCC=/usr/local/cuda-12/bin/nvcc -allow-unsupported-compiler + +O_DIRS = o/ + +PARAMETERS_O_CPU = -O3 -std=c++14 -fPIC -Wall -fopenmp=libomp +PARAMETERS_Linker_CPU = -shared -lm -lomp -lstdc++ -Wall + +PARAMETERS_O_GPU= -O3 -std=c++14 -ccbin=$(CC) \ + -Xcompiler "-fPIC -Wall -fopenmp=libomp" +PARAMETERS_Linker_GPU=-Xcompiler "-shared -lm -lomp -lstdc++ -Wall" + +name = TCopy +type = CPU + +PYPOSTFIX := $(shell $(PYBIN)python3-config --extension-suffix) +PYBIND11INCLUDE := $(shell $(PYBIN)python3 -m pybind11 --includes) +PARAMETERS_O = $(PARAMETERS_O_CPU) $(PYBIND11INCLUDE) +PARAMETERS_Linker = $(PARAMETERS_Linker_CPU) + +so_file = Py$(name)$(type)$(PYPOSTFIX) +pyi_file = Py$(name)$(type).pyi +all: $(so_file) + +$(O_DIRS)$(name)$(type).o: $(name)$(type).h $(name)$(type).cpp + mkdir -p $(O_DIRS) + $(CC) $(PARAMETERS_O) -c $(name)$(type).cpp -o $(O_DIRS)$(name)$(type).o + +$(O_DIRS)Py$(name)$(type).o: $(name)$(type).h Py$(name)$(type).cpp + mkdir -p $(O_DIRS) + $(CC) $(PARAMETERS_O) -c Py$(name)$(type).cpp -o $(O_DIRS)Py$(name)$(type).o + +$(so_file): $(O_DIRS)$(name)$(type).o $(O_DIRS)Py$(name)$(type).o + $(CC) $(PARAMETERS_Linker) -o $(so_file) $(O_DIRS)$(name)$(type).o $(O_DIRS)Py$(name)$(type).o + + +####################### +clean: + rm -rf $(O_DIRS) + rm -f $(so_file) + rm -f $(pyi_file) + diff --git a/RenderStimuli/CPPExtensions/PyTCopyCPU.cpp b/RenderStimuli/CPPExtensions/PyTCopyCPU.cpp new file mode 100644 index 0000000..25405ff --- /dev/null +++ b/RenderStimuli/CPPExtensions/PyTCopyCPU.cpp @@ -0,0 +1,15 @@ +#include + +#include "TCopyCPU.h" + +namespace py = pybind11; + +PYBIND11_MODULE(PyTCopyCPU, m) +{ + m.doc() = "TCopyCPU Module"; + py::class_(m, "TCopyCPU") + .def(py::init<>()) + .def("process", + &TCopyCPU::process); +} + diff --git a/RenderStimuli/CPPExtensions/TCopyCPU.cpp b/RenderStimuli/CPPExtensions/TCopyCPU.cpp new file mode 100644 index 0000000..8b85d7c --- /dev/null +++ b/RenderStimuli/CPPExtensions/TCopyCPU.cpp @@ -0,0 +1,196 @@ +#include "TCopyCPU.h" + +#include +#include +#include + +#include +#include +#include + + +TCopyCPU::TCopyCPU() +{ + +}; + +TCopyCPU::~TCopyCPU() +{ + +}; + + +void TCopyCPU::process( + int64_t sparse_pointer_addr, // int64 * + int64_t sparse_dim_0, // Gabor ID + int64_t sparse_dim_1, // Gabor Parameter + + int64_t gabor_pointer_addr, // float32 * + int64_t gabor_dim_0, // Gabor ID + int64_t gabor_dim_1, // X + int64_t gabor_dim_2, // Y + + int64_t output_pointer_addr, // float32 * + int64_t output_dim_0, // Pattern ID + int64_t output_dim_1, // X + int64_t output_dim_2, // Y + + int64_t number_of_cpu_processes +){ + int64_t* sparse_pointer = (int64_t*)sparse_pointer_addr; + float* gabor_pointer = (float*)gabor_pointer_addr; + float* output_pointer = (float*)output_pointer_addr; + + // Sparse Matrix + assert((sparse_pointer != nullptr)); + assert((sparse_dim_0 > 0)); + assert((sparse_dim_1 > 0)); + + // I assume three parameters: Pattern ID, Type, X, Y + assert((sparse_dim_1 == 4)); + + // Gabor Matrix + assert((gabor_pointer != nullptr)); + assert((gabor_dim_0 > 0)); + assert((gabor_dim_1 > 0)); + assert((gabor_dim_2 > 0)); + + // Output Matrix + assert((output_pointer != nullptr)); + assert((output_dim_0 > 0)); + assert((output_dim_1 > 0)); + + + // Cache data for the pointer calculations + size_t sparse_dim_c0 = sparse_dim_1; + + size_t gabor_dim_c0 = gabor_dim_1 * gabor_dim_2; + // size_t gabor_dim_c1 = gabor_dim_2; + + size_t output_dim_c0 = output_dim_1 * output_dim_2; + size_t output_dim_c1 = output_dim_2; + + assert((number_of_cpu_processes > 0)); + + omp_set_num_threads(number_of_cpu_processes); + + // DEBUG: + // omp_set_num_threads(1); + +#pragma omp parallel for + for (size_t gabor_id = 0; gabor_id < sparse_dim_0; gabor_id++) + { + process_sub(gabor_id, + sparse_pointer, + sparse_dim_c0, + + gabor_pointer, + gabor_dim_0, + gabor_dim_1, + gabor_dim_2, + gabor_dim_c0, + + output_pointer, + output_dim_0, + output_dim_1, + output_dim_2, + output_dim_c0, + output_dim_c1 + ); + } + + return; +}; + + +void TCopyCPU::process_sub( + size_t gabor_id, + int64_t* sparse_pointer, + size_t sparse_dim_c0, + + float* gabor_pointer, + int64_t gabor_dim_0, + int64_t gabor_dim_1, + int64_t gabor_dim_2, + size_t gabor_dim_c0, + + float* output_pointer, + int64_t output_dim_0, + int64_t output_dim_1, + int64_t output_dim_2, + size_t output_dim_c0, + size_t output_dim_c1 + +){ + int64_t* sparse_offset = sparse_pointer + gabor_id * sparse_dim_c0; + // Extract the gabor parameter + int64_t gabor_pattern_id = sparse_offset[0]; + int64_t gabor_type = sparse_offset[1]; + int64_t gabor_x = sparse_offset[2]; + int64_t gabor_y = sparse_offset[3]; + + // Filter out non valid stimulus ids -- we don't do anything + if ((gabor_pattern_id < 0) || (gabor_pattern_id >= output_dim_0)) { + printf("Stimulus ID=%li outside range [0, %li]!\n", + (long int)gabor_pattern_id, (long int)output_dim_0); + return; + } + + // Filter out non valid patch types -- we don't do anything + if ((gabor_type < 0) || (gabor_type >= gabor_dim_0)) { + printf("Patch ID=%li outside range [0, %li]!\n", + (long int)gabor_type, (long int)gabor_dim_0); + return; + } + + // X position is too big -- we don't do anything + if (gabor_x >= output_dim_1) { + return; + } + + // Y position is too big -- we don't do anything + if (gabor_y >= output_dim_2){ + return; + } + + + // Get the offset to the gabor patch + float* gabor_offset = gabor_pointer + gabor_type * gabor_dim_c0; + + // Get the offset to the output image with the id pattern_id + float* output_offset = output_pointer + gabor_pattern_id * output_dim_c0; + + float* output_position_x = nullptr; + int64_t gabor_y_start = gabor_y; + + for (int64_t g_x = 0; g_x < gabor_dim_1; g_x ++) { + + // Start at the first y (i.e. last dimension) position + gabor_y = gabor_y_start; + + // We copy only if we are on the output canvas -- X dimension + if ((gabor_x >= 0) && (gabor_x < output_dim_1)) { + + // Where is our x line in memory? + output_position_x = output_offset + gabor_x * output_dim_c1; + + for (int64_t g_y = 0; g_y < gabor_dim_2; g_y ++) { + + // We copy only if we are on the output canvas -- Y dimension + if ((gabor_y >= 0) && (gabor_y < output_dim_2)) { + output_position_x[gabor_y] += *gabor_offset; + } + gabor_offset++; + gabor_y++; + } + } + // We skip an x line + else + { + gabor_offset += gabor_dim_2; + } + gabor_x++; + } + + return; +}; diff --git a/RenderStimuli/CPPExtensions/TCopyCPU.h b/RenderStimuli/CPPExtensions/TCopyCPU.h new file mode 100644 index 0000000..5395e20 --- /dev/null +++ b/RenderStimuli/CPPExtensions/TCopyCPU.h @@ -0,0 +1,52 @@ +#ifndef TCOPYCPU +#define TCOPYCPU + +#include + +#include +#include + +class TCopyCPU +{ + public: + TCopyCPU(); + ~TCopyCPU(); + + void process( + int64_t sparse_pointer_addr, // int64 * + int64_t sparse_dim_0, // Gabor ID + int64_t sparse_dim_1, // Gabor Parameter + + int64_t garbor_pointer_addr, // float32 * + int64_t garbor_dim_0, // Gabor ID + int64_t garbor_dim_1, // X + int64_t garbor_dim_2, // Y + + int64_t output_pointer_addr, // float32 * + int64_t output_dim_0, // Pattern ID + int64_t output_dim_1, // X + int64_t output_dim_2, // Y + + int64_t number_of_cpu_processes + ); + + private: + void process_sub( + size_t gabor_id, + int64_t* sparse_pointer, + size_t sparse_dim_c0, + float* garbor_pointer, + int64_t garbor_dim_0, + int64_t garbor_dim_1, + int64_t garbor_dim_2, + size_t garbor_dim_c0, + float* output_pointer, + int64_t output_dim_0, + int64_t output_dim_1, + int64_t output_dim_2, + size_t output_dim_c0, + size_t output_dim_c1 + ); +}; + +#endif /* TCOPYCPU */ diff --git a/RenderStimuli/CPPExtensions/test.py b/RenderStimuli/CPPExtensions/test.py new file mode 100644 index 0000000..662beb2 --- /dev/null +++ b/RenderStimuli/CPPExtensions/test.py @@ -0,0 +1,78 @@ +import torch +import matplotlib.pyplot as plt +import os + +from PyTCopyCPU import TCopyCPU + + +copyier = TCopyCPU() + +# Output canvas +output_pattern: int = 10 +output_x: int = 160 +output_y: int = 200 # Last dim + +output = torch.zeros( + (output_pattern, output_x, output_y), device="cpu", dtype=torch.float32 +) + +# The "gabors" +garbor_amount: int = 3 +garbor_x: int = 11 +garbor_y: int = 13 # Last dim + +gabor = torch.arange( + 1, garbor_amount * garbor_x * garbor_y + 1, device="cpu", dtype=torch.float32 +).reshape((garbor_amount, garbor_x, garbor_y)) + +# The sparse control matrix + +sparse_matrix = torch.zeros((3, 4), device="cpu", dtype=torch.int64) + +sparse_matrix[0, 0] = 0 # pattern_id -> output dim 0 +sparse_matrix[0, 1] = 2 # gabor_type -> gabor dim 0 +sparse_matrix[0, 2] = 0 # gabor_x -> output dim 1 start point +sparse_matrix[0, 3] = 0 # gabor_x -> output dim 2 start point + +sparse_matrix[1, 0] = 0 # pattern_id -> output dim 0 +sparse_matrix[1, 1] = 1 # gabor_type -> gabor dim 0 +sparse_matrix[1, 2] = 40 # gabor_x -> output dim 1 start point +sparse_matrix[1, 3] = 60 # gabor_x -> output dim 2 start point + +sparse_matrix[2, 0] = 1 # pattern_id -> output dim 0 +sparse_matrix[2, 1] = 0 # gabor_type -> gabor dim 0 +sparse_matrix[2, 2] = 0 # gabor_x -> output dim 1 start point +sparse_matrix[2, 3] = 0 # gabor_x -> output dim 2 start point + +# ########################################### + +assert sparse_matrix.ndim == 2 +assert int(sparse_matrix.shape[1]) == 4 + +assert gabor.ndim == 3 +assert output.ndim == 3 + +number_of_cpu_processes = os.cpu_count() + +copyier.process( + sparse_matrix.data_ptr(), + int(sparse_matrix.shape[0]), + int(sparse_matrix.shape[1]), + gabor.data_ptr(), + int(gabor.shape[0]), + int(gabor.shape[1]), + int(gabor.shape[2]), + output.data_ptr(), + int(output.shape[0]), + int(output.shape[1]), + int(output.shape[2]), + int(number_of_cpu_processes), +) + +plt.imshow(output[0, :, :]) +plt.title("Pattern 0") +plt.show() + +plt.imshow(output[1, :, :]) +plt.title("Pattern 1") +plt.show() diff --git a/RenderStimuli/contours.py b/RenderStimuli/contours.py new file mode 100644 index 0000000..a614c5f --- /dev/null +++ b/RenderStimuli/contours.py @@ -0,0 +1,603 @@ +# %% +# +# contours.py +# +# Tools for contour integration studies +# +# Version 2.0, 28.04.2023: +# phases can now be randomized... +# + +# +# Coordinate system assumptions: +# +# for arrays: +# [..., HEIGHT, WIDTH], origin is on TOP LEFT +# HEIGHT indices *decrease* with increasing y-coordinates (reversed) +# WIDTH indices *increase* with increasing x-coordinates (normal) +# +# Orientations: +# 0 is horizontal, orientation *increase* counter-clockwise +# Corner elements, quantified by [dir_source, dir_change]: +# - consist of two legs +# - contour *enters* corner from *source direction* at one leg +# and goes from border to its center... +# - contour path changes by *direction change* and goes +# from center to the border +# + +import torch +import time +import matplotlib.pyplot as plt +import math +import scipy.io +import numpy as np +import os + +torch_device = "cuda" +default_dtype = torch.float32 +torch.set_default_dtype(default_dtype) +torch.device(torch_device) + + +# +# performs a coordinate transform (rotation with phi around origin) +# rotation is performed CLOCKWISE with increasing phi +# +# remark: rotating a mesh grid by phi and orienting an image element +# along the new x-axis is EQUIVALENT to rotating the image element +# by -phi (so this realizes a rotation COUNTER-CLOCKWISE with +# increasing phi) +# +def rotate_CW(x: torch.Tensor, y: torch.Tensor, phi: torch.float32): + xr = +x * torch.cos(phi) + y * torch.sin(phi) + yr = -x * torch.sin(phi) + y * torch.cos(phi) + + return xr, yr + + +# +# renders a Gabor with (or without) corner +# +def gaborner( + r_gab: int, # radius, size will be 2*r_gab+1 + dir_source: float, # contour enters in this dir + dir_change: float, # contour turns around by this dir + lambdah: float, # wavelength of Gabor + sigma: float, # half-width of Gabor + phase: float, # phase of Gabor + normalize: bool, # normalize patch to zero + torch_device: str, # GPU or CPU... +) -> torch.Tensor: + # incoming dir: change to outgoing dir + dir1 = dir_source + torch.pi + nook = dir_change - torch.pi + + # create coordinate grids + d_gab = 2 * r_gab + 1 + x = -r_gab + torch.arange(d_gab, device=torch_device) + yg, xg = torch.meshgrid(x, x, indexing="ij") + + # put into tensor for performing vectorized scalar products + xyg = torch.zeros([d_gab, d_gab, 1, 2], device=torch_device) + xyg[:, :, 0, 0] = xg + xyg[:, :, 0, 1] = yg + + # create Gaussian hull + gauss = torch.exp(-(xg**2 + yg**2) / 2 / sigma**2) + gabor_corner = gauss.clone() + + if (dir_change == 0) or (dir_change == torch.pi): + # handle special case of straight Gabor or change by 180 deg + + # vector orth to Gabor axis + ev1_orth = torch.tensor( + [math.cos(-dir1 + math.pi / 2), math.sin(-dir1 + math.pi / 2)], + device=torch_device, + ) + # project coords to orth vector to get distance + legs = torch.cos( + 2 + * torch.pi + * torch.matmul(xyg, ev1_orth.unsqueeze(1).unsqueeze(0).unsqueeze(0)) + / lambdah + + phase + ) + gabor_corner *= legs[:, :, 0, 0] + + else: + dir2 = dir1 + nook + + # compute separation line between corner's legs + ev1 = torch.tensor([math.cos(-dir1), math.sin(-dir1)], device=torch_device) + ev2 = torch.tensor([math.cos(-dir2), math.sin(-dir2)], device=torch_device) + v_towards_1 = (ev1 - ev2).unsqueeze(1).unsqueeze(0).unsqueeze(0) + + # which coords belong to which leg? + which_side = torch.matmul(xyg, v_towards_1)[:, :, 0, 0] + towards_1y, towards_1x = torch.where(which_side > 0) + towards_2y, towards_2x = torch.where(which_side <= 0) + + # compute orth distance to legs + side_sign = -1 + 2 * ((dir_change % 2 * torch.pi) > torch.pi) + ev12 = ev1 + ev2 + v1_orth = ev12 - ev1 * torch.matmul(ev12, ev1) + v2_orth = ev12 - ev2 * torch.matmul(ev12, ev2) + ev1_orth = side_sign * v1_orth / torch.sqrt((v1_orth**2).sum()) + ev2_orth = side_sign * v2_orth / torch.sqrt((v2_orth**2).sum()) + + leg1 = torch.cos( + 2 + * torch.pi + * torch.matmul(xyg, ev1_orth.unsqueeze(1).unsqueeze(0).unsqueeze(0)) + / lambdah + + phase + ) + leg2 = torch.cos( + 2 + * torch.pi + * torch.matmul(xyg, ev2_orth.unsqueeze(1).unsqueeze(0).unsqueeze(0)) + / lambdah + + phase + ) + gabor_corner[towards_1y, towards_1x] *= leg1[towards_1y, towards_1x, 0, 0] + gabor_corner[towards_2y, towards_2x] *= leg2[towards_2y, towards_2x, 0, 0] + + # depending on phase, Gabor might not be normalized... + if normalize: + s = gabor_corner.sum() + s0 = gauss.sum() + gabor_corner -= s / s0 * gauss + + return gabor_corner + + +# +# creates a filter bank of Gabor corners +# +# outputs: +# filters: [n_source, n_change, HEIGHT, WIDTH] +# dirs_source: [n_source] +# dirs_change: [n_change] +# +def gaborner_filterbank( + r_gab: int, # radius, size will be 2*r_gab+1 + n_source: int, # number of source orientations + n_change: int, # number of direction changes + lambdah: float, # wavelength of Gabor + sigma: float, # half-width of Gabor + phase: float, # phase of Gabor + normalize: bool, # normalize patch to zero + torch_device: str, # GPU or CPU... +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + kernels = torch.zeros( + [n_source, n_change, 2 * r_gab + 1, 2 * r_gab + 1], + device=torch_device, + requires_grad=False, + ) + dirs_source = 2 * torch.pi * torch.arange(n_source, device=torch_device) / n_source + dirs_change = 2 * torch.pi * torch.arange(n_change, device=torch_device) / n_change + + for i_source in range(n_source): + for i_change in range(n_change): + gabor_corner = gaborner( + r_gab=r_gab, + dir_source=dirs_source[i_source], + dir_change=dirs_change[i_change], + lambdah=lambdah, + sigma=sigma, + phase=phase, + normalize=normalize, + torch_device=torch_device, + ) + kernels[i_source, i_change] = gabor_corner + + # check = torch.isnan(gabor_corner).sum() + # if check > 0: + # print(i_source, i_change, check) + # kernels[i_source, i_change] = 1 + + return kernels, dirs_source, dirs_change + + +def discretize_stimuli( + posori, + x_range: tuple, + y_range: tuple, + scale_factor: float, + r_gab_PIX: int, + n_source: int, + n_change: int, + torch_device: str, + n_phase: int = 1, +) -> torch.Tensor: + # check correct input size + s = posori.shape + assert len(s) == 2, "posori should be NDARRAY with N x 1 entries" + assert s[1] == 1, "posori should be NDARRAY with N x 1 entries" + + # determine size of (extended) canvas + x_canvas_PIX = torch.tensor( + (x_range[1] - x_range[0]) * scale_factor, device=torch_device + ).ceil() + y_canvas_PIX = torch.tensor( + (y_range[1] - y_range[0]) * scale_factor, device=torch_device + ).ceil() + x_canvas_ext_PIX = int(x_canvas_PIX + 2 * r_gab_PIX) + y_canvas_ext_PIX = int(y_canvas_PIX + 2 * r_gab_PIX) + + # get number of contours + n_contours = s[0] + index_phasrcchg = [] + index_y = [] + index_x = [] + for i_contour in range(n_contours): + x_y_src_chg = torch.asarray(posori[i_contour, 0][1:, :].copy()) + x_y_src_chg[2] += torch.pi + + # if i_contour == 0: + # print(x_y_src_chg[2][:3]) + + # compute integer coordinates and find all visible elements + x = ((x_y_src_chg[0] - x_range[0]) * scale_factor + r_gab_PIX).type(torch.long) + y = y_canvas_ext_PIX - ( + (x_y_src_chg[1] - y_range[0]) * scale_factor + r_gab_PIX + ).type(torch.long) + i_visible = torch.where( + (x >= 0) * (y >= 0) * (x < x_canvas_ext_PIX) * (y < y_canvas_ext_PIX) + )[0] + + # compute integer (changes of) directions + i_source = ( + ((((x_y_src_chg[2]) / (2 * torch.pi)) + 1 / (2 * n_source)) % 1) * n_source + ).type(torch.long) + i_change = ( + (((x_y_src_chg[3] / (2 * torch.pi)) + 1 / (2 * n_change)) % 1) * n_change + ).type(torch.long) + + i_phase = torch.randint(n_phase, i_visible.size()) + index_phasrcchg.append( + (i_phase * n_source + i_source[i_visible]) * n_change + i_change[i_visible] + ) + # index_change.append(i_change[i_visible]) + index_y.append(y[i_visible]) + index_x.append(x[i_visible]) + + return ( + index_phasrcchg, + index_x, + index_y, + x_canvas_ext_PIX, + y_canvas_ext_PIX, + ) + + +def render_stimulus( + kernels, index_element, index_y, index_x, y_canvas, x_canvas, torch_device +): + s = kernels.shape + kx = s[-1] + ky = s[-2] + + stimulus = torch.zeros((y_canvas + ky - 1, x_canvas + kx - 1), device=torch_device) + n = index_element.size()[0] + for i in torch.arange(n, device=torch_device): + x = index_x[i] + y = index_y[i] + stimulus[y : y + ky, x : x + kx] += kernels[index_element[i]] + + return stimulus[ky - 1 : -(ky - 1), kx - 1 : -(kx - 1)] + + +if __name__ == "__main__": + VERBOSE = True + BENCH_CONVOLVE = True + BENCH_GPU = True + BENCH_CPU = True + BENCH_DAVID = True + + print("Testing contour rendering speed:") + print("================================") + + # load contours, multiplex coordinates to simulate a larger set of contours + n_multiplex = 1 + mat = scipy.io.loadmat("z.mat") + posori = np.tile(mat["z"], (n_multiplex, 1)) + n_contours = posori.shape[0] + print(f"Processing {n_contours} contour stimuli") + + # how many contours to render simultaneously? + n_simultaneous = 5 + n_simultaneous_chunks, n_remaining = divmod(n_contours, n_simultaneous) + assert n_remaining == 0, "Check parameters for simultaneous contour rendering!" + + # repeat some times for speed testing + n_repeat = 10 + t_dis = torch.zeros((n_repeat + 2), device=torch_device) + t_con = torch.zeros((n_repeat + 2), device=torch_device) + t_rsg = torch.zeros((n_repeat + 2), device=torch_device) + t_rsc = torch.zeros((n_repeat + 2), device="cpu") + t_rsd = torch.zeros((n_repeat + 2), device="cpu") + + # cutout for stimuli, and gabor parameters + x_range = [140, 940] + y_range = [140, 940] + d_gab = 40 + lambdah = 12 + sigma = 8 + phase = 0.0 + normalize = True + + # scale to convert coordinates to pixel values + scale_factor = 0.25 + + # number of directions for dictionary + n_source = 32 + n_change = 32 + + # convert sizes to pixel units + lambdah_PIX = lambdah * scale_factor + sigma_PIX = sigma * scale_factor + r_gab_PIX = int(d_gab * scale_factor / 2) + d_gab_PIX = r_gab_PIX * 2 + 1 + + # make filterbank + kernels, dirs_source, dirs_change = gaborner_filterbank( + r_gab=r_gab_PIX, + n_source=n_source, + n_change=n_change, + lambdah=lambdah_PIX, + sigma=sigma_PIX, + phase=phase, + normalize=normalize, + torch_device=torch_device, + ) + kernels = kernels.reshape([1, n_source * n_change, d_gab_PIX, d_gab_PIX]) + kernels_flip = kernels.flip(dims=(-1, -2)) + + # define "network" and put to cuda + conv = torch.nn.Conv2d( + in_channels=n_source * n_change, + out_channels=1, + kernel_size=d_gab_PIX, + stride=1, + device=torch_device, + ) + conv.weight.data = kernels_flip + + print("Discretizing START!!!") + t_dis[0] = time.perf_counter() + for i_rep in range(n_repeat): + # discretize + ( + index_srcchg, + index_x, + index_y, + x_canvas, + y_canvas, + ) = discretize_stimuli( + posori=posori, + x_range=x_range, + y_range=y_range, + scale_factor=scale_factor, + r_gab_PIX=r_gab_PIX, + n_source=n_source, + n_change=n_change, + torch_device=torch_device, + ) + t_dis[i_rep + 1] = time.perf_counter() + t_dis[-1] = time.perf_counter() + print("Discretizing END!!!") + + if BENCH_CONVOLVE: + print("Allocating!") + stimuli = torch.zeros( + [n_simultaneous, n_source * n_change, y_canvas, x_canvas], + device=torch_device, + requires_grad=False, + ) + + print("Generation by CONVOLUTION start!") + t_con[0] = time.perf_counter() + for i_rep in torch.arange(n_repeat): + for i_simultaneous_chunks in torch.arange(n_simultaneous_chunks): + i_ofs = i_simultaneous_chunks * n_simultaneous + + for i_sim in torch.arange(n_simultaneous): + stimuli[ + i_sim, + index_srcchg[i_sim + i_ofs], + index_y[i_sim + i_ofs], + index_x[i_sim + i_ofs], + ] = 1 + + output = conv(stimuli) + + for i_sim in range(n_simultaneous): + stimuli[ + i_sim, + index_srcchg[i_sim + i_ofs], + index_y[i_sim + i_ofs], + index_x[i_sim + i_ofs], + ] = 0 + + t_con[i_rep + 1] = time.perf_counter() + t_con[-1] = time.perf_counter() + print("Generation by CONVOLUTION stop!") + + if BENCH_GPU: + print("Generation by GPU start!") + output_gpu = torch.zeros( + ( + n_contours, + y_canvas - d_gab_PIX + 1, + x_canvas - d_gab_PIX + 1, + ), + device=torch_device, + ) + t_rsg[0] = time.perf_counter() + for i_rep in torch.arange(n_repeat): + for i_con in torch.arange(n_contours): + output_gpu[i_con] = render_stimulus( + kernels=kernels[0], + index_element=index_srcchg[i_con], + index_y=index_y[i_con], + index_x=index_x[i_con], + y_canvas=y_canvas, + x_canvas=x_canvas, + torch_device=torch_device, + ) + # output_gpu = torch.clip(output_gpu, -1, +1) + + t_rsg[i_rep + 1] = time.perf_counter() + t_rsg[-1] = time.perf_counter() + print("Generation by GPU stop!") + + if BENCH_CPU: + print("Generation by CPU start!") + output_cpu = torch.zeros( + ( + n_contours, + y_canvas - d_gab_PIX + 1, + x_canvas - d_gab_PIX + 1, + ), + device="cpu", + ) + kernels_cpu = kernels.detach().cpu() + t_rsc[0] = time.perf_counter() + for i_rep in range(n_repeat): + for i_con in range(n_contours): + output_cpu[i_con] = render_stimulus( + kernels=kernels_cpu[0], + index_element=index_srcchg[i_con], + index_y=index_y[i_con], + index_x=index_x[i_con], + y_canvas=y_canvas, + x_canvas=x_canvas, + torch_device="cpu", + ) + # output_cpu = torch.clip(output_cpu, -1, +1) + + t_rsc[i_rep + 1] = time.perf_counter() + t_rsc[-1] = time.perf_counter() + print("Generation by CPU stop!") + + if BENCH_DAVID: + print("Generation by DAVID start!") + from CPPExtensions.PyTCopyCPU import TCopyCPU as render_stimulus_CPP + + copyier = render_stimulus_CPP() + + number_of_cpu_processes = os.cpu_count() + output_dav_tmp = torch.zeros( + ( + n_contours, + y_canvas + 2 * r_gab_PIX, + x_canvas + 2 * r_gab_PIX, + ), + device="cpu", + dtype=torch.float, + ) + gabor = kernels[0].detach().cpu() + + # Umsort! + n_elements_total = 0 + for i_con in range(n_contours): + n_elements_total += len(index_x[i_con]) + sparse_matrix = torch.zeros( + (n_elements_total, 4), device="cpu", dtype=torch.int64 + ) + i_elements_total = 0 + for i_con in range(n_contours): + n_add = len(index_x[i_con]) + sparse_matrix[i_elements_total : i_elements_total + n_add, 0] = i_con + sparse_matrix[ + i_elements_total : i_elements_total + n_add, 1 + ] = index_srcchg[i_con] + sparse_matrix[i_elements_total : i_elements_total + n_add, 2] = index_y[ + i_con + ] + sparse_matrix[i_elements_total : i_elements_total + n_add, 3] = index_x[ + i_con + ] + i_elements_total += n_add + assert i_elements_total == n_elements_total, "UNBEHAGEN macht sich breit!" + + t_dav = torch.zeros((n_repeat + 2), device="cpu") + t_dav[0] = time.perf_counter() + for i_rep in range(n_repeat): + output_dav_tmp.fill_(0.0) + copyier.process( + sparse_matrix.data_ptr(), + int(sparse_matrix.shape[0]), + int(sparse_matrix.shape[1]), + gabor.data_ptr(), + int(gabor.shape[0]), + int(gabor.shape[1]), + int(gabor.shape[2]), + output_dav_tmp.data_ptr(), + int(output_dav_tmp.shape[0]), + int(output_dav_tmp.shape[1]), + int(output_dav_tmp.shape[2]), + int(number_of_cpu_processes), + ) + output_dav = output_dav_tmp[ + :, + d_gab_PIX - 1 : -(d_gab_PIX - 1), + d_gab_PIX - 1 : -(d_gab_PIX - 1), + ].clone() + t_dav[i_rep + 1] = time.perf_counter() + t_dav[-1] = time.perf_counter() + print("Generation by DAVID done!") + + if VERBOSE: # show last stimulus + if BENCH_CONVOLVE: + plt.subplot(2, 2, 1) + plt.imshow(output[-1, 0].detach().cpu(), cmap="gray", vmin=-1, vmax=+1) + plt.title("convolve") + if BENCH_GPU: + plt.subplot(2, 2, 2) + plt.imshow(output_gpu[-1].detach().cpu(), cmap="gray", vmin=-1, vmax=+1) + plt.title("gpu") + if BENCH_CPU: + plt.subplot(2, 2, 3) + plt.imshow(output_cpu[-1], cmap="gray", vmin=-1, vmax=+1) + plt.title("cpu") + if BENCH_DAVID: + plt.subplot(2, 2, 4) + plt.imshow(output_dav[-1], cmap="gray", vmin=-1, vmax=+1) + plt.title("david") + plt.show() + + dt_discretize = t_dis.diff() / n_contours + plt.plot(dt_discretize.detach().cpu()) + dt_convolve = t_con.diff() / n_contours + plt.plot(dt_convolve.detach().cpu()) + dt_gpu = t_rsg.diff() / n_contours + plt.plot(dt_gpu.detach().cpu()) + dt_cpu = t_rsc.diff() / n_contours + plt.plot(dt_cpu.detach().cpu()) + dt_david = t_dav.diff() / n_contours + plt.plot(dt_david.detach().cpu()) + + plt.legend(["discretize", "convolve", "gpu", "cpu", "david"]) + plt.show() + print( + f"Average discretize for 1k stims: {1000*dt_discretize[:-1].detach().cpu().mean()} secs." + ) + print( + f"Average convolve for 1k stims: {1000*dt_convolve[:-1].detach().cpu().mean()} secs." + ) + print(f"Average gpu for 1k stims: {1000*dt_gpu[:-1].detach().cpu().mean()} secs.") + print(f"Average cpu for 1k stims: {1000*dt_cpu[:-1].detach().cpu().mean()} secs.") + print( + f"Average david for 1k stims: {1000*dt_david[:-1].detach().cpu().mean()} secs." + ) + + if BENCH_GPU and BENCH_CPU and BENCH_DAVID: + df1 = (torch.abs(output_gpu[-1].detach().cpu() - output_cpu[-1])).mean() + df2 = (torch.abs(output_gpu[-1].detach().cpu() - output_dav[-1])).mean() + df3 = (torch.abs(output_dav[-1].cpu() - output_cpu[-1])).mean() + print(f"Differences: CPU-GPU:{df1}, GPU-David:{df2}, David-CPU:{df3}") + + # %% diff --git a/RenderStimuli/meanGabors.py b/RenderStimuli/meanGabors.py new file mode 100644 index 0000000..9f1c43e --- /dev/null +++ b/RenderStimuli/meanGabors.py @@ -0,0 +1,73 @@ +import torch +import scipy +import os +import matplotlib.pyplot as plt +import numpy as np +import glob +import logging + + +# this code calculates the mean number of Gabor patches inside a stimulus for each class + +logging.basicConfig(filename='AngularAvrg.txt', filemode='w', format='%(message)s', level=logging.INFO) + +avg_avg_size: list = [] +n_contours = 0 +x_range = [140, 940] +y_range = [140, 940] +for i in range(10): + path = f"/data_1/kk/StimulusGeneration/Alicorn/Angular/Ang0{i}0_n10000" + files = glob.glob(path + os.sep + "*.mat") + + n_files = len(files) + print(f"Going through {n_files} contour files...") + logging.info(f"Going through {n_files} contour files...") + varname=f"Table_intr_crn0{i}0" + varname_dist=f"Table_intr_crn0{i}0_dist" + for i_file in range(n_files): + # get path, basename, suffix... + full = files[i_file] + path, file = os.path.split(full) + base, suffix = os.path.splitext(file) + + # load file + print(full) + mat = scipy.io.loadmat(full) + if "dist" in full: + posori = mat[varname_dist] + else: + posori = mat[varname] + + sec_dim_sizes = [] #[posori[i][0].shape[1] for i in range(posori.shape[0])] + + for s in range(posori.shape[0]): + # Extract the entry + entry = posori[s][0] + + # Get the x and y coordinates + x = entry[1] + y = entry[2] + + # Find the indices of the coordinates that fall within the specified range + idx = np.where((x >= x_range[0]) & (x <= x_range[1]) & (y >= y_range[0]) & (y <= y_range[1]))[0] + + # Calculate the size of the second dimension while only considering the coordinates within the specified range + sec_dim_size = len(idx) + + # Append the size to the list + sec_dim_sizes.append(sec_dim_size) + + avg_size = np.mean(sec_dim_sizes) + print(f"Average 2nd dim of posori: {avg_size}") + logging.info(f"Average 2nd dim of posori: {avg_size}") + avg_avg_size.append(avg_size) + n_contours += posori.shape[0] + + print(f"...overall {n_contours} contours so far.") + logging.info(f"...overall {n_contours} contours so far.") + + +# calculate avg number Gabors over whole condition +overall = np.mean(avg_avg_size) +print(f"OVERALL average 2nd dim of posori: {overall}") +logging.info(f"OVERALL average 2nd dim of posori: {overall}") \ No newline at end of file diff --git a/RenderStimuli/render.py b/RenderStimuli/render.py new file mode 100644 index 0000000..d4df782 --- /dev/null +++ b/RenderStimuli/render.py @@ -0,0 +1,269 @@ +# %% + +import torch +import time +import scipy +import os +import matplotlib.pyplot as plt +import numpy as np +import contours +import glob + +USE_CEXT_FROM_DAVID = True +if USE_CEXT_FROM_DAVID: + # from CPPExtensions.PyTCopyCPU import TCopyCPU + from CPPExtensions.PyTCopyCPU import TCopyCPU as render_stimulus_CPP + + +def render_gaborfield(posori, params, verbose=False): + scale_factor = params["scale_factor"] + n_source = params["n_source"] + n_change = params["n_change"] + n_phase = params["n_phase"] + + # convert sizes to pixel units + lambda_PIX = params["lambda_gabor"] * scale_factor + sigma_PIX = params["sigma_gabor"] * scale_factor + r_gab_PIX = int(params["d_gabor"] * scale_factor / 2) + d_gab_PIX = r_gab_PIX * 2 + 1 + + # make filterbank + gabors = torch.zeros( + [n_phase, n_source, n_change, d_gab_PIX, d_gab_PIX], dtype=torch.float32 + ) + for i_phase in range(n_phase): + phase = (torch.pi * 2 * i_phase) / n_phase + gabors[i_phase], dirs_source, dirs_change = contours.gaborner_filterbank( + r_gab=r_gab_PIX, + n_source=n_source, + n_change=n_change, + lambdah=lambda_PIX, + sigma=sigma_PIX, + phase=phase, + normalize=params["normalize_gabor"], + torch_device="cpu", + ) + gabors = gabors.reshape([n_phase * n_source * n_change, d_gab_PIX, d_gab_PIX]) + + n_contours = posori.shape[0] + + # discretize ALL stimuli + if verbose: + print("Discretizing START!!!") + t_dis0 = time.perf_counter() + ( + index_srcchg, + index_x, + index_y, + x_canvas, + y_canvas, + ) = contours.discretize_stimuli( + posori=posori, + x_range=params["x_range"], + y_range=params["y_range"], + scale_factor=scale_factor, + r_gab_PIX=r_gab_PIX, + n_source=n_source, + n_change=n_change, + n_phase=n_phase, + torch_device="cpu", + ) + t_dis1 = time.perf_counter() + if verbose: + print(f"Discretizing END, took {t_dis1-t_dis0} seconds.!!!") + + if verbose: + print("Generation START!!!") + t0 = time.perf_counter() + + if not USE_CEXT_FROM_DAVID: + if verbose: + print(" (using NUMPY...)") + output = torch.zeros( + ( + n_contours, + y_canvas - d_gab_PIX + 1, + x_canvas - d_gab_PIX + 1, + ), + device="cpu", + dtype=torch.float32, + ) + kernels_cpu = gabors.detach().cpu() + for i_con in range(n_contours): + output[i_con] = contours.render_stimulus( + kernels=kernels_cpu, + index_element=index_srcchg[i_con], + index_y=index_y[i_con], + index_x=index_x[i_con], + y_canvas=y_canvas, + x_canvas=x_canvas, + torch_device="cpu", + ) + output = torch.clip(output, -1, +1) + + else: + if verbose: + print(" (using C++...)") + copyier = render_stimulus_CPP() + number_of_cpu_processes = os.cpu_count() + output_dav_tmp = torch.zeros( + ( + n_contours, + y_canvas + 2 * r_gab_PIX, + x_canvas + 2 * r_gab_PIX, + ), + device="cpu", + dtype=torch.float32, + ) + + # Umsort! + n_elements_total = 0 + for i_con in range(n_contours): + n_elements_total += len(index_x[i_con]) + sparse_matrix = torch.zeros( + (n_elements_total, 4), device="cpu", dtype=torch.int64 + ) + i_elements_total = 0 + for i_con in range(n_contours): + n_add = len(index_x[i_con]) + sparse_matrix[i_elements_total : i_elements_total + n_add, 0] = i_con + sparse_matrix[ + i_elements_total : i_elements_total + n_add, 1 + ] = index_srcchg[i_con] + sparse_matrix[i_elements_total : i_elements_total + n_add, 2] = index_y[ + i_con + ] + sparse_matrix[i_elements_total : i_elements_total + n_add, 3] = index_x[ + i_con + ] + i_elements_total += n_add + assert i_elements_total == n_elements_total, "UNBEHAGEN macht sich breit!" + + # output_dav_tmp.fill_(0.0) + copyier.process( + sparse_matrix.data_ptr(), + int(sparse_matrix.shape[0]), + int(sparse_matrix.shape[1]), + gabors.data_ptr(), + int(gabors.shape[0]), + int(gabors.shape[1]), + int(gabors.shape[2]), + output_dav_tmp.data_ptr(), + int(output_dav_tmp.shape[0]), + int(output_dav_tmp.shape[1]), + int(output_dav_tmp.shape[2]), + int(number_of_cpu_processes), + ) + output = torch.clip( + output_dav_tmp[ + :, + d_gab_PIX - 1 : -(d_gab_PIX - 1), + d_gab_PIX - 1 : -(d_gab_PIX - 1), + ], + -1, + +1, + ) + + t1 = time.perf_counter() + if verbose: + print(f"Generating END, took {t1-t0} seconds.!!!") + + if verbose: + print("Showing first and last stimulus generated...") + plt.imshow(output[0], cmap="gray", vmin=-1, vmax=+1) + plt.show() + plt.imshow(output[-1], cmap="gray", vmin=-1, vmax=+1) + plt.show() + print(f"Processed {n_contours} stimuli in {t1-t_dis0} seconds!") + + return output + + +def render_gaborfield_frommatfiles(files, params, varname, varname_dist, altpath=None, verbose=False): + n_total = 0 + n_files = len(files) + print(f"Going through {n_files} contour files...") + + for i_file in range(n_files): + # get path, basename, suffix... + full = files[i_file] + path, file = os.path.split(full) + base, suffix = os.path.splitext(file) + + # load file + mat = scipy.io.loadmat(full) + # posori = mat[varname] + if "dist" in full: + posori = mat[varname_dist] + else: + posori = mat[varname] + n_contours = posori.shape[0] + n_total += n_contours + print(f" ...file {file} contains {n_contours} contours.") + + # process... + gaborfield = render_gaborfield(posori, params=params, verbose=verbose) + + # save + if altpath: + savepath = altpath + else: + savepath = path + savefull = savepath + os.sep + base + "_RENDERED.npz" + print(f" ...saving under {savefull}...") + gaborfield = (torch.clip(gaborfield, -1, 1) * 127 + 128).type(torch.uint8) + np.savez_compressed(savefull, gaborfield=gaborfield) + + return n_total + + +if __name__ == "__main__": + TESTMODE = "files" # "files" or "posori" + + # cutout for stimuli, and gabor parameters + params = { + "x_range": [140, 940], + "y_range": [140, 940], + "scale_factor": 0.25, # scale to convert coordinates to pixel values + "d_gabor": 40, + "lambda_gabor": 16, + "sigma_gabor": 8, + "n_phase": 4, + "normalize_gabor": True, + # number of directions for dictionary + "n_source": 32, + "n_change": 32, + } + + if TESTMODE == "files": + num = int(9) + path = f"/data_1/kk/StimulusGeneration/Alicorn/Coignless/Base0{num}0_n100" + files = glob.glob(path + os.sep + "*.mat") + + t0 = time.perf_counter() + n_total = render_gaborfield_frommatfiles( + files=files, params=params, varname=f"Table_base_crn0{num}0", varname_dist=f"Table_base_crn0{num}0_dist", altpath="./Output100/Coignless" #intr crn base + ) + t1 = time.perf_counter() + dt = t1 - t0 + print( + f"Rendered {n_total} contours in {dt} secs, yielding {n_total/dt} contours/sec." + ) + + if TESTMODE == "posori": + print("Sample stimulus generation:") + print("===========================") + + # load contours, multiplex coordinates to simulate a larger set of contours + n_multiplex = 500 + mat = scipy.io.loadmat("z.mat") + posori = np.tile(mat["z"], (n_multiplex, 1)) + n_contours = posori.shape[0] + print(f"Processing {n_contours} contour stimuli") + + output = render_gaborfield(posori, params=params, verbose=True) + # output8 = (torch.clip(output, -1, 1) * 127 + 128).type(torch.uint8) + # np.savez_compressed("output8_compressed.npz", output8=output8) + + +# %% diff --git a/RenderStimuli/renderLess.py b/RenderStimuli/renderLess.py new file mode 100644 index 0000000..7fd8e49 --- /dev/null +++ b/RenderStimuli/renderLess.py @@ -0,0 +1,299 @@ +# %% + +import torch +import time +import scipy +import os +import matplotlib.pyplot as plt +import numpy as np +import contours +import glob + +USE_CEXT_FROM_DAVID = False +if USE_CEXT_FROM_DAVID: + # from CPPExtensions.PyTCopyCPU import TCopyCPU + from CPPExtensions.PyTCopyCPU import TCopyCPU as render_stimulus_CPP + + +def render_gaborfield(posori, params, verbose=False): + scale_factor = params["scale_factor"] + n_source = params["n_source"] + n_change = params["n_change"] + n_phase = params["n_phase"] + + # convert sizes to pixel units + lambda_PIX = params["lambda_gabor"] * scale_factor + sigma_PIX = params["sigma_gabor"] * scale_factor + r_gab_PIX = int(params["d_gabor"] * scale_factor / 2) + d_gab_PIX = r_gab_PIX * 2 + 1 + + # make filterbank + gabors = torch.zeros( + [n_phase, n_source, n_change, d_gab_PIX, d_gab_PIX], dtype=torch.float32 + ) + for i_phase in range(n_phase): + phase = (torch.pi * 2 * i_phase) / n_phase + gabors[i_phase], dirs_source, dirs_change = contours.gaborner_filterbank( + r_gab=r_gab_PIX, + n_source=n_source, + n_change=n_change, + lambdah=lambda_PIX, + sigma=sigma_PIX, + phase=phase, + normalize=params["normalize_gabor"], + torch_device="cpu", + ) + gabors = gabors.reshape([n_phase * n_source * n_change, d_gab_PIX, d_gab_PIX]) + + n_contours = posori.shape[0] + + # discretize ALL stimuli + if verbose: + print("Discretizing START!!!") + t_dis0 = time.perf_counter() + ( + index_srcchg, + index_x, + index_y, + x_canvas, + y_canvas, + ) = contours.discretize_stimuli( + posori=posori, + x_range=params["x_range"], + y_range=params["y_range"], + scale_factor=scale_factor, + r_gab_PIX=r_gab_PIX, + n_source=n_source, + n_change=n_change, + n_phase=n_phase, + torch_device="cpu", + ) + t_dis1 = time.perf_counter() + if verbose: + print(f"Discretizing END, took {t_dis1-t_dis0} seconds.!!!") + + if verbose: + print("Generation START!!!") + t0 = time.perf_counter() + + if not USE_CEXT_FROM_DAVID: + if verbose: + print(" (using NUMPY...)") + output = torch.zeros( + ( + n_contours, + y_canvas - d_gab_PIX + 1, + x_canvas - d_gab_PIX + 1, + ), + device="cpu", + dtype=torch.float32, + ) + kernels_cpu = gabors.detach().cpu() + for i_con in range(n_contours): + output[i_con] = contours.render_stimulus( + kernels=kernels_cpu, + index_element=index_srcchg[i_con], + index_y=index_y[i_con], + index_x=index_x[i_con], + y_canvas=y_canvas, + x_canvas=x_canvas, + torch_device="cpu", + ) + output = torch.clip(output, -1, +1) + + else: + if verbose: + print(" (using C++...)") + copyier = render_stimulus_CPP() + number_of_cpu_processes = os.cpu_count() + output_dav_tmp = torch.zeros( + ( + n_contours, + y_canvas + 2 * r_gab_PIX, + x_canvas + 2 * r_gab_PIX, + ), + device="cpu", + dtype=torch.float32, + ) + + # Umsort! + n_elements_total = 0 + for i_con in range(n_contours): + n_elements_total += len(index_x[i_con]) + sparse_matrix = torch.zeros( + (n_elements_total, 4), device="cpu", dtype=torch.int64 + ) + i_elements_total = 0 + for i_con in range(n_contours): + n_add = len(index_x[i_con]) + sparse_matrix[i_elements_total : i_elements_total + n_add, 0] = i_con + sparse_matrix[ + i_elements_total : i_elements_total + n_add, 1 + ] = index_srcchg[i_con] + sparse_matrix[i_elements_total : i_elements_total + n_add, 2] = index_y[ + i_con + ] + sparse_matrix[i_elements_total : i_elements_total + n_add, 3] = index_x[ + i_con + ] + i_elements_total += n_add + assert i_elements_total == n_elements_total, "UNBEHAGEN macht sich breit!" + + # output_dav_tmp.fill_(0.0) + copyier.process( + sparse_matrix.data_ptr(), + int(sparse_matrix.shape[0]), + int(sparse_matrix.shape[1]), + gabors.data_ptr(), + int(gabors.shape[0]), + int(gabors.shape[1]), + int(gabors.shape[2]), + output_dav_tmp.data_ptr(), + int(output_dav_tmp.shape[0]), + int(output_dav_tmp.shape[1]), + int(output_dav_tmp.shape[2]), + int(number_of_cpu_processes), # type: ignore + ) + output = torch.clip( + output_dav_tmp[ + :, + d_gab_PIX - 1 : -(d_gab_PIX - 1), + d_gab_PIX - 1 : -(d_gab_PIX - 1), + ], + -1, + +1, + ) + + t1 = time.perf_counter() + if verbose: + print(f"Generating END, took {t1-t0} seconds.!!!") + + if verbose: + print("Showing first and last stimulus generated...") + plt.imshow(output[0], cmap="gray", vmin=-1, vmax=+1) + plt.show() + plt.imshow(output[-1], cmap="gray", vmin=-1, vmax=+1) + plt.show() + print(f"Processed {n_contours} stimuli in {t1-t_dis0} seconds!") + + return output + + +def render_gaborfield_frommatfiles( + files, params, varname, num_make_background, altpath=None, verbose=False +): + n_total = 0 + n_files = len(files) + print(f"Going through {n_files} contour files...") + + # how many elements are in contour path + num_c_elems = 7 - num_make_background + print(f"Number of contour elements: {num_c_elems}") + + for i_file in range(n_files): + # get path, basename, suffix... + full = files[i_file] + path, file = os.path.split(full) + base, suffix = os.path.splitext(file) + + # ... if distractor file + if "dist" in full: + continue + + # load file + mat = scipy.io.loadmat(full) + posori = mat[varname] + n_contours = posori.shape[0] + n_total += n_contours + print(f" ...file {file} contains {n_contours} contours.") + + # adjust number of contour path elements + their angles + ang_range = [np.pi / 3, np.pi / 4, np.pi / 2] + ang_range = ang_range + [-x for x in ang_range] + for i in range(posori.shape[0]): + for b in range(num_make_background): + # change contour elem to back-elem + elem_idx = 6 - b + posori[i][0][0][elem_idx] = 0 + + # add random orientation + ang = np.random.choice(ang_range) + posori[i][0][3][elem_idx] += ang + + # process... + gaborfield = render_gaborfield(posori, params=params, verbose=verbose) + +# # plot some +# for i in range(5): +# plt.imshow(gaborfield[i], cmap="gray") +# plt.show(block=True) + + # save + if altpath: + savepath = altpath + else: + savepath = path + savefull = ( + savepath + os.sep + base + "_" + str(num_c_elems) + "conElems_RENDERED.npz" + ) + print(f" ...saving under {savefull}...") + gaborfield = (torch.clip(gaborfield, -1, 1) * 127 + 128).type(torch.uint8) + np.savez_compressed(savefull, gaborfield=gaborfield) + + return n_total + + +if __name__ == "__main__": + TESTMODE = "files" # "files" or "posori" + + # cutout for stimuli, and gabor parameters + params = { + "x_range": [140, 940], + "y_range": [140, 940], + "scale_factor": 0.25, # scale to convert coordinates to pixel values + "d_gabor": 40, + "lambda_gabor": 16, + "sigma_gabor": 8, + "n_phase": 4, + "normalize_gabor": True, + # number of directions for dictionary + "n_source": 32, + "n_change": 32, + } + + if TESTMODE == "files": + num_make_background: int = 5 + path = "/data_1/kk/StimulusGeneration/Alicorn/Coignless/Base000_n10000/" + files = glob.glob(path + os.sep + "*.mat") + + t0 = time.perf_counter() + n_total = render_gaborfield_frommatfiles( + files=files, + params=params, + varname="Table_base_crn000", + num_make_background=num_make_background, + altpath="/home/kk/Documents/Semester4/code/RenderStimuli/OutputLess/CoignLess/", + ) + t1 = time.perf_counter() + dt = t1 - t0 + print( + f"Rendered {n_total} contours in {dt} secs, yielding {n_total/dt} contours/sec." + ) + + if TESTMODE == "posori": + print("Sample stimulus generation:") + print("===========================") + + # load contours, multiplex coordinates to simulate a larger set of contours + n_multiplex = 500 + mat = scipy.io.loadmat("z.mat") + posori = np.tile(mat["z"], (n_multiplex, 1)) + n_contours = posori.shape[0] + print(f"Processing {n_contours} contour stimuli") + + output = render_gaborfield(posori, params=params, verbose=True) + # output8 = (torch.clip(output, -1, 1) * 127 + 128).type(torch.uint8) + # np.savez_compressed("output8_compressed.npz", output8=output8) + + +# %% diff --git a/RenderStimuli/test_readgaborfield.py b/RenderStimuli/test_readgaborfield.py new file mode 100644 index 0000000..6c3aa4c --- /dev/null +++ b/RenderStimuli/test_readgaborfield.py @@ -0,0 +1,24 @@ +import numpy as np +import time +import matplotlib.pyplot as plt +import os + +file = "/home/kk/Documents/Semester4/code/RenderStimuli/Output/Coignless/base_angle_090_b001_n10000_RENDERED.npz" +t0 = time.perf_counter() +with np.load(file) as data: + a = data["gaborfield"] +t1 = time.perf_counter() +print(t1 - t0) + +for i in range(5): + plt.imshow(a[i], cmap="gray", vmin=0, vmax=255) + plt.savefig( + os.path.join( + "./poster", + "classic_90_stimulus.pdf", + ), + dpi=300, + bbox_inches="tight", + ) + plt.show(block=True) + diff --git a/RenderStimuli/z.mat b/RenderStimuli/z.mat new file mode 100644 index 0000000000000000000000000000000000000000..c56a317eb37ccef70f894a02caad2ff9f21f4448 GIT binary patch literal 190776 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 OmCBjqOoxB+!T$p~zB}&# literal 0 HcmV?d00001