@propertydefinput_units(self):ifself.x_mean.unitisNoneandself.y_mean.unitisNone:returnNoneelse:return{'x':self.x_mean.unit,'y':self.y_mean.unit}def_parameter_units_for_data_units(self,inputs_unit,outputs_unit):# Note that here we need to make sure that x and y are in the same# units otherwise this can lead to issues since rotation is not well# defined.ifinputs_unit['x']!=inputs_unit['y']:raiseUnitsError("Units of 'x' and 'y' inputs should match")returnOrderedDict([('x_mean',inputs_unit['x']),('y_mean',inputs_unit['x']),('x_stddev',inputs_unit['x']),('y_stddev',inputs_unit['x']),('theta',u.rad),('amplitude',outputs_unit['z'])])

[docs]classScale(Fittable1DModel):""" Multiply a model by a dimensionless factor. Parameters ---------- factor : float Factor by which to scale a coordinate. Notes ----- If ``factor`` is a `~astropy.units.Quantity` then the units will be stripped before the scaling operation. """inputs=('x',)outputs=('x',)factor=Parameter(default=1)linear=Truefittable=True_input_units_strict=True_input_units_allow_dimensionless=True@propertydefinput_units(self):ifself.factor.unitisNone:returnNoneelse:return{'x':self.factor.unit}@propertydefinverse(self):"""One dimensional inverse Scale model function"""inv=self.copy()inv.factor=1/self.factorreturninv

[docs]@staticmethoddefevaluate(x,amplitude,frequency,phase):"""One dimensional Sine model function"""# Note: If frequency and x are quantities, they should normally have# inverse units, so that argument ends up being dimensionless. However,# np.sin of a dimensionless quantity will crash, so we remove the# quantity-ness from argument in this case (another option would be to# multiply by * u.rad but this would be slower overall).argument=TWOPI*(frequency*x+phase)ifisinstance(argument,Quantity):argument=argument.valuereturnamplitude*np.sin(argument)

defbounding_box(self,factor=25):"""Tuple defining the default ``bounding_box`` limits, ``(x_low, x_high)``. Parameters ---------- factor : float The multiple of FWHM used to define the limits. Default is chosen to include most (99%) of the area under the curve, while still showing the central feature of interest. """x0=self.x_0dx=factor*self.fwhmreturn(x0-dx,x0+dx)@propertydefinput_units(self):ifself.x_0.unitisNone:returnNoneelse:return{'x':self.x_0.unit}def_parameter_units_for_data_units(self,inputs_unit,outputs_unit):returnOrderedDict([('x_0',inputs_unit['x']),('fwhm',inputs_unit['x']),('amplitude',outputs_unit['y'])])

[docs]@staticmethoddefevaluate(x,amplitude):"""One dimensional Constant model function"""ifamplitude.size==1:# This is slightly faster than using ones_like and multiplyingx=np.empty_like(x,subok=False)x.fill(amplitude.item())else:# This case is less likely but could occur if the amplitude# parameter is given an array-like valuex=amplitude*np.ones_like(x,subok=False)ifisinstance(amplitude,Quantity):returnQuantity(x,unit=amplitude.unit,copy=False)else:returnx

[docs]@staticmethoddefevaluate(x,y,amplitude):"""Two dimensional Constant model function"""ifamplitude.size==1:# This is slightly faster than using ones_like and multiplyingx=np.empty_like(x,subok=False)x.fill(amplitude.item())else:# This case is less likely but could occur if the amplitude# parameter is given an array-like valuex=amplitude*np.ones_like(x,subok=False)ifisinstance(amplitude,Quantity):returnQuantity(x,unit=amplitude.unit,copy=False)else:returnx

@propertydefbounding_box(self):""" Tuple defining the default ``bounding_box`` limits. ``((y_low, y_high), (x_low, x_high))`` """a=self.ab=self.btheta=self.theta.valuedx,dy=ellipse_extent(a,b,theta)return((self.y_0-dy,self.y_0+dy),(self.x_0-dx,self.x_0+dx))@propertydefinput_units(self):ifself.x_0.unitisNone:returnNoneelse:return{'x':self.x_0.unit,'y':self.y_0.unit}def_parameter_units_for_data_units(self,inputs_unit,outputs_unit):# Note that here we need to make sure that x and y are in the same# units otherwise this can lead to issues since rotation is not well# defined.ifinputs_unit['x']!=inputs_unit['y']:raiseUnitsError("Units of 'x' and 'y' inputs should match")returnOrderedDict([('x_0',inputs_unit['x']),('y_0',inputs_unit['x']),('a',inputs_unit['x']),('b',inputs_unit['x']),('theta',u.rad),('amplitude',outputs_unit['z'])])

@propertydefbounding_box(self):""" Tuple defining the default ``bounding_box`` limits. ``((y_low, y_high), (x_low, x_high))`` """return((self.y_0-self.R_0,self.y_0+self.R_0),(self.x_0-self.R_0,self.x_0+self.R_0))@propertydefinput_units(self):ifself.x_0.unitisNoneandself.y_0.unitisNone:returnNoneelse:return{'x':self.x_0.unit,'y':self.y_0.unit}def_parameter_units_for_data_units(self,inputs_unit,outputs_unit):# Note that here we need to make sure that x and y are in the same# units otherwise this can lead to issues since rotation is not well# defined.ifinputs_unit['x']!=inputs_unit['y']:raiseUnitsError("Units of 'x' and 'y' inputs should match")returnOrderedDict([('x_0',inputs_unit['x']),('y_0',inputs_unit['x']),('R_0',inputs_unit['x']),('amplitude',outputs_unit['z'])])

@propertydefbounding_box(self):""" Tuple defining the default ``bounding_box``. ``((y_low, y_high), (x_low, x_high))`` """dr=self.r_in+self.widthreturn((self.y_0-dr,self.y_0+dr),(self.x_0-dr,self.x_0+dr))@propertydefinput_units(self):ifself.x_0.unitisNone:returnNoneelse:return{'x':self.x_0.unit,'y':self.y_0.unit}def_parameter_units_for_data_units(self,inputs_unit,outputs_unit):# Note that here we need to make sure that x and y are in the same# units otherwise this can lead to issues since rotation is not well# defined.ifinputs_unit['x']!=inputs_unit['y']:raiseUnitsError("Units of 'x' and 'y' inputs should match")returnOrderedDict([('x_0',inputs_unit['x']),('y_0',inputs_unit['x']),('r_in',inputs_unit['x']),('width',inputs_unit['x']),('amplitude',outputs_unit['z'])])

[docs]classTrapezoidDisk2D(Fittable2DModel):""" Two dimensional circular Trapezoid model. Parameters ---------- amplitude : float Amplitude of the trapezoid x_0 : float x position of the center of the trapezoid y_0 : float y position of the center of the trapezoid R_0 : float Radius of the constant part of the trapezoid. slope : float Slope of the tails of the trapezoid in x direction. See Also -------- Disk2D, Box2D """amplitude=Parameter(default=1)x_0=Parameter(default=0)y_0=Parameter(default=0)R_0=Parameter(default=1)slope=Parameter(default=1)

@propertydefbounding_box(self):""" Tuple defining the default ``bounding_box``. ``((y_low, y_high), (x_low, x_high))`` """dr=self.R_0+self.amplitude/self.slopereturn((self.y_0-dr,self.y_0+dr),(self.x_0-dr,self.x_0+dr))@propertydefinput_units(self):ifself.x_0.unitisNoneandself.y_0.unitisNone:returnNoneelse:return{'x':self.x_0.unit,'y':self.y_0.unit}def_parameter_units_for_data_units(self,inputs_unit,outputs_unit):# Note that here we need to make sure that x and y are in the same# units otherwise this can lead to issues since rotation is not well# defined.ifinputs_unit['x']!=inputs_unit['y']:raiseUnitsError("Units of 'x' and 'y' inputs should match")returnOrderedDict([('x_0',inputs_unit['x']),('y_0',inputs_unit['x']),('R_0',inputs_unit['x']),('slope',outputs_unit['z']/inputs_unit['x']),('amplitude',outputs_unit['z'])])

@propertydefinput_units(self):ifself.x_0.unitisNone:returnNoneelse:return{'x':self.x_0.unit,'y':self.y_0.unit}def_parameter_units_for_data_units(self,inputs_unit,outputs_unit):# Note that here we need to make sure that x and y are in the same# units otherwise this can lead to issues since rotation is not well# defined.ifinputs_unit['x']!=inputs_unit['y']:raiseUnitsError("Units of 'x' and 'y' inputs should match")returnOrderedDict([('x_0',inputs_unit['x']),('y_0',inputs_unit['x']),('sigma',inputs_unit['x']),('amplitude',outputs_unit['z'])])

[docs]classAiryDisk2D(Fittable2DModel):""" Two dimensional Airy disk model. Parameters ---------- amplitude : float Amplitude of the Airy function. x_0 : float x position of the maximum of the Airy function. y_0 : float y position of the maximum of the Airy function. radius : float The radius of the Airy disk (radius of the first zero). See Also -------- Box2D, TrapezoidDisk2D, Gaussian2D Notes ----- Model formula: .. math:: f(r) = A \\left[\\frac{2 J_1(\\frac{\\pi r}{R/R_z})}{\\frac{\\pi r}{R/R_z}}\\right]^2 Where :math:`J_1` is the first order Bessel function of the first kind, :math:`r` is radial distance from the maximum of the Airy function (:math:`r = \\sqrt{(x - x_0)^2 + (y - y_0)^2}`), :math:`R` is the input ``radius`` parameter, and :math:`R_z = 1.2196698912665045`). For an optical system, the radius of the first zero represents the limiting angular resolution and is approximately 1.22 * lambda / D, where lambda is the wavelength of the light and D is the diameter of the aperture. See [1]_ for more details about the Airy disk. References ---------- .. [1] https://en.wikipedia.org/wiki/Airy_disk """amplitude=Parameter(default=1)x_0=Parameter(default=0)y_0=Parameter(default=0)radius=Parameter(default=1)_rz=None_j1=None

[docs]@classmethoddefevaluate(cls,x,y,amplitude,x_0,y_0,radius):"""Two dimensional Airy model function"""ifcls._rzisNone:try:fromscipy.specialimportj1,jn_zeroscls._rz=jn_zeros(1,1)[0]/np.picls._j1=j1exceptValueError:raiseImportError('AiryDisk2D model requires scipy > 0.11.')r=np.sqrt((x-x_0)**2+(y-y_0)**2)/(radius/cls._rz)ifisinstance(r,Quantity):# scipy function cannot handle Quantity, so turn into array.r=r.to_value(u.dimensionless_unscaled)# Since r can be zero, we have to take care to treat that case# separately so as not to raise a numpy warningz=np.ones(r.shape)rt=np.pi*r[r>0]z[r>0]=(2.0*cls._j1(rt)/rt)**2ifisinstance(amplitude,Quantity):# make z quantity too, otherwise in-place multiplication fails.z=Quantity(z,u.dimensionless_unscaled,copy=False)z*=amplitudereturnz

@propertydefinput_units(self):ifself.x_0.unitisNone:returnNoneelse:return{'x':self.x_0.unit,'y':self.y_0.unit}def_parameter_units_for_data_units(self,inputs_unit,outputs_unit):# Note that here we need to make sure that x and y are in the same# units otherwise this can lead to issues since rotation is not well# defined.ifinputs_unit['x']!=inputs_unit['y']:raiseUnitsError("Units of 'x' and 'y' inputs should match")returnOrderedDict([('x_0',inputs_unit['x']),('y_0',inputs_unit['x']),('radius',inputs_unit['x']),('amplitude',outputs_unit['z'])])

@propertydefinput_units(self):ifself.x_0.unitisNone:returnNoneelse:return{'x':self.x_0.unit,'y':self.y_0.unit}def_parameter_units_for_data_units(self,inputs_unit,outputs_unit):# Note that here we need to make sure that x and y are in the same# units otherwise this can lead to issues since rotation is not well# defined.ifinputs_unit['x']!=inputs_unit['y']:raiseUnitsError("Units of 'x' and 'y' inputs should match")returnOrderedDict([('x_0',inputs_unit['x']),('y_0',inputs_unit['x']),('gamma',inputs_unit['x']),('amplitude',outputs_unit['z'])])

@propertydefinput_units(self):ifself.x_0.unitisNone:returnNoneelse:return{'x':self.x_0.unit,'y':self.y_0.unit}def_parameter_units_for_data_units(self,inputs_unit,outputs_unit):# Note that here we need to make sure that x and y are in the same# units otherwise this can lead to issues since rotation is not well# defined.ifinputs_unit['x']!=inputs_unit['y']:raiseUnitsError("Units of 'x' and 'y' inputs should match")returnOrderedDict([('x_0',inputs_unit['x']),('y_0',inputs_unit['x']),('r_eff',inputs_unit['x']),('theta',u.rad),('amplitude',outputs_unit['z'])])