synapse_net.tools.base_widget
1import os 2import sys 3from pathlib import Path 4 5import napari 6import qtpy.QtWidgets as QtWidgets 7 8from napari.utils.notifications import show_info 9from qtpy.QtWidgets import ( 10 QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSpinBox, QComboBox, QCheckBox 11) 12from superqt import QCollapsible 13 14try: 15 from napari_skimage_regionprops import add_table, get_table 16except ImportError: 17 add_table, get_table = None, None 18 19 20class _SilencePrint: 21 def __enter__(self): 22 self._original_stdout = sys.stdout 23 sys.stdout = open(os.devnull, "w") 24 25 def __exit__(self, exc_type, exc_val, exc_tb): 26 sys.stdout.close() 27 sys.stdout = self._original_stdout 28 29 30class BaseWidget(QWidget): 31 def __init__(self): 32 super().__init__() 33 self.viewer = napari.current_viewer() 34 self.attribute_dict = {} 35 36 def _create_layer_selector(self, selector_name, layer_type="Image"): 37 """Create a layer selector for an image or labels and store it in a dictionary. 38 39 Args: 40 selector_name (str): The name of the selector, used as a key in the dictionary. 41 layer_type (str): The type of layer to filter for ("Image" or "Labels"). 42 """ 43 if not hasattr(self, "layer_selectors"): 44 self.layer_selectors = {} 45 46 # Determine the annotation type for the widget 47 if layer_type == "Image": 48 layer_filter = napari.layers.Image 49 elif layer_type == "Labels": 50 layer_filter = napari.layers.Labels 51 elif layer_type == "Shapes": 52 layer_filter = napari.layers.Shapes 53 else: 54 raise ValueError("layer_type must be either 'Image' or 'Labels'.") 55 56 selector_widget = QtWidgets.QWidget() 57 image_selector = QtWidgets.QComboBox() 58 layer_label = QtWidgets.QLabel(f"{selector_name}:") 59 60 # Populate initial options 61 self._update_selector(selector=image_selector, layer_filter=layer_filter) 62 63 # Update selector on layer events 64 self.viewer.layers.events.inserted.connect(lambda event: self._update_selector(image_selector, layer_filter)) 65 self.viewer.layers.events.removed.connect(lambda event: self._update_selector(image_selector, layer_filter)) 66 67 # Store the selector in the dictionary 68 self.layer_selectors[selector_name] = selector_widget 69 70 # Set up layout 71 layout = QVBoxLayout() 72 layout.addWidget(layer_label) 73 layout.addWidget(image_selector) 74 selector_widget.setLayout(layout) 75 return selector_widget 76 77 def _update_selector(self, selector, layer_filter): 78 """Update a single selector with the current image layers in the viewer.""" 79 selector.clear() 80 image_layers = [layer.name for layer in self.viewer.layers if isinstance(layer, layer_filter)] 81 selector.addItems(image_layers) 82 83 def _get_layer_selector_layer(self, selector_name): 84 """Return the layer currently selected in a given selector.""" 85 if selector_name in self.layer_selectors: 86 selector_widget = self.layer_selectors[selector_name] 87 88 # Retrieve the QComboBox from the QWidget's layout 89 image_selector = selector_widget.layout().itemAt(1).widget() 90 91 if isinstance(image_selector, QComboBox): 92 selected_layer_name = image_selector.currentText() 93 if selected_layer_name in self.viewer.layers: 94 return self.viewer.layers[selected_layer_name] 95 return None # Return None if layer not found 96 97 def _get_layer_selector_data(self, selector_name, return_metadata=False): 98 """Return the data for the layer currently selected in a given selector.""" 99 if selector_name in self.layer_selectors: 100 selector_widget = self.layer_selectors[selector_name] 101 102 # Retrieve the QComboBox from the QWidget's layout 103 image_selector = selector_widget.layout().itemAt(1).widget() 104 105 if isinstance(image_selector, QComboBox): 106 selected_layer_name = image_selector.currentText() 107 if selected_layer_name in self.viewer.layers: 108 if return_metadata: 109 return self.viewer.layers[selected_layer_name].metadata 110 else: 111 return self.viewer.layers[selected_layer_name].data 112 return None # Return None if layer not found 113 114 def _add_string_param(self, name, value, title=None, placeholder=None, layout=None, tooltip=None): 115 if layout is None: 116 layout = QtWidgets.QHBoxLayout() 117 label = QtWidgets.QLabel(title or name) 118 if tooltip: 119 label.setToolTip(tooltip) 120 layout.addWidget(label) 121 param = QtWidgets.QLineEdit() 122 param.setText(value) 123 if placeholder is not None: 124 param.setPlaceholderText(placeholder) 125 param.textChanged.connect(lambda val: setattr(self, name, val)) 126 if tooltip: 127 param.setToolTip(tooltip) 128 layout.addWidget(param) 129 return param, layout 130 131 def _add_float_param(self, name, value, title=None, min_val=0.0, max_val=1.0, decimals=2, 132 step=0.01, layout=None, tooltip=None): 133 if layout is None: 134 layout = QtWidgets.QHBoxLayout() 135 label = QtWidgets.QLabel(title or name) 136 if tooltip: 137 label.setToolTip(tooltip) 138 layout.addWidget(label) 139 param = QtWidgets.QDoubleSpinBox() 140 param.setRange(min_val, max_val) 141 param.setDecimals(decimals) 142 param.setValue(value) 143 param.setSingleStep(step) 144 param.valueChanged.connect(lambda val: setattr(self, name, val)) 145 if tooltip: 146 param.setToolTip(tooltip) 147 layout.addWidget(param) 148 return param, layout 149 150 def _add_int_param(self, name, value, min_val, max_val, title=None, step=1, layout=None, tooltip=None): 151 if layout is None: 152 layout = QHBoxLayout() 153 label = QLabel(title or name) 154 if tooltip: 155 label.setToolTip(tooltip) 156 layout.addWidget(label) 157 param = QSpinBox() 158 param.setRange(min_val, max_val) 159 param.setValue(value) 160 param.setSingleStep(step) 161 param.valueChanged.connect(lambda val: setattr(self, name, val)) 162 if tooltip: 163 param.setToolTip(tooltip) 164 layout.addWidget(param) 165 return param, layout 166 167 def _add_choice_param(self, name, value, options, title=None, layout=None, update=None, tooltip=None): 168 if layout is None: 169 layout = QHBoxLayout() 170 label = QLabel(title or name) 171 if tooltip: 172 label.setToolTip(tooltip) 173 layout.addWidget(label) 174 175 # Create the dropdown menu via QComboBox, set the available values. 176 dropdown = QComboBox() 177 dropdown.addItems(options) 178 if update is None: 179 dropdown.currentIndexChanged.connect(lambda index: setattr(self, name, options[index])) 180 else: 181 dropdown.currentIndexChanged.connect(update) 182 183 # Set the correct value for the value. 184 dropdown.setCurrentIndex(dropdown.findText(value)) 185 186 if tooltip: 187 dropdown.setToolTip(tooltip) 188 189 layout.addWidget(dropdown) 190 return dropdown, layout 191 192 def _add_shape_param(self, names, values, min_val, max_val, step=1, title=None, tooltip=None): 193 layout = QHBoxLayout() 194 195 x_layout = QVBoxLayout() 196 x_param, _ = self._add_int_param( 197 names[0], values[0], min_val=min_val, max_val=max_val, layout=x_layout, step=step, 198 title=title[0] if title is not None else title, tooltip=tooltip 199 ) 200 layout.addLayout(x_layout) 201 202 y_layout = QVBoxLayout() 203 y_param, _ = self._add_int_param( 204 names[1], values[1], min_val=min_val, max_val=max_val, layout=y_layout, step=step, 205 title=title[1] if title is not None else title, tooltip=tooltip 206 ) 207 layout.addLayout(y_layout) 208 209 if len(names) == 3: 210 z_layout = QVBoxLayout() 211 z_param, _ = self._add_int_param( 212 names[2], values[2], min_val=min_val, max_val=max_val, layout=z_layout, step=step, 213 title=title[2] if title is not None else title, tooltip=tooltip 214 ) 215 layout.addLayout(z_layout) 216 return x_param, y_param, z_param, layout 217 218 return x_param, y_param, layout 219 220 def _make_collapsible(self, widget, title): 221 parent_widget = QWidget() 222 parent_widget.setLayout(QVBoxLayout()) 223 collapsible = QCollapsible(title, parent_widget) 224 collapsible.addWidget(widget) 225 parent_widget.layout().addWidget(collapsible) 226 return parent_widget 227 228 def _add_boolean_param(self, name, value, title=None, tooltip=None): 229 checkbox = QCheckBox(name if title is None else title) 230 checkbox.setChecked(value) 231 checkbox.stateChanged.connect(lambda val: setattr(self, name, val)) 232 if tooltip: 233 checkbox.setToolTip(tooltip) 234 return checkbox 235 236 def _add_path_param(self, name, value, select_type, title=None, placeholder=None, tooltip=None): 237 assert select_type in ("directory", "file", "both") 238 239 layout = QtWidgets.QHBoxLayout() 240 label = QtWidgets.QLabel(title or name) 241 if tooltip: 242 label.setToolTip(tooltip) 243 layout.addWidget(label) 244 245 path_textbox = QtWidgets.QLineEdit() 246 path_textbox.setText(str(value)) 247 if placeholder is not None: 248 path_textbox.setPlaceholderText(placeholder) 249 path_textbox.textChanged.connect(lambda val: setattr(self, name, val)) 250 if tooltip: 251 path_textbox.setToolTip(tooltip) 252 253 layout.addWidget(path_textbox) 254 255 def add_path_button(select_type, tooltip=None): 256 # Adjust button text. 257 button_text = f"Select {select_type.capitalize()}" 258 path_button = QtWidgets.QPushButton(button_text) 259 260 # Call appropriate function based on select_type. 261 path_button.clicked.connect(lambda: getattr(self, f"_get_{select_type}_path")(name, path_textbox)) 262 if tooltip: 263 path_button.setToolTip(tooltip) 264 layout.addWidget(path_button) 265 266 if select_type == "both": 267 add_path_button("file") 268 add_path_button("directory") 269 270 else: 271 add_path_button(select_type) 272 273 return path_textbox, layout 274 275 def _get_directory_path(self, name, textbox, tooltip=None): 276 directory = QtWidgets.QFileDialog.getExistingDirectory( 277 self, "Select Directory", "", QtWidgets.QFileDialog.ShowDirsOnly 278 ) 279 if tooltip: 280 directory.setToolTip(tooltip) 281 if directory and Path(directory).is_dir(): 282 textbox.setText(str(directory)) 283 else: 284 # Handle the case where the selected path is not a directory 285 print("Invalid directory selected. Please try again.") 286 287 def _get_file_path(self, name, textbox, tooltip=None): 288 file_path, _ = QtWidgets.QFileDialog.getOpenFileName( 289 self, "Select File", "", "All Files (*)" 290 ) 291 if tooltip: 292 file_path.setToolTip(tooltip) 293 if file_path and Path(file_path).is_file(): 294 textbox.setText(str(file_path)) 295 else: 296 # Handle the case where the selected path is not a file 297 print("Invalid file selected. Please try again.") 298 299 def _handle_resolution(self, metadata, voxel_size_param, ndim, return_as_list=True): 300 # Get the resolution / voxel size from the layer metadata if available. 301 resolution = metadata.get("voxel_size", None) 302 303 # If user input was given then override resolution from metadata. 304 axes = "zyx" if ndim == 3 else "yx" 305 if voxel_size_param.value() != 0.0: # Changed from default. 306 resolution = {ax: voxel_size_param.value() for ax in axes} 307 308 if resolution is not None and return_as_list: 309 resolution = [resolution[ax] for ax in axes] 310 assert len(resolution) == ndim 311 312 return resolution 313 314 def _save_table(self, save_path, data): 315 ext = os.path.splitext(save_path)[1] 316 if ext == "": # No file extension given, By default we save to CSV. 317 file_path = f"{save_path}.csv" 318 data.to_csv(file_path, index=False) 319 elif ext == ".csv": # Extension was specified as csv 320 file_path = save_path 321 data.to_csv(file_path, index=False) 322 elif ext == ".xlsx": # We also support excel. 323 file_path = save_path 324 data.to_excel(file_path, index=False) 325 else: 326 raise ValueError("Invalid extension for table: {ext}. We support .csv or .xlsx.") 327 return file_path 328 329 def _add_properties_and_table(self, layer, table_data, save_path=""): 330 layer.properties = table_data 331 332 if add_table is not None: 333 with _SilencePrint(): 334 add_table(layer, self.viewer) 335 336 # Save table to file if save path is provided. 337 if save_path != "": 338 file_path = self._save_table(self.save_path.text(), table_data) 339 show_info(f"INFO: Added table and saved file to {file_path}.")
class
BaseWidget(PyQt5.QtWidgets.QWidget):
31class BaseWidget(QWidget): 32 def __init__(self): 33 super().__init__() 34 self.viewer = napari.current_viewer() 35 self.attribute_dict = {} 36 37 def _create_layer_selector(self, selector_name, layer_type="Image"): 38 """Create a layer selector for an image or labels and store it in a dictionary. 39 40 Args: 41 selector_name (str): The name of the selector, used as a key in the dictionary. 42 layer_type (str): The type of layer to filter for ("Image" or "Labels"). 43 """ 44 if not hasattr(self, "layer_selectors"): 45 self.layer_selectors = {} 46 47 # Determine the annotation type for the widget 48 if layer_type == "Image": 49 layer_filter = napari.layers.Image 50 elif layer_type == "Labels": 51 layer_filter = napari.layers.Labels 52 elif layer_type == "Shapes": 53 layer_filter = napari.layers.Shapes 54 else: 55 raise ValueError("layer_type must be either 'Image' or 'Labels'.") 56 57 selector_widget = QtWidgets.QWidget() 58 image_selector = QtWidgets.QComboBox() 59 layer_label = QtWidgets.QLabel(f"{selector_name}:") 60 61 # Populate initial options 62 self._update_selector(selector=image_selector, layer_filter=layer_filter) 63 64 # Update selector on layer events 65 self.viewer.layers.events.inserted.connect(lambda event: self._update_selector(image_selector, layer_filter)) 66 self.viewer.layers.events.removed.connect(lambda event: self._update_selector(image_selector, layer_filter)) 67 68 # Store the selector in the dictionary 69 self.layer_selectors[selector_name] = selector_widget 70 71 # Set up layout 72 layout = QVBoxLayout() 73 layout.addWidget(layer_label) 74 layout.addWidget(image_selector) 75 selector_widget.setLayout(layout) 76 return selector_widget 77 78 def _update_selector(self, selector, layer_filter): 79 """Update a single selector with the current image layers in the viewer.""" 80 selector.clear() 81 image_layers = [layer.name for layer in self.viewer.layers if isinstance(layer, layer_filter)] 82 selector.addItems(image_layers) 83 84 def _get_layer_selector_layer(self, selector_name): 85 """Return the layer currently selected in a given selector.""" 86 if selector_name in self.layer_selectors: 87 selector_widget = self.layer_selectors[selector_name] 88 89 # Retrieve the QComboBox from the QWidget's layout 90 image_selector = selector_widget.layout().itemAt(1).widget() 91 92 if isinstance(image_selector, QComboBox): 93 selected_layer_name = image_selector.currentText() 94 if selected_layer_name in self.viewer.layers: 95 return self.viewer.layers[selected_layer_name] 96 return None # Return None if layer not found 97 98 def _get_layer_selector_data(self, selector_name, return_metadata=False): 99 """Return the data for the layer currently selected in a given selector.""" 100 if selector_name in self.layer_selectors: 101 selector_widget = self.layer_selectors[selector_name] 102 103 # Retrieve the QComboBox from the QWidget's layout 104 image_selector = selector_widget.layout().itemAt(1).widget() 105 106 if isinstance(image_selector, QComboBox): 107 selected_layer_name = image_selector.currentText() 108 if selected_layer_name in self.viewer.layers: 109 if return_metadata: 110 return self.viewer.layers[selected_layer_name].metadata 111 else: 112 return self.viewer.layers[selected_layer_name].data 113 return None # Return None if layer not found 114 115 def _add_string_param(self, name, value, title=None, placeholder=None, layout=None, tooltip=None): 116 if layout is None: 117 layout = QtWidgets.QHBoxLayout() 118 label = QtWidgets.QLabel(title or name) 119 if tooltip: 120 label.setToolTip(tooltip) 121 layout.addWidget(label) 122 param = QtWidgets.QLineEdit() 123 param.setText(value) 124 if placeholder is not None: 125 param.setPlaceholderText(placeholder) 126 param.textChanged.connect(lambda val: setattr(self, name, val)) 127 if tooltip: 128 param.setToolTip(tooltip) 129 layout.addWidget(param) 130 return param, layout 131 132 def _add_float_param(self, name, value, title=None, min_val=0.0, max_val=1.0, decimals=2, 133 step=0.01, layout=None, tooltip=None): 134 if layout is None: 135 layout = QtWidgets.QHBoxLayout() 136 label = QtWidgets.QLabel(title or name) 137 if tooltip: 138 label.setToolTip(tooltip) 139 layout.addWidget(label) 140 param = QtWidgets.QDoubleSpinBox() 141 param.setRange(min_val, max_val) 142 param.setDecimals(decimals) 143 param.setValue(value) 144 param.setSingleStep(step) 145 param.valueChanged.connect(lambda val: setattr(self, name, val)) 146 if tooltip: 147 param.setToolTip(tooltip) 148 layout.addWidget(param) 149 return param, layout 150 151 def _add_int_param(self, name, value, min_val, max_val, title=None, step=1, layout=None, tooltip=None): 152 if layout is None: 153 layout = QHBoxLayout() 154 label = QLabel(title or name) 155 if tooltip: 156 label.setToolTip(tooltip) 157 layout.addWidget(label) 158 param = QSpinBox() 159 param.setRange(min_val, max_val) 160 param.setValue(value) 161 param.setSingleStep(step) 162 param.valueChanged.connect(lambda val: setattr(self, name, val)) 163 if tooltip: 164 param.setToolTip(tooltip) 165 layout.addWidget(param) 166 return param, layout 167 168 def _add_choice_param(self, name, value, options, title=None, layout=None, update=None, tooltip=None): 169 if layout is None: 170 layout = QHBoxLayout() 171 label = QLabel(title or name) 172 if tooltip: 173 label.setToolTip(tooltip) 174 layout.addWidget(label) 175 176 # Create the dropdown menu via QComboBox, set the available values. 177 dropdown = QComboBox() 178 dropdown.addItems(options) 179 if update is None: 180 dropdown.currentIndexChanged.connect(lambda index: setattr(self, name, options[index])) 181 else: 182 dropdown.currentIndexChanged.connect(update) 183 184 # Set the correct value for the value. 185 dropdown.setCurrentIndex(dropdown.findText(value)) 186 187 if tooltip: 188 dropdown.setToolTip(tooltip) 189 190 layout.addWidget(dropdown) 191 return dropdown, layout 192 193 def _add_shape_param(self, names, values, min_val, max_val, step=1, title=None, tooltip=None): 194 layout = QHBoxLayout() 195 196 x_layout = QVBoxLayout() 197 x_param, _ = self._add_int_param( 198 names[0], values[0], min_val=min_val, max_val=max_val, layout=x_layout, step=step, 199 title=title[0] if title is not None else title, tooltip=tooltip 200 ) 201 layout.addLayout(x_layout) 202 203 y_layout = QVBoxLayout() 204 y_param, _ = self._add_int_param( 205 names[1], values[1], min_val=min_val, max_val=max_val, layout=y_layout, step=step, 206 title=title[1] if title is not None else title, tooltip=tooltip 207 ) 208 layout.addLayout(y_layout) 209 210 if len(names) == 3: 211 z_layout = QVBoxLayout() 212 z_param, _ = self._add_int_param( 213 names[2], values[2], min_val=min_val, max_val=max_val, layout=z_layout, step=step, 214 title=title[2] if title is not None else title, tooltip=tooltip 215 ) 216 layout.addLayout(z_layout) 217 return x_param, y_param, z_param, layout 218 219 return x_param, y_param, layout 220 221 def _make_collapsible(self, widget, title): 222 parent_widget = QWidget() 223 parent_widget.setLayout(QVBoxLayout()) 224 collapsible = QCollapsible(title, parent_widget) 225 collapsible.addWidget(widget) 226 parent_widget.layout().addWidget(collapsible) 227 return parent_widget 228 229 def _add_boolean_param(self, name, value, title=None, tooltip=None): 230 checkbox = QCheckBox(name if title is None else title) 231 checkbox.setChecked(value) 232 checkbox.stateChanged.connect(lambda val: setattr(self, name, val)) 233 if tooltip: 234 checkbox.setToolTip(tooltip) 235 return checkbox 236 237 def _add_path_param(self, name, value, select_type, title=None, placeholder=None, tooltip=None): 238 assert select_type in ("directory", "file", "both") 239 240 layout = QtWidgets.QHBoxLayout() 241 label = QtWidgets.QLabel(title or name) 242 if tooltip: 243 label.setToolTip(tooltip) 244 layout.addWidget(label) 245 246 path_textbox = QtWidgets.QLineEdit() 247 path_textbox.setText(str(value)) 248 if placeholder is not None: 249 path_textbox.setPlaceholderText(placeholder) 250 path_textbox.textChanged.connect(lambda val: setattr(self, name, val)) 251 if tooltip: 252 path_textbox.setToolTip(tooltip) 253 254 layout.addWidget(path_textbox) 255 256 def add_path_button(select_type, tooltip=None): 257 # Adjust button text. 258 button_text = f"Select {select_type.capitalize()}" 259 path_button = QtWidgets.QPushButton(button_text) 260 261 # Call appropriate function based on select_type. 262 path_button.clicked.connect(lambda: getattr(self, f"_get_{select_type}_path")(name, path_textbox)) 263 if tooltip: 264 path_button.setToolTip(tooltip) 265 layout.addWidget(path_button) 266 267 if select_type == "both": 268 add_path_button("file") 269 add_path_button("directory") 270 271 else: 272 add_path_button(select_type) 273 274 return path_textbox, layout 275 276 def _get_directory_path(self, name, textbox, tooltip=None): 277 directory = QtWidgets.QFileDialog.getExistingDirectory( 278 self, "Select Directory", "", QtWidgets.QFileDialog.ShowDirsOnly 279 ) 280 if tooltip: 281 directory.setToolTip(tooltip) 282 if directory and Path(directory).is_dir(): 283 textbox.setText(str(directory)) 284 else: 285 # Handle the case where the selected path is not a directory 286 print("Invalid directory selected. Please try again.") 287 288 def _get_file_path(self, name, textbox, tooltip=None): 289 file_path, _ = QtWidgets.QFileDialog.getOpenFileName( 290 self, "Select File", "", "All Files (*)" 291 ) 292 if tooltip: 293 file_path.setToolTip(tooltip) 294 if file_path and Path(file_path).is_file(): 295 textbox.setText(str(file_path)) 296 else: 297 # Handle the case where the selected path is not a file 298 print("Invalid file selected. Please try again.") 299 300 def _handle_resolution(self, metadata, voxel_size_param, ndim, return_as_list=True): 301 # Get the resolution / voxel size from the layer metadata if available. 302 resolution = metadata.get("voxel_size", None) 303 304 # If user input was given then override resolution from metadata. 305 axes = "zyx" if ndim == 3 else "yx" 306 if voxel_size_param.value() != 0.0: # Changed from default. 307 resolution = {ax: voxel_size_param.value() for ax in axes} 308 309 if resolution is not None and return_as_list: 310 resolution = [resolution[ax] for ax in axes] 311 assert len(resolution) == ndim 312 313 return resolution 314 315 def _save_table(self, save_path, data): 316 ext = os.path.splitext(save_path)[1] 317 if ext == "": # No file extension given, By default we save to CSV. 318 file_path = f"{save_path}.csv" 319 data.to_csv(file_path, index=False) 320 elif ext == ".csv": # Extension was specified as csv 321 file_path = save_path 322 data.to_csv(file_path, index=False) 323 elif ext == ".xlsx": # We also support excel. 324 file_path = save_path 325 data.to_excel(file_path, index=False) 326 else: 327 raise ValueError("Invalid extension for table: {ext}. We support .csv or .xlsx.") 328 return file_path 329 330 def _add_properties_and_table(self, layer, table_data, save_path=""): 331 layer.properties = table_data 332 333 if add_table is not None: 334 with _SilencePrint(): 335 add_table(layer, self.viewer) 336 337 # Save table to file if save path is provided. 338 if save_path != "": 339 file_path = self._save_table(self.save_path.text(), table_data) 340 show_info(f"INFO: Added table and saved file to {file_path}.")
QWidget(parent: typing.Optional[QWidget] = None, flags: Union[Qt.WindowFlags, Qt.WindowType] = Qt.WindowFlags())