diff --git a/nexus_constructor/geometry/pixel_data.py b/nexus_constructor/geometry/pixel_data.py index 5dc11c428..06d851039 100644 --- a/nexus_constructor/geometry/pixel_data.py +++ b/nexus_constructor/geometry/pixel_data.py @@ -42,6 +42,9 @@ class PixelGrid(PixelData): The corner that counting starts in, and whether counting should first happen along rows or columns can be set with the 'count_direction' and 'initial_count_corner' attributes, which respectively take 'CountDirection' and 'Corner' Enum values. + If there are gaps between pixels at regular intervals, the number of pixels between two gaps is configured + via the 'gap_every_rows' / 'gap_every_columns' attributes, and the size of the gap is set with 'row_gap_height' + / 'column_gap_width'. """ rows = attr.ib(default=1, type=int) @@ -51,6 +54,10 @@ class PixelGrid(PixelData): first_id = attr.ib(default=0, type=int) count_direction = attr.ib(default=CountDirection.ROW, type=CountDirection) initial_count_corner = attr.ib(default=Corner.BOTTOM_LEFT, type=Corner) + gap_every_rows = attr.ib(default=0, type=int) + gap_every_columns = attr.ib(default=0, type=int) + row_gap_height = attr.ib(default=0, type=float) + column_gap_width = attr.ib(default=0, type=float) @attr.s diff --git a/nexus_constructor/geometry/pixel_data_utils.py b/nexus_constructor/geometry/pixel_data_utils.py index 3757577f6..54e4d0fc9 100644 --- a/nexus_constructor/geometry/pixel_data_utils.py +++ b/nexus_constructor/geometry/pixel_data_utils.py @@ -57,10 +57,22 @@ def get_x_offsets_from_pixel_grid(grid: PixelGrid) -> Union[np.ndarray, float]: Returns an array of x-offsets. Each value in the array is the x position of a pixel instance defined in the PixelGrid. """ - half_distance = grid.col_width / 2 - end = half_distance * (grid.columns - 1) - - offsets = np.linspace(start=-end, stop=end, num=grid.columns) + if grid.gap_every_columns > 0: + total_width = ( + grid.columns * grid.col_width + + ((grid.columns - 1) // grid.gap_every_columns) * grid.column_gap_width + ) + else: + total_width = grid.columns * grid.col_width + + start = -total_width / 2 + grid.col_width / 2 + offsets = np.zeros(grid.columns) + gap_counter = 0 + for i in range(grid.columns): + offsets[i] = start + i * grid.col_width + gap_counter * grid.column_gap_width + # If there is a gap, increment the gap counter + if grid.gap_every_columns > 0 and (i + 1) % grid.gap_every_columns == 0: + gap_counter += 1 return np.tile(offsets, (grid.rows, 1)) @@ -69,10 +81,22 @@ def get_y_offsets_from_pixel_grid(grid: PixelGrid) -> Union[np.ndarray, float]: Returns an array of y-offsets. Each value in the array is the y position of a pixel instance defined in the PixelGrid. """ - half_distance = grid.row_height / 2 - end = half_distance * (grid.rows - 1) - - offsets = np.linspace(start=end, stop=-end, num=grid.rows) + if grid.gap_every_rows > 0: + total_height = ( + grid.rows * grid.row_height + + ((grid.rows - 1) // grid.gap_every_rows) * grid.row_gap_height + ) + else: + total_height = grid.rows * grid.row_height + + start = total_height / 2 - grid.row_height / 2 + offsets = np.zeros(grid.rows) + gap_counter = 0 + for i in range(grid.rows): + offsets[i] = start - i * grid.row_height - gap_counter * grid.row_gap_height + # If there is a gap, increment the gap counter + if grid.gap_every_rows > 0 and (i + 1) % grid.gap_every_rows == 0: + gap_counter += 1 return np.tile(offsets, (grid.columns, 1)).transpose() diff --git a/nexus_constructor/model/component.py b/nexus_constructor/model/component.py index 309f4e520..b225398f6 100644 --- a/nexus_constructor/model/component.py +++ b/nexus_constructor/model/component.py @@ -466,6 +466,20 @@ def record_pixel_grid(self, pixel_grid: PixelGrid, unit: str = ""): np.array(get_detector_ids_from_pixel_grid(pixel_grid)), ValueTypes.INT, ) + if pixel_grid.gap_every_rows and pixel_grid.row_gap_height: + self.attributes.set_attribute_value( + "pixelgrid_gap_every_rows", pixel_grid.gap_every_rows + ) + self.attributes.set_attribute_value( + "pixelgrid_row_gap_height", pixel_grid.row_gap_height + ) + if pixel_grid.gap_every_columns and pixel_grid.column_gap_width: + self.attributes.set_attribute_value( + "pixelgrid_gap_every_columns", pixel_grid.gap_every_columns + ) + self.attributes.set_attribute_value( + "pixelgrid_column_gap_width", pixel_grid.column_gap_width + ) def record_pixel_mapping(self, pixel_mapping: PixelMapping): """ diff --git a/nexus_constructor/pixel_options.py b/nexus_constructor/pixel_options.py index f6a63062e..7cb534ac1 100644 --- a/nexus_constructor/pixel_options.py +++ b/nexus_constructor/pixel_options.py @@ -154,6 +154,18 @@ def _fill_single_pixel_fields(self, component_to_edit: Component): self.start_counting_combo_box.setCurrentText(start_counting_text) self.count_first_combo_box.setCurrentText(count_along_text) + # Set pixel gap information from attributes + ( + gap_every_rows, + gap_every_columns, + row_gap_height, + column_gap_width, + ) = self._get_pixel_gaps_information(component_to_edit) + self.gap_every_rows_spin_box.setValue(gap_every_rows) + self.gap_every_columns_spin_box.setValue(gap_every_columns) + self.row_gap_height_spin_box.setValue(row_gap_height) + self.column_gap_width_spin_box.setValue(column_gap_width) + else: # If the pixel offset information represents a single pixel pass @@ -249,6 +261,40 @@ def _get_detector_number_information( return first_id, start_counting_text, count_along_text + @staticmethod + def _get_pixel_gaps_information( + component_to_edit: Component, + ) -> Tuple[Optional[int], Optional[int], Optional[float], Optional[float]]: + """ + Loads pixel gap information from NXdetector custom attributes. + """ + gap_every_rows = ( + component_to_edit.attributes.get_attribute_value("pixelgrid_gap_every_rows") + or 0 + ) + gap_every_columns = ( + component_to_edit.attributes.get_attribute_value( + "pixelgrid_gap_every_columns" + ) + or 0 + ) + row_gap_height = ( + component_to_edit.attributes.get_attribute_value("pixelgrid_row_gap_height") + or 0 + ) + column_gap_width = ( + component_to_edit.attributes.get_attribute_value( + "pixelgrid_column_gap_width" + ) + or 0 + ) + return ( + int(gap_every_rows), + int(gap_every_columns), + float(row_gap_height), + float(column_gap_width), + ) + def _fill_entire_shape_fields(self, component_to_edit: Component): """ Fill the "entire shape" fields a component that is being edited and contains pixel data. @@ -492,6 +538,10 @@ def generate_pixel_data(self) -> PixelData: initial_count_corner=INITIAL_COUNT_CORNER[ self.start_counting_combo_box.currentText() ], + gap_every_rows=self.gap_every_rows_spin_box.value(), + gap_every_columns=self.gap_every_columns_spin_box.value(), + row_gap_height=self.row_gap_height_spin_box.value(), + column_gap_width=self.column_gap_width_spin_box.value(), ) if self.entire_shape_radio_button.isChecked(): diff --git a/tests/geometry/test_pixel_data_to_nexus_utils.py b/tests/geometry/test_pixel_data_to_nexus_utils.py index 797d5103d..d0de68c9c 100644 --- a/tests/geometry/test_pixel_data_to_nexus_utils.py +++ b/tests/geometry/test_pixel_data_to_nexus_utils.py @@ -34,16 +34,47 @@ ROW_COL_VALS = [4, 7] +@pytest.fixture(params=[0, 1, 4]) +def pixel_grid_gap_every_rows(request): + return request.param + + +@pytest.fixture(params=[0, 1, 3]) +def pixel_grid_gap_every_columns(request): + return request.param + + @pytest.fixture(scope="function") -def pixel_grid(): +def pixel_grid(pixel_grid_gap_every_rows, pixel_grid_gap_every_columns): return PixelGrid( rows=5, + columns=7, + row_height=0.873, + col_width=2.0 / 3, + first_id=0, + count_direction=CountDirection.ROW, + initial_count_corner=Corner.BOTTOM_LEFT, + gap_every_rows=pixel_grid_gap_every_rows, + gap_every_columns=pixel_grid_gap_every_columns, + row_gap_height=0.1, + column_gap_width=0.7, + ) + + +@pytest.fixture(scope="function") +def pixel_grid_without_gaps(): + return PixelGrid( + rows=3, columns=4, row_height=0.873, col_width=2.0 / 3, first_id=0, count_direction=CountDirection.ROW, initial_count_corner=Corner.BOTTOM_LEFT, + gap_every_rows=0, + gap_every_columns=3, + row_gap_height=0.1, + column_gap_width=0, ) @@ -89,11 +120,63 @@ def test_GIVEN_single_id_WHEN_calling_detector_number_THEN_list_is_not_returned( assert get_detector_number_from_pixel_mapping(pixel_mapping) == [pixel_id] +def test_GIVEN_simple_pixel_grid_WHEN_calling_pixel_grid_offsets_THEN_correct_offset_lists_are_returned(): + pixel_grid = PixelGrid( + rows=4, + columns=4, + row_height=0.9, + col_width=2.0 / 3, + first_id=0, + count_direction=CountDirection.ROW, + initial_count_corner=Corner.BOTTOM_LEFT, + gap_every_rows=2, + gap_every_columns=1, + row_gap_height=0.25, + column_gap_width=0.7, + ) + + expected_x_offsets = [ + [ + (pixel_grid.column_gap_width + pixel_grid.col_width) * -1.5, + (pixel_grid.column_gap_width + pixel_grid.col_width) * -0.5, + (pixel_grid.column_gap_width + pixel_grid.col_width) * 0.5, + (pixel_grid.column_gap_width + pixel_grid.col_width) * 1.5, + ] + for _ in range(pixel_grid.rows) + ] + expected_y_offsets = [ + [ + (1.5 * pixel_grid.row_height + 0.5 * pixel_grid.row_gap_height) + for _ in range(pixel_grid.columns) + ], + [ + (0.5 * pixel_grid.row_height + 0.5 * pixel_grid.row_gap_height) + for _ in range(pixel_grid.columns) + ], + [ + (-0.5 * pixel_grid.row_height + -0.5 * pixel_grid.row_gap_height) + for _ in range(pixel_grid.columns) + ], + [ + (-1.5 * pixel_grid.row_height + -0.5 * pixel_grid.row_gap_height) + for _ in range(pixel_grid.columns) + ], + ] + + assert np.allclose( + np.array(expected_x_offsets), get_x_offsets_from_pixel_grid(pixel_grid) + ) + assert np.allclose( + np.array(expected_y_offsets), get_y_offsets_from_pixel_grid(pixel_grid) + ) + + @pytest.mark.parametrize("rows", ROW_COL_VALS) @pytest.mark.parametrize("columns", ROW_COL_VALS) -def test_GIVEN_pixel_grid_WHEN_calling_pixel_grid_x_offsets_THEN_correct_x_offset_list_is_returned( - pixel_grid, rows, columns +def test_GIVEN_pixel_grid_without_gaps_WHEN_calling_pixel_grid_x_offsets_THEN_correct_x_offset_list_is_returned( + pixel_grid_without_gaps, rows, columns ): + pixel_grid = pixel_grid_without_gaps pixel_grid.columns = columns pixel_grid.rows = rows @@ -110,11 +193,50 @@ def test_GIVEN_pixel_grid_WHEN_calling_pixel_grid_x_offsets_THEN_correct_x_offse @pytest.mark.parametrize("rows", ROW_COL_VALS) @pytest.mark.parametrize("columns", ROW_COL_VALS) -def test_GIVEN_pixel_grid_WHEN_calling_pixel_grid_y_offsets_THEN_correct_y_offset_list_is_returned( +def test_GIVEN_pixel_grid_WHEN_calling_pixel_grid_x_offsets_THEN_correct_x_offset_list_is_returned( pixel_grid, rows, columns ): pixel_grid.columns = columns pixel_grid.rows = rows + number_of_column_gaps = ( + (pixel_grid.columns - 1) // pixel_grid.gap_every_columns + if pixel_grid.gap_every_columns + else 0 + ) + + offset_offset = ((pixel_grid.columns - 1) * pixel_grid.col_width / 2) + ( + pixel_grid.column_gap_width * number_of_column_gaps / 2 + ) + expected_x_offsets = [ + [0 for _ in range(pixel_grid.columns)] for _ in range(pixel_grid.rows) + ] + gap_counter = 0 + for i in range(pixel_grid.columns): + for j in range(pixel_grid.rows): + expected_x_offsets[j][i] = ( + (i * pixel_grid.col_width) + + gap_counter * pixel_grid.column_gap_width + - offset_offset + ) + if ( + pixel_grid.gap_every_columns > 0 + and (i + 1) % pixel_grid.gap_every_columns == 0 + ): + gap_counter += 1 + + assert np.allclose( + np.array(expected_x_offsets), get_x_offsets_from_pixel_grid(pixel_grid) + ) + + +@pytest.mark.parametrize("rows", ROW_COL_VALS) +@pytest.mark.parametrize("columns", ROW_COL_VALS) +def test_GIVEN_pixel_grid_without_gaps_WHEN_calling_pixel_grid_y_offsets_THEN_correct_y_offset_list_is_returned( + pixel_grid_without_gaps, rows, columns +): + pixel_grid = pixel_grid_without_gaps + pixel_grid.columns = columns + pixel_grid.rows = rows offset_offset = (pixel_grid.rows - 1) * pixel_grid.row_height / 2 expected_y_offsets = [ @@ -127,6 +249,41 @@ def test_GIVEN_pixel_grid_WHEN_calling_pixel_grid_y_offsets_THEN_correct_y_offse ) +@pytest.mark.parametrize("rows", ROW_COL_VALS) +@pytest.mark.parametrize("columns", ROW_COL_VALS) +def test_GIVEN_pixel_grid_WHEN_calling_pixel_grid_y_offsets_THEN_correct_y_offset_list_is_returned( + pixel_grid, rows, columns +): + pixel_grid.columns = columns + pixel_grid.rows = rows + number_of_row_gaps = ( + (pixel_grid.rows - 1) // pixel_grid.gap_every_rows + if pixel_grid.gap_every_rows + else 0 + ) + + offset_offset = ((pixel_grid.rows - 1) * pixel_grid.row_height / 2) + ( + pixel_grid.row_gap_height * number_of_row_gaps / 2 + ) + expected_y_offsets = [ + [0 for _ in range(pixel_grid.columns)] for _ in range(pixel_grid.rows) + ] + gap_counter = 0 + for i in range(pixel_grid.rows): + for j in range(pixel_grid.columns): + expected_y_offsets[i][j] = ( + offset_offset + - (i * pixel_grid.row_height) + - gap_counter * pixel_grid.row_gap_height + ) + if pixel_grid.gap_every_rows > 0 and (i + 1) % pixel_grid.gap_every_rows == 0: + gap_counter += 1 + + assert np.allclose( + np.array(expected_y_offsets), get_y_offsets_from_pixel_grid(pixel_grid) + ) + + @pytest.mark.parametrize("rows", ROW_COL_VALS) @pytest.mark.parametrize("columns", ROW_COL_VALS) def test_GIVEN_pixel_grid_WHEN_calling_pixel_grid_z_offsets_THEN_z_offsets_are_all_zero( diff --git a/ui/pixel_options.py b/ui/pixel_options.py index 35a2ddaf2..f6a65770f 100644 --- a/ui/pixel_options.py +++ b/ui/pixel_options.py @@ -108,6 +108,39 @@ def _set_up_pixel_grid_group_box(self): self.column_width_label = QtWidgets.QLabel(self.pixel_grid_group_box) self.column_width_label.setObjectName("columnWidthLabel") self.pixel_grid_group_box_layout.addWidget(self.column_width_label, 1, 2, 1, 1) + # gaps + self.gap_every_rows_label = QtWidgets.QLabel(self.pixel_grid_group_box) + self.gap_every_rows_label.setObjectName("gapEveryRowsLabel") + self.pixel_grid_group_box_layout.addWidget( + self.gap_every_rows_label, 2, 2, 1, 1 + ) + self.gap_every_rows_tail_label = QtWidgets.QLabel(self.pixel_grid_group_box) + self.gap_every_rows_tail_label.setObjectName("gapEveryRowsTailLabel") + self.pixel_grid_group_box_layout.addWidget( + self.gap_every_rows_tail_label, 2, 4, 1, 1 + ) + self.row_gap_height_label = QtWidgets.QLabel(self.pixel_grid_group_box) + self.row_gap_height_label.setObjectName("rowGapHeight") + self.pixel_grid_group_box_layout.addWidget( + self.row_gap_height_label, 3, 2, 1, 1 + ) + # + self.gap_every_columns_label = QtWidgets.QLabel(self.pixel_grid_group_box) + self.gap_every_columns_label.setObjectName("gapEveryColumnsLabel") + self.pixel_grid_group_box_layout.addWidget( + self.gap_every_columns_label, 4, 2, 1, 1 + ) + self.gap_every_columns_tail_label = QtWidgets.QLabel(self.pixel_grid_group_box) + self.gap_every_columns_tail_label.setObjectName("gapEveryColumnsTailLabel") + self.pixel_grid_group_box_layout.addWidget( + self.gap_every_columns_tail_label, 4, 4, 1, 1 + ) + self.column_gap_width_label = QtWidgets.QLabel(self.pixel_grid_group_box) + self.column_gap_width_label.setObjectName("columnGapWidth") + self.pixel_grid_group_box_layout.addWidget( + self.column_gap_width_label, 5, 2, 1, 1 + ) + self.first_id_label = QtWidgets.QLabel(self.pixel_grid_group_box) self.first_id_label.setObjectName("firstIDLabel") self.pixel_grid_group_box_layout.addWidget(self.first_id_label, 2, 0, 1, 1) @@ -132,7 +165,7 @@ def _set_up_pixel_grid_group_box(self): self.start_counting_combo_box.addItem("") self.start_counting_combo_box.addItem("") self.pixel_grid_group_box_layout.addWidget( - self.start_counting_combo_box, 3, 2, 1, 2 + self.start_counting_combo_box, 3, 1, 1, 1 ) self.count_first_combo_box = QtWidgets.QComboBox(self.pixel_grid_group_box) @@ -141,7 +174,7 @@ def _set_up_pixel_grid_group_box(self): self.count_first_combo_box.setInsertPolicy(QtWidgets.QComboBox.InsertAtCurrent) self.count_first_combo_box.setObjectName("countFirstComboBox") self.pixel_grid_group_box_layout.addWidget( - self.count_first_combo_box, 4, 2, 1, 2 + self.count_first_combo_box, 4, 1, 1, 1 ) self.row_count_spin_box = QtWidgets.QSpinBox(self.pixel_grid_group_box) @@ -179,6 +212,48 @@ def _set_up_pixel_grid_group_box(self): self.pixel_grid_group_box_layout.addWidget( self.column_width_spin_box, 1, 3, 1, 1 ) + # gaps + self.gap_every_rows_spin_box = QtWidgets.QSpinBox(self.pixel_grid_group_box) + self.gap_every_rows_spin_box.setMinimum(0) + self.gap_every_rows_spin_box.setSingleStep(1) + self.gap_every_rows_spin_box.setProperty("value", 0) + self.gap_every_rows_spin_box.setObjectName("gapEveryRowsSpinBox") + self.gap_every_rows_spin_box.setMaximum(10000000) + self.pixel_grid_group_box_layout.addWidget( + self.gap_every_rows_spin_box, 2, 3, 1, 1 + ) + self.row_gap_height_spin_box = QtWidgets.QDoubleSpinBox( + self.pixel_grid_group_box + ) + self.row_gap_height_spin_box.setMinimum(0) + self.row_gap_height_spin_box.setSingleStep(0.01) + self.row_gap_height_spin_box.setProperty("value", 0) + self.row_gap_height_spin_box.setObjectName("rowGapHeightSpinBox") + self.row_gap_height_spin_box.setMaximum(1000000) + self.pixel_grid_group_box_layout.addWidget( + self.row_gap_height_spin_box, 3, 3, 1, 1 + ) + self.gap_every_columns_spin_box = QtWidgets.QSpinBox(self.pixel_grid_group_box) + self.gap_every_columns_spin_box.setMinimum(0) + self.gap_every_columns_spin_box.setSingleStep(1) + self.gap_every_columns_spin_box.setProperty("value", 0) + self.gap_every_columns_spin_box.setObjectName("gapEveryColumnsSpinBox") + self.gap_every_columns_spin_box.setMaximum(10000000) + self.pixel_grid_group_box_layout.addWidget( + self.gap_every_columns_spin_box, 4, 3, 1, 1 + ) + self.column_gap_width_spin_box = QtWidgets.QDoubleSpinBox( + self.pixel_grid_group_box + ) + self.column_gap_width_spin_box.setMinimum(0) + self.column_gap_width_spin_box.setSingleStep(0.01) + self.column_gap_width_spin_box.setProperty("value", 0) + self.column_gap_width_spin_box.setObjectName("columnGapWidthSpinBox") + self.column_gap_width_spin_box.setMaximum(1000000) + self.pixel_grid_group_box_layout.addWidget( + self.column_gap_width_spin_box, 5, 3, 1, 1 + ) + # self.pixel_grid_page_layout.addWidget(self.pixel_grid_group_box) def retranslateUi(self, PixelOptionsWidget): @@ -240,7 +315,7 @@ def retranslateUi(self, PixelOptionsWidget): ) ) self.row_label.setText( - QtWidgets.QApplication.translate("PixelOptionsWidget", "Row:", None, -1) + QtWidgets.QApplication.translate("PixelOptionsWidget", "Rows:", None, -1) ) self.row_height_label.setText( QtWidgets.QApplication.translate( @@ -252,6 +327,32 @@ def retranslateUi(self, PixelOptionsWidget): "PixelOptionsWidget", "Column width:", None, -1 ) ) + self.gap_every_rows_label.setText( + QtWidgets.QApplication.translate( + "PixelOptionsWidget", "Gap every", None, -1 + ) + ) + self.gap_every_rows_tail_label.setText( + QtWidgets.QApplication.translate("PixelOptionsWidget", "rows", None, -1) + ) + self.row_gap_height_label.setText( + QtWidgets.QApplication.translate( + "PixelOptionsWidget", "Row gap height:", None, -1 + ) + ) + self.gap_every_columns_label.setText( + QtWidgets.QApplication.translate( + "PixelOptionsWidget", "Gap every", None, -1 + ) + ) + self.gap_every_columns_tail_label.setText( + QtWidgets.QApplication.translate("PixelOptionsWidget", "columns", None, -1) + ) + self.column_gap_width_label.setText( + QtWidgets.QApplication.translate( + "PixelOptionsWidget", "Column gap width:", None, -1 + ) + ) self.first_id_label.setText( QtWidgets.QApplication.translate( "PixelOptionsWidget", "First ID:", None, -1 diff --git a/ui_tests/test_ui_pixel_options_widget.py b/ui_tests/test_ui_pixel_options_widget.py index dc15eab25..92fe8337b 100644 --- a/ui_tests/test_ui_pixel_options_widget.py +++ b/ui_tests/test_ui_pixel_options_widget.py @@ -49,7 +49,6 @@ def pixel_options(qtbot, template): @pytest.fixture(scope="function") def pixel_grid(): - pixel_grid = PixelGrid() pixel_grid.rows = 5 pixel_grid.columns = 4 @@ -58,6 +57,10 @@ def pixel_grid(): pixel_grid.first_id = 2 pixel_grid.count_direction = CountDirection.ROW pixel_grid.initial_count_corner = Corner.BOTTOM_LEFT + pixel_grid.gap_every_rows = 2 + pixel_grid.gap_every_columns = 3 + pixel_grid.row_gap_height = 0.1 + pixel_grid.column_gap_width = 3.6 return pixel_grid @@ -129,7 +132,6 @@ def component(): @pytest.fixture(scope="function") def off_geometry_file(pixel_mapping_with_six_pixels): - off_string = StringIO("".join(VALID_CUBE_OFF_FILE)) off_geometry = load_geometry_from_file_object(off_string, ".off", "m") return off_geometry @@ -210,7 +212,6 @@ def test_UI_GIVEN_user_selects_entire_shape_WHEN_choosing_pixel_layout_THEN_pixe def test_UI_GIVEN_user_selects_no_pixels_WHEN_changing_pixel_layout_THEN_pixel_options_stack_becomes_invisible( qtbot, template, pixel_options ): - # Press the entire shape button under pixel layout systematic_button_press(qtbot, template, pixel_options.no_pixels_button) @@ -221,7 +222,6 @@ def test_UI_GIVEN_user_selects_no_pixels_WHEN_changing_pixel_layout_THEN_pixel_o def test_UI_GIVEN_user_selects_single_pixel_WHEN_changing_pixel_layout_THEN_pixel_grid_becomes_visible( qtbot, template, pixel_options ): - # Single pixel is selected by default so switch to entire shape then switch back systematic_button_press(qtbot, template, pixel_options.entire_shape_radio_button) systematic_button_press(qtbot, template, pixel_options.single_pixel_radio_button) @@ -233,7 +233,6 @@ def test_UI_GIVEN_user_selects_single_pixel_WHEN_changing_pixel_layout_THEN_pixe def test_UI_GIVEN_user_selects_pixel_grid_WHEN_changing_pixel_layout_THEN_pixel_grid_is_set_to_true_in_ok_validator( qtbot, template, pixel_options ): - # Press the pixel grid button systematic_button_press(qtbot, template, pixel_options.entire_shape_radio_button) @@ -246,7 +245,6 @@ def test_UI_GIVEN_user_selects_pixel_grid_WHEN_changing_pixel_layout_THEN_pixel_ def test_UI_GIVEN_user_selects_no_pixels_and_gives_valid_nonpixel_input_WHEN_changing_pixel_layout_THEN_add_component_button_is_enabled( qtbot, template, pixel_options ): - systematic_button_press(qtbot, template, pixel_options.no_pixels_button) # Check that the add component button is enabled @@ -256,7 +254,6 @@ def test_UI_GIVEN_user_selects_no_pixels_and_gives_valid_nonpixel_input_WHEN_cha def test_UI_GIVEN_valid_pixel_grid_WHEN_entering_pixel_options_THEN_changing_to_pixel_mapping_causes_validity_to_change( qtbot, template, pixel_options ): - # Change the first ID qtbot.keyClick(pixel_options.first_id_spin_box, Qt.Key_Up) qtbot.keyClick(pixel_options.first_id_spin_box, Qt.Key_Up) @@ -274,7 +271,6 @@ def test_UI_GIVEN_valid_pixel_grid_WHEN_entering_pixel_options_THEN_changing_to_ def test_UI_GIVEN_invalid_pixel_grid_WHEN_entering_pixel_options_THEN_changing_to_valid_pixel_mapping_causes_validity_to_change( qtbot, template, pixel_options ): - manually_create_pixel_mapping_list(pixel_options) # Make the pixel grid invalid @@ -297,7 +293,6 @@ def test_UI_GIVEN_invalid_pixel_grid_WHEN_entering_pixel_options_THEN_changing_t def test_UI_GIVEN_valid_pixel_mapping_WHEN_entering_pixel_options_THEN_changing_to_invalid_pixel_mapping_causes_validity_to_change( qtbot, template, pixel_options ): - # Make the pixel grid invalid qtbot.keyClick(pixel_options.row_count_spin_box, Qt.Key_Down) qtbot.keyClick(pixel_options.column_count_spin_box, Qt.Key_Down) @@ -318,7 +313,6 @@ def test_UI_GIVEN_valid_pixel_mapping_WHEN_entering_pixel_options_THEN_changing_ def test_UI_GIVEN_invalid_pixel_mapping_WHEN_entering_pixel_options_THEN_changing_to_valid_pixel_grid_causes_validity_to_change( qtbot, template, pixel_options ): - # Change to pixel mapping systematic_button_press(qtbot, template, pixel_options.entire_shape_radio_button) @@ -338,7 +332,6 @@ def test_UI_GIVEN_invalid_pixel_mapping_WHEN_entering_pixel_options_THEN_changin def test_UI_GIVEN_valid_pixel_mapping_WHEN_entering_pixel_options_THEN_changing_to_invalid_pixel_grid_causes_validity_to_change( qtbot, template, pixel_options ): - # Change to pixel mapping systematic_button_press(qtbot, template, pixel_options.entire_shape_radio_button) @@ -362,7 +355,6 @@ def test_UI_GIVEN_valid_pixel_mapping_WHEN_entering_pixel_options_THEN_changing_ def test_UI_GIVEN_invalid_mapping_and_grid_WHEN_entering_pixel_options_THEN_changing_to_no_pixels_causes_validity_to_change( qtbot, template, pixel_options ): - # Make the pixel grid invalid qtbot.keyClick(pixel_options.row_count_spin_box, Qt.Key_Down) qtbot.keyClick(pixel_options.column_count_spin_box, Qt.Key_Down) @@ -384,7 +376,6 @@ def test_UI_GIVEN_invalid_mapping_and_grid_WHEN_entering_pixel_options_THEN_chan def test_UI_GIVEN_nothing_WHEN_pixel_mapping_options_are_visible_THEN_options_have_expected_default_values( qtbot, template, pixel_options ): - # Check that the pixel-related fields start out with the expected default values assert pixel_options.row_count_spin_box.value() == 1 assert pixel_options.column_count_spin_box.value() == 1 @@ -404,7 +395,6 @@ def test_UI_GIVEN_nothing_WHEN_pixel_mapping_options_are_visible_THEN_options_ha def test_UI_GIVEN_row_count_is_not_zero_WHEN_entering_pixel_grid_THEN_row_height_becomes_enabled( qtbot, template, pixel_options ): - # Make the row count go to zero and then back to one again qtbot.keyClick(pixel_options.row_count_spin_box, Qt.Key_Down) qtbot.keyClick(pixel_options.row_count_spin_box, Qt.Key_Up) @@ -416,7 +406,6 @@ def test_UI_GIVEN_row_count_is_not_zero_WHEN_entering_pixel_grid_THEN_row_height def test_UI_GIVEN_column_count_is_not_zero_WHEN_entering_pixel_grid_THEN_column_width_becomes_enabled( qtbot, template, pixel_options ): - # Make the column count go to zero and then back to one again qtbot.keyClick(pixel_options.column_count_spin_box, Qt.Key_Down) qtbot.keyClick(pixel_options.column_count_spin_box, Qt.Key_Up) @@ -428,7 +417,6 @@ def test_UI_GIVEN_column_count_is_not_zero_WHEN_entering_pixel_grid_THEN_column_ def test_UI_GIVEN_user_provides_mesh_file_WHEN_entering_pixel_mapping_THEN_pixel_mapping_list_is_populated_with_correct_number_of_widgets( qtbot, template, pixel_options ): - systematic_button_press(qtbot, template, pixel_options.entire_shape_radio_button) manually_create_pixel_mapping_list(pixel_options) assert pixel_options.get_pixel_mapping_table_size() == CORRECT_CUBE_FACES @@ -437,7 +425,6 @@ def test_UI_GIVEN_user_provides_mesh_file_WHEN_entering_pixel_mapping_THEN_pixel def test_UI_GIVEN_mesh_file_changes_WHEN_entering_pxixel_mapping_THEN_pixel_mapping_list_changes( qtbot, template, pixel_options ): - systematic_button_press(qtbot, template, pixel_options.entire_shape_radio_button) manually_create_pixel_mapping_list(pixel_options) manually_create_pixel_mapping_list(pixel_options, VALID_OCTA_OFF_FILE) @@ -447,7 +434,6 @@ def test_UI_GIVEN_mesh_file_changes_WHEN_entering_pxixel_mapping_THEN_pixel_mapp def test_UI_GIVEN_cylinder_number_WHEN_entering_pixel_mapping_THEN_pixel_mapping_list_is_populated_with_correct_number_of_widgets( qtbot, template, pixel_options ): - cylinder_number = 6 systematic_button_press(qtbot, template, pixel_options.entire_shape_radio_button) pixel_options.populate_pixel_mapping_list_with_cylinder_number(cylinder_number) @@ -457,7 +443,6 @@ def test_UI_GIVEN_cylinder_number_WHEN_entering_pixel_mapping_THEN_pixel_mapping def test_UI_GIVEN_cylinder_number_changes_WHEN_entering_pixel_mapping_THEN_pixel_mapping_list_changes( qtbot, template, pixel_options ): - first_cylinder_number = 6 second_cylinder_number = first_cylinder_number - 1 systematic_button_press(qtbot, template, pixel_options.entire_shape_radio_button) @@ -473,7 +458,6 @@ def test_UI_GIVEN_cylinder_number_changes_WHEN_entering_pixel_mapping_THEN_pixel def test_UI_GIVEN_user_switches_to_pixel_mapping_WHEN_creating_component_THEN_pixel_mapping_signal_is_emitted( qtbot, template, pixel_options ): - global emitted emitted = False @@ -489,7 +473,6 @@ def check_that_signal_is_emitted(): def test_UI_GIVEN_mesh_file_WHEN_generating_mapping_list_THEN_filename_returned_by_pixel_options_matches_filename_of_mesh( qtbot, template, pixel_options ): - filename = "a/mesh/file.off" systematic_button_press(qtbot, template, pixel_options.entire_shape_radio_button) @@ -501,7 +484,6 @@ def test_UI_GIVEN_mesh_file_WHEN_generating_mapping_list_THEN_filename_returned_ def test_UI_GIVEN_user_opens_two_different_files_WHEN_creating_off_geometry_THEN_filename_stored_by_pixel_options_changes( qtbot, template, pixel_options ): - first_filename = "a/mesh/file.off" second_filename = "a/different/mesh/file.off" @@ -515,7 +497,6 @@ def test_UI_GIVEN_user_opens_two_different_files_WHEN_creating_off_geometry_THEN def test_UI_GIVEN_user_switches_from_mesh_to_cylinder_WHEN_creating_cylindrical_geometry_THEN_pixel_mapping_filename_is_changed_to_none( qtbot, template, pixel_options ): - systematic_button_press(qtbot, template, pixel_options.entire_shape_radio_button) manually_create_pixel_mapping_list(pixel_options) @@ -527,7 +508,6 @@ def test_UI_GIVEN_user_switches_from_mesh_to_cylinder_WHEN_creating_cylindrical_ def test_UI_GIVEN_entire_shape_button_is_not_selected_WHEN_calling_pixel_mapping_method_THEN_pixel_mapping_method_returns_without_populating_list( qtbot, template, pixel_options ): - manually_create_pixel_mapping_list(pixel_options) assert pixel_options.get_pixel_mapping_table_size() == 0 @@ -539,7 +519,6 @@ def test_UI_GIVEN_entire_shape_button_is_not_selected_WHEN_calling_pixel_mapping def test_UI_GIVEN_mapping_list_provided_by_user_WHEN_entering_pixel_data_THEN_calling_generate_pixel_data_returns_mapping_with_list_that_matches_user_input( qtbot, template, pixel_options ): - systematic_button_press(qtbot, template, pixel_options.entire_shape_radio_button) num_faces = 6 expected_id_list = [i if i % 2 != 0 else None for i in range(num_faces)] @@ -558,25 +537,21 @@ def test_UI_GIVEN_mapping_list_provided_by_user_WHEN_entering_pixel_data_THEN_ca def test_UI_GIVEN_no_pixels_button_is_pressed_WHEN_entering_pixel_data_THEN_calling_generate_pixel_data_returns_none( qtbot, template, pixel_options ): - systematic_button_press(qtbot, template, pixel_options.no_pixels_button) assert pixel_options.generate_pixel_data() is None def test_GIVEN_scalar_value_WHEN_calling_check_data_is_an_array_THEN_returns_false(): - data = 3.5 assert not data_is_an_array_with_more_than_one_element(data) def test_GIVEN_array_with_single_element_WHEN_calling_check_data_is_an_array_THEN_returns_false(): - data = np.array([3.5]) assert not data_is_an_array_with_more_than_one_element(data) def test_GIVEN_array_with_multiple_elements_WHEN_calling_check_data_is_an_array_THEN_returns_true(): - data = np.arange(5) assert data_is_an_array_with_more_than_one_element(data) @@ -723,6 +698,16 @@ def test_GIVEN_component_with_pixel_grid_WHEN_editing_a_component_THEN_pixel_gri == pixel_grid.initial_count_corner ) + # gaps + assert pixel_options.gap_every_rows_spin_box.value() == pixel_grid.gap_every_rows + assert ( + pixel_options.gap_every_columns_spin_box.value() == pixel_grid.gap_every_columns + ) + assert pixel_options.row_gap_height_spin_box.value() == pixel_grid.row_gap_height + assert ( + pixel_options.column_gap_width_spin_box.value() == pixel_grid.column_gap_width + ) + @pytest.mark.parametrize("count_along", COUNT_DIRECTION.values()) def test_GIVEN_detector_numbers_WHEN_calling_get_detector_number_information_THEN_expected_count_direction_is_returned( @@ -815,7 +800,6 @@ def test_GIVEN_cylindrical_geometry_WHEN_editing_pixel_mapping_with_single_pixel def test_GIVEN_pixel_grid_information_WHEN_creating_pixel_grid_THEN_calling_generate_pixel_data_returns_grid_that_matches_user_input( qtbot, template, pixel_options ): - systematic_button_press(qtbot, template, pixel_options.single_pixel_radio_button) pixel_grid = pixel_options.generate_pixel_data()