# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.""".. Image computation objects (see parent package :mod:`cdl.computation`)"""# pylint: disable=invalid-name # Allows short reference names like x, y, ...# MARK: Important note# --------------------# All `guidata.dataset.DataSet` classes must also be imported in the `cdl.param` module.from__future__importannotationsimportwarningsfromcollections.abcimportCallablefromtypingimportAny,Literalimportguidata.datasetasgdsimportnumpyasnpimportscipy.ndimageasspiimportscipy.signalasspsfromnumpyimportmafromplotpy.mathutils.geometryimportvector_rotation# Import as "csline" to avoid the function to be interpreted as a validation function# in the context of DataLab's validation process:fromplotpy.panels.csection.csitemimportcompute_line_sectionascslinefromskimageimportfiltersimportcdl.algorithms.imageasalgfromcdl.algorithms.datatypesimportclip_astype,is_integer_dtypefromcdl.computation.baseimport(ArithmeticParam,ClipParam,ConstantParam,FFTParam,GaussianParam,HistogramParam,MovingAverageParam,MovingMedianParam,NormalizeParam,SpectrumParam,calc_resultproperties,dst_11,dst_n1n,new_signal_result,)fromcdl.configimportConf,_fromcdl.objimport(BaseProcParam,ImageObj,ImageROI,ResultProperties,ResultShape,ROI2DParam,SignalObj,)VALID_DTYPES_STRLIST=ImageObj.get_valid_dtypenames()
[docs]defrestore_data_outside_roi(dst:ImageObj,src:ImageObj)->None:"""Restore data outside the Region Of Interest (ROI) of the input image after a computation, only if the input image has a ROI, and if the output image has the same ROI as the input image, and if the data types are compatible, and if the shapes are the same. Otherwise, do nothing. Args: dst: output image object src: input image object """ifsrc.maskdataisnotNoneanddst.maskdataisnotNone:if(np.array_equal(src.maskdata,dst.maskdata)and(dst.data.dtype==src.data.dtypeornotis_integer_dtype(dst.data.dtype))anddst.data.shape==src.data.shape):dst.data[src.maskdata]=src.data[src.maskdata]
[docs]classWrap11Func:"""Wrap a 1 array → 1 array function to produce a 1 image → 1 image function, which can be used inside DataLab's infrastructure to perform computations with :class:`cdl.core.gui.processor.image.ImageProcessor`. This wrapping mechanism using a class is necessary for the resulted function to be pickable by the ``multiprocessing`` module. The instance of this wrapper is callable and returns a :class:`cdl.obj.ImageObj` object. Example: >>> import numpy as np >>> from cdl.computation.image import Wrap11Func >>> import cdl.obj >>> def add_noise(data): ... return data + np.random.random(data.shape) >>> compute_add_noise = Wrap11Func(add_noise) >>> data= np.ones((100, 100)) >>> ima0 = cdl.obj.create_image("Example", data) >>> ima1 = compute_add_noise(ima0) Args: func: 1 array → 1 array function *args: Additional positional arguments to pass to the function **kwargs: Additional keyword arguments to pass to the function """def__init__(self,func:Callable,*args:Any,**kwargs:Any)->None:self.func=funcself.args=argsself.kwargs=kwargsself.__name__=func.__name__self.__doc__=func.__doc__self.__call__.__func__.__doc__=self.func.__doc__def__call__(self,src:ImageObj)->ImageObj:"""Compute the function on the input image and return the result image Args: src: input image object Returns: Output image object """suffix=", ".join([str(arg)forarginself.args]+[f"{k}={v}"fork,vinself.kwargs.items()ifvisnotNone])dst=dst_11(src,self.func.__name__,suffix)dst.data=self.func(src.data,*self.args,**self.kwargs)restore_data_outside_roi(dst,src)returndst
[docs]defdst_11_signal(src:ImageObj,name:str,suffix:str|None=None)->SignalObj:"""Create a result signal object, as returned by the callback function of the :func:`cdl.core.gui.processor.base.BaseProcessor.compute_11` method Args: src: input image object name: name of the processing function Returns: Output signal object """returnnew_signal_result(src,name,suffix,(src.xunit,src.zunit),(src.xlabel,src.zlabel))
# MARK: compute_n1 functions -----------------------------------------------------------# Functions with N input images and 1 output image# --------------------------------------------------------------------------------------# Those functions are perfoming a computation on N input images and return a single# output image. If we were only executing these functions locally, we would not need# to define them here, but since we are using the multiprocessing module, we need to# define them here so that they can be pickled and sent to the worker processes.# Also, we need to systematically return the output image object, even if it is already# modified in place, because the multiprocessing module will not be able to retrieve# the modified object from the worker processes.
[docs]defcompute_addition(dst:ImageObj,src:ImageObj)->ImageObj:"""Add **dst** and **src** images and return **dst** image modified in place Args: dst: output image object src: input image object Returns: Output image object (modified in place) """dst.data=np.add(dst.data,src.data,dtype=float)restore_data_outside_roi(dst,src)returndst
[docs]defcompute_product(dst:ImageObj,src:ImageObj)->ImageObj:"""Multiply **dst** and **src** images and return **dst** image modified in place Args: dst: output image object src: input image object Returns: Output image object (modified in place) """dst.data=np.multiply(dst.data,src.data,dtype=float)restore_data_outside_roi(dst,src)returndst
[docs]defcompute_addition_constant(src:ImageObj,p:ConstantParam)->ImageObj:"""Add **dst** and a constant value and return the new result image object Args: src: input image object p: constant value Returns: Result image object **src** + **p.value** (new object) """# For the addition of a constant value, we convert the constant value to the same# data type as the input image, for consistency.value=np.array(p.value).astype(dtype=src.data.dtype)dst=dst_11(src,"+",str(value))dst.data=np.add(src.data,value,dtype=float)restore_data_outside_roi(dst,src)returndst
[docs]defcompute_difference_constant(src:ImageObj,p:ConstantParam)->ImageObj:"""Subtract a constant value from an image and return the new result image object Args: src: input image object p: constant value Returns: Result image object **src** - **p.value** (new object) """# For the subtraction of a constant value, we convert the constant value to the same# data type as the input image, for consistency.value=np.array(p.value).astype(dtype=src.data.dtype)dst=dst_11(src,"-",str(value))dst.data=np.subtract(src.data,value,dtype=float)restore_data_outside_roi(dst,src)returndst
[docs]defcompute_product_constant(src:ImageObj,p:ConstantParam)->ImageObj:"""Multiply **dst** by a constant value and return the new result image object Args: src: input image object p: constant value Returns: Result image object **src** * **p.value** (new object) """# For the multiplication by a constant value, we do not convert the constant value# to the same data type as the input image, because we want to allow the user to# multiply an image by a constant value of a different data type. The final data# type conversion ensures that the output image has the same data type as the input# image.dst=dst_11(src,"×",str(p.value))dst.data=np.multiply(src.data,p.value,dtype=float)restore_data_outside_roi(dst,src)returndst
[docs]defcompute_division_constant(src:ImageObj,p:ConstantParam)->ImageObj:"""Divide an image by a constant value and return the new result image object Args: src: input image object p: constant value Returns: Result image object **src** / **p.value** (new object) """# For the division by a constant value, we do not convert the constant value to the# same data type as the input image, because we want to allow the user to divide an# image by a constant value of a different data type. The final data type conversion# ensures that the output image has the same data type as the input image.dst=dst_11(src,"/",str(p.value))dst.data=np.divide(src.data,p.value,dtype=float)restore_data_outside_roi(dst,src)returndst
# MARK: compute_n1n functions ----------------------------------------------------------# Functions with N input images + 1 input image and N output images# --------------------------------------------------------------------------------------
[docs]defcompute_arithmetic(src1:ImageObj,src2:ImageObj,p:ArithmeticParam)->ImageObj:"""Compute arithmetic operation on two images Args: src1: input image object src2: input image object p: arithmetic parameters Returns: Result image object """initial_dtype=src1.data.dtypetitle=p.operation.replace("obj1",src1.short_id).replace("obj2",src2.short_id)dst=src1.copy(title=title)ifnotConf.proc.keep_results.get():dst.delete_results()# Remove any previous resultso,a,b=p.operator,p.factor,p.constant# Apply operatorifoin("×","/")anda==0.0:dst.data=np.ones_like(src1.data)*belifo=="+":dst.data=np.add(src1.data,src2.data,dtype=float)*a+belifo=="-":dst.data=np.subtract(src1.data,src2.data,dtype=float)*a+belifo=="×":dst.data=np.multiply(src1.data,src2.data,dtype=float)*a+belifo=="/":dst.data=np.divide(src1.data,src2.data,dtype=float)*a+b# Eventually convert to initial data typeifp.restore_dtype:dst.data=clip_astype(dst.data,initial_dtype)restore_data_outside_roi(dst,src1)returndst
[docs]defcompute_difference(src1:ImageObj,src2:ImageObj)->ImageObj:"""Compute difference between two images Args: src1: input image object src2: input image object Returns: Result image object **src1** - **src2** (new object) """dst=dst_n1n(src1,src2,"-")dst.data=np.subtract(src1.data,src2.data,dtype=float)restore_data_outside_roi(dst,src1)returndst
[docs]defcompute_quadratic_difference(src1:ImageObj,src2:ImageObj)->ImageObj:"""Compute quadratic difference between two images Args: src1: input image object src2: input image object Returns: Result image object (**src1** - **src2**) / sqrt(2.0) (new object) """dst=dst_n1n(src1,src2,"quadratic_difference")dst.data=np.subtract(src1.data,src2.data,dtype=float)/np.sqrt(2.0)restore_data_outside_roi(dst,src1)returndst
[docs]defcompute_division(src1:ImageObj,src2:ImageObj)->ImageObj:"""Compute division between two images Args: src1: input image object src2: input image object Returns: Result image object **src1** / **src2** (new object) """dst=dst_n1n(src1,src2,"/")dst.data=np.divide(src1.data,src2.data,dtype=float)restore_data_outside_roi(dst,src1)returndst
[docs]defcompute_flatfield(src1:ImageObj,src2:ImageObj,p:FlatFieldParam)->ImageObj:"""Compute flat field correction with :py:func:`cdl.algorithms.image.flatfield` Args: src1: raw data image object src2: flat field image object p: flat field parameters Returns: Output image object """dst=dst_n1n(src1,src2,"flatfield",f"threshold={p.threshold}")dst.data=alg.flatfield(src1.data,src2.data,p.threshold)restore_data_outside_roi(dst,src1)returndst
# MARK: compute_11 functions -----------------------------------------------------------# Functions with 1 input image and 1 output image# --------------------------------------------------------------------------------------
[docs]defcompute_normalize(src:ImageObj,p:NormalizeParam)->ImageObj:""" Normalize image data depending on its maximum, with :py:func:`cdl.algorithms.image.normalize` Args: src: input image object Returns: Output image object """dst=dst_11(src,"normalize",suffix=f"ref={p.method}")dst.data=alg.normalize(src.data,p.method)# type: ignorerestore_data_outside_roi(dst,src)returndst
[docs]classRotateParam(gds.DataSet):"""Rotate parameters"""boundaries=("constant","nearest","reflect","wrap")prop=gds.ValueProp(False)angle=gds.FloatItem(f"{_('Angle')} (°)")mode=gds.ChoiceItem(_("Mode"),list(zip(boundaries,boundaries)),default=boundaries[0])cval=gds.FloatItem(_("cval"),default=0.0,help=_("Value used for points outside the ""boundaries of the input if mode is ""'constant'"),)reshape=gds.BoolItem(_("Reshape the output array"),default=False,help=_("Reshape the output array ""so that the input array is ""contained completely in the output"),)prefilter=gds.BoolItem(_("Prefilter the input image"),default=True).set_prop("display",store=prop)order=gds.IntItem(_("Order"),default=3,min=0,max=5,help=_("Spline interpolation order"),).set_prop("display",active=prop)
[docs]defrotate_obj_coords(angle:float,obj:ImageObj,orig:ImageObj,coords:np.ndarray)->None:"""Apply rotation to coords associated to image obj Args: angle: rotation angle (in degrees) obj: image object orig: original image object coords: coordinates to rotate Returns: Output data """forrowinrange(coords.shape[0]):forcolinrange(0,coords.shape[1],2):x1,y1=coords[row,col:col+2]dx1=x1-orig.xcdy1=y1-orig.ycdx2,dy2=vector_rotation(-angle*np.pi/180.0,dx1,dy1)coords[row,col:col+2]=dx2+obj.xc,dy2+obj.ycobj.roi=None
[docs]defrotate_obj_alpha(obj:ImageObj,orig:ImageObj,coords:np.ndarray,p:RotateParam)->None:"""Apply rotation to coords associated to image obj"""rotate_obj_coords(p.angle,obj,orig,coords)
[docs]defcompute_rotate(src:ImageObj,p:RotateParam)->ImageObj:"""Rotate data with :py:func:`scipy.ndimage.rotate` Args: src: input image object p: parameters Returns: Output image object """dst=dst_11(src,"rotate",f"α={p.angle:.3f}°, mode='{p.mode}'")dst.data=spi.rotate(src.data,p.angle,reshape=p.reshape,order=p.order,mode=p.mode,cval=p.cval,prefilter=p.prefilter,)dst.transform_shapes(src,rotate_obj_alpha,p)returndst
[docs]defrotate_obj_90(dst:ImageObj,src:ImageObj,coords:np.ndarray)->None:"""Apply rotation to coords associated to image obj"""rotate_obj_coords(90.0,dst,src,coords)
[docs]defcompute_rotate90(src:ImageObj)->ImageObj:"""Rotate data 90° with :py:func:`numpy.rot90` Args: src: input image object Returns: Output image object """dst=dst_11(src,"rotate90")dst.data=np.rot90(src.data)dst.transform_shapes(src,rotate_obj_90)returndst
[docs]defrotate_obj_270(dst:ImageObj,src:ImageObj,coords:np.ndarray)->None:"""Apply rotation to coords associated to image obj"""rotate_obj_coords(270.0,dst,src,coords)
[docs]defcompute_rotate270(src:ImageObj)->ImageObj:"""Rotate data 270° with :py:func:`numpy.rot90` Args: src: input image object Returns: Output image object """dst=dst_11(src,"rotate270")dst.data=np.rot90(src.data,3)dst.transform_shapes(src,rotate_obj_270)returndst
# pylint: disable=unused-argument
[docs]defhflip_coords(dst:ImageObj,src:ImageObj,coords:np.ndarray)->None:"""Apply HFlip to coords"""coords[:,::2]=dst.x0+dst.width-coords[:,::2]dst.roi=None
[docs]defcompute_fliph(src:ImageObj)->ImageObj:"""Flip data horizontally with :py:func:`numpy.fliplr` Args: src: input image object Returns: Output image object """dst=Wrap11Func(np.fliplr)(src)dst.transform_shapes(src,hflip_coords)returndst
# pylint: disable=unused-argument
[docs]defvflip_coords(dst:ImageObj,src:ImageObj,coords:np.ndarray)->None:"""Apply VFlip to coords"""coords[:,1::2]=dst.y0+dst.height-coords[:,1::2]dst.roi=None
[docs]defcompute_flipv(src:ImageObj)->ImageObj:"""Flip data vertically with :py:func:`numpy.flipud` Args: src: input image object Returns: Output image object """dst=Wrap11Func(np.flipud)(src)dst.transform_shapes(src,vflip_coords)returndst
[docs]classResizeParam(gds.DataSet):"""Resize parameters"""boundaries=("constant","nearest","reflect","wrap")prop=gds.ValueProp(False)zoom=gds.FloatItem(_("Zoom"))mode=gds.ChoiceItem(_("Mode"),list(zip(boundaries,boundaries)),default=boundaries[0])cval=gds.FloatItem(_("cval"),default=0.0,help=_("Value used for points outside the ""boundaries of the input if mode is ""'constant'"),)prefilter=gds.BoolItem(_("Prefilter the input image"),default=True).set_prop("display",store=prop)order=gds.IntItem(_("Order"),default=3,min=0,max=5,help=_("Spline interpolation order"),).set_prop("display",active=prop)
[docs]defcompute_resize(src:ImageObj,p:ResizeParam)->ImageObj:"""Zooming function with :py:func:`scipy.ndimage.zoom` Args: src: input image object p: parameters Returns: Output image object """dst=dst_11(src,"resize",f"zoom={p.zoom:.3f}")dst.data=spi.interpolation.zoom(src.data,p.zoom,order=p.order,mode=p.mode,cval=p.cval,prefilter=p.prefilter,)ifdst.dxisnotNoneanddst.dyisnotNone:dst.dx,dst.dy=dst.dx/p.zoom,dst.dy/p.zoomreturndst
[docs]classBinningParam(gds.DataSet):"""Binning parameters"""sx=gds.IntItem(_("Cluster size (X)"),default=2,min=2,help=_("Number of adjacent pixels to be combined together along X-axis."),)sy=gds.IntItem(_("Cluster size (Y)"),default=2,min=2,help=_("Number of adjacent pixels to be combined together along Y-axis."),)operations=alg.BINNING_OPERATIONSoperation=gds.ChoiceItem(_("Operation"),list(zip(operations,operations)),default=operations[0],)dtypes=["dtype"]+VALID_DTYPES_STRLISTdtype_str=gds.ChoiceItem(_("Data type"),list(zip(dtypes,dtypes)),help=_("Output image data type."),)change_pixel_size=gds.BoolItem(_("Change pixel size"),default=False,help=_("Change pixel size so that overall image size remains the same."),)
[docs]defcompute_binning(src:ImageObj,param:BinningParam)->ImageObj:"""Binning function on data with :py:func:`cdl.algorithms.image.binning` Args: src: input image object param: parameters Returns: Output image object """dst=dst_11(src,"binning",f"{param.sx}x{param.sy},{param.operation},"f"change_pixel_size={param.change_pixel_size}",)dst.data=alg.binning(src.data,sx=param.sx,sy=param.sy,operation=param.operation,dtype=Noneifparam.dtype_str=="dtype"elseparam.dtype_str,)ifparam.change_pixel_size:ifsrc.dxisnotNoneandsrc.dyisnotNone:dst.dx=src.dx*param.sxdst.dy=src.dy*param.syelse:# TODO: [P2] Instead of removing geometric shapes, apply zoomdst.remove_all_shapes()returndst
[docs]defextract_multiple_roi(src:ImageObj,group:gds.DataSetGroup)->ImageObj:"""Extract multiple regions of interest from data Args: src: input image object group: parameters defining the regions of interest Returns: Output image object """# Initialize x0, y0 with maximum values:y0,x0=ymax,xmax=src.data.shape# Initialize x1, y1 with minimum values:y1,x1=ymin,xmin=0,0forpingroup.datasets:p:ROI2DParamx0i,y0i,x1i,y1i=p.get_bounding_box_indices()x0,y0,x1,y1=min(x0,x0i),min(y0,y0i),max(x1,x1i),max(y1,y1i)x0,y0=max(x0,xmin),max(y0,ymin)x1,y1=min(x1,xmax),min(y1,ymax)suffix=Noneiflen(group.datasets)==1:p=group.datasets[0]suffix=p.get_suffix()dst=dst_11(src,"extract_multiple_roi",suffix)dst.x0+=x0*src.dxdst.y0+=y0*src.dydst.roi=Nonesrc2=src.copy()src2.roi=ImageROI.from_params(src2,group)src2.data[src2.maskdata]=0dst.data=src2.data[y0:y1,x0:x1]returndst
[docs]defextract_single_roi(src:ImageObj,p:ROI2DParam)->ImageObj:"""Extract single ROI Args: src: input image object p: ROI parameters Returns: Output image object """dst=dst_11(src,"extract_single_roi",p.get_suffix())dst.data=p.get_data(src).copy()dst.roi=p.get_extracted_roi(src)x0,y0,_x1,_y1=p.get_bounding_box_indices()dst.x0+=x0*src.dxdst.y0+=y0*src.dyreturndst
[docs]classLineProfileParam(gds.DataSet):"""Horizontal or vertical profile parameters"""_prop=gds.GetAttrProp("direction")_directions=(("horizontal",_("horizontal")),("vertical",_("vertical")))direction=gds.ChoiceItem(_("Direction"),_directions,radio=True).set_prop("display",store=_prop)row=gds.IntItem(_("Row"),default=0,min=0).set_prop("display",active=gds.FuncProp(_prop,lambdax:x=="horizontal"))col=gds.IntItem(_("Column"),default=0,min=0).set_prop("display",active=gds.FuncProp(_prop,lambdax:x=="vertical"))
[docs]defcompute_line_profile(src:ImageObj,p:LineProfileParam)->SignalObj:"""Compute horizontal or vertical profile Args: src: input image object p: parameters Returns: Signal object with the profile """data=src.get_masked_view()p.row=min(p.row,data.shape[0]-1)p.col=min(p.col,data.shape[1]-1)ifp.direction=="horizontal":suffix,shape_index,pdata=f"row={p.row}",1,data[p.row,:]else:suffix,shape_index,pdata=f"col={p.col}",0,data[:,p.col]pdata:ma.MaskedArrayx=np.arange(data.shape[shape_index])[~pdata.mask]y=np.array(pdata,dtype=float)[~pdata.mask]dst=dst_11_signal(src,"profile",suffix)dst.set_xydata(x,y)returndst
[docs]defcompute_average_profile(src:ImageObj,p:AverageProfileParam)->SignalObj:"""Compute horizontal or vertical average profile Args: src: input image object p: parameters Returns: Signal object with the average profile """data=src.get_masked_view()ifp.row2==-1:p.row2=data.shape[0]-1ifp.col2==-1:p.col2=data.shape[1]-1ifp.row1>p.row2:p.row1,p.row2=p.row2,p.row1ifp.col1>p.col2:p.col1,p.col2=p.col2,p.col1p.row1=min(p.row1,data.shape[0]-1)p.row2=min(p.row2,data.shape[0]-1)p.col1=min(p.col1,data.shape[1]-1)p.col2=min(p.col2,data.shape[1]-1)suffix=f"{p.direction}, rows=[{p.row1}, {p.row2}], cols=[{p.col1}, {p.col2}]"ifp.direction=="horizontal":x,axis=np.arange(p.col1,p.col2+1),0else:x,axis=np.arange(p.row1,p.row2+1),1y=ma.mean(data[p.row1:p.row2+1,p.col1:p.col2+1],axis=axis)dst=dst_11_signal(src,"average_profile",suffix)dst.set_xydata(x,y)returndst
[docs]defupdate_from_image(self,obj:ImageObj)->None:"""Update parameters from image"""self.__obj=objself.x0=obj.xcself.y0=obj.yc
[docs]defchoice_callback(self,item,value):"""Callback for choice item"""ifvalue=="centroid":self.y0,self.x0=alg.get_centroid_fourier(self.__obj.get_masked_view())elifvalue=="center":self.x0,self.y0=self.__obj.xc,self.__obj.yc
[docs]defcompute_radial_profile(src:ImageObj,p:RadialProfileParam)->SignalObj:"""Compute radial profile around the centroid with :py:func:`cdl.algorithms.image.get_radial_profile` Args: src: input image object p: parameters Returns: Signal object with the radial profile """data=src.get_masked_view()ifp.center=="centroid":y0,x0=alg.get_centroid_fourier(data)elifp.center=="center":x0,y0=src.xc,src.ycelse:x0,y0=p.x0,p.y0suffix=f"center=({x0:.3f}, {y0:.3f})"dst=dst_11_signal(src,"radial_profile",suffix)x,y=alg.get_radial_profile(data,(x0,y0))dst.set_xydata(x,y)returndst
[docs]defcompute_histogram(src:ImageObj,p:HistogramParam)->SignalObj:"""Compute histogram of the image data, with :py:func:`numpy.histogram` Args: src: input image object p: parameters Returns: Signal object with the histogram """data=src.get_masked_view().compressed()suffix=p.get_suffix(data)# Also updates p.lower and p.uppery,bin_edges=np.histogram(data,bins=p.bins,range=(p.lower,p.upper))x=(bin_edges[:-1]+bin_edges[1:])/2dst=new_signal_result(src,"histogram",suffix=suffix,units=(src.zunit,""),labels=(src.zlabel,_("Counts")),)dst.set_xydata(x,y)dst.metadata["shade"]=0.5returndst
[docs]defcompute_inverse(src:ImageObj)->ImageObj:"""Compute the inverse of an image and return the new result image object Args: src: input image object Returns: Result image object 1 / **src** (new object) """dst=dst_11(src,"inverse")withwarnings.catch_warnings():warnings.simplefilter("ignore",category=RuntimeWarning)dst.data=np.reciprocal(src.data,dtype=float)dst.data[np.isinf(dst.data)]=np.nanrestore_data_outside_roi(dst,src)returndst
[docs]defcompute_abs(src:ImageObj)->ImageObj:"""Compute absolute value with :py:data:`numpy.absolute` Args: src: input image object Returns: Output image object """returnWrap11Func(np.absolute)(src)
[docs]defcompute_re(src:ImageObj)->ImageObj:"""Compute real part with :py:func:`numpy.real` Args: src: input image object Returns: Output image object """returnWrap11Func(np.real)(src)
[docs]defcompute_im(src:ImageObj)->ImageObj:"""Compute imaginary part with :py:func:`numpy.imag` Args: src: input image object Returns: Output image object """returnWrap11Func(np.imag)(src)
[docs]classDataTypeIParam(gds.DataSet):"""Convert image data type parameters"""dtype_str=gds.ChoiceItem(_("Destination data type"),list(zip(VALID_DTYPES_STRLIST,VALID_DTYPES_STRLIST)),help=_("Output image data type."),)
[docs]defcompute_astype(src:ImageObj,p:DataTypeIParam)->ImageObj:"""Convert image data type with :py:func:`cdl.algorithms.datatypes.clip_astype` Args: src: input image object p: parameters Returns: Output image object """dst=dst_11(src,"clip_astype",p.dtype_str)dst.data=clip_astype(src.data,p.dtype_str)returndst
[docs]defcompute_psd(src:ImageObj,p:SpectrumParam|None=None)->ImageObj:"""Compute power spectral density with :py:func:`cdl.algorithms.image.psd` Args: src: input image object p: parameters Returns: Output image object """dst=dst_11(src,"psd")log_scale=pisnotNoneandp.logdst.data=alg.psd(src.data,log_scale=log_scale)dst.xunit=dst.yunit=dst.zunit=""dst.xlabel=dst.ylabel=_("Frequency")returndst
[docs]classButterworthParam(gds.DataSet):"""Butterworth filter parameters"""cut_off=gds.FloatItem(_("Cut-off frequency ratio"),default=0.005,min=0.0,max=0.5,help=_("Cut-off frequency ratio"),)high_pass=gds.BoolItem(_("High-pass filter"),default=False,help=_("If True, apply high-pass filter instead of low-pass"),)order=gds.IntItem(_("Order"),default=2,min=1,help=_("Order of the Butterworth filter"),)
# MARK: compute_10 functions -----------------------------------------------------------# Functions with 1 input image and 0 output image# --------------------------------------------------------------------------------------
[docs]defcalc_resultshape(title:str,shape:Literal["rectangle","circle","ellipse","segment","marker","point","polygon"],obj:ImageObj,func:Callable,*args:Any,add_label:bool=False,)->ResultShape|None:"""Calculate result shape by executing a computation function on an image object, taking into account the image origin (x0, y0), scale (dx, dy) and ROIs. Args: title: result title shape: result shape kind obj: input image object func: computation function *args: computation function arguments add_label: if True, add a label item (and the geometrical shape) to plot (default to False) Returns: Result shape object or None if no result is found .. warning:: The computation function must take either a single argument (the data) or multiple arguments (the data followed by the computation parameters). Moreover, the computation function must return a single value or a NumPy array containing the result of the computation. This array contains the coordinates of points, polygons, circles or ellipses in the form [[x, y], ...], or [[x0, y0, x1, y1, ...], ...], or [[x0, y0, r], ...], or [[x0, y0, a, b, theta], ...]. """res=[]num_cols=[]fori_roiinobj.iterate_roi_indices():data_roi=obj.get_data(i_roi)ifargsisNone:coords:np.ndarray=func(data_roi)else:coords:np.ndarray=func(data_roi,*args)# This is a very long condition, but it's still quite readable, so we keep it# as is and disable the pylint warning.## pylint: disable=too-many-boolean-expressionsifnotisinstance(coords,np.ndarray)or((coords.ndim!=2orcoords.shape[1]<2or(coords.shape[1]>5andcoords.shape[1]%2!=0))andcoords.size>0):raiseValueError(f"Computation function {func.__name__} must return a NumPy array "f"containing coordinates of points, polygons, circles or ellipses "f"(in the form [[x, y], ...], or [[x0, y0, x1, y1, ...], ...], or "f"[[x0, y0, r], ...], or [[x0, y0, a, b, theta], ...]), or an empty "f"array.")ifcoords.size:coords=np.array(coords,dtype=float)ifcoords.shape[1]%2==0:# Coordinates are in the form [x0, y0, x1, y1, ...]colx,coly=slice(None,None,2),slice(1,None,2)else:# Circle [x0, y0, r] or ellipse coordinates [x0, y0, a, b, theta]colx,coly=0,1coords[:,colx]=obj.dx*coords[:,colx]+obj.x0coords[:,coly]=obj.dy*coords[:,coly]+obj.y0ifobj.roiisnotNone:x0,y0,_x1,_y1=obj.roi.get_single_roi(i_roi).get_bounding_box(obj)coords[:,colx]+=x0-obj.x0coords[:,coly]+=y0-obj.y0idx=np.ones((coords.shape[0],1))*(0ifi_roiisNoneelsei_roi)coords=np.hstack([idx,coords])res.append(coords)num_cols.append(coords.shape[1])ifres:iflen(set(num_cols))!=1:# This happens when the number of columns is not the same for all ROIs.# As of now, this happens only for polygon contours.# We need to pad the arrays with NaNs.max_cols=max(num_cols)num_rows=sum(coords.shape[0]forcoordsinres)array=np.full((num_rows,max_cols),np.nan)row=0forcoordsinres:array[row:row+coords.shape[0],:coords.shape[1]]=coordsrow+=coords.shape[0]else:array=np.vstack(res)returnResultShape(title,array,shape,add_label=add_label)returnNone
[docs]defget_centroid_coords(data:np.ndarray)->np.ndarray:"""Return centroid coordinates with :py:func:`cdl.algorithms.image.get_centroid_fourier` Args: data: input data Returns: Centroid coordinates """y,x=alg.get_centroid_fourier(data)returnnp.array([(x,y)])