5858 MODES ,
5959 SHAPE ,
6060 ZOOMS ,
61- Location ,
6261 _same_direction ,
6362 log ,
6463 logging ,
@@ -309,13 +308,22 @@ def _unpack(self):
309308 try :
310309 if self .__unpacked :
311310 return
312- if not self ._checked_dtype or self .seg :
313- dtype = _check_if_nifty_is_lying_about_its_dtype (self )
314- #print("unpack-nii",f"{self.seg=}",dtype)
315- self ._checked_dtype = True
316- self ._arr = np .asanyarray (self .nii .dataobj , dtype = dtype ).copy ()
317- else :
318- self ._arr = np .asanyarray (self .nii .dataobj , dtype = self .nii .dataobj .dtype ).copy () #type: ignore
311+ try :
312+ #if arr.dtype.fields is not None: # structured dtype (RGB)
313+ # arr = np.stack([arr[name] for name in arr.dtype.names], axis=-1)
314+ if not self ._checked_dtype or self .seg :
315+ dtype = _check_if_nifty_is_lying_about_its_dtype (self )
316+ #print("unpack-nii",f"{self.seg=}",dtype)
317+ self ._checked_dtype = True
318+ self ._arr = np .asanyarray (self .nii .dataobj , dtype = dtype ).copy ()
319+ else :
320+ self ._arr = np .asanyarray (self .nii .dataobj , dtype = self .nii .dataobj .dtype ).copy () #type: ignore
321+ except np .exceptions .DTypePromotionError :
322+ arr = np .asarray (self .nii .dataobj )
323+ if arr .dtype .fields is not None : # structured dtype (RGB)
324+ self ._arr = np .stack ([arr [name ] for name in arr .dtype .names ], axis = - 1 )
325+ else :
326+ raise np .exceptions .DTypePromotionError (f"The DTypes <class '{ self .nii .dataobj .dtype } '> do not have a common numerical DType. { np .asarray (self .nii .dataobj )} " ) from None
319327
320328 self ._aff = self .nii .affine
321329 self ._header :Nifti1Header = self .nii .header # type: ignore
@@ -779,9 +787,11 @@ def pad_to(self,target_shape:list[int]|tuple[int,int,int] | Self, mode:MODES="co
779787 s = s .apply_crop (tuple (crop ),inplace = inplace )
780788 return s .apply_pad (padding ,inplace = inplace ,mode = mode )
781789
782- def apply_pad (self ,padd :Sequence [tuple [int | None ,int ]],mode :MODES = "constant" ,inplace = False ,verbose :logging = True ):
790+ def apply_pad (self ,padd :Sequence [tuple [int | None ,int ]]| None ,mode :MODES = "constant" ,inplace = False ,verbose :logging = True ):
783791 #TODO add other modes
784792 #TODO add testcases and options for modes
793+ if padd is None :
794+ return self if inplace else self .copy ()
785795 transform = np .eye (self .dims + 1 , dtype = int )
786796 assert len (padd ) == self .dims
787797 for i , (before ,_ ) in enumerate (padd ):
@@ -1518,7 +1528,9 @@ def is_segmentation_in_border(self,minimum=0, voxel_tolerance: int = 2,use_mm=Fa
15181528 Returns:
15191529 - bool: True if the segmentation is within the defined voxel tolerance of the border, False otherwise.
15201530 """
1521- slices = self .compute_crop (minimum ,dist = 0 ,use_mm = use_mm )
1531+ slices = self .compute_crop (minimum ,dist = 0 ,use_mm = use_mm ,raise_error = False )
1532+ if slices is None :
1533+ return False
15221534 shp = self .shape
15231535 seg_at_border = False
15241536 for d in range (3 ):
@@ -1567,7 +1579,7 @@ def truncate_labels_beyond_reference_(
15671579 if len (threshold [axis_ ]) == 0 :
15681580 return self if inplace else self .copy ()
15691581 flip_up = flip
1570- if inclusion :
1582+ if not inclusion :
15711583 flip_up = not flip_up
15721584 # Determine the lowest index along the axis
15731585 limit = threshold [axis_ ].min () if flip_up else threshold [axis_ ].max ()
@@ -1593,11 +1605,12 @@ def truncate_labels_beyond_reference(
15931605 not_beyond : int | list [int ] = 1 ,
15941606 fill : int = 0 ,
15951607 axis : DIRECTIONS = "S" ,
1596- inclusion : bool = False
1608+ inclusion : bool = False ,
1609+ inplace = False
15971610 ):
1598- return self .truncate_labels_beyond_reference_ (idx ,not_beyond ,fill ,axis ,inclusion )
1611+ return self .truncate_labels_beyond_reference_ (idx ,not_beyond ,fill ,axis ,inclusion , inplace = inplace )
15991612
1600- def infect (self : NII , reference_mask : NII , inplace = False ,verbose = True ,axis :int | str | None = None ):
1613+ def infect (self : NII , reference_mask : NII , inplace = False ,verbose = True ,axis :int | str | None = None , max_depth = None ):
16011614 """
16021615 Expands labels from self_mask into regions of reference_mask == 1 via breadth-first diffusion.
16031616
@@ -1633,7 +1646,7 @@ def infect(self: NII, reference_mask: NII, inplace=False,verbose=True,axis:int|s
16331646
16341647 search = []
16351648 coords = np .where (self_mask != 0 )
1636- def _add_idx (x ,y ,z ,v ):
1649+ def _add_idx (x ,y ,z ,v , d ):
16371650 for x1 ,y1 ,z1 in kernel :
16381651 a = x + x1
16391652 b = y + y1
@@ -1644,27 +1657,29 @@ def _add_idx(x,y,z,v):
16441657 continue
16451658 #try:
16461659 if searched [a ,b ,c ] == 0 and ref_mask [a ,b ,c ] == 1 :
1647- search .append ((a ,b ,c ,v ))
1660+ search .append ((a ,b ,c ,v , d ))
16481661 #except Exception:
16491662 # pass
1650- def _infect (a ,b ,c ,v ):
1663+ def _infect (a ,b ,c ,v ,d ):
1664+ if d - 1 == max_depth :
1665+ return
16511666 if searched [a ,b ,c ] != 0 :
16521667 return
16531668 if ref_mask [a ,b ,c ] == 0 :
16541669 return
16551670 #print(a,b,c)
16561671 searched [a ,b ,c ] = 1
16571672 self_mask [a ,b ,c ] = v
1658- _add_idx (x ,y ,z ,v )
1673+ _add_idx (x ,y ,z ,v , d )
16591674
16601675 from tqdm import tqdm
16611676 for x ,y ,z in tqdm (zip (coords [0 ],coords [1 ],coords [2 ]),total = len (coords [0 ]),disable = not verbose ,desc = "Collecting Surface" ):
1662- _add_idx (x ,y ,z ,self_mask [x ,y ,z ])
1677+ _add_idx (x ,y ,z ,self_mask [x ,y ,z ], 0 )
16631678 while len (search ) != 0 :
16641679 search2 = search
16651680 search = []
1666- for x ,y ,z ,v in tqdm (search2 ,disable = not verbose ,desc = "infect" ):
1667- _infect (x ,y ,z ,v )
1681+ for x ,y ,z ,v , d in tqdm (search2 ,disable = not verbose ,desc = "infect" ):
1682+ _infect (x ,y ,z ,v , d + 1 )
16681683 self_mask [self_mask == 0 ] = self_mask_org [self_mask == 0 ]
16691684 return self .set_array (self_mask ,inplace = inplace )
16701685
@@ -1897,9 +1912,9 @@ def extract_label(self,label:int|Enum|Sequence[int]|Sequence[Enum]|None, keep_la
18971912 if keep_label :
18981913 seg_arr = seg_arr * self .get_seg_array ()
18991914 return self .set_array (seg_arr ,inplace = inplace )
1900- def extract_label_ (self ,label :int | Location | Sequence [int ]| Sequence [Location ], keep_label = False ):
1915+ def extract_label_ (self ,label :int | Enum | Sequence [int ]| Sequence [Enum ], keep_label = False ):
19011916 return self .extract_label (label ,keep_label ,inplace = True )
1902- def remove_labels (self ,label :int | Location | Sequence [int ]| Sequence [Location ], inplace = False , verbose :logging = True , removed_to_label = 0 ):
1917+ def remove_labels (self ,label :int | Enum | Sequence [int ]| Sequence [Enum ], inplace = False , verbose :logging = True , removed_to_label = 0 ):
19031918 '''If this NII is a segmentation you can single out one label.'''
19041919 assert label != 0 , 'Zero label does not make sens. This is the background'
19051920 seg_arr = self .get_seg_array ()
@@ -1912,18 +1927,25 @@ def remove_labels(self,label:int|Location|Sequence[int]|Sequence[Location], inpl
19121927 else :
19131928 seg_arr [seg_arr == l ] = removed_to_label
19141929 return self .set_array (seg_arr ,inplace = inplace , verbose = verbose )
1915- def remove_labels_ (self ,label :int | Location | Sequence [int ]| Sequence [Location ] , verbose :logging = True ):
1916- return self .remove_labels (label ,inplace = True ,verbose = verbose )
1930+ def remove_labels_ (self ,label :int | Enum | Sequence [int ]| Sequence [Enum ], removed_to_label = 0 , verbose :logging = True ):
1931+ return self .remove_labels (label ,inplace = True ,removed_to_label = removed_to_label , verbose = verbose )
19171932 def apply_mask (self ,mask :Self , inplace = False ):
19181933 assert mask .shape == self .shape , f"[def apply_mask] Mask and Shape are not equal: \n Mask - { mask } ,\n Self - { self } )"
19191934 seg_arr = mask .get_seg_array ()
19201935 seg_arr [seg_arr != 0 ] = 1
19211936 arr = self .get_array ()
19221937 return self .set_array (arr * seg_arr ,inplace = inplace )
19231938
1924- def unique (self ,verbose :logging = False ):
1939+ def unique (self ,verbose :logging = False , crop = False ):
19251940 '''Returns all integer labels WITHOUT 0. Must be performed only on a segmentation nii'''
1926- out = np_unique_withoutzero (self .get_seg_array ())
1941+
1942+ arr = self .get_seg_array ()
1943+ if crop :
1944+ try :
1945+ arr = arr [np_bbox_binary (arr )]
1946+ except Exception :
1947+ pass
1948+ out = np_unique_withoutzero (arr )
19271949 log .print (out ,verbose = verbose )
19281950 return out
19291951 def voxel_volume (self ):
0 commit comments