@@ -395,185 +395,225 @@ def __setitem__(self, index, value):
395395 def __len__ (self ):
396396 return self .shape ()[0 ]
397397
398+ def _validate_arithmetic_operands (self , other , operation_name ):
399+ """Validate operand types for arithmetic operations.
400+
401+ Args:
402+ other: The other operand
403+ operation_name: Name of the operation (e.g., 'add', 'multiply')
404+
405+ Returns:
406+ str: Type of operand ('scalar', 'brain_data', or 'array')
407+
408+ Raises:
409+ ValueError: If operand type is not supported
410+ """
411+ if isinstance (other , (int , np .integer , float , np .floating )):
412+ return "scalar"
413+ elif isinstance (other , Brain_Data ):
414+ return "brain_data"
415+ elif isinstance (other , (list , np .ndarray )) and operation_name == "multiply" :
416+ return "array"
417+ else :
418+ valid_types = "int, float, or Brain_Data"
419+ if operation_name == "multiply" :
420+ valid_types = "int, float, list, np.ndarray, or Brain_Data"
421+ raise ValueError (
422+ f"Can only { operation_name } { valid_types } . Provided { type (other )} "
423+ )
424+
425+ def _check_shape_compatibility (self , other , operation_name ):
426+ """Check shape compatibility between two Brain_Data objects.
427+
428+ Args:
429+ other: The other Brain_Data object
430+ operation_name: Name of the operation for error messages
431+
432+ Raises:
433+ ValueError: If shapes are incompatible
434+ """
435+ self_shape , other_shape = self .shape (), other .shape ()
436+ self_is_single = len (self_shape ) == 1
437+ other_is_single = len (other_shape ) == 1
438+
439+ if self_is_single and other_is_single :
440+ if self_shape [0 ] != other_shape [0 ]:
441+ raise ValueError ("Both images must have the same number of voxels" )
442+ elif self_is_single and not other_is_single :
443+ raise ValueError (
444+ f"Cannot { operation_name } multiple images to a single image"
445+ )
446+ elif not self_is_single and other_is_single :
447+ if self_shape [1 ] != other_shape [0 ]:
448+ raise ValueError ("Both images must have the same number of voxels" )
449+ elif not self_is_single and not other_is_single :
450+ if self_shape [0 ] != other_shape [0 ] or self_shape [1 ] != other_shape [1 ]:
451+ raise ValueError (
452+ f"Cannot { operation_name } multiple images of a different shape"
453+ )
454+
398455 def __add__ (self , y ):
399456 new = deepcopy (self )
400- if isinstance (y , (int , np .integer , float , np .floating )):
457+ operand_type = self ._validate_arithmetic_operands (y , "add" )
458+
459+ if operand_type == "scalar" :
401460 new .data = new .data + y
402- elif isinstance (y , Brain_Data ):
403- self_shape , y_shape = self .shape (), y .shape ()
404- self_is_single = len (self_shape ) == 1
405- y_is_single = len (y_shape ) == 1
406-
407- if self_is_single and y_is_single :
408- if self_shape [0 ] != y_shape [0 ]:
409- raise ValueError ("Both images must have the same number of voxels" )
410- elif self_is_single and not y_is_single :
411- raise ValueError ("Cannot add multiple images to a single image" )
412- elif not self_is_single and y_is_single :
413- if self_shape [1 ] != y_shape [0 ]:
414- raise ValueError ("Both images must have the same number of voxels" )
415- elif not self_is_single and not y_is_single :
416- if self_shape [0 ] != y_shape [0 ] or self_shape [1 ] != y_shape [1 ]:
417- raise ValueError ("Cannot add multiple images of a different shape" )
418- else :
419- raise ValueError (f"Data shape mismatch { self_shape } and { y_shape } " )
420- else :
421- raise ValueError ("Can only add int, float, or Brain_Data" )
461+ elif operand_type == "brain_data" :
462+ self ._check_shape_compatibility (y , "add" )
463+ new .data = new .data + y .data
464+
422465 return new
423466
424467 def __radd__ (self , y ):
425468 new = deepcopy (self )
426- if isinstance (y , (int , np .integer , float , np .floating )):
469+ operand_type = self ._validate_arithmetic_operands (y , "add" )
470+
471+ if operand_type == "scalar" :
427472 new .data = y + new .data
428- elif isinstance (y , Brain_Data ):
429- self_shape , y_shape = self .shape (), y .shape ()
430- self_is_single = len (self_shape ) == 1
431- y_is_single = len (y_shape ) == 1
432-
433- if self_is_single and y_is_single :
434- if self_shape [0 ] != y_shape [0 ]:
435- raise ValueError ("Both images must have the same number of voxels" )
436- elif self_is_single and not y_is_single :
437- raise ValueError ("Cannot add multiple images to a single image" )
438- elif not self_is_single and y_is_single :
439- if self_shape [1 ] != y_shape [0 ]:
440- raise ValueError ("Both images must have the same number of voxels" )
441- elif not self_is_single and not y_is_single :
442- if self_shape [0 ] != y_shape [0 ] or self_shape [1 ] != y_shape [1 ]:
443- raise ValueError ("Cannot add multiple images of a different shape" )
444- else :
445- raise ValueError (f"Data shape mismatch { self_shape } and { y_shape } " )
446- else :
447- raise ValueError ("Can only add int, float, or Brain_Data" )
448- new .data = y + new .data
473+ elif operand_type == "brain_data" :
474+ self ._check_shape_compatibility (y , "add" )
475+ new .data = y .data + new .data
476+
449477 return new
450478
451479 def __sub__ (self , y ):
452480 new = deepcopy (self )
453- if isinstance (y , (int , np .integer , float , np .floating )):
481+ operand_type = self ._validate_arithmetic_operands (y , "subtract" )
482+
483+ if operand_type == "scalar" :
454484 new .data = new .data - y
455- elif isinstance (y , Brain_Data ):
456- self_shape , y_shape = self .shape (), y .shape ()
457- self_is_single = len (self_shape ) == 1
458- y_is_single = len (y_shape ) == 1
459-
460- if self_is_single and y_is_single :
461- if self_shape [0 ] != y_shape [0 ]:
462- raise ValueError ("Both images must have the same number of voxels" )
463- elif self_is_single and not y_is_single :
464- raise ValueError ("Cannot subtract multiple images from a single image" )
465- elif not self_is_single and y_is_single :
466- if self_shape [1 ] != y_shape [0 ]:
467- raise ValueError ("Both images must have the same number of voxels" )
468- elif not self_is_single and not y_is_single :
469- if self_shape [0 ] != y_shape [0 ] or self_shape [1 ] != y_shape [1 ]:
470- raise ValueError (
471- "Cannot subtract multiple images from multiple images of a different shape"
472- )
473- else :
474- raise ValueError (f"Data shape mismatch { self_shape } and { y_shape } " )
475- else :
476- raise ValueError ("Can only add int, float, or Brain_Data" )
477- new .data = new .data - y .data
485+ elif operand_type == "brain_data" :
486+ self ._check_shape_compatibility (y , "subtract" )
487+ new .data = new .data - y .data
488+
478489 return new
479490
480491 def __rsub__ (self , y ):
481492 new = deepcopy (self )
482- if isinstance (y , (int , np .integer , float , np .floating )):
493+ operand_type = self ._validate_arithmetic_operands (y , "subtract" )
494+
495+ if operand_type == "scalar" :
483496 new .data = y - new .data
484- elif isinstance (y , Brain_Data ):
485- if self .shape () != y .shape ():
486- raise ValueError (
487- "Both Brain_Data() instances need to be the same shape."
488- )
497+ elif operand_type == "brain_data" :
498+ self ._check_shape_compatibility (y , "subtract" )
489499 new .data = y .data - new .data
490- else :
491- raise ValueError ("Can only add int, float, or Brain_Data" )
500+
492501 return new
493502
494503 def __mul__ (self , y ):
495504 new = deepcopy (self )
496- if isinstance (y , (int , np .integer , float , np .floating )):
505+ operand_type = self ._validate_arithmetic_operands (y , "multiply" )
506+
507+ if operand_type == "scalar" :
497508 new .data = new .data * y
498- elif isinstance (y , Brain_Data ):
499- self_shape , y_shape = self .shape (), y .shape ()
500- self_is_single = len (self_shape ) == 1
501- y_is_single = len (y_shape ) == 1
502-
503- if self_is_single and y_is_single :
504- if self_shape [0 ] != y_shape [0 ]:
505- raise ValueError ("Both images must have the same number of voxels" )
506- elif self_is_single and not y_is_single :
507- raise ValueError ("Cannot multiply multiple images by a single image" )
508- elif not self_is_single and y_is_single :
509- if self_shape [1 ] != y_shape [0 ]:
510- raise ValueError ("Both images must have the same number of voxels" )
511- elif not self_is_single and not y_is_single :
512- if self_shape [0 ] != y_shape [0 ] or self_shape [1 ] != y_shape [1 ]:
513- raise ValueError (
514- "Cannot multiply multiple images by multiple images of a different shape"
515- )
516- else :
517- raise ValueError (f"Data shape mismatch { self_shape } and { y_shape } " )
509+ elif operand_type == "brain_data" :
510+ self ._check_shape_compatibility (y , "multiply" )
518511 new .data = np .multiply (new .data , y .data )
519- elif isinstance ( y , ( list , np . ndarray )) :
512+ elif operand_type == "array" :
520513 if len (y ) != len (self ):
521514 raise ValueError (
522515 "Vector multiplication requires that the "
523516 "length of the vector match the number of "
524517 "images in Brain_Data instance."
525518 )
526- else :
527- new .data = np .dot (new .data .T , y ).T
528- else :
529- raise ValueError ("Can only multiply int, float, list, or Brain_Data" )
519+ new .data = np .dot (new .data .T , y ).T
520+
530521 return new
531522
532523 def __rmul__ (self , y ):
533524 new = deepcopy (self )
534- if isinstance (y , (int , np .integer , float , np .floating )):
525+ operand_type = self ._validate_arithmetic_operands (y , "multiply" )
526+
527+ if operand_type == "scalar" :
535528 new .data = y * new .data
536- elif isinstance (y , Brain_Data ):
537- if self .shape () != y .shape ():
529+ elif operand_type == "brain_data" :
530+ self ._check_shape_compatibility (y , "multiply" )
531+ new .data = np .multiply (y .data , new .data )
532+ elif operand_type == "array" :
533+ # For right multiplication with array, it's typically not supported
534+ # but we'll keep consistent behavior
535+ if len (y ) != len (self ):
538536 raise ValueError (
539- "Both Brain_Data() instances need to be the same shape."
537+ "Vector multiplication requires that the "
538+ "length of the vector match the number of "
539+ "images in Brain_Data instance."
540540 )
541- new .data = np .multiply (y .data , new .data )
542- else :
543- raise ValueError ("Can only multiply int, float, or Brain_Data" )
541+ new .data = np .dot (new .data .T , y ).T
542+
544543 return new
545544
546545 def __truediv__ (self , y ):
547546 new = deepcopy (self )
548- if isinstance (y , (int , np .integer , float , np .floating )):
547+ operand_type = self ._validate_arithmetic_operands (y , "divide" )
548+
549+ if operand_type == "scalar" :
549550 with np .errstate (invalid = "ignore" , divide = "ignore" ):
550551 new .data = new .data / y
551- elif isinstance (y , Brain_Data ):
552- self_shape , y_shape = self .shape (), y .shape ()
553- self_is_single = len (self_shape ) == 1
554- y_is_single = len (y_shape ) == 1
555-
556- if self_is_single and y_is_single :
557- if self_shape [0 ] != y_shape [0 ]:
558- raise ValueError ("Both images must have the same number of voxels" )
559- elif self_is_single and not y_is_single :
560- raise ValueError ("Cannot divide a single image by multiple images" )
561- elif not self_is_single and y_is_single :
562- if self_shape [1 ] != y_shape [0 ]:
563- raise ValueError ("Both images must have the same number of voxels" )
564- elif not self_is_single and not y_is_single :
565- if self_shape [0 ] != y_shape [0 ] or self_shape [1 ] != y_shape [1 ]:
566- raise ValueError (
567- "Cannot divide multiple images by multiple images of a different shape"
568- )
569- else :
570- raise ValueError (f"Data shape mismatch { self_shape } and { y_shape } " )
552+ elif operand_type == "brain_data" :
553+ self ._check_shape_compatibility (y , "divide" )
571554 with np .errstate (invalid = "ignore" , divide = "ignore" ):
572555 new .data = np .divide (new .data , y .data )
573- else :
574- raise ValueError ("Can only divide int, float, list, or Brain_Data" )
556+
575557 return new
576558
559+ def __iadd__ (self , y ):
560+ """In-place addition (+=)."""
561+ operand_type = self ._validate_arithmetic_operands (y , "add" )
562+
563+ if operand_type == "scalar" :
564+ self .data = self .data + y
565+ elif operand_type == "brain_data" :
566+ self ._check_shape_compatibility (y , "add" )
567+ self .data = self .data + y .data
568+
569+ return self
570+
571+ def __isub__ (self , y ):
572+ """In-place subtraction (-=)."""
573+ operand_type = self ._validate_arithmetic_operands (y , "subtract" )
574+
575+ if operand_type == "scalar" :
576+ self .data = self .data - y
577+ elif operand_type == "brain_data" :
578+ self ._check_shape_compatibility (y , "subtract" )
579+ self .data = self .data - y .data
580+
581+ return self
582+
583+ def __imul__ (self , y ):
584+ """In-place multiplication (*=)."""
585+ operand_type = self ._validate_arithmetic_operands (y , "multiply" )
586+
587+ if operand_type == "scalar" :
588+ self .data = self .data * y
589+ elif operand_type == "brain_data" :
590+ self ._check_shape_compatibility (y , "multiply" )
591+ self .data = np .multiply (self .data , y .data )
592+ elif operand_type == "array" :
593+ if len (y ) != len (self ):
594+ raise ValueError (
595+ "Vector multiplication requires that the "
596+ "length of the vector match the number of "
597+ "images in Brain_Data instance."
598+ )
599+ self .data = np .dot (self .data .T , y ).T
600+
601+ return self
602+
603+ def __itruediv__ (self , y ):
604+ """In-place true division (/=)."""
605+ operand_type = self ._validate_arithmetic_operands (y , "divide" )
606+
607+ if operand_type == "scalar" :
608+ with np .errstate (invalid = "ignore" , divide = "ignore" ):
609+ self .data = self .data / y
610+ elif operand_type == "brain_data" :
611+ self ._check_shape_compatibility (y , "divide" )
612+ with np .errstate (invalid = "ignore" , divide = "ignore" ):
613+ self .data = np .divide (self .data , y .data )
614+
615+ return self
616+
577617 def __iter__ (self ):
578618 for x in range (len (self )):
579619 yield self [x ]
0 commit comments