diff --git a/notebooks/Jungfrau/Jungfrau_Gain_Correct_and_Verify_NBC.ipynb b/notebooks/Jungfrau/Jungfrau_Gain_Correct_and_Verify_NBC.ipynb index 29b213b34e5a7e4d1842f512f8c532e6569548b8..b313a0b0c3fa20b31fcfacda366c67441d732163 100644 --- a/notebooks/Jungfrau/Jungfrau_Gain_Correct_and_Verify_NBC.ipynb +++ b/notebooks/Jungfrau/Jungfrau_Gain_Correct_and_Verify_NBC.ipynb @@ -216,13 +216,9 @@ "metadata": {}, "outputs": [], "source": [ - "strixel_transform = None\n", - "output_frame_shape = None\n", - "\n", "if strixel_sensor:\n", - " from cal_tools.jfalgs import strixel_transform, strixel_shape, strixel_double_pixels\n", - " output_frame_shape = strixel_shape()\n", - " Ydouble, Xdouble = strixel_double_pixels()\n", + " from cal_tools.jfstrixel import STRIXEL_SHAPE as strixel_frame_shape, double_pixel_indices, to_strixel\n", + " Ydouble, Xdouble = double_pixel_indices()\n", " print('Strixel sensor transformation enabled')" ] }, @@ -360,8 +356,8 @@ " g = gain[index]\n", " \n", " # Copy gain over first to keep it at the original 3 for low gain.\n", - " if strixel_transform is not None:\n", - " strixel_transform(g, out=gain_corr[index, ...])\n", + " if strixel_sensor:\n", + " to_strixel(g, out=gain_corr[index, ...])\n", " else:\n", " gain_corr[index, ...] = g\n", "\n", @@ -405,11 +401,10 @@ "\n", " msk = np.choose(g, np.moveaxis(mask_cell, -1, 0))\n", "\n", - " if strixel_transform is not None:\n", - " strixel_transform(d, out=data_corr[index, ...])\n", + " if strixel_sensor:\n", + " to_strixel(d, out=data_corr[index, ...])\n", " data_corr[index, :, Ydouble, Xdouble] /= strixel_double_norm\n", - "\n", - " strixel_transform(msk, out=mask_corr[index, ...])\n", + " to_strixel(msk, out=mask_corr[index, ...])\n", " else:\n", " data_corr[index, ...] = d\n", " mask_corr[index, ...] = msk" @@ -525,8 +520,8 @@ " offset_map, mask, gain_map = constants[local_karabo_da]\n", " \n", " # Determine total output shape.\n", - " if output_frame_shape is not None:\n", - " oshape = (*ishape[:-2], *output_frame_shape)\n", + " if strixel_sensor:\n", + " oshape = (*ishape[:-2], *strixel_frame_shape)\n", " else:\n", " oshape = ishape\n", "\n", diff --git a/setup.py b/setup.py index 0e39c2bf29538423d82f8ec6ccbe2c2e5c8ed96d..c9f0722aa2d75076598cfcf57cf4bed7c71236e3 100644 --- a/setup.py +++ b/setup.py @@ -24,12 +24,6 @@ ext_modules = [ '-ftree-vectorize', '-frename-registers'], extra_link_args=['-fopenmp'], ), - Extension( - "cal_tools.jfalgs", - ['src/cal_tools/jfalgs.pyx'], - extra_compile_args=['-O3', '-march=native', '-ftree-vectorize', - '-frename-registers'] - ), Extension( "cal_tools.gotthard2.gotthard2algs", ["src/cal_tools/gotthard2/gotthard2algs.pyx"], diff --git a/src/cal_tools/jfalgs.pyx b/src/cal_tools/jfalgs.pyx deleted file mode 100644 index fd1476918d8a8f165150abe1c5bbeb6175721913..0000000000000000000000000000000000000000 --- a/src/cal_tools/jfalgs.pyx +++ /dev/null @@ -1,93 +0,0 @@ - -from cython cimport boundscheck, wraparound, cdivision -from cython.view cimport contiguous - -import numpy as np - - -ctypedef fused jf_data_t: - unsigned short # raw pixel data - float # corrected pixel data - unsigned int # mask data - unsigned char # gain data - - -DEF STRIXEL_Y = 86 -DEF STRIXEL_X = 1024 * 3 + 18 - - -def strixel_shape(): - return STRIXEL_Y, STRIXEL_X - - -def strixel_double_pixels(): - """Build index arrays for double-size pixels. - - In raw data, the entire columns 255, 256, 511, 512, 767 and 768 - are double-size pixels. After strixelation, these end up in columns - 765-776, 1539-1550 and 2313-2324 on rows 0-85 or 0-83, with a set - of four columns with 86 rows followed by a set of 84 and 86 again. - - This function builds the index arrays after strixelation. - """ - - Ydouble = [] - Xdouble = [] - - for double_col in [765, 1539, 2313]: - for col in range(double_col, double_col+12): - for row in range(84 if ((col-double_col) // 4) == 1 else 86): - Ydouble.append(row) - Xdouble.append(col) - - return np.array(Ydouble), np.array(Xdouble) - - -@boundscheck(False) -@wraparound(False) -@cdivision(True) -def strixel_transform(jf_data_t[:, :, ::contiguous] data, - jf_data_t[:, :, ::contiguous] out = None): - """Reorder raw data to physical strixel sensor layout. """ - - if data.shape[1] < 256 or data.shape[2] < 256: - raise ValueError('Pixel shape of data may not be below (256, 256)') - - if out is None: - import numpy as np - out = np.zeros((data.shape[0], STRIXEL_Y, STRIXEL_X), dtype=np.float32) - elif data.shape[0] > out.shape[0]: - raise ValueError('Cell shape of data exceeds out') - elif out.shape[1] < STRIXEL_Y or out.shape[2] < STRIXEL_X: - raise ValueError(f'Pixel shape of out may not be below ' - f'({STRIXEL_Y}, {STRIXEL_X})') - - cdef int cell, yin, xin, xout, yout, igap - - for cell in range(data.shape[0]): - # Normal pixels. - for yin in range(256): - yout = yin // 3 - - for xin in range(1024) : - xout = 774 * (xin // 256) + 3 * (xin % 256) + yin % 3 - out[cell, yout, xout] = data[cell, yin, xin] - - # Gap pixels. - for yin in range(256): - yout = 2 * (yin // 6) - - for igap in range(3) : - # Left side of the gap. - xin = igap * 256 + 255 - xout = igap * 774 + 765 + yin % 6 - out[cell, yout, xout] = data[cell, yin, xin] - out[cell, yout+1, xout] = data[cell, yin, xin] - - # Right side of the gap. - xin = igap * 256 + 255 + 1 - xout = igap * 774 + 765 + 11 - yin % 6 - out[cell, yout, xout] = data[cell, yin, xin] - out[cell, yout+1, xout] = data[cell, yin, xin] - - return out diff --git a/src/cal_tools/jfstrixel.py b/src/cal_tools/jfstrixel.py new file mode 100644 index 0000000000000000000000000000000000000000..189b036f6cddf4aaa8ba802e908a999afb6cdfe3 --- /dev/null +++ b/src/cal_tools/jfstrixel.py @@ -0,0 +1,161 @@ + +import numpy as np + + +REGULAR_SHAPE = (512, 1024) +STRIXEL_SHAPE = (86, 3090) + + +def _normal_indices(): + """Build normal size pixel indices.""" + + # Normal pixels + yin = np.arange(256) + xin = np.arange(1024) + + Yin, Xin = np.meshgrid(yin, xin) + Yout, Xout = np.meshgrid(yin // 3, (xin // 256 * 774) + (xin % 256) * 3) + Xout += (yin % 3).astype(int)[None, :] + + return Yout, Xout, Yin, Xin + + +def _gap_indices(in_gap_offset=0, out_gap_offset=0, + xout_factor=+1, yout_offset=0): + """Build one half of double size gap pixel indices.""" + + igap = np.arange(3) + yin = np.arange(256) + + Yin, Xin = np.meshgrid(yin, igap * 256 + 255 + in_gap_offset) + Yout, Xout = np.meshgrid(yin // 6 * 2, igap * 774 + 765 + out_gap_offset) + Xout += xout_factor * (yin % 6).astype(int)[None, :] + Yout += yout_offset + + return Yout, Xout, Yin, Xin + + +def transformation_indices2d(): + """Build 2D strixel transformation index arrays.""" + + # Each of this index sets contains four 2D index arrays + # Yout, Xout, Yin, Xin from different parts constituting the full + # strixel frame. They are each concatenated across these parts into + # four final index arrays to be used for translating between the + # regular frame and the strixel frame. + index_sets = [ + _normal_indices(), + + # Left gap + _gap_indices(0, 0, +1, 0), _gap_indices(0, 0, +1, 1), + + # Right gap + _gap_indices(1, 11, -1, 0), _gap_indices(1, 11, -1, 1) + ] + + # Yout, Xout, Yin, Xin + # Casting to int64 improves indexing performance by up to 30%. + return [np.concatenate(index_set).astype(np.int64) + for index_set in zip(*index_sets)] + + +def transformation_indices1d(): + """Build 1D strixel transformation index arrays. + + Internally this function reduces the 2D index arrays to a single + dimension to operate on raveled data arrays. This improves the + transformation performance substantially by up to 3x. + """ + + Yout, Xout, Yin, Xin = transformation_indices2d() + + regular_pixel_idx = np.arange(np.prod(REGULAR_SHAPE), dtype=np.uint32) \ + .reshape(REGULAR_SHAPE) + strixel_pixel_idx = np.empty(STRIXEL_SHAPE, dtype=np.int64) + strixel_pixel_idx.fill(-1) + strixel_pixel_idx[Yout, Xout] = regular_pixel_idx[Yin, Xin] + + Iout = np.where(strixel_pixel_idx.ravel() != -1)[0].astype(np.int64) + Iin = strixel_pixel_idx.ravel()[Iout].astype(np.int64) + + return Iout, Iin + + +def double_pixel_indices(): + """Build index arrays for double-size pixels. + + In raw data, the entire columns 255, 256, 511, 512, 767 and 768 + are double-size pixels. After strixelation, these end up in columns + 765-776, 1539-1550 and 2313-2324 on rows 0-85 or 0-83, with a set + of four columns with 86 rows followed by a set of 84 and 86 again. + + This function builds the index arrays for double pixels after + strixelation. + + Returns: + (ndarray, ndarray) 2D index arrays for double pixel Y and X. + """ + + Ydouble = [] + Xdouble = [] + + for double_col in [765, 1539, 2313]: + for col in range(double_col, double_col+12): + for row in range(84 if ((col-double_col) // 4) == 1 else 86): + Ydouble.append(row) + Xdouble.append(col) + + return np.array(Ydouble), np.array(Xdouble) + + +def to_strixel(data, out=None): + """Transform from regular to strixel geometry. + + Only the last two axes are considered for transformation, input data + may have any number of additional axes in front. + + Args: + data (array_like): Data in regular geometry. + out (array_like, optional): Buffer for transformed output, a new + one is allocated if omitted. Must match all non-frame axes + of input data and able to hold strixel frame. + + Returns: + (array_like) Data in strixel geometry. + """ + + if out is None: + out = np.zeros((*data.shape[:-2], *STRIXEL_SHAPE), dtype=data.dtype) + + out.reshape(*out.shape[:-2], -1)[..., Iout] = data.reshape( + *data.shape[:-2], -1)[..., Iin] + + return out + + +def from_strixel(data, out=None): + """Transform from strixel to regular geometry. + + Only the last two axes are considered for transformation, input data + may have any number of additional axes in front. + + Args: + data (array_like): Data in strixel geometry. + out (array_like, optional): Buffer for transformed output, a new + one is allocated if omitted. Must match all non-frame axes + of input data and able to hold regular frame. + + Returns: + (array_like): Data in regular geometry. + """ + + if out is None: + out = np.zeros((*data.shape[:-2], *REGULAR_SHAPE), dtype=data.dtype) + + out.reshape(*out.shape[:-2], -1)[..., Iin] = data.reshape( + *data.shape[:-2], -1)[..., Iout] + + return out + + +Iout, Iin = transformation_indices1d()