def main(): ## Initial print print('\n') print('########################################################') print('############# REGRIDDING BACKPROJECTION #############') print('########################################################') print('\n') ## Get the startimg time of the reconstruction time1 = time.time() ## Get input arguments args = getArgs() ## Get input/output directory pathin, pathout = utils.get_io_path(args) print('\nInput path:\n', pathin) print('\nOutput path:\n', pathout) ## Get input files file_list, file1, nimg, ext = utils.get_input(args, pathin) print('\nNumber of input files: ', nimg) print('Extension of the files: ', ext) ## Read first sinogram sino = io.readImage(pathin + file1).astype(myfloat) nang, npix = sino.shape print('\nSinogram to reconstruct:\n', file1) print('Number of projection angles: ', nang) print('Number of pixels: ', npix) if args.plot is True: dis.plot(sino, 'Input sinogram') ## Getting projection geometry if args.geometry == '0': print( '\nDealing with equiangular projections distributed between 0 and' + ' 180 degrees ---> [0,180)') angles = utils.create_projection_angles(nang) else: print('\nReading list of projection angles:\n', pathin + args.geometry) angles = utils.create_projection_angles(textfile=pathin + args.geometry) ## Set center of rotation axis if args.ctr == None: ctr = 0.0 print('Center of rotation axis placed at pixel: ', npix * 0.5) else: if args.ctr == -1: ctr = proc.search_rot_ctr(sino, None, 'a') print('Center of rotation axis placed at pixel: ', ctr) else: ctr = args.ctr print('Center of rotation axis placed at pixel: ', ctr) if args.dbp is True: sino[:, :] = proc.sino_correct_rot_axis(sino, ctr) print('Center of rotation corrected') ctr = 0.0 ## Enable edge padding if args.edge_pad is True: sino = proc.sino_edge_padding(sino, 0.5).astype(myfloat) i1 = int((sino.shape[1] - npix) * 0.5) i2 = i1 + npix npix = sino.shape[1] if ctr != 0.0: ctr += i1 if args.plot is True: dis.plot(sino, 'Edge padded sinogram') else: i1 = 0 i2 = npix ## External sinogram filtering if args.filt == 'ramp-ext' or args.filt == 'conv-ext': if filt == 'ramp-ext': sino = fil.filter_fft(sino, 'ramp') elif filt == 'conv-ext': sino = fil.filter_convolve(sino) sino *= 4.0 / myfloat(npix) args.filt = 0 ## Differential sinogram fo DBP if args.dbp is True: print('\nDifferential backprojection enabled') print( 'Computing differential sinogram by means of Savitzky-Golay method' ) print('Savizky-Golay window length: ', args.sg) sino[:, :] = proc.diff_sino_savitzky_golay(sino, window_size=args.sg) if args.plot is True: dis.plot(sino, 'Differential sinogram') ## Initialize projectior class tp = cpj.projectors(npix, angles, ctr=ctr, args=args) ## Apply forward projection operator time_rec1 = time.time() reco = tp.fbp(sino) time_rec2 = time.time() ## Crop reconstruction if args.edge_pad: reco = reco[i1:i2, i1:i2] ## Display reconstruction if args.plot is True: dis.plot(reco, 'Reconstruction') ## Save reconstruction save_reconstruction(pathout, file1, args, reco) ## Reconstruct sinograms from all the other images in the stack if args.filein is None: pool = mproc.Pool() for i in range(1, nimg): pool.apply_async( multi_thread, (pathin, pathout, file_list[0][i], args, [i1, i2])) pool.close() pool.join() time2 = time.time() print('\nTime elapsed to run the 1st backward gridrec: ', time_rec2 - time_rec1) print('Total time elapsed for the run of the program: ', time2 - time1) print('\n') print('##############################################') print('#### REGRIDDING BACKPROJECTION DONE ! ####') print('##############################################') print('\n')
def main(): ## Initial print print('\n') print('##########################################################') print('##########################################################') print('##### #####') print('##### ADMM Iterative Reconstruction #####') print('##### #####') print('##########################################################') print('##########################################################') print('\n') ## Get input arguments args = getArgs() ## Get input and output paths pathin, pathout = utils.get_io_path(args) print('\nInput path:\n', pathin) print('\nOutput path:\n', pathout) ## Get input sinogram sino_list = [] filein = [] if args.filein is not None: sinoname = pathin + args.filein sino = io.readImage(sinoname).astype(myfloat) if args.angle_pi is True: sino = sino[:sino.shape[0] - 1, :] nang, npix = sino.shape nz = 1 sino_list.append(sino) filein.append(args.filein) print('\nSinogram to reconstruct:\n', sinoname) else: print('\nReading stack of images\n') curr_dir = os.getcwd() os.chdir(pathin) for f in os.listdir('./'): if f.endswith('.DMP') is True: ext = '.DMP' break elif f.endswith('.tif') is True: ext = '.tif' break else: sys.exit('\nERROR: no .DMP or .tif file found in:\n' + pathin) filein.append(sorted(glob.glob('*' + ext))) nz = len(filein[0]) os.chdir(curr_dir) print('Stack extension: ', ext) print('Number of slices: ', nz) print('\nLoading images .... ') for i in range(nz): if i == 0: sino = io.readImage(pathin + filein[0][i]).astype(myfloat) nang, npix = sino.shape sino_list.append(sino) else: sino_list.append( io.readImage(pathin + filein[0][i]).astype(myfloat)) print(' done! ') print('\nNumber of projection angles: ', nang) print('Number of pixels: ', npix) ## Check plot if args.plot is True: if nz == 1: dis.plot(sino_list[0], 'Input sinogram') else: nzz = np.int(nz * 0.5) dis.plot(sino_list[nzz], 'Input sinogram') ## Center of rotation axis if args.ctr == -1: ctr = npix * 0.5 elif args.ctr != -1: ctr = args.ctr ## Enable edge padding if args.lt is True: edf = 0.87 elif args.lt is False and args.edge_padding != 0: edf = args.edge_padding else: edf = 0.0 if edf: npix_old = npix for i in range(nz): sino_list[i] = proc.sino_edge_padding(sino_list[i], edf) npix = sino_list[0].shape[1] i1 = myint((npix - npix_old) * 0.5) i2 = i1 + npix_old ctr += i1 print('\nEdge padding: ', edf) print('Number of edge-padded pixels: ', npix) print('Index start: ', i1, ' Index end: ', i2) print('Center of rotation axis new position: ', ctr) if args.plot is True: if nz == 1: dis.plot(sino_list[0], 'Sinogram with edge-padding') else: nzz = np.int(nz * 0.5) dis.plot(sino_list[nzz], 'Input sinogram') else: npix_old = npix i1 = 0 i2 = npix ## Compute differential sinogram if DBP option enabled if args.dbp is True: print('\nComputing differential sinogram ....') for i in range(nz): sino_list[i] = proc.diff_sino_savitzky_golay(sino_list[i], window_size=args.sg) if args.plot is True: dis.plot(sino_list[0], 'Differential sinogram') ## Correct for the center of rotation axis if args.ctr != -1: for i in range(nz): sino_list[i] = proc.sino_correct_rot_axis(sino_list[i], ctr) print('\nCenter of rotation axis position: ', ctr) print('Center of rotation corrected') ## Get geometry if args.geometry == '0': angles = utils.create_projection_angles(nang) else: angles = utils.create_projection_angles(textfile=pathin + args.geometry) ## Getting stopping criterion print('\nSetup of the iterative procedure:') if nz == 0: labelout = pathout + filein[0][:len(filein[0]) - 4] else: labelout = pathout + filein[0][0][:len(filein[0]) - 4] param = cap.admm_param(npix_old, nang, nz, ctr, labelout, args) print('Projectors enabled: ', args.projector) if args.dbp is True: print('DBP reconstruction enabled') if args.dpc is True: print('DPC reconstruction enabled') if args.n_iter is not None: print('Number of iterations: ', param.n_iter) if args.eps is not None: print('Stopping epsilon: ', param.eps) if args.n_iter_pcg is not None: print('Number of PCG iterations: ', param.n_iter_pcg) if args.plot is True: print('Interactive plot:', param.plot) if args.logfile is True: print('Interactive plot:', param.logfile) if param.reg is not None: if param.reg == 'cg': print('Conjugate gradient') if param.reg == 'lasso': print('Regularization type: Lasso L1') elif param.reg == 'lasso-tv': print('Regularization type: Lasso TV') elif param.reg == 'pp-breg': print('Regularization type: Plug and Play -- TV Bregman') elif param.reg == 'pp-chamb': print('Regularization type: Plug and Play -- TV Chambolle') elif param.reg == 'pp-nlmeans': print('Regularization type: Plug and Play -- Non Local Means') elif param.reg == 'pp-tgv': print( 'Regularization type: Plug and Play -- Total generalized variation' ) elif param.reg == 'pp-nltv': print( 'Regularization type: Plug and Play -- Non-local total variation' ) if param.reg != 'cg': print('\nLambda 1: ', param.lambd1) print('Lambda 2: ', param.lambd2) print('Mu: ', param.mu) if args.init_object is True: print('\nInitialization with FBP reconstruction:', param.init_object) if param.mask is not None: print('\nObject support enabled') if param.plot is True: dis.plot(param.mask, 'Object support') if param.mask_add is not None: print('\nAdditional supports provided') if param.plot is True: if param.mask_add_n == 1: dis.plot(param.mask_add[0]) else: dis.plot_multi(param.mask_add) if param.lt is True: print('\nLocal tomography mode enabled') ## Iterative reconstruction print('\n\nReconstructing with ADMM ....') time1 = time.time() reco_list, info = admm(sino_list, angles, param) time2 = time.time() print('.... reconstruction done!') for i in range(nz): ## Crop reconstruction if edge-padding enabled if edf != 0.0: reco = reco_list[i, i1:i2, i1:i2] else: reco = reco_list[i, :, :] ## Show reconstruction if args.plot is True and nz == 1 and args.reg != 'cg': dis.plot(reco, 'Reconstruction') plot_convergence_curves(info) elif args.plot is True and i == nzz and args.reg != 'cg': nzz = np.int(nz * 0.5) dis.plot(reco_list[nzz, :, :], 'Reconstruction of slice ' + str(nzz)) ## Save reconstruction if nz == 1: save_reco(pathout, filein[0], args, param, reco) else: save_reco(pathout, filein[0][i], args, param, reco) ## Time elapsed for the reconstruction time_tot = (time2 - time1) / 60.0 print('\nTime elapsed for the reconstruction: ', time_tot) ## Write log file if args.logfile is True: write_logfile(pathin, pathout, args, angles, ctr, param, time_tot, info) write_info(pathout, filein[0], info, param, args) ## Final print print('\n') print('\n') print('##########################################################') print('##########################################################') print('##### #####') print('##### ADMM Reconstruction done! #####') print('##### #####') print('##########################################################') print('##########################################################') print('\n')
def main(): ## Initial print print('\n') print('##########################################################') print('##########################################################') print('##### #####') print('##### STATISTICAL ITERATIVE RECONSTRUCTION #####') print('##### #####') print('##########################################################') print('##########################################################') print('\n') ## Getting arguments args = getArgs() ## Get input & output paths pathin, pathout = utils.get_io_path(args) print('\nInput path: \n', pathin) print('\nOutput path:\n', pathout) ## Get input sinogram sino_list = [] filein = [] if args.filein is not None: sinoname = pathin + args.filein sino = io.readImage(sinoname).astype(myfloat) nang, npix = sino.shape nz = 1 sino_list.append(sino) filein.append(args.filein) print('\nSinogram to reconstruct:\n', sinoname) else: print('\nReading stack of images\n') curr_dir = os.getcwd() os.chdir(pathin) for f in os.listdir('./'): if f.endswith('.DMP') is True: ext = '.DMP' break elif f.endswith('.tif') is True: ext = '.tif' break else: sys.exit('\nERROR: no .DMP or .tif file found in:\n' + pathin) filein.append(sorted(glob.glob('*' + ext))) nz = len(filein[0]) os.chdir(curr_dir) print('Stack extension: ', ext) print('Number of slices: ', nz) print('\nLoading images .... ') for i in range(nz): if i == 0: sino = io.readImage(pathin + filein[0][i]).astype(myfloat) nang, npix = sino.shape sino_list.append(sino) else: sino_list.append( io.readImage(pathin + filein[0][i]).astype(myfloat)) print(' done! ') print('\nNumber of projection angles: ', nang) print('Number of pixels: ', npix) ## Check plot if args.plot is True: if nz == 1: dis.plot(sino_list[0], 'Input sinogram') else: nzz = np.int(0.5 * nz) dis.plot(sino_list[nzz], 'Input sinogram') ## Center of rotation axis if args.ctr == -1: ctr = npix * 0.5 elif args.ctr != -1: ctr = args.ctr ## Enable edge padding if args.lt is True: edf = 0.87 elif args.lt is False and args.edge_padding != 0: edf = args.edge_padding else: edf = 0.0 if edf: npix_old = npix for i in range(nz): sino_list[i] = proc.sino_edge_padding(sino_list[i], edf) npix = sino_list[0].shape[1] i1 = myint((npix - npix_old) * 0.5) i2 = i1 + npix_old ctr += i1 print('\nEdge padding: ', edf) print('Number of edge-padded pixels: ', npix) print('Index start: ', i1, ' Index end: ', i2) print('Center of rotation axis new position: ', ctr) if args.plot is True: dis.plot(sino_list[0], 'Sinogram with edge-padding') else: npix_old = npix i1 = 0 i2 = npix ## Correct for the center of rotation axis if args.ctr != -1: for i in range(nz): sino_list[i] = proc.sino_correct_rot_axis(sino_list[i], ctr) print('\nCenter of rotation axis position: ', ctr) print('Center of rotation corrected') ## Get geometry if args.geometry == '0': angles = utils.create_projection_angles(nang) else: angles = utils.create_projection_angles(textfile=pathin + args.geometry) ## Setup iterative procedure print('\nSetup of the iterative procedure:') if nz == 0: labelout = pathout + filein[0][:len(filein[0]) - 4] else: labelout = pathout + filein[0][0][:len(filein[0]) - 4] param = csp.sir_param(nang, npix_old, nz, ctr, labelout, args) print('Selected projector: ', args.projector) if args.eps is not None: print('Stopping threshold: ', param.eps) if args.n_iter is not None: print('Number of iterations: ', param.n_iter) if args.plot is True: print('Interactive plot:', param.plot) if args.logfile is True: print('Interactive plot:', param.logfile) if param.reg is not None: if param.reg == 'huber': print('Regularization type: Huber penalty') print('Huber constant ---> delta: ') elif param.reg == 'tikhonov': print('Regularization type: l2 penalty') elif param.reg == 'haar': print('Regularization type: l1 penalty') print('Regularization constant (beta): ', param.reg_cost) if args.init_object is True: print('Initialization with FBP reconstruction:', param.init_object) ## Reconstruction print('\n\nPerforming STASTICAL ITERATIVE RECONSTRUCTION ....') time1 = time.time() reco_list, info = sir(sino_list, angles, param) time2 = time.time() print('.... reconstruction done!') for i in range(nz): ## Crop reconstruction if edge-padding enabled if edf != 0.0: reco = reco_list[i, i1:i2, i1:i2] else: reco = reco_list[i, :, :] ## Show reconstruction if args.plot is True and nz == 1: dis.plot(reco, 'Reconstruction') elif args.plot is True and i == nzz: dis.plot(reco, 'Reconstruction') ## Save reconstruction if nz == 1: save_reco(pathout, filein[0], args, param, reco) else: save_reco(pathout, filein[0][i], args, param, reco) ## Time elapsed for the reconstruction time_tot = (time2 - time1) / 60.0 print('\nTime elapsed for the reconstruction: ', time_tot) ## Write log file if args.logfile is True: write_logfile(pathin, pathout, args, angles, ctr, param, time_tot, info) write_info(pathout, filein[0], info, param, args) ## Final print print('\n') print('\n') print('##########################################################') print('##########################################################') print('##### #####') print('##### STATISTICAL ITERATIVE RECONSTRUCTION DONE! #####') print('##### #####') print('##########################################################') print('##########################################################') print('\n')
def main(): ## Initial print print('\n') print('###########################################################') print('############# FORWARD REGRIDDING PROJECTOR #############') print('###########################################################') print('\n') ## Get the startimg time of the reconstruction time1 = time.time() ## Get input arguments args = getArgs() ## Get input/output directory pathin, pathout = utils.get_io_path(args) print('\nInput path:\n', pathin) print('\nOutput path:\n', pathout) ## Get input files file_list, file1, nimg, ext = utils.get_input(args, pathin) print('\nNumber of input files: ', nimg) print('Extension of the files: ', ext) ## Read first image image = io.readImage(pathin + file1).astype(myfloat) npix = image.shape[0] print('\nFirst image to forward project: ', file1) print('Number of pixels: ', npix) if args.plot is True: dis.plot(image, 'Input image') ## Get projection angles if args.geometry == '0' or args.geometry == '1': nang = args.nang angle_type = myint(args.geometry) if args.angle_range.find(':') == -1 or args.geometry == -1: angle_start = myfloat(args.angle_range) angle_end = angle_start + 180.0 else: angle_aux = args.angle_range.find(':') angle_start = myfloat(angle_aux[0]) angle_end = myfloat(angle_aux[1]) angles = utils.create_projection_angles(nang, angle_start, angle_end) else: angles = utils.create_projection_angles(pathin + args.geometry) nang = len(angles) print('\nNumber of views: ', nang) print('Selected angle range: [ ', angle_start, ' , ', angle_end, ' )') print('Angles:\n', angles) ## Initialize projectior class tp = cpj.projectors(npix, angles, args=args) ## Apply forward projection operator time_rec1 = time.time() sino = tp.A(image) time_rec2 = time.time() ## Display sinogram if args.plot is True: dis.plot(sino, 'Sinogram') ## Save sinogram save_sinogram(pathout, file1, angles, args, sino) ## Create sinograms from all the other images in the stack if args.filein is None: pool = mproc.Pool() for i in range(1, nimg): pool.apply_async(multi_thread, (pathin, pathout, file_list[0][i], angles, args)) pool.close() pool.join() time2 = time.time() print('\nTime elapsed to run the 1st forward gridrec: ', time_rec2 - time_rec1) print('Total time elapsed for the run of the program: ', time2 - time1) print('\n') print('#######################################') print('#### FORWARD PROJECTION DONE ! ####') print('#######################################') print('\n')
def main(): ## Initial print print('\n') print('########################################################') print('#### CREATE VIRTUAL SINOGRAM ####') print('########################################################') print('\n') ## Get arguments args = getArgs() ## Get input/output directory pathin , pathout = utils.get_io_path( args ) print('\nInput path:\n', pathin) print('\nOutput path:\n', pathout) ## Get input files file_list , file1 , nimg , ext = utils.get_input( args , pathin ) print('\nNumber of input files: ' , nimg) print('Extension of the files: ', ext) ## Read first sinogram sino = io.readImage( pathin + file1 ).astype( myfloat ) nang , npix = sino.shape print('\nSinogram to reconstruct:\n', file1) print('Number of projection angles: ', nang) print('Number of pixels: ', npix) if args.plot is True: dis.plot( sino , 'Input sinogram' ) ## Set center of rotation axis if args.ctr == None: ctr = 0.0 print('Center of rotation axis placed at pixel: ', npix * 0.5) else: ctr = args.ctr print('Center of rotation axis placed at pixel: ', ctr) ## Enable edge-padding if args.edge_pad is True: sino = proc.sino_edge_padding( sino , 0.5 ).astype( myfloat ) i1 = int( ( sino.shape[1] - npix ) * 0.5 ) i2 = i1 + npix npix = sino.shape[1] if ctr != 0.0: ctr += i1 if args.plot is True: dis.plot( sino , 'Edge padded sinogram' ) else: i1 = 0 i2 = npix ## Prepare projectors ang = np.arange( nang ) * 180.0 / myfloat( nang ) tp = cpj.projectors( npix , ang , kernel='kb' , oversampl=2.32 , W=6.6 , errs=6.0e-6 , interp='lin' , radon_degree=0 , filt=args.filt , ctr=ctr ) ## Reconstruct reco = tp.fbp( sino ) if args.plot is True: dis.plot( reco , 'Reconstruction' ) #reco = reco[i1:i2,i1:i2] ## Zero-out pixels outside resolution circle reco_new = reco.copy(); reco_new[:] = 0.0 io.writeImage( 'reco.DMP' , reco[i1:i2,i1:i2] ) reco_new[i1:i2,i1:i2] = utils.resol_circle_constr( reco[i1:i2,i1:i2] ) reco[:] = reco_new[:] if args.plot is True: dis.plot( reco , 'Constrained reconstruction' ) io.writeImage( 'reco_circle.DMP' , reco ) ## Background equalization reco[:] = background_equalization( reco ) if args.plot is True: dis.plot( reco , 'Equalized reconstruction' ) io.writeImage( 'reco_equaliz.DMP' , reco ) ## Forward projection nang_new = np.int( npix * np.pi / 2.0 ) ang_new = np.arange( nang_new ) * 180.0 / np.float32( nang_new ) tp = cpj.projectors( npix , ang_new , kernel='kb' , oversampl=2.32 , W=6.6 , errs=6.0e-6 , interp='lin' , radon_degree=0 ) sino = tp.A( reco ) #sino = sino[:,i1:i2] if args.plot is True: dis.plot( sino , 'Forward projection' ) io.writeImage( 'sino_circle.DMP' , sino ) ## Save output file if args.fileout is None: filein = args.filein extension = filein[len(filein)-4:] fileout = filein[:len(filein)-4] + '_virt.tif' else: fileout = args.fileout io.writeImage( pathout + fileout , sino ) print( '\nWritten output file:\n' , pathout + fileout )
def main(): ## Initial print print('\n') print('##################################################################') print('############# TOMOGRAPHIC GRIDDING RECONSTRUCTION #############') print('##################################################################') print('\n') ## Get the startimg time of the reconstruction time1 = time.time() ## Get input arguments args = getArgs() ## Get input/output directory pathin, pathout = utils.get_io_path(args) print('\nInput path:\n', pathin) print('\nOutput path:\n', pathout) ## Get input files file_list, file1, nimg, ext = utils.get_input(args, pathin) print('\nNumber of input files: ', nimg) print('Extension of the files: ', ext) ## Read first sinogram sino = io.readImage(pathin + file1).astype(myfloat) nang, npix = sino.shape print('\nSinogram to reconstruct:\n', file1) print('Number of projection angles: ', nang) print('Number of pixels: ', npix) if args.plot is True: dis.plot(sino, 'Input sinogram') ## Enable edge padding if args.edge_pad is True: sino = proc.sino_edge_padding(sino, 0.5).astype(myfloat) i1 = int((sino.shape[1] - npix) * 0.5) i2 = i1 + npix npix = sino.shape[1] ctr = args.ctr if ctr != 0.0: ctr += i1 if args.plot is True: dis.plot(sino, 'Edge padded sinogram') else: i1 = 0 i2 = npix ctr = args.ctr ## Getting projection geometry if args.geometry == '0': print( '\nDealing with equiangular projections distributed between 0 and' + ' 180 degrees ---> [0,180)') angles = utils.create_projection_angles(nang) else: print('\nReading list of projection angles:\n', pathin + args.geometry) angles = utils.create_projection_angles(textfile=pathin + args.geometry) if args.plot is True: print('\nProjection angles:') pp.printArray(angles) ## Enable object support calculation if args.object_support is True: print('\nObject support calculation enabled') mask = ob.object_support(sino) if args.plot is True: dis.plot(mask, 'Object support mask') ## Differential sinogram fo DBP if args.dbp is True: print('\nDifferential backprojection enabled') print( 'Computing differential sinogram by means of Savitzky-Golay method' ) sino[:, :] = proc.diff_sino_savitzky_golay(sino, window_size=11) if args.plot is True: dis.plot(sino, 'Differential sinogram') ## Initialize projectior class tp = cpj.projectors(npix, angles, ctr=ctr, args=args) ## Apply forward projection operator time_rec1 = time.time() reco = tp.fbp(sino) time_rec2 = time.time() ## Crop reconstruction if args.edge_pad: reco = reco[i1:i2, i1:i2] ## Apply object support mask if args.object_support is True: reco[mask == 0] = 0.0 ## Display reconstruction if args.plot is True: dis.plot(reco, 'Reconstruction') ## Save reconstruction save_reconstruction(pathout, file1, args, reco) ## Reconstruct sinograms from all the other images in the stack if args.filein is None: pool = mproc.Pool() for i in range(1, nimg): pool.apply_async( multi_thread, (pathin, pathout, file_list[0][i], args, [i1, i2])) pool.close() pool.join() time2 = time.time() print('\nTime elapsed to run the 1st backward gridrec: ', time_rec2 - time_rec1) print('Total time elapsed for the run of the program: ', time2 - time1) print('\n') print('##############################################') print('#### GRIDDING RECONSTRUCTION DONE ! ####') print('##############################################') print('\n')