@@ -124,7 +124,7 @@ def __init__( # noqa: PLR0913
124124 segmentation_file_regex : Optional [Dict [str , str ]] = None ,
125125 image_adjustment : Optional [Callable ] = None ,
126126 display_options : Optional [Dict [str , Any ]] = None ,
127- * args : Tuple [ Any , ...] ,
127+ * args : Any ,
128128 ** kwargs : Dict [str , Any ],
129129 ) -> None :
130130 """
@@ -277,6 +277,7 @@ def __init__( # noqa: PLR0913
277277 )
278278 ),
279279 "_filter_range_sliders" : {},
280+ "_initializing" : True ,
280281 }
281282
282283 if self ._custom_attrs ["data_context_dir" ] is not None :
@@ -351,6 +352,7 @@ def __init__( # noqa: PLR0913
351352 # Wrap methods so they return CytoDataFrames
352353 # instead of Pandas DataFrames.
353354 self ._wrap_methods ()
355+ self ._custom_attrs ["_initializing" ] = False
354356
355357 def __getitem__ (self : CytoDataFrame_type , key : Union [int , str ]) -> Any :
356358 """
@@ -371,34 +373,56 @@ def __getitem__(self: CytoDataFrame_type, key: Union[int, str]) -> Any:
371373 return result
372374
373375 elif isinstance (result , pd .DataFrame ):
374- cdf = CytoDataFrame (
375- super ().__getitem__ (key ),
376- data_context_dir = self ._custom_attrs ["data_context_dir" ],
377- data_image_paths = self ._custom_attrs ["data_image_paths" ],
378- data_bounding_box = self ._custom_attrs ["data_bounding_box" ],
379- compartment_center_xy = self ._custom_attrs ["compartment_center_xy" ],
380- data_mask_context_dir = self ._custom_attrs ["data_mask_context_dir" ],
381- data_outline_context_dir = self ._custom_attrs ["data_outline_context_dir" ],
382- segmentation_file_regex = self ._custom_attrs ["segmentation_file_regex" ],
383- image_adjustment = self ._custom_attrs ["image_adjustment" ],
384- display_options = self ._custom_attrs ["display_options" ],
385- )
376+ return self ._build_result_cdf (result )
386377
387- # add widget control meta
388- cdf ._custom_attrs ["_widget_state" ] = self ._custom_attrs ["_widget_state" ]
389- cdf ._custom_attrs ["_scale_slider" ] = self ._custom_attrs ["_scale_slider" ]
390- cdf ._custom_attrs ["_filter_range_sliders" ] = self ._custom_attrs [
391- "_filter_range_sliders"
392- ]
393- cdf ._custom_attrs ["_output" ] = self ._custom_attrs ["_output" ]
378+ @property
379+ def _constructor (self : CytoDataFrame_type ) -> Callable [..., CytoDataFrame_type ]:
380+ """Return a constructor that preserves CytoDataFrame display state."""
381+ if self ._custom_attrs .get ("_initializing" , False ):
382+ return pd .DataFrame
383+ return self ._build_result_cdf
394384
395- return cdf
385+ def _build_result_cdf (
386+ self : CytoDataFrame_type ,
387+ data : Union ["CytoDataFrame" , pd .DataFrame , pd .Series , Any ],
388+ * args : Any ,
389+ ** kwargs : Dict [str , Any ],
390+ ) -> CytoDataFrame_type :
391+ """Construct a result frame while preserving CytoDataFrame metadata."""
392+ if data is None and "data" in kwargs :
393+ data = kwargs .pop ("data" )
394+
395+ cdf = CytoDataFrame (
396+ data ,
397+ * args ,
398+ data_context_dir = self ._custom_attrs ["data_context_dir" ],
399+ data_image_paths = self ._custom_attrs ["data_image_paths" ],
400+ data_bounding_box = self ._custom_attrs ["data_bounding_box" ],
401+ compartment_center_xy = self ._custom_attrs ["compartment_center_xy" ],
402+ data_mask_context_dir = self ._custom_attrs ["data_mask_context_dir" ],
403+ data_outline_context_dir = self ._custom_attrs ["data_outline_context_dir" ],
404+ segmentation_file_regex = self ._custom_attrs ["segmentation_file_regex" ],
405+ image_adjustment = self ._custom_attrs ["image_adjustment" ],
406+ display_options = self ._custom_attrs ["display_options" ],
407+ ** kwargs ,
408+ )
409+
410+ # Preserve the shared interactive widget objects across derived views.
411+ cdf ._custom_attrs ["is_transposed" ] = self ._custom_attrs ["is_transposed" ]
412+ cdf ._custom_attrs ["_widget_state" ] = self ._custom_attrs ["_widget_state" ]
413+ cdf ._custom_attrs ["_scale_slider" ] = self ._custom_attrs ["_scale_slider" ]
414+ cdf ._custom_attrs ["_filter_range_sliders" ] = self ._custom_attrs [
415+ "_filter_range_sliders"
416+ ]
417+ cdf ._custom_attrs ["_output" ] = self ._custom_attrs ["_output" ]
418+
419+ return cdf
396420
397421 def _return_cytodataframe (
398422 self : CytoDataFrame_type ,
399423 method : Callable ,
400424 method_name : str ,
401- * args : Tuple [ Any , ...] ,
425+ * args : Any ,
402426 ** kwargs : Dict [str , Any ],
403427 ) -> Any :
404428 """
@@ -410,7 +434,7 @@ def _return_cytodataframe(
410434 The method to be called and wrapped.
411435 method_name (str):
412436 The name of the method to be wrapped.
413- *args (Tuple[ Any, ...] ):
437+ *args (Any):
414438 Positional arguments to be passed to the method.
415439 **kwargs (Dict[str, Any]):
416440 Keyword arguments to be passed to the method.
@@ -423,33 +447,20 @@ def _return_cytodataframe(
423447
424448 """
425449
426- result = method (* args , ** kwargs )
427-
428- if isinstance (result , pd .DataFrame ):
429- cdf = CytoDataFrame (
430- data = result ,
431- data_context_dir = self ._custom_attrs ["data_context_dir" ],
432- data_image_paths = self ._custom_attrs ["data_image_paths" ],
433- data_bounding_box = self ._custom_attrs ["data_bounding_box" ],
434- compartment_center_xy = self ._custom_attrs ["compartment_center_xy" ],
435- data_mask_context_dir = self ._custom_attrs ["data_mask_context_dir" ],
436- data_outline_context_dir = self ._custom_attrs ["data_outline_context_dir" ],
437- segmentation_file_regex = self ._custom_attrs ["segmentation_file_regex" ],
438- image_adjustment = self ._custom_attrs ["image_adjustment" ],
439- display_options = self ._custom_attrs ["display_options" ],
440- )
441- # If the method name is transpose we know that
442- # the dataframe has been transposed.
443- if method_name == "transpose" and not self ._custom_attrs ["is_transposed" ]:
444- cdf ._custom_attrs ["is_transposed" ] = True
450+ result = (
451+ pd .DataFrame (self ).transpose (* args , ** kwargs )
452+ if method_name == "transpose"
453+ else method (* args , ** kwargs )
454+ )
445455
446- # add widget control meta
447- cdf ._custom_attrs ["_widget_state" ] = self ._custom_attrs ["_widget_state" ]
448- cdf ._custom_attrs ["_scale_slider" ] = self ._custom_attrs ["_scale_slider" ]
449- cdf ._custom_attrs ["_filter_range_sliders" ] = self ._custom_attrs [
450- "_filter_range_sliders"
451- ]
452- cdf ._custom_attrs ["_output" ] = self ._custom_attrs ["_output" ]
456+ if not isinstance (result , pd .DataFrame ):
457+ return result
458+
459+ cdf = self ._build_result_cdf (result )
460+ # If the method name is transpose we know that
461+ # the dataframe has been transposed.
462+ if method_name == "transpose" :
463+ cdf ._custom_attrs ["is_transposed" ] = not self ._custom_attrs ["is_transposed" ]
453464
454465 return cdf
455466
@@ -473,7 +484,7 @@ def _wrap_method(self: CytoDataFrame_type, method_name: str) -> Callable:
473484 the result is a CytoDataFrame.
474485 """
475486
476- def wrapper (* args : Tuple [ Any , ...] , ** kwargs : Dict [str , Any ]) -> Any :
487+ def wrapper (* args : Any , ** kwargs : Dict [str , Any ]) -> Any :
477488 """
478489 Wraps the specified method to ensure
479490 it returns a CytoDataFrame.
@@ -485,7 +496,7 @@ def wrapper(*args: Tuple[Any, ...], **kwargs: Dict[str, Any]) -> Any:
485496 custom attributes.
486497
487498 Args:
488- *args (Tuple[ Any, ...] ):
499+ *args (Any):
489500 Positional arguments to be passed to the method.
490501 **kwargs (Dict[str, Any]):
491502 Keyword arguments to be passed to the method.
@@ -4346,7 +4357,7 @@ def _generate_jupyter_dataframe_html( # noqa: C901, PLR0912, PLR0915
43464357 # if the data are transposed,
43474358 # we transpose them back to keep
43484359 # logic the same here.
4349- data = self .transpose ()
4360+ data = pd . DataFrame ( self ) .transpose ()
43504361
43514362 # Re-add bounding box columns if they are no longer available
43524363 bounding_box_externally_joined = False
@@ -4362,7 +4373,12 @@ def _generate_jupyter_dataframe_html( # noqa: C901, PLR0912, PLR0915
43624373 )
43634374 bounding_box_externally_joined = True
43644375 else :
4365- data = self .copy () if not bounding_box_externally_joined else data
4376+ data = (
4377+ data
4378+ if self ._custom_attrs ["is_transposed" ]
4379+ or bounding_box_externally_joined
4380+ else self .copy ()
4381+ )
43664382
43674383 # Re-add compartment center xy columns if they are no longer available
43684384 compartment_center_externally_joined = False
@@ -4381,7 +4397,8 @@ def _generate_jupyter_dataframe_html( # noqa: C901, PLR0912, PLR0915
43814397 else :
43824398 data = (
43834399 data
4384- if bounding_box_externally_joined
4400+ if self ._custom_attrs ["is_transposed" ]
4401+ or bounding_box_externally_joined
43854402 or compartment_center_externally_joined
43864403 else self .copy ()
43874404 )
@@ -4411,7 +4428,9 @@ def _generate_jupyter_dataframe_html( # noqa: C901, PLR0912, PLR0915
44114428 else :
44124429 data = (
44134430 data
4414- if image_paths_externally_joined or bounding_box_externally_joined
4431+ if self ._custom_attrs ["is_transposed" ]
4432+ or image_paths_externally_joined
4433+ or bounding_box_externally_joined
44154434 else self .copy ()
44164435 )
44174436
0 commit comments