diff --git a/CHANGES.rst b/CHANGES.rst index 9ae34fee26..62af5b8d39 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,8 @@ New Features * New design for viewer legend and data-menu. [#3220, #3254, #3263, #3264, #3271, #3272, #3274, #3289, #3310] +* Improve performance while importing multiple regions. [#3321] + Cubeviz ^^^^^^^ diff --git a/jdaviz/configs/default/plugins/subset_tools/subset_tools.py b/jdaviz/configs/default/plugins/subset_tools/subset_tools.py index d536d1a5cf..4a572ec69c 100644 --- a/jdaviz/configs/default/plugins/subset_tools/subset_tools.py +++ b/jdaviz/configs/default/plugins/subset_tools/subset_tools.py @@ -882,109 +882,111 @@ def _load_regions(self, regions, combination_mode=None, max_num_regions=None, previous_mode = self.app.session.edit_subset_mode.mode - for index, region in enumerate(regions): - # Set combination mode for how region will be applied to current subset - # or created as a new subset - if combo_mode_is_list: - combo_mode = combination_mode[index] - else: - combo_mode = combination_mode - - if combo_mode == 'new': - # Remove selection of subset so that new one will be created - self.app.session.edit_subset_mode.edit_subset = None # No overwrite next iteration - self.app.session.edit_subset_mode.mode = SUBSET_MODES_PRETTY['new'] - elif combo_mode: - self.combination_mode.selected = combo_mode - - if (isinstance(region, (SkyCircularAperture, SkyEllipticalAperture, - SkyRectangularAperture, SkyCircularAnnulus, - CircleSkyRegion, EllipseSkyRegion, - RectangleSkyRegion, CircleAnnulusSkyRegion)) - and not has_wcs): - bad_regions.append((region, 'Sky region provided but data has no valid WCS')) - continue - - if (isinstance(region, (CircularAperture, EllipticalAperture, - RectangularAperture, CircularAnnulus, - CirclePixelRegion, EllipsePixelRegion, - RectanglePixelRegion, CircleAnnulusPixelRegion)) - and (hasattr(self.app, '_link_type') and self.app._link_type == "wcs")): - bad_regions.append((region, 'Pixel region provided by data is linked by WCS')) - continue - - # photutils: Convert to region shape first - if isinstance(region, (CircularAperture, SkyCircularAperture, - EllipticalAperture, SkyEllipticalAperture, - RectangularAperture, SkyRectangularAperture, - CircularAnnulus, SkyCircularAnnulus)): - region = aperture2regions(region) - - # region: Convert to ROI. - # NOTE: Out-of-bounds ROI will succeed; this is native glue behavior. - if isinstance(region, (CirclePixelRegion, CircleSkyRegion, - EllipsePixelRegion, EllipseSkyRegion, - RectanglePixelRegion, RectangleSkyRegion, - CircleAnnulusPixelRegion, CircleAnnulusSkyRegion)): - state = regions2roi(region, wcs=data.coords) - viewer.apply_roi(state) - - elif isinstance(region, (CircularROI, CircularAnnulusROI, - EllipticalROI, RectangularROI)): - viewer.apply_roi(region) - - elif isinstance(region, SpectralRegion): - # Use viewer_name if provided in kwarg, otherwise use - # default spectrum viewer name - viewer_name = (viewer_parameter or - self.app._jdaviz_helper._default_spectrum_viewer_reference_name) - range_viewer = self.app.get_viewer(viewer_name) - - s = RangeSubsetState(lo=region.lower.value, hi=region.upper.value, - att=range_viewer.state.x_att) - range_viewer.apply_subset_state(s) - - # Last resort: Masked Subset that is static (if data is not a cube) - elif data.ndim == 2: - im = None - if hasattr(region, 'to_pixel'): # Sky region: Convert to pixel region - if not has_wcs: - bad_region.append((region, 'Sky region provided but data has no valid WCS')) # noqa + with self.app._jdaviz_helper.batch_load(): + for index, region in enumerate(regions): + # Set combination mode for how region will be applied to current subset + # or created as a new subset + if combo_mode_is_list: + combo_mode = combination_mode[index] + else: + combo_mode = combination_mode + + if combo_mode == 'new': + # Remove selection of subset so that new one will be created + # No overwrite next iteration + self.app.session.edit_subset_mode.edit_subset = None + self.app.session.edit_subset_mode.mode = SUBSET_MODES_PRETTY['new'] + elif combo_mode: + self.combination_mode.selected = combo_mode + + if (isinstance(region, (SkyCircularAperture, SkyEllipticalAperture, + SkyRectangularAperture, SkyCircularAnnulus, + CircleSkyRegion, EllipseSkyRegion, + RectangleSkyRegion, CircleAnnulusSkyRegion)) + and not has_wcs): + bad_regions.append((region, 'Sky region provided but data has no valid WCS')) + continue + + if (isinstance(region, (CircularAperture, EllipticalAperture, + RectangularAperture, CircularAnnulus, + CirclePixelRegion, EllipsePixelRegion, + RectanglePixelRegion, CircleAnnulusPixelRegion)) + and (hasattr(self.app, '_link_type') and self.app._link_type == "wcs")): + bad_regions.append((region, 'Pixel region provided by data is linked by WCS')) + continue + + # photutils: Convert to region shape first + if isinstance(region, (CircularAperture, SkyCircularAperture, + EllipticalAperture, SkyEllipticalAperture, + RectangularAperture, SkyRectangularAperture, + CircularAnnulus, SkyCircularAnnulus)): + region = aperture2regions(region) + + # region: Convert to ROI. + # NOTE: Out-of-bounds ROI will succeed; this is native glue behavior. + if isinstance(region, (CirclePixelRegion, CircleSkyRegion, + EllipsePixelRegion, EllipseSkyRegion, + RectanglePixelRegion, RectangleSkyRegion, + CircleAnnulusPixelRegion, CircleAnnulusSkyRegion)): + state = regions2roi(region, wcs=data.coords) + viewer.apply_roi(state) + + elif isinstance(region, (CircularROI, CircularAnnulusROI, + EllipticalROI, RectangularROI)): + viewer.apply_roi(region) + + elif isinstance(region, SpectralRegion): + # Use viewer_name if provided in kwarg, otherwise use + # default spectrum viewer name + viewer_name = (viewer_parameter or + self.app._jdaviz_helper._default_spectrum_viewer_reference_name) + range_viewer = self.app.get_viewer(viewer_name) + + s = RangeSubsetState(lo=region.lower.value, hi=region.upper.value, + att=range_viewer.state.x_att) + range_viewer.apply_subset_state(s) + + # Last resort: Masked Subset that is static (if data is not a cube) + elif data.ndim == 2: + im = None + if hasattr(region, 'to_pixel'): # Sky region: Convert to pixel region + if not has_wcs: + bad_regions.append((region, 'Sky region provided but data has no valid WCS')) # noqa + continue + region = region.to_pixel(data.coords) + + if hasattr(region, 'to_mask'): + try: + mask = region.to_mask(**kwargs) + im = mask.to_image(data.shape) # Can be None + except Exception as e: # pragma: no cover + bad_regions.append((region, f'Failed to load: {repr(e)}')) + continue + + # Boolean mask as input is supported but not advertised. + elif (isinstance(region, np.ndarray) and region.shape == data.shape + and region.dtype == np.bool_): + im = region + + if im is None: + bad_regions.append((region, 'Mask creation failed')) continue - region = region.to_pixel(data.coords) - if hasattr(region, 'to_mask'): + # NOTE: Region creation info is thus lost. try: - mask = region.to_mask(**kwargs) - im = mask.to_image(data.shape) # Can be None + subset_label = f'{msg_prefix} {msg_count}' + state = MaskSubsetState(im, data.pixel_component_ids) + self.app.data_collection.new_subset_group(subset_label, state) + msg_count += 1 except Exception as e: # pragma: no cover bad_regions.append((region, f'Failed to load: {repr(e)}')) continue - - # Boolean mask as input is supported but not advertised. - elif (isinstance(region, np.ndarray) and region.shape == data.shape - and region.dtype == np.bool_): - im = region - - if im is None: + else: bad_regions.append((region, 'Mask creation failed')) continue - - # NOTE: Region creation info is thus lost. - try: - subset_label = f'{msg_prefix} {msg_count}' - state = MaskSubsetState(im, data.pixel_component_ids) - self.app.data_collection.new_subset_group(subset_label, state) - msg_count += 1 - except Exception as e: # pragma: no cover - bad_regions.append((region, f'Failed to load: {repr(e)}')) - continue - else: - bad_regions.append((region, 'Mask creation failed')) - continue - n_loaded += 1 - if max_num_regions is not None and n_loaded >= max_num_regions: - break + n_loaded += 1 + if max_num_regions is not None and n_loaded >= max_num_regions: + break # Revert edit mode to before the import_region call self.app.session.edit_subset_mode.mode = previous_mode diff --git a/jdaviz/core/template_mixin.py b/jdaviz/core/template_mixin.py index 25516855d9..165e34b3f3 100644 --- a/jdaviz/core/template_mixin.py +++ b/jdaviz/core/template_mixin.py @@ -4063,15 +4063,21 @@ def add_results_from_plugin(self, data_item, replace=None, label=None): else: this_replace = isinstance(this_viewer, BqplotImageView) - self.app.add_data_to_viewer(viewer_ref, - label, - visible=visible, clear_other_data=this_replace) - - if preserved != {}: - layer_state = [layer.state for layer in this_viewer.layers if - layer.layer.label == label][0] - for att in preserved: - setattr(layer_state, att, preserved[att]) + if self.app._jdaviz_helper._in_batch_load: + # NOTE: this currently only stores the viewer reference, and so + # will not handle preserving layer options if overwriting an existing + # entry. + self.app._jdaviz_helper._delayed_show_in_viewer_labels[label] = viewer_ref + else: + self.app.add_data_to_viewer(viewer_ref, + label, + visible=visible, clear_other_data=this_replace) + + if preserved != {}: + layer_state = [layer.state for layer in this_viewer.layers if + layer.layer.label == label][0] + for att in preserved: + setattr(layer_state, att, preserved[att]) # update overwrite warnings, etc self._on_label_changed()