@@ -502,3 +502,141 @@ def adata_labels() -> AnnData:
502502 "tensor_copy" : rng .integers (0 , blobs .shape [0 ], size = (n_obs_labels , 2 )),
503503 }
504504 return generate_adata (n_var , obs_labels , obsm_labels , uns_labels )
505+
506+
507+ @pytest .fixture ()
508+ def complex_sdata () -> SpatialData :
509+ """
510+ Create a complex SpatialData object with multiple data types for comprehensive testing.
511+
512+ Contains:
513+ - Images (2D and 3D)
514+ - Labels (2D and 3D)
515+ - Shapes (polygons and circles)
516+ - Points
517+ - Multiple tables with different annotations
518+ - Categorical and numerical values in both obs and var
519+
520+ Returns
521+ -------
522+ SpatialData
523+ A complex SpatialData object for testing.
524+ """
525+ RNG = np .random .default_rng (seed = SEED )
526+
527+ # Get basic components using existing functions
528+ images = _get_images ()
529+ labels = _get_labels ()
530+ shapes = _get_shapes ()
531+ points = _get_points ()
532+
533+ # Create tables with enhanced var data
534+ n_var = 10
535+
536+ # Table 1: Basic table annotating labels2d
537+ obs1 = pd .DataFrame (
538+ {
539+ "region" : pd .Categorical (["labels2d" ] * 50 ),
540+ "instance_id" : range (1 , 51 ), # Skip background (0)
541+ "cell_type" : pd .Categorical (RNG .choice (["T cell" , "B cell" , "Macrophage" ], size = 50 )),
542+ "size" : RNG .uniform (10 , 100 , size = 50 ),
543+ }
544+ )
545+
546+ var1 = pd .DataFrame (
547+ {
548+ "feature_type" : pd .Categorical (["gene" , "protein" , "gene" , "protein" , "gene" ] * 2 ),
549+ "importance" : RNG .uniform (0 , 10 , size = n_var ),
550+ "is_marker" : RNG .choice ([True , False ], size = n_var ),
551+ },
552+ index = [f"feature_{ i } " for i in range (n_var )],
553+ )
554+
555+ X1 = RNG .normal (size = (50 , n_var ))
556+ uns1 = {
557+ "spatialdata_attrs" : {
558+ "region" : "labels2d" ,
559+ "region_key" : "region" ,
560+ "instance_key" : "instance_id" ,
561+ }
562+ }
563+
564+ table1 = AnnData (X = X1 , obs = obs1 , var = var1 , uns = uns1 )
565+
566+ # Table 2: Annotating both polygons and circles from shapes
567+ n_polygons = len (shapes ["poly" ])
568+ n_circles = len (shapes ["circles" ])
569+ total_items = n_polygons + n_circles
570+
571+ obs2 = pd .DataFrame (
572+ {
573+ "region" : pd .Categorical (["poly" ] * n_polygons + ["circles" ] * n_circles ),
574+ "instance_id" : np .concatenate ([range (n_polygons ), range (n_circles )]),
575+ "category" : pd .Categorical (RNG .choice (["A" , "B" , "C" ], size = total_items )),
576+ "value" : RNG .normal (size = total_items ),
577+ "count" : RNG .poisson (10 , size = total_items ),
578+ }
579+ )
580+
581+ var2 = pd .DataFrame (
582+ {
583+ "feature_type" : pd .Categorical (
584+ ["feature_type1" , "feature_type2" , "feature_type1" , "feature_type2" , "feature_type1" ] * 2
585+ ),
586+ "score" : RNG .exponential (2 , size = n_var ),
587+ "detected" : RNG .choice ([True , False ], p = [0.7 , 0.3 ], size = n_var ),
588+ },
589+ index = [f"metric_{ i } " for i in range (n_var )],
590+ )
591+
592+ X2 = RNG .normal (size = (total_items , n_var ))
593+ uns2 = {
594+ "spatialdata_attrs" : {
595+ "region" : ["poly" , "circles" ],
596+ "region_key" : "region" ,
597+ "instance_key" : "instance_id" ,
598+ }
599+ }
600+
601+ table2 = AnnData (X = X2 , obs = obs2 , var = var2 , uns = uns2 )
602+
603+ # Table 3: Orphan table not annotating any elements
604+ obs3 = pd .DataFrame (
605+ {
606+ "cluster" : pd .Categorical (RNG .choice (["cluster_1" , "cluster_2" , "cluster_3" ], size = 40 )),
607+ "sample" : pd .Categorical (["sample_A" ] * 20 + ["sample_B" ] * 20 ),
608+ "qc_pass" : RNG .choice ([True , False ], p = [0.8 , 0.2 ], size = 40 ),
609+ }
610+ )
611+
612+ var3 = pd .DataFrame (
613+ {
614+ "feature_type" : pd .Categorical (["gene" , "protein" , "gene" , "protein" , "gene" ] * 2 ),
615+ "mean_expression" : RNG .uniform (0 , 20 , size = n_var ),
616+ "variance" : RNG .gamma (2 , 2 , size = n_var ),
617+ },
618+ index = [f"feature_{ i } " for i in range (n_var )],
619+ )
620+
621+ X3 = RNG .normal (size = (40 , n_var ))
622+ table3 = AnnData (X = X3 , obs = obs3 , var = var3 )
623+
624+ # Create additional coordinate system in one of the shapes for testing
625+ # Modified copy of circles with an additional coordinate system
626+ circles_alt_coords = shapes ["circles" ].copy ()
627+ circles_alt_coords ["coordinate_system" ] = "alt_system"
628+
629+ # Add everything to a SpatialData object
630+ sdata = SpatialData (
631+ images = images ,
632+ labels = labels ,
633+ shapes = {** shapes , "circles_alt_coords" : circles_alt_coords },
634+ points = points ,
635+ tables = {"labels_table" : table1 , "shapes_table" : table2 , "orphan_table" : table3 },
636+ )
637+
638+ # Add layers to tables for testing layer-specific operations
639+ sdata .tables ["labels_table" ].layers ["scaled" ] = sdata .tables ["labels_table" ].X * 2
640+ sdata .tables ["labels_table" ].layers ["log" ] = np .log1p (np .abs (sdata .tables ["labels_table" ].X ))
641+
642+ return sdata
0 commit comments