From c8315f6a29dc0d7f08651b170e84399b6184c06d Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 21 Nov 2024 12:56:13 +0000 Subject: [PATCH 001/189] added new functionality for autopicking amyloids, not optimal yet... --- src/amyloid_finder.cpp | 1072 +++++++++++++++++++++++++++++++++ src/amyloid_finder.h | 145 +++++ src/amyloid_finder_mpi.cpp | 90 +++ src/amyloid_finder_mpi.h | 49 ++ src/apps/find_amyloid.cpp | 51 ++ src/apps/find_amyloid_mpi.cpp | 51 ++ src/gui_jobwindow.cpp | 3 +- src/pipeline_jobs.cpp | 64 +- 8 files changed, 1513 insertions(+), 12 deletions(-) create mode 100644 src/amyloid_finder.cpp create mode 100644 src/amyloid_finder.h create mode 100644 src/amyloid_finder_mpi.cpp create mode 100644 src/amyloid_finder_mpi.h create mode 100644 src/apps/find_amyloid.cpp create mode 100644 src/apps/find_amyloid_mpi.cpp diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp new file mode 100644 index 000000000..010b5d851 --- /dev/null +++ b/src/amyloid_finder.cpp @@ -0,0 +1,1072 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ +#include "src/amyloid_finder.h" + +void AmyloidFinder::read(int argc, char **argv, int rank) +{ + parser.setCommandLine(argc, argv); + + + int general_section = parser.addSection("General options"); + fn_in = parser.getOption("--i", "Input image (.mrc) or STAR file with micrographs"); + fn_out = parser.getOption("--pickname", "Rootname for coordinate STAR files", "amypick"); + fn_odir = parser.getOption("--odir", "Output directory for coordinate files (default is to store next to micrographs)", "AutoPick/"); + do_only_unfinished = parser.checkOption("--only_do_unfinished", "Only estimate CTFs for those tomograms for which there is not yet a logfile with Final values."); + do_write_intermediate = parser.checkOption("--write_intermediates", "Write out intermediate FOM maps for fast testing of tracing parameters?"); + nr_threads = textToInteger(parser.getOption("--j", "Number of threads to us in parallel", "1")); + + int search_section = parser.addSection("Filament searching options "); + psi_step = textToFloat(parser.getOption("--psi_step", "Angular sampling rate (in degrees)", "10.")); + shift_step = textToInteger(parser.getOption("--shift_step", "Step in shifts to search (in downscaled pixels)", "5")); + width = textToFloat(parser.getOption("--width", "Width of searching image (in A)", "100")); + length = textToFloat(parser.getOption("--length", "Length of searching image (in A)", "300")); + + int pick_section = parser.addSection("Filament tracing options "); + zscore_threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "2.")); + minimum_filament_length = textToFloat(parser.getOption("--min_filament_length", "Minimum length of filaments to trace (in A)", "400.")); + filament_width = textToFloat(parser.getOption("--filament_width", "Minimum distance between two traced filaments (in A)", "200.")); + nr_rungs_per_segment = textToInteger(parser.getOption("--rungs_per_segment", "Number of new amyloid rungs per segment", "3")); + RFLOAT kappa = textToFloat(parser.getOption("--kappa", "Curvature parameter kappa ", "0.07")); + if (kappa > 0.1) + REPORT_ERROR("ERROR: for amyloids you cannot use kappa larger than 0.1!"); + psidiff_per_segment = RAD2DEG(kappa*2.); + + int expert_section = parser.addSection("Expert options (typically no need to change)"); + signal_minres = textToFloat(parser.getOption("--signal_minres", "Minimum resolution value for signal (in A)", "4.85")); + signal_maxres = textToFloat(parser.getOption("--signal_maxres", "Maximum resolution value for signal (in A)", "4.65")); + down_angpix = textToFloat(parser.getOption("--down_angpix", "Pixel size for downscaled images (needs to include signal frequency!)", "2.25")); + angpix = textToFloat(parser.getOption("--force_angpix", "Force this pixel size, regardless of what is in the image header", "-1")); + + verb =textToInteger(parser.getOption("--verb", "Verbosity", "1")); + + // Check for errors in the command-line option + if (parser.checkForErrors()) + REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); +} + +void AmyloidFinder::usage() +{ + parser.writeUsage(std::cout); +} + +void AmyloidFinder::initialise(bool is_leader) +{ + // Make sure fn_odir ends with a slash + if (fn_odir[fn_odir.length()-1] != '/') + fn_odir += "/"; + + fn_micrographs.clear(); + if (fn_in.isStarFile()) + { + MetaDataTable MDin; + MDin.read(fn_in, "micrographs"); + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDin) + { + FileName fn_mic; + MDin.getValue(EMDL_MICROGRAPH_NAME, fn_mic); + fn_micrographs.push_back(fn_mic); + } + } + else + { + // Read a single micrograph + fn_micrographs.push_back(fn_in); + } + + fn_ori_micrographs = fn_micrographs; + // If we're continuing an old run, see which micrographs have not been finished yet... + if (do_only_unfinished && !do_write_intermediate) + { + if (verb > 0) + { + std::cout << " + Skipping those micrographs for which coordinate file already exists" << std::endl; + } + + std::vector fns_todo; + for (long int imic = 0; imic < fn_micrographs.size(); imic++) + { + FileName fn_tmp = getOutputRootName(fn_micrographs[imic]) + "_" + fn_out + ".star"; + if (!exists(fn_tmp)) + fns_todo.push_back(fn_micrographs[imic]); + } + + fn_micrographs = fns_todo; + } + + // If there is nothing to do, then go out of initialise + todo_anything = true; + if (fn_micrographs.size() == 0) + { + if (verb > 0) + std::cout << " + No new micrographs to do, so exiting finding amyloids ..." << std::endl; + todo_anything = false; + return; + } + + if (verb > 0) std::cout << " + Finding amyloids in the " << fn_micrographs.size() << " micrographs... " << std::endl; + + // Read in header of first image + Image Iin; + Iin.read(fn_micrographs[0], false); + ori_xsize = XSIZE(Iin()); + ori_ysize = YSIZE(Iin()); + Iin().setXmippOrigin(); + if (angpix < 0.) + { + angpix = Iin.samplingRateX(); + if (verb > 0) std::cout << " - Using pixel size from the header of : " << fn_in << " = " << angpix << std::endl; + } + + if (signal_maxres < 2*down_angpix) REPORT_ERROR("ERROR: the down_angpix is not enough to support the maximum resolution of the signal!"); + if (angpix > down_angpix) REPORT_ERROR("ERROR: this program requires input images with a pixel size of at least down_angpix (" + floatToString(down_angpix) + ")!"); + + // Width and length in the downscaled pixels + iwidthmax = ROUND(width / down_angpix ); + ilengthmax = CEIL(length/ down_angpix ); + + down_xsize = FLOOR( (ori_xsize * angpix) / down_angpix ); + down_ysize = FLOOR( (ori_ysize * angpix) / down_angpix ); + if (ilengthmax %2 != 0) ilengthmax++; + nr_psi = ROUND(180./psi_step); + psi_step = 180./nr_psi; + + // Calculate Fourier shells for amyloid signal + imin_signal = FLOOR(ilengthmax*down_angpix/signal_minres); + imax_signal = CEIL(ilengthmax*down_angpix/signal_maxres); + + // Box size, orginal and cropped: set size of rectangular image to largest dimension + large_box = XMIPP_MAX(ori_xsize, ori_ysize); + // Also calculate size of cropped box: + crop_box = large_box * angpix/down_angpix; + if (crop_box%2 != 0) crop_box++; + + // Output some information to the user + if (verb > 0) + { + std::cout << " + Number of 1D rows for filament width (in downscaled pixels): " << iwidthmax << std::endl; + std::cout << " + Length of 1D rows for filament (in downscaled pixels): " << ilengthmax << std::endl; + std::cout << " + Number of in-plane rotations to sample: " << nr_psi << " with step of " << psi_step << " degrees" << std::endl; + std::cout << " + Original size of the input micrographs: " << ori_xsize << " x " << ori_ysize << " pixels" << std::endl; + std::cout << " + Size of image to sample (in downscaled pixels): " << down_xsize << " x " << down_ysize << std::endl; + std::cout << " + Fourier shells for the amyloid signal (in downscaled pixels): " << imin_signal << " - " << imax_signal << std::endl; + std::cout << " ========================== " << std::endl; + } + + // For filament tracing + // Set up a vector with coordinates of feasible next coordinates regarding distance and psi-angle + amyloid_rung = (signal_minres + signal_maxres)/2.; + int myrad = ROUND(nr_rungs_per_segment * amyloid_rung / angpix); + int myradb = myrad + 1; + float myrad2 = (float)myrad * (float)myrad; + float myradb2 = (float)myradb * (float)myradb; + for (int ii = -myradb; ii <= myradb; ii++) + { + for (int jj = -myradb; jj <= myradb; jj++) + { + float r2 = (float)(ii*ii) + (float)(jj*jj); + if (r2 > myrad2 && r2 <= myradb2) + { + float myang = RAD2DEG(atan2((float)(ii),(float)(jj))); + if (myang > 90.) + myang -= 180.; + if (myang < -90.) + myang += 180.; + if (fabs(myang) < psidiff_per_segment) + { + AmyloidCoordinate circlecoord; + circlecoord.x = (RFLOAT)jj; + circlecoord.y = (RFLOAT)ii; + circlecoord.fom =0.; + circlecoord.psi =myang; + circle.push_back(circlecoord); + //std::cerr << " circlecoord.x= " << circlecoord.x << " circlecoord.y= " << circlecoord.y << " psi= " << circlecoord.psi << std::endl; + } + } + } + } + + + +} + +FileName AmyloidFinder::getOutputRootName(FileName fn_mic) +{ + FileName fn_pre, fn_jobnr, fn_post; + decomposePipelineFileName(fn_mic, fn_pre, fn_jobnr, fn_post); + return fn_odir + fn_post.withoutExtension(); +} + +RFLOAT AmyloidFinder::getPsiAngle(int ipsi) +{ + return 2.3 + ipsi * psi_step; +} + +void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, MultidimArray &Mangle, bool myverb) +{ + + // Rotate the large image, and store downscaled images by cropping their Fourier Transform + std::vector > rotated_imgs(nr_psi), rotated_scores_perline(nr_psi), rotated_scores(nr_psi); + std::vector transformer(nr_threads); + // TODO: in principle, only need to rotate to 90 degrees, as I can use both the X and the Y direction for the 1D FFTs! + if (myverb) + { + std::cout << " - Rotating the input image ..." << std::endl; + init_progress_bar(nr_psi); + } + +#pragma omp parallel for num_threads(nr_threads) + for (int ipsi = 0; ipsi < nr_psi; ipsi++) + { + const int tid = omp_get_thread_num(); + RFLOAT psi = getPsiAngle(ipsi); + + // Rotate the images in their original size to prevent interpolation artefacts near the signal frequencies + MultidimArray Mrot; + Mrot.setXmippOrigin(); + Mrot.initZeros(large_box, large_box); + rotate(image, Mrot, psi, 'Z', true); + + // Re-scale image so that Nyquist is at down_angpix + MultidimArray FT, FT2; + transformer[tid].FourierTransform(Mrot, FT, false); + windowFourierTransform(FT, FT2, crop_box); + Mrot.resize(crop_box, crop_box); + transformer[tid].inverseFourierTransform(FT2, Mrot); + Mrot.setXmippOrigin(); + rotated_imgs[ipsi] = Mrot; + // Just prepare the rotated_scores vector too + rotated_scores_perline[ipsi].initZeros(crop_box, crop_box); + rotated_scores_perline[ipsi].setXmippOrigin(); + rotated_scores[ipsi].initZeros(crop_box/shift_step, crop_box/shift_step); + rotated_scores[ipsi].setXmippOrigin(); + + } + if (myverb) progress_bar(nr_psi); + + // Prepare all the transformers for the 1D lines + MultidimArray oneline_tmp(ilengthmax); + for (int i = 0; i < nr_threads; i++) + transformer[i].setReal(oneline_tmp); + + // Now loop over all positions to calculate 1D FFTs + if (myverb) + { + std::cout << " - Searching over all coordinates ..." << std::endl; + init_progress_bar(nr_psi); + } + + int my_skip_sides = ilengthmax/2; + for (int ipsi = 0; ipsi < nr_psi; ipsi++) + { +#pragma omp parallel for num_threads(nr_threads) + for (int ypos = my_skip_sides; ypos < down_ysize - my_skip_sides; ypos += 1) + { + int cen_ypos = ypos - down_ysize/2; + const int tid = omp_get_thread_num(); + MultidimArray oneline(ilengthmax); + MultidimArray FTline(ilengthmax/2 + 1); + + for (int xpos = my_skip_sides; xpos < down_xsize - my_skip_sides; xpos += 1) + { + int cen_xpos = xpos - down_xsize/2; + + // Grab the line from the rotated image, in X and in Y directions + for (int iline = 0; iline < ilengthmax; iline++) + DIRECT_A1D_ELEM(oneline, iline) = A2D_ELEM(rotated_imgs[ipsi], cen_ypos, cen_xpos+iline-ilengthmax/2); + + transformer[tid].FourierTransform(oneline, FTline, false); + + //std::cerr << " ypos= "<< cen_ypos <<" "<< cen_xpos << std::endl; + for (int isig = imin_signal; isig <= imax_signal; isig++) + { + A2D_ELEM(rotated_scores_perline[ipsi], cen_ypos, cen_xpos) += norm(DIRECT_A1D_ELEM(FTline, isig)); + } + + } // end loop ypos + } // end for xpos + + //Image It0; + //It0()= rotated_scores_perline[ipsi]; + //FileName fnt0="It0_psi"+ integerToString(ipsi)+".spi"; + //It0.write(fnt0); + //std::cerr <<" written: "< It; + //It()= rotated_scores[ipsi]; + //FileName fnt="It_psi"+ integerToString(ipsi)+".spi"; + //It.write(fnt); + //std::cerr <<" written: "< A2D_ELEM(Mscore, cen_ypos, cen_xpos)) + { + A2D_ELEM(Mscore, cen_ypos, cen_xpos) = myscore; + A2D_ELEM(Mangle, cen_ypos, cen_xpos) = mypsi; + } + + } + } + } + if (myverb) progress_bar(nr_psi); + + // Re-scale and re-box image to original size + int newsize = (XSIZE(Mangle)*down_angpix) / angpix; + if (newsize%2 !=0) newsize++; + resizeMap(Mangle, newsize); + resizeMap(Mscore, newsize); + Mangle.setXmippOrigin(); + Mscore.setXmippOrigin(); + Mangle.window(STARTINGY(image), STARTINGX(image), FINISHINGY(image), FINISHINGX(image)); + int skip_sides = ROUND(length/(angpix*2.)); + Mscore.window(STARTINGY(image), STARTINGX(image), FINISHINGY(image), FINISHINGX(image)); + RFLOAT sum = 0., sum2 = 0., nn= 0.; + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(Mscore) + { + if (i > skip_sides && i < YSIZE(Mscore) - skip_sides && + j > skip_sides && j < XSIZE(Mscore) - skip_sides) + { + sum += DIRECT_A2D_ELEM(Mscore, i, j); + sum2 += DIRECT_A2D_ELEM(Mscore, i, j) * DIRECT_A2D_ELEM(Mscore, i, j); + nn+= 1.; + } + else + { + DIRECT_A2D_ELEM(Mscore, i, j) = 0.; + } + } + sum /= nn; + sum2 /= nn; + RFLOAT stddev = sqrt(sum2 - sum * sum); + if (stddev <= 0.) REPORT_ERROR("ERROR: stddev of scores= " + floatToString(stddev)); + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Mscore) + { + if (DIRECT_MULTIDIM_ELEM(Mscore, n) > 0.) + DIRECT_MULTIDIM_ELEM(Mscore, n) = (DIRECT_MULTIDIM_ELEM(Mscore, n) - sum) / stddev; + } + + +} + + +std::vector AmyloidFinder::findNextCandidateCoordinates(AmyloidCoordinate &mycoord, MultidimArray &Mscore, MultidimArray &Mpsi) +{ + + std::vector result; + + Matrix2D A2D; + Matrix1D vec_c(2), vec_p(2); + rotation2DMatrix(-mycoord.psi, A2D, false); + + for (int icoor = 0; icoor < circle.size(); icoor++) + { + // Rotate the circle-vector coordinates along the mycoord.psi + XX(vec_c) = (circle[icoor]).x; + YY(vec_c) = (circle[icoor]).y; + vec_p = A2D * vec_c; + + long int jj = ROUND(mycoord.x + XX(vec_p)); + long int ii = ROUND(mycoord.y + YY(vec_p)); + + RFLOAT myscore = A2D_ELEM(Mscore, ii, jj); + RFLOAT mypsi = A2D_ELEM(Mpsi, ii, jj); + + // Small difference in psi-angle with mycoord + RFLOAT psidiff = fabs(mycoord.psi - mypsi); + psidiff = realWRAP(psidiff, 0., 360.); + if (psidiff > 180.) + psidiff -= 180.; + if (psidiff > 90.) + psidiff -= 180.; + + // How many psi-steps away is this segment allowed to be from the previous one: just two original psi_steps? + RFLOAT max_psidiff = (2. * psi_step) + 0.1; + if (fabs(psidiff) < max_psidiff && myscore > zscore_threshold) + { + AmyloidCoordinate newcoord; + newcoord.x = mycoord.x + XX(vec_p); + newcoord.y = mycoord.y + YY(vec_p); + newcoord.psi = A2D_ELEM(Mpsi, ii, jj); + newcoord.fom = myscore; + //std::cerr << " myccf= " << myccf << " psi= " << newcoord.psi << std::endl; + result.push_back(newcoord); + } + } + + return result; + +} + + + +AmyloidCoordinate AmyloidFinder::findNextAmyloidCoordinate(AmyloidCoordinate &mycoord, MultidimArray &Mscore, MultidimArray &Mpsi) +{ + + // Return if this one has been done already. + AmyloidCoordinate result; + result.x = result.y = result.psi = 0.; + result.fom = -2.; + if (A2D_ELEM(Mscore, ROUND(mycoord.y), ROUND(mycoord.x)) < zscore_threshold) + return result; + + // Set FOM to small value (-2.) in rectangle around mycoord (filament-width wide, distance to new segment high) + Matrix2D A2D; + Matrix1D vec_c(2), vec_p(2); + rotation2DMatrix(mycoord.psi, A2D, false); + int rectangle_hwidth = CEIL(0.6 * filament_width / angpix); + int rectangle_hheight = CEIL(0.6 * nr_rungs_per_segment*amyloid_rung / angpix); + int maxr = XMIPP_MAX(rectangle_hwidth, rectangle_hheight); + for (int ii = -maxr; ii <= maxr; ii++) + { + for (int jj = -maxr; jj <= maxr; jj++) + { + XX(vec_c) = jj; + YY(vec_c) = ii; + vec_p = A2D * vec_c; + if (YY(vec_p) > -rectangle_hwidth && YY(vec_p) < rectangle_hwidth && + XX(vec_p) > -rectangle_hheight && XX(vec_p) < rectangle_hheight ) + { + long int jp = ROUND(mycoord.x + jj); + long int ip = ROUND(mycoord.y + ii); + A2D_ELEM(Mscore, ip, jp) = -2.; + } + } + } + /* + Image It; + It()=Mscore; + It.write("Mscore.spi"); + char c; + std::cerr << "press any key" << std::endl; + std::cin >> c; + */ + + // See how far we can grow in any of the circle directions. + // Note recursive calls to findNextCandidateCoordinates below. + // Let's search 3 layers deep... + std::vector new1coords; + new1coords = findNextCandidateCoordinates(mycoord, Mscore, Mpsi); + + long int N = new1coords.size(); + std::vector max_depths(N, 0); + std::vector max_sumfoms(N, -9999.); + + RFLOAT sumfom = 0.; + RFLOAT max_sumfom = -9999.; + int best_inew1=-1; + for (int inew1 = 0; inew1 < new1coords.size(); inew1++) + { + sumfom = new1coords[inew1].fom; + // Just select for highest score from all candidate coordinates + // TODO: should I avoid changes in psi instead? + if (sumfom > max_sumfom) + { + max_sumfom = sumfom; + best_inew1 = inew1; + } + + std::vector new2coords; + new2coords = findNextCandidateCoordinates(new1coords[inew1], Mscore, Mpsi); + for (int inew2 = 0; inew2 < new2coords.size(); inew2++) + { + + sumfom = new1coords[inew1].fom + new2coords[inew2].fom; + if (sumfom > max_sumfom) + { + max_sumfom = sumfom; + best_inew1 = inew1; + } + + std::vector new3coords; + new3coords = findNextCandidateCoordinates(new2coords[inew2], Mscore, Mpsi); + for (int inew3 = 0; inew3 < new3coords.size(); inew3++) + { + sumfom = new1coords[inew1].fom + new2coords[inew2].fom + new3coords[inew3].fom; + if (sumfom > max_sumfom) + { + max_sumfom = sumfom; + best_inew1 = inew1; + } + + std::vector new4coords; + new4coords = findNextCandidateCoordinates(new3coords[inew3], Mscore, Mpsi); + for (int inew4 = 0; inew4 < new4coords.size(); inew4++) + { + sumfom = new1coords[inew1].fom + new2coords[inew2].fom + new3coords[inew3].fom + new4coords[inew4].fom; + if (sumfom > max_sumfom) + { + max_sumfom = sumfom; + best_inew1 = inew1; + } + } + } + } + } + + if (best_inew1 < 0) + { + return result; + } + else + { + + RFLOAT prevpsi = (best_inew1 > 0) ? new1coords[best_inew1-1].psi : -99999.; + RFLOAT nextpsi = (new1coords.size() - best_inew1 > 1) ? new1coords[best_inew1+1].psi : -99999.; + + RFLOAT nextpsidiff = -9999., prevpsidiff=-9999.; + if (prevpsi > -999.) + { + RFLOAT psidiff = fabs(mycoord.psi - prevpsi); + psidiff = realWRAP(psidiff, 0., 360.); + if (psidiff > 180.) + psidiff -= 180.; + if (psidiff > 90.) + psidiff -= 180.; + prevpsidiff = psidiff; + } + if (nextpsi > -999.) + { + RFLOAT psidiff = fabs(mycoord.psi - nextpsi); + psidiff = realWRAP(psidiff, 0., 360.); + if (psidiff > 180.) + psidiff -= 180.; + if (psidiff > 90.) + psidiff -= 180.; + nextpsidiff = psidiff; + } + + + +/* std::cerr << " new1coords[best_inew1].fom= " << new1coords[best_inew1].fom + << " x= " << new1coords[best_inew1].x + << " y= " << new1coords[best_inew1].y + << " myx= " << mycoord.x + << " myy= " << mycoord.y + << " mypsi= " << mycoord.psi + << " new1coords[best_inew1].psi= " << new1coords[best_inew1].psi + << " prevpsi= " << prevpsi << " prevpsidiff= " << prevpsidiff + << " nextpsi= " << nextpsi << " nextpsidiff= " << nextpsidiff + << std::endl; + */ + + return new1coords[best_inew1]; + } + +} + + +RFLOAT AmyloidFinder::maxIndex_multithreaded(MultidimArray &m, long int &imax, long int &jmax) +{ + RFLOAT maxval= -99.e99; + std::vector maxval_per_thread(nr_threads, -99.e99); + std::vector maxi_per_thread(nr_threads), maxj_per_thread(nr_threads); + +#pragma omp parallel for num_threads(nr_threads) + for (long int i=STARTINGY(m); i<=FINISHINGY(m); i++) + { + const int tid = omp_get_thread_num(); + for (long int j=STARTINGX(m); j<=FINISHINGX(m); j++) + { + const int tid = omp_get_thread_num(); + RFLOAT aux = A2D_ELEM(m, i, j); + if (aux > maxval_per_thread[tid]) + { + maxval_per_thread[tid] = aux; + maxi_per_thread[tid] = i; + maxj_per_thread[tid] = j; + + } + } + } + + for (int tid = 0; tid < nr_threads; tid++) + { + if (maxval_per_thread[tid] > maxval) + { + maxval = maxval_per_thread[tid]; + imax = maxi_per_thread[tid]; + jmax = maxj_per_thread[tid]; + } + } + + return maxval; + +} + +MetaDataTable AmyloidFinder::traceFilaments(MultidimArray &Mscore, MultidimArray &Mpsi) +{ + + std::vector< std::vector > helices; + bool no_more_ccf_peaks = false; + int nr_fail = 0; + while (!no_more_ccf_peaks) + { + long int imax, jmax; + float myscore = maxIndex_multithreaded(Mscore, imax, jmax); + float mypsi = Mpsi(imax, jmax); + + // Stop searching if all pixels are below min_ccf! + //std::cerr << " myscore= " << myscore << " imax= " << imax << " jmax= " << jmax << std::endl; + //std::cerr << " helices.size()= " << helices.size() << " threshold_value= " << threshold_value << " mypsi= " << mypsi << std::endl; + // Peaks to seed new filaments have to be at least 50% higher than the threshold + if (myscore < zscore_threshold || nr_fail >250) + { + no_more_ccf_peaks = true; + } + + std::vector helix; + AmyloidCoordinate coord, newcoord; + coord.x = jmax; + coord.y = imax; + coord.fom = myscore; + coord.psi = mypsi; + coord.order = 0; + helix.push_back(coord); + + // Try grow from the start + bool is_done_start = false; + while ( !is_done_start ) + { + //std::cerr << " START-in: x= "<< helix[0].x <<" y= " << helix[0].y << " psi= " << helix[0].psi << " fom= " << helix[0].fom << std::endl; + newcoord = findNextAmyloidCoordinate(helix[0],Mscore, Mpsi); + //std::cerr << " START newcoord.x= " << newcoord.x << " newcoord.y= " << newcoord.y << " newcoord.fom= " << newcoord.fom << " helix.size()= " << helix.size() << std::endl; + if (newcoord.fom > zscore_threshold) + { + newcoord.order = helix[0].order - 1; + helix.insert(helix.begin(), newcoord); + } + else + { + is_done_start = true; + //std::cerr << " START IS DONE!" << std::endl; + } + } + + // And try grow from the end, but only if any segments were added in growing from the start + if (helix.size() > 1) + { + // Now that we're done with the START, reset the value for Qscore in the original coordinate, otherwise END won't run + A2D_ELEM(Mscore, ROUND(helix[helix.size()-1].y), ROUND(helix[helix.size()-1].x)) = helix[helix.size()-1].fom; + + bool is_done_end = false; + while ( !is_done_end ) + { + //std::cerr << " END-in: x= "<< helix[helix.size()-1].x <<" y= " << helix[helix.size()-1].y << " psi= " << helix[helix.size()-1].psi << " fom= " << helix[helix.size()-1].fom << std::endl; + newcoord = findNextAmyloidCoordinate(helix[helix.size()-1], Mscore, Mpsi); + //std::cerr << " END newcoord.x= " << newcoord.x << " newcoord.y= " << newcoord.y << " newcoord.fom= " << newcoord.fom << " helix.size()= " << helix.size() << std::endl; + if (newcoord.fom > zscore_threshold) + { + newcoord.order = helix[0].order + 1; + helix.push_back(newcoord); + } + else + { + is_done_end = true; + //std::cerr << " END IS DONE!" << std::endl; + } + + } + } + + + if (nr_rungs_per_segment * amyloid_rung * helix.size() > minimum_filament_length) + { + helices.push_back(helix); + +#ifdef DEBUG_TRACE + std::cerr << "PUSHING BACK HELIX " << helices.size() << " << WITH SIZE= " << helix.size() << std::endl; + char c; + std::cerr << " helices.size()= " << helices.size() << std::endl; + std::cerr << "press any key" << std::endl; + std::cin >> c; + Image It; + It()=Mscore; + It.write("Mscore.spi"); +#endif + + } + else + { + nr_fail++; + } + + } // end while (!no_more_ccf_peaks) + + // Now write out in a STAR file + // Write out a STAR file with the coordinates + FileName fn_tmp; + MetaDataTable MDout; + + // Only output STAR header if there are no tubes... + MDout.clear(); + MDout.addLabel(EMDL_IMAGE_COORD_X); + MDout.addLabel(EMDL_IMAGE_COORD_Y); + MDout.addLabel(EMDL_PARTICLE_AUTOPICK_FOM); + MDout.addLabel(EMDL_PARTICLE_HELICAL_TUBE_ID); + MDout.addLabel(EMDL_ORIENT_TILT_PRIOR); + MDout.addLabel(EMDL_ORIENT_PSI_PRIOR); + MDout.addLabel(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM); + MDout.addLabel(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO); + MDout.addLabel(EMDL_ORIENT_ROT_PRIOR_FLIP_RATIO); // KThurber + + + // Write out segments for all helices + int helixid = 0; + for (int ihelix = 0; ihelix < helices.size(); ihelix++) + { + // Sort all coordinates in the helix based on their position + std::sort(helices[ihelix].begin(), helices[ihelix].end(), + [](const AmyloidCoordinate& a, const AmyloidCoordinate& b) { return a.order < b.order;}); + + RFLOAT tube_length = 0.; + RFLOAT mypsi, old_mypsi; + for (long int iseg = 0; iseg < helices[ihelix].size(); iseg++) + { + + RFLOAT xpos = (helices[ihelix][iseg].x ) - (RFLOAT)(FIRST_XMIPP_INDEX(ori_xsize)); + RFLOAT ypos = (helices[ihelix][iseg].y ) - (RFLOAT)(FIRST_XMIPP_INDEX(ori_ysize)); + + // Angle with the next segment (for last one, take mypsi from previous segment!) + if (iseg < helices[ihelix].size()-1) + { + float dx = (float)(helices[ihelix][iseg+1].x - helices[ihelix][iseg].x); + float dy = (float)(helices[ihelix][iseg+1].y - helices[ihelix][iseg].y); + mypsi = -1. * RAD2DEG(atan2(dy,dx)); + if (mypsi < 0.) + { + if (fabs(mypsi+180. - old_mypsi) < 90.) + mypsi+= 180.; + } + } + else + { + mypsi = old_mypsi; + } + + MDout.addObject(); + MDout.setValue(EMDL_IMAGE_COORD_X, xpos); + MDout.setValue(EMDL_IMAGE_COORD_Y, ypos); + MDout.setValue(EMDL_PARTICLE_AUTOPICK_FOM, helices[ihelix][iseg].fom); + MDout.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, ihelix+1); // start counting at 1 + MDout.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); + MDout.setValue(EMDL_ORIENT_PSI_PRIOR, mypsi); + MDout.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, tube_length); + MDout.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, 0.5); + MDout.setValue(EMDL_ORIENT_ROT_PRIOR_FLIP_RATIO, 0.5); // KThurber + + tube_length += nr_rungs_per_segment * amyloid_rung; + } + helixid++; + } + + return MDout; + +} + + + +void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) +{ + + FileName fn_root = getOutputRootName(fn_mic); + FileName fn_fom = fn_root + "_" + fn_out + "_fom.mrc"; + FileName fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; + MultidimArray Mscore, Mangle; + + if (!do_write_intermediate || !exists(fn_fom) || !exists(fn_psi)) + { + + Image Iin; + Iin.read(fn_mic); + Iin().setXmippOrigin(); + if (XSIZE(Iin()) != ori_xsize || YSIZE(Iin()) != ori_ysize || fabs(angpix - Iin.samplingRateX()) > 0.001) + REPORT_ERROR("ERROR: incorrect size or pixel size for image " + fn_mic); + + getScoreForOneMicrograph(Iin(), Mscore, Mangle, myverb); + + if (do_write_intermediate) + { + Image Ipsi, Izscore; + Ipsi()=Mangle; + Izscore()=Mscore; + Ipsi.write(fn_psi); + Izscore.write(fn_fom); + } + } + else + { + Image Ipsi, Izscore; + Ipsi.read(fn_psi); + Izscore.read(fn_fom); + Mangle=Ipsi(); + Mscore=Izscore(); + Mangle.setXmippOrigin(); + Mscore.setXmippOrigin(); + } + + if (myverb) + { + std::cout << " - Tracing filaments ..." << std::endl; + init_progress_bar(1); + } + MetaDataTable MDmic = traceFilaments(Mscore, Mangle); + if (myverb) progress_bar(1); + + if (MDmic.numberOfObjects() > 0) + { + if (myverb) std::cout << "Picked " << MDmic.numberOfObjects() << " particles " << std::endl; + FileName fn_pick = getOutputRootName(fn_mic) + "_" + fn_out + ".star"; + MDmic.write(fn_pick); + } + + if (myverb) std::cout << "done!" << std::endl; + + + +} + + +void AmyloidFinder::run() +{ + int barstep; + if (verb > 0) + { + std::cout << " Finding amyloids ..." << std::endl; + init_progress_bar(fn_micrographs.size()); + barstep = XMIPP_MAX(1, fn_micrographs.size() / 60); + } + + FileName fn_olddir=""; + for (long int imic = 0; imic < fn_micrographs.size(); imic++) + { + + // Abort through the pipeline_control system + if (pipeline_control_check_abort_job()) + exit(RELION_EXIT_ABORTED); + + if (verb > 0 && imic % barstep == 0) + progress_bar(imic); + + // Check new-style outputdirectory exists and make it if not! + FileName fn_oroot = getOutputRootName(fn_micrographs[imic]); + FileName fn_dir = fn_oroot.beforeLastOf("/"); + if (fn_dir != fn_olddir) + { + // Make a Particles directory + mktree(fn_dir); + fn_olddir = fn_dir; + } +#ifdef TIMING + timer.tic(TIMING_A5); +#endif + processOneMicrograph(fn_micrographs[imic], fn_micrographs.size()==1); + +#ifdef TIMING + timer.toc(TIMING_A5); +#endif + } + + if (verb > 0) + progress_bar(fn_micrographs.size()); + +} + +void AmyloidFinder::finalise() +{ + + long int barstep = XMIPP_MAX(1, fn_ori_micrographs.size() / 60); + if (verb > 0) + { + std::cout << " Generating output list of coordinate files ... " << std::endl; + init_progress_bar(fn_ori_micrographs.size()); + } + + MetaDataTable MDcoords; + MetaDataTable MDresult; + long total_nr_picked = 0; + int nr_coord_files = 0; + for (long int imic = 0; imic < fn_ori_micrographs.size(); imic++) + { + MetaDataTable MD; + FileName fn_pick = getOutputRootName(fn_ori_micrographs[imic]) + "_" + fn_out + ".star"; + if (exists(fn_pick)) + { + + MDcoords.addObject(); + MDcoords.setValue(EMDL_MICROGRAPH_NAME, fn_ori_micrographs[imic]); + MDcoords.setValue(EMDL_MICROGRAPH_COORDINATES, fn_pick); + nr_coord_files++; + + MD.read(fn_pick); + long nr_pick = MD.numberOfObjects(); + total_nr_picked += nr_pick; + if (MD.containsLabel(EMDL_PARTICLE_AUTOPICK_FOM)) + { + RFLOAT fom, avg_fom = 0.; + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MD) + { + MD.getValue(EMDL_PARTICLE_AUTOPICK_FOM, fom); + avg_fom += fom; + } + avg_fom /= nr_pick; + // mis-use MetadataTable to conveniently make histograms and value-plots + MDresult.addObject(); + MDresult.setValue(EMDL_MICROGRAPH_NAME, fn_ori_micrographs[imic]); + MDresult.setValue(EMDL_PARTICLE_AUTOPICK_FOM, avg_fom); + MDresult.setValue(EMDL_MLMODEL_GROUP_NR_PARTICLES, nr_pick); + } + } + + if (verb > 0 && imic % 60 == 0) progress_bar(imic); + + } + + + FileName fn_coords = fn_odir + fn_out + ".star"; + MDcoords.setName("coordinate_files"); + MDcoords.write(fn_coords); + + if (verb > 0 ) + { + progress_bar(fn_ori_micrographs.size()); + std::cout << " Saved list with " << nr_coord_files << " coordinate files in: " << fn_coords << std::endl; + } + + if (verb > 0) + { + std::cout << " Total number of particles from " << fn_ori_micrographs.size() << " micrographs is " << total_nr_picked << std::endl; + + long avg = 0; + if (fn_ori_micrographs.size() > 0) avg = ROUND((RFLOAT)total_nr_picked/fn_ori_micrographs.size()); + std::cout << " i.e. on average there were " << avg << " particles per micrograph" << std::endl; + + std::cout << " Now generating logfile.pdf ... " << std::endl; + } + + // Values for all micrographs + FileName fn_eps; + std::vector all_fn_eps; + std::vector histX, histY; + + MDresult.write(fn_odir + "summary.star"); + CPlot2D *plot2Db=new CPlot2D("Nr of picked particles for all micrographs"); + MDresult.addToCPlot2D(plot2Db, EMDL_UNDEFINED, EMDL_MLMODEL_GROUP_NR_PARTICLES, 1.); + plot2Db->SetDrawLegend(false); + fn_eps = fn_odir + "all_nr_parts.eps"; + plot2Db->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2Db; + if (MDresult.numberOfObjects() > 3) + { + CPlot2D *plot2D=new CPlot2D(""); + MDresult.columnHistogram(EMDL_MLMODEL_GROUP_NR_PARTICLES,histX,histY,0, plot2D); + fn_eps = fn_odir + "histogram_nrparts.eps"; + plot2D->SetTitle("Histogram of nr of picked particles per micrograph"); + plot2D->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2D; + } + + CPlot2D *plot2Dc=new CPlot2D("Average autopick FOM for all micrographs"); + MDresult.addToCPlot2D(plot2Dc, EMDL_UNDEFINED, EMDL_PARTICLE_AUTOPICK_FOM, 1.); + plot2Dc->SetDrawLegend(false); + fn_eps = fn_odir + "all_FOMs.eps"; + plot2Dc->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2Dc; + if (MDresult.numberOfObjects() > 3) + { + CPlot2D *plot2Dd=new CPlot2D(""); + MDresult.columnHistogram(EMDL_PARTICLE_AUTOPICK_FOM,histX,histY,0, plot2Dd); + fn_eps = fn_odir + "histogram_FOMs.eps"; + plot2Dd->SetTitle("Histogram of average autopick FOM per micrograph"); + plot2Dd->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2Dd; + } + + joinMultipleEPSIntoSinglePDF(fn_odir + "logfile.pdf", all_fn_eps); + +} \ No newline at end of file diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h new file mode 100644 index 000000000..df9a3db22 --- /dev/null +++ b/src/amyloid_finder.h @@ -0,0 +1,145 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ +#ifndef RELION_AMYLOID_FINDER_H +#define RELION_AMYLOID_FINDER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +struct AmyloidCoordinate +{ + RFLOAT x, y, psi, fom, order; +}; + +class AmyloidFinder +{ +public: + + // I/O Parser + IOParser parser; + + // Verbosity + int verb; + + // Output rootname + FileName fn_in, fn_odir, fn_out; + + // Psi sampling step + RFLOAT psi_step; + + // Original pixel size, and the downscaled one + RFLOAT angpix, down_angpix; + + // Fourier shells for amyloid signal + RFLOAT signal_minres, signal_maxres; + + // Threshold for selecting peaks in Zscore map + RFLOAT zscore_threshold; + + // Number of new amyloid rungs per segment (particle) + int nr_rungs_per_segment; + + // Allowed psi-deviation per segment (particle) + RFLOAT psidiff_per_segment; + + // Minimum width and length of filaments (in A) for tracing of segments + RFLOAT minimum_filament_length, filament_width; + + // Width and length of the filament searching motifs (in A) + int width, length; + + // Sampling of positions + int shift_step, ori_xsize, ori_ysize, down_xsize, down_ysize; + + // Number of psi samplings + int nr_psi; + + //Number of openMP trhreads + int nr_threads; + + // Some public parameters + int iwidthmax, ilengthmax, imin_signal, imax_signal, large_box, crop_box; + RFLOAT amyloid_rung; + std::vector circle; + + // All micrographs to autopick from + std::vector fn_micrographs, fn_ori_micrographs; + + // Continue an old run: only estimate CTF if logfile WITH Final Values line does not yet exist, otherwise skip the tomogram + bool do_only_unfinished; + + // Write out intermediate FOM and Psi maps? + bool do_write_intermediate; + + // Is there any work to be done? + bool todo_anything; + + +public: + // Read command line arguments + void read(int argc, char **argv, int rank = 0); + + // Print usage instructions + void usage(); + + // Initialise some stuff after reading + void initialise(bool is_leader = true); + + // Get the output coordinate filename given the micrograph filename + FileName getOutputRootName(FileName fn_mic); + + // Make sure all pieces of code use same psi angles from ipsi + RFLOAT getPsiAngle(int ipsi); + + // Loop over all psi-angles and coordinates to get accumulated score and angle image for a given micrograph + void getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, MultidimArray &Mangle, bool myverb = false); + + // Find next candidate coordinates around a previous coordinate + std::vector findNextCandidateCoordinates(AmyloidCoordinate &mycoord, MultidimArray &Mscore, MultidimArray &Mpsi); + + // Find next segment in an amyloid + AmyloidCoordinate findNextAmyloidCoordinate(AmyloidCoordinate &mycoord, MultidimArray &Mscore, MultidimArray &Mpsi); + + // Multi-threaded maxIndex + RFLOAT maxIndex_multithreaded(MultidimArray &Mscore, long int &imax, long int &jmax); + + // Trace individual segments in the images based on the scores and angles + MetaDataTable traceFilaments(MultidimArray &Mscore, MultidimArray &Mpsi); + + // Run on one micrograph + void processOneMicrograph(FileName fn_mic, bool myverb = false); + + // Execute all CTFFIND jobs to get CTF parameters + void run(); + + // Write out combined star files, etc + void finalise(); + + +}; + + +#endif //RELION_AMYLOID_FINDER_H diff --git a/src/amyloid_finder_mpi.cpp b/src/amyloid_finder_mpi.cpp new file mode 100644 index 000000000..cac8fc0ce --- /dev/null +++ b/src/amyloid_finder_mpi.cpp @@ -0,0 +1,90 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ + +#include "src/amyloid_finder_mpi.h" + +void AmyloidFinderMpi::read(int argc, char **argv) +{ + // Define a new MpiNode + node = new MpiNode(argc, argv); + + if (node->isLeader()) + PRINT_VERSION_INFO(); + + // First read in non-parallelisation-dependent variables + AmyloidFinder::read(argc, argv); + + // Don't put any output to screen for mpi followers + if (!node->isLeader()) + verb = 0; + + if (do_write_intermediate && node->isLeader()) + std::cerr << "WARNING : --write_intermediates is heavy on disc I/O and is not advised in parallel execution." << std::endl; + + // Possibly also read parallelisation-dependent variables here + + // Print out MPI info + printMpiNodesMachineNames(*node); +} + +void AmyloidFinderMpi::run() +{ + // Each node does part of the work + long int my_first_micrograph, my_last_micrograph, my_nr_micrographs; + divide_equally(fn_micrographs.size(), node->size, node->rank, my_first_micrograph, my_last_micrograph); + my_nr_micrographs = my_last_micrograph - my_first_micrograph + 1; + + int barstep; + if (verb > 0) + { + std::cout << " Finding amyloids ..." << std::endl; + init_progress_bar(my_nr_micrographs); + barstep = XMIPP_MAX(1, my_nr_micrographs / 60); + } + + FileName fn_olddir=""; + for (long int imic = my_first_micrograph; imic <= my_last_micrograph; imic++) + { + // Abort through the pipeline_control system + if (pipeline_control_check_abort_job()) + MPI_Abort(MPI_COMM_WORLD, RELION_EXIT_ABORTED); + + // Check new-style outputdirectory exists and make it if not! + FileName fn_dir = getOutputRootName(fn_micrographs[imic]); + fn_dir = fn_dir.beforeLastOf("/"); + if (fn_dir != fn_olddir) + { + // Make a Particles directory + mktree(fn_dir); + fn_olddir = fn_dir; + } + + processOneMicrograph(fn_micrographs[imic], fn_micrographs.size()==1); + + if (verb > 0 && ((imic-my_first_micrograph) % barstep == 0)) + { + progress_bar(imic-my_first_micrograph); + } + + } + + if (verb > 0) + progress_bar(my_nr_micrographs); +} diff --git a/src/amyloid_finder_mpi.h b/src/amyloid_finder_mpi.h new file mode 100644 index 000000000..025a57df3 --- /dev/null +++ b/src/amyloid_finder_mpi.h @@ -0,0 +1,49 @@ +/* + * autopicker_mpi.h + * + * Created on: Sep 18, 2013 + * Author: "Sjors H.W. Scheres" + */ + +#ifndef AMYLOID_FINDER_MPI_H_ +#define AMYLOID_FINDER_MPI_H_ + +#include "src/mpi.h" +#include "src/amyloid_finder.h" +#include "src/parallel.h" + +class AmyloidFinderMpi: public AmyloidFinder +{ +private: + MpiNode *node; + +public: + /** Destructor, calls MPI_Finalize */ + ~AmyloidFinderMpi() + { + delete node; + } + + /** Read + * This could take care of mpi-parallelisation-dependent variables + */ + void read(int argc, char **argv); + + // Set device-affinity + void deviceInitialise(); + + // Parallelized run function + void run(); + + int getRank() + { + return(node->rank); + } + + MpiNode * getNode() + { + return(node); + } +}; + +#endif /* AMYLOID_FINDER_MPI_H_ */ diff --git a/src/apps/find_amyloid.cpp b/src/apps/find_amyloid.cpp new file mode 100644 index 000000000..737185be0 --- /dev/null +++ b/src/apps/find_amyloid.cpp @@ -0,0 +1,51 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ +#include + +int main(int argc, char *argv[]) +{ + AmyloidFinder prm; + + try + { + prm.read(argc, argv); + + prm.initialise(); + + prm.run(); + + prm.finalise(); + +#ifdef TIMING + std::cout << "timings:" << std::endl; + prm.timer.printTimes(false); +#endif + } + catch (RelionError XE) + { + //prm.usage(); + std::cerr << XE; + + return RELION_EXIT_FAILURE; + } + + return RELION_EXIT_SUCCESS; +} + diff --git a/src/apps/find_amyloid_mpi.cpp b/src/apps/find_amyloid_mpi.cpp new file mode 100644 index 000000000..0c4629959 --- /dev/null +++ b/src/apps/find_amyloid_mpi.cpp @@ -0,0 +1,51 @@ +/*************************************************************************** + * + * Author: "Sjors H.W. Scheres" + * MRC Laboratory of Molecular Biology + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This complete copyright notice must be included in any revised version of the + * source code. Additional authorship citations may be added, but existing + * author citations must be preserved. + ***************************************************************************/ +#include + +int main(int argc, char *argv[]) +{ + AmyloidFinderMpi prm; + + try + { + prm.read(argc, argv); + + prm.initialise(prm.getRank()); + + prm.run(); + + MPI_Barrier(MPI_COMM_WORLD); + + prm.finalise(); + + } + + catch (RelionError XE) + { + if (prm.verb > 0) std::cerr << XE; + MPI_Abort(MPI_COMM_WORLD, RELION_EXIT_FAILURE); + + return RELION_EXIT_FAILURE; + } + + MPI_Barrier(MPI_COMM_WORLD); + return RELION_EXIT_SUCCESS; +} + diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 2b26ff1a4..94a16feb1 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -767,6 +767,7 @@ void JobWindow::initialiseAutopickWindow() place("do_refs", TOGGLE_DEACTIVATE); place("do_log", TOGGLE_DEACTIVATE); place("do_topaz", TOGGLE_DEACTIVATE); + place("do_amyloid", TOGGLE_DEACTIVATE); place("continue_manual", TOGGLE_REACTIVATE); tab1->end(); @@ -938,7 +939,7 @@ void JobWindow::initialiseAutopickWindow() current_y += STEPY/2; - place("do_amyloid"); + place("amyloid_threshold"); group4->end(); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 9d0e3044f..0195654a8 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -810,7 +810,7 @@ void RelionJob::initialise(int _job_type) else if (type == PROC_AUTOPICK) { has_mpi = true; - has_thread = false; + has_thread = true; initialiseAutopickJob(); } else if (type == PROC_EXTRACT) @@ -2036,18 +2036,21 @@ The samplings are approximate numbers and vary slightly over the sphere.\n\n For joboptions["gpu_ids"] = JobOption("Which GPUs to use:", std::string(""), "This argument is not necessary. If left empty, the job itself will try to allocate available GPU resources. You can override the default allocation by providing a list of which GPUs (0,1,2,3, etc) to use. MPI-processes are separated by ':'. For example: 0:1:0:1:0:1"); joboptions["do_pick_helical_segments"] = JobOption("Pick 2D helical segments?", false, "Set to Yes if you want to pick 2D helical segments. Note this will run the old algorithms for reference-based helical segment picking, as described by He & Scheres, J Struct Biol, 2017. Often, we now run filament picking from the Topaz tab instead...."); - joboptions["do_amyloid"] = JobOption("Pick amyloid segments?", false, "Set to Yes if you want to use the algorithm that was developed specifically for picking amyloids."); + joboptions["do_amyloid"] = JobOption("Pick amyloid segments?", false, "Set to Yes if you want to use the find_amyloid program that was developed specifically for picking amyloids. Note this is the only option that will use threads!"); joboptions["helical_tube_outer_diameter"] = JobOption("Tube diameter (A): ", 200, 100, 1000, 10, "Outer diameter (in Angstroms) of helical tubes. \ This value should be slightly larger than the actual width of the tubes."); - joboptions["helical_nr_asu"] = JobOption("Number of unique asymmetrical units:", 1, 1, 100, 1, "Number of unique helical asymmetrical units in each segment box. This integer should not be less than 1. The inter-box distance (pixels) = helical rise (Angstroms) * number of asymmetrical units / pixel size (Angstroms). \ + joboptions["helical_nr_asu"] = JobOption("Number of unique asymmetrical units:", 3, 1, 100, 1, "Number of unique helical asymmetrical units in each segment box. This integer should not be less than 1. The inter-box distance (pixels) = helical rise (Angstroms) * number of asymmetrical units / pixel size (Angstroms). \ The optimal inter-box distance might also depend on the box size, the helical rise and the flexibility of the structure. In general, an inter-box distance of ~10% * the box size seems appropriate."); - joboptions["helical_rise"] = JobOption("Helical rise (A):", -1, 0, 100, 0.01, "Helical rise in Angstroms. (Please click '?' next to the option above for details about how the inter-box distance is calculated.)"); - joboptions["helical_tube_kappa_max"] = JobOption("Maximum curvature (kappa): ", 0.1, 0.05, 0.5, 0.01, "Maximum curvature allowed for picking helical tubes. \ + joboptions["helical_rise"] = JobOption("Helical rise (A):", 4.75, 0, 100, 0.01, "Helical rise in Angstroms. (Please click '?' next to the option above for details about how the inter-box distance is calculated.)"); + joboptions["helical_tube_kappa_max"] = JobOption("Maximum curvature (kappa): ", 0.07, 0.05, 0.5, 0.01, "Maximum curvature allowed for picking helical tubes. \ Kappa = 0.3 means that the curvature of the picked helical tubes should not be larger than 30% the curvature of a circle (diameter = particle mask diameter). \ Kappa ~ 0.05 is recommended for long and straight tubes (e.g. TMV, VipA/VipB and AChR tubes) while 0.20 ~ 0.40 seems suitable for flexible ones (e.g. ParM and MAVS-CARD filaments)."); - joboptions["helical_tube_length_min"] = JobOption("Minimum length (A): ", -1, 100, 1000, 10, "Minimum length (in Angstroms) of helical tubes for auto-picking. \ + joboptions["helical_tube_length_min"] = JobOption("Minimum length (A): ", 400, 100, 1000, 10, "Minimum length (in Angstroms) of helical tubes for auto-picking. \ Helical tubes with shorter lengths will not be picked. Note that a long helical tube seen by human eye might be treated as short broken pieces due to low FOM values or high picking threshold."); + joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 2, 0.3, 5, 0.1, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); + + } bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector &commands, @@ -2146,7 +2149,8 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector 1) command="`which relion_autopick_mpi`"; else @@ -2157,11 +2161,12 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector 1) + command="`which relion_find_amyloid_mpi`"; + else + command="`which relion_find_amyloid`"; + if (error_message != "") return false; + + + command += " --odir " + outputname; + command += " --pickname autopick"; + + command += " --i " + joboptions["fn_input_autopick"].getString(); + Node node(joboptions["fn_input_autopick"].getString(), joboptions["fn_input_autopick"].node_type); + inputNodes.push_back(node); + + // Output new version: no longer save coords_suffix nodetype, but 2-column list of micrographs and coordinate files + Node node3(outputname + "amypick.star", LABEL_AUTOPICK_COORDS); + outputNodes.push_back(node3); + + // PDF with histograms of the eigenvalues + Node node3b(outputname + "logfile.pdf", LABEL_AUTOPICK_LOG); + outputNodes.push_back(node3b); + + command += " --min_filament_length " + joboptions["helical_tube_length_min"].getString(); + command += " --filament_width " + joboptions["helical_tube_outer_diameter"].getString(); + command += " --rungs_per_segment " + joboptions["helical_nr_asu"].getString(); + command += " --kappa " + joboptions["helical_tube_kappa_max"].getString(); + command += " --threshold " + joboptions["amyloid_threshold"].getString(); + + command += " --j " + joboptions["nr_threads"].getString(); + + + } else if (joboptions["do_refs"].getBoolean()) { if (joboptions["do_ref3d"].getBoolean()) @@ -2408,7 +2450,7 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Fri, 22 Nov 2024 14:48:33 +0000 Subject: [PATCH 002/189] updated find_amyloid --- src/amyloid_finder.cpp | 242 +++++++++++++++++++++++++---------------- src/amyloid_finder.h | 15 ++- src/pipeline_jobs.cpp | 2 +- 3 files changed, 157 insertions(+), 102 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 010b5d851..e25ac3d18 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -35,22 +35,22 @@ void AmyloidFinder::read(int argc, char **argv, int rank) int search_section = parser.addSection("Filament searching options "); psi_step = textToFloat(parser.getOption("--psi_step", "Angular sampling rate (in degrees)", "10.")); shift_step = textToInteger(parser.getOption("--shift_step", "Step in shifts to search (in downscaled pixels)", "5")); - width = textToFloat(parser.getOption("--width", "Width of searching image (in A)", "100")); - length = textToFloat(parser.getOption("--length", "Length of searching image (in A)", "300")); + filament_width = textToFloat(parser.getOption("--filament_width", "Width of searching image (in A)", "200")); + filament_length = textToFloat(parser.getOption("--filament_length", "Length of searching image (in A)", "400")); int pick_section = parser.addSection("Filament tracing options "); - zscore_threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "2.")); - minimum_filament_length = textToFloat(parser.getOption("--min_filament_length", "Minimum length of filaments to trace (in A)", "400.")); - filament_width = textToFloat(parser.getOption("--filament_width", "Minimum distance between two traced filaments (in A)", "200.")); + zscore_threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "1.")); nr_rungs_per_segment = textToInteger(parser.getOption("--rungs_per_segment", "Number of new amyloid rungs per segment", "3")); + min_filament_length = textToFloat(parser.getOption("--min_filament_length", "Minimum length of traced filaments (in A)", "300")); RFLOAT kappa = textToFloat(parser.getOption("--kappa", "Curvature parameter kappa ", "0.07")); - if (kappa > 0.1) + if (kappa > 0.101) REPORT_ERROR("ERROR: for amyloids you cannot use kappa larger than 0.1!"); psidiff_per_segment = RAD2DEG(kappa*2.); int expert_section = parser.addSection("Expert options (typically no need to change)"); signal_minres = textToFloat(parser.getOption("--signal_minres", "Minimum resolution value for signal (in A)", "4.85")); signal_maxres = textToFloat(parser.getOption("--signal_maxres", "Maximum resolution value for signal (in A)", "4.65")); + inifactor_threshold = textToFloat(parser.getOption("--inifactor_threshold", "What fraction of the Z-score threshold for allowing to grow filaments?", "0.5")); down_angpix = textToFloat(parser.getOption("--down_angpix", "Pixel size for downscaled images (needs to include signal frequency!)", "2.25")); angpix = textToFloat(parser.getOption("--force_angpix", "Force this pixel size, regardless of what is in the image header", "-1")); @@ -72,6 +72,10 @@ void AmyloidFinder::initialise(bool is_leader) if (fn_odir[fn_odir.length()-1] != '/') fn_odir += "/"; + // Make sure inifactor is <0.1] + if (inifactor_threshold > 1 || inifactor_threshold <= 0.) + REPORT_ERROR("ERROR: --inifactor_threshold should be between <0,1]"); + fn_micrographs.clear(); if (fn_in.isStarFile()) { @@ -138,8 +142,8 @@ void AmyloidFinder::initialise(bool is_leader) if (angpix > down_angpix) REPORT_ERROR("ERROR: this program requires input images with a pixel size of at least down_angpix (" + floatToString(down_angpix) + ")!"); // Width and length in the downscaled pixels - iwidthmax = ROUND(width / down_angpix ); - ilengthmax = CEIL(length/ down_angpix ); + iwidthmax = ROUND(filament_width / down_angpix ); + ilengthmax = CEIL(filament_length/ down_angpix ); down_xsize = FLOOR( (ori_xsize * angpix) / down_angpix ); down_ysize = FLOOR( (ori_ysize * angpix) / down_angpix ); @@ -152,7 +156,9 @@ void AmyloidFinder::initialise(bool is_leader) imax_signal = CEIL(ilengthmax*down_angpix/signal_maxres); // Box size, orginal and cropped: set size of rectangular image to largest dimension - large_box = XMIPP_MAX(ori_xsize, ori_ysize); + large_box = 1.1*XMIPP_MAX(ori_xsize, ori_ysize); + large_box += ROUND(XMIPP_MAX(filament_width, filament_length)/angpix); + if (large_box%2 != 0) large_box++; // Also calculate size of cropped box: crop_box = large_box * angpix/down_angpix; if (crop_box%2 != 0) crop_box++; @@ -178,17 +184,15 @@ void AmyloidFinder::initialise(bool is_leader) float myradb2 = (float)myradb * (float)myradb; for (int ii = -myradb; ii <= myradb; ii++) { - for (int jj = -myradb; jj <= myradb; jj++) + for (int jj = 0; jj <= myradb; jj++) { float r2 = (float)(ii*ii) + (float)(jj*jj); if (r2 > myrad2 && r2 <= myradb2) { float myang = RAD2DEG(atan2((float)(ii),(float)(jj))); - if (myang > 90.) - myang -= 180.; - if (myang < -90.) - myang += 180.; - if (fabs(myang) < psidiff_per_segment) + if (myang > 90.) myang -= 180.; + if (myang < -90.) myang += 180.; + if (myang < 90. && fabs(myang) < psidiff_per_segment) { AmyloidCoordinate circlecoord; circlecoord.x = (RFLOAT)jj; @@ -218,9 +222,37 @@ RFLOAT AmyloidFinder::getPsiAngle(int ipsi) return 2.3 + ipsi * psi_step; } +RFLOAT AmyloidFinder::getPsiDiff(RFLOAT psi1, RFLOAT psi2) +{ + RFLOAT psidiff = fabs(psi1 - psi2); + if (psidiff > 90.) psidiff -= 180.; + return fabs(psidiff); + +} + void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, MultidimArray &Mangle, bool myverb) { + MultidimArray Mbig(large_box, large_box); + Mbig.setXmippOrigin(); + for (long int i=STARTINGY(Mbig); i<=FINISHINGY(Mbig); i++) + { + long int ip = i; + //if (i < STARTINGY(image)) ip += YSIZE(image); + //else if (i > FINISHINGY(image)) ip -= YSIZE(image); + if (i < STARTINGY(image)) ip = 2*STARTINGY(image) - i; + else if (i > FINISHINGY(image)) ip = 2*FINISHINGY(image) - i; + + for (long int j=STARTINGX(Mbig); j<=FINISHINGX(Mbig); j++) + { + long int jp = j; + if (j < STARTINGX(image)) jp = 2*STARTINGX(image) - j; + else if (j > FINISHINGX(image)) jp = 2*FINISHINGX(image) - j; + + A2D_ELEM(Mbig, i, j) = A2D_ELEM(image, ip, jp); + } + } + // Rotate the large image, and store downscaled images by cropping their Fourier Transform std::vector > rotated_imgs(nr_psi), rotated_scores_perline(nr_psi), rotated_scores(nr_psi); std::vector transformer(nr_threads); @@ -241,7 +273,12 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi MultidimArray Mrot; Mrot.setXmippOrigin(); Mrot.initZeros(large_box, large_box); - rotate(image, Mrot, psi, 'Z', true); + rotate(Mbig, Mrot, psi, 'Z', true); + + //Image Ir; + //Ir()=Mrot; + //Ir.write("Ir_psi"+ integerToString(ipsi)+".spi"); + //std::cerr << " written: " << "Ir_psi"<< integerToString(ipsi)<<".spi" << std::endl; // Re-scale image so that Nyquist is at down_angpix MultidimArray FT, FT2; @@ -272,20 +309,20 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi init_progress_bar(nr_psi); } - int my_skip_sides = ilengthmax/2; + int my_skip_side_length = ilengthmax/2; for (int ipsi = 0; ipsi < nr_psi; ipsi++) { #pragma omp parallel for num_threads(nr_threads) - for (int ypos = my_skip_sides; ypos < down_ysize - my_skip_sides; ypos += 1) + for (int ypos = my_skip_side_length; ypos < YSIZE(rotated_imgs[ipsi]) - my_skip_side_length; ypos += 1) { - int cen_ypos = ypos - down_ysize/2; + int cen_ypos = ypos - YSIZE(rotated_imgs[ipsi])/2; const int tid = omp_get_thread_num(); MultidimArray oneline(ilengthmax); MultidimArray FTline(ilengthmax/2 + 1); - for (int xpos = my_skip_sides; xpos < down_xsize - my_skip_sides; xpos += 1) + for (int xpos = my_skip_side_length; xpos < XSIZE(rotated_imgs[ipsi]) - my_skip_side_length; xpos += 1) { - int cen_xpos = xpos - down_xsize/2; + int cen_xpos = xpos - XSIZE(rotated_imgs[ipsi])/2; // Grab the line from the rotated image, in X and in Y directions for (int iline = 0; iline < ilengthmax; iline++) @@ -311,15 +348,16 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi // Now that we have signal per individual line for each coordinate, sum over the width of the search box // The below is split in two halves, becauses otherwise cen_pos=0 may be sampled twice!!! + int my_skip_side_width = iwidthmax/2; #pragma omp parallel for num_threads(nr_threads) - for (int ypos = 0; ypos < down_ysize/2 - my_skip_sides; ypos += shift_step) + for (int ypos = 0; ypos < YSIZE(rotated_imgs[ipsi])/2 - my_skip_side_width; ypos += shift_step) { for (int ipassy = 0; ipassy < 2; ipassy++) { int cen_ypos = (ipassy == 0) ? ypos : -ypos; if (ypos == 0 && ipassy == 1) continue; - for (int xpos = 0; xpos < down_xsize/2 - my_skip_sides; xpos += shift_step) + for (int xpos = 0; xpos < XSIZE(rotated_imgs[ipsi])/2 - my_skip_side_width; xpos += shift_step) { for (int ipass = 0; ipass < 2; ipass++) { @@ -392,6 +430,51 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi } if (myverb) progress_bar(nr_psi); + // 1D-convolution in direction perpendicular to psi! + // Pre-made coordinates of a line along Y-axis + // exclude (0,0) as later in convolution all ypoints will be added to that one + std::vector > ypoints; + Matrix1D ypoint(2); + // Try twice the normal width of the filament and hope that different psi angles will limit this! + for (int iwidth = 1, ii = 0; iwidth < iwidthmax; iwidth+=shift_step, ii++) + { + YY(ypoint) = (RFLOAT)ii; + ypoints.push_back(ypoint); + YY(ypoint) = -(RFLOAT)ii; + ypoints.push_back(ypoint); + } + + MultidimArray Mconv; + Mconv.resize(Mscore); + RFLOAT max_psidiff = (1.5 * psi_step); + #pragma omp parallel for num_threads(nr_threads) + for (int ypos = 0; ypos < down_ysize; ypos ++) + { + int cen_ypos = ypos - down_ysize/2; + for (int xpos = 0; xpos < down_xsize; xpos ++) + { + int cen_xpos = xpos - down_xsize/2; + RFLOAT mypsi = A2D_ELEM(Mangle, cen_ypos, cen_xpos); + + Matrix2D A2D; + Matrix1D vec_p(2); + rotation2DMatrix(mypsi, A2D, false); + A2D_ELEM(Mconv, cen_ypos, cen_xpos) = A2D_ELEM(Mscore, cen_ypos, cen_xpos); + for (int ipoint = 0; ipoint < ypoints.size(); ipoint++) + { + vec_p = A2D * ypoints[ipoint]; + int newy = cen_ypos + ROUND(YY(vec_p)); + int newx = cen_xpos + ROUND(XX(vec_p)); + RFLOAT nearpsi = A2D_ELEM(Mangle, newy, newx); + RFLOAT psidiff = getPsiDiff(mypsi, nearpsi); + if (psidiff < max_psidiff) + A2D_ELEM(Mconv, cen_ypos, cen_xpos) += A2D_ELEM(Mscore, newy, newx); + } + } + } + Mscore=Mconv; + Mconv.clear(); + // Re-scale and re-box image to original size int newsize = (XSIZE(Mangle)*down_angpix) / angpix; if (newsize%2 !=0) newsize++; @@ -400,8 +483,9 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi Mangle.setXmippOrigin(); Mscore.setXmippOrigin(); Mangle.window(STARTINGY(image), STARTINGX(image), FINISHINGY(image), FINISHINGX(image)); - int skip_sides = ROUND(length/(angpix*2.)); Mscore.window(STARTINGY(image), STARTINGX(image), FINISHINGY(image), FINISHINGX(image)); + int skip_sides = ROUND(filament_length/(angpix*2.)); + skip_sides = 25; RFLOAT sum = 0., sum2 = 0., nn= 0.; FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(Mscore) { @@ -440,6 +524,8 @@ std::vector AmyloidFinder::findNextCandidateCoordinates(Amylo Matrix1D vec_c(2), vec_p(2); rotation2DMatrix(-mycoord.psi, A2D, false); + // How many psi-steps away is this segment allowed to be from the previous one: just a bit more than original psi_steps? + RFLOAT max_psidiff = (1.5 * psi_step); for (int icoor = 0; icoor < circle.size(); icoor++) { // Rotate the circle-vector coordinates along the mycoord.psi @@ -452,18 +538,10 @@ std::vector AmyloidFinder::findNextCandidateCoordinates(Amylo RFLOAT myscore = A2D_ELEM(Mscore, ii, jj); RFLOAT mypsi = A2D_ELEM(Mpsi, ii, jj); - - // Small difference in psi-angle with mycoord - RFLOAT psidiff = fabs(mycoord.psi - mypsi); - psidiff = realWRAP(psidiff, 0., 360.); - if (psidiff > 180.) - psidiff -= 180.; - if (psidiff > 90.) - psidiff -= 180.; - - // How many psi-steps away is this segment allowed to be from the previous one: just two original psi_steps? - RFLOAT max_psidiff = (2. * psi_step) + 0.1; - if (fabs(psidiff) < max_psidiff && myscore > zscore_threshold) + // Impose small difference in psi-angle with mycoord + RFLOAT psidiff = getPsiDiff(mycoord.psi, mypsi); + //std::cerr <<" icoor= " << icoor <<"x= "< 0) ? new1coords[best_inew1-1].psi : -99999.; - RFLOAT nextpsi = (new1coords.size() - best_inew1 > 1) ? new1coords[best_inew1+1].psi : -99999.; - - RFLOAT nextpsidiff = -9999., prevpsidiff=-9999.; - if (prevpsi > -999.) - { - RFLOAT psidiff = fabs(mycoord.psi - prevpsi); - psidiff = realWRAP(psidiff, 0., 360.); - if (psidiff > 180.) - psidiff -= 180.; - if (psidiff > 90.) - psidiff -= 180.; - prevpsidiff = psidiff; - } - if (nextpsi > -999.) - { - RFLOAT psidiff = fabs(mycoord.psi - nextpsi); - psidiff = realWRAP(psidiff, 0., 360.); - if (psidiff > 180.) - psidiff -= 180.; - if (psidiff > 90.) - psidiff -= 180.; - nextpsidiff = psidiff; - } - - - -/* std::cerr << " new1coords[best_inew1].fom= " << new1coords[best_inew1].fom - << " x= " << new1coords[best_inew1].x - << " y= " << new1coords[best_inew1].y - << " myx= " << mycoord.x - << " myy= " << mycoord.y - << " mypsi= " << mycoord.psi - << " new1coords[best_inew1].psi= " << new1coords[best_inew1].psi - << " prevpsi= " << prevpsi << " prevpsidiff= " << prevpsidiff - << " nextpsi= " << nextpsi << " nextpsidiff= " << nextpsidiff - << std::endl; - */ - return new1coords[best_inew1]; } } -RFLOAT AmyloidFinder::maxIndex_multithreaded(MultidimArray &m, long int &imax, long int &jmax) +RFLOAT AmyloidFinder::maxIndex_multithreaded(MultidimArray &m, long int &imax, long int &jmax, int skip_sides) { RFLOAT maxval= -99.e99; std::vector maxval_per_thread(nr_threads, -99.e99); std::vector maxi_per_thread(nr_threads), maxj_per_thread(nr_threads); #pragma omp parallel for num_threads(nr_threads) - for (long int i=STARTINGY(m); i<=FINISHINGY(m); i++) + for (long int i=STARTINGY(m)+skip_sides; i<=FINISHINGY(m)-skip_sides; i++) { const int tid = omp_get_thread_num(); - for (long int j=STARTINGX(m); j<=FINISHINGX(m); j++) + for (long int j=STARTINGX(m)+skip_sides; j<=FINISHINGX(m)-skip_sides; j++) { const int tid = omp_get_thread_num(); RFLOAT aux = A2D_ELEM(m, i, j); @@ -678,21 +723,20 @@ RFLOAT AmyloidFinder::maxIndex_multithreaded(MultidimArray &m, long int MetaDataTable AmyloidFinder::traceFilaments(MultidimArray &Mscore, MultidimArray &Mpsi) { - std::vector< std::vector > helices; + std::vector< std::vector > helices; bool no_more_ccf_peaks = false; int nr_fail = 0; + int skip_sides = CEIL(0.6*filament_width/angpix); while (!no_more_ccf_peaks) { long int imax, jmax; - float myscore = maxIndex_multithreaded(Mscore, imax, jmax); + float myscore = maxIndex_multithreaded(Mscore, imax, jmax, skip_sides); float mypsi = Mpsi(imax, jmax); - // Stop searching if all pixels are below min_ccf! - //std::cerr << " myscore= " << myscore << " imax= " << imax << " jmax= " << jmax << std::endl; - //std::cerr << " helices.size()= " << helices.size() << " threshold_value= " << threshold_value << " mypsi= " << mypsi << std::endl; - // Peaks to seed new filaments have to be at least 50% higher than the threshold - if (myscore < zscore_threshold || nr_fail >250) + // Peaks to seed new filaments have to be at least the zscore_threshold + if (myscore < zscore_threshold ) { + //std::cerr << " myscore= " << myscore << " nr_fail= " << nr_fail << std::endl; no_more_ccf_peaks = true; } @@ -711,8 +755,8 @@ MetaDataTable AmyloidFinder::traceFilaments(MultidimArray &Mscore, Multi { //std::cerr << " START-in: x= "<< helix[0].x <<" y= " << helix[0].y << " psi= " << helix[0].psi << " fom= " << helix[0].fom << std::endl; newcoord = findNextAmyloidCoordinate(helix[0],Mscore, Mpsi); - //std::cerr << " START newcoord.x= " << newcoord.x << " newcoord.y= " << newcoord.y << " newcoord.fom= " << newcoord.fom << " helix.size()= " << helix.size() << std::endl; - if (newcoord.fom > zscore_threshold) + //std::cerr << " START newcoord.x= " << newcoord.x << " newcoord.y= " << newcoord.y << " newcoord.fom= " << newcoord.fom << " newcoord.psi= " << newcoord.psi << " helix.size()= " << helix.size() << std::endl; + if (newcoord.fom > inifactor_threshold * zscore_threshold) { newcoord.order = helix[0].order - 1; helix.insert(helix.begin(), newcoord); @@ -735,8 +779,8 @@ MetaDataTable AmyloidFinder::traceFilaments(MultidimArray &Mscore, Multi { //std::cerr << " END-in: x= "<< helix[helix.size()-1].x <<" y= " << helix[helix.size()-1].y << " psi= " << helix[helix.size()-1].psi << " fom= " << helix[helix.size()-1].fom << std::endl; newcoord = findNextAmyloidCoordinate(helix[helix.size()-1], Mscore, Mpsi); - //std::cerr << " END newcoord.x= " << newcoord.x << " newcoord.y= " << newcoord.y << " newcoord.fom= " << newcoord.fom << " helix.size()= " << helix.size() << std::endl; - if (newcoord.fom > zscore_threshold) + //std::cerr << " END newcoord.x= " << newcoord.x << " newcoord.y= " << newcoord.y << " newcoord.fom= " << newcoord.fom << " newcoord.psi= " << newcoord.psi << " helix.size()= " << helix.size() << std::endl; + if (newcoord.fom > inifactor_threshold * zscore_threshold) { newcoord.order = helix[0].order + 1; helix.push_back(newcoord); @@ -751,7 +795,7 @@ MetaDataTable AmyloidFinder::traceFilaments(MultidimArray &Mscore, Multi } - if (nr_rungs_per_segment * amyloid_rung * helix.size() > minimum_filament_length) + if (nr_rungs_per_segment * amyloid_rung * helix.size() > min_filament_length) { helices.push_back(helix); @@ -851,8 +895,8 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) { FileName fn_root = getOutputRootName(fn_mic); - FileName fn_fom = fn_root + "_" + fn_out + "_fom.mrc"; - FileName fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; + FileName fn_fom = fn_root + "_" + fn_out + "_fom.spi"; + FileName fn_psi = fn_root + "_" + fn_out + "_psi.spi"; MultidimArray Mscore, Mangle; if (!do_write_intermediate || !exists(fn_fom) || !exists(fn_psi)) @@ -891,6 +935,14 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) std::cout << " - Tracing filaments ..." << std::endl; init_progress_bar(1); } + // set Mscore in a slightly bigger box to avoid to have to search boundary conditions + int rectangle_diam = sqrt(filament_width*filament_width + nr_rungs_per_segment*amyloid_rung*nr_rungs_per_segment*amyloid_rung)/angpix; + int larger_box_x = XSIZE(Mscore) + 1.5*rectangle_diam; + int larger_box_y = YSIZE(Mscore) + 1.5*rectangle_diam; + Mscore.window(FIRST_XMIPP_INDEX(larger_box_y), FIRST_XMIPP_INDEX(larger_box_x), LAST_XMIPP_INDEX(larger_box_y), LAST_XMIPP_INDEX(larger_box_x)); + Mscore.setXmippOrigin(); + Mangle.window(FIRST_XMIPP_INDEX(larger_box_y), FIRST_XMIPP_INDEX(larger_box_x), LAST_XMIPP_INDEX(larger_box_y), LAST_XMIPP_INDEX(larger_box_x)); + Mangle.setXmippOrigin(); MetaDataTable MDmic = traceFilaments(Mscore, Mangle); if (myverb) progress_bar(1); diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index df9a3db22..51846657b 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -57,7 +57,7 @@ class AmyloidFinder RFLOAT signal_minres, signal_maxres; // Threshold for selecting peaks in Zscore map - RFLOAT zscore_threshold; + RFLOAT zscore_threshold, inifactor_threshold; // Number of new amyloid rungs per segment (particle) int nr_rungs_per_segment; @@ -65,11 +65,11 @@ class AmyloidFinder // Allowed psi-deviation per segment (particle) RFLOAT psidiff_per_segment; - // Minimum width and length of filaments (in A) for tracing of segments - RFLOAT minimum_filament_length, filament_width; + // width and length of filaments (in A) for searching of 4.7A signal + RFLOAT filament_length, filament_width; - // Width and length of the filament searching motifs (in A) - int width, length; + // Minimum length for tracing filaments + RFLOAT min_filament_length; // Sampling of positions int shift_step, ori_xsize, ori_ysize, down_xsize, down_ysize; @@ -114,6 +114,9 @@ class AmyloidFinder // Make sure all pieces of code use same psi angles from ipsi RFLOAT getPsiAngle(int ipsi); + // Difference between two psi angles that range from 0-180 + RFLOAT getPsiDiff(RFLOAT psi1, RFLOAT psi2); + // Loop over all psi-angles and coordinates to get accumulated score and angle image for a given micrograph void getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, MultidimArray &Mangle, bool myverb = false); @@ -124,7 +127,7 @@ class AmyloidFinder AmyloidCoordinate findNextAmyloidCoordinate(AmyloidCoordinate &mycoord, MultidimArray &Mscore, MultidimArray &Mpsi); // Multi-threaded maxIndex - RFLOAT maxIndex_multithreaded(MultidimArray &Mscore, long int &imax, long int &jmax); + RFLOAT maxIndex_multithreaded(MultidimArray &Mscore, long int &imax, long int &jmax, int skip_sides = 0); // Trace individual segments in the images based on the scores and angles MetaDataTable traceFilaments(MultidimArray &Mscore, MultidimArray &Mpsi); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 0195654a8..3929ae19e 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2324,7 +2324,7 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Tue, 26 Nov 2024 11:39:45 +0000 Subject: [PATCH 003/189] cleanup up amyloid_finder code --- src/amyloid_finder.cpp | 197 +++++++++++++++++--------------------- src/amyloid_finder.h | 12 +-- src/apps/find_amyloid.cpp | 2 +- 3 files changed, 93 insertions(+), 118 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index e25ac3d18..3312af87a 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -18,6 +18,7 @@ * author citations must be preserved. ***************************************************************************/ #include "src/amyloid_finder.h" +//#define DEBUG_BOUNDS void AmyloidFinder::read(int argc, char **argv, int rank) { @@ -29,19 +30,21 @@ void AmyloidFinder::read(int argc, char **argv, int rank) fn_out = parser.getOption("--pickname", "Rootname for coordinate STAR files", "amypick"); fn_odir = parser.getOption("--odir", "Output directory for coordinate files (default is to store next to micrographs)", "AutoPick/"); do_only_unfinished = parser.checkOption("--only_do_unfinished", "Only estimate CTFs for those tomograms for which there is not yet a logfile with Final values."); - do_write_intermediate = parser.checkOption("--write_intermediates", "Write out intermediate FOM maps for fast testing of tracing parameters?"); + do_write_intermediate = parser.checkOption("--write_intermediates", "Write out intermediate FOM & PSI maps for faster testing of tracing parameters?"); + do_only_write_intermediate = parser.checkOption("--only_write_intermediates", "Write out only intermediate FOM & PSI maps?"); nr_threads = textToInteger(parser.getOption("--j", "Number of threads to us in parallel", "1")); int search_section = parser.addSection("Filament searching options "); psi_step = textToFloat(parser.getOption("--psi_step", "Angular sampling rate (in degrees)", "10.")); shift_step = textToInteger(parser.getOption("--shift_step", "Step in shifts to search (in downscaled pixels)", "5")); - filament_width = textToFloat(parser.getOption("--filament_width", "Width of searching image (in A)", "200")); - filament_length = textToFloat(parser.getOption("--filament_length", "Length of searching image (in A)", "400")); + search_filament_width = textToFloat(parser.getOption("--search_filament_width", "Width of searching image (in A)", "50")); + search_filament_length = textToFloat(parser.getOption("--search_filament_length", "Length of searching image (in A)", "250")); int pick_section = parser.addSection("Filament tracing options "); - zscore_threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "1.")); + zscore_threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "2.")); nr_rungs_per_segment = textToInteger(parser.getOption("--rungs_per_segment", "Number of new amyloid rungs per segment", "3")); - min_filament_length = textToFloat(parser.getOption("--min_filament_length", "Minimum length of traced filaments (in A)", "300")); + trace_filament_width = textToFloat(parser.getOption("--trace_filament_width", "Minimum width occupied by a traced filaments (in A)", "200")); + trace_filament_length = textToFloat(parser.getOption("--trace_filament_length", "Minimum length of traced filaments (in A)", "300")); RFLOAT kappa = textToFloat(parser.getOption("--kappa", "Curvature parameter kappa ", "0.07")); if (kappa > 0.101) REPORT_ERROR("ERROR: for amyloids you cannot use kappa larger than 0.1!"); @@ -53,7 +56,6 @@ void AmyloidFinder::read(int argc, char **argv, int rank) inifactor_threshold = textToFloat(parser.getOption("--inifactor_threshold", "What fraction of the Z-score threshold for allowing to grow filaments?", "0.5")); down_angpix = textToFloat(parser.getOption("--down_angpix", "Pixel size for downscaled images (needs to include signal frequency!)", "2.25")); angpix = textToFloat(parser.getOption("--force_angpix", "Force this pixel size, regardless of what is in the image header", "-1")); - verb =textToInteger(parser.getOption("--verb", "Verbosity", "1")); // Check for errors in the command-line option @@ -115,12 +117,10 @@ void AmyloidFinder::initialise(bool is_leader) } // If there is nothing to do, then go out of initialise - todo_anything = true; if (fn_micrographs.size() == 0) { if (verb > 0) std::cout << " + No new micrographs to do, so exiting finding amyloids ..." << std::endl; - todo_anything = false; return; } @@ -142,8 +142,8 @@ void AmyloidFinder::initialise(bool is_leader) if (angpix > down_angpix) REPORT_ERROR("ERROR: this program requires input images with a pixel size of at least down_angpix (" + floatToString(down_angpix) + ")!"); // Width and length in the downscaled pixels - iwidthmax = ROUND(filament_width / down_angpix ); - ilengthmax = CEIL(filament_length/ down_angpix ); + iwidthmax = ROUND(search_filament_width / down_angpix ); + ilengthmax = CEIL(search_filament_length / down_angpix ); down_xsize = FLOOR( (ori_xsize * angpix) / down_angpix ); down_ysize = FLOOR( (ori_ysize * angpix) / down_angpix ); @@ -156,8 +156,8 @@ void AmyloidFinder::initialise(bool is_leader) imax_signal = CEIL(ilengthmax*down_angpix/signal_maxres); // Box size, orginal and cropped: set size of rectangular image to largest dimension - large_box = 1.1*XMIPP_MAX(ori_xsize, ori_ysize); - large_box += ROUND(XMIPP_MAX(filament_width, filament_length)/angpix); + large_box = 1.05*XMIPP_MAX(ori_xsize, ori_ysize); + large_box += ROUND(XMIPP_MAX(search_filament_width, search_filament_length) / angpix); if (large_box%2 != 0) large_box++; // Also calculate size of cropped box: crop_box = large_box * angpix/down_angpix; @@ -330,7 +330,6 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi transformer[tid].FourierTransform(oneline, FTline, false); - //std::cerr << " ypos= "<< cen_ypos <<" "<< cen_xpos << std::endl; for (int isig = imin_signal; isig <= imax_signal; isig++) { A2D_ELEM(rotated_scores_perline[ipsi], cen_ypos, cen_xpos) += norm(DIRECT_A1D_ELEM(FTline, isig)); @@ -402,7 +401,7 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi selfRotate(rotated_scores[ipsi], -psi); } - Mangle.resize(rotated_imgs[0]); + Mangle.resize(down_ysize, down_xsize); Mangle.setXmippOrigin(); Mscore.resize(Mangle); @@ -428,79 +427,15 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi } } } - if (myverb) progress_bar(nr_psi); - - // 1D-convolution in direction perpendicular to psi! - // Pre-made coordinates of a line along Y-axis - // exclude (0,0) as later in convolution all ypoints will be added to that one - std::vector > ypoints; - Matrix1D ypoint(2); - // Try twice the normal width of the filament and hope that different psi angles will limit this! - for (int iwidth = 1, ii = 0; iwidth < iwidthmax; iwidth+=shift_step, ii++) - { - YY(ypoint) = (RFLOAT)ii; - ypoints.push_back(ypoint); - YY(ypoint) = -(RFLOAT)ii; - ypoints.push_back(ypoint); - } - MultidimArray Mconv; - Mconv.resize(Mscore); - RFLOAT max_psidiff = (1.5 * psi_step); - #pragma omp parallel for num_threads(nr_threads) - for (int ypos = 0; ypos < down_ysize; ypos ++) - { - int cen_ypos = ypos - down_ysize/2; - for (int xpos = 0; xpos < down_xsize; xpos ++) - { - int cen_xpos = xpos - down_xsize/2; - RFLOAT mypsi = A2D_ELEM(Mangle, cen_ypos, cen_xpos); - - Matrix2D A2D; - Matrix1D vec_p(2); - rotation2DMatrix(mypsi, A2D, false); - A2D_ELEM(Mconv, cen_ypos, cen_xpos) = A2D_ELEM(Mscore, cen_ypos, cen_xpos); - for (int ipoint = 0; ipoint < ypoints.size(); ipoint++) - { - vec_p = A2D * ypoints[ipoint]; - int newy = cen_ypos + ROUND(YY(vec_p)); - int newx = cen_xpos + ROUND(XX(vec_p)); - RFLOAT nearpsi = A2D_ELEM(Mangle, newy, newx); - RFLOAT psidiff = getPsiDiff(mypsi, nearpsi); - if (psidiff < max_psidiff) - A2D_ELEM(Mconv, cen_ypos, cen_xpos) += A2D_ELEM(Mscore, newy, newx); - } - } - } - Mscore=Mconv; - Mconv.clear(); - - // Re-scale and re-box image to original size - int newsize = (XSIZE(Mangle)*down_angpix) / angpix; - if (newsize%2 !=0) newsize++; - resizeMap(Mangle, newsize); - resizeMap(Mscore, newsize); - Mangle.setXmippOrigin(); - Mscore.setXmippOrigin(); - Mangle.window(STARTINGY(image), STARTINGX(image), FINISHINGY(image), FINISHINGX(image)); - Mscore.window(STARTINGY(image), STARTINGX(image), FINISHINGY(image), FINISHINGX(image)); - int skip_sides = ROUND(filament_length/(angpix*2.)); - skip_sides = 25; + // And normalise RFLOAT sum = 0., sum2 = 0., nn= 0.; FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(Mscore) - { - if (i > skip_sides && i < YSIZE(Mscore) - skip_sides && - j > skip_sides && j < XSIZE(Mscore) - skip_sides) - { - sum += DIRECT_A2D_ELEM(Mscore, i, j); - sum2 += DIRECT_A2D_ELEM(Mscore, i, j) * DIRECT_A2D_ELEM(Mscore, i, j); - nn+= 1.; - } - else - { - DIRECT_A2D_ELEM(Mscore, i, j) = 0.; - } - } + { + sum += DIRECT_A2D_ELEM(Mscore, i, j); + sum2 += DIRECT_A2D_ELEM(Mscore, i, j) * DIRECT_A2D_ELEM(Mscore, i, j); + nn+= 1.; + } sum /= nn; sum2 /= nn; RFLOAT stddev = sqrt(sum2 - sum * sum); @@ -512,6 +447,8 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi } + if (myverb) progress_bar(nr_psi); + } @@ -546,6 +483,14 @@ std::vector AmyloidFinder::findNextCandidateCoordinates(Amylo AmyloidCoordinate newcoord; newcoord.x = mycoord.x + XX(vec_p); newcoord.y = mycoord.y + YY(vec_p); +#ifdef DEBUG_BOUNDS + if (ii < STARTINGY(Mscore) || ii > FINISHINGY(Mscore) || + jj < STARTINGX(Mscore) || jj > FINISHINGX(Mscore)) + { + REPORT_ERROR("ERROR: out of bounds in findNextCandidateCoordinates"); + } +#endif + newcoord.psi = A2D_ELEM(Mpsi, ii, jj); newcoord.fom = myscore; //std::cerr << " myccf= " << myccf << " psi= " << newcoord.psi << std::endl; @@ -577,7 +522,7 @@ AmyloidCoordinate AmyloidFinder::findNextAmyloidCoordinate(AmyloidCoordinate &my Matrix2D A2D; Matrix1D vec_c(2), vec_p(2); rotation2DMatrix(mycoord.psi, A2D, false); - int rectangle_hwidth = CEIL(0.6 * filament_width / angpix); + int rectangle_hwidth = CEIL(0.5 * trace_filament_width / angpix); int rectangle_hheight = CEIL(0.6 * nr_rungs_per_segment*amyloid_rung / angpix); int maxr = XMIPP_MAX(rectangle_hwidth, rectangle_hheight); for (int ii = -maxr; ii <= maxr; ii++) @@ -592,6 +537,16 @@ AmyloidCoordinate AmyloidFinder::findNextAmyloidCoordinate(AmyloidCoordinate &my { long int jp = ROUND(mycoord.x + jj); long int ip = ROUND(mycoord.y + ii); +#ifdef DEBUG_BOUNDS + if (ip < STARTINGY(Mscore) || ip > FINISHINGY(Mscore) || + jp < STARTINGX(Mscore) || jp > FINISHINGX(Mscore)) + { + Mscore.printShape(); + std::cerr << " ii= " << ii << " + " << mycoord.y << " = " << ip << std::endl; + std::cerr << " jj= " << jj << " + " << mycoord.x << " = " << jp << std::endl; + REPORT_ERROR("ERROR: coordinates out of bound"); + } +#endif A2D_ELEM(Mscore, ip, jp) = -2.; } } @@ -723,16 +678,42 @@ RFLOAT AmyloidFinder::maxIndex_multithreaded(MultidimArray &m, long int MetaDataTable AmyloidFinder::traceFilaments(MultidimArray &Mscore, MultidimArray &Mpsi) { + // Re-scale and re-box image to original size + int new_box = XMIPP_MAX(YSIZE(Mscore), XSIZE(Mscore)); + Mscore.window(FIRST_XMIPP_INDEX(new_box), FIRST_XMIPP_INDEX(new_box), LAST_XMIPP_INDEX(new_box), + LAST_XMIPP_INDEX(new_box)); + Mpsi.window(FIRST_XMIPP_INDEX(new_box), FIRST_XMIPP_INDEX(new_box), LAST_XMIPP_INDEX(new_box), + LAST_XMIPP_INDEX(new_box)); + Mscore.setXmippOrigin(); + Mpsi.setXmippOrigin(); + + int newsize = (XSIZE(Mpsi)*down_angpix) / angpix; + if (newsize%2 !=0) newsize++; + resizeMap(Mscore, newsize); + resizeMap(Mpsi, newsize); + Mscore.setXmippOrigin(); + Mpsi.setXmippOrigin(); + + // Again, leave some space near the edges to account for setting the rectangles to zero + int larger_side = CEIL(1.5 * trace_filament_width / angpix); + if (larger_side%2 != 0) larger_side++; + + Mscore.window(FIRST_XMIPP_INDEX(ori_ysize + larger_side), FIRST_XMIPP_INDEX(ori_xsize + larger_side), + LAST_XMIPP_INDEX(ori_ysize + larger_side), LAST_XMIPP_INDEX(ori_xsize + larger_side)); + Mpsi.window(FIRST_XMIPP_INDEX(ori_ysize + larger_side), FIRST_XMIPP_INDEX(ori_xsize + larger_side), + LAST_XMIPP_INDEX(ori_ysize + larger_side), LAST_XMIPP_INDEX(ori_xsize + larger_side)); + Mscore.setXmippOrigin(); + Mpsi.setXmippOrigin(); + std::vector< std::vector > helices; bool no_more_ccf_peaks = false; int nr_fail = 0; - int skip_sides = CEIL(0.6*filament_width/angpix); + int skip_sides = 25; while (!no_more_ccf_peaks) { long int imax, jmax; float myscore = maxIndex_multithreaded(Mscore, imax, jmax, skip_sides); float mypsi = Mpsi(imax, jmax); - // Peaks to seed new filaments have to be at least the zscore_threshold if (myscore < zscore_threshold ) { @@ -795,7 +776,7 @@ MetaDataTable AmyloidFinder::traceFilaments(MultidimArray &Mscore, Multi } - if (nr_rungs_per_segment * amyloid_rung * helix.size() > min_filament_length) + if (nr_rungs_per_segment * amyloid_rung * helix.size() > trace_filament_length) { helices.push_back(helix); @@ -897,6 +878,7 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) FileName fn_root = getOutputRootName(fn_mic); FileName fn_fom = fn_root + "_" + fn_out + "_fom.spi"; FileName fn_psi = fn_root + "_" + fn_out + "_psi.spi"; + FileName fn_conv = fn_root + "_" + fn_out + "_conv.spi"; MultidimArray Mscore, Mangle; if (!do_write_intermediate || !exists(fn_fom) || !exists(fn_psi)) @@ -910,7 +892,7 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) getScoreForOneMicrograph(Iin(), Mscore, Mangle, myverb); - if (do_write_intermediate) + if (do_write_intermediate || do_only_write_intermediate) { Image Ipsi, Izscore; Ipsi()=Mangle; @@ -930,28 +912,25 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) Mscore.setXmippOrigin(); } - if (myverb) + if (!do_only_write_intermediate) { - std::cout << " - Tracing filaments ..." << std::endl; - init_progress_bar(1); - } - // set Mscore in a slightly bigger box to avoid to have to search boundary conditions - int rectangle_diam = sqrt(filament_width*filament_width + nr_rungs_per_segment*amyloid_rung*nr_rungs_per_segment*amyloid_rung)/angpix; - int larger_box_x = XSIZE(Mscore) + 1.5*rectangle_diam; - int larger_box_y = YSIZE(Mscore) + 1.5*rectangle_diam; - Mscore.window(FIRST_XMIPP_INDEX(larger_box_y), FIRST_XMIPP_INDEX(larger_box_x), LAST_XMIPP_INDEX(larger_box_y), LAST_XMIPP_INDEX(larger_box_x)); - Mscore.setXmippOrigin(); - Mangle.window(FIRST_XMIPP_INDEX(larger_box_y), FIRST_XMIPP_INDEX(larger_box_x), LAST_XMIPP_INDEX(larger_box_y), LAST_XMIPP_INDEX(larger_box_x)); - Mangle.setXmippOrigin(); - MetaDataTable MDmic = traceFilaments(Mscore, Mangle); - if (myverb) progress_bar(1); + if (myverb) + { + std::cout << " - Tracing filaments ..." << std::endl; + init_progress_bar(1); + } - if (MDmic.numberOfObjects() > 0) - { - if (myverb) std::cout << "Picked " << MDmic.numberOfObjects() << " particles " << std::endl; - FileName fn_pick = getOutputRootName(fn_mic) + "_" + fn_out + ".star"; - MDmic.write(fn_pick); - } + MetaDataTable MDmic = traceFilaments(Mscore, Mangle); + + if (myverb) progress_bar(1); + + if (MDmic.numberOfObjects() > 0) + { + if (myverb) std::cout << "Picked " << MDmic.numberOfObjects() << " particles " << std::endl; + FileName fn_pick = getOutputRootName(fn_mic) + "_" + fn_out + ".star"; + MDmic.write(fn_pick); + } + } if (myverb) std::cout << "done!" << std::endl; diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index 51846657b..6a7d7c6ea 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -66,10 +66,10 @@ class AmyloidFinder RFLOAT psidiff_per_segment; // width and length of filaments (in A) for searching of 4.7A signal - RFLOAT filament_length, filament_width; + RFLOAT search_filament_length, search_filament_width; // Minimum length for tracing filaments - RFLOAT min_filament_length; + RFLOAT trace_filament_length, trace_filament_width; // Sampling of positions int shift_step, ori_xsize, ori_ysize, down_xsize, down_ysize; @@ -91,12 +91,8 @@ class AmyloidFinder // Continue an old run: only estimate CTF if logfile WITH Final Values line does not yet exist, otherwise skip the tomogram bool do_only_unfinished; - // Write out intermediate FOM and Psi maps? - bool do_write_intermediate; - - // Is there any work to be done? - bool todo_anything; - + // Write out (only) intermediate FOM and Psi maps? + bool do_write_intermediate, do_only_write_intermediate; public: // Read command line arguments diff --git a/src/apps/find_amyloid.cpp b/src/apps/find_amyloid.cpp index 737185be0..3f8446592 100644 --- a/src/apps/find_amyloid.cpp +++ b/src/apps/find_amyloid.cpp @@ -31,7 +31,7 @@ int main(int argc, char *argv[]) prm.run(); - prm.finalise(); + if (!prm.do_only_write_intermediate) prm.finalise(); #ifdef TIMING std::cout << "timings:" << std::endl; From 863cef85350f957a8a2a7a47d88d4abded64e7f0 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 26 Nov 2024 12:44:28 +0000 Subject: [PATCH 004/189] output normalised scores of (max-adjusted_mean)/adjusted_mean --- src/amyloid_finder.cpp | 43 ++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 3312af87a..0520a1535 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -41,7 +41,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) search_filament_length = textToFloat(parser.getOption("--search_filament_length", "Length of searching image (in A)", "250")); int pick_section = parser.addSection("Filament tracing options "); - zscore_threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "2.")); + zscore_threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "0.5")); nr_rungs_per_segment = textToInteger(parser.getOption("--rungs_per_segment", "Number of new amyloid rungs per segment", "3")); trace_filament_width = textToFloat(parser.getOption("--trace_filament_width", "Minimum width occupied by a traced filaments (in A)", "200")); trace_filament_length = textToFloat(parser.getOption("--trace_filament_length", "Minimum length of traced filaments (in A)", "300")); @@ -53,7 +53,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) int expert_section = parser.addSection("Expert options (typically no need to change)"); signal_minres = textToFloat(parser.getOption("--signal_minres", "Minimum resolution value for signal (in A)", "4.85")); signal_maxres = textToFloat(parser.getOption("--signal_maxres", "Maximum resolution value for signal (in A)", "4.65")); - inifactor_threshold = textToFloat(parser.getOption("--inifactor_threshold", "What fraction of the Z-score threshold for allowing to grow filaments?", "0.5")); + inifactor_threshold = textToFloat(parser.getOption("--inifactor_threshold", "What fraction of the Z-score threshold for allowing to grow filaments?", "0.33")); down_angpix = textToFloat(parser.getOption("--down_angpix", "Pixel size for downscaled images (needs to include signal frequency!)", "2.25")); angpix = textToFloat(parser.getOption("--force_angpix", "Force this pixel size, regardless of what is in the image header", "-1")); verb =textToInteger(parser.getOption("--verb", "Verbosity", "1")); @@ -156,7 +156,7 @@ void AmyloidFinder::initialise(bool is_leader) imax_signal = CEIL(ilengthmax*down_angpix/signal_maxres); // Box size, orginal and cropped: set size of rectangular image to largest dimension - large_box = 1.05*XMIPP_MAX(ori_xsize, ori_ysize); + large_box = sqrt(2.)*XMIPP_MAX(ori_xsize, ori_ysize); large_box += ROUND(XMIPP_MAX(search_filament_width, search_filament_length) / angpix); if (large_box%2 != 0) large_box++; // Also calculate size of cropped box: @@ -404,8 +404,11 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi Mangle.resize(down_ysize, down_xsize); Mangle.setXmippOrigin(); Mscore.resize(Mangle); + MultidimArray Msum; + Msum.resize(Mangle); - // This can't be parallelised efficiently because need to protect Izscore from simultaneous writing... + // This can't be parallelised efficiently because need to protect Msums, Mscore and Mangle from simultaneous writing... + // Calculate Z-scores over psi: (max_psi - avg_psi) /stddev_psi for (int ipsi = 0; ipsi < nr_psi; ipsi++) { RFLOAT mypsi = getPsiAngle(ipsi); @@ -417,6 +420,7 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi int cen_xpos = xpos - down_xsize/2; RFLOAT myscore = A2D_ELEM(rotated_scores[ipsi], cen_ypos/shift_step, cen_xpos/shift_step); + A2D_ELEM(Msum, cen_ypos, cen_xpos) += myscore; if (myscore > A2D_ELEM(Mscore, cen_ypos, cen_xpos)) { @@ -428,24 +432,23 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi } } - // And normalise - RFLOAT sum = 0., sum2 = 0., nn= 0.; - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(Mscore) + // Now need to subtract the Mscore for the best psi, as the mean and stddev should be calculated for the non-signal! + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Msum) { - sum += DIRECT_A2D_ELEM(Mscore, i, j); - sum2 += DIRECT_A2D_ELEM(Mscore, i, j) * DIRECT_A2D_ELEM(Mscore, i, j); - nn+= 1.; - } - sum /= nn; - sum2 /= nn; - RFLOAT stddev = sqrt(sum2 - sum * sum); - if (stddev <= 0.) REPORT_ERROR("ERROR: stddev of scores= " + floatToString(stddev)); - FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Mscore) - { - if (DIRECT_MULTIDIM_ELEM(Mscore, n) > 0.) - DIRECT_MULTIDIM_ELEM(Mscore, n) = (DIRECT_MULTIDIM_ELEM(Mscore, n) - sum) / stddev; + DIRECT_MULTIDIM_ELEM(Msum, n) -= DIRECT_MULTIDIM_ELEM(Mscore, n); + DIRECT_MULTIDIM_ELEM(Msum, n) /= (RFLOAT)(nr_psi-1); + // calculate as normalised score: + // (max_psi - adjusted_mean_psi) / adjusted_mean_psi + // where adjusted_mean is the average of the score over all psi-values, except the maximum + if (DIRECT_MULTIDIM_ELEM(Msum, n) > 0.) + DIRECT_MULTIDIM_ELEM(Mscore, n) = (DIRECT_MULTIDIM_ELEM(Mscore, n) - DIRECT_MULTIDIM_ELEM(Msum, n)) / DIRECT_MULTIDIM_ELEM(Msum, n); } + Image Itt; + Itt()=Msum; + Itt.write("Mscore_avg_over_psi.spi"); + Itt()=Mscore; + Itt.write("Mscore_Zscore_over_psi.spi"); if (myverb) progress_bar(nr_psi); @@ -881,7 +884,7 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) FileName fn_conv = fn_root + "_" + fn_out + "_conv.spi"; MultidimArray Mscore, Mangle; - if (!do_write_intermediate || !exists(fn_fom) || !exists(fn_psi)) + if (!(do_write_intermediate || do_only_write_intermediate) || !exists(fn_fom) || !exists(fn_psi)) { Image Iin; From 5a47229adf1055da171f0bb61e8e12c79e90d413 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 26 Nov 2024 15:30:49 +0000 Subject: [PATCH 005/189] added an option to write out kurtosis and skewness of micrographs, to select and display mics with filaments --- src/amyloid_finder.cpp | 76 ++++++++++++++++++++++++++++++++++++++---- src/amyloid_finder.h | 4 ++- src/displayer.cpp | 7 +++- src/metadata_label.h | 10 +++++- src/pipeline_jobs.cpp | 12 ++++--- 5 files changed, 95 insertions(+), 14 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 0520a1535..7565260e7 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -27,7 +27,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) int general_section = parser.addSection("General options"); fn_in = parser.getOption("--i", "Input image (.mrc) or STAR file with micrographs"); - fn_out = parser.getOption("--pickname", "Rootname for coordinate STAR files", "amypick"); + fn_out = parser.getOption("--pickname", "Rootname for coordinate STAR files", "autopick"); fn_odir = parser.getOption("--odir", "Output directory for coordinate files (default is to store next to micrographs)", "AutoPick/"); do_only_unfinished = parser.checkOption("--only_do_unfinished", "Only estimate CTFs for those tomograms for which there is not yet a logfile with Final values."); do_write_intermediate = parser.checkOption("--write_intermediates", "Write out intermediate FOM & PSI maps for faster testing of tracing parameters?"); @@ -230,7 +230,8 @@ RFLOAT AmyloidFinder::getPsiDiff(RFLOAT psi1, RFLOAT psi2) } -void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, MultidimArray &Mangle, bool myverb) +void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, + MultidimArray &Mangle, RFLOAT &skew, RFLOAT &kurt, bool myverb) { MultidimArray Mbig(large_box, large_box); @@ -433,6 +434,7 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi } // Now need to subtract the Mscore for the best psi, as the mean and stddev should be calculated for the non-signal! + RFLOAT sum=0., sum2=0.; FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Msum) { DIRECT_MULTIDIM_ELEM(Msum, n) -= DIRECT_MULTIDIM_ELEM(Mscore, n); @@ -442,13 +444,34 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi // where adjusted_mean is the average of the score over all psi-values, except the maximum if (DIRECT_MULTIDIM_ELEM(Msum, n) > 0.) DIRECT_MULTIDIM_ELEM(Mscore, n) = (DIRECT_MULTIDIM_ELEM(Mscore, n) - DIRECT_MULTIDIM_ELEM(Msum, n)) / DIRECT_MULTIDIM_ELEM(Msum, n); + sum += DIRECT_MULTIDIM_ELEM(Mscore, n); + sum2 += DIRECT_MULTIDIM_ELEM(Mscore, n) * DIRECT_MULTIDIM_ELEM(Mscore, n); } + // Output skewness and kurtosis of Mscore distribution to detect which micrographs have filaments + RFLOAT n = NZYXSIZE(Msum); + sum /= n; + sum2 /= n; + sum2 = sqrt(sum2-sum*sum); + skew = 0.; + kurt = 0.; + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Msum) + { + RFLOAT aux = (DIRECT_MULTIDIM_ELEM(Mscore, n) - sum)/sum2; + skew += aux*aux*aux; + kurt += aux*aux*aux*aux; + } + kurt *= n*(n+1)/((n-1)*(n-2)*(n-3)); + skew *= n/((n-1)*(n-2)); + + /* + std::cerr << " skewness= " << skew << " excess kurtosis= " << kurt -3 << std::endl; Image Itt; Itt()=Msum; Itt.write("Mscore_avg_over_psi.spi"); Itt()=Mscore; Itt.write("Mscore_Zscore_over_psi.spi"); + */ if (myverb) progress_bar(nr_psi); @@ -879,9 +902,9 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) { FileName fn_root = getOutputRootName(fn_mic); - FileName fn_fom = fn_root + "_" + fn_out + "_fom.spi"; - FileName fn_psi = fn_root + "_" + fn_out + "_psi.spi"; - FileName fn_conv = fn_root + "_" + fn_out + "_conv.spi"; + FileName fn_fom = fn_root + "_" + fn_out + "_fom.mrc"; + FileName fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; + FileName fn_skew = fn_root + "_" + fn_out + "_skew.star"; MultidimArray Mscore, Mangle; if (!(do_write_intermediate || do_only_write_intermediate) || !exists(fn_fom) || !exists(fn_psi)) @@ -893,7 +916,15 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) if (XSIZE(Iin()) != ori_xsize || YSIZE(Iin()) != ori_ysize || fabs(angpix - Iin.samplingRateX()) > 0.001) REPORT_ERROR("ERROR: incorrect size or pixel size for image " + fn_mic); - getScoreForOneMicrograph(Iin(), Mscore, Mangle, myverb); + RFLOAT skew, kurt; + getScoreForOneMicrograph(Iin(), Mscore, Mangle, skew, kurt, myverb); + + MetaDataTable MDskew; + MDskew.setIsList(true); + MDskew.addObject(); + MDskew.setValue(EMDL_MICROGRAPH_SCORE_SKEWNESS, skew); + MDskew.setValue(EMDL_MICROGRAPH_SCORE_KURTOSIS, kurt); + MDskew.write(fn_skew); if (do_write_intermediate || do_only_write_intermediate) { @@ -997,14 +1028,24 @@ void AmyloidFinder::finalise() init_progress_bar(fn_ori_micrographs.size()); } + MetaDataTable MDin; + ObservationModel obsModel; + ObservationModel::loadSafely(fn_in, obsModel, MDin, "micrographs", verb); + MetaDataTable MDcoords; MetaDataTable MDresult; long total_nr_picked = 0; int nr_coord_files = 0; for (long int imic = 0; imic < fn_ori_micrographs.size(); imic++) { + + FileName fn_root = getOutputRootName(fn_ori_micrographs[imic]); + FileName fn_fom = fn_root + "_" + fn_out + "_fom.mrc"; + FileName fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; + FileName fn_skew = fn_root + "_" + fn_out + "_skew.star"; + FileName fn_pick = fn_root + "_" + fn_out + ".star"; + MetaDataTable MD; - FileName fn_pick = getOutputRootName(fn_ori_micrographs[imic]) + "_" + fn_out + ".star"; if (exists(fn_pick)) { @@ -1033,6 +1074,23 @@ void AmyloidFinder::finalise() } } + if (exists(fn_skew)) + { + MetaDataTable MDskew; + RFLOAT kurt, skew; + MDskew.read(fn_skew); + MDskew.getValue(EMDL_MICROGRAPH_SCORE_KURTOSIS, kurt); + MDskew.getValue(EMDL_MICROGRAPH_SCORE_SKEWNESS, skew); + MDin.setValue(EMDL_MICROGRAPH_SCORE_KURTOSIS, kurt, imic); + MDin.setValue(EMDL_MICROGRAPH_SCORE_SKEWNESS, skew, imic); + + if (do_write_intermediate || do_only_write_intermediate) + { + MDin.setValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_fom, imic); + MDin.setValue(EMDL_MICROGRAPH_AUTOPICK_PSI, fn_psi, imic); + } + } + if (verb > 0 && imic % 60 == 0) progress_bar(imic); } @@ -1042,6 +1100,10 @@ void AmyloidFinder::finalise() MDcoords.setName("coordinate_files"); MDcoords.write(fn_coords); + MDin.write(std::cerr); + FileName fn_mics = fn_odir + "micrographs_" + fn_out + ".star"; + obsModel.save(MDin, fn_mics, "micrographs"); + if (verb > 0 ) { progress_bar(fn_ori_micrographs.size()); diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index 6a7d7c6ea..6e28ba29a 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -27,6 +27,7 @@ #include #include #include +#include #include struct AmyloidCoordinate @@ -114,7 +115,8 @@ class AmyloidFinder RFLOAT getPsiDiff(RFLOAT psi1, RFLOAT psi2); // Loop over all psi-angles and coordinates to get accumulated score and angle image for a given micrograph - void getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, MultidimArray &Mangle, bool myverb = false); + void getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, + MultidimArray &Mangle, RFLOAT &skew, RFLOAT &kurt, bool myverb = false); // Find next candidate coordinates around a previous coordinate std::vector findNextCandidateCoordinates(AmyloidCoordinate &mycoord, MultidimArray &Mscore, MultidimArray &Mpsi); diff --git a/src/displayer.cpp b/src/displayer.cpp index c0acfe939..8c1e2aec7 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -2888,8 +2888,13 @@ int Displayer::runGui() win.display_labels.push_back(EMDL::label2Str(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_PROJ2D_HALF1_FILE_NAME)); if (MD.containsLabel(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_PROJ2D_HALF2_FILE_NAME)) win.display_labels.push_back(EMDL::label2Str(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_PROJ2D_HALF2_FILE_NAME)); + if (MD.containsLabel(EMDL_MICROGRAPH_AUTOPICK_FOM)) + win.display_labels.push_back(EMDL::label2Str(EMDL_MICROGRAPH_AUTOPICK_FOM)); + if (MD.containsLabel(EMDL_MICROGRAPH_AUTOPICK_PSI)) + win.display_labels.push_back(EMDL::label2Str(EMDL_MICROGRAPH_AUTOPICK_PSI)); - } + + } else { // Try reading as an image/stack header diff --git a/src/metadata_label.h b/src/metadata_label.h index 9c37aec7d..4ef6d1965 100644 --- a/src/metadata_label.h +++ b/src/metadata_label.h @@ -266,6 +266,10 @@ enum EMDLabel EMDL_MICROGRAPH_END_FRAME, EMDL_MICROGRAPH_SHIFT_X, EMDL_MICROGRAPH_SHIFT_Y, + EMDL_MICROGRAPH_AUTOPICK_FOM, + EMDL_MICROGRAPH_AUTOPICK_PSI, + EMDL_MICROGRAPH_SCORE_KURTOSIS, + EMDL_MICROGRAPH_SCORE_SKEWNESS, EMDL_MICROGRAPH_MOTION_COEFFS_IDX, EMDL_MICROGRAPH_MOTION_COEFF, EMDL_MICROGRAPH_EER_UPSAMPLING, @@ -996,7 +1000,11 @@ class StaticInitialization EMDL::addLabel(EMDL_MICROGRAPH_END_FRAME, EMDL_INT, "rlnMicrographEndFrame", "End frame of a motion model"); EMDL::addLabel(EMDL_MICROGRAPH_SHIFT_X, EMDL_DOUBLE, "rlnMicrographShiftX", "X shift of a (patch of) micrograph"); EMDL::addLabel(EMDL_MICROGRAPH_SHIFT_Y, EMDL_DOUBLE, "rlnMicrographShiftY", "Y shift of a (patch of) micrograph"); - EMDL::addLabel(EMDL_MICROGRAPH_MOTION_COEFFS_IDX, EMDL_INT, "rlnMotionModelCoeffsIdx", "Index of a coefficient of a motion model"); + EMDL::addLabel(EMDL_MICROGRAPH_AUTOPICK_FOM, EMDL_STRING, "rlnMicrographFomImage", "Filename of the FOM image of micrograph autopicking "); + EMDL::addLabel(EMDL_MICROGRAPH_AUTOPICK_PSI, EMDL_STRING, "rlnMicrographPsiImage", "Filename of the PSI image of micrograph autopicking"); + EMDL::addLabel(EMDL_MICROGRAPH_SCORE_KURTOSIS, EMDL_DOUBLE, "rlnMicrographScoreKurtosis", "(Excess) kurtosis in micrograph autopick FOM scores"); + EMDL::addLabel(EMDL_MICROGRAPH_SCORE_SKEWNESS, EMDL_DOUBLE, "rlnMicrographScoreSkewness", "Skewness in micrograph autopick FOM scores"); + EMDL::addLabel(EMDL_MICROGRAPH_MOTION_COEFFS_IDX, EMDL_INT, "rlnMotionModelCoeffsIdx", "Index of a coefficient of a motion model"); EMDL::addLabel(EMDL_MICROGRAPH_MOTION_COEFF, EMDL_DOUBLE, "rlnMotionModelCoeff", "A coefficient of a motion model"); EMDL::addLabel(EMDL_MICROGRAPH_EER_UPSAMPLING, EMDL_INT, "rlnEERUpsampling", "EER upsampling ratio (1 = physical, 2 = 2x super-resolution)"); EMDL::addLabel(EMDL_MICROGRAPH_EER_GROUPING, EMDL_INT, "rlnEERGrouping", "The number of hardware frames to group"); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 3929ae19e..11fcb775b 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2048,7 +2048,7 @@ Kappa = 0.3 means that the curvature of the picked helical tubes should not be l Kappa ~ 0.05 is recommended for long and straight tubes (e.g. TMV, VipA/VipB and AChR tubes) while 0.20 ~ 0.40 seems suitable for flexible ones (e.g. ParM and MAVS-CARD filaments)."); joboptions["helical_tube_length_min"] = JobOption("Minimum length (A): ", 400, 100, 1000, 10, "Minimum length (in Angstroms) of helical tubes for auto-picking. \ Helical tubes with shorter lengths will not be picked. Note that a long helical tube seen by human eye might be treated as short broken pieces due to low FOM values or high picking threshold."); - joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 2, 0.3, 5, 0.1, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); + joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 0.5, 0.2, 2, 0.1, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); } @@ -2317,15 +2317,19 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Wed, 27 Nov 2024 09:05:01 +0000 Subject: [PATCH 006/189] repaired some minor issues --- src/amyloid_finder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 7565260e7..a783634a0 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -929,6 +929,7 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) if (do_write_intermediate || do_only_write_intermediate) { Image Ipsi, Izscore; + Ipsi.setSamplingRateInHeader(down_angpix); Ipsi()=Mangle; Izscore()=Mscore; Ipsi.write(fn_psi); @@ -1100,7 +1101,6 @@ void AmyloidFinder::finalise() MDcoords.setName("coordinate_files"); MDcoords.write(fn_coords); - MDin.write(std::cerr); FileName fn_mics = fn_odir + "micrographs_" + fn_out + ".star"; obsModel.save(MDin, fn_mics, "micrographs"); From 85c81f64d5aeeab89cd53bd7c313edc072c77c6d Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 27 Nov 2024 09:11:56 +0000 Subject: [PATCH 007/189] histograms of skewness and kurtosis --- src/amyloid_finder.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index a783634a0..c40aa9e62 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -1163,6 +1163,42 @@ void AmyloidFinder::finalise() delete plot2Dd; } + CPlot2D *plot2De=new CPlot2D("Skewness of FOM for all micrographs"); + MDresult.addToCPlot2D(plot2De, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_SKEWNESS, 1.); + plot2De->SetDrawLegend(false); + fn_eps = fn_odir + "all_FOM_skew.eps"; + plot2De->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2De; + if (MDresult.numberOfObjects() > 3) + { + CPlot2D *plot2Df=new CPlot2D(""); + MDresult.columnHistogram(EMDL_MICROGRAPH_SCORE_SKEWNESS,histX,histY,0, plot2Df); + fn_eps = fn_odir + "histogram_FOM_skew.eps"; + plot2Df->SetTitle("Histogram of FOM skewness per micrograph"); + plot2Df->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2Df; + } + + CPlot2D *plot2Dg=new CPlot2D("Kurtosis of FOM for all micrographs"); + MDresult.addToCPlot2D(plot2Dg, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_KURTOSIS, 1.); + plot2Dg->SetDrawLegend(false); + fn_eps = fn_odir + "all_FOM_kurt.eps"; + plot2Dg->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2Dg; + if (MDresult.numberOfObjects() > 3) + { + CPlot2D *plot2Dh=new CPlot2D(""); + MDresult.columnHistogram(EMDL_MICROGRAPH_SCORE_KURTOSIS,histX,histY,0, plot2Dh); + fn_eps = fn_odir + "histogram_FOM_kurt.eps"; + plot2Dh->SetTitle("Histogram of FOM kurtosis per micrograph"); + plot2Dh->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2Dh; + } + joinMultipleEPSIntoSinglePDF(fn_odir + "logfile.pdf", all_fn_eps); } \ No newline at end of file From dc262f6b8bb283d08c60a5f872e456629b6cb083 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 27 Nov 2024 13:46:15 +0000 Subject: [PATCH 008/189] add option to pick filaments as lines --- src/amyloid_finder.cpp | 14 +++---- src/displayer.cpp | 89 +++++++++++++++++++++++++++--------------- src/displayer.h | 8 +++- src/manualpicker.cpp | 5 +++ 4 files changed, 76 insertions(+), 40 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index c40aa9e62..f20fdf620 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -1164,34 +1164,34 @@ void AmyloidFinder::finalise() } CPlot2D *plot2De=new CPlot2D("Skewness of FOM for all micrographs"); - MDresult.addToCPlot2D(plot2De, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_SKEWNESS, 1.); + MDin.addToCPlot2D(plot2De, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_SKEWNESS, 1.); plot2De->SetDrawLegend(false); fn_eps = fn_odir + "all_FOM_skew.eps"; plot2De->OutputPostScriptPlot(fn_eps); all_fn_eps.push_back(fn_eps); delete plot2De; - if (MDresult.numberOfObjects() > 3) + if (MDin.numberOfObjects() > 3) { CPlot2D *plot2Df=new CPlot2D(""); - MDresult.columnHistogram(EMDL_MICROGRAPH_SCORE_SKEWNESS,histX,histY,0, plot2Df); + MDin.columnHistogram(EMDL_MICROGRAPH_SCORE_SKEWNESS,histX,histY,0, plot2Df); fn_eps = fn_odir + "histogram_FOM_skew.eps"; plot2Df->SetTitle("Histogram of FOM skewness per micrograph"); plot2Df->OutputPostScriptPlot(fn_eps); all_fn_eps.push_back(fn_eps); delete plot2Df; } - + CPlot2D *plot2Dg=new CPlot2D("Kurtosis of FOM for all micrographs"); - MDresult.addToCPlot2D(plot2Dg, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_KURTOSIS, 1.); + MDin.addToCPlot2D(plot2Dg, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_KURTOSIS, 1.); plot2Dg->SetDrawLegend(false); fn_eps = fn_odir + "all_FOM_kurt.eps"; plot2Dg->OutputPostScriptPlot(fn_eps); all_fn_eps.push_back(fn_eps); delete plot2Dg; - if (MDresult.numberOfObjects() > 3) + if (MDin.numberOfObjects() > 3) { CPlot2D *plot2Dh=new CPlot2D(""); - MDresult.columnHistogram(EMDL_MICROGRAPH_SCORE_KURTOSIS,histX,histY,0, plot2Dh); + MDin.columnHistogram(EMDL_MICROGRAPH_SCORE_KURTOSIS,histX,histY,0, plot2Dh); fn_eps = fn_odir + "histogram_FOM_kurt.eps"; plot2Dh->SetTitle("Histogram of FOM kurtosis per micrograph"); plot2Dh->OutputPostScriptPlot(fn_eps); diff --git a/src/displayer.cpp b/src/displayer.cpp index 8c1e2aec7..d3fbab887 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -386,7 +386,7 @@ int basisViewerWindow::fillCanvas(int viewer_type, MetaDataTable &MDin, Observat } int basisViewerWindow::fillPickerViewerCanvas(MultidimArray image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, - RFLOAT _scale, RFLOAT _coord_scale, int _particle_radius, bool _do_startend, FileName _fn_coords, + RFLOAT _scale, RFLOAT _coord_scale, int _particle_radius, bool _do_startend, bool _do_lines, FileName _fn_coords, FileName _fn_color, FileName _fn_mic, FileName _color_label, RFLOAT _color_blue_value, RFLOAT _color_red_value, RFLOAT _minimum_pick_fom) { @@ -398,7 +398,8 @@ int basisViewerWindow::fillPickerViewerCanvas(MultidimArray image, RFLOA int ysize_canvas = CEIL(YSIZE(image)*_scale); pickerViewerCanvas canvas(0, 0, xsize_canvas, ysize_canvas); canvas.particle_radius = _particle_radius; - canvas.do_startend = _do_startend; + canvas.do_startend = _do_startend; + canvas.do_lines = _do_lines; canvas.coord_scale = _coord_scale; canvas.SetScroll(&scroll); canvas.fill(image, _minval, _maxval, _sigma_contrast, _scale); @@ -1832,50 +1833,57 @@ void pickerViewerCanvas::draw() { int selected; MDcoords.getValue(EMDL_PARTICLE_SELECTION_TYPE, selected); - if (selected >= 1 && selected <= 6) - fl_color(color_choices[selected - 1].labelcolor_); - else - REPORT_ERROR("ERROR: rlnParticleSelectionType in coordinate file has value outside allowed [1,6] range"); + int mycol = (selected-1)%6; + fl_color(color_choices[mycol].labelcolor_); } else { fl_color(FL_GREEN); } - int xcoori, ycoori; - xcoori = ROUND(xcoor * coord_scale * scale) + scroll->x() - scroll->hscrollbar.value(); - ycoori = ROUND(ycoor * coord_scale * scale) + scroll->y() - scroll->scrollbar.value(); - fl_circle(xcoori, ycoori, particle_radius); + int xcoori, ycoori; + xcoori = ROUND(xcoor * coord_scale * scale) + scroll->x() - scroll->hscrollbar.value(); + ycoori = ROUND(ycoor * coord_scale * scale) + scroll->y() - scroll->scrollbar.value(); - if (do_startend) - { - if (icoord % 2 == 1) - { - xcoori_start = xcoori; - ycoori_start = ycoori; - } - else - { - fl_line(xcoori_start, ycoori_start, xcoori, ycoori); - } - } - } + fl_circle(xcoori, ycoori, particle_radius); + + if (do_startend) + { + if (icoord % 2 == 1) + { + xcoori_start = xcoori; + ycoori_start = ycoori; + } + else + { + fl_line(xcoori_start, ycoori_start, xcoori, ycoori); + } + } + } } int pickerViewerCanvas::handle(int ev) { const int button = Fl::event_button() ; const bool with_shift = (Fl::event_shift() != 0); - const bool with_control = (Fl::event_ctrl() != 0); + const bool with_control = (Fl::event_ctrl() != 0); const int key = Fl::event_key(); - if (ev==FL_PUSH || (ev==FL_DRAG && (button == FL_MIDDLE_MOUSE || (button == FL_LEFT_MOUSE && with_shift)))) + if (do_lines && has_dragged && ev == FL_RELEASE) current_selection_type++; + has_dragged = false; + + if (ev==FL_PUSH || (ev==FL_DRAG && + ( (button == FL_MIDDLE_MOUSE) || + (do_lines && button == FL_LEFT_MOUSE) || + (button == FL_LEFT_MOUSE && with_shift) ) )) { RFLOAT scale = boxes[0]->scale; int xc = (int)Fl::event_x() - scroll->x() + scroll->hscrollbar.value(); int yc = (int)Fl::event_y() - scroll->y() + scroll->scrollbar.value(); RFLOAT xcoor = (RFLOAT)ROUND(xc / (coord_scale * scale)); RFLOAT ycoor = (RFLOAT)ROUND(yc / (coord_scale * scale)); + int iaux = current_selection_type; RFLOAT rad2 = particle_radius * particle_radius / (coord_scale * coord_scale * scale * scale); + has_dragged = (ev==FL_DRAG && (do_lines && button == FL_LEFT_MOUSE)); if (button == FL_LEFT_MOUSE && !with_shift && !with_control) { // Left mouse for picking @@ -1892,8 +1900,6 @@ int pickerViewerCanvas::handle(int ev) return 0; } RFLOAT aux = -999., zero = 0.; - int iaux = current_selection_type; - // Else store new coordinate if (!MDcoords.isEmpty()) { @@ -1937,7 +1943,25 @@ int pickerViewerCanvas::handle(int ev) if (xcoor_p*xcoor_p + ycoor_p*ycoor_p < rad2) { - MDcoords.removeObject(); + if (do_lines) + { + int delval; + MDcoords.getValue(EMDL_PARTICLE_SELECTION_TYPE, delval); + MetaDataTable MDout; + MDout.setName(MDcoords.getName()); + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDcoords) + { + int val; + MDcoords.getValue(EMDL_PARTICLE_SELECTION_TYPE, val); + + if (val != delval) MDout.addObject(MDcoords.getObject(current_object)); + } + MDcoords = MDout; + } + else + { + MDcoords.removeObject(); + } break; } } @@ -2644,7 +2668,8 @@ void Displayer::read(int argc, char **argv) int pick_section = parser.addSection("Picking options"); do_pick = parser.checkOption("--pick", "Pick coordinates in input image"); - do_pick_startend = parser.checkOption("--pick_start_end", "Pick start-end coordinates in input image"); + do_pick_startend = parser.checkOption("--pick_start_end", "Pick start-end coordinates in input image"); + do_pick_lines = parser.checkOption("--pick_lines", "Pick lines in input image"); fn_coords = parser.getOption("--coords", "STAR file with picked particle coordinates", ""); coord_scale = textToFloat(parser.getOption("--coord_scale", "Scale particle coordinates before display", "1.0")); particle_radius = textToFloat(parser.getOption("--particle_radius", "Particle radius in pixels", "100")); @@ -2769,7 +2794,7 @@ void Displayer::initialise() REPORT_ERROR("Displayer::initialise ERROR: cannot display Fourier amplitudes and phase angles at the same time!"); if (show_fourier_amplitudes || show_fourier_phase_angles) { - if (do_pick || do_pick_startend) + if (do_pick || do_pick_startend || do_pick_lines) REPORT_ERROR("Displayer::initialise ERROR: cannot pick particles from Fourier maps!"); if (fn_in.isStarFile()) REPORT_ERROR("Displayer::initialise ERROR: use single 2D image files as input!"); @@ -2922,7 +2947,7 @@ void Displayer::run() basisViewerWindow win(CEIL(scale*XSIZE(img())), CEIL(scale*YSIZE(img())), fnt.c_str()); win.fillSingleViewerCanvas(img(), 0., 255., 0., scale); } - else if (do_pick || do_pick_startend) + else if (do_pick || do_pick_startend || do_pick_lines) { Image img; @@ -2943,7 +2968,7 @@ void Displayer::run() basisViewerWindow win(CEIL(scale*XSIZE(img())), CEIL(scale*YSIZE(img())), fn_in.c_str()); if (fn_coords=="") fn_coords = fn_in.withoutExtension()+"_coords.star"; - win.fillPickerViewerCanvas(img(), minval, maxval, sigma_contrast, scale, coord_scale, ROUND(scale*particle_radius), do_pick_startend, fn_coords, + win.fillPickerViewerCanvas(img(), minval, maxval, sigma_contrast, scale, coord_scale, ROUND(scale*particle_radius), do_pick_startend, do_pick_lines, fn_coords, fn_color, fn_in, color_label, color_blue_value, color_red_value, minimum_pick_fom); } else if (fn_in.isStarFile()) diff --git a/src/displayer.h b/src/displayer.h index cf9ed5437..da3f30d6e 100644 --- a/src/displayer.h +++ b/src/displayer.h @@ -148,7 +148,7 @@ class basisViewerWindow : public Fl_Window bool do_allow_save = false, FileName fn_selected_imgs="", FileName fn_selected_parts="", int max_nr_parts_per_class = -1); int fillSingleViewerCanvas(MultidimArray image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale); int fillPickerViewerCanvas(MultidimArray image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale, RFLOAT _coord_scale, - int _particle_radius, bool do_startend = false, FileName _fn_coords = "", + int _particle_radius, bool do_startend = false, bool do_lines = false, FileName _fn_coords = "", FileName _fn_color = "", FileName _fn_mic= "", FileName _color_label = "", RFLOAT _color_blue_value = 0., RFLOAT _color_red_value = 1., RFLOAT _minimum_pick_fom = -999.); }; @@ -410,6 +410,9 @@ class pickerViewerCanvas : public basisViewerCanvas // Draw lines between start-end coordinates? bool do_startend; + // Draw lines for curvy filaments? + bool do_lines; + // Micrograph name (useful to search relevant particles in fn_color) FileName fn_mic; @@ -584,6 +587,9 @@ class Displayer // Flag to pick start-end bool do_pick_startend; + // Flag to pick lines + bool do_pick_lines; + // Flag for looking at classes bool do_class; diff --git a/src/manualpicker.cpp b/src/manualpicker.cpp index 882437b95..777199aff 100644 --- a/src/manualpicker.cpp +++ b/src/manualpicker.cpp @@ -37,6 +37,7 @@ int last_ctf_viewed; bool global_has_ctf; bool global_pick_startend; +bool global_pick_lines; RFLOAT global_angpix; RFLOAT global_coord_scale; RFLOAT global_lowpass; @@ -143,6 +144,8 @@ void cb_viewmic(Fl_Widget* w, void* data) command += " --angpix " + floatToString(global_angpix); if (global_pick_startend) command += " --pick_start_end "; + else if (global_pick_lines) + command += " --pick_lines "; if (fabs(global_minimum_fom + 9999.) > 1e-6) { @@ -693,6 +696,8 @@ void ManualPicker::read(int argc, char **argv) global_coord_scale = textToFloat(parser.getOption("--coord_scale", "Scale coordinates before display", "1.0")); global_particle_diameter = textToFloat(parser.getOption("--particle_diameter", "Diameter of the circles that will be drawn around each picked particle (in Angstroms)")); global_pick_startend = parser.checkOption("--pick_start_end", "Pick start-end coordinates of helices"); + global_pick_lines = parser.checkOption("--pick_lines", "Pick lines for curvy helices"); + do_allow_save = parser.checkOption("--allow_save", "Allow saving of the selected micrographs"); do_fast_save = parser.checkOption("--fast_save", "Save a default selection of all micrographs immediately"); global_nr_simultaneous = textToInteger(parser.getOption("--open_simultaneous", "Open this many of the next micrographs simultaneously when pressing CTRL and a Pick button", "10")); From c7ba4c3f91d717ec90d1a400508507d565c5b8c6 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 27 Nov 2024 15:28:05 +0000 Subject: [PATCH 009/189] added option to extract helices from drawn lines --- scripts/Schemes/proc/extract/job.star | 3 +- src/gui_jobwindow.cpp | 6 +- src/pipeline_jobs.cpp | 38 +++----- src/preprocessing.cpp | 129 ++++++++++++++++++++++++-- src/preprocessing.h | 22 ++++- 5 files changed, 163 insertions(+), 35 deletions(-) diff --git a/scripts/Schemes/proc/extract/job.star b/scripts/Schemes/proc/extract/job.star index 9764c54e4..08c9717ac 100644 --- a/scripts/Schemes/proc/extract/job.star +++ b/scripts/Schemes/proc/extract/job.star @@ -18,7 +18,8 @@ bg_diameter -1 black_dust -1 coords_suffix Schemes/proc/autopick/autopick.star do_cut_into_segments Yes -do_extract_helical_tubes Yes +do_extract_helical_tubes Yes +do_lines No do_extract_helix No do_fom_threshold Yes do_invert Yes diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 94a16feb1..ca8a5b8fb 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -695,7 +695,8 @@ void JobWindow::initialiseManualpickWindow() place("fn_in", TOGGLE_DEACTIVATE); current_y += STEPY/2; - place ("do_startend"); + place ("do_startend"); + place ("do_lines"); current_y += STEPY/2; @@ -1064,8 +1065,7 @@ Pixels values higher than this many times the image stddev will be replaced with place("do_extract_helical_tubes", TOGGLE_DEACTIVATE, group6); group6->begin(); - - place("do_cut_into_segments", TOGGLE_DEACTIVATE); + place("do_lines", TOGGLE_DEACTIVATE); place("helical_nr_asu", TOGGLE_DEACTIVATE); place("helical_rise", TOGGLE_DEACTIVATE); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 11fcb775b..2614ae450 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -1861,7 +1861,8 @@ to the average PLUS this value times the standard deviation. Use zero to set the joboptions["angpix"] = JobOption("Pixel size (A)", -1, 0.3, 5, 0.1, "Pixel size in Angstroms. This will be used to calculate the filters and the particle diameter in pixels. If a CTF-containing STAR file is input, then the value given here will be ignored, and the pixel size will be calculated from the values in the STAR file. A negative value can then be given here."); joboptions["do_topaz_denoise"] = JobOption("OR: use Topaz denoising?", false, "If set to true, Topaz denoising will be performed instead of lowpass filtering."); - joboptions["do_startend"] = JobOption("Pick start-end coordinates helices?", false, "If set to true, start and end coordinates are picked subsequently and a line will be drawn between each pair"); + joboptions["do_startend"] = JobOption("Pick start-end coordinates helices?", false, "If set to true, start and end coordinates are picked subsequently and a line will be drawn between each pair"); + joboptions["do_lines"] = JobOption("Pick helices as lines?", false, "If set to true, lines of coordinates are picked as one drags the mouse"); joboptions["do_fom_threshold"] = JobOption("Use autopick FOM threshold?", false, "If set to Yes, only particles with rlnAutopickFigureOfMerit values below the threshold below will be extracted."); joboptions["minimum_pick_fom"] = JobOption("Minimum autopick FOM: ", 0, -5, 10, 0.1, "The minimum value for the rlnAutopickFigureOfMerit for particles to be extracted."); @@ -1935,11 +1936,12 @@ bool RelionJob::getCommandsManualpickJob(std::string &outputname, std::vector Date: Wed, 27 Nov 2024 16:44:15 +0000 Subject: [PATCH 010/189] display lines as lines --- src/displayer.cpp | 40 +++++++++++++++++++++++++++++----------- src/displayer.h | 1 - 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/displayer.cpp b/src/displayer.cpp index d3fbab887..ab11c9d1a 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -1768,6 +1768,7 @@ void pickerViewerCanvas::draw() RFLOAT scale = boxes[0]->scale; long int icoord = 0; + int my_prev_type = 0; int xcoori_start, ycoori_start; FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDcoords) { @@ -1776,6 +1777,8 @@ void pickerViewerCanvas::draw() RFLOAT xcoor, ycoor; MDcoords.getValue(EMDL_IMAGE_COORD_X, xcoor); MDcoords.getValue(EMDL_IMAGE_COORD_Y, ycoor); + int mytype; + MDcoords.getValue(EMDL_PARTICLE_SELECTION_TYPE, mytype); if (MDcoords.containsLabel(EMDL_PARTICLE_AUTOPICK_FOM) && fabs(minimum_pick_fom + 9999.) > 1e-6) { @@ -1845,20 +1848,33 @@ void pickerViewerCanvas::draw() xcoori = ROUND(xcoor * coord_scale * scale) + scroll->x() - scroll->hscrollbar.value(); ycoori = ROUND(ycoor * coord_scale * scale) + scroll->y() - scroll->scrollbar.value(); - fl_circle(xcoori, ycoori, particle_radius); - - if (do_startend) + if (do_lines) { - if (icoord % 2 == 1) + if (mytype == my_prev_type) { - xcoori_start = xcoori; - ycoori_start = ycoori; + fl_line(xcoori_start, ycoori_start, xcoori, ycoori); } - else + xcoori_start = xcoori; + ycoori_start = ycoori; + } + else + { + fl_circle(xcoori, ycoori, particle_radius); + + if (do_startend) { - fl_line(xcoori_start, ycoori_start, xcoori, ycoori); + if (icoord % 2 == 1) + { + xcoori_start = xcoori; + ycoori_start = ycoori; + } + else + { + fl_line(xcoori_start, ycoori_start, xcoori, ycoori); + } } } + my_prev_type = mytype; } } @@ -1868,7 +1884,10 @@ int pickerViewerCanvas::handle(int ev) const bool with_shift = (Fl::event_shift() != 0); const bool with_control = (Fl::event_ctrl() != 0); const int key = Fl::event_key(); - if (do_lines && has_dragged && ev == FL_RELEASE) current_selection_type++; + if (do_lines && has_dragged && ev == FL_RELEASE) + { + current_selection_type++; + } has_dragged = false; if (ev==FL_PUSH || (ev==FL_DRAG && @@ -1925,9 +1944,8 @@ int pickerViewerCanvas::handle(int ev) MDcoords.setValue(EMDL_PARTICLE_SELECTION_TYPE, iaux); MDcoords.setValue(EMDL_ORIENT_PSI, aux); MDcoords.setValue(EMDL_PARTICLE_AUTOPICK_FOM, aux); - redraw(); - return 1; + return 1; } else if ((button == FL_MIDDLE_MOUSE) || (button == FL_LEFT_MOUSE && with_shift)) { diff --git a/src/displayer.h b/src/displayer.h index da3f30d6e..4faa95e94 100644 --- a/src/displayer.h +++ b/src/displayer.h @@ -72,7 +72,6 @@ static int predrag_xc; static int predrag_yc; static bool has_shift; static int preshift_ipos; - static int current_selection_type; static int colour_scheme; From 44297f4fab894989122f0e37e873070245aa570e Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 27 Nov 2024 20:28:00 +0000 Subject: [PATCH 011/189] continue with filament_type in lines mode and have red-coloured fom colouring --- src/displayer.cpp | 162 ++++++++++++++++++++++++++++++++++++++++--- src/displayer.h | 10 ++- src/manualpicker.cpp | 2 +- 3 files changed, 161 insertions(+), 13 deletions(-) diff --git a/src/displayer.cpp b/src/displayer.cpp index ab11c9d1a..b74930d3a 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -267,6 +267,111 @@ void DisplayBox::setData(MultidimArray &img, MetaDataContainer *MDCin, i } } +void DisplayBox::setData(MultidimArray &img, MultidimArray &fom_img, MetaDataContainer *MDCin, int _ipos, + RFLOAT _minval, RFLOAT _maxval, RFLOAT _scale, bool do_relion_scale) +{ + scale = _scale; + minval = _minval; + maxval = _maxval; + ipos = _ipos; + selected = NOTSELECTED; + + RFLOAT fom_min=9999., fom_max=-9999.; + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(fom_img) + { + RFLOAT aux = DIRECT_MULTIDIM_ELEM(fom_img, n); + if (aux > fom_max) fom_max = aux; + if (aux < fom_min) fom_min = aux; + } + RFLOAT fom_range = fom_max-fom_min; + RFLOAT fom_step = fom_range / 255; + // Set its own MetaDataTable + MDimg.setIsList(true); + MDimg.addObject(MDCin); + + // For volumes only show the central slice + if (ZSIZE(img) > 1) + { + MultidimArray slice; + img.getSlice(ZSIZE(img)/2, slice); + img=slice; + } + + // create array for the scaled image data + xsize_data = CEIL(XSIZE(img) * scale); + ysize_data = CEIL(YSIZE(img) * scale); + xoff = (xsize_data < w() ) ? (w() - xsize_data) / 2 : 0; + yoff = (ysize_data < h() ) ? (h() - ysize_data) / 2 : 0; + img_data = new unsigned char [3 * xsize_data * ysize_data]; + RFLOAT range = maxval - minval; + RFLOAT step = range / 255; // 8-bit scaling range from 0 to 255 + RFLOAT* old_ptr=NULL; + long int n; + + // For micrographs use relion-scaling to avoid bias in down-sampled positions + // For multi-image viewers, do not use this scaling as it is slower... + if (do_relion_scale && ABS(scale - 1.0) > 0.01) + { + selfScaleToSize(img, xsize_data, ysize_data); + selfScaleToSize(fom_img, xsize_data, ysize_data); + } + + // Use the same nearest-neighbor algorithm as in the copy function of Fl_Image... + if (ABS(scale - 1.0) > 0.01 && !do_relion_scale) + { + int xmod = XSIZE(img) % xsize_data; + int xstep = XSIZE(img) / xsize_data; + int ymod = YSIZE(img) % ysize_data; + int ystep = YSIZE(img) / ysize_data; + int line_d = XSIZE(img); + int dx, dy, sy, xerr, yerr; + + for (dy = ysize_data, sy = 0, yerr = ysize_data, n = 0; dy > 0; dy --) + { + for (dx = xsize_data, xerr = xsize_data, old_ptr = img.data + sy * line_d; dx > 0; dx --, n++) + { + RFLOAT myval = (DIRECT_MULTIDIM_ELEM(img, n) - minval) / step; + RFLOAT myfom = (DIRECT_MULTIDIM_ELEM(fom_img, n) - fom_min) / fom_step; + std::cerr << myval << " + " << myfom << " = " << myval+myfom << std::endl; + img_data[3*n] = FLOOR(0.5*myval + myfom); + img_data[3*n+1] = FLOOR(0.5*myval); + img_data[3*n+2] = FLOOR(0.5*myval); + old_ptr += xstep; + xerr -= xmod; + if (xerr <= 0) + { + xerr += xsize_data; + old_ptr += 1; + } + } + + sy += ystep; + yerr -= ymod; + if (yerr <= 0) + { + yerr += ysize_data; + sy ++; + } + } + } + else + { + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(img) + { + RFLOAT val = (DIRECT_MULTIDIM_ELEM(img, n) - minval) / step; + RFLOAT fom = (DIRECT_MULTIDIM_ELEM(fom_img, n) - fom_min) / fom_step; + if (FLOOR(0.5*val+0.5*fom)>254) std::cerr << "v= "<setData(image, fom_image, MDtmp.getObject(), 0, _minval, _maxval, _scale, true); + my_box->redraw(); + boxes.push_back(my_box); +} + void basisViewerCanvas::draw() { for (int ipos = 0 ; ipos < boxes.size(); ipos++) @@ -2116,6 +2242,17 @@ void pickerViewerCanvas::loadCoordinates(bool ask_filename) fn_coord_in = (fn_coords=="") ? "picked.star" : fn_coords; } MDcoords.read(fn_coord_in); + if (do_lines) + { + int ifil_max =0; + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDcoords) + { + int ifil; + MDcoords.getValue(EMDL_PARTICLE_SELECTION_TYPE, ifil); + if (ifil > ifil_max) ifil_max=ifil; + } + current_selection_type = ifil_max+1; + } if (fn_color != "") { @@ -2645,7 +2782,7 @@ void Displayer::read(int argc, char **argv) parser.setCommandLine(argc, argv); int gen_section = parser.addSection("General options"); - fn_in = parser.getOption("--i", "Input STAR file, image or stack",""); + fn_in = parser.getOption("--i", "Input STAR file, image or stack",""); do_gui = parser.checkOption("--gui", "Use this to provide all other parameters through a GUI"); display_label = EMDL::str2Label(parser.getOption("--display", "Metadata label to display", "rlnImageName")); text_label = EMDL::str2Label(parser.getOption("--text_label", "Metadata label to display text", "EMDL_UNDEFINED")); @@ -2701,6 +2838,7 @@ void Displayer::read(int argc, char **argv) color_label = parser.getOption("--color_label", "MetaDataLabel to color particles on (e.g. rlnParticleSelectZScore)", ""); color_blue_value = textToFloat(parser.getOption("--blue", "Value of the blue color", "1.")); color_red_value = textToFloat(parser.getOption("--red", "Value of the red color", "0.")); + fn_fom = parser.getOption("--fom", "FOM-image to colour the micrograph for picking",""); verb = textToInteger(parser.getOption("--verb", "Verbosity", "1")); @@ -2967,7 +3105,7 @@ void Displayer::run() } else if (do_pick || do_pick_startend || do_pick_lines) { - Image img; + Image img, fom_img; if (do_topaz_denoise) { @@ -2977,8 +3115,14 @@ void Displayer::run() { img.read(fn_in); // dont read data yet: only header to get size } + if (fn_fom != "") + { + colour_scheme = BLACKGREYREDSCALE; + fom_img.read(fn_fom); + selfScaleToSize(fom_img(), img().xdim, img().ydim); + } - if (lowpass > 0.) + if (lowpass > 0.) lowPassFilterMap(img(), lowpass, angpix); if (highpass > 0.) highPassFilterMap(img(), highpass, angpix, 25); // use a rather soft high-pass edge of 25 pixels wide @@ -2986,7 +3130,7 @@ void Displayer::run() basisViewerWindow win(CEIL(scale*XSIZE(img())), CEIL(scale*YSIZE(img())), fn_in.c_str()); if (fn_coords=="") fn_coords = fn_in.withoutExtension()+"_coords.star"; - win.fillPickerViewerCanvas(img(), minval, maxval, sigma_contrast, scale, coord_scale, ROUND(scale*particle_radius), do_pick_startend, do_pick_lines, fn_coords, + win.fillPickerViewerCanvas(img(), fom_img(), minval, maxval, sigma_contrast, scale, coord_scale, ROUND(scale*particle_radius), do_pick_startend, do_pick_lines, fn_coords, fn_color, fn_in, color_label, color_blue_value, color_red_value, minimum_pick_fom); } else if (fn_in.isStarFile()) diff --git a/src/displayer.h b/src/displayer.h index 4faa95e94..b82d08d3b 100644 --- a/src/displayer.h +++ b/src/displayer.h @@ -111,6 +111,9 @@ class DisplayBox : public Fl_Box void setData(MultidimArray &img, MetaDataContainer *MDCin, int ipos, RFLOAT minval, RFLOAT maxval, RFLOAT _scale, bool do_relion_scale = false); + void setData(MultidimArray &img, MultidimArray &fom_img, MetaDataContainer *MDCin, int ipos, RFLOAT minval, RFLOAT maxval, + RFLOAT _scale, bool do_relion_scale = false); + // Destructor ~DisplayBox() { @@ -146,7 +149,7 @@ class basisViewerWindow : public Fl_Window int _nr_regroup = -1, bool do_recenter = false, bool _is_data = false, MetaDataTable *MDgroups = NULL, bool do_allow_save = false, FileName fn_selected_imgs="", FileName fn_selected_parts="", int max_nr_parts_per_class = -1); int fillSingleViewerCanvas(MultidimArray image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale); - int fillPickerViewerCanvas(MultidimArray image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale, RFLOAT _coord_scale, + int fillPickerViewerCanvas(MultidimArray image, MultidimArray fom_image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale, RFLOAT _coord_scale, int _particle_radius, bool do_startend = false, bool do_lines = false, FileName _fn_coords = "", FileName _fn_color = "", FileName _fn_mic= "", FileName _color_label = "", RFLOAT _color_blue_value = 0., RFLOAT _color_red_value = 1., RFLOAT _minimum_pick_fom = -999.); @@ -188,7 +191,8 @@ class basisViewerCanvas : public Fl_Widget void fill(MetaDataTable &MDin, ObservationModel *obsModel, EMDLabel display_label, EMDLabel text_label, bool _do_apply_orient, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale, int _ncol, bool do_recenter = false, long int max_images = -1, RFLOAT lowpass = -1.0, RFLOAT highpass = -1.0); - void fill(MultidimArray &image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale = 1.); + void fill(MultidimArray &image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale = 1.); + void fill(MultidimArray &image, MultidimArray &fom_image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale = 1.); void setSelectionType(); void setFOMThreshold(); }; @@ -557,7 +561,7 @@ class Displayer RFLOAT coord_scale; // Input & Output rootname - FileName fn_in; + FileName fn_in, fn_fom; // Ignore optics groups bool do_ignore_optics; diff --git a/src/manualpicker.cpp b/src/manualpicker.cpp index 777199aff..7ba1062da 100644 --- a/src/manualpicker.cpp +++ b/src/manualpicker.cpp @@ -161,7 +161,7 @@ void cb_viewmic(Fl_Widget* w, void* data) } command += " &"; - //std::cerr << " command= " << command << std::endl; + std::cerr << " command= " << command << std::endl; int res = system(command.c_str()); } From 03124a286a2d1fa2693a8c5573bc1659ad5649f3 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 28 Nov 2024 08:41:09 +0000 Subject: [PATCH 012/189] added colouring of manualpicker according to a FOM image --- src/amyloid_finder.cpp | 3 +- src/apps/find_amyloid.cpp | 2 +- src/displayer.cpp | 85 ++++++++++++++++++++++++++++----------- src/displayer.h | 10 +++-- src/manualpicker.cpp | 20 +++++++-- 5 files changed, 88 insertions(+), 32 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index f20fdf620..7a25ddb30 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -31,7 +31,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) fn_odir = parser.getOption("--odir", "Output directory for coordinate files (default is to store next to micrographs)", "AutoPick/"); do_only_unfinished = parser.checkOption("--only_do_unfinished", "Only estimate CTFs for those tomograms for which there is not yet a logfile with Final values."); do_write_intermediate = parser.checkOption("--write_intermediates", "Write out intermediate FOM & PSI maps for faster testing of tracing parameters?"); - do_only_write_intermediate = parser.checkOption("--only_write_intermediates", "Write out only intermediate FOM & PSI maps?"); + do_only_write_intermediate = !parser.checkOption("--dont_only_write_intermediates", "For now, default is to only intermediate FOM & PSI maps, as picking doesn't work well yet"); nr_threads = textToInteger(parser.getOption("--j", "Number of threads to us in parallel", "1")); int search_section = parser.addSection("Filament searching options "); @@ -1102,6 +1102,7 @@ void AmyloidFinder::finalise() MDcoords.write(fn_coords); FileName fn_mics = fn_odir + "micrographs_" + fn_out + ".star"; + std::cerr <<" fn_mics= " << fn_mics << std::endl; obsModel.save(MDin, fn_mics, "micrographs"); if (verb > 0 ) diff --git a/src/apps/find_amyloid.cpp b/src/apps/find_amyloid.cpp index 3f8446592..737185be0 100644 --- a/src/apps/find_amyloid.cpp +++ b/src/apps/find_amyloid.cpp @@ -31,7 +31,7 @@ int main(int argc, char *argv[]) prm.run(); - if (!prm.do_only_write_intermediate) prm.finalise(); + prm.finalise(); #ifdef TIMING std::cout << "timings:" << std::endl; diff --git a/src/displayer.cpp b/src/displayer.cpp index b74930d3a..ec4f43a5a 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -268,7 +268,7 @@ void DisplayBox::setData(MultidimArray &img, MetaDataContainer *MDCin, i } void DisplayBox::setData(MultidimArray &img, MultidimArray &fom_img, MetaDataContainer *MDCin, int _ipos, - RFLOAT _minval, RFLOAT _maxval, RFLOAT _scale, bool do_relion_scale) + RFLOAT _minval, RFLOAT _maxval, RFLOAT _fom_min, RFLOAT _fom_max, RFLOAT _scale, bool do_relion_scale) { scale = _scale; minval = _minval; @@ -276,14 +276,28 @@ void DisplayBox::setData(MultidimArray &img, MultidimArray &fom_ ipos = _ipos; selected = NOTSELECTED; - RFLOAT fom_min=9999., fom_max=-9999.; + if (fabs(_fom_min) < 0.001 && fabs(_fom_max) < 0.001 ) + { + RFLOAT fom_min=9999., fom_max=-9999.; + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(fom_img) + { + RFLOAT aux = DIRECT_MULTIDIM_ELEM(fom_img, n); + if (aux > fom_max) fom_max = aux; + if (aux < fom_min) fom_min = aux; + } + _fom_min= fom_min; + _fom_max = fom_max; + } + // Cap high and low values of FOM FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(fom_img) { RFLOAT aux = DIRECT_MULTIDIM_ELEM(fom_img, n); - if (aux > fom_max) fom_max = aux; - if (aux < fom_min) fom_min = aux; + if (aux > _fom_max) DIRECT_MULTIDIM_ELEM(fom_img, n) = _fom_max; + if (aux < _fom_min) DIRECT_MULTIDIM_ELEM(fom_img, n) = _fom_min; } - RFLOAT fom_range = fom_max-fom_min; + + + RFLOAT fom_range = _fom_max - _fom_min; RFLOAT fom_step = fom_range / 255; // Set its own MetaDataTable MDimg.setIsList(true); @@ -330,12 +344,21 @@ void DisplayBox::setData(MultidimArray &img, MultidimArray &fom_ { for (dx = xsize_data, xerr = xsize_data, old_ptr = img.data + sy * line_d; dx > 0; dx --, n++) { - RFLOAT myval = (DIRECT_MULTIDIM_ELEM(img, n) - minval) / step; - RFLOAT myfom = (DIRECT_MULTIDIM_ELEM(fom_img, n) - fom_min) / fom_step; - std::cerr << myval << " + " << myfom << " = " << myval+myfom << std::endl; - img_data[3*n] = FLOOR(0.5*myval + myfom); - img_data[3*n+1] = FLOOR(0.5*myval); - img_data[3*n+2] = FLOOR(0.5*myval); + RFLOAT val = (maxval - DIRECT_MULTIDIM_ELEM(img, n)) / range; + RFLOAT fom = (DIRECT_MULTIDIM_ELEM(fom_img, n) - _fom_min) / fom_range; + if (fom < 0.5) + { + img_data[3*n] = FLOOR(127*val); + img_data[3*n+1] = FLOOR(127*val); + img_data[3*n+2] = FLOOR(127*val); + } + else + { + img_data[3*n] = FLOOR(127*val + 127*fom); + img_data[3*n+1] = FLOOR(127*val + 127*(2*fom-1)); + img_data[3*n+2] = FLOOR(127*val); + + } old_ptr += xstep; xerr -= xmod; if (xerr <= 0) @@ -358,14 +381,23 @@ void DisplayBox::setData(MultidimArray &img, MultidimArray &fom_ { FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(img) { - RFLOAT val = (DIRECT_MULTIDIM_ELEM(img, n) - minval) / step; - RFLOAT fom = (DIRECT_MULTIDIM_ELEM(fom_img, n) - fom_min) / fom_step; - if (FLOOR(0.5*val+0.5*fom)>254) std::cerr << "v= "<setData(image, fom_image, MDtmp.getObject(), 0, _minval, _maxval, _scale, true); + my_box->setData(image, fom_image, MDtmp.getObject(), 0, + _minval, _maxval, _fom_min, _fom_max, _scale, true); my_box->redraw(); boxes.push_back(my_box); } @@ -2838,7 +2873,9 @@ void Displayer::read(int argc, char **argv) color_label = parser.getOption("--color_label", "MetaDataLabel to color particles on (e.g. rlnParticleSelectZScore)", ""); color_blue_value = textToFloat(parser.getOption("--blue", "Value of the blue color", "1.")); color_red_value = textToFloat(parser.getOption("--red", "Value of the red color", "0.")); - fn_fom = parser.getOption("--fom", "FOM-image to colour the micrograph for picking",""); + fn_fom = parser.getOption("--fom_img", "FOM-image to colour the micrograph for picking",""); + fom_min = textToFloat(parser.getOption("--fom_min", "Pixel value for lowest FOM value (black)", "0")); + fom_max = textToFloat(parser.getOption("--fom_max", "Pixel value for highest FOM value (yellow)", "0")); verb = textToInteger(parser.getOption("--verb", "Verbosity", "1")); @@ -3131,7 +3168,7 @@ void Displayer::run() if (fn_coords=="") fn_coords = fn_in.withoutExtension()+"_coords.star"; win.fillPickerViewerCanvas(img(), fom_img(), minval, maxval, sigma_contrast, scale, coord_scale, ROUND(scale*particle_radius), do_pick_startend, do_pick_lines, fn_coords, - fn_color, fn_in, color_label, color_blue_value, color_red_value, minimum_pick_fom); + fn_color, fn_in, color_label, color_blue_value, color_red_value, minimum_pick_fom, fom_min, fom_max); } else if (fn_in.isStarFile()) { diff --git a/src/displayer.h b/src/displayer.h index b82d08d3b..afda28221 100644 --- a/src/displayer.h +++ b/src/displayer.h @@ -111,7 +111,8 @@ class DisplayBox : public Fl_Box void setData(MultidimArray &img, MetaDataContainer *MDCin, int ipos, RFLOAT minval, RFLOAT maxval, RFLOAT _scale, bool do_relion_scale = false); - void setData(MultidimArray &img, MultidimArray &fom_img, MetaDataContainer *MDCin, int ipos, RFLOAT minval, RFLOAT maxval, + void setData(MultidimArray &img, MultidimArray &fom_img, MetaDataContainer *MDCin, int ipos, + RFLOAT minval, RFLOAT maxval, RFLOAT _fom_min, RFLOAT _fom_max, RFLOAT _scale, bool do_relion_scale = false); // Destructor @@ -152,7 +153,7 @@ class basisViewerWindow : public Fl_Window int fillPickerViewerCanvas(MultidimArray image, MultidimArray fom_image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale, RFLOAT _coord_scale, int _particle_radius, bool do_startend = false, bool do_lines = false, FileName _fn_coords = "", FileName _fn_color = "", FileName _fn_mic= "", FileName _color_label = "", RFLOAT _color_blue_value = 0., RFLOAT _color_red_value = 1., - RFLOAT _minimum_pick_fom = -999.); + RFLOAT _minimum_pick_fom = -999., RFLOAT _min_fom = 0., RFLOAT _max_fom = 0.); }; class basisViewerCanvas : public Fl_Widget @@ -192,7 +193,7 @@ class basisViewerCanvas : public Fl_Widget RFLOAT _sigma_contrast, RFLOAT _scale, int _ncol, bool do_recenter = false, long int max_images = -1, RFLOAT lowpass = -1.0, RFLOAT highpass = -1.0); void fill(MultidimArray &image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale = 1.); - void fill(MultidimArray &image, MultidimArray &fom_image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale = 1.); + void fill(MultidimArray &image, MultidimArray &fom_image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _fom_min, RFLOAT _fom_max, RFLOAT _sigma_contrast, RFLOAT _scale = 1.); void setSelectionType(); void setFOMThreshold(); }; @@ -563,6 +564,9 @@ class Displayer // Input & Output rootname FileName fn_in, fn_fom; + // Min and max for FOM scale + RFLOAT fom_min, fom_max; + // Ignore optics groups bool do_ignore_optics; diff --git a/src/manualpicker.cpp b/src/manualpicker.cpp index 7ba1062da..970aea6e0 100644 --- a/src/manualpicker.cpp +++ b/src/manualpicker.cpp @@ -24,6 +24,7 @@ std::vector imics; std::vector global_fn_mics; std::vector global_fn_picks; std::vector global_fn_ctfs; +std::vector global_fn_foms; std::vector selected; std::vector number_picked; std::vector viewmic_buttons; @@ -159,9 +160,15 @@ void cb_viewmic(Fl_Widget* w, void* data) if (global_fn_color != "") command += " --color_star " + global_fn_color; } + if (global_fn_foms.size() > 0) + { + command += " --fom_img " + global_fn_foms[mymic]; + command += " --fom_min " + floatToString(global_blue_value); + command += " --fom_max " + floatToString(global_red_value); + } command += " &"; - std::cerr << " command= " << command << std::endl; + //std::cerr << " command= " << command << std::endl; int res = system(command.c_str()); } @@ -190,7 +197,7 @@ void cb_viewctf(Fl_Widget* w, void* data) command = "relion_display --i " + global_fn_ctfs[imic]; command += " --scale " + floatToString(global_ctfscale); command += " --sigma_contrast " + floatToString(global_ctfsigma); - command += " &"; + command += " &"; int res = system(command.c_str()); last_ctf_viewed = imic; @@ -276,7 +283,7 @@ int manualpickerGuiWindow::fill() global_has_ctf = MDin.containsLabel(EMDL_CTF_IMAGE); - FileName fn_mic, fn_pick, fn_ctf; + FileName fn_mic, fn_pick, fn_ctf, fn_fom; int ystep = 35; imics.clear(); @@ -289,6 +296,7 @@ int manualpickerGuiWindow::fill() global_fn_mics.clear(); global_fn_picks.clear(); global_fn_ctfs.clear(); + global_fn_foms.clear(); text_displays.clear(); viewmic_buttons.clear(); viewctf_buttons.clear(); @@ -314,6 +322,12 @@ int manualpickerGuiWindow::fill() } global_fn_picks.push_back(fn_pick); + if (MDin.containsLabel(EMDL_MICROGRAPH_AUTOPICK_FOM)) + { + MDin.getValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_fom); + global_fn_foms.push_back(fn_fom); + } + Fl_Check_Button *mycheck = new Fl_Check_Button(4, current_y, ystep-8, ystep-8, ""); mycheck->callback(cb_selectmic, &(imics[imic])); mycheck->value(1); From bd8a326a19ea32f82edd2ae7053aa26672a034f1 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 28 Nov 2024 09:08:36 +0000 Subject: [PATCH 013/189] fix coulour scheme with fom images --- src/displayer.cpp | 18 +++++++++--------- src/manualpicker.cpp | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/displayer.cpp b/src/displayer.cpp index ec4f43a5a..af32ac3a2 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -2277,17 +2277,17 @@ void pickerViewerCanvas::loadCoordinates(bool ask_filename) fn_coord_in = (fn_coords=="") ? "picked.star" : fn_coords; } MDcoords.read(fn_coord_in); - if (do_lines) + if (do_lines) + { + int ifil_max =0; + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDcoords) { - int ifil_max =0; - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDcoords) - { - int ifil; - MDcoords.getValue(EMDL_PARTICLE_SELECTION_TYPE, ifil); - if (ifil > ifil_max) ifil_max=ifil; - } - current_selection_type = ifil_max+1; + int ifil; + MDcoords.getValue(EMDL_PARTICLE_SELECTION_TYPE, ifil); + if (ifil > ifil_max) ifil_max=ifil; } + current_selection_type = ifil_max+1; + } if (fn_color != "") { diff --git a/src/manualpicker.cpp b/src/manualpicker.cpp index 970aea6e0..b5ca2fcc4 100644 --- a/src/manualpicker.cpp +++ b/src/manualpicker.cpp @@ -152,7 +152,7 @@ void cb_viewmic(Fl_Widget* w, void* data) { command += " --minimum_pick_fom " + floatToString(global_minimum_fom); } - if (global_color_label != "") + if (global_fn_foms.size() ==0 & global_color_label != "") { command += " --color_label " + global_color_label; command += " --blue " + floatToString(global_blue_value); From ef6256475d6b011594b41fab706153cc5936a2ce Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 28 Nov 2024 15:04:40 +0000 Subject: [PATCH 014/189] for David Barford: write out subtomos --- src/gui_jobwindow.cpp | 1 + src/jaz/tomography/programs/subtomo.cpp | 105 +++++++++++++++--------- src/jaz/tomography/programs/subtomo.h | 3 +- src/pipeline_jobs.cpp | 16 +++- 4 files changed, 81 insertions(+), 44 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index ca8a5b8fb..3c2efd528 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -2887,6 +2887,7 @@ void JobWindow::initialiseTomoSubtomoWindow() current_y += STEPY /2 ; + place("do_reproject_subtomo", TOGGLE_DEACTIVATE); place("do_real_subtomo", TOGGLE_DEACTIVATE); tab2->end(); diff --git a/src/jaz/tomography/programs/subtomo.cpp b/src/jaz/tomography/programs/subtomo.cpp index dc1a2adfb..698f10777 100644 --- a/src/jaz/tomography/programs/subtomo.cpp +++ b/src/jaz/tomography/programs/subtomo.cpp @@ -95,7 +95,8 @@ void SubtomoProgram::readBasicParameters(IOParser& parser) run_from_GUI = is_under_pipeline_control(); - do_real_subtomo = parser.checkOption("--real_subtomo", "Extract true subtomograms and write out projections of those out as 2D stacks"); + do_reproject_subtomo = parser.checkOption("--reproject_subtomo", "Extract true subtomograms and write out re-projections of those out as 2D stacks"); + do_real_subtomo = parser.checkOption("--real_subtomo", "Extract true subtomograms and write those out as 3D volumes"); } @@ -239,6 +240,10 @@ void SubtomoProgram::initialise( { Log::print("Slash found in first particle name: not creating subdirectories for each tomogram"); } + if (do_real_subtomo) + { + Log::print("Extracting true subtomogram volumes, which loose all information about the missing wedge and are therefore suboptimal for averaging, alignment or classification"); + } } for (int t = 0; t < tc; t++) @@ -282,7 +287,7 @@ void SubtomoProgram::writeParticleSet( copy.clearParticles(); copy.is_stack2d = do_stack2d; - if (do_real_subtomo) + if (do_reproject_subtomo) { copy2d = particleSet; copy2d.clearParticles(); @@ -303,7 +308,7 @@ void SubtomoProgram::writeParticleSet( Tomogram tomogram = tomogramSet.loadTomogram(t, false); - if (do_real_subtomo) + if (do_reproject_subtomo || do_real_subtomo) { RFLOAT tomogram_binning; tomogramSet.globalTable.getValue(EMDL_TOMO_TOMOGRAM_BINNING, tomogram_binning, t); @@ -334,7 +339,7 @@ void SubtomoProgram::writeParticleSet( const ParticleIndex new_id = copy.addParticle(particleSet, part_id); // Also set isVisible in the output particle STAR file - if (do_stack2d) + if (do_stack2d && !do_real_subtomo) { std::vector isVisibleInt(isVisible.size(), 0); for (int f = 0; f < tomogram.frameCount; f++) @@ -349,6 +354,7 @@ void SubtomoProgram::writeParticleSet( part_id, t, particleSet, tomogramSet); std::string outData = (do_stack2d) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; + if (do_real_subtomo) outData = filenameRoot + "_subtomo.mrc"; std::string outWeight = (do_stack2d) ? "" : filenameRoot + "_weights.mrc"; copy.setImageFileNames(outData, outWeight, new_id); @@ -374,7 +380,7 @@ void SubtomoProgram::writeParticleSet( copy.partTable.setValue(EMDL_ORIENT_PSI, 0.0, new_id.value); } - if (do_real_subtomo) + if (do_reproject_subtomo) { // Also make a particle star file for 2D classification mintilt_idx = tomogramSet.getImageIndexWithSmallestVisibleTiltAngle(t, isVisible); @@ -405,21 +411,21 @@ void SubtomoProgram::writeParticleSet( { bool is_premultiplied = (do_stack2d) ? do_ctf : true; - if (do_real_subtomo) + if (do_reproject_subtomo || do_real_subtomo) { is_premultiplied = false; copy.optTable.setValue(EMDL_OPTIMISER_DATA_ARE_CTF_CORRECTED, true, og); } copy.optTable.setValue(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED, is_premultiplied, og); - int datadim = (do_stack2d || do_real_subtomo) ? 2 : 3; + int datadim = (do_stack2d || do_reproject_subtomo) ? 2 : 3; copy.optTable.setValue(EMDL_IMAGE_DIMENSIONALITY, datadim, og); - RFLOAT mybinning = (do_real_subtomo) ? real_subtomo_binning : binning; + RFLOAT mybinning = (do_reproject_subtomo || do_real_subtomo) ? real_subtomo_binning : binning; copy.optTable.setValue(EMDL_TOMO_SUBTOMOGRAM_BINNING, mybinning, og); const double ps_img = copy.optTable.getDouble(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, og); const double ps_out = mybinning * ps_img; copy.optTable.setValue(EMDL_IMAGE_PIXEL_SIZE, ps_out, og); - int mysize = (do_real_subtomo) ? cropSize_tomogram : cropSize; + int mysize = (do_reproject_subtomo || do_real_subtomo) ? cropSize_tomogram : cropSize; copy.optTable.setValue(EMDL_IMAGE_SIZE, mysize, og); @@ -433,7 +439,7 @@ void SubtomoProgram::writeParticleSet( copy.write(outDir + "particles.star"); - if (do_real_subtomo) + if (do_reproject_subtomo) { if (copy2d.partTable.containsLabel(EMDL_IMAGE_COORD_X)) copy2d.partTable.deactivateLabel(EMDL_IMAGE_COORD_X); @@ -460,7 +466,7 @@ void SubtomoProgram::writeParticleSet( BufferedImage SubtomoProgram::extractSubtomogramsAndReProject( ParticleIndex part_id, MultidimArray &recTomo, const Tomogram& tomogram, const ParticleSet &particleSet, - const std::vector &isVisible, RFLOAT tomogram_angpix) + const std::vector &isVisible, RFLOAT tomogram_angpix, bool do_reproject) { // get decentered coordinates of the particle (in tilt series pixels) @@ -548,41 +554,52 @@ BufferedImage SubtomoProgram::extractSubtomogramsAndReProject( //It.write("subtom.spi"); //exit(0); - MultidimArray dummy; - Projector projector(cropSize_tomogram); - projector.computeFourierTransformMap(subtom, dummy); + BufferedImage resultImg; + if (do_reproject) + { - MultidimArray img(cropSize_tomogram, cropSize_tomogram); - MultidimArray F2D; - FourierTransformer transformer; - transformer.setReal(img); - transformer.getFourierAlias(F2D); + MultidimArray dummy; + Projector projector(cropSize_tomogram); + projector.computeFourierTransformMap(subtom, dummy); - BufferedImage resultImg(cropSize_tomogram, cropSize_tomogram, tomogram.frameCount); - for (int f = 0; f < tomogram.frameCount; f++) - { + MultidimArray img(cropSize_tomogram, cropSize_tomogram); + MultidimArray F2D; + FourierTransformer transformer; + transformer.setReal(img); + transformer.getFourierAlias(F2D); - if (!isVisible[f]) continue; + resultImg.resize(cropSize_tomogram, cropSize_tomogram, tomogram.frameCount); + for (int f = 0; f < tomogram.frameCount; f++) + { - d4Matrix Aproj = tomogram.projectionMatrices[f]; - Matrix2D A(3,3); - for (int row= 0; row < 3; row++) - for (int col = 0; col < 3; col++) - MAT_ELEM(A, row, col) = Aproj(row, col); + if (!isVisible[f]) continue; - // Get the 2D slice out of the 3D Fourier transform - F2D.initZeros(); - projector.get2DFourierTransform(F2D, A); - shiftImageInFourierTransform(F2D, F2D, cropSize_tomogram, cropSize_tomogram/2, cropSize_tomogram/2); - transformer.inverseFourierTransform(); + d4Matrix Aproj = tomogram.projectionMatrices[f]; + Matrix2D A(3,3); + for (int row= 0; row < 3; row++) + for (int col = 0; col < 3; col++) + MAT_ELEM(A, row, col) = Aproj(row, col); - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(img) + // Get the 2D slice out of the 3D Fourier transform + F2D.initZeros(); + projector.get2DFourierTransform(F2D, A); + shiftImageInFourierTransform(F2D, F2D, cropSize_tomogram, cropSize_tomogram/2, cropSize_tomogram/2); + transformer.inverseFourierTransform(); + + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(img) + { + resultImg(j, i, f) = DIRECT_A2D_ELEM(img, i, j); + } + } + } + else + { + resultImg.resize(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY3D(subtom) { - resultImg(j, i, f) = DIRECT_A2D_ELEM(img, i, j); + resultImg(j, i, k) = DIRECT_A3D_ELEM(subtom, k, i, j); } - } - return resultImg; } @@ -637,7 +654,7 @@ void SubtomoProgram::processTomograms( // If using the real_subtomo approach, then need to read in the reconstructed tomogram volume Image recTomo; RFLOAT tomogram_angpix, tomogram_binning; - if (do_real_subtomo) + if (do_reproject_subtomo || do_real_subtomo) { FileName fn_tomo, fn_tomo2=""; if (tomogramSet.globalTable.containsLabel(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_FILE_NAME)) @@ -707,6 +724,7 @@ void SubtomoProgram::processTomograms( part_id, t, particleSet, tomogramSet); std::string outData = (do_stack2d) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; + if (do_real_subtomo) outData = filenameRoot + "_subtomo.mrc"; std::string outWeight = (do_stack2d) ? "" : filenameRoot + "_weights.mrc"; std::string outCTF = filenameRoot + "_CTF2.mrc"; std::string outDiv = filenameRoot + "_div.mrc"; @@ -730,16 +748,23 @@ void SubtomoProgram::processTomograms( BufferedImage particleStack = BufferedImage(sh2D, s2D, fc); BufferedImage weightStack(sh2D, s2D, fc); - if (do_real_subtomo) + if (do_reproject_subtomo) { // This will extract the true subtomograms and calculate their FTs in the directions of the tilt series BufferedImage subtomo_reprojs = extractSubtomogramsAndReProject(part_id, recTomo(), - tomogram, particleSet, isVisible, tomogram_angpix); + tomogram, particleSet, isVisible, tomogram_angpix, true); BufferedImage visible_reprojs = NewStackHelper::getVisibleSlices(subtomo_reprojs, isVisible); visible_reprojs.write(outData, tomogram_angpix, write_float16); } + else if (do_real_subtomo) + { + // This will extract the true subtomograms and calculate their FTs in the directions of the tilt series + BufferedImage subtomo = extractSubtomogramsAndReProject(part_id, recTomo(), + tomogram, particleSet, isVisible, tomogram_angpix, false); + subtomo.write(outData, tomogram_angpix, write_float16); + } else { diff --git a/src/jaz/tomography/programs/subtomo.h b/src/jaz/tomography/programs/subtomo.h index 3402ceaf5..8c3d48cf4 100644 --- a/src/jaz/tomography/programs/subtomo.h +++ b/src/jaz/tomography/programs/subtomo.h @@ -70,6 +70,7 @@ class SubtomoProgram write_float16, run_from_GUI, run_from_MPI, + do_reproject_subtomo, do_real_subtomo; void readBasicParameters(IOParser& parser); @@ -88,7 +89,7 @@ class SubtomoProgram BufferedImage extractSubtomogramsAndReProject( ParticleIndex part_id, MultidimArray &recTomo, const Tomogram& tomogram, const ParticleSet &particleSet, - const std::vector &isVisible, RFLOAT tomogram_angpix); + const std::vector &isVisible, RFLOAT tomogram_angpix, bool do_reproject); void processTomograms( const std::vector& tomoIndices, diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 2614ae450..55d0be633 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -7103,7 +7103,8 @@ void RelionJob::initialiseTomoSubtomoJob() joboptions["do_stack2d"] = JobOption("Write output as 2D stacks?", true ,"If set to Yes, this program will write output subtomograms as 2D substacks. This is new as of relion-4.1, and the preferred way of generating subtomograms. If set to No, then relion-4.0 3D pseudo-subtomograms will be written out. Either can be used in subsequent refinements and classifications."); joboptions["do_float16"] = JobOption("Write output in float16?", true ,"If set to Yes, this program will write output images in float16 MRC format. This will save a factor of two in disk space compared to the default of writing in float32. Note that RELION and CCPEM will read float16 images, but other programs may not (yet) do so."); - joboptions["do_real_subtomo"] = JobOption("Extract and re-project real subtomograms?", false, "If set to Yes, this program will box out real subtomograms from the input tomogram and then re-project these into 2D stacks that resemble the 2D stacks from windowed tilt series images. The size of the images is determined by the crop size; the pixel size is that of the tomogram. This will result in a particles.star file that is suitable for low-resolution subtomogram averaging and a particles_for_class2d.star file that can be used for fast 2D classification in order to select good particles. You can re-launch the GUI without the --tomo argument to get access to the Class2D job type. If set to No, the program will write out normal windowed tilt-series images that are suitable for high-resolution subtomogram averaging."); + joboptions["do_reproject_subtomo"] = JobOption("Extract and re-project real subtomograms?", false, "If set to Yes, this program will box out real subtomograms from the input tomogram and then re-project these into 2D stacks that resemble the 2D stacks from windowed tilt series images. The size of the images is determined by the crop size; the pixel size is that of the tomogram. This will result in a particles.star file that is suitable for low-resolution subtomogram averaging and a particles_for_class2d.star file that can be used for fast 2D classification in order to select good particles. You can re-launch the GUI without the --tomo argument to get access to the Class2D job type. If set to No, the program will write out normal windowed tilt-series images that are suitable for high-resolution subtomogram averaging."); + joboptions["do_real_subtomo"] = JobOption("Extract real subtomograms?", false, "If set to Yes, this program will box out real subtomograms from the input tomogram and write those out as 3D subvolumes. All information about the missing wedge will be lost, and these volumes should probably not be used for alignment, classification or averaging."); } @@ -7159,12 +7160,21 @@ bool RelionJob::getCommandsTomoSubtomoJob(std::string &outputname, std::vector Date: Thu, 28 Nov 2024 15:52:44 +0000 Subject: [PATCH 015/189] repiared bug in subtomo when first tomogram is empty --- src/jaz/tomography/programs/subtomo.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/jaz/tomography/programs/subtomo.cpp b/src/jaz/tomography/programs/subtomo.cpp index 698f10777..b4606981e 100644 --- a/src/jaz/tomography/programs/subtomo.cpp +++ b/src/jaz/tomography/programs/subtomo.cpp @@ -298,6 +298,7 @@ void SubtomoProgram::writeParticleSet( RFLOAT real_subtomo_binning = 0.; int cropSize_tomogram; + bool is_first = true; for (int t = 0; t < tc; t++) { const int pc = particles[t].size(); @@ -312,8 +313,15 @@ void SubtomoProgram::writeParticleSet( { RFLOAT tomogram_binning; tomogramSet.globalTable.getValue(EMDL_TOMO_TOMOGRAM_BINNING, tomogram_binning, t); - if (t == 0) real_subtomo_binning = tomogram_binning; - else if (real_subtomo_binning != tomogram_binning) REPORT_ERROR("ERROR: not all tomograms have the same binning; can't do real subtomos"); + if (is_first) + { + real_subtomo_binning = tomogram_binning; + is_first = false; + } + else if (fabs(real_subtomo_binning - tomogram_binning) > 0.01) + { + REPORT_ERROR("ERROR: not all tomograms have the same binning; can't do real subtomos"); + } cropSize_tomogram = ROUND(cropSize * binning / tomogram_binning); if (cropSize_tomogram%2 != 0) { From 00f21acdffef1990b73126450f3ea23e4e782ed8 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 3 Dec 2024 13:59:44 +0000 Subject: [PATCH 016/189] protect symfile reading from threads; necessary somehow... --- src/backprojector.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/backprojector.h b/src/backprojector.h index 7651d5530..2933274f6 100644 --- a/src/backprojector.h +++ b/src/backprojector.h @@ -91,8 +91,11 @@ class BackProjector: public Projector // Skip gridding skip_gridding = _skip_gridding; - // Set the symmetry object - SL.read_sym_file(fn_sym); + // Set the symmetry object (somehow this is not thread-safe...) +#pragma omp critical + { + SL.read_sym_file(fn_sym); + } // Padding factor for the map if (_padding_factor_3d < 1.0) From 043956ed7cf9e7993c0b93196cf925a817da7431 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 3 Dec 2024 14:16:26 +0000 Subject: [PATCH 017/189] repaired bug if 3D subtomos without multiplicity --- src/ml_optimiser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 2a28fc90e..70dfae550 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -6785,7 +6785,7 @@ void MlOptimiser::precalculateShiftedImagesCtfsAndInvSigma2s(bool do_also_unmask exp_local_Fctf.resize(exp_nr_images); exp_local_sqrtXi2.resize(exp_nr_images); - bool do_subtomo_correction = NZYXSIZE(exp_STMulti) > 0; + bool do_subtomo_correction = YSIZE(exp_STMulti) > 1; int group_id = mydata.getGroupId(part_id); int optics_group = mydata.getOpticsGroup(part_id); @@ -8277,7 +8277,7 @@ void MlOptimiser::storeWeightedSums(long int part_id, int ibody, bool ctf_premultiplied = mydata.obsModel.getCtfPremultiplied(optics_group); MultidimArray exp_local_STMulti; - bool do_subtomo_correction = NZYXSIZE(exp_STMulti) > 0; + bool do_subtomo_correction = YSIZE(exp_STMulti) > 1; // Re-do below because now also want unmasked images AND if (stricht_highres_exp >0.) then may need to resize precalculateShiftedImagesCtfsAndInvSigma2s(true, true, part_id, exp_current_oversampling, metadata_offset, From e1bc05f36c51b641508c43b0a71a0c1dd0a5082c Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 3 Dec 2024 14:16:44 +0000 Subject: [PATCH 018/189] repaired bug if 3D subtomos without multiplicity --- src/acc/acc_ml_optimiser_impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/acc/acc_ml_optimiser_impl.h b/src/acc/acc_ml_optimiser_impl.h index 081f49117..c29c2401b 100644 --- a/src/acc/acc_ml_optimiser_impl.h +++ b/src/acc/acc_ml_optimiser_impl.h @@ -2570,7 +2570,7 @@ void storeWeightedSums(OptimisationParamters &op, SamplingParameters &sp, std::vector > dummy; std::vector > > dummy2; MultidimArray exp_local_STMulti; - bool do_subtomo_correction = NZYXSIZE(op.FstMulti) > 0; + bool do_subtomo_correction = YSIZE(op.FstMulti) > 1; baseMLO->precalculateShiftedImagesCtfsAndInvSigma2s(false, true, op.part_id, sp.current_oversampling, op.metadata_offset, // inserted SHWS 12112015 sp.itrans_min, sp.itrans_max, op.Fimg, op.Fimg_nomask, op.Fctf, op.old_offset, dummy2, dummy2, From bbc1c74a68f8d8356b6f7bdaba250e6d0c12b292 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 3 Dec 2024 14:52:10 +0000 Subject: [PATCH 019/189] allow refine3d and class3d without --tomograms, for relion-3-style averaging of subtomos --- src/pipeline_jobs.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 55d0be633..bf01e5c4a 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -3909,7 +3909,7 @@ bool RelionJob::getCommandsClass3DJob(std::string &outputname, std::vector Date: Fri, 6 Dec 2024 09:19:54 +0000 Subject: [PATCH 020/189] better comment on why ysize for Fstmulti check --- src/ml_optimiser.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 70dfae550..b154d3498 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -6785,6 +6785,7 @@ void MlOptimiser::precalculateShiftedImagesCtfsAndInvSigma2s(bool do_also_unmask exp_local_Fctf.resize(exp_nr_images); exp_local_sqrtXi2.resize(exp_nr_images); + // This used to be NZYXSIZE(exp_STMulti) > 0, but the GPU resizes this array to non-zero xsize, where ysize=zsize=1! bool do_subtomo_correction = YSIZE(exp_STMulti) > 1; int group_id = mydata.getGroupId(part_id); From 497cb3208a52c19f99c89a311d6d4e9ef87ee765 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 6 Dec 2024 09:20:17 +0000 Subject: [PATCH 021/189] allow setting individual pixel values in images --- src/apps/image_handler.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/apps/image_handler.cpp b/src/apps/image_handler.cpp index 249d50e00..74d755e03 100644 --- a/src/apps/image_handler.cpp +++ b/src/apps/image_handler.cpp @@ -36,9 +36,9 @@ class image_handler_parameters { public: FileName fn_in, fn_out, fn_sel, fn_img, fn_sym, fn_sub, fn_mult, fn_div, fn_add, fn_subtract, fn_mask, fn_fsc, fn_adjust_power, fn_correct_ampl, fn_fourfilter, fn_cosDPhi; - int bin_avg, avg_first, avg_last, edge_x0, edge_xF, edge_y0, edge_yF, filter_edge_width, new_box, minr_ampl_corr, my_new_box_size; - bool do_add_edge, do_invert_hand, do_flipXY, do_flipmXY, do_flipZ, do_flipX, do_flipY, do_shiftCOM, do_stats, do_calc_com, do_avg_ampl, do_avg_ampl2, do_avg_ampl2_ali, do_average, do_remove_nan, do_average_all_frames, do_power, do_guinier, do_ignore_optics, do_optimise_scale_subtract, write_float16; - RFLOAT multiply_constant, divide_constant, add_constant, subtract_constant, threshold_above, threshold_below, angpix, requested_angpix, real_angpix, force_header_angpix, lowpass, highpass, logfilter, bfactor, shift_x, shift_y, shift_z, replace_nan, randomize_at, optimise_bfactor_subtract; + int bin_avg, avg_first, avg_last, edge_x0, edge_xF, edge_y0, edge_yF, filter_edge_width, new_box, minr_ampl_corr, my_new_box_size, pix_x, pix_y, pix_z; + bool do_add_edge, do_invert_hand, do_flipXY, do_flipmXY, do_flipZ, do_flipX, do_flipY, do_shiftCOM, do_stats, do_calc_com, do_avg_ampl, do_avg_ampl2, do_avg_ampl2_ali, do_average, do_remove_nan, do_average_all_frames, do_power, do_guinier, do_ignore_optics, do_optimise_scale_subtract, write_float16, do_set_pixel; + RFLOAT multiply_constant, divide_constant, add_constant, subtract_constant, threshold_above, threshold_below, angpix, requested_angpix, real_angpix, force_header_angpix, lowpass, highpass, logfilter, bfactor, shift_x, shift_y, shift_z, replace_nan, randomize_at, optimise_bfactor_subtract, pix_val; // PNG options RFLOAT minval, maxval, sigma_contrast, guinier_fit_minres, guinier_fit_maxres; int color_scheme; // There is a global variable called colour_scheme in displayer.h! @@ -133,8 +133,12 @@ class image_handler_parameters do_remove_nan = parser.checkOption("--remove_nan", "Replace non-numerical values (NaN, inf, etc) in the image(s)"); replace_nan = textToFloat(parser.getOption("--replace_nan", "Replace non-numerical values (NaN, inf, etc) with this value", "0")); randomize_at = textToFloat(parser.getOption("--phase_randomise", "Randomise phases beyond this resolution (in Angstroms)", "-1")); + pix_val = textToFloat(parser.getOption("--pixel_value", "Replace pixel specified below with this value (default is don't replace)", "-999")); + pix_x = textToInteger(parser.getOption("--pixel_x", "Pixel X-coordinate to replace value n absolute coordinates", "-1")); + pix_y = textToInteger(parser.getOption("--pixel_y", "Pixel Y-coordinate to replace value n absolute coordinates", "-1")); + pix_z = textToInteger(parser.getOption("--pixel_z", "Pixel Z-coordinate to replace value n absolute coordinates", "-1")); - int three_d_section = parser.addSection("3D operations"); + int three_d_section = parser.addSection("3D operations"); fn_sym = parser.getOption("--sym", "Symmetrise 3D map with this point group (e.g. D6)", ""); int preprocess_section = parser.addSection("2D-micrograph (or movie) operations"); @@ -618,6 +622,14 @@ class image_handler_parameters DIRECT_A3D_ELEM(Iout(), k, i, j) = A3D_ELEM(Iin(), k, i, dest_x); } } + else if (fabs(pix_val+999) > 0.1) + { + Iout() = Iin(); + if (ZSIZE(Iout()) < 2) + DIRECT_A2D_ELEM(Iout(), pix_y, pix_x) = pix_val; + else + DIRECT_A3D_ELEM(Iout(), pix_z, pix_y, pix_x) = pix_val; + } // Shifting if (do_shiftCOM) From 1d3da22de6f9b6fc0382cd117a50c61ff253f134 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 6 Dec 2024 13:45:42 +0000 Subject: [PATCH 022/189] added option to output FFT power image --- src/apps/image_handler.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/apps/image_handler.cpp b/src/apps/image_handler.cpp index 74d755e03..21106a409 100644 --- a/src/apps/image_handler.cpp +++ b/src/apps/image_handler.cpp @@ -37,7 +37,7 @@ class image_handler_parameters public: FileName fn_in, fn_out, fn_sel, fn_img, fn_sym, fn_sub, fn_mult, fn_div, fn_add, fn_subtract, fn_mask, fn_fsc, fn_adjust_power, fn_correct_ampl, fn_fourfilter, fn_cosDPhi; int bin_avg, avg_first, avg_last, edge_x0, edge_xF, edge_y0, edge_yF, filter_edge_width, new_box, minr_ampl_corr, my_new_box_size, pix_x, pix_y, pix_z; - bool do_add_edge, do_invert_hand, do_flipXY, do_flipmXY, do_flipZ, do_flipX, do_flipY, do_shiftCOM, do_stats, do_calc_com, do_avg_ampl, do_avg_ampl2, do_avg_ampl2_ali, do_average, do_remove_nan, do_average_all_frames, do_power, do_guinier, do_ignore_optics, do_optimise_scale_subtract, write_float16, do_set_pixel; + bool do_add_edge, do_invert_hand, do_flipXY, do_flipmXY, do_flipZ, do_flipX, do_flipY, do_shiftCOM, do_stats, do_power_image, do_calc_com, do_avg_ampl, do_avg_ampl2, do_avg_ampl2_ali, do_average, do_remove_nan, do_average_all_frames, do_power, do_guinier, do_ignore_optics, do_optimise_scale_subtract, write_float16, do_set_pixel; RFLOAT multiply_constant, divide_constant, add_constant, subtract_constant, threshold_above, threshold_below, angpix, requested_angpix, real_angpix, force_header_angpix, lowpass, highpass, logfilter, bfactor, shift_x, shift_y, shift_z, replace_nan, randomize_at, optimise_bfactor_subtract, pix_val; // PNG options RFLOAT minval, maxval, sigma_contrast, guinier_fit_minres, guinier_fit_maxres; @@ -124,7 +124,8 @@ class image_handler_parameters shift_x = textToFloat(parser.getOption("--shift_x", "Shift images this many pixels in the X-direction", "0.")); shift_y = textToFloat(parser.getOption("--shift_y", "Shift images this many pixels in the Y-direction", "0.")); shift_z = textToFloat(parser.getOption("--shift_z", "Shift images this many pixels in the Z-direction", "0.")); - do_avg_ampl = parser.checkOption("--avg_ampl", "Calculate average amplitude spectrum for all images?"); + do_power_image = parser.checkOption("--power_image", "Output image with Fourier squared amplitudes?"); + do_avg_ampl = parser.checkOption("--avg_ampl", "Calculate average amplitude spectrum for all images?"); do_avg_ampl2 = parser.checkOption("--avg_ampl2", "Calculate average amplitude spectrum for all images?"); do_avg_ampl2_ali = parser.checkOption("--avg_ampl2_ali", "Calculate average amplitude spectrum for all aligned images?"); do_average = parser.checkOption("--average", "Calculate average of all images (without alignment)"); @@ -435,6 +436,18 @@ class image_handler_parameters } MDpower.write(std::cout); } + else if (do_power_image) + { + MultidimArray< Complex > FT; + FourierTransformer transformer; + transformer.FourierTransform(Iin(), FT); + Iout().resize(ZSIZE(FT), YSIZE(FT), XSIZE(FT)); + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(FT) + { + DIRECT_MULTIDIM_ELEM(Iout(), n) = NZYXSIZE(Iin())*norm(DIRECT_MULTIDIM_ELEM(FT, n)); + } + DIRECT_MULTIDIM_ELEM(Iout(), 0) = 0.; //remove zero-component + } else if (do_guinier) { From 9d29660957db7b4c910f05dc6480a54803ef7171 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 6 Dec 2024 13:47:56 +0000 Subject: [PATCH 023/189] also order tables on pre-exposure before generating the single MD --- src/jaz/tomography/tomogram_set.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/jaz/tomography/tomogram_set.cpp b/src/jaz/tomography/tomogram_set.cpp index e19425aa7..060cd6210 100644 --- a/src/jaz/tomography/tomogram_set.cpp +++ b/src/jaz/tomography/tomogram_set.cpp @@ -769,6 +769,10 @@ void TomogramSet::generateSingleMetaDataTable(MetaDataTable &MDout, ObservationM MDout.clear(); for (long int t = 0; t < tomogramTables.size(); t++) { + + //Make sure we order tilt series on pre-exposure, as we'll also do this when combining everything at the end + tomogramTables[t].newSort(EMDL_MICROGRAPH_PRE_EXPOSURE); + // Store all the necessary optics stuff in an opticsGroup per tomogram RFLOAT moviePixelSize, voltage, Cs, Q0; std::string tomo_name = getTomogramName(t); From aa9e745d386ed08cfe6ff873be7f49c8afdb8d0d Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 6 Dec 2024 15:37:37 +0000 Subject: [PATCH 024/189] make sure the tomogram size gets sets correctly... --- src/jaz/tomography/tomogram_set.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/jaz/tomography/tomogram_set.cpp b/src/jaz/tomography/tomogram_set.cpp index 060cd6210..c6e4223da 100644 --- a/src/jaz/tomography/tomogram_set.cpp +++ b/src/jaz/tomography/tomogram_set.cpp @@ -177,9 +177,7 @@ Tomogram TomogramSet::loadTomogram(int index, bool loadImageData, bool loadEvenF } else { - out.w0 = -999; - out.h0 = -999; - out.d0 = -999; + REPORT_ERROR("ERROR loadTomogram: tomogram sizes have not been set for tomogram " + tomoName); } // Select only a subset of the tilt series images with the lowest dose From 27d16020b13a0f31921eb804d2cb9d8cafb8c621 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 6 Dec 2024 15:48:36 +0000 Subject: [PATCH 025/189] option to skip wiener filter in fourier method for reconstruct tomograms --- .../programs/reconstruct_tomogram.cpp | 242 +++++++++++------- .../programs/reconstruct_tomogram.h | 2 +- 2 files changed, 155 insertions(+), 89 deletions(-) diff --git a/src/jaz/tomography/programs/reconstruct_tomogram.cpp b/src/jaz/tomography/programs/reconstruct_tomogram.cpp index 19d5699eb..a5e139528 100644 --- a/src/jaz/tomography/programs/reconstruct_tomogram.cpp +++ b/src/jaz/tomography/programs/reconstruct_tomogram.cpp @@ -63,43 +63,48 @@ void TomoBackprojectProgram::readParameters(int argc, char *argv[]) tomoName = parser.getOption("--tn", "Tomogram name", "*"); outFn = parser.getOption("--o", "Output filename (or output directory in case of reconstructing multiple tomograms)"); do_even_odd_tomograms = parser.checkOption("--generate_split_tomograms", "Reconstruct tomograms from even/odd movie frames or tilt image index for denoising"); + do_only_unfinished = parser.checkOption("--only_do_unfinished", "Only reconstruct those tomograms that haven't finished yet"); w = textToInteger(parser.getOption("--w", "Width")); h = textToInteger(parser.getOption("--h", "Height" )); d = textToInteger(parser.getOption("--d", "Thickness")); - fourierInversion = parser.checkOption("--fourier", "Use a Fourier-inversion reconstruction algorithm"); - lambda = textToDouble(parser.getOption("--lambda", "Regularisation constant for CTF-correction of the SNRs in the Fourier-inversion algorithm", "10")); - ctf_intact_first_peak = parser.checkOption("--ctf_intact_first_peak", "Leave CTFs intact until first peak"); + spacing = textToDouble(parser.getOption("--bin", "Binning", "1.0")); + angpix_spacing = textToDouble(parser.getOption("--binned_angpix", "OR: desired pixel size after binning", "-1")); + + tiltAngleOffset = textToDouble(parser.getOption("--tiltangle_offset", "Offset applied to all tilt angles (in deg)", "0")); + BfactorPerElectronDose = textToDouble(parser.getOption("--bfactor_per_edose", "B-factor dose-weighting per electron/A^2 dose (default is use Niko's model)", "0")); + n_threads = textToInteger(parser.getOption("--j", "Number of threads", "1")); + + int wbp_section = parser.addSection("Weighted backprojection options"); applyWeight = !parser.checkOption("--no_weight", "Do not perform weighting in Fourier space using a Wiener filter"); applyPreWeight = parser.checkOption("--pre_weight", "Pre-weight the 2D slices prior to backprojection"); FourierCrop = parser.checkOption("--Fc", "Downsample the 2D images by Fourier cropping"); - do_only_unfinished = parser.checkOption("--only_do_unfinished", "Only reconstruct those tomograms that haven't finished yet"); SNR = textToDouble(parser.getOption("--SNR", "SNR assumed by the Wiener filter", "10")); applyCtf = parser.checkOption("--ctf", "Perform CTF correction"); doWiener = !parser.checkOption("--skip_wiener", "Do multiply images with CTF, but don't divide by CTF^2 in Wiener filter"); - - if (!doWiener) applyCtf = true; + if (doWiener) applyCtf = true; zeroDC = !parser.checkOption("--keep_mean", "Do not zero the DC component of each frame"); - - taperDist = textToDouble(parser.getOption("--td", "Tapering distance", "0.0")); - taperFalloff = textToDouble(parser.getOption("--tf", "Tapering falloff", "0.0")); + taperDist = textToDouble(parser.getOption("--td", "Tapering distance", "0.0")); + taperFalloff = textToDouble(parser.getOption("--tf", "Tapering falloff", "0.0")); // SHWS & Aburt 19Jul2022: use zero-origins from relion-4.1 onwards.... x0 = textToDouble(parser.getOption("--x0", "X origin", "0.0")); y0 = textToDouble(parser.getOption("--y0", "Y origin", "0.0")); z0 = textToDouble(parser.getOption("--z0", "Z origin", "0.0")); - spacing = textToDouble(parser.getOption("--bin", "Binning", "1.0")); - angpix_spacing = textToDouble(parser.getOption("--binned_angpix", "OR: desired pixel size after binning", "-1")); - tiltAngleOffset = textToDouble(parser.getOption("--tiltangle_offset", "Offset applied to all tilt angles (in deg)", "0")); - BfactorPerElectronDose = textToDouble(parser.getOption("--bfactor_per_edose", "B-factor dose-weighting per electron/A^2 dose (default is use Niko's model)", "0")); - n_threads = textToInteger(parser.getOption("--j", "Number of threads", "1")); + int fourier_section = parser.addSection("Fourier inversion options"); + fourierInversion = parser.checkOption("--fourier", "Use a Fourier-inversion reconstruction algorithm"); + fourierWienerFilter = !parser.checkOption("--skip_fourier_wiener", "Only CTF-premultiplication will be performed, but Wiener-type division is skipped"); + lambda = textToDouble(parser.getOption("--lambda", "Regularisation constant for CTF-correction of the SNRs in the Fourier-inversion algorithm", "10")); + ctf_intact_first_peak = parser.checkOption("--ctf_intact_first_peak", "Leave CTFs intact until first peak"); - do_2dproj = parser.checkOption("--do_proj", "Use this to skip calculation of 2D projection of the tomogram along the Z-axis"); + int proj_section = parser.addSection("2D projection options"); + + do_2dproj = parser.checkOption("--do_proj", "Use this to also generate 2D projection of the entire tomogram along the Z-axis"); centre_2dproj = textToInteger(parser.getOption("--centre_proj", "Central Z-slice for 2D projection (in tomogram pixels from the middle)", "0")); thickness_2dproj = textToInteger(parser.getOption("--thickness_proj", "Thickness of the 2D projection (in tomogram pixels)", "10")); @@ -165,6 +170,12 @@ void TomoBackprojectProgram::initialise(bool verbose) std::cout << " - centered at " << centre_2dproj << " tomogram pixels from the centre of the tomogram" << std::endl; std::cout << " - and a thickness of " << thickness_2dproj << " tomogram pixels" << std::endl; } + std::cout << " - fourierInversion= " << fourierInversion << " fourierWienerFilter= " << fourierWienerFilter << std::endl; + std::cout << " - doWiener= " << doWiener << std::endl; + std::cout << " - applyCtf= " << applyCtf << std::endl; + std::cout << " - applyPreWeight= " << applyPreWeight << std::endl; + if (doWiener) std::cout << " - applyWeight= " << applyWeight << std::endl; + } } @@ -337,9 +348,15 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) Tomogram tomogram1, tomogram2; - tomogram1 = tomogramSet.loadTomogram(tomoIndex, true, true, false, w, h, d); - tomogram2 = tomogramSet.loadTomogram(tomoIndex, true, false, true, w, h, d); - + if (fourierWienerFilter) + { + tomogram1 = tomogramSet.loadTomogram(tomoIndex, true, true, false, w, h, d); + tomogram2 = tomogramSet.loadTomogram(tomoIndex, true, false, true, w, h, d); + } + else + { + tomogram1 = tomogramSet.loadTomogram(tomoIndex, true, false, false, w, h, d); + } MetaDataTable& m = tomogramSet.tomogramTables[tomoIndex]; if (!tomogram1.hasMatrices) REPORT_ERROR("ERROR; tomograms do not have tilt series alignment parameters to calculate projectionMatrices!"); @@ -352,6 +369,7 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) int square_box = (tomogram1.stack.xdim == tomogram1.stack.ydim) ? tomogram1.stack.xdim : XMIPP_MAX(tomogram1.stack.xdim, tomogram1.stack.ydim); // Make sqrt(2) bigger to account for the empty corners that otherwise appear with the spherical mask.... square_box *= sqrt(2.); + if (square_box%2 != 0) square_box++; double pixelSizeAct = tomogramSet.getTiltSeriesPixelSize(tomoIndex); int new_box = square_box; @@ -370,18 +388,14 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) } tomogramSet.globalTable.setValue(EMDL_TOMO_TOMOGRAM_BINNING, spacing, tomoIndex); - float padding_factor = 1; - bool skip_gridding = true; BackProjector BP = BackProjector(new_box, 3, - "C1", TRILINEAR, padding_factor, - 10, 0, 1.9, 15, 2, skip_gridding); + "C1", TRILINEAR, 1., + 10, 0, 1.9, 15, 2, true); BP.initZeros(); #pragma omp parallel for num_threads(n_threads) for (int f = 0; f < fc; f++) { - //std::cerr << " f= " << f << std::endl; - CTF ctf = tomogram1.centralCTFs[f]; // Don't use CTF scale factors, as we will measure SNRs using the FSC! ctf.scale = 1.0; @@ -389,55 +403,93 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) if (ctf.DeltafU < 100. || ctf.DeltafV < 100.) continue; - // Get frame from Jasenko's stack - MultidimArray frame1(tomogram1.stack.ydim, tomogram1.stack.xdim); - MultidimArray frame2(tomogram1.stack.ydim, tomogram1.stack.xdim); - for (long int y=0; y FT1, FT2; + MultidimArray frame(tomogram1.stack.ydim, tomogram1.stack.xdim); + for (int ihalf = 0; ihalf < nr_halves; ihalf++) + { + + //std::cerr << " ihalf= " << ihalf << std::endl; + // Get frame from Jasenko's stack + if (ihalf == 0) { - DIRECT_A2D_ELEM(frame1, y, x) = tomogram1.stack(x, y, f); - DIRECT_A2D_ELEM(frame2, y, x) = tomogram2.stack(x, y, f); + for (long int y = 0; y < tomogram1.stack.ydim; y++) + for (long int x = 0; x < tomogram1.stack.xdim; x++) + DIRECT_A2D_ELEM(frame, y, x) = tomogram1.stack(x, y, f); + } + else + { + for (long int y = 0; y < tomogram2.stack.ydim; y++) + for (long int x = 0; x < tomogram2.stack.xdim; x++) + DIRECT_A2D_ELEM(frame, y, x) = tomogram2.stack(x, y, f); } - // Make square (plus factor 1.4 padding) - frame1.setXmippOrigin(); - frame2.setXmippOrigin(); - frame1.window(FIRST_XMIPP_INDEX(square_box), FIRST_XMIPP_INDEX(square_box), - LAST_XMIPP_INDEX(square_box), LAST_XMIPP_INDEX(square_box)); - frame2.window(FIRST_XMIPP_INDEX(square_box), FIRST_XMIPP_INDEX(square_box), - LAST_XMIPP_INDEX(square_box), LAST_XMIPP_INDEX(square_box)); - - // Mirror the image back out into the padding area to prevent low-resolution artifacts - int first_x = FIRST_XMIPP_INDEX(tomogram1.stack.xdim); - int last_x = LAST_XMIPP_INDEX(tomogram1.stack.xdim); - int first_y = FIRST_XMIPP_INDEX(tomogram1.stack.ydim); - int last_y = LAST_XMIPP_INDEX(tomogram1.stack.ydim); - FOR_ALL_ELEMENTS_IN_ARRAY2D(frame1) - { - int jp = j, ip = i; - bool do_change = false; - if (j < first_x) {jp = 2 * first_x - j; do_change = true;} - else if (j > last_x) {jp = 2 * last_x - j; do_change = true;} - if (i < first_y) {ip = 2 * first_y - i; do_change = true;} - else if (i > last_y) {ip = 2 * last_y - i; do_change = true;} - if (do_change) + //std::cerr <<" got frame"<< std::endl; + // Make square (plus factor 1.4 padding) + frame.setXmippOrigin(); + frame.window(FIRST_XMIPP_INDEX(square_box), FIRST_XMIPP_INDEX(square_box), + LAST_XMIPP_INDEX(square_box), LAST_XMIPP_INDEX(square_box)); + //std::cerr <<" windowed"<< std::endl; + + // Mirror the image back out into the padding area to prevent low-resolution artifacts + int first_x = FIRST_XMIPP_INDEX(tomogram1.stack.xdim); + int last_x = LAST_XMIPP_INDEX(tomogram1.stack.xdim); + int first_y = FIRST_XMIPP_INDEX(tomogram1.stack.ydim); + int last_y = LAST_XMIPP_INDEX(tomogram1.stack.ydim); + FOR_ALL_ELEMENTS_IN_ARRAY2D(frame) { - A2D_ELEM(frame1, i, j) = A2D_ELEM(frame1, ip, jp); - A2D_ELEM(frame2, i, j) = A2D_ELEM(frame2, ip, jp); + int jp = j, ip = i; + bool do_change = false; + if (j < first_x) + { + jp = 2 * first_x - j; + do_change = true; + } + else if (j > last_x) + { + jp = 2 * last_x - j; + do_change = true; + } + if (i < first_y) + { + ip = 2 * first_y - i; + do_change = true; + } + else if (i > last_y) + { + ip = 2 * last_y - i; + do_change = true; + } + if (do_change) + { + A2D_ELEM(frame, i, j) = A2D_ELEM(frame, ip, jp); + } } + //std::cerr <<" dealt with edges"<< std::endl; - } + // Downscale + //std::cerr <<" new_box= " << new_box << " square_box= " << square_box << std::endl; + if (new_box != square_box) resizeMap(frame, new_box); - // Downscale - if (new_box != square_box) - { - resizeMap(frame1, new_box); - resizeMap(frame2, new_box); + //Image It; + //It()=frame; + //It.write("rec_frame"+ integerToString(f)+".mrc"); + + // FT and get SNRs + // Not entirely sure this is necessary, but FFTW transformers have been troublesome with threads in the past... + //std::cerr << " before FT" << std::endl; +#pragma omp critical + { + FourierTransformer transformer; + if (ihalf == 0) transformer.FourierTransform(frame, FT1); + else transformer.FourierTransform(frame, FT2); + }; + //std::cerr << " after FT" << std::endl; } // Get the transformation matrix - const Matrix2D A(3,3); - for (int row= 0; row < 3; row++) + const Matrix2D A(3, 3); + for (int row = 0; row < 3; row++) for (int col = 0; col < 3; col++) MAT_ELEM(A, row, col) = tomogram1.projectionMatrices[f](row, col); @@ -445,54 +497,68 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) m.getValueSafely(EMDL_TOMO_XSHIFT_ANGST, xshift, f); m.getValueSafely(EMDL_TOMO_YSHIFT_ANGST, yshift, f); - // FT and get SNRs - MultidimArray FT1, FT2; - // Not entirely sure this is necessary, but FFTW transformers have been troublesome with threads in the past... - #pragma omp critical + MultidimArray FSC, SNR; + if (fourierWienerFilter) { - FourierTransformer transformer; - transformer.FourierTransform(frame1, FT1); - transformer.FourierTransform(frame2, FT2); - }; - MultidimArray FSC; - getFSC(FT1, FT2, FSC); + getFSC(FT1, FT2, FSC); - // Now that we have the FSC, sum the two halves together - FT1 += FT2; - // Center and shift + // Now that we have the FSC, sum the two halves together + FT1 += FT2; + } + // Center and shift + //std::cerr << " before centerFFT" << std::endl; CenterFFTbySign(FT1); - shiftImageInFourierTransform(FT1, FT1, XSIZE(frame1), -xshift/angpix_spacing, -yshift/angpix_spacing); + shiftImageInFourierTransform(FT1, FT1, XSIZE(frame), -xshift/angpix_spacing, -yshift/angpix_spacing); + //std::cerr << " after centerFFT" << std::endl; // Get CTF - MultidimArray Fctf, Ftmp; + MultidimArray Fctf; Fctf.resize(YSIZE(FT1), XSIZE(FT1)); ctf.getFftwImage(Fctf, new_box, new_box, angpix_spacing, false, false, ctf_intact_first_peak, false); + //std::cerr << " after getCTF" << std::endl; - // Calculate the CTF-corrected SNR from the FSC - MultidimArray SNR = getCtfCorrectedSNR(FSC, Fctf, lambda, 0); + //Image Ictf; + //Ictf()=Fctf; + //Ictf.write("rec_Ictf_frame"+ integerToString(f)+".mrc"); - FOR_ALL_ELEMENTS_IN_FFTW_TRANSFORM2D(FT1) + // Calculate the CTF-corrected SNR from the FSC + if (fourierWienerFilter) { - long int idx = XMIPP_MIN(ROUND(sqrt(ip*ip + jp*jp)), XSIZE(SNR)-1); - // Wiener filter = Sum(CTF*SNR*X) / (Sum(CTF^2*SNR) + 1.) - DIRECT_A2D_ELEM(FT1, i, j) *= DIRECT_MULTIDIM_ELEM(SNR, idx) * DIRECT_A2D_ELEM(Fctf, i, j); - DIRECT_A2D_ELEM(Fctf, i, j) *= DIRECT_MULTIDIM_ELEM(SNR, idx) * DIRECT_A2D_ELEM(Fctf, i, j); + // Wiener filter-type correction + SNR = getCtfCorrectedSNR(FSC, Fctf, lambda, 0); + FOR_ALL_ELEMENTS_IN_FFTW_TRANSFORM2D(FT1) + { + long int idx = XMIPP_MIN(ROUND(sqrt(ip*ip + jp*jp)), XSIZE(SNR)-1); + // Wiener filter = Sum(CTF*SNR*X) / (Sum(CTF^2*SNR) + 1.) + DIRECT_A2D_ELEM(FT1, i, j) *= DIRECT_MULTIDIM_ELEM(SNR, idx) * DIRECT_A2D_ELEM(Fctf, i, j); + DIRECT_A2D_ELEM(Fctf, i, j) *= DIRECT_MULTIDIM_ELEM(SNR, idx) * DIRECT_A2D_ELEM(Fctf, i, j); + } + } + else + { + // Just premultiply with the CTF + FOR_ALL_ELEMENTS_IN_FFTW_TRANSFORM2D(FT1) + { + DIRECT_A2D_ELEM(FT1, i, j) *= DIRECT_A2D_ELEM(Fctf, i, j); + DIRECT_A2D_ELEM(Fctf, i, j) = 1.; + } } #pragma omp critical { BP.set2DFourierTransform(FT1, A, &Fctf); }; + //std::cerr << " after set2DFT for frame " << f << std::endl; - } + + } // end for loop over frames Image vol; vol().resize(new_box, new_box, new_box); vol().setXmippOrigin(); - bool do_map = true; MultidimArray tau2(new_box); tau2.initConstant(1.); - BP.reconstruct(vol(), 0, do_map, tau2); + BP.reconstruct(vol(), 0, fourierWienerFilter, tau2); if (!do_multiple) Log::print("Writing output"); vol().window(FIRST_XMIPP_INDEX(d/spacing), FIRST_XMIPP_INDEX(h/spacing), FIRST_XMIPP_INDEX(w/spacing), diff --git a/src/jaz/tomography/programs/reconstruct_tomogram.h b/src/jaz/tomography/programs/reconstruct_tomogram.h index b26a2eeeb..2b3044d36 100644 --- a/src/jaz/tomography/programs/reconstruct_tomogram.h +++ b/src/jaz/tomography/programs/reconstruct_tomogram.h @@ -74,7 +74,7 @@ class TomoBackprojectProgram int w, h, d; double spacing, angpix_spacing, x0, y0, z0, taperDist, taperFalloff; FileName tomoName, outFn; - bool applyPreWeight, applyWeight, applyCtf, doWiener, zeroDC, FourierCrop, fourierInversion; + bool applyPreWeight, applyWeight, applyCtf, doWiener, zeroDC, FourierCrop, fourierInversion, fourierWienerFilter; bool do_multiple, do_only_unfinished; bool do_even_odd_tomograms, do_2dproj, ctf_intact_first_peak; int centre_2dproj, thickness_2dproj; From 487658e10791877f740a79258c3a888ecec4b970 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 6 Dec 2024 15:48:51 +0000 Subject: [PATCH 026/189] option to write out real subtomograms --- src/jaz/tomography/programs/subtomo.cpp | 249 +++++++++++++++--------- src/jaz/tomography/programs/subtomo.h | 6 +- 2 files changed, 165 insertions(+), 90 deletions(-) diff --git a/src/jaz/tomography/programs/subtomo.cpp b/src/jaz/tomography/programs/subtomo.cpp index b4606981e..406b1f9a2 100644 --- a/src/jaz/tomography/programs/subtomo.cpp +++ b/src/jaz/tomography/programs/subtomo.cpp @@ -95,8 +95,7 @@ void SubtomoProgram::readBasicParameters(IOParser& parser) run_from_GUI = is_under_pipeline_control(); - do_reproject_subtomo = parser.checkOption("--reproject_subtomo", "Extract true subtomograms and write out re-projections of those out as 2D stacks"); - do_real_subtomo = parser.checkOption("--real_subtomo", "Extract true subtomograms and write those out as 3D volumes"); + do_real_subtomo = parser.checkOption("--real_subtomo", "Extract true subtomograms and write those out as 3D volumes, together with their 3D CTFs and 2D projections along Z"); } @@ -242,7 +241,7 @@ void SubtomoProgram::initialise( } if (do_real_subtomo) { - Log::print("Extracting true subtomogram volumes, which loose all information about the missing wedge and are therefore suboptimal for averaging, alignment or classification"); + Log::print("Extracting true subtomogram volumes for relion-3-style averaging in 3D; will also write out projections along Z for 2D classification"); } } @@ -287,7 +286,7 @@ void SubtomoProgram::writeParticleSet( copy.clearParticles(); copy.is_stack2d = do_stack2d; - if (do_reproject_subtomo) + if (do_real_subtomo) { copy2d = particleSet; copy2d.clearParticles(); @@ -309,7 +308,8 @@ void SubtomoProgram::writeParticleSet( Tomogram tomogram = tomogramSet.loadTomogram(t, false); - if (do_reproject_subtomo || do_real_subtomo) + std::string outProj; + if (do_real_subtomo) { RFLOAT tomogram_binning; tomogramSet.globalTable.getValue(EMDL_TOMO_TOMOGRAM_BINNING, tomogram_binning, t); @@ -329,6 +329,8 @@ void SubtomoProgram::writeParticleSet( else cropSize_tomogram--; } + outProj = outDir + "Subtomograms/" + tomogram.name + "/proj2d.mrcs"; + } for (int p = 0; p < pc; p++) { @@ -337,8 +339,6 @@ void SubtomoProgram::writeParticleSet( const std::vector traj = particleSet.getTrajectoryInPixels( part_id, tomogram.frameCount, tomogram.centre, tomogram.optics.pixelSize, !apply_offsets); - //SHWS 24jan2024 only for debugging: // int my_min_frames = (min_frames < 0 ) ? tomogram.frameCount / 2 : min_frames; - //SHWS 24jan2024 only for debugging: // if (tomogram.isVisibleFirstFrames(traj, boxSize / 2.0, my_min_frames)) std::vector isVisible; if (tomogram.getVisibilityMinFramesMaxDose(traj, binning * cropSize / 2.0, maxDose, min_frames, isVisible)) { @@ -361,11 +361,16 @@ void SubtomoProgram::writeParticleSet( const std::string filenameRoot = getOutputFilename( part_id, t, particleSet, tomogramSet); + std::string outCtf; std::string outData = (do_stack2d) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; - if (do_real_subtomo) outData = filenameRoot + "_subtomo.mrc"; std::string outWeight = (do_stack2d) ? "" : filenameRoot + "_weights.mrc"; - copy.setImageFileNames(outData, outWeight, new_id); + if (do_real_subtomo) + { + outData = filenameRoot + "_subtomo.mrc"; + outCtf = filenameRoot + "_ctf.mrc"; + copy.setImageFileNames(outData, outCtf, new_id); + } if (apply_offsets) { @@ -388,14 +393,11 @@ void SubtomoProgram::writeParticleSet( copy.partTable.setValue(EMDL_ORIENT_PSI, 0.0, new_id.value); } - if (do_reproject_subtomo) + if (do_real_subtomo) { // Also make a particle star file for 2D classification - mintilt_idx = tomogramSet.getImageIndexWithSmallestVisibleTiltAngle(t, isVisible); - copy2d.partTable.addObject( copy.partTable.getObject(new_id.value) ); - - std::string outData2d = integerToString(mintilt_idx+1) + "@" + outData; + std::string outData2d = integerToString(p+1) + "@" + outProj; copy2d.partTable.setValue(EMDL_IMAGE_NAME, outData2d); } } @@ -419,21 +421,16 @@ void SubtomoProgram::writeParticleSet( { bool is_premultiplied = (do_stack2d) ? do_ctf : true; - if (do_reproject_subtomo || do_real_subtomo) - { - is_premultiplied = false; - copy.optTable.setValue(EMDL_OPTIMISER_DATA_ARE_CTF_CORRECTED, true, og); - } copy.optTable.setValue(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED, is_premultiplied, og); - int datadim = (do_stack2d || do_reproject_subtomo) ? 2 : 3; + int datadim = (do_stack2d) ? 2 : 3; copy.optTable.setValue(EMDL_IMAGE_DIMENSIONALITY, datadim, og); - RFLOAT mybinning = (do_reproject_subtomo || do_real_subtomo) ? real_subtomo_binning : binning; + RFLOAT mybinning = (do_real_subtomo) ? real_subtomo_binning : binning; copy.optTable.setValue(EMDL_TOMO_SUBTOMOGRAM_BINNING, mybinning, og); const double ps_img = copy.optTable.getDouble(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, og); const double ps_out = mybinning * ps_img; copy.optTable.setValue(EMDL_IMAGE_PIXEL_SIZE, ps_out, og); - int mysize = (do_reproject_subtomo || do_real_subtomo) ? cropSize_tomogram : cropSize; + int mysize = (do_real_subtomo) ? cropSize_tomogram : cropSize; copy.optTable.setValue(EMDL_IMAGE_SIZE, mysize, og); @@ -447,7 +444,7 @@ void SubtomoProgram::writeParticleSet( copy.write(outDir + "particles.star"); - if (do_reproject_subtomo) + if (do_real_subtomo) { if (copy2d.partTable.containsLabel(EMDL_IMAGE_COORD_X)) copy2d.partTable.deactivateLabel(EMDL_IMAGE_COORD_X); @@ -471,10 +468,10 @@ void SubtomoProgram::writeParticleSet( optimisationSet.write(outDir + "optimisation_set.star"); } -BufferedImage SubtomoProgram::extractSubtomogramsAndReProject( +BufferedImage SubtomoProgram::extractRealSubtomograms( ParticleIndex part_id, MultidimArray &recTomo, const Tomogram& tomogram, const ParticleSet &particleSet, - const std::vector &isVisible, RFLOAT tomogram_angpix, bool do_reproject) + RFLOAT tomogram_angpix, BufferedImage &ctfImage, BufferedImage &projImage) { // get decentered coordinates of the particle (in tilt series pixels) @@ -559,55 +556,106 @@ BufferedImage SubtomoProgram::extractSubtomogramsAndReProject( //Image It; //It()= subtom; - //It.write("subtom.spi"); + //It.write("subtom.mrc"); //exit(0); BufferedImage resultImg; - if (do_reproject) + resultImg.resize(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); + ctfImage.resize(cropSize_tomogram / 2 + 1, cropSize_tomogram, cropSize_tomogram); + projImage.resize(cropSize_tomogram, cropSize_tomogram); + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY3D(subtom) { + resultImg(j, i, k) = DIRECT_A3D_ELEM(subtom, k, i, j); + projImage(i, j) += DIRECT_A3D_ELEM(subtom, k, i, j); + } - MultidimArray dummy; - Projector projector(cropSize_tomogram); - projector.computeFourierTransformMap(subtom, dummy); - - MultidimArray img(cropSize_tomogram, cropSize_tomogram); - MultidimArray F2D; - FourierTransformer transformer; - transformer.setReal(img); - transformer.getFourierAlias(F2D); + BackProjector backprojector(cropSize_tomogram, 3, "C1", TRILINEAR, + 1, 10, 0, 1.9, 15., 2, true); - resultImg.resize(cropSize_tomogram, cropSize_tomogram, tomogram.frameCount); - for (int f = 0; f < tomogram.frameCount; f++) - { + backprojector.initZeros(); - if (!isVisible[f]) continue; + MultidimArray Fw(cropSize_tomogram, cropSize_tomogram/2 + 1), Fctf(cropSize_tomogram, cropSize_tomogram/2 + 1); + MultidimArray F2D; + F2D.initZeros(cropSize_tomogram, cropSize_tomogram/2 + 1); + Fw.initConstant(1.); - d4Matrix Aproj = tomogram.projectionMatrices[f]; - Matrix2D A(3,3); - for (int row= 0; row < 3; row++) - for (int col = 0; col < 3; col++) - MAT_ELEM(A, row, col) = Aproj(row, col); - - // Get the 2D slice out of the 3D Fourier transform - F2D.initZeros(); - projector.get2DFourierTransform(F2D, A); - shiftImageInFourierTransform(F2D, F2D, cropSize_tomogram, cropSize_tomogram/2, cropSize_tomogram/2); - transformer.inverseFourierTransform(); + for (int f = 0; f < tomogram.frameCount; f++) + { - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(img) - { - resultImg(j, i, f) = DIRECT_A2D_ELEM(img, i, j); - } + d4Matrix Aproj = tomogram.projectionMatrices[f]; + Matrix2D A(3,3); + for (int row= 0; row < 3; row++) + for (int col = 0; col < 3; col++) + MAT_ELEM(A, row, col) = Aproj(row, col); + + // Get the CTF, as per Tanmay's paper on subtomogram averaging in relion-3 + RFLOAT xtilt, ytilt, zrot, xshift_angst, yshift_angst; + tomogram.getProjectionAnglesFromMatrix(f, Aproj, xtilt, ytilt, zrot, xshift_angst, yshift_angst); + + CTF ctf = tomogram.getCtf(f, pos); + ctf.Bfac = 4.0 * tomogram.getCumulativeDose(f); + ctf.scale = COSD(ytilt); + ctf.initialise(); + ctf.getFftwImage(Fctf, cropSize_tomogram, cropSize_tomogram, tomogram_angpix, + false, false, false, true); + + /* + std::cerr << " A= " << A << " ytilt= " << ytilt << " ctf.scale= " << ctf.scale << " tomogram_angpix= " << tomogram_angpix << std::endl; + std::cerr << "ctf.defocusU= " << ctf.DeltafU << " ctf.Bfac= " << ctf.Bfac << " pos= " << pos.x <<" "<< pos.y << " f= " << f << std::endl; + Image It; + It()= Fctf; + It.write("Fctf.spi"); + std::cerr << " pres any key to continue..."<< std::endl; + char c; + std::cin >> c; +*/ + + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Fctf) + { + DIRECT_MULTIDIM_ELEM(F2D, n) = DIRECT_MULTIDIM_ELEM(Fctf, n) * DIRECT_MULTIDIM_ELEM(Fctf, n); } + backprojector.set2DFourierTransform(F2D, A, &Fw); + } - else + + // Do division in 3D + MultidimArray CTF3D; + CTF3D.initZeros(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); + CTF3D.setXmippOrigin(); + FOR_ALL_ELEMENTS_IN_ARRAY3D(CTF3D) { - resultImg.resize(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY3D(subtom) + int jp = j; + int ip = i; + int kp = k; + + // for negative j's: use inverse + if (j < 0) { - resultImg(j, i, k) = DIRECT_A3D_ELEM(subtom, k, i, j); + jp = -j; + ip = -i; + kp = -k; + } + + if (jp >= STARTINGX(backprojector.data) && jp <= FINISHINGX(backprojector.data) && + ip >= STARTINGY(backprojector.data) && ip <= FINISHINGY(backprojector.data) && + kp >= STARTINGZ(backprojector.data) && kp <= FINISHINGZ(backprojector.data)) + { + if (A3D_ELEM(backprojector.weight, kp, ip, jp) > 0.) + { + A3D_ELEM(CTF3D, k, i, j) = sqrt(A3D_ELEM(backprojector.data, kp, ip, jp) / A3D_ELEM(backprojector.weight, kp, ip, jp)); + } } } + + // FOR_ALL_ELEMENTS_IN_FFTW_TRANSFORM + for (long int k = 0, kp = 0; k recTomo; RFLOAT tomogram_angpix, tomogram_binning; - if (do_reproject_subtomo || do_real_subtomo) + if (do_real_subtomo) { FileName fn_tomo, fn_tomo2=""; if (tomogramSet.globalTable.containsLabel(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_FILE_NAME)) @@ -718,6 +769,20 @@ void SubtomoProgram::processTomograms( omp_lock_t writelock; if (do_sum_all) omp_init_lock(&writelock); + BufferedImage proj2d; + std::string outProj; + if (do_real_subtomo) + { + int cropSize_tomogram = ROUND(cropSize * binning * tomogram.optics.pixelSize / tomogram_angpix); + if (cropSize_tomogram%2 != 0) + { + if ( (cropSize * binning * tomogram.optics.pixelSize / tomogram_angpix) - cropSize_tomogram >= 0. ) cropSize_tomogram++; + else cropSize_tomogram--; + } + proj2d.resize(cropSize_tomogram, cropSize_tomogram, pc); + outProj = outDir + "Subtomograms/" + tomogram.name + "/proj2d.mrcs"; + } + #pragma omp parallel for num_threads(outer_thread_num) for (int p = 0; p < pc; p++) { const int th = omp_get_thread_num(); @@ -731,8 +796,13 @@ void SubtomoProgram::processTomograms( const std::string filenameRoot = getOutputFilename( part_id, t, particleSet, tomogramSet); + std::string outCtf; std::string outData = (do_stack2d) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; - if (do_real_subtomo) outData = filenameRoot + "_subtomo.mrc"; + if (do_real_subtomo) + { + outData = filenameRoot + "_subtomo.mrc"; + outCtf = filenameRoot + "_ctf.mrc"; + } std::string outWeight = (do_stack2d) ? "" : filenameRoot + "_weights.mrc"; std::string outCTF = filenameRoot + "_CTF2.mrc"; std::string outDiv = filenameRoot + "_div.mrc"; @@ -756,22 +826,15 @@ void SubtomoProgram::processTomograms( BufferedImage particleStack = BufferedImage(sh2D, s2D, fc); BufferedImage weightStack(sh2D, s2D, fc); - if (do_reproject_subtomo) - { - - // This will extract the true subtomograms and calculate their FTs in the directions of the tilt series - BufferedImage subtomo_reprojs = extractSubtomogramsAndReProject(part_id, recTomo(), - tomogram, particleSet, isVisible, tomogram_angpix, true); - BufferedImage visible_reprojs = NewStackHelper::getVisibleSlices(subtomo_reprojs, isVisible); - visible_reprojs.write(outData, tomogram_angpix, write_float16); - - } - else if (do_real_subtomo) + if (do_real_subtomo) { // This will extract the true subtomograms and calculate their FTs in the directions of the tilt series - BufferedImage subtomo = extractSubtomogramsAndReProject(part_id, recTomo(), - tomogram, particleSet, isVisible, tomogram_angpix, false); + BufferedImage ctfImage, projImage; + BufferedImage subtomo = extractRealSubtomograms(part_id, recTomo(), + tomogram, particleSet, tomogram_angpix, ctfImage, projImage); subtomo.write(outData, tomogram_angpix, write_float16); + ctfImage.write(outCtf, tomogram_angpix, write_float16); + NewStackHelper::insertSliceZ(projImage, proj2d, p); } else { @@ -794,7 +857,8 @@ void SubtomoProgram::processTomograms( d3Matrix A; - if (apply_orientations) { + if (apply_orientations) + { A = particleSet.getMatrix3x3(part_id); } else { A = particleSet.getSubtomogramMatrix(part_id); @@ -802,16 +866,18 @@ void SubtomoProgram::processTomograms( projPart[f] = projCut[f] * d4Matrix(A); - if (do_ctf) { + if (do_ctf) + { const d3Vector pos = particleSet.getPosition(part_id, tomogram.centre, apply_offsets); - CTF ctf = tomogram.getCtf(f, pos); BufferedImage ctfImg(sh2D, s2D); ctf.draw(s2D, s2D, binnedPixelSize, gammaOffset, &ctfImg(0, 0, 0)); // Apply doseWeigths until Nyquist frequency! Otherwise, convolution artefacts when do_circle_crop invFFT/FFT below - for (int y = 0; y < s2D; y++) { - for (int x = 0; x < sh2D; x++) { + for (int y = 0; y < s2D; y++) + { + for (int x = 0; x < sh2D; x++) + { const double c = ctfImg(x, y) * doseWeights(x, y, f); particleStack(x, y, f) *= sign * c; @@ -826,7 +892,8 @@ void SubtomoProgram::processTomograms( aberrationsCache.correctObservations(particleStack, og); - if (do_whiten) { + if (do_whiten) + { particleStack *= noiseWeights; weightStack *= noiseWeights; } @@ -837,28 +904,34 @@ void SubtomoProgram::processTomograms( const int boundary = (boxSize - cropSize) / 2; BufferedImage particlesRS; - if (do_gridding_precorrection || do_circle_crop || do_stack2d) { + if (do_gridding_precorrection || do_circle_crop || do_stack2d) + { particlesRS = NewStackHelper::inverseFourierTransformStack(particleStack); } - if (do_circle_crop) { + if (do_circle_crop) + { const double crop_boundary = do_narrow_circle_crop ? boundary : 0.0; TomoExtraction::cropCircle(particlesRS, crop_boundary, 5, num_threads); } - if (do_gridding_precorrection) { + if (do_gridding_precorrection) + { TomoExtraction::griddingPreCorrect(particlesRS, boundary, num_threads); } - if (do_stack2d) { + if (do_stack2d) + { BufferedImage cropParticlesRS = Padding::unpadCenter2D_full(particlesRS, boundary); BufferedImage cropParticlesRS2 = NewStackHelper::getVisibleSlices(cropParticlesRS, isVisible); cropParticlesRS2.write(outData, binnedPixelSize, write_float16); - } else { + } + else + { if (do_gridding_precorrection || do_circle_crop) { @@ -999,7 +1072,9 @@ void SubtomoProgram::processTomograms( } // end if do_real_subtomo } // end loop particles p - if (verbosity > 0) + if (do_real_subtomo) proj2d.write(outProj, tomogram_angpix, write_float16); + + if (verbosity > 0) { Log::endProgress(); Log::endSection(); // tomogram diff --git a/src/jaz/tomography/programs/subtomo.h b/src/jaz/tomography/programs/subtomo.h index 8c3d48cf4..f718921a9 100644 --- a/src/jaz/tomography/programs/subtomo.h +++ b/src/jaz/tomography/programs/subtomo.h @@ -7,7 +7,7 @@ #include #include #include -#include +#include @@ -86,10 +86,10 @@ class SubtomoProgram const TomogramSet& tomogramSet, bool verbose = true); - BufferedImage extractSubtomogramsAndReProject( + BufferedImage extractRealSubtomograms( ParticleIndex part_id, MultidimArray &recTomo, const Tomogram& tomogram, const ParticleSet &particleSet, - const std::vector &isVisible, RFLOAT tomogram_angpix, bool do_reproject); + RFLOAT tomogram_angpix, BufferedImage &ctfImage, BufferedImage &projImage); void processTomograms( const std::vector& tomoIndices, From 05aba2223609b5f610f26176533bf94fd1907583 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 6 Dec 2024 15:49:48 +0000 Subject: [PATCH 027/189] removed unnecessary check --- src/jaz/tomography/programs/subtomo.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/jaz/tomography/programs/subtomo.cpp b/src/jaz/tomography/programs/subtomo.cpp index 406b1f9a2..820ee7d7d 100644 --- a/src/jaz/tomography/programs/subtomo.cpp +++ b/src/jaz/tomography/programs/subtomo.cpp @@ -705,9 +705,6 @@ void SubtomoProgram::processTomograms( } Tomogram tomogram = tomogramSet.loadTomogram(t, true); - if (tomogram.centre.x == 0 && tomogram.centre.y == 0 && tomogram.centre.z == 0) - REPORT_ERROR("ERROR: centre for tomogram " + integerToString(t+1) + " has not been set!"); - tomogram.validateParticleOptics(particles[t], particleSet); // If using the real_subtomo approach, then need to read in the reconstructed tomogram volume From 39388ab8753e17bffcfd5bff17202a65937c2327 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 10 Dec 2024 17:32:59 +0000 Subject: [PATCH 028/189] added option to reproject subtomos back on the GUI, also option to skp-Wiener for reconstruct tomograms in fourier mode --- src/gui_jobwindow.cpp | 1 + src/pipeline_jobs.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 3c2efd528..fa9e2ca85 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -2758,6 +2758,7 @@ void JobWindow::initialiseTomoReconstructTomogramsWindow() place("do_fourier", TOGGLE_DEACTIVATE, group2, false); group2->begin(); + place("do_skio_wiener", TOGGLE_DEACTIVATE); place("ctf_intact_first_peak", TOGGLE_DEACTIVATE); group2->end(); guientries["do_fourier"].cb_menu_i(); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index bf01e5c4a..fdc2ab24f 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -6729,7 +6729,8 @@ void RelionJob::initialiseTomoReconstructTomogramsJob() joboptions["ydim"] = JobOption("Unbinned tomogram height (Ydim): ", 4000, 1, 6000, 100, "The tomogram Y-dimension in unbinned pixels."); joboptions["zdim"] = JobOption("Unbinned tomogram thickness (Zdim): ", 2000, 1, 6000, 100, "The tomogram Z-dimension in unbinned pixels."); - joboptions["do_fourier"] = JobOption("Fourier-inversion with odd/even frames?", true, "When set to Yes, a Wiener-filtered reconstruction will be calculated by Fourier inversion. The SNRs of all frames will be measured from the odd/even frames, which should have thus been calculated"); + joboptions["do_fourier"] = JobOption("Use Fourier-inversion?", true, "When set to Yes, instead of using real-space backprojection, a Fourier-space inversion algorithm is used."); + joboptions["do_skip_wiener"] = JobOption("Skip Wiener filter?", false, "By default, the Fourier-inversion method uses a Wiener-filter, where the signal-to-noise ratios are calculated from Fourier shell correlations between the odd/even frames, which should have thus been calculated during MotionCorrection. If this option is true, then the odd/even frames are not necessary, and the images will only be pre-calculated with the CTF, without any division by CTF^2 as in the Wiener filter. This is the option that should be used for subsequent (real) sub-tomogram averaging"); joboptions["ctf_intact_first_peak"] =JobOption("Ignore CTFs until first peak?", true, "When set to Yes, the lowest spatial frequencies will not be boosted through CTF-correction, which will lead to a reconstruction with less low-resolution contrast, but better high-resolution details>"); joboptions["do_proj"] = JobOption("Also write 2D sums of central Z-slices?:", true, "When set to Yes, this option will result in the calculation of 2D sums of Z-slices from the reconstructed tomograms. These may be useful to quickly screen for bad tomograms using the relion_display program."); @@ -6791,9 +6792,14 @@ bool RelionJob::getCommandsTomoReconstructTomogramsJob(std::string &outputname, { command += " --ctf_intact_first_peak "; } + if (joboptions["do_skip_wiener"].getBoolean()) + { + command += " --skip_fourier_wiener "; + } + } - if (joboptions["do_proj"].getBoolean()) + if (joboptions["do_proj"].getBoolean()) { command += " --do_proj "; command += " --centre_proj " + joboptions["centre_proj"].getString(); From 429964a71cfda8154925175f36eb94101888205e Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 10 Dec 2024 17:33:21 +0000 Subject: [PATCH 029/189] added option back to reproject real subtomos --- src/jaz/tomography/programs/subtomo.cpp | 217 +++++++++++++++--------- 1 file changed, 134 insertions(+), 83 deletions(-) diff --git a/src/jaz/tomography/programs/subtomo.cpp b/src/jaz/tomography/programs/subtomo.cpp index 820ee7d7d..446fab9e9 100644 --- a/src/jaz/tomography/programs/subtomo.cpp +++ b/src/jaz/tomography/programs/subtomo.cpp @@ -96,6 +96,7 @@ void SubtomoProgram::readBasicParameters(IOParser& parser) run_from_GUI = is_under_pipeline_control(); do_real_subtomo = parser.checkOption("--real_subtomo", "Extract true subtomograms and write those out as 3D volumes, together with their 3D CTFs and 2D projections along Z"); + do_reproject_subtomo = parser.checkOption("--reproject_subtomo", "Extract true subtomograms, but then write out re-projections of those as 2D stacks"); } @@ -284,9 +285,9 @@ void SubtomoProgram::writeParticleSet( ParticleSet copy2d, copy = particleSet; copy.clearParticles(); - copy.is_stack2d = do_stack2d; + copy.is_stack2d = (do_stack2d || do_reproject_subtomo); - if (do_real_subtomo) + if (do_real_subtomo || do_reproject_subtomo) { copy2d = particleSet; copy2d.clearParticles(); @@ -309,7 +310,7 @@ void SubtomoProgram::writeParticleSet( Tomogram tomogram = tomogramSet.loadTomogram(t, false); std::string outProj; - if (do_real_subtomo) + if (do_real_subtomo || do_reproject_subtomo) { RFLOAT tomogram_binning; tomogramSet.globalTable.getValue(EMDL_TOMO_TOMOGRAM_BINNING, tomogram_binning, t); @@ -347,7 +348,7 @@ void SubtomoProgram::writeParticleSet( const ParticleIndex new_id = copy.addParticle(particleSet, part_id); // Also set isVisible in the output particle STAR file - if (do_stack2d && !do_real_subtomo) + if (do_stack2d || do_reproject_subtomo) { std::vector isVisibleInt(isVisible.size(), 0); for (int f = 0; f < tomogram.frameCount; f++) @@ -362,8 +363,8 @@ void SubtomoProgram::writeParticleSet( part_id, t, particleSet, tomogramSet); std::string outCtf; - std::string outData = (do_stack2d) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; - std::string outWeight = (do_stack2d) ? "" : filenameRoot + "_weights.mrc"; + std::string outData = (do_stack2d || do_reproject_subtomo) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; + std::string outWeight = (do_stack2d || do_reproject_subtomo) ? "" : filenameRoot + "_weights.mrc"; copy.setImageFileNames(outData, outWeight, new_id); if (do_real_subtomo) { @@ -393,7 +394,7 @@ void SubtomoProgram::writeParticleSet( copy.partTable.setValue(EMDL_ORIENT_PSI, 0.0, new_id.value); } - if (do_real_subtomo) + if (do_real_subtomo || do_reproject_subtomo) { // Also make a particle star file for 2D classification copy2d.partTable.addObject( copy.partTable.getObject(new_id.value) ); @@ -420,9 +421,9 @@ void SubtomoProgram::writeParticleSet( for (int og = 0; og < copy.numberOfOpticsGroups(); og++) { - bool is_premultiplied = (do_stack2d) ? do_ctf : true; + bool is_premultiplied = (do_stack2d || do_reproject_subtomo) ? do_ctf : true; copy.optTable.setValue(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED, is_premultiplied, og); - int datadim = (do_stack2d) ? 2 : 3; + int datadim = (do_stack2d || do_reproject_subtomo) ? 2 : 3; copy.optTable.setValue(EMDL_IMAGE_DIMENSIONALITY, datadim, og); RFLOAT mybinning = (do_real_subtomo) ? real_subtomo_binning : binning; @@ -440,11 +441,11 @@ void SubtomoProgram::writeParticleSet( if (copy.partTable.containsLabel(EMDL_IMAGE_COORD_Y)) copy.partTable.deactivateLabel(EMDL_IMAGE_COORD_Y); if (copy.partTable.containsLabel(EMDL_IMAGE_COORD_Z)) copy.partTable.deactivateLabel(EMDL_IMAGE_COORD_Z); // remove CTF image name that could be there from old pseudo-subtomo jobs if we're writing 2D stacks - if (do_stack2d && copy.partTable.containsLabel(EMDL_CTF_IMAGE)) copy.partTable.deactivateLabel(EMDL_CTF_IMAGE); + if ((do_stack2d || do_reproject_subtomo) && copy.partTable.containsLabel(EMDL_CTF_IMAGE)) copy.partTable.deactivateLabel(EMDL_CTF_IMAGE); copy.write(outDir + "particles.star"); - if (do_real_subtomo) + if (do_real_subtomo || do_reproject_subtomo) { if (copy2d.partTable.containsLabel(EMDL_IMAGE_COORD_X)) copy2d.partTable.deactivateLabel(EMDL_IMAGE_COORD_X); @@ -559,47 +560,90 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( //It.write("subtom.mrc"); //exit(0); - BufferedImage resultImg; - resultImg.resize(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); - ctfImage.resize(cropSize_tomogram / 2 + 1, cropSize_tomogram, cropSize_tomogram); + // Make 2D projection along Z-axis projImage.resize(cropSize_tomogram, cropSize_tomogram); FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY3D(subtom) { - resultImg(j, i, k) = DIRECT_A3D_ELEM(subtom, k, i, j); projImage(i, j) += DIRECT_A3D_ELEM(subtom, k, i, j); } - BackProjector backprojector(cropSize_tomogram, 3, "C1", TRILINEAR, - 1, 10, 0, 1.9, 15., 2, true); - backprojector.initZeros(); + BufferedImage resultImg; + if (do_reproject_subtomo) + { - MultidimArray Fw(cropSize_tomogram, cropSize_tomogram/2 + 1), Fctf(cropSize_tomogram, cropSize_tomogram/2 + 1); - MultidimArray F2D; - F2D.initZeros(cropSize_tomogram, cropSize_tomogram/2 + 1); - Fw.initConstant(1.); + MultidimArray dummy; + Projector projector(cropSize_tomogram); + projector.computeFourierTransformMap(subtom, dummy); + + MultidimArray img(cropSize_tomogram, cropSize_tomogram); + MultidimArray F2D; + FourierTransformer transformer; + transformer.setReal(img); + transformer.getFourierAlias(F2D); + resultImg.resize(cropSize_tomogram, cropSize_tomogram, tomogram.frameCount); + for (int f = 0; f < tomogram.frameCount; f++) + { + d4Matrix Aproj = tomogram.projectionMatrices[f]; + Matrix2D A(3,3); + for (int row= 0; row < 3; row++) + for (int col = 0; col < 3; col++) + MAT_ELEM(A, row, col) = Aproj(row, col); + + // Get the 2D slice out of the 3D Fourier transform + F2D.initZeros(); + projector.get2DFourierTransform(F2D, A); + //shiftImageInFourierTransform(F2D, F2D, cropSize_tomogram, cropSize_tomogram/2, cropSize_tomogram/2); + transformer.inverseFourierTransform(); + + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(img) + { + resultImg(j, i, f) = DIRECT_A2D_ELEM(img, i, j); + } + } - for (int f = 0; f < tomogram.frameCount; f++) + } + else if (do_real_subtomo)// end if do_reproject_subtomo { - d4Matrix Aproj = tomogram.projectionMatrices[f]; - Matrix2D A(3,3); - for (int row= 0; row < 3; row++) - for (int col = 0; col < 3; col++) - MAT_ELEM(A, row, col) = Aproj(row, col); + resultImg.resize(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); + ctfImage.resize(cropSize_tomogram / 2 + 1, cropSize_tomogram, cropSize_tomogram); + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY3D(subtom) + { + resultImg(j, i, k) = DIRECT_A3D_ELEM(subtom, k, i, j); + } + + BackProjector backprojector(cropSize_tomogram, 3, "C1", TRILINEAR, + 1, 10, 0, 1.9, 15., 2, true); - // Get the CTF, as per Tanmay's paper on subtomogram averaging in relion-3 - RFLOAT xtilt, ytilt, zrot, xshift_angst, yshift_angst; - tomogram.getProjectionAnglesFromMatrix(f, Aproj, xtilt, ytilt, zrot, xshift_angst, yshift_angst); + backprojector.initZeros(); - CTF ctf = tomogram.getCtf(f, pos); - ctf.Bfac = 4.0 * tomogram.getCumulativeDose(f); - ctf.scale = COSD(ytilt); - ctf.initialise(); - ctf.getFftwImage(Fctf, cropSize_tomogram, cropSize_tomogram, tomogram_angpix, - false, false, false, true); + MultidimArray Fw(cropSize_tomogram, cropSize_tomogram/2 + 1), Fctf(cropSize_tomogram, cropSize_tomogram/2 + 1); + MultidimArray F2D; + F2D.initZeros(cropSize_tomogram, cropSize_tomogram/2 + 1); + Fw.initConstant(1.); - /* + for (int f = 0; f < tomogram.frameCount; f++) + { + + d4Matrix Aproj = tomogram.projectionMatrices[f]; + Matrix2D A(3,3); + for (int row= 0; row < 3; row++) + for (int col = 0; col < 3; col++) + MAT_ELEM(A, row, col) = Aproj(row, col); + + // Get the CTF, as per Tanmay's paper on subtomogram averaging in relion-3 + RFLOAT xtilt, ytilt, zrot, xshift_angst, yshift_angst; + tomogram.getProjectionAnglesFromMatrix(f, Aproj, xtilt, ytilt, zrot, xshift_angst, yshift_angst); + + CTF ctf = tomogram.getCtf(f, pos); + ctf.Bfac = 4.0 * tomogram.getCumulativeDose(f); + ctf.scale = COSD(ytilt); + ctf.initialise(); + ctf.getFftwImage(Fctf, cropSize_tomogram, cropSize_tomogram, tomogram_angpix, + false, false, false, true); + + /* std::cerr << " A= " << A << " ytilt= " << ytilt << " ctf.scale= " << ctf.scale << " tomogram_angpix= " << tomogram_angpix << std::endl; std::cerr << "ctf.defocusU= " << ctf.DeltafU << " ctf.Bfac= " << ctf.Bfac << " pos= " << pos.x <<" "<< pos.y << " f= " << f << std::endl; Image It; @@ -610,51 +654,57 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( std::cin >> c; */ - FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Fctf) - { - DIRECT_MULTIDIM_ELEM(F2D, n) = DIRECT_MULTIDIM_ELEM(Fctf, n) * DIRECT_MULTIDIM_ELEM(Fctf, n); + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Fctf) + { + DIRECT_MULTIDIM_ELEM(F2D, n) = DIRECT_MULTIDIM_ELEM(Fctf, n) * DIRECT_MULTIDIM_ELEM(Fctf, n); + } + backprojector.set2DFourierTransform(F2D, A, &Fw); + } - backprojector.set2DFourierTransform(F2D, A, &Fw); - } + // Do division in 3D + MultidimArray CTF3D; + CTF3D.initZeros(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); + CTF3D.setXmippOrigin(); + FOR_ALL_ELEMENTS_IN_ARRAY3D(CTF3D) + { + int jp = j; + int ip = i; + int kp = k; - // Do division in 3D - MultidimArray CTF3D; - CTF3D.initZeros(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); - CTF3D.setXmippOrigin(); - FOR_ALL_ELEMENTS_IN_ARRAY3D(CTF3D) - { - int jp = j; - int ip = i; - int kp = k; + // for negative j's: use inverse + if (j < 0) + { + jp = -j; + ip = -i; + kp = -k; + } - // for negative j's: use inverse - if (j < 0) - { - jp = -j; - ip = -i; - kp = -k; - } + if (jp >= STARTINGX(backprojector.data) && jp <= FINISHINGX(backprojector.data) && + ip >= STARTINGY(backprojector.data) && ip <= FINISHINGY(backprojector.data) && + kp >= STARTINGZ(backprojector.data) && kp <= FINISHINGZ(backprojector.data)) + { + if (A3D_ELEM(backprojector.weight, kp, ip, jp) > 0.) + { + A3D_ELEM(CTF3D, k, i, j) = sqrt(A3D_ELEM(backprojector.data, kp, ip, jp) / A3D_ELEM(backprojector.weight, kp, ip, jp)); + } + } + } - if (jp >= STARTINGX(backprojector.data) && jp <= FINISHINGX(backprojector.data) && - ip >= STARTINGY(backprojector.data) && ip <= FINISHINGY(backprojector.data) && - kp >= STARTINGZ(backprojector.data) && kp <= FINISHINGZ(backprojector.data)) - { - if (A3D_ELEM(backprojector.weight, kp, ip, jp) > 0.) - { - A3D_ELEM(CTF3D, k, i, j) = sqrt(A3D_ELEM(backprojector.data, kp, ip, jp) / A3D_ELEM(backprojector.weight, kp, ip, jp)); - } - } - } + // FOR_ALL_ELEMENTS_IN_FFTW_TRANSFORM + for (long int k = 0, kp = 0; k recTomo; RFLOAT tomogram_angpix, tomogram_binning; - if (do_real_subtomo) + if (do_real_subtomo || do_reproject_subtomo) { FileName fn_tomo, fn_tomo2=""; if (tomogramSet.globalTable.containsLabel(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_FILE_NAME)) @@ -768,7 +818,7 @@ void SubtomoProgram::processTomograms( BufferedImage proj2d; std::string outProj; - if (do_real_subtomo) + if (do_real_subtomo || do_reproject_subtomo) { int cropSize_tomogram = ROUND(cropSize * binning * tomogram.optics.pixelSize / tomogram_angpix); if (cropSize_tomogram%2 != 0) @@ -794,13 +844,13 @@ void SubtomoProgram::processTomograms( part_id, t, particleSet, tomogramSet); std::string outCtf; - std::string outData = (do_stack2d) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; + std::string outData = (do_stack2d || do_reproject_subtomo) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; if (do_real_subtomo) { outData = filenameRoot + "_subtomo.mrc"; outCtf = filenameRoot + "_ctf.mrc"; } - std::string outWeight = (do_stack2d) ? "" : filenameRoot + "_weights.mrc"; + std::string outWeight = (do_stack2d || do_reproject_subtomo) ? "" : filenameRoot + "_weights.mrc"; std::string outCTF = filenameRoot + "_CTF2.mrc"; std::string outDiv = filenameRoot + "_div.mrc"; std::string outMulti = filenameRoot + "_multi.mrc"; @@ -823,14 +873,15 @@ void SubtomoProgram::processTomograms( BufferedImage particleStack = BufferedImage(sh2D, s2D, fc); BufferedImage weightStack(sh2D, s2D, fc); - if (do_real_subtomo) + if (do_real_subtomo || do_reproject_subtomo) { // This will extract the true subtomograms and calculate their FTs in the directions of the tilt series BufferedImage ctfImage, projImage; BufferedImage subtomo = extractRealSubtomograms(part_id, recTomo(), tomogram, particleSet, tomogram_angpix, ctfImage, projImage); + subtomo.write(outData, tomogram_angpix, write_float16); - ctfImage.write(outCtf, tomogram_angpix, write_float16); + if (do_real_subtomo) ctfImage.write(outCtf, tomogram_angpix, write_float16); NewStackHelper::insertSliceZ(projImage, proj2d, p); } else From 93f8a658dd659183d3c0aedb932aba9d6aaae499 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 10 Dec 2024 17:39:19 +0000 Subject: [PATCH 030/189] two small bugs --- src/gui_jobwindow.cpp | 2 +- src/jaz/tomography/programs/subtomo.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index fa9e2ca85..75f9b9bb9 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -2758,7 +2758,7 @@ void JobWindow::initialiseTomoReconstructTomogramsWindow() place("do_fourier", TOGGLE_DEACTIVATE, group2, false); group2->begin(); - place("do_skio_wiener", TOGGLE_DEACTIVATE); + place("do_skip_wiener", TOGGLE_DEACTIVATE); place("ctf_intact_first_peak", TOGGLE_DEACTIVATE); group2->end(); guientries["do_fourier"].cb_menu_i(); diff --git a/src/jaz/tomography/programs/subtomo.cpp b/src/jaz/tomography/programs/subtomo.cpp index 446fab9e9..6e93f9237 100644 --- a/src/jaz/tomography/programs/subtomo.cpp +++ b/src/jaz/tomography/programs/subtomo.cpp @@ -593,7 +593,7 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( // Get the 2D slice out of the 3D Fourier transform F2D.initZeros(); projector.get2DFourierTransform(F2D, A); - //shiftImageInFourierTransform(F2D, F2D, cropSize_tomogram, cropSize_tomogram/2, cropSize_tomogram/2); + shiftImageInFourierTransform(F2D, F2D, cropSize_tomogram, cropSize_tomogram/2, cropSize_tomogram/2); transformer.inverseFourierTransform(); FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(img) From 9a71c66f63bc9ee4c9f056275c9d16688de349b7 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 11 Dec 2024 08:39:52 +0000 Subject: [PATCH 031/189] rearrange subtomo --- src/gui_jobwindow.cpp | 8 ++++---- src/pipeline_jobs.cpp | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 75f9b9bb9..6eba46d36 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -2869,7 +2869,7 @@ void JobWindow::initialiseTomoSubtomoWindow() tab1->end(); tab2->begin(); - tab2->label("Reconstruct"); + tab2->label("Extract"); resetHeight(); place("binning", TOGGLE_DEACTIVATE); @@ -2884,12 +2884,12 @@ void JobWindow::initialiseTomoSubtomoWindow() current_y += STEPY /2 ; place("do_stack2d", TOGGLE_DEACTIVATE); - place("do_float16", TOGGLE_DEACTIVATE); + place("do_real_subtomo", TOGGLE_DEACTIVATE); + place("do_reproject_subtomo", TOGGLE_DEACTIVATE); current_y += STEPY /2 ; - place("do_reproject_subtomo", TOGGLE_DEACTIVATE); - place("do_real_subtomo", TOGGLE_DEACTIVATE); + place("do_float16", TOGGLE_DEACTIVATE); tab2->end(); } diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index fdc2ab24f..97e14641a 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -7110,10 +7110,11 @@ void RelionJob::initialiseTomoSubtomoJob() joboptions["min_frames"] = JobOption("Minimum nr. frames:", 1, 1, 40, 1, "Each selected pseudo-subtomogram need to be visible in at least this number of tilt series frames with doses below the maximum dose"); joboptions["do_stack2d"] = JobOption("Write output as 2D stacks?", true ,"If set to Yes, this program will write output subtomograms as 2D substacks. This is new as of relion-4.1, and the preferred way of generating subtomograms. If set to No, then relion-4.0 3D pseudo-subtomograms will be written out. Either can be used in subsequent refinements and classifications."); + joboptions["do_real_subtomo"] = JobOption("Extract real subtomograms?", false, "If set to Yes, this program will box out real subtomograms from the input tomogram and write those out as 3D subvolumes. All information about the missing wedge will be lost, and these volumes should probably not be used for alignment, classification or averaging."); + joboptions["do_reproject_subtomo"] = JobOption("Extract and re-project real subtomograms?", false, "If set to Yes, this program will box out real subtomograms from the input tomogram and then re-project these into 2D stacks that resemble the 2D stacks from windowed tilt series images. The size of the images is determined by the crop size; the pixel size is that of the tomogram. This will result in a particles.star file that is suitable for low-resolution subtomogram averaging and a particles_for_class2d.star file that can be used for fast 2D classification in order to select good particles. You can re-launch the GUI without the --tomo argument to get access to the Class2D job type. If set to No, the program will write out normal windowed tilt-series images that are suitable for high-resolution subtomogram averaging."); + joboptions["do_float16"] = JobOption("Write output in float16?", true ,"If set to Yes, this program will write output images in float16 MRC format. This will save a factor of two in disk space compared to the default of writing in float32. Note that RELION and CCPEM will read float16 images, but other programs may not (yet) do so."); - joboptions["do_reproject_subtomo"] = JobOption("Extract and re-project real subtomograms?", false, "If set to Yes, this program will box out real subtomograms from the input tomogram and then re-project these into 2D stacks that resemble the 2D stacks from windowed tilt series images. The size of the images is determined by the crop size; the pixel size is that of the tomogram. This will result in a particles.star file that is suitable for low-resolution subtomogram averaging and a particles_for_class2d.star file that can be used for fast 2D classification in order to select good particles. You can re-launch the GUI without the --tomo argument to get access to the Class2D job type. If set to No, the program will write out normal windowed tilt-series images that are suitable for high-resolution subtomogram averaging."); - joboptions["do_real_subtomo"] = JobOption("Extract real subtomograms?", false, "If set to Yes, this program will box out real subtomograms from the input tomogram and write those out as 3D subvolumes. All information about the missing wedge will be lost, and these volumes should probably not be used for alignment, classification or averaging."); } @@ -7124,6 +7125,16 @@ bool RelionJob::getCommandsTomoSubtomoJob(std::string &outputname, std::vector 1) + { + error_message = "You have to choose either to write output as 2D stacks, to extract real subtomos, or to extract and reproject real subtomos."; + return false; + } + if (joboptions["nr_mpi"].getNumber(error_message) > 1) command="`which relion_tomo_subtomo_mpi`"; else From 46ac3328c296c310227211f24e17c7157f10cb40 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 11 Dec 2024 08:40:13 +0000 Subject: [PATCH 032/189] tidied up code a bit --- .../tomography/programs/reconstruct_tomogram.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/jaz/tomography/programs/reconstruct_tomogram.cpp b/src/jaz/tomography/programs/reconstruct_tomogram.cpp index a5e139528..33e8484eb 100644 --- a/src/jaz/tomography/programs/reconstruct_tomogram.cpp +++ b/src/jaz/tomography/programs/reconstruct_tomogram.cpp @@ -409,7 +409,6 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) for (int ihalf = 0; ihalf < nr_halves; ihalf++) { - //std::cerr << " ihalf= " << ihalf << std::endl; // Get frame from Jasenko's stack if (ihalf == 0) { @@ -424,12 +423,10 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) DIRECT_A2D_ELEM(frame, y, x) = tomogram2.stack(x, y, f); } - //std::cerr <<" got frame"<< std::endl; // Make square (plus factor 1.4 padding) frame.setXmippOrigin(); frame.window(FIRST_XMIPP_INDEX(square_box), FIRST_XMIPP_INDEX(square_box), LAST_XMIPP_INDEX(square_box), LAST_XMIPP_INDEX(square_box)); - //std::cerr <<" windowed"<< std::endl; // Mirror the image back out into the padding area to prevent low-resolution artifacts int first_x = FIRST_XMIPP_INDEX(tomogram1.stack.xdim); @@ -465,10 +462,8 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) A2D_ELEM(frame, i, j) = A2D_ELEM(frame, ip, jp); } } - //std::cerr <<" dealt with edges"<< std::endl; // Downscale - //std::cerr <<" new_box= " << new_box << " square_box= " << square_box << std::endl; if (new_box != square_box) resizeMap(frame, new_box); //Image It; @@ -477,14 +472,12 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) // FT and get SNRs // Not entirely sure this is necessary, but FFTW transformers have been troublesome with threads in the past... - //std::cerr << " before FT" << std::endl; #pragma omp critical { FourierTransformer transformer; if (ihalf == 0) transformer.FourierTransform(frame, FT1); else transformer.FourierTransform(frame, FT2); }; - //std::cerr << " after FT" << std::endl; } // Get the transformation matrix @@ -505,17 +498,15 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) // Now that we have the FSC, sum the two halves together FT1 += FT2; } - // Center and shift - //std::cerr << " before centerFFT" << std::endl; + + // Center and shift CenterFFTbySign(FT1); shiftImageInFourierTransform(FT1, FT1, XSIZE(frame), -xshift/angpix_spacing, -yshift/angpix_spacing); - //std::cerr << " after centerFFT" << std::endl; // Get CTF MultidimArray Fctf; Fctf.resize(YSIZE(FT1), XSIZE(FT1)); ctf.getFftwImage(Fctf, new_box, new_box, angpix_spacing, false, false, ctf_intact_first_peak, false); - //std::cerr << " after getCTF" << std::endl; //Image Ictf; //Ictf()=Fctf; From e93f5ad810bd1509141524621537d57844567979 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 11 Dec 2024 08:40:34 +0000 Subject: [PATCH 033/189] repaired bug reproject subtomos --- src/jaz/tomography/programs/subtomo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jaz/tomography/programs/subtomo.cpp b/src/jaz/tomography/programs/subtomo.cpp index 6e93f9237..8d3d8281e 100644 --- a/src/jaz/tomography/programs/subtomo.cpp +++ b/src/jaz/tomography/programs/subtomo.cpp @@ -426,12 +426,12 @@ void SubtomoProgram::writeParticleSet( int datadim = (do_stack2d || do_reproject_subtomo) ? 2 : 3; copy.optTable.setValue(EMDL_IMAGE_DIMENSIONALITY, datadim, og); - RFLOAT mybinning = (do_real_subtomo) ? real_subtomo_binning : binning; + RFLOAT mybinning = (do_real_subtomo || do_reproject_subtomo) ? real_subtomo_binning : binning; copy.optTable.setValue(EMDL_TOMO_SUBTOMOGRAM_BINNING, mybinning, og); const double ps_img = copy.optTable.getDouble(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, og); const double ps_out = mybinning * ps_img; copy.optTable.setValue(EMDL_IMAGE_PIXEL_SIZE, ps_out, og); - int mysize = (do_real_subtomo) ? cropSize_tomogram : cropSize; + int mysize = (do_real_subtomo || do_reproject_subtomo) ? cropSize_tomogram : cropSize; copy.optTable.setValue(EMDL_IMAGE_SIZE, mysize, og); From aa2cf64fd2764fee0c31c2d4d450ebde9de9c012 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 11 Dec 2024 09:19:56 +0000 Subject: [PATCH 034/189] removed a big from fourier inversion tomogram reconstruction --- .../programs/reconstruct_tomogram.cpp | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/jaz/tomography/programs/reconstruct_tomogram.cpp b/src/jaz/tomography/programs/reconstruct_tomogram.cpp index 33e8484eb..7324a12b6 100644 --- a/src/jaz/tomography/programs/reconstruct_tomogram.cpp +++ b/src/jaz/tomography/programs/reconstruct_tomogram.cpp @@ -164,17 +164,22 @@ void TomoBackprojectProgram::initialise(bool verbose) std::cout << " + Applying a tilt angle offset of " << tiltAngleOffset << " degrees" << std::endl; } + if (fourierInversion) + { + std::cout << " + Using a Fourier-inversion reconstruction algorithm " << std::endl; + if (fourierWienerFilter) std::cout << " - with a Wiener-filter correction for the CTFs " << std::endl; + else std::cout << " - with only CTF-premultiplication of the tilt series images (for subsequent subtomogram extraction) " << std::endl; + } + else + { + std::cout << " + Using a real-space back-projection algorithm " << std::endl; + } if (do_2dproj) { - std::cout << " + Making 2D projections " << std::endl; + std::cout << " + Also making 2D projections of the tomograms " << std::endl; std::cout << " - centered at " << centre_2dproj << " tomogram pixels from the centre of the tomogram" << std::endl; std::cout << " - and a thickness of " << thickness_2dproj << " tomogram pixels" << std::endl; } - std::cout << " - fourierInversion= " << fourierInversion << " fourierWienerFilter= " << fourierWienerFilter << std::endl; - std::cout << " - doWiener= " << doWiener << std::endl; - std::cout << " - applyCtf= " << applyCtf << std::endl; - std::cout << " - applyPreWeight= " << applyPreWeight << std::endl; - if (doWiener) std::cout << " - applyWeight= " << applyWeight << std::endl; } @@ -405,10 +410,11 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) int nr_halves = (fourierWienerFilter) ? 2 : 1; MultidimArray FT1, FT2; - MultidimArray frame(tomogram1.stack.ydim, tomogram1.stack.xdim); + MultidimArray frame; for (int ihalf = 0; ihalf < nr_halves; ihalf++) { + frame.resize(tomogram1.stack.ydim, tomogram1.stack.xdim); // Get frame from Jasenko's stack if (ihalf == 0) { @@ -539,8 +545,6 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) { BP.set2DFourierTransform(FT1, A, &Fctf); }; - //std::cerr << " after set2DFT for frame " << f << std::endl; - } // end for loop over frames From c26316651fd27cd4675e35bf6d1f045da675ed09 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 11 Dec 2024 17:24:38 +0000 Subject: [PATCH 035/189] replaced CTF-corrected SNR algorithm with much faster vector version --- .../programs/reconstruct_tomogram.cpp | 58 ++++++++++++++++--- .../programs/reconstruct_tomogram.h | 23 +++----- 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/jaz/tomography/programs/reconstruct_tomogram.cpp b/src/jaz/tomography/programs/reconstruct_tomogram.cpp index 7324a12b6..b9038585f 100644 --- a/src/jaz/tomography/programs/reconstruct_tomogram.cpp +++ b/src/jaz/tomography/programs/reconstruct_tomogram.cpp @@ -18,15 +18,31 @@ using namespace gravis; double ReconstructSnrOptimisation::gradAndValue(const std::vector &x, std::vector &gradDest) const { - int size = x.size(); if (size != ctf2.size()) REPORT_ERROR("ReconstructSnrOptimisation ERROR: input x has incorrect size!"); if (size != gradDest.size()) REPORT_ERROR("ReconstructSnrOptimisation ERROR: input gradDest has incorrect size!"); + // Optimisation target: + // F(A) = 1/2 *|| C^2A - X||^2 +lamda/2 ||DA||^2 + // with + // ( 1 0 0 0) + // D = (-1 1 0 0) + // ( 0 -1 1 0) + // ( 0 0 -1 1) + // + // Then gradient: + // Delta F(A) = C^2 * ( C^2 A - X) * lambda * D^T * D * A + // + // and + // ( 2 -1 0 0) + // D ^T * D= (-1 2 -1 0) + // ( 0 -1 2 -1) + // ( 0 0 -1 2) + + /* Old, slow code for readability of the algorithm... // Convert input vector to Matrix1D Matrix1D xx(size); - for (int i = 0; i < size; i++) - VEC_ELEM(xx, i) = x[i]; + for (int i = 0; i < size; i++) VEC_ELEM(xx, i) = x[i]; // Calculate the current value of the target function Matrix1D Dy = D * xx; @@ -37,10 +53,30 @@ double ReconstructSnrOptimisation::gradAndValue(const std::vector &x, st Matrix1D grad = ctf2 * diff + lambda * D.transpose() * D * xx; // Convert output Matrix1D grad to vector - for (int i = 0; i < size; i++) - gradDest[i] = VEC_ELEM(grad, i); + for (int i = 0; i < size; i++) gradDest[i] = VEC_ELEM(grad, i); + */ + + // Much faster to act on the vectors only + std::vector myGrad(size); + RFLOAT sumDy2 = 0., mydiff, diff2 = 0, myDtDx; + for (long int i = 0; i < size; i++) + { + RFLOAT elem = x[i]; + if (i-1 >= 0) elem -= x[i-1]; + sumDy2 += elem*elem; + mydiff = ctf2[i] * x[i] - snr[i]; + diff2 += mydiff * mydiff; + + myDtDx = (i == size-1) ? x[i] : 2*x[i]; + if (i-1 >= 0) myDtDx -= x[i-1]; + if (i+1 < size) myDtDx -= x[i+1]; + + // set gradient + gradDest[i] = ctf2[i] * mydiff + lambda * myDtDx; + } + + return diff2 + (lambda / 2.) * sumDy2; - return value; } void TomoBackprojectProgram::readParameters(int argc, char *argv[]) @@ -299,6 +335,7 @@ MultidimArray TomoBackprojectProgram::getCtfCorrectedSNR(const Multidim // Ignore the spatial frequencies until the first peak in the CTF: just divide SNR by CTF^2 for those in the block below this one // Because SNRs can be very high here, they can mess up the optimisation below, just set all values before first peak to the SNR at first_peak int first_peak = problem.getFirstPeak(); + if (verb > 0) std::cout << " first_peak= " << first_peak << " xsize= " < initial(XSIZE(oriSNR)); FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(oriSNR) @@ -308,7 +345,9 @@ MultidimArray TomoBackprojectProgram::getCtfCorrectedSNR(const Multidim else initial[n] = DIRECT_MULTIDIM_ELEM(oriSNR, n); } + if (verb > 0) std::cout << "before optimise" << std::endl; std::vector newSNR= LBFGS::optimize(initial, problem, false, 100, 1e-7, 1e-6); + if (verb > 0) std::cout << "after optimise" << std::endl; FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(corrSNR) @@ -329,7 +368,6 @@ MultidimArray TomoBackprojectProgram::getCtfCorrectedSNR(const Multidim if (verb > 0) { - std::cout << " first_peak= " << first_peak << std::endl; std::cout << "# shell corrSNR newSNR oriSNR corrSNR*CTF^2 oriSNR*CTF^2 CTF^2 FSC" << std::endl; FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(corrSNR) { @@ -512,6 +550,7 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) // Get CTF MultidimArray Fctf; Fctf.resize(YSIZE(FT1), XSIZE(FT1)); + //std::cerr << " new_box= " << new_box << " YSIZE(FT1)= " << YSIZE(FT1) <<" defocus= " << ctf.DeltafU<< std::endl; ctf.getFftwImage(Fctf, new_box, new_box, angpix_spacing, false, false, ctf_intact_first_peak, false); //Image Ictf; @@ -523,6 +562,8 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) { // Wiener filter-type correction SNR = getCtfCorrectedSNR(FSC, Fctf, lambda, 0); + //for (int i = 0; i < XSIZE(SNR); i++) + // std::cerr << " f= " << f << " i =" << i << " snr= " << DIRECT_MULTIDIM_ELEM(SNR, i)< vol; vol().resize(new_box, new_box, new_box); vol().setXmippOrigin(); + vol().printShape(std::cerr); MultidimArray tau2(new_box); tau2.initConstant(1.); + BP.reconstruct(vol(), 0, fourierWienerFilter, tau2); if (!do_multiple) Log::print("Writing output"); + vol().window(FIRST_XMIPP_INDEX(d/spacing), FIRST_XMIPP_INDEX(h/spacing), FIRST_XMIPP_INDEX(w/spacing), LAST_XMIPP_INDEX(d/spacing), LAST_XMIPP_INDEX(h/spacing), LAST_XMIPP_INDEX(w/spacing), 0.); diff --git a/src/jaz/tomography/programs/reconstruct_tomogram.h b/src/jaz/tomography/programs/reconstruct_tomogram.h index 2b3044d36..d7f963f3b 100644 --- a/src/jaz/tomography/programs/reconstruct_tomogram.h +++ b/src/jaz/tomography/programs/reconstruct_tomogram.h @@ -29,26 +29,22 @@ class ReconstructSnrOptimisation : public FastDifferentiableOptimization if (ctf > 0.99) has_reached_one = true; if (has_reached_one && ctf < 0.99 && first_peak < 0) first_peak = n; } + if (first_peak < 0) first_peak = XSIZE(CTF) -1 ; + // set lambda + lambda = _lambda; + + + // vectors ctf2.resize(size); snr.resize(size); FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(CTF) { double ctf = DIRECT_MULTIDIM_ELEM(CTF, n); - VEC_ELEM(ctf2, n) = ctf * ctf; - VEC_ELEM(snr, n) = DIRECT_MULTIDIM_ELEM(SNR, n); - + ctf2[n] = ctf * ctf; + snr[n] = DIRECT_MULTIDIM_ELEM(SNR, n); } - // Set D matrix - D.initIdentity(size); - for (int i=1; i < size - 1; i++) - { - MAT_ELEM(D, i+1, i) = -1; - } - - // set lambda - lambda = _lambda; } double gradAndValue(const std::vector& x, std::vector& gradDest) const; @@ -57,8 +53,7 @@ class ReconstructSnrOptimisation : public FastDifferentiableOptimization int getFirstPeak() {return first_peak;}; private: - Matrix1D ctf2, snr; - Matrix2D D; + std::vector ctf2, snr; double lambda; int first_peak; From 9b4fca258aec802045319ff7e76544e45f8cc699 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 12 Dec 2024 12:18:49 +0000 Subject: [PATCH 036/189] repaired bug reported by Tina Drobnic in particle_reposition --- src/apps/particle_reposition.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/apps/particle_reposition.cpp b/src/apps/particle_reposition.cpp index c7ef86b99..391e2e2b6 100644 --- a/src/apps/particle_reposition.cpp +++ b/src/apps/particle_reposition.cpp @@ -87,9 +87,7 @@ class particle_reposition_parameters if (fn_dat != "") { std::cout <<" Reading data ..." << std::endl; - MetaDataTable MDdata; - MDdata.read(fn_dat); - optimiser.mydata.MDimg = MDdata; + optimiser.mydata.read(fn_dat, "", ""); } @@ -167,8 +165,7 @@ class particle_reposition_parameters FileName fn_mic2; optimiser.mydata.MDimg.getValue(EMDL_MICROGRAPH_NAME, fn_mic2, part_id); FileName fn_mic2_pre, fn_mic2_jobnr, fn_mic2_post; - decomposePipelineFileName(fn_mic2, fn_mic2_pre, fn_mic2_jobnr, fn_mic2_post); - + decomposePipelineFileName(fn_mic2, fn_mic2_pre, fn_mic2_jobnr, fn_mic2_post); if (fn_mic2_post == fn_mic_post) { From e08576922463e10b41869e3af3de98c889c4a270 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 18 Dec 2024 17:56:39 +0000 Subject: [PATCH 037/189] set angpix in fom image header --- src/amyloid_finder.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 7a25ddb30..db135f68d 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -715,6 +715,7 @@ MetaDataTable AmyloidFinder::traceFilaments(MultidimArray &Mscore, Multi int newsize = (XSIZE(Mpsi)*down_angpix) / angpix; if (newsize%2 !=0) newsize++; + down_angpix = angpix * XSIZE(Mpsi) / (float)newsize; resizeMap(Mscore, newsize); resizeMap(Mpsi, newsize); Mscore.setXmippOrigin(); @@ -930,6 +931,7 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) { Image Ipsi, Izscore; Ipsi.setSamplingRateInHeader(down_angpix); + Izscore.setSamplingRateInHeader(down_angpix); Ipsi()=Mangle; Izscore()=Mscore; Ipsi.write(fn_psi); From 56849b1d8b6b6a5bff15fe26f746fe2b63b6ef7c Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 18 Dec 2024 17:57:43 +0000 Subject: [PATCH 038/189] changed logic of tomogram reconstruction and subtomo extraction on the GUI, plus other changes to both programs... --- src/gui_jobwindow.cpp | 8 +- .../programs/reconstruct_tomogram.cpp | 58 ++-- .../programs/reconstruct_tomogram.h | 2 + src/jaz/tomography/programs/subtomo.cpp | 272 +++++++++--------- src/jaz/tomography/programs/subtomo.h | 7 +- src/jaz/tomography/programs/subtomo_mpi.cpp | 2 +- src/pipeline_jobs.cpp | 51 ++-- 7 files changed, 194 insertions(+), 206 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 6eba46d36..48dc8ce75 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -2872,9 +2872,9 @@ void JobWindow::initialiseTomoSubtomoWindow() tab2->label("Extract"); resetHeight(); - place("binning", TOGGLE_DEACTIVATE); - place("box_size", TOGGLE_DEACTIVATE); - place("crop_size", TOGGLE_DEACTIVATE); + place("box_size_angst", TOGGLE_DEACTIVATE); + place("precrop_size_angst", TOGGLE_DEACTIVATE); + place("binned_size_pix", TOGGLE_DEACTIVATE); current_y += STEPY /2 ; @@ -2884,8 +2884,8 @@ void JobWindow::initialiseTomoSubtomoWindow() current_y += STEPY /2 ; place("do_stack2d", TOGGLE_DEACTIVATE); + place("do_pseudo3d", TOGGLE_DEACTIVATE); place("do_real_subtomo", TOGGLE_DEACTIVATE); - place("do_reproject_subtomo", TOGGLE_DEACTIVATE); current_y += STEPY /2 ; diff --git a/src/jaz/tomography/programs/reconstruct_tomogram.cpp b/src/jaz/tomography/programs/reconstruct_tomogram.cpp index b9038585f..395a67ecd 100644 --- a/src/jaz/tomography/programs/reconstruct_tomogram.cpp +++ b/src/jaz/tomography/programs/reconstruct_tomogram.cpp @@ -117,10 +117,9 @@ void TomoBackprojectProgram::readParameters(int argc, char *argv[]) applyPreWeight = parser.checkOption("--pre_weight", "Pre-weight the 2D slices prior to backprojection"); FourierCrop = parser.checkOption("--Fc", "Downsample the 2D images by Fourier cropping"); SNR = textToDouble(parser.getOption("--SNR", "SNR assumed by the Wiener filter", "10")); - applyCtf = parser.checkOption("--ctf", "Perform CTF correction"); doWiener = !parser.checkOption("--skip_wiener", "Do multiply images with CTF, but don't divide by CTF^2 in Wiener filter"); - if (doWiener) applyCtf = true; + if (!doWiener) applyCtf = true; zeroDC = !parser.checkOption("--keep_mean", "Do not zero the DC component of each frame"); taperDist = textToDouble(parser.getOption("--td", "Tapering distance", "0.0")); @@ -209,6 +208,11 @@ void TomoBackprojectProgram::initialise(bool verbose) else { std::cout << " + Using a real-space back-projection algorithm " << std::endl; + std::cout << " - applyWeight= " << applyWeight << std::endl; + std::cout << " - applyPreWeight= " << applyPreWeight << std::endl; + std::cout << " - FourierCrop= " << FourierCrop << std::endl; + std::cout << " - applyCtf= " << applyCtf << std::endl; + std::cout << " - doWiener= " << doWiener << " with SNR= " << SNR << std::endl; } if (do_2dproj) { @@ -335,7 +339,6 @@ MultidimArray TomoBackprojectProgram::getCtfCorrectedSNR(const Multidim // Ignore the spatial frequencies until the first peak in the CTF: just divide SNR by CTF^2 for those in the block below this one // Because SNRs can be very high here, they can mess up the optimisation below, just set all values before first peak to the SNR at first_peak int first_peak = problem.getFirstPeak(); - if (verb > 0) std::cout << " first_peak= " << first_peak << " xsize= " < initial(XSIZE(oriSNR)); FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(oriSNR) @@ -345,10 +348,7 @@ MultidimArray TomoBackprojectProgram::getCtfCorrectedSNR(const Multidim else initial[n] = DIRECT_MULTIDIM_ELEM(oriSNR, n); } - if (verb > 0) std::cout << "before optimise" << std::endl; std::vector newSNR= LBFGS::optimize(initial, problem, false, 100, 1e-7, 1e-6); - if (verb > 0) std::cout << "after optimise" << std::endl; - FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(corrSNR) { @@ -410,8 +410,9 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) // Make sure to make all images squared before using FFTs int square_box = (tomogram1.stack.xdim == tomogram1.stack.ydim) ? tomogram1.stack.xdim : XMIPP_MAX(tomogram1.stack.xdim, tomogram1.stack.ydim); - // Make sqrt(2) bigger to account for the empty corners that otherwise appear with the spherical mask.... - square_box *= sqrt(2.); + + RFLOAT extra_box = 0.2 * square_box; + square_box += extra_box; if (square_box%2 != 0) square_box++; double pixelSizeAct = tomogramSet.getTiltSeriesPixelSize(tomoIndex); @@ -440,12 +441,22 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) for (int f = 0; f < fc; f++) { CTF ctf = tomogram1.centralCTFs[f]; - // Don't use CTF scale factors, as we will measure SNRs using the FSC! + // Don't use CTF scale factors, as we will measure SNRs using the FSC, and without the Wiener filter we will use doseWeights ctf.scale = 1.0; // Skip any frames that are over-focused, as first-peak calculations below will be invalid. These frames are probably bad anyway.... if (ctf.DeltafU < 100. || ctf.DeltafV < 100.) continue; + // Get shifts and angles + RFLOAT xtilt, ytilt, zrot, xshift_angst, yshift_angst; + tomogram1.getProjectionAnglesFromMatrix(f, tomogram1.projectionMatrices[f], xtilt, ytilt, zrot, xshift_angst, yshift_angst); + + // Also get the 3D transformation matrix for the backprojection + const Matrix2D A(3, 3); + for (int row = 0; row < 3; row++) + for (int col = 0; col < 3; col++) + MAT_ELEM(A, row, col) = tomogram1.projectionMatrices[f](row, col); + int nr_halves = (fourierWienerFilter) ? 2 : 1; MultidimArray FT1, FT2; MultidimArray frame; @@ -479,6 +490,8 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) int last_y = LAST_XMIPP_INDEX(tomogram1.stack.ydim); FOR_ALL_ELEMENTS_IN_ARRAY2D(frame) { + + // Check whether I need to mirror in the rotated corners from the other side int jp = j, ip = i; bool do_change = false; if (j < first_x) @@ -505,6 +518,7 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) { A2D_ELEM(frame, i, j) = A2D_ELEM(frame, ip, jp); } + } // Downscale @@ -512,7 +526,7 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) //Image It; //It()=frame; - //It.write("rec_frame"+ integerToString(f)+".mrc"); + //It.write(outFn + "rec_frame"+ integerToString(f)+".mrc"); // FT and get SNRs // Not entirely sure this is necessary, but FFTW transformers have been troublesome with threads in the past... @@ -524,16 +538,6 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) }; } - // Get the transformation matrix - const Matrix2D A(3, 3); - for (int row = 0; row < 3; row++) - for (int col = 0; col < 3; col++) - MAT_ELEM(A, row, col) = tomogram1.projectionMatrices[f](row, col); - - RFLOAT xshift, yshift; - m.getValueSafely(EMDL_TOMO_XSHIFT_ANGST, xshift, f); - m.getValueSafely(EMDL_TOMO_YSHIFT_ANGST, yshift, f); - MultidimArray FSC, SNR; if (fourierWienerFilter) { @@ -545,25 +549,22 @@ void TomoBackprojectProgram::reconstructOneTomogramFourier(int tomoIndex) // Center and shift CenterFFTbySign(FT1); - shiftImageInFourierTransform(FT1, FT1, XSIZE(frame), -xshift/angpix_spacing, -yshift/angpix_spacing); + shiftImageInFourierTransform(FT1, FT1, XSIZE(frame), -xshift_angst / angpix_spacing, -yshift_angst / angpix_spacing); // Get CTF MultidimArray Fctf; Fctf.resize(YSIZE(FT1), XSIZE(FT1)); - //std::cerr << " new_box= " << new_box << " YSIZE(FT1)= " << YSIZE(FT1) <<" defocus= " << ctf.DeltafU<< std::endl; ctf.getFftwImage(Fctf, new_box, new_box, angpix_spacing, false, false, ctf_intact_first_peak, false); //Image Ictf; //Ictf()=Fctf; - //Ictf.write("rec_Ictf_frame"+ integerToString(f)+".mrc"); + //Ictf.write(outFn + "rec_Ictf_frame"+ integerToString(f)+".mrc"); // Calculate the CTF-corrected SNR from the FSC if (fourierWienerFilter) { // Wiener filter-type correction SNR = getCtfCorrectedSNR(FSC, Fctf, lambda, 0); - //for (int i = 0; i < XSIZE(SNR); i++) - // std::cerr << " f= " << f << " i =" << i << " snr= " << DIRECT_MULTIDIM_ELEM(SNR, i)< vol; vol().resize(new_box, new_box, new_box); vol().setXmippOrigin(); - vol().printShape(std::cerr); MultidimArray tau2(new_box); tau2.initConstant(1.); BP.reconstruct(vol(), 0, fourierWienerFilter, tau2); if (!do_multiple) Log::print("Writing output"); - vol().window(FIRST_XMIPP_INDEX(d/spacing), FIRST_XMIPP_INDEX(h/spacing), FIRST_XMIPP_INDEX(w/spacing), LAST_XMIPP_INDEX(d/spacing), LAST_XMIPP_INDEX(h/spacing), LAST_XMIPP_INDEX(w/spacing), 0.); @@ -777,7 +777,7 @@ void TomoBackprojectProgram::reconstructOneTomogram(int tomoIndex, bool doEven, if (applyPreWeight) { - stackAct = RealSpaceBackprojection::preWeight(stackAct, projAct, n_threads); + stackAct = RealSpaceBackprojection::preWeight(stackAct, projAct, n_threads); } if (!do_multiple) Log::print("Backprojecting"); diff --git a/src/jaz/tomography/programs/reconstruct_tomogram.h b/src/jaz/tomography/programs/reconstruct_tomogram.h index d7f963f3b..5867950a9 100644 --- a/src/jaz/tomography/programs/reconstruct_tomogram.h +++ b/src/jaz/tomography/programs/reconstruct_tomogram.h @@ -7,10 +7,12 @@ #include #include #include +#include #include #include #include #include +#include class ReconstructSnrOptimisation : public FastDifferentiableOptimization { diff --git a/src/jaz/tomography/programs/subtomo.cpp b/src/jaz/tomography/programs/subtomo.cpp index 8d3d8281e..b49635309 100644 --- a/src/jaz/tomography/programs/subtomo.cpp +++ b/src/jaz/tomography/programs/subtomo.cpp @@ -44,9 +44,15 @@ void SubtomoProgram::readBasicParameters(IOParser& parser) int gen_section = parser.addSection("Reconstruction options"); - boxSize = textToInteger(parser.getOption("--b", "Binned projection box size")); + + + boxSize = textToInteger(parser.getOption("--b", "Binned projection box size", "-1")); cropSize = textToInteger(parser.getOption("--crop", "Output box size", "-1")); binning = textToDouble(parser.getOption("--bin", "Binning factor", "1")); + boxSizeAngst = textToDouble(parser.getOption("--box_size_A", "OR: Final (cropped) box size (in Angstrom)", "-1")); + precropSizeAngst = textToDouble(parser.getOption("--precrop_size_A", "OR: Pre-cropped box size for extraction from tilt series (in Angstrom)", "-1")); + binnedSizePixel = textToInteger(parser.getOption("--binned_size_pix", "OR: Binned box size (in pixels)", "-1")); + do_stack2d = parser.checkOption("--stack2d", "Write out 2D stacks of cropped images for each particle, instead of pseudo-subtomograms"); write_multiplicity = parser.checkOption("--multi", "Write out multiplicity volumes"); SNR = textToDouble(parser.getOption("--SNR", "Assumed signal-to-noise ratio (negative means use a heuristic)", "-1")); @@ -96,7 +102,6 @@ void SubtomoProgram::readBasicParameters(IOParser& parser) run_from_GUI = is_under_pipeline_control(); do_real_subtomo = parser.checkOption("--real_subtomo", "Extract true subtomograms and write those out as 3D volumes, together with their 3D CTFs and 2D projections along Z"); - do_reproject_subtomo = parser.checkOption("--reproject_subtomo", "Extract true subtomograms, but then write out re-projections of those as 2D stacks"); } @@ -126,6 +131,31 @@ void SubtomoProgram::readParameters(int argc, char *argv[]) outDir = ZIO::prepareTomoOutputDirectory(outDir, argc, argv); } + +void SubtomoProgram::setBoxsizes(TomogramSet &tomogramSet, bool verb) +{ + if (boxSizeAngst > 0.) + { + double angpix = tomogramSet.getTiltSeriesPixelSize(0); + binning = boxSizeAngst / (binnedSizePixel * angpix); + cropSize = binnedSizePixel; + + if (precropSizeAngst < 0.) + { + boxSize = cropSize; + } + else + { + boxSize = precropSizeAngst / (binning * angpix); + if (boxSize%2 != 0) boxSize++; + } + if (verb) Log::print("Setting boxSize= " + ZIO::itoa(boxSize)+" cropSize= " + ZIO::itoa(cropSize) + " binning= " + ZIO::itoa(binning)); + } + + if (cropSize < 0) cropSize = boxSize; + +} + void SubtomoProgram::run() { TomogramSet tomogramSet(optimisationSet.tomograms, true); @@ -133,8 +163,8 @@ void SubtomoProgram::run() ParticleSet particleSet(optimisationSet.particles, optimisationSet.trajectories, true, &tomogramSet); std::vector > particles = particleSet.splitByTomogram(tomogramSet, true); - if (cropSize < 0) cropSize = boxSize; - + setBoxsizes(tomogramSet); + const long int s2D = boxSize; const long int s3D = cropSize; @@ -285,9 +315,9 @@ void SubtomoProgram::writeParticleSet( ParticleSet copy2d, copy = particleSet; copy.clearParticles(); - copy.is_stack2d = (do_stack2d || do_reproject_subtomo); + copy.is_stack2d = do_stack2d; - if (do_real_subtomo || do_reproject_subtomo) + if (do_real_subtomo) { copy2d = particleSet; copy2d.clearParticles(); @@ -310,7 +340,7 @@ void SubtomoProgram::writeParticleSet( Tomogram tomogram = tomogramSet.loadTomogram(t, false); std::string outProj; - if (do_real_subtomo || do_reproject_subtomo) + if (do_real_subtomo) { RFLOAT tomogram_binning; tomogramSet.globalTable.getValue(EMDL_TOMO_TOMOGRAM_BINNING, tomogram_binning, t); @@ -348,11 +378,12 @@ void SubtomoProgram::writeParticleSet( const ParticleIndex new_id = copy.addParticle(particleSet, part_id); // Also set isVisible in the output particle STAR file - if (do_stack2d || do_reproject_subtomo) + if (do_stack2d) { std::vector isVisibleInt(isVisible.size(), 0); for (int f = 0; f < tomogram.frameCount; f++) if (isVisible[f]) isVisibleInt[f] = 1; + copy.partTable.setValue(EMDL_TOMO_VISIBLE_FRAMES, isVisibleInt); } @@ -363,8 +394,8 @@ void SubtomoProgram::writeParticleSet( part_id, t, particleSet, tomogramSet); std::string outCtf; - std::string outData = (do_stack2d || do_reproject_subtomo) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; - std::string outWeight = (do_stack2d || do_reproject_subtomo) ? "" : filenameRoot + "_weights.mrc"; + std::string outData = (do_stack2d) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; + std::string outWeight = (do_stack2d) ? "" : filenameRoot + "_weights.mrc"; copy.setImageFileNames(outData, outWeight, new_id); if (do_real_subtomo) { @@ -394,7 +425,7 @@ void SubtomoProgram::writeParticleSet( copy.partTable.setValue(EMDL_ORIENT_PSI, 0.0, new_id.value); } - if (do_real_subtomo || do_reproject_subtomo) + if (do_real_subtomo) { // Also make a particle star file for 2D classification copy2d.partTable.addObject( copy.partTable.getObject(new_id.value) ); @@ -421,17 +452,17 @@ void SubtomoProgram::writeParticleSet( for (int og = 0; og < copy.numberOfOpticsGroups(); og++) { - bool is_premultiplied = (do_stack2d || do_reproject_subtomo) ? do_ctf : true; + bool is_premultiplied = (do_stack2d) ? do_ctf : true; copy.optTable.setValue(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED, is_premultiplied, og); - int datadim = (do_stack2d || do_reproject_subtomo) ? 2 : 3; + int datadim = (do_stack2d) ? 2 : 3; copy.optTable.setValue(EMDL_IMAGE_DIMENSIONALITY, datadim, og); - RFLOAT mybinning = (do_real_subtomo || do_reproject_subtomo) ? real_subtomo_binning : binning; + RFLOAT mybinning = (do_real_subtomo) ? real_subtomo_binning : binning; copy.optTable.setValue(EMDL_TOMO_SUBTOMOGRAM_BINNING, mybinning, og); const double ps_img = copy.optTable.getDouble(EMDL_TOMO_TILT_SERIES_PIXEL_SIZE, og); const double ps_out = mybinning * ps_img; copy.optTable.setValue(EMDL_IMAGE_PIXEL_SIZE, ps_out, og); - int mysize = (do_real_subtomo || do_reproject_subtomo) ? cropSize_tomogram : cropSize; + int mysize = (do_real_subtomo) ? cropSize_tomogram : cropSize; copy.optTable.setValue(EMDL_IMAGE_SIZE, mysize, og); @@ -441,11 +472,11 @@ void SubtomoProgram::writeParticleSet( if (copy.partTable.containsLabel(EMDL_IMAGE_COORD_Y)) copy.partTable.deactivateLabel(EMDL_IMAGE_COORD_Y); if (copy.partTable.containsLabel(EMDL_IMAGE_COORD_Z)) copy.partTable.deactivateLabel(EMDL_IMAGE_COORD_Z); // remove CTF image name that could be there from old pseudo-subtomo jobs if we're writing 2D stacks - if ((do_stack2d || do_reproject_subtomo) && copy.partTable.containsLabel(EMDL_CTF_IMAGE)) copy.partTable.deactivateLabel(EMDL_CTF_IMAGE); + if (do_stack2d && copy.partTable.containsLabel(EMDL_CTF_IMAGE)) copy.partTable.deactivateLabel(EMDL_CTF_IMAGE); copy.write(outDir + "particles.star"); - if (do_real_subtomo || do_reproject_subtomo) + if (do_real_subtomo) { if (copy2d.partTable.containsLabel(EMDL_IMAGE_COORD_X)) copy2d.partTable.deactivateLabel(EMDL_IMAGE_COORD_X); @@ -569,143 +600,102 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( BufferedImage resultImg; - if (do_reproject_subtomo) - { - - MultidimArray dummy; - Projector projector(cropSize_tomogram); - projector.computeFourierTransformMap(subtom, dummy); - - MultidimArray img(cropSize_tomogram, cropSize_tomogram); - MultidimArray F2D; - FourierTransformer transformer; - transformer.setReal(img); - transformer.getFourierAlias(F2D); - resultImg.resize(cropSize_tomogram, cropSize_tomogram, tomogram.frameCount); - for (int f = 0; f < tomogram.frameCount; f++) - { - d4Matrix Aproj = tomogram.projectionMatrices[f]; - Matrix2D A(3,3); - for (int row= 0; row < 3; row++) - for (int col = 0; col < 3; col++) - MAT_ELEM(A, row, col) = Aproj(row, col); - - // Get the 2D slice out of the 3D Fourier transform - F2D.initZeros(); - projector.get2DFourierTransform(F2D, A); - shiftImageInFourierTransform(F2D, F2D, cropSize_tomogram, cropSize_tomogram/2, cropSize_tomogram/2); - transformer.inverseFourierTransform(); - - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(img) - { - resultImg(j, i, f) = DIRECT_A2D_ELEM(img, i, j); - } - } - } - else if (do_real_subtomo)// end if do_reproject_subtomo + resultImg.resize(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); + ctfImage.resize(cropSize_tomogram / 2 + 1, cropSize_tomogram, cropSize_tomogram); + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY3D(subtom) { + resultImg(j, i, k) = DIRECT_A3D_ELEM(subtom, k, i, j); + } - resultImg.resize(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); - ctfImage.resize(cropSize_tomogram / 2 + 1, cropSize_tomogram, cropSize_tomogram); - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY3D(subtom) - { - resultImg(j, i, k) = DIRECT_A3D_ELEM(subtom, k, i, j); - } - - BackProjector backprojector(cropSize_tomogram, 3, "C1", TRILINEAR, - 1, 10, 0, 1.9, 15., 2, true); + BackProjector backprojector(cropSize_tomogram, 3, "C1", TRILINEAR, + 1, 10, 0, 1.9, 15., 2, true); - backprojector.initZeros(); + backprojector.initZeros(); - MultidimArray Fw(cropSize_tomogram, cropSize_tomogram/2 + 1), Fctf(cropSize_tomogram, cropSize_tomogram/2 + 1); - MultidimArray F2D; - F2D.initZeros(cropSize_tomogram, cropSize_tomogram/2 + 1); - Fw.initConstant(1.); + MultidimArray Fw(cropSize_tomogram, cropSize_tomogram/2 + 1), Fctf(cropSize_tomogram, cropSize_tomogram/2 + 1); + MultidimArray F2D; + F2D.initZeros(cropSize_tomogram, cropSize_tomogram/2 + 1); + Fw.initConstant(1.); - for (int f = 0; f < tomogram.frameCount; f++) - { + for (int f = 0; f < tomogram.frameCount; f++) + { - d4Matrix Aproj = tomogram.projectionMatrices[f]; - Matrix2D A(3,3); - for (int row= 0; row < 3; row++) - for (int col = 0; col < 3; col++) - MAT_ELEM(A, row, col) = Aproj(row, col); - - // Get the CTF, as per Tanmay's paper on subtomogram averaging in relion-3 - RFLOAT xtilt, ytilt, zrot, xshift_angst, yshift_angst; - tomogram.getProjectionAnglesFromMatrix(f, Aproj, xtilt, ytilt, zrot, xshift_angst, yshift_angst); - - CTF ctf = tomogram.getCtf(f, pos); - ctf.Bfac = 4.0 * tomogram.getCumulativeDose(f); - ctf.scale = COSD(ytilt); - ctf.initialise(); - ctf.getFftwImage(Fctf, cropSize_tomogram, cropSize_tomogram, tomogram_angpix, - false, false, false, true); - - /* - std::cerr << " A= " << A << " ytilt= " << ytilt << " ctf.scale= " << ctf.scale << " tomogram_angpix= " << tomogram_angpix << std::endl; - std::cerr << "ctf.defocusU= " << ctf.DeltafU << " ctf.Bfac= " << ctf.Bfac << " pos= " << pos.x <<" "<< pos.y << " f= " << f << std::endl; + d4Matrix Aproj = tomogram.projectionMatrices[f]; + Matrix2D A(3,3); + for (int row= 0; row < 3; row++) + for (int col = 0; col < 3; col++) + MAT_ELEM(A, row, col) = Aproj(row, col); + + // Get the CTF, as per Tanmay's paper on subtomogram averaging in relion-3 + RFLOAT xtilt, ytilt, zrot, xshift_angst, yshift_angst; + tomogram.getProjectionAnglesFromMatrix(f, Aproj, xtilt, ytilt, zrot, xshift_angst, yshift_angst); + + CTF ctf = tomogram.getCtf(f, pos); + ctf.Bfac = 4.0 * tomogram.getCumulativeDose(f); + ctf.scale = COSD(ytilt); + ctf.initialise(); + ctf.getFftwImage(Fctf, cropSize_tomogram, cropSize_tomogram, tomogram_angpix, + false, false, false, true); + + /* + std::cerr << " f= " << f << " A= " << A << " ytilt= " << ytilt << " ctf.scale= " << ctf.scale << " tomogram_angpix= " << tomogram_angpix << std::endl; + std::cerr << "ctf.defocusU= " << ctf.DeltafU << " ctf.Bfac= " << ctf.Bfac << " pos= " << pos.x <<" "<< pos.y << std::endl; Image It; It()= Fctf; It.write("Fctf.spi"); std::cerr << " pres any key to continue..."<< std::endl; char c; std::cin >> c; -*/ - - FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Fctf) - { - DIRECT_MULTIDIM_ELEM(F2D, n) = DIRECT_MULTIDIM_ELEM(Fctf, n) * DIRECT_MULTIDIM_ELEM(Fctf, n); - } - backprojector.set2DFourierTransform(F2D, A, &Fw); + */ + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Fctf) + { + DIRECT_MULTIDIM_ELEM(F2D, n) = DIRECT_MULTIDIM_ELEM(Fctf, n) * DIRECT_MULTIDIM_ELEM(Fctf, n); } + backprojector.set2DFourierTransform(F2D, A, &Fw); - // Do division in 3D - MultidimArray CTF3D; - CTF3D.initZeros(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); - CTF3D.setXmippOrigin(); - FOR_ALL_ELEMENTS_IN_ARRAY3D(CTF3D) - { - int jp = j; - int ip = i; - int kp = k; - - // for negative j's: use inverse - if (j < 0) - { - jp = -j; - ip = -i; - kp = -k; - } + } - if (jp >= STARTINGX(backprojector.data) && jp <= FINISHINGX(backprojector.data) && - ip >= STARTINGY(backprojector.data) && ip <= FINISHINGY(backprojector.data) && - kp >= STARTINGZ(backprojector.data) && kp <= FINISHINGZ(backprojector.data)) - { - if (A3D_ELEM(backprojector.weight, kp, ip, jp) > 0.) - { - A3D_ELEM(CTF3D, k, i, j) = sqrt(A3D_ELEM(backprojector.data, kp, ip, jp) / A3D_ELEM(backprojector.weight, kp, ip, jp)); - } - } - } + // Do division in 3D + MultidimArray CTF3D; + CTF3D.initZeros(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); + CTF3D.setXmippOrigin(); + FOR_ALL_ELEMENTS_IN_ARRAY3D(CTF3D) + { + int jp = j; + int ip = i; + int kp = k; - // FOR_ALL_ELEMENTS_IN_FFTW_TRANSFORM - for (long int k = 0, kp = 0; k= STARTINGX(backprojector.data) && jp <= FINISHINGX(backprojector.data) && + ip >= STARTINGY(backprojector.data) && ip <= FINISHINGY(backprojector.data) && + kp >= STARTINGZ(backprojector.data) && kp <= FINISHINGZ(backprojector.data)) + { + if (A3D_ELEM(backprojector.weight, kp, ip, jp) > 0.) + { + A3D_ELEM(CTF3D, k, i, j) = sqrt(A3D_ELEM(backprojector.data, kp, ip, jp) / A3D_ELEM(backprojector.weight, kp, ip, jp)); + } + } } + // FOR_ALL_ELEMENTS_IN_FFTW_TRANSFORM + for (long int k = 0, kp = 0; k recTomo; RFLOAT tomogram_angpix, tomogram_binning; - if (do_real_subtomo || do_reproject_subtomo) + if (do_real_subtomo) { FileName fn_tomo, fn_tomo2=""; if (tomogramSet.globalTable.containsLabel(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_FILE_NAME)) @@ -818,7 +808,7 @@ void SubtomoProgram::processTomograms( BufferedImage proj2d; std::string outProj; - if (do_real_subtomo || do_reproject_subtomo) + if (do_real_subtomo) { int cropSize_tomogram = ROUND(cropSize * binning * tomogram.optics.pixelSize / tomogram_angpix); if (cropSize_tomogram%2 != 0) @@ -844,13 +834,13 @@ void SubtomoProgram::processTomograms( part_id, t, particleSet, tomogramSet); std::string outCtf; - std::string outData = (do_stack2d || do_reproject_subtomo) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; + std::string outData = (do_stack2d) ? filenameRoot + "_stack2d.mrcs" : filenameRoot + "_data.mrc"; if (do_real_subtomo) { outData = filenameRoot + "_subtomo.mrc"; outCtf = filenameRoot + "_ctf.mrc"; } - std::string outWeight = (do_stack2d || do_reproject_subtomo) ? "" : filenameRoot + "_weights.mrc"; + std::string outWeight = (do_stack2d) ? "" : filenameRoot + "_weights.mrc"; std::string outCTF = filenameRoot + "_CTF2.mrc"; std::string outDiv = filenameRoot + "_div.mrc"; std::string outMulti = filenameRoot + "_multi.mrc"; @@ -873,12 +863,16 @@ void SubtomoProgram::processTomograms( BufferedImage particleStack = BufferedImage(sh2D, s2D, fc); BufferedImage weightStack(sh2D, s2D, fc); - if (do_real_subtomo || do_reproject_subtomo) + if (do_real_subtomo) { + // Reste isVisible to all ones + std::fill(isVisible.begin(), isVisible.end(), true); + // This will extract the true subtomograms and calculate their FTs in the directions of the tilt series BufferedImage ctfImage, projImage; BufferedImage subtomo = extractRealSubtomograms(part_id, recTomo(), - tomogram, particleSet, tomogram_angpix, ctfImage, projImage); + tomogram, particleSet, tomogram_angpix, + ctfImage, projImage); subtomo.write(outData, tomogram_angpix, write_float16); if (do_real_subtomo) ctfImage.write(outCtf, tomogram_angpix, write_float16); diff --git a/src/jaz/tomography/programs/subtomo.h b/src/jaz/tomography/programs/subtomo.h index f718921a9..b1ee53dc1 100644 --- a/src/jaz/tomography/programs/subtomo.h +++ b/src/jaz/tomography/programs/subtomo.h @@ -31,13 +31,16 @@ class SubtomoProgram int boxSize, cropSize, + binnedSizePixel, min_frames, num_threads; double SNR, binning, - taper, + boxSizeAngst, + precropSizeAngst, + taper, env_sigma, cone_slope, cone_sig0, @@ -70,11 +73,11 @@ class SubtomoProgram write_float16, run_from_GUI, run_from_MPI, - do_reproject_subtomo, do_real_subtomo; void readBasicParameters(IOParser& parser); virtual void readParameters(int argc, char *argv[]); + virtual void setBoxsizes(TomogramSet &tomogramSet, bool verb = true); virtual void run(); diff --git a/src/jaz/tomography/programs/subtomo_mpi.cpp b/src/jaz/tomography/programs/subtomo_mpi.cpp index a7e60c2f0..0f85668eb 100644 --- a/src/jaz/tomography/programs/subtomo_mpi.cpp +++ b/src/jaz/tomography/programs/subtomo_mpi.cpp @@ -75,7 +75,7 @@ void SubtomoProgramMpi::run() ParticleSet particleSet(optimisationSet.particles, optimisationSet.trajectories, node->isLeader(), &tomogramSet); std::vector > particles = particleSet.splitByTomogram(tomogramSet, node->isLeader()); - if (cropSize < 0) cropSize = boxSize; + setBoxsizes(tomogramSet, node->isLeader()); const long int s2D = boxSize; diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 97e14641a..861c6382a 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -6716,9 +6716,7 @@ void RelionJob::initialiseTomoReconstructTomogramsJob() hidden_name = ".gui_tomo_reconstruct_tomograms"; joboptions["in_tiltseries"] = JobOption("Input tilt series:", LABEL_TOMOGRAMS_CPIPE, 1, "", "STAR files (*.star)", "Input global tilt series star file."); - joboptions["tiltangle_offset"] = JobOption("Tilt angle offset (deg): ", 0., -25., 25, 1, "The tomogram tilt angles will all be changed by this value. This may be useful to reconstruct lamellae that are all milled under a given angle. All tomograms will be reconstructed with the same offset. Use the tomogram name option below to reconstruct only a single tomogram."); - joboptions["tomo_name"] = JobOption("Reconstruct only this tomogram:", std::string(""), "If not left empty, the program will only reconstruct this particular tomogram"); joboptions["generate_split_tomograms"] = JobOption("Generate tomograms for denoising?:", false, "Generate tomograms for input into a denoising job. For this option to work, Save images for denoising? should have been True during Motion Correction. Additionally, adjust zdim to minimise the amount of empty space without sample within the tomograms."); @@ -6796,7 +6794,11 @@ bool RelionJob::getCommandsTomoReconstructTomogramsJob(std::string &outputname, { command += " --skip_fourier_wiener "; } - + } + else + { + // Let's do CTF-premultiplication by default, in case people want to do real subtomogram averaging afterwards + command += " --ctf "; } if (joboptions["do_proj"].getBoolean()) @@ -7102,16 +7104,17 @@ void RelionJob::initialiseTomoSubtomoJob() addTomoInputOptions(true, true, true, false); - joboptions["binning"] = JobOption("Binning factor:", 1, 1, 16, 1, "The tilt series images will be binned by this (real-valued) factor and then reconstructed in the specified box size above. Note that thereby the reconstructed region becomes larger when specifying binning factors larger than one."); - joboptions["box_size"] = JobOption("Box size (binned pix):", 128, 32, 512, 16, "The initial box size of the reconstruction. A sufficiently large box size allows more of the high-frequency signal to be captured that has been delocalised by the CTF."); - joboptions["crop_size"] = JobOption("Cropped box size (binned pix):", -1, -1, 512, 16, "If set to a positive value, after construction, the resulting pseudo subtomograms are cropped to this size. A smaller box size allows the (generally expensive) refinement using relion_refine to proceed more rapidly."); + + joboptions["box_size_angst"] = JobOption("Box size (A):", 128, 32, 512, 16, "The final particle box size in Angstrom."); + joboptions["binned_size_pix"] = JobOption("Binning to box size (pixels):", 64, 16, 256, 16, "The box size above will be binned to yield the number of pixels given here. Smaller boxes will refine faster and occupy less space, but will be limited to lower Nyquist frequency."); + joboptions["precrop_size_angst"] = JobOption("Pre-cropping box size (A):", -1, -1, 512, 16, "If set to a positive value, then a larger box size can be used to extract 2D slices out of the micrograph, pre-multiply these with the CTF, and then crop to the box size above. A sufficiently large pre-crop box size therefore will allow more of the high-frequency signal to be captured that has been delocalised by the CTF. "); joboptions["max_dose"] = JobOption("Maximum dose (e/A^2):", -1, -1, 200, 1, "Tilt series frames with a dose higher than this maximum dose (in electrons per squared Angstroms) will not be included in the 3D pseudo-subtomogram, or in the 2D stack. For the latter, this will disc I/O operations and increase speed."); joboptions["min_frames"] = JobOption("Minimum nr. frames:", 1, 1, 40, 1, "Each selected pseudo-subtomogram need to be visible in at least this number of tilt series frames with doses below the maximum dose"); - joboptions["do_stack2d"] = JobOption("Write output as 2D stacks?", true ,"If set to Yes, this program will write output subtomograms as 2D substacks. This is new as of relion-4.1, and the preferred way of generating subtomograms. If set to No, then relion-4.0 3D pseudo-subtomograms will be written out. Either can be used in subsequent refinements and classifications."); - joboptions["do_real_subtomo"] = JobOption("Extract real subtomograms?", false, "If set to Yes, this program will box out real subtomograms from the input tomogram and write those out as 3D subvolumes. All information about the missing wedge will be lost, and these volumes should probably not be used for alignment, classification or averaging."); - joboptions["do_reproject_subtomo"] = JobOption("Extract and re-project real subtomograms?", false, "If set to Yes, this program will box out real subtomograms from the input tomogram and then re-project these into 2D stacks that resemble the 2D stacks from windowed tilt series images. The size of the images is determined by the crop size; the pixel size is that of the tomogram. This will result in a particles.star file that is suitable for low-resolution subtomogram averaging and a particles_for_class2d.star file that can be used for fast 2D classification in order to select good particles. You can re-launch the GUI without the --tomo argument to get access to the Class2D job type. If set to No, the program will write out normal windowed tilt-series images that are suitable for high-resolution subtomogram averaging."); + joboptions["do_stack2d"] = JobOption("Output 2D stacks?", true ,"If set to Yes, this program will write output subtomograms as 2D substacks. This is new in relion-5, and the preferred way of generating subtomograms."); + joboptions["do_pseudo3d"] = JobOption("OR: output 3D pseudo-subtomograms?", false ,"If set to Yes, this program will write output 3D pseudo-subtomograms as were defined in relion-4."); + joboptions["do_real_subtomo"] = JobOption("OR: output real 3D subtomograms?", false, "If set to Yes, this program will box out real subtomograms from the input tomogram and write those out as 3D subvolumes. Information about the 3D CTFs and missing wedge will be stored in ctf volumes, which can be used as they were in relion-3. Note that the binning cannot be changed anymore, so particles will be extracted at the pixel size of the tomogram, using the cropped box size in Angstrom to define the box size."); joboptions["do_float16"] = JobOption("Write output in float16?", true ,"If set to Yes, this program will write output images in float16 MRC format. This will save a factor of two in disk space compared to the default of writing in float32. Note that RELION and CCPEM will read float16 images, but other programs may not (yet) do so."); @@ -7127,11 +7130,11 @@ bool RelionJob::getCommandsTomoSubtomoJob(std::string &outputname, std::vector 1) { - error_message = "You have to choose either to write output as 2D stacks, to extract real subtomos, or to extract and reproject real subtomos."; + error_message = "You have to choose either to write output as 2D stacks, 3D pseudo-subtomos, or to extract real subtomograms from the tomographic reconstruction."; return false; } @@ -7153,14 +7156,9 @@ bool RelionJob::getCommandsTomoSubtomoJob(std::string &outputname, std::vector 0.) command += " --crop " + joboptions["crop_size"].getString(); - - command += " --bin " + joboptions["binning"].getString(); + command += " --box_size_A " + joboptions["box_size_angst"].getString(); + command += " --precrop_size_A " + joboptions["precrop_size_angst"].getString(); + command += " --binned_size_pix " + joboptions["binned_size_pix"].getString(); float max_dose = joboptions["max_dose"].getNumber(error_message); if (error_message != "") return false; @@ -7180,21 +7178,12 @@ bool RelionJob::getCommandsTomoSubtomoJob(std::string &outputname, std::vector Date: Thu, 19 Dec 2024 15:03:24 +0000 Subject: [PATCH 039/189] allow empty isVisible and look for ytilt, not nominal tilt --- src/jaz/tomography/tomogram_set.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jaz/tomography/tomogram_set.cpp b/src/jaz/tomography/tomogram_set.cpp index c6e4223da..dceb6c65d 100644 --- a/src/jaz/tomography/tomogram_set.cpp +++ b/src/jaz/tomography/tomogram_set.cpp @@ -688,18 +688,18 @@ int TomogramSet::getImageIndexWithSmallestVisibleTiltAngle(int index, std::vecto RFLOAT mindiff = 999.; int nr_idx = tomogramTables[index].numberOfObjects(); - if (nr_idx != isVisible.size()) REPORT_ERROR("BUG: incofrrect number of elements in isVisible vector..."); + if ( isVisible.size() > 0 && nr_idx != isVisible.size()) REPORT_ERROR("BUG: incofrrect number of elements in isVisible vector..."); int nr_invisible = 0; for (int idx = 0; idx < nr_idx; idx++) { - if (!isVisible[idx]) + if (isVisible.size() > 0 && !isVisible[idx]) { nr_invisible++; continue; } RFLOAT tilt; - tomogramTables[index].getValue(EMDL_TOMO_NOMINAL_TILT_STAGE_ANGLE, tilt, idx); + tomogramTables[index].getValue(EMDL_TOMO_YTILT, tilt, idx); if (fabs(tilt) < mindiff) { mindiff = fabs(tilt); From 8c73e47dfaf931814da860fe318a960cde1cd66d Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 19 Dec 2024 15:03:51 +0000 Subject: [PATCH 040/189] keep debugging for real_subtomos --- src/jaz/tomography/programs/subtomo.cpp | 112 ++++++++++++++---------- 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/src/jaz/tomography/programs/subtomo.cpp b/src/jaz/tomography/programs/subtomo.cpp index b49635309..a3cf6b160 100644 --- a/src/jaz/tomography/programs/subtomo.cpp +++ b/src/jaz/tomography/programs/subtomo.cpp @@ -363,6 +363,14 @@ void SubtomoProgram::writeParticleSet( outProj = outDir + "Subtomograms/" + tomogram.name + "/proj2d.mrcs"; } + + int least_tilt_frame; + if (do_real_subtomo) + { + std::vector dummy; + least_tilt_frame = tomogramSet.getImageIndexWithSmallestVisibleTiltAngle(t, dummy); + } + for (int p = 0; p < pc; p++) { const ParticleIndex part_id = particles[t][p]; @@ -404,9 +412,9 @@ void SubtomoProgram::writeParticleSet( copy.setImageFileNames(outData, outCtf, new_id); } + const d3Vector pos = particleSet.getPosition(part_id, tomogram.centre, apply_offsets); if (apply_offsets) { - const d3Vector pos = particleSet.getPosition(part_id, tomogram.centre, true); copy.setParticleOffset(new_id, d3Vector(0,0,0)); copy.setParticleCoordDecenteredPixel(new_id, pos, tomogram.centre, tiltSeriesPixelSize); } @@ -431,6 +439,13 @@ void SubtomoProgram::writeParticleSet( copy2d.partTable.addObject( copy.partTable.getObject(new_id.value) ); std::string outData2d = integerToString(p+1) + "@" + outProj; copy2d.partTable.setValue(EMDL_IMAGE_NAME, outData2d); + + // And get the defocus of the untilted image + CTF ctf = tomogram.getCtf(least_tilt_frame, pos); + copy2d.partTable.setValue(EMDL_CTF_DEFOCUSU, ctf.DeltafU); + copy2d.partTable.setValue(EMDL_CTF_DEFOCUSV, ctf.DeltafV); + copy2d.partTable.setValue(EMDL_CTF_DEFOCUS_ANGLE, ctf.azimuthal_angle); + } } else @@ -452,7 +467,7 @@ void SubtomoProgram::writeParticleSet( for (int og = 0; og < copy.numberOfOpticsGroups(); og++) { - bool is_premultiplied = (do_stack2d) ? do_ctf : true; + bool is_premultiplied = (do_stack2d) ? do_ctf : false; copy.optTable.setValue(EMDL_OPTIMISER_DATA_ARE_CTF_PREMULTIPLIED, is_premultiplied, og); int datadim = (do_stack2d) ? 2 : 3; copy.optTable.setValue(EMDL_IMAGE_DIMENSIONALITY, datadim, og); @@ -465,7 +480,6 @@ void SubtomoProgram::writeParticleSet( int mysize = (do_real_subtomo) ? cropSize_tomogram : cropSize; copy.optTable.setValue(EMDL_IMAGE_SIZE, mysize, og); - } if (copy.partTable.containsLabel(EMDL_IMAGE_COORD_X)) copy.partTable.deactivateLabel(EMDL_IMAGE_COORD_X); @@ -486,6 +500,9 @@ void SubtomoProgram::writeParticleSet( copy2d.optTable.clear(); copy2d.optTable = copy.optTable; + for (int og = 0; og < copy.numberOfOpticsGroups(); og++) + copy2d.optTable.setValue(EMDL_IMAGE_DIMENSIONALITY, 2, og); + copy2d.write(outDir + "particles_for_class2d.star"); } @@ -509,7 +526,7 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( // get decentered coordinates of the particle (in tilt series pixels) d3Vector pos = particleSet.getPosition(part_id, tomogram.centre, apply_offsets); // convert from tilt series pixel to tomogram pixel - pos *= tomogram.optics.pixelSize / tomogram_angpix; + d3Vector tomopos = pos * (tomogram.optics.pixelSize / tomogram_angpix); const float sign = flip_value ? -1.f : 1.f; @@ -534,9 +551,9 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( for (long int i= FIRST_XMIPP_INDEX(cropSize_tomogram); i<= LAST_XMIPP_INDEX(cropSize_tomogram); i++) for (long int j= FIRST_XMIPP_INDEX(cropSize_tomogram); j<= LAST_XMIPP_INDEX(cropSize_tomogram); j++) { - int kp = k + pos.z; - int ip = i + pos.y; - int jp = j + pos.x; + int kp = k + tomopos.z; + int ip = i + tomopos.y; + int jp = j + tomopos.x; long int r2 = k*k + i*i + j*j; if (r2 <= r2_max && jp > 0 && jp < XSIZE(recTomo) && @@ -566,9 +583,9 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( for (long int i= FIRST_XMIPP_INDEX(cropSize_tomogram); i<= LAST_XMIPP_INDEX(cropSize_tomogram); i++) for (long int j= FIRST_XMIPP_INDEX(cropSize_tomogram); j<= LAST_XMIPP_INDEX(cropSize_tomogram); j++) { - int kp = k + pos.z; - int ip = i + pos.y; - int jp = j + pos.x; + int kp = k + tomopos.z; + int ip = i + tomopos.y; + int jp = j + tomopos.x; long int r2 = k*k + i*i + j*j; if (r2 <= r2_max && jp > 0 && jp < XSIZE(recTomo) && @@ -592,25 +609,37 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( //exit(0); // Make 2D projection along Z-axis - projImage.resize(cropSize_tomogram, cropSize_tomogram); - FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY3D(subtom) - { - projImage(i, j) += DIRECT_A3D_ELEM(subtom, k, i, j); - } - - BufferedImage resultImg; - resultImg.resize(cropSize_tomogram, cropSize_tomogram, cropSize_tomogram); + projImage.resize(cropSize_tomogram, cropSize_tomogram); ctfImage.resize(cropSize_tomogram / 2 + 1, cropSize_tomogram, cropSize_tomogram); FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY3D(subtom) { + projImage(i, j) += DIRECT_A3D_ELEM(subtom, k, i, j); resultImg(j, i, k) = DIRECT_A3D_ELEM(subtom, k, i, j); } + // Normalise the projected image to zero-mean and unity-stddev + RFLOAT psum = 0., psum2 = 0., pn = 0.; + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(projImage) + { + sum += projImage(i, j); + sum2 += projImage(i, j) * projImage(i, j); + pn += 1.; + } + sum /= pn; + sum2 = sqrt( (sum2 / pn) - sum * sum); + if (sum2 > 0.) + { + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(projImage) + { + projImage(i, j) = (projImage(i, j) - sum) / sum2; + } + } + + // Now make 3D-CTF and store in ctfImage BackProjector backprojector(cropSize_tomogram, 3, "C1", TRILINEAR, 1, 10, 0, 1.9, 15., 2, true); - backprojector.initZeros(); MultidimArray Fw(cropSize_tomogram, cropSize_tomogram/2 + 1), Fctf(cropSize_tomogram, cropSize_tomogram/2 + 1); @@ -618,6 +647,8 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( F2D.initZeros(cropSize_tomogram, cropSize_tomogram/2 + 1); Fw.initConstant(1.); + BufferedImage doseWeights = tomogram.computeDoseWeight(cropSize_tomogram, binning); + for (int f = 0; f < tomogram.frameCount; f++) { @@ -627,32 +658,16 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( for (int col = 0; col < 3; col++) MAT_ELEM(A, row, col) = Aproj(row, col); - // Get the CTF, as per Tanmay's paper on subtomogram averaging in relion-3 - RFLOAT xtilt, ytilt, zrot, xshift_angst, yshift_angst; - tomogram.getProjectionAnglesFromMatrix(f, Aproj, xtilt, ytilt, zrot, xshift_angst, yshift_angst); - CTF ctf = tomogram.getCtf(f, pos); - ctf.Bfac = 4.0 * tomogram.getCumulativeDose(f); - ctf.scale = COSD(ytilt); - ctf.initialise(); - ctf.getFftwImage(Fctf, cropSize_tomogram, cropSize_tomogram, tomogram_angpix, - false, false, false, true); - - /* - std::cerr << " f= " << f << " A= " << A << " ytilt= " << ytilt << " ctf.scale= " << ctf.scale << " tomogram_angpix= " << tomogram_angpix << std::endl; - std::cerr << "ctf.defocusU= " << ctf.DeltafU << " ctf.Bfac= " << ctf.Bfac << " pos= " << pos.x <<" "<< pos.y << std::endl; - Image It; - It()= Fctf; - It.write("Fctf.spi"); - std::cerr << " pres any key to continue..."<< std::endl; - char c; - std::cin >> c; - */ - - FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(Fctf) + BufferedImage ctfImg(cropSize_tomogram/2 + 1, cropSize_tomogram); + ctf.draw(cropSize_tomogram, cropSize_tomogram, tomogram_angpix, 0, &ctfImg(0, 0, 0)); + + FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(Fctf) { - DIRECT_MULTIDIM_ELEM(F2D, n) = DIRECT_MULTIDIM_ELEM(Fctf, n) * DIRECT_MULTIDIM_ELEM(Fctf, n); + RFLOAT c = ctfImg(j, i) * doseWeights(j, i, f); + DIRECT_A2D_ELEM(F2D, i, j) = c * c; } + backprojector.set2DFourierTransform(F2D, A, &Fw); } @@ -679,10 +694,14 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( ip >= STARTINGY(backprojector.data) && ip <= FINISHINGY(backprojector.data) && kp >= STARTINGZ(backprojector.data) && kp <= FINISHINGZ(backprojector.data)) { - if (A3D_ELEM(backprojector.weight, kp, ip, jp) > 0.) + if (A3D_ELEM(backprojector.weight, kp, ip, jp) > 0.01) { A3D_ELEM(CTF3D, k, i, j) = sqrt(A3D_ELEM(backprojector.data, kp, ip, jp) / A3D_ELEM(backprojector.weight, kp, ip, jp)); } + else + { + A3D_ELEM(CTF3D, k, i, j) = 0.; + } } } @@ -780,7 +799,7 @@ void SubtomoProgram::processTomograms( particleSet.checkTrajectoryLengths(particles[t], fc, "subtomo"); - BufferedImage doseWeights = tomogram.computeDoseWeight(s2D, binning); + BufferedImage doseWeights = tomogram.computeDoseWeight(s2D, binning); BufferedImage noiseWeights; if (do_whiten) @@ -865,9 +884,6 @@ void SubtomoProgram::processTomograms( if (do_real_subtomo) { - // Reste isVisible to all ones - std::fill(isVisible.begin(), isVisible.end(), true); - // This will extract the true subtomograms and calculate their FTs in the directions of the tilt series BufferedImage ctfImage, projImage; BufferedImage subtomo = extractRealSubtomograms(part_id, recTomo(), @@ -875,7 +891,7 @@ void SubtomoProgram::processTomograms( ctfImage, projImage); subtomo.write(outData, tomogram_angpix, write_float16); - if (do_real_subtomo) ctfImage.write(outCtf, tomogram_angpix, write_float16); + ctfImage.write(outCtf, tomogram_angpix, write_float16); NewStackHelper::insertSliceZ(projImage, proj2d, p); } else From 0361793b00c377d04d0db90cae6bb65d7e57bdcb Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 19 Dec 2024 15:04:17 +0000 Subject: [PATCH 041/189] dont allow ignoring CTF until first peak for tomography --- src/gui_jobwindow.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 48dc8ce75..bc42398b1 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -1407,7 +1407,8 @@ void JobWindow::initialiseInimodelWindow() place("do_ctf_correction", TOGGLE_DEACTIVATE, group1); group1->begin(); - place("ctf_intact_first_peak", TOGGLE_DEACTIVATE); + if (is_tomo) place("ctf_intact_first_peak", TOGGLE_ALWAYS_DEACTIVATE); + else place("ctf_intact_first_peak", TOGGLE_DEACTIVATE); group1->end(); guientries["do_ctf_correction"].cb_menu_i(); // To make default effective @@ -1517,7 +1518,8 @@ void JobWindow::initialiseClass3DWindow() place("do_ctf_correction", TOGGLE_DEACTIVATE, group1); group1->begin(); - place("ctf_intact_first_peak", TOGGLE_DEACTIVATE); + if (is_tomo) place("ctf_intact_first_peak", TOGGLE_ALWAYS_DEACTIVATE); + else place("ctf_intact_first_peak", TOGGLE_DEACTIVATE); group1->end(); @@ -1720,7 +1722,8 @@ void JobWindow::initialiseAutorefineWindow() group1->begin(); - place("ctf_intact_first_peak", TOGGLE_DEACTIVATE); + if (is_tomo) place("ctf_intact_first_peak", TOGGLE_ALWAYS_DEACTIVATE); + else place("ctf_intact_first_peak", TOGGLE_DEACTIVATE); group1->end(); guientries["do_ctf_correction"].cb_menu_i(); // To make default effective From 7b39885f661adf526e9f93d1a04f86c35a51de70 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 19 Dec 2024 15:04:39 +0000 Subject: [PATCH 042/189] dont always add --ctf to tomogram reconstruction --- src/pipeline_jobs.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 861c6382a..2be2117ee 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -6795,11 +6795,6 @@ bool RelionJob::getCommandsTomoReconstructTomogramsJob(std::string &outputname, command += " --skip_fourier_wiener "; } } - else - { - // Let's do CTF-premultiplication by default, in case people want to do real subtomogram averaging afterwards - command += " --ctf "; - } if (joboptions["do_proj"].getBoolean()) { From a96e1c94e2ec46421d2fec31ecd8228c41199f1e Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 19 Dec 2024 16:06:14 +0000 Subject: [PATCH 043/189] repiared bug in normalisation proj2d.mrcs --- src/jaz/tomography/programs/subtomo.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jaz/tomography/programs/subtomo.cpp b/src/jaz/tomography/programs/subtomo.cpp index a3cf6b160..bdc105216 100644 --- a/src/jaz/tomography/programs/subtomo.cpp +++ b/src/jaz/tomography/programs/subtomo.cpp @@ -623,20 +623,20 @@ BufferedImage SubtomoProgram::extractRealSubtomograms( RFLOAT psum = 0., psum2 = 0., pn = 0.; FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(projImage) { - sum += projImage(i, j); - sum2 += projImage(i, j) * projImage(i, j); + psum += projImage(i, j); + psum2 += projImage(i, j) * projImage(i, j); pn += 1.; } - sum /= pn; - sum2 = sqrt( (sum2 / pn) - sum * sum); - if (sum2 > 0.) + psum /= pn; + psum2 = sqrt( (psum2 / pn) - psum * psum); + if (psum2 > 0.) { FOR_ALL_DIRECT_ELEMENTS_IN_ARRAY2D(projImage) { - projImage(i, j) = (projImage(i, j) - sum) / sum2; + projImage(i, j) = (projImage(i, j) - psum) / psum2; } } - + // Now make 3D-CTF and store in ctfImage BackProjector backprojector(cropSize_tomogram, 3, "C1", TRILINEAR, 1, 10, 0, 1.9, 15., 2, true); From 42d9e66a4be6019f62269247295ceb1c409069f2 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 20 Dec 2024 11:54:33 +0000 Subject: [PATCH 044/189] added option to reconstruct tomograms with aretomo to aligntiltseries job --- src/align_tiltseries_runner.cpp | 65 ++++++++++++++++++++++++++++----- src/align_tiltseries_runner.h | 15 ++++++++ src/gui_jobwindow.cpp | 11 ++++++ src/pipeline_jobs.cpp | 19 +++++++++- 4 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/align_tiltseries_runner.cpp b/src/align_tiltseries_runner.cpp index 4e7eb22a8..6dd6b4a0e 100644 --- a/src/align_tiltseries_runner.cpp +++ b/src/align_tiltseries_runner.cpp @@ -47,10 +47,16 @@ void AlignTiltseriesRunner::read(int argc, char **argv, int rank) aretomo_tilcorrect_angle = textToFloat(parser.getOption("--aretomo_tiltcorrect_angle", "User-specified tilt angle correction (value > 180, means estimate automatically", "999.")); do_aretomo_ctf = parser.checkOption("--aretomo_ctf", "Perform CTF estimation in AreTomo? (default=false)"); do_aretomo_phaseshift = parser.checkOption("--aretomo_phaseshift", "Perform CTF estimation in AreTomo? (default=false)"); + do_aretomo_reconstruct = parser.checkOption("--aretomo_reconstruct", "Perform Tomogram reconstruction (WBP) in AreTomo? (default=false)"); + aretomo_OutBin = textToInteger(parser.getOption("--aretomo_OutBin", "Binning for the output reconstruction by AreTomo", "4")); + aretomo_VolZ = textToInteger(parser.getOption("--aretomo_VolZ", "Height of the output tomogram reconstruction (in unbinned voxels)", "0")); gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread, e.g 0:1:2:3", ""); int exp_section = parser.addSection("Expert options"); other_wrapper_args = parser.getOption("--other_wrapper_args", "Additional command-line arguments that will be passed onto the wrapper.", ""); + do_aretomo_sart = parser.checkOption("--aretomo_sart", "Use SART instead of weighted backprojection for aretomo reconstructions."); + aretomo_sart_iter = textToInteger(parser.getOption("--aretomo_sart_iter", "Number of iterations for aretomo SART reconstruction", "20")); + aretomo_sart_proj = textToInteger(parser.getOption("--aretomo_sart_proj", "Number of projections per iterations for aretomo SART reconstruction", "5")); // Initialise verb for non-parallel execution verb = 1; @@ -260,15 +266,12 @@ bool AlignTiltseriesRunner::checkResults(long idx_tomo) // check that .aln (and _ctf.txt if do_aretomo_ctf) file(s) has been written out FileName fn_aln = fn_dir + tomoname + ".aln"; FileName fn_ctf = fn_dir + tomoname + "_ctf.txt"; + FileName fn_tomo = fn_out + "tomograms/rec_" + tomoname + ".mrc"; - if (do_aretomo_ctf) - { - return (exists(fn_ctf) && exists(fn_aln)); - } - else - { - return exists(fn_aln); - } + bool has_required = exists(fn_aln); + if (has_required && do_aretomo_ctf) has_required = exists(fn_ctf); + if (has_required && do_aretomo_reconstruct) has_required = exists(fn_tomo); + return has_required; } else @@ -456,7 +459,23 @@ void AlignTiltseriesRunner::executeAreTomo(long idx_tomo, int rank) // Tomogram thickness should be in unbinned pixels command += " -AlignZ " + floatToString(thickness_pix); // Skip reconstruction of the tomogram in AreTomo... - command += " -volZ 0"; + if (do_aretomo_reconstruct) + { + command += " -FlipVol 1 -volZ " + integerToString(aretomo_VolZ); + command += " -OutBin " + integerToString(aretomo_OutBin); + if (do_aretomo_sart) + { + command += " -Sart " + integerToString(aretomo_sart_iter) + " " + integerToString(aretomo_sart_proj); + } + else + { + command += " -Wbp 1"; + } + } + else + { + command += " -volZ 0"; + } if (tomogramSet.tomogramTables[idx_tomo].containsLabel(EMDL_TOMO_NOMINAL_TILT_AXIS_ANGLE)) { @@ -838,6 +857,31 @@ bool AlignTiltseriesRunner::readAreTomoResults(long idx_tomo, std::string &error } } + if (do_aretomo_reconstruct) + { + std::string tomoname = tomogramSet.getTomogramName(idx_tomo); + FileName fn_inmap = fn_out + "external/" + tomoname + "/" + tomoname + "_aligned.mrc"; + FileName fn_tomodir = fn_out + "tomograms/"; + if (!exists(fn_tomodir)) mktree(fn_tomodir); + FileName fn_outmap = fn_tomodir + "rec_" + tomoname + ".mrc"; + std::string command = "mv -f " + fn_inmap + " " + fn_outmap; + int res = system(command.c_str()); + + // Read image size from header (don't read data!) + Image Itomo; + Itomo.read(fn_outmap, false); + // Set information about the tomogram in the globalTable + float floatbin = aretomo_OutBin; + int xs = aretomo_OutBin*XSIZE(Itomo()); + int ys = aretomo_OutBin*YSIZE(Itomo()); + int zs = aretomo_OutBin*ZSIZE(Itomo()); + tomogramSet.globalTable.setValue(EMDL_TOMO_TOMOGRAM_BINNING, floatbin, idx_tomo); + tomogramSet.globalTable.setValue(EMDL_TOMO_SIZE_X, xs, idx_tomo); + tomogramSet.globalTable.setValue(EMDL_TOMO_SIZE_Y, ys, idx_tomo); + tomogramSet.globalTable.setValue(EMDL_TOMO_SIZE_Z, zs, idx_tomo); + tomogramSet.globalTable.setValue(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_FILE_NAME, fn_outmap, idx_tomo); + + } MetaDataTable MDnew; for (int i = 0; i < rot.size(); i++) @@ -946,7 +990,8 @@ void AlignTiltseriesRunner::joinResults() } } - tomogramSet.write(fn_out+"aligned_tilt_series.star"); + FileName fnt = (do_aretomo && do_aretomo_reconstruct) ? fn_out+"tomograms.star" : fn_out+"aligned_tilt_series.star"; + tomogramSet.write(fnt); if (do_aretomo && do_aretomo_ctf) { diff --git a/src/align_tiltseries_runner.h b/src/align_tiltseries_runner.h index 346ff0be9..64af84658 100644 --- a/src/align_tiltseries_runner.h +++ b/src/align_tiltseries_runner.h @@ -157,6 +157,21 @@ class AlignTiltseriesRunner // Do phase shift estimation in AreTomo? bool do_aretomo_phaseshift; + // Do AreTomo reconstruction? + bool do_aretomo_reconstruct; + + // By default use Wbp in Aretomo, but can also use SART + bool do_aretomo_sart; + + // Number of SART iterations and the number of projections per iteration + int aretomo_sart_iter, aretomo_sart_proj; + + // AreTomo reconstruction Z-height (in unbinned pixels) + int aretomo_VolZ; + + // Aretomo reconstruction binning + int aretomo_OutBin; + // estimated tomogram thickness (for -AlignZ) RFLOAT tomogram_thickness; diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index bc42398b1..67f72916a 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -2697,6 +2697,17 @@ void JobWindow::initialiseTomoAlignTiltseriesWindow() group5->end(); guientries["do_aretomo_ctf"].cb_menu_i(); // make default active + // Add a little spacer + current_y += STEPY/2; + group6 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group6->end(); + place("do_aretomo_reconstruct", TOGGLE_DEACTIVATE, group6, false); + group6->begin(); + place("aretomo_VolZ", TOGGLE_DEACTIVATE); + place("aretomo_OutBin", TOGGLE_DEACTIVATE); + group6->end(); + guientries["do_aretomo_reconstruct"].cb_menu_i(); // make default active + // Add a little spacer current_y += STEPY/2; place("other_aretomo_args", TOGGLE_DEACTIVATE); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 2be2117ee..84447587f 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -6607,6 +6607,12 @@ void RelionJob::initialiseTomoAlignTiltSeriesJob() joboptions["do_aretomo_phaseshift"] = JobOption("Also estimate phase shift? ", false, "If set to Yes, AreTomo2 will also perform estimation of the phase shift (due to a phase plate) during CTF estimation."); joboptions["do_aretomo_tiltcorrect"] = JobOption("Correct Tilt Angle Offset?", false, "Specify Yes to correct the tilt angle offset in the tomogram (applies the AreTomo -TiltCor option). This is useful for correcting slanting in tomograms which can arise due to sample mounting or milling angle. This can be useful for in situ data."); joboptions["aretomo_tiltcorrect_angle"] = JobOption("Tilt Angle Offset:", 999 , -50, 50, 5, "The tilt angle (in degrees) to be offset. If set to a value larger than 180, AreTomo will search for the optimal value itself, otherwise the value specified here will be used."); + + + joboptions["do_aretomo_reconstruct"] = JobOption("Also reconstruct tomograms? ", false, "If set to Yes, AreTomo2 will also perform tomogram reconstruction (the default is to use weighted backprojection, but you can use SART by providing the additional argument --aretomo_sart."); + joboptions["aretomo_VolZ"] = JobOption("Tomogram thickness (in unbinned voxels):", 1000 , 50, 5000, 50, "The tomogram will be reconstructed to this thickness, using the -VolZ parameter from AreTomo."); + joboptions["aretomo_OutBin"] = JobOption("Tomogram binning:", 4 , 1, 20, 1, "The tomogram will be reconstructed with this integer binning factor, using the -OutBin parameter from AreTomo."); + joboptions["other_aretomo_args"] = JobOption("Other AreTomo2 arguments", std::string(""), "Additional arguments that need to be passed to AreTomo2."); joboptions["gpu_ids"] = JobOption("Which GPUs to use for AreTomo:", std::string(""), "Provide a list of which GPUs (e.g. 0:1:2:3) to use in AreTomo2. MPI-processes are separated by ':'. For example, to place one rank on device 0 and one rank on device 1, provide '0:1'."); @@ -6685,6 +6691,15 @@ bool RelionJob::getCommandsTomoAlignTiltSeriesJob(std::string &outputname, std:: } + if (joboptions["do_aretomo_reconstruct"].getBoolean()) + { + command += " --aretomo_reconstruct "; + command += " --aretomo_VolZ " + joboptions["aretomo_VolZ"].getString(); + command += " --aretomo_OutBin " + joboptions["aretomo_OutBin"].getString(); + + } + + command += " --other_wrapper_args \" " + joboptions["other_aretomo_args"].getString() + " \""; command += " --gpu " + joboptions["gpu_ids"].getString() + ' '; @@ -6698,7 +6713,9 @@ bool RelionJob::getCommandsTomoAlignTiltSeriesJob(std::string &outputname, std:: Node node(joboptions["in_tiltseries"].getString(), joboptions["in_tiltseries"].node_type); inputNodes.push_back(node); - Node node2(outputname+"aligned_tilt_series.star", LABEL_TILTALIGN_TOMOGRAMS); + FileName fn_output = (joboptions["do_aretomo2"].getBoolean() && joboptions["do_aretomo_reconstruct"].getBoolean()) ? + outputname+"tomograms.star" : outputname+"aligned_tilt_series.star"; + Node node2(fn_output, LABEL_TILTALIGN_TOMOGRAMS); outputNodes.push_back(node2); Node node3(outputname + "logfile.pdf", LABEL_TILTALIGN_LOG); From 54a01ee8bb685c3f0c0d3a63a5cc252b9cd792ca Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 20 Dec 2024 14:07:12 +0000 Subject: [PATCH 045/189] set name of table in subset selection --- src/metadata_table.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/metadata_table.cpp b/src/metadata_table.cpp index 6e4b42cdd..400661140 100644 --- a/src/metadata_table.cpp +++ b/src/metadata_table.cpp @@ -2036,6 +2036,7 @@ MetaDataTable subsetMetaDataTable(MetaDataTable &MDin, EMDLabel label, RFLOAT mi REPORT_ERROR("subsetMetadataTable ERROR: input MetaDataTable does not contain label: " + EMDL::label2Str(label)); MetaDataTable MDout; + MDout.setName(MDin.getName()); FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDin) { bool do_include = false; From 8a76f6ae7594eb2b4207a2a8bc35afeb702f13ae Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 20 Dec 2024 16:27:22 +0000 Subject: [PATCH 046/189] add option to reconstruct only in aretomo2, generating .aln file from input star file --- src/align_tiltseries_runner.cpp | 170 +++++++++++++++++++++----------- src/align_tiltseries_runner.h | 5 +- src/gui_jobwindow.cpp | 1 + src/pipeline_jobs.cpp | 5 +- 4 files changed, 119 insertions(+), 62 deletions(-) diff --git a/src/align_tiltseries_runner.cpp b/src/align_tiltseries_runner.cpp index 6dd6b4a0e..93b172bc9 100644 --- a/src/align_tiltseries_runner.cpp +++ b/src/align_tiltseries_runner.cpp @@ -46,8 +46,10 @@ void AlignTiltseriesRunner::read(int argc, char **argv, int rank) do_aretomo_tiltcorrect = parser.checkOption("--aretomo_tiltcorrect", "Specify to correct the tilt angle offset in the tomogram (AreTomo -TiltCor option; default=false)"); aretomo_tilcorrect_angle = textToFloat(parser.getOption("--aretomo_tiltcorrect_angle", "User-specified tilt angle correction (value > 180, means estimate automatically", "999.")); do_aretomo_ctf = parser.checkOption("--aretomo_ctf", "Perform CTF estimation in AreTomo? (default=false)"); - do_aretomo_phaseshift = parser.checkOption("--aretomo_phaseshift", "Perform CTF estimation in AreTomo? (default=false)"); - do_aretomo_reconstruct = parser.checkOption("--aretomo_reconstruct", "Perform Tomogram reconstruction (WBP) in AreTomo? (default=false)"); + do_aretomo_phaseshift = parser.checkOption("--aretomo_phaseshift", "Perform CTF estimation in AreTomo?"); + do_aretomo_reconstruct = parser.checkOption("--aretomo_reconstruct", "Perform Tomogram reconstruction (WBP) in AreTomo?"); + do_only_aretomo_reconstruct = parser.checkOption("--aretomo_only_reconstruct", "Only perform Tomogram reconstruction in AreTomo, and skip alignment/CTF estimation?"); + if (do_only_aretomo_reconstruct) do_aretomo_reconstruct = true; aretomo_OutBin = textToInteger(parser.getOption("--aretomo_OutBin", "Binning for the output reconstruction by AreTomo", "4")); aretomo_VolZ = textToInteger(parser.getOption("--aretomo_VolZ", "Height of the output tomogram reconstruction (in unbinned voxels)", "0")); gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread, e.g 0:1:2:3", ""); @@ -287,44 +289,82 @@ bool AlignTiltseriesRunner::checkResults(long idx_tomo) return false; } -void AlignTiltseriesRunner::generateMRCStackAndRawTiltFile(long idx_tomo, bool is_aretomo) +void AlignTiltseriesRunner::generateMRCStackAndRawTiltFileOrAlnFile(long idx_tomo, bool is_aretomo) { FileName fn_dir = fn_out + "external/" + tomogramSet.getTomogramName(idx_tomo) + '/'; - FileName fn_tilt = fn_dir + tomogramSet.getTomogramName(idx_tomo) + ".rawtlt"; - FileName fn_series = fn_dir + tomogramSet.getTomogramName(idx_tomo) + ".mrc"; - std::ofstream fh; - fh.open((fn_tilt).c_str(), std::ios::out); + // Write out tilt series stack Image Imic, Iseries; - std::vector frame_dose_order = tomogramSet.getFrameDoseOrder(idx_tomo); - + RFLOAT angpix = tomogramSet.getTiltSeriesPixelSize(idx_tomo); int fc = tomogramSet.tomogramTables[idx_tomo].numberOfObjects(); for (int f = 0; f < fc; f++) { FileName fn_mic; - RFLOAT tiltangle; tomogramSet.tomogramTables[idx_tomo].getValue(EMDL_MICROGRAPH_NAME, fn_mic, f); - tomogramSet.tomogramTables[idx_tomo].getValue(EMDL_TOMO_NOMINAL_TILT_STAGE_ANGLE, tiltangle, f); - RFLOAT dose; - tomogramSet.tomogramTables[idx_tomo].getValue(EMDL_MICROGRAPH_PRE_EXPOSURE, dose, f); - // IMOD: 1-column and AreTomo2 2-column raw tilt angle file with tilt angles (and order of acquisition). - fh << tiltangle; - if (is_aretomo) fh << " " << frame_dose_order[f]; - fh << std::endl; Imic.read(fn_mic); if (f == 0) Iseries().resize(fc, YSIZE(Imic()), XSIZE(Imic())); Iseries().setSlice(f, Imic()); - } - RFLOAT angpix = tomogramSet.getTiltSeriesPixelSize(idx_tomo); tomogramSet.globalTable.getDouble(EMDL_TOMO_IMPORT_FRACT_DOSE, idx_tomo); if (is_aretomo) Iseries.setSamplingRateInHeader(angpix); + FileName fn_series = fn_dir + tomogramSet.getTomogramName(idx_tomo) + ".mrc"; Iseries.write(fn_series); - fh.close(); + // Write out text file with rawtilt or aln + std::ofstream fh; + if (do_only_aretomo_reconstruct) + { + // make aln file + FileName fn_aln = fn_dir + tomogramSet.getTomogramName(idx_tomo) + ".aln"; + fh.open((fn_aln).c_str(), std::ios::out); + + std::vector frame_tilt_order_index = tomogramSet.getFrameTiltOrderIndex(idx_tomo); + + fh << "# AreTomo Alignment / Priims bprmMn \n"; + fh << "# RawSize = " << integerToString(XSIZE(Iseries())) << " " << integerToString(YSIZE(Iseries())) << " " << integerToString(ZSIZE(Iseries())) <<"\n"; + fh << "# NumPatches = 0\n"; + fh << "# SEC ROT GMAG TX TY SMEAN SFIT SCALE BASE TILT\n"; + for (int f = 0; f < fc; f++) + { + int fp = frame_tilt_order_index[f]; + RFLOAT xtilt=0., ytilt, zrot, xshift, yshift; + tomogramSet.tomogramTables[idx_tomo].getValue(EMDL_TOMO_YTILT, ytilt, fp); + tomogramSet.tomogramTables[idx_tomo].getValue(EMDL_TOMO_ZROT, zrot, fp); + tomogramSet.tomogramTables[idx_tomo].getValue(EMDL_TOMO_XSHIFT_ANGST, xshift, fp); + tomogramSet.tomogramTables[idx_tomo].getValue(EMDL_TOMO_YSHIFT_ANGST, yshift, fp); + //AreTomo shifts are in pixels, bnot Angstroms! + xshift /= angpix; + yshift /= angpix; + + char buffer[256]; + std::snprintf(buffer, sizeof(buffer), + "%5d %9.4f %9.5f %9.3f %9.3f %7.2f %7.2f %7.2f %7.2f %8.2f\n", + fp, zrot, 1., xshift, yshift, 1., 1., 1., 0., ytilt); + fh << buffer; + } + } + else + { + // make rawtlt file + FileName fn_tilt = fn_dir + tomogramSet.getTomogramName(idx_tomo) + ".rawtlt"; + fh.open((fn_tilt).c_str(), std::ios::out); + std::vector frame_dose_order = tomogramSet.getFrameDoseOrder(idx_tomo); + for (int f = 0; f < fc; f++) + { + RFLOAT tiltangle; + tomogramSet.tomogramTables[idx_tomo].getValue(EMDL_TOMO_NOMINAL_TILT_STAGE_ANGLE, tiltangle, f); + RFLOAT dose; + tomogramSet.tomogramTables[idx_tomo].getValue(EMDL_MICROGRAPH_PRE_EXPOSURE, dose, f); + // IMOD: 1-column and AreTomo2 2-column raw tilt angle file with tilt angles (and order of acquisition). + fh << tiltangle; + if (is_aretomo) fh << " " << frame_dose_order[f]; + fh << std::endl; + } + } + fh.close(); } @@ -392,7 +432,7 @@ void AlignTiltseriesRunner::executeIMOD(long idx_tomo, int rank) // Make sure metadata table is sorted on rlnTomoNominalStageTiltAngle (it should be, but anyways...) tomogramSet.tomogramTables[idx_tomo].newSort(EMDL_TOMO_NOMINAL_TILT_STAGE_ANGLE); - generateMRCStackAndRawTiltFile(idx_tomo, false); + generateMRCStackAndRawTiltFileOrAlnFile(idx_tomo, false); // Now run the actual IMOD command std::string command = fn_batchtomo_exe; @@ -433,14 +473,12 @@ void AlignTiltseriesRunner::executeAreTomo(long idx_tomo, int rank) FileName fn_ali = fn_dir + tomoname + "_aligned.mrc"; FileName fn_log = fn_dir + tomoname + ".log"; FileName fn_com = fn_dir + tomoname + ".com"; + FileName fn_aln = fn_dir + tomoname + ".aln"; - std::ofstream fh; - fh.open((fn_tilt).c_str(), std::ios::out); - Image Imic, Iseries; - int fc = tomogramSet.tomogramTables[idx_tomo].numberOfObjects(); - std::vector frame_dose_order = tomogramSet.getFrameDoseOrder(idx_tomo); + // Make sure metadata table is sorted on rlnTomoNominalStageTiltAngle (it should be, but anyways...) + tomogramSet.tomogramTables[idx_tomo].newSort(EMDL_TOMO_NOMINAL_TILT_STAGE_ANGLE); - generateMRCStackAndRawTiltFile(idx_tomo, true); + generateMRCStackAndRawTiltFileOrAlnFile(idx_tomo, true); RFLOAT frac_dose = tomogramSet.globalTable.getDouble(EMDL_TOMO_IMPORT_FRACT_DOSE, idx_tomo); RFLOAT pixel_size = tomogramSet.getTiltSeriesPixelSize(idx_tomo); @@ -453,12 +491,20 @@ void AlignTiltseriesRunner::executeAreTomo(long idx_tomo, int rank) // Now run the actual AreTomo command std::string command = fn_aretomo_exe + " "; command += " -InMrc " + fn_series; - command += " -AngFile " + fn_tilt; command += " -OutMrc " + fn_ali; - command += " -ImgDose " + floatToString(frac_dose); - // Tomogram thickness should be in unbinned pixels - command += " -AlignZ " + floatToString(thickness_pix); - // Skip reconstruction of the tomogram in AreTomo... + + if (do_only_aretomo_reconstruct) + { + command += " -Align 0 "; + command += " -AlnFile " + fn_aln; + } + else + { + command += " -AngFile " + fn_tilt; + command += " -ImgDose " + floatToString(frac_dose); + // Tomogram thickness should be in unbinned pixels + command += " -AlignZ " + floatToString(thickness_pix); + } if (do_aretomo_reconstruct) { command += " -FlipVol 1 -volZ " + integerToString(aretomo_VolZ); @@ -477,38 +523,42 @@ void AlignTiltseriesRunner::executeAreTomo(long idx_tomo, int rank) command += " -volZ 0"; } - if (tomogramSet.tomogramTables[idx_tomo].containsLabel(EMDL_TOMO_NOMINAL_TILT_AXIS_ANGLE)) - { - RFLOAT tiltaxis_angle = tomogramSet.tomogramTables[idx_tomo].getDouble(EMDL_TOMO_NOMINAL_TILT_AXIS_ANGLE, 0); - command += " -TiltAxis " + floatToString(tiltaxis_angle); - } - if (do_aretomo_tiltcorrect) - { - command += " -TiltCor 1 "; - if (aretomo_tilcorrect_angle < 180.) - command += floatToString(aretomo_tilcorrect_angle); - } - else + if (!do_only_aretomo_reconstruct) { - command += " -TiltCor -1 "; - } + if (tomogramSet.tomogramTables[idx_tomo].containsLabel(EMDL_TOMO_NOMINAL_TILT_AXIS_ANGLE)) + { + RFLOAT tiltaxis_angle = tomogramSet.tomogramTables[idx_tomo].getDouble(EMDL_TOMO_NOMINAL_TILT_AXIS_ANGLE, 0); + command += " -TiltAxis " + floatToString(tiltaxis_angle); + } + if (do_aretomo_tiltcorrect) + { + command += " -TiltCor 1 "; + if (aretomo_tilcorrect_angle < 180.) + command += floatToString(aretomo_tilcorrect_angle); + } + else + { + command += " -TiltCor -1 "; + } - if (do_aretomo_ctf) - { - // Also estimate CTF parameters in AreTomo - RFLOAT kV, Cs, Q0; - RFLOAT angpix = tomogramSet.getTiltSeriesPixelSize(idx_tomo); - tomogramSet.globalTable.getValue(EMDL_CTF_VOLTAGE, kV, idx_tomo); - tomogramSet.globalTable.getValue(EMDL_CTF_CS, Cs, idx_tomo); - tomogramSet.globalTable.getValue(EMDL_CTF_Q0, Q0, idx_tomo); - command += " -Kv " + floatToString(kV); - command += " -Cs " + floatToString(Cs); - command += " -AmpContrast " + floatToString(Q0); - command += " -PixSize " + floatToString(angpix); - if (do_aretomo_phaseshift) - command += " -ExtPhase 90 180"; + + if (do_aretomo_ctf) + { + // Also estimate CTF parameters in AreTomo + RFLOAT kV, Cs, Q0; + RFLOAT angpix = tomogramSet.getTiltSeriesPixelSize(idx_tomo); + tomogramSet.globalTable.getValue(EMDL_CTF_VOLTAGE, kV, idx_tomo); + tomogramSet.globalTable.getValue(EMDL_CTF_CS, Cs, idx_tomo); + tomogramSet.globalTable.getValue(EMDL_CTF_Q0, Q0, idx_tomo); + command += " -Kv " + floatToString(kV); + command += " -Cs " + floatToString(Cs); + command += " -AmpContrast " + floatToString(Q0); + command += " -PixSize " + floatToString(angpix); + if (do_aretomo_phaseshift) + command += " -ExtPhase 90 180"; + } } if (gpu_ids.length() > 0) diff --git a/src/align_tiltseries_runner.h b/src/align_tiltseries_runner.h index 64af84658..bc926e844 100644 --- a/src/align_tiltseries_runner.h +++ b/src/align_tiltseries_runner.h @@ -160,6 +160,9 @@ class AlignTiltseriesRunner // Do AreTomo reconstruction? bool do_aretomo_reconstruct; + //Skip alignment and CTF-estimation: only reconstruct: generate .aln file from input STAR file, which must contain tiltseries alignments + bool do_only_aretomo_reconstruct; + // By default use Wbp in Aretomo, but can also use SART bool do_aretomo_sart; @@ -206,7 +209,7 @@ class AlignTiltseriesRunner bool checkResults(long idx_tomo); // Generate MRC stack and raw tilt file - void generateMRCStackAndRawTiltFile(long idx_tomo, bool is_aretomo); + void generateMRCStackAndRawTiltFileOrAlnFile(long idx_tomo, bool is_aretomo); // Execute IMOD for a single tomogram void executeIMOD(long idx_tomo, int rank = 0); diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 67f72916a..c9c41b0a3 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -2703,6 +2703,7 @@ void JobWindow::initialiseTomoAlignTiltseriesWindow() group6->end(); place("do_aretomo_reconstruct", TOGGLE_DEACTIVATE, group6, false); group6->begin(); + place("do_skip_aretomo_align", TOGGLE_DEACTIVATE); place("aretomo_VolZ", TOGGLE_DEACTIVATE); place("aretomo_OutBin", TOGGLE_DEACTIVATE); group6->end(); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 84447587f..bc8ee3266 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -6609,7 +6609,8 @@ void RelionJob::initialiseTomoAlignTiltSeriesJob() joboptions["aretomo_tiltcorrect_angle"] = JobOption("Tilt Angle Offset:", 999 , -50, 50, 5, "The tilt angle (in degrees) to be offset. If set to a value larger than 180, AreTomo will search for the optimal value itself, otherwise the value specified here will be used."); - joboptions["do_aretomo_reconstruct"] = JobOption("Also reconstruct tomograms? ", false, "If set to Yes, AreTomo2 will also perform tomogram reconstruction (the default is to use weighted backprojection, but you can use SART by providing the additional argument --aretomo_sart."); + joboptions["do_aretomo_reconstruct"] = JobOption("Reconstruct tomograms? ", false, "If set to Yes, AreTomo2 will also perform tomogram reconstruction (the default is to use weighted backprojection, but you can use SART by providing the additional argument --aretomo_sart."); + joboptions["do_skip_aretomo_align"] = JobOption("Skip alignment and only reconstruct?", false, "If set to Yes, AreTomo2 alignment (and CTF estimation) will be skipped, and only tomogram reconstruction will be performed. For this to work, tilt series alignment parameters need to be present in the input tiltseries STAR file. This allows AreTomo2 reconstructions from IMOD alignments."); joboptions["aretomo_VolZ"] = JobOption("Tomogram thickness (in unbinned voxels):", 1000 , 50, 5000, 50, "The tomogram will be reconstructed to this thickness, using the -VolZ parameter from AreTomo."); joboptions["aretomo_OutBin"] = JobOption("Tomogram binning:", 4 , 1, 20, 1, "The tomogram will be reconstructed with this integer binning factor, using the -OutBin parameter from AreTomo."); @@ -6694,6 +6695,8 @@ bool RelionJob::getCommandsTomoAlignTiltSeriesJob(std::string &outputname, std:: if (joboptions["do_aretomo_reconstruct"].getBoolean()) { command += " --aretomo_reconstruct "; + if (joboptions["do_skip_aretomo_align"].getBoolean()) + command += " --aretomo_only_reconstruct "; command += " --aretomo_VolZ " + joboptions["aretomo_VolZ"].getString(); command += " --aretomo_OutBin " + joboptions["aretomo_OutBin"].getString(); From b101bd6f40769a3e84f01e5ec0843b7b2318e358 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 14 Jan 2025 10:12:16 +0000 Subject: [PATCH 047/189] removed suprious debugging statement --- src/amyloid_finder.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index db135f68d..fb3d1024a 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -1104,7 +1104,6 @@ void AmyloidFinder::finalise() MDcoords.write(fn_coords); FileName fn_mics = fn_odir + "micrographs_" + fn_out + ".star"; - std::cerr <<" fn_mics= " << fn_mics << std::endl; obsModel.save(MDin, fn_mics, "micrographs"); if (verb > 0 ) From c169b1e0cc3e805ce3dc0be3c4ead95e39a6d3db Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 14 Jan 2025 10:13:58 +0000 Subject: [PATCH 048/189] allow toggling of FOM-coluring and greyscale image --- src/displayer.cpp | 52 +++++++++++++++++++++++++++++++++++++++-------- src/displayer.h | 5 +++++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/displayer.cpp b/src/displayer.cpp index af32ac3a2..466bdfac7 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -41,7 +41,7 @@ const int NUM_COLORS = 6; /************************************************************************/ void DisplayBox::draw() { - if (!img_data) return; + if (!img_data) return; short xpos = x() + xoff; short ypos = y() + yoff; @@ -50,8 +50,18 @@ void DisplayBox::draw() //fl_push_clip(x(),y(),w(),h()); /* Redraw the whole image */ - int depth = (colour_scheme) ? 3 : 1; - fl_draw_image((const uchar *)img_data, xpos, ypos, (short)xsize_data, (short)ysize_data, depth); + int depth; + if (fom_is_grey_instead) + { + // Even though we colour the FOM image red/yellow, we can toggle to an only-grey micrograph! + depth = 1; + fl_draw_image((const uchar *)img_data2, xpos, ypos, (short)xsize_data, (short)ysize_data, depth); + } + else + { + depth = (colour_scheme) ? 3 : 1; + fl_draw_image((const uchar *)img_data, xpos, ypos, (short)xsize_data, (short)ysize_data, depth); + } if (img_label != "") { fl_color(FL_WHITE); @@ -149,8 +159,9 @@ void DisplayBox::setData(MultidimArray &img, MetaDataContainer *MDCin, i maxval = _maxval; ipos = _ipos; selected = NOTSELECTED; + fom_is_grey_instead = false; - // Set its own MetaDataTable + // Set its own MetaDataTable MDimg.setIsList(true); MDimg.addObject(MDCin); @@ -275,6 +286,7 @@ void DisplayBox::setData(MultidimArray &img, MultidimArray &fom_ maxval = _maxval; ipos = _ipos; selected = NOTSELECTED; + fom_is_grey_instead = false; if (fabs(_fom_min) < 0.001 && fabs(_fom_max) < 0.001 ) { @@ -316,7 +328,8 @@ void DisplayBox::setData(MultidimArray &img, MultidimArray &fom_ ysize_data = CEIL(YSIZE(img) * scale); xoff = (xsize_data < w() ) ? (w() - xsize_data) / 2 : 0; yoff = (ysize_data < h() ) ? (h() - ysize_data) / 2 : 0; - img_data = new unsigned char [3 * xsize_data * ysize_data]; + img_data = new unsigned char [3 * xsize_data * ysize_data]; + img_data2 = new unsigned char [xsize_data * ysize_data]; RFLOAT range = maxval - minval; RFLOAT step = range / 255; // 8-bit scaling range from 0 to 255 RFLOAT* old_ptr=NULL; @@ -357,8 +370,11 @@ void DisplayBox::setData(MultidimArray &img, MultidimArray &fom_ img_data[3*n] = FLOOR(127*val + 127*fom); img_data[3*n+1] = FLOOR(127*val + 127*(2*fom-1)); img_data[3*n+2] = FLOOR(127*val); - } + + // Also store greyscale image data in img_data2 + img_data2[n] = FLOOR((*old_ptr - minval) / step); + old_ptr += xstep; xerr -= xmod; if (xerr <= 0) @@ -376,7 +392,7 @@ void DisplayBox::setData(MultidimArray &img, MultidimArray &fom_ sy ++; } } - } + } else { FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(img) @@ -396,6 +412,9 @@ void DisplayBox::setData(MultidimArray &img, MultidimArray &fom_ img_data[3*n+2] = FLOOR(127*val); } + // Also store greyscale image data in img_data2 + img_data2[n] = FLOOR((DIRECT_MULTIDIM_ELEM(img, n) - minval) / step); + } } } @@ -2152,12 +2171,13 @@ int pickerViewerCanvas::handle(int ev) redraw(); Fl_Menu_Item rclick_menu[] = { { "Save STAR with coordinates (CTRL-s)" }, + { "Toggle FOM colouring (CTRL-f)" }, // { "Save_as STAR with coordinates" }, { "Load coordinates" }, { "Reload coordinates" }, { "Clear coordinates" }, { "Set selection type" }, - { "Set FOM threshold" }, + { "Set FOM threshold" }, { "Help" }, { "Quit (CTRL-q)" }, { 0 } @@ -2182,6 +2202,8 @@ int pickerViewerCanvas::handle(int ev) setFOMThreshold(); loadCoordinates(false); } + else if ( strcmp(m->label(), "Toggle FOM colouring (CTRL-f)") == 0) + toggleFomImageIsGreyInstead(); else if ( strcmp(m->label(), "Help") == 0 ) printHelp(); else if ( strcmp(m->label(), "Quit (CTRL-q)") == 0 ) @@ -2206,6 +2228,13 @@ int pickerViewerCanvas::handle(int ev) sleep(1); // to prevent multiple saves... dirty but don't know how to do this otherwise... return 1; // (tells caller we handled this event) } + if (key == 'f') + { + toggleFomImageIsGreyInstead(); + redraw(); + sleep(0.5); // to prevent multiple toggles? + return 1; // (tells caller we handled this event) + } else if (key == 'q') { sleep(1); @@ -2222,6 +2251,13 @@ int pickerViewerCanvas::handle(int ev) return 0; } +void pickerViewerCanvas::toggleFomImageIsGreyInstead() +{ + fom_is_grey_instead = !fom_is_grey_instead; + boxes[0]->redraw(); +} + + void pickerViewerCanvas::saveCoordinates(bool ask_filename) { FileName fn_out; diff --git a/src/displayer.h b/src/displayer.h index afda28221..244bd1497 100644 --- a/src/displayer.h +++ b/src/displayer.h @@ -74,6 +74,7 @@ static bool has_shift; static int preshift_ipos; static int current_selection_type; static int colour_scheme; +static int fom_is_grey_instead; class DisplayBox : public Fl_Box { @@ -98,6 +99,9 @@ class DisplayBox : public Fl_Box // The actual image data array unsigned char *img_data; + // A copy of the actual image data array (to allow toggling of FOM-colouring) + unsigned char *img_data2; + std::string img_label; // For getting back close the original image values from the uchar ones... @@ -431,6 +435,7 @@ class pickerViewerCanvas : public basisViewerCanvas private: // Functionalities for popup menu + void toggleFomImageIsGreyInstead(); void saveCoordinates(bool ask_filename = false); void clearCoordinates(); void printHelp(); From 039b984fa2c8b3feafb475649af2d384fdd5b483 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 14 Jan 2025 11:07:37 +0000 Subject: [PATCH 049/189] sleep one second to prevent nervous toggling on a mac for fom-colouring --- src/displayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/displayer.cpp b/src/displayer.cpp index 466bdfac7..f0e5fc2ae 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -2232,7 +2232,7 @@ int pickerViewerCanvas::handle(int ev) { toggleFomImageIsGreyInstead(); redraw(); - sleep(0.5); // to prevent multiple toggles? + sleep(1); // to prevent multiple toggles? return 1; // (tells caller we handled this event) } else if (key == 'q') From e3baabaf43a65d9854c1628c7bb31c4a1e875998 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 14 Jan 2025 11:33:42 +0000 Subject: [PATCH 050/189] put error message back for particles outside micrograph, and check this exception when using recenter --- src/preprocessing.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/preprocessing.cpp b/src/preprocessing.cpp index 47f52221c..7e0f0593a 100644 --- a/src/preprocessing.cpp +++ b/src/preprocessing.cpp @@ -1359,7 +1359,7 @@ MetaDataTable Preprocessing::getCoordinateMetaDataTable(FileName fn_mic) } RFLOAT mag2, dstep2, particle_angpix, rescale_fndata = 1.0; - if (MDresult.numberOfObjects() > 0) + if (MDresult.numberOfObjects() > 0) { MDresult.goToObject(0); @@ -1467,6 +1467,34 @@ MetaDataTable Preprocessing::getCoordinateMetaDataTable(FileName fn_mic) } // end if recenter } // end loop all objects + + // Check whether do_center has moved some particles outside the micrograph and remove those + if (do_recenter) + { + + Image Imic; + Imic.read(fn_mic, false); + + MetaDataTable MDcopy; + FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDresult) + { + int xcoord, ycoord, zcoord = 0; + MDresult.getValue(EMDL_IMAGE_COORD_X, xcoord); + MDresult.getValue(EMDL_IMAGE_COORD_Y, ycoord); + if (dimensionality == 3) + MDresult.getValue(EMDL_IMAGE_COORD_Z, zcoord); + + // Discard particles that are completely outside the micrograph and print a warning + if (ycoord >= 0 && ycoord < YSIZE(Imic()) && xcoord >= 0 && xcoord < XSIZE(Imic())) + { + if (dimensionality == 2 || (zcoord >= 0 && zcoord < ZSIZE(Imic())) ) + MDcopy.addObject(MDresult.getObject()); + } + } + MDresult = MDcopy; + + } + } return MDresult; From 598cd620f384e152b058d128f4b0a536e5b27ae1 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 14 Jan 2025 12:19:00 +0000 Subject: [PATCH 051/189] fixed another merge error --- src/pipeline_jobs.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 3227ea924..49b9cb042 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -7192,17 +7192,14 @@ bool RelionJob::getCommandsTomoSubtomoJob(std::string &outputname, std::vector>>>>>> master if (is_continue) { command += " --only_do_unfinished "; From ffdef917cba1cf4039276f1abec4589d9f43e3b7 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 14 Jan 2025 12:20:55 +0000 Subject: [PATCH 052/189] moved version to devel --- src/macros.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/macros.h b/src/macros.h index aa7472337..c2102debd 100644 --- a/src/macros.h +++ b/src/macros.h @@ -45,7 +45,7 @@ #ifndef MACROS_H #define MACROS_H -#define RELION_SHORT_VERSION "5.0.0" +#define RELION_SHORT_VERSION "devel" extern const char *g_RELION_VERSION; #include From 8fb2fdaa6f8c47d8b5d86185aca334f8d870c8f3 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 14 Jan 2025 14:17:47 +0000 Subject: [PATCH 053/189] deactive tilt angle offset for continue jobs, as changing this half-way through will lead to errors in the star files --- src/gui_jobwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index c9c41b0a3..0abe0cbd9 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -2763,8 +2763,8 @@ void JobWindow::initialiseTomoReconstructTomogramsWindow() current_y += STEPY /2 ; - place ("tiltangle_offset"); - place("tomo_name", TOGGLE_REACTIVATE); + place ("tiltangle_offset", TOGGLE_DEACTIVATE); + place("tomo_name"); current_y += STEPY /2 ; From cc3e941c3b25a33773c97674cda0856d08d968f3 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 14 Jan 2025 16:54:59 +0000 Subject: [PATCH 054/189] made Jans extraction work again --- src/preprocessing.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/preprocessing.cpp b/src/preprocessing.cpp index 7e0f0593a..88678f7e9 100644 --- a/src/preprocessing.cpp +++ b/src/preprocessing.cpp @@ -1003,16 +1003,19 @@ void Preprocessing::extractParticlesFromOneMicrograph(MetaDataTable &MD, } // Discard particles that are completely outside the micrograph and print a warning - if (yF < 0 || y0 >= YSIZE(Imic()) || xF < 0 || x0 >= XSIZE(Imic()) || + if (!do_recenter) + { + if (yF < 0 || y0 >= YSIZE(Imic()) || xF < 0 || x0 >= XSIZE(Imic()) || (dimensionality==3 && (zF < 0 || z0 >= ZSIZE(Imic())) ) ) - { - std::cerr << " micrograph x,y,z,n-size= " << XSIZE(Imic()) << " , " << YSIZE(Imic()) << " , " << ZSIZE(Imic()) << " , " << NSIZE(Imic()) << std::endl; - std::cerr << " particle position= " << xpos << " , " << ypos; - if (dimensionality == 3) - std::cerr << " , " << zpos; - std::cerr << std::endl; - REPORT_ERROR("Preprocessing::extractParticlesFromOneFrame ERROR: particle" + integerToString(ipos+1) + " lies completely outside micrograph " + fn_mic); - } + { + std::cerr << " micrograph x,y,z,n-size= " << XSIZE(Imic()) << " , " << YSIZE(Imic()) << " , " << ZSIZE(Imic()) << " , " << NSIZE(Imic()) << std::endl; + std::cerr << " particle position= " << xpos << " , " << ypos; + if (dimensionality == 3) + std::cerr << " , " << zpos; + std::cerr << std::endl; + REPORT_ERROR("Preprocessing::extractParticlesFromOneFrame ERROR: particle" + integerToString(ipos+1) + " lies completely outside micrograph " + fn_mic); + } + } // Read per-particle CTF if (MDin_has_ctf && !keep_ctf_from_micrographs) From c35c1e2f54be3bb3c1cae2b343bba582eb60db81 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 15 Jan 2025 09:25:39 +0000 Subject: [PATCH 055/189] repaired bug in removal of decentered particles outside micrograph --- src/preprocessing.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/preprocessing.cpp b/src/preprocessing.cpp index 88678f7e9..f0fb0db0d 100644 --- a/src/preprocessing.cpp +++ b/src/preprocessing.cpp @@ -525,7 +525,7 @@ void Preprocessing::runExtractParticles() progress_bar(imic); TIMING_TIC(TIMING_TOP); - micIsUsed = extractParticlesFromFieldOfView(fn_mic, imic); + micIsUsed = extractParticlesFromFieldOfView(fn_mic, imic); TIMING_TOC(TIMING_TOP); if(micIsUsed) @@ -831,7 +831,7 @@ bool Preprocessing::extractParticlesFromFieldOfView(FileName fn_mic, long int im if (fn_data != "") { // Search for this micrograph in the MDdata table - MDin = getCoordinateMetaDataTable(fn_mic); + MDin = getCoordinateMetaDataTable(fn_mic); } else { @@ -1479,9 +1479,10 @@ MetaDataTable Preprocessing::getCoordinateMetaDataTable(FileName fn_mic) Imic.read(fn_mic, false); MetaDataTable MDcopy; + MDcopy.setName(MDresult.getName()); FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDresult) { - int xcoord, ycoord, zcoord = 0; + RFLOAT xcoord, ycoord, zcoord = 0.; MDresult.getValue(EMDL_IMAGE_COORD_X, xcoord); MDresult.getValue(EMDL_IMAGE_COORD_Y, ycoord); if (dimensionality == 3) From 43c434854a84e38355c7b6f80eefa74910132354 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 22 Jan 2025 09:20:40 +0000 Subject: [PATCH 056/189] added non-signal for getting rid of ice crystals --- src/amyloid_finder.cpp | 59 +++++++++++++++++++++++++++++++++++++++--- src/amyloid_finder.h | 6 ++--- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index fb3d1024a..d8e38d45f 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -53,8 +53,10 @@ void AmyloidFinder::read(int argc, char **argv, int rank) int expert_section = parser.addSection("Expert options (typically no need to change)"); signal_minres = textToFloat(parser.getOption("--signal_minres", "Minimum resolution value for signal (in A)", "4.85")); signal_maxres = textToFloat(parser.getOption("--signal_maxres", "Maximum resolution value for signal (in A)", "4.65")); + nonsignal_minres = textToFloat(parser.getOption("--nonsignal_minres", "Minimum resolution value for non-signal (in A)", "4.4")); + nonsignal_maxres = textToFloat(parser.getOption("--nonsignal_maxres", "Maximum resolution value for non-signal (in A)", "4.2")); inifactor_threshold = textToFloat(parser.getOption("--inifactor_threshold", "What fraction of the Z-score threshold for allowing to grow filaments?", "0.33")); - down_angpix = textToFloat(parser.getOption("--down_angpix", "Pixel size for downscaled images (needs to include signal frequency!)", "2.25")); + down_angpix = textToFloat(parser.getOption("--down_angpix", "Pixel size for downscaled images (needs to include signal frequency!)", "2.1")); angpix = textToFloat(parser.getOption("--force_angpix", "Force this pixel size, regardless of what is in the image header", "-1")); verb =textToInteger(parser.getOption("--verb", "Verbosity", "1")); @@ -154,6 +156,8 @@ void AmyloidFinder::initialise(bool is_leader) // Calculate Fourier shells for amyloid signal imin_signal = FLOOR(ilengthmax*down_angpix/signal_minres); imax_signal = CEIL(ilengthmax*down_angpix/signal_maxres); + imin_nonsignal = FLOOR(ilengthmax*down_angpix/nonsignal_minres); + imax_nonsignal = CEIL(ilengthmax*down_angpix/nonsignal_maxres); // Box size, orginal and cropped: set size of rectangular image to largest dimension large_box = sqrt(2.)*XMIPP_MAX(ori_xsize, ori_ysize); @@ -172,6 +176,7 @@ void AmyloidFinder::initialise(bool is_leader) std::cout << " + Original size of the input micrographs: " << ori_xsize << " x " << ori_ysize << " pixels" << std::endl; std::cout << " + Size of image to sample (in downscaled pixels): " << down_xsize << " x " << down_ysize << std::endl; std::cout << " + Fourier shells for the amyloid signal (in downscaled pixels): " << imin_signal << " - " << imax_signal << std::endl; + std::cout << " + Fourier shells for the non-signal control (in downscaled pixels): " << imin_nonsignal << " - " << imax_nonsignal << std::endl; std::cout << " ========================== " << std::endl; } @@ -256,6 +261,7 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi // Rotate the large image, and store downscaled images by cropping their Fourier Transform std::vector > rotated_imgs(nr_psi), rotated_scores_perline(nr_psi), rotated_scores(nr_psi); + std::vector > rotated_nonscores_perline(nr_psi), rotated_nonscores(nr_psi); std::vector transformer(nr_threads); // TODO: in principle, only need to rotate to 90 degrees, as I can use both the X and the Y direction for the 1D FFTs! if (myverb) @@ -292,8 +298,12 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi // Just prepare the rotated_scores vector too rotated_scores_perline[ipsi].initZeros(crop_box, crop_box); rotated_scores_perline[ipsi].setXmippOrigin(); + rotated_nonscores_perline[ipsi].initZeros(crop_box, crop_box); + rotated_nonscores_perline[ipsi].setXmippOrigin(); rotated_scores[ipsi].initZeros(crop_box/shift_step, crop_box/shift_step); rotated_scores[ipsi].setXmippOrigin(); + rotated_nonscores[ipsi].initZeros(crop_box/shift_step, crop_box/shift_step); + rotated_nonscores[ipsi].setXmippOrigin(); } if (myverb) progress_bar(nr_psi); @@ -333,7 +343,11 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi for (int isig = imin_signal; isig <= imax_signal; isig++) { - A2D_ELEM(rotated_scores_perline[ipsi], cen_ypos, cen_xpos) += norm(DIRECT_A1D_ELEM(FTline, isig)); + A2D_ELEM(rotated_scores_perline[ipsi], cen_ypos, cen_xpos) += norm(DIRECT_A1D_ELEM(FTline, isig)); + } + for (int isig = imin_nonsignal; isig <= imax_nonsignal; isig++) + { + A2D_ELEM(rotated_nonscores_perline[ipsi], cen_ypos, cen_xpos) += norm(DIRECT_A1D_ELEM(FTline, isig)); } } // end loop ypos @@ -368,6 +382,8 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi // Grab the line from the rotated image, in X and in Y directions A2D_ELEM(rotated_scores[ipsi], cen_ypos/shift_step, cen_xpos/shift_step) += A2D_ELEM(rotated_scores_perline[ipsi], cen_ypos+iwidth-iwidthmax/2, cen_xpos); + A2D_ELEM(rotated_nonscores[ipsi], cen_ypos/shift_step, cen_xpos/shift_step) += + A2D_ELEM(rotated_nonscores_perline[ipsi], cen_ypos+iwidth-iwidthmax/2, cen_xpos); } // end loop iwidth } // end loop ipass } // end loop xpos @@ -400,13 +416,16 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi { RFLOAT psi = getPsiAngle(ipsi); selfRotate(rotated_scores[ipsi], -psi); + selfRotate(rotated_nonscores[ipsi], -psi); } Mangle.resize(down_ysize, down_xsize); Mangle.setXmippOrigin(); Mscore.resize(Mangle); - MultidimArray Msum; + MultidimArray Msum, Mnonsum, Mnonscore; Msum.resize(Mangle); + Mnonsum.resize(Mangle); + Mnonscore.resize(Mangle); // This can't be parallelised efficiently because need to protect Msums, Mscore and Mangle from simultaneous writing... // Calculate Z-scores over psi: (max_psi - avg_psi) /stddev_psi @@ -421,7 +440,9 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi int cen_xpos = xpos - down_xsize/2; RFLOAT myscore = A2D_ELEM(rotated_scores[ipsi], cen_ypos/shift_step, cen_xpos/shift_step); + RFLOAT mynonscore = A2D_ELEM(rotated_nonscores[ipsi], cen_ypos/shift_step, cen_xpos/shift_step); A2D_ELEM(Msum, cen_ypos, cen_xpos) += myscore; + A2D_ELEM(Mnonsum, cen_ypos, cen_xpos) += mynonscore; if (myscore > A2D_ELEM(Mscore, cen_ypos, cen_xpos)) { @@ -429,6 +450,11 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi A2D_ELEM(Mangle, cen_ypos, cen_xpos) = mypsi; } + if (mynonscore > A2D_ELEM(Mnonscore, cen_ypos, cen_xpos)) + { + A2D_ELEM(Mnonscore, cen_ypos, cen_xpos) = mynonscore; + } + } } } @@ -442,8 +468,33 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi // calculate as normalised score: // (max_psi - adjusted_mean_psi) / adjusted_mean_psi // where adjusted_mean is the average of the score over all psi-values, except the maximum + RFLOAT Zscore_signal = 0.; if (DIRECT_MULTIDIM_ELEM(Msum, n) > 0.) - DIRECT_MULTIDIM_ELEM(Mscore, n) = (DIRECT_MULTIDIM_ELEM(Mscore, n) - DIRECT_MULTIDIM_ELEM(Msum, n)) / DIRECT_MULTIDIM_ELEM(Msum, n); + Zscore_signal = (DIRECT_MULTIDIM_ELEM(Mscore, n) - DIRECT_MULTIDIM_ELEM(Msum, n)) / DIRECT_MULTIDIM_ELEM(Msum, n); + + // Also for non-signal + DIRECT_MULTIDIM_ELEM(Mnonsum, n) -= DIRECT_MULTIDIM_ELEM(Mnonscore, n); + DIRECT_MULTIDIM_ELEM(Mnonsum, n) /= (RFLOAT)(nr_psi-1); + RFLOAT Zscore_nonsignal = 0.; + if (DIRECT_MULTIDIM_ELEM(Mnonsum, n) > 0.) + Zscore_nonsignal = (DIRECT_MULTIDIM_ELEM(Mnonscore, n) - DIRECT_MULTIDIM_ELEM(Mnonsum, n)) / DIRECT_MULTIDIM_ELEM(Mnonsum, n); + // Avoid negative non-signals + //if (Zscore_nonsignal < 0.) Zscore_nonsignal = 0.; + // Calculate a combined score that emphasizes high signals, but low non-signals: + // Z_signal * (1 - Z_nonsignal) + // If lower than 0, just set score to 0 + DIRECT_MULTIDIM_ELEM(Mscore, n) = XMIPP_MAX(0., Zscore_signal * (1. - Zscore_nonsignal)); + + /* + if (DIRECT_MULTIDIM_ELEM(Mscore, n)>0.5) + { + std::cerr << "score= " << DIRECT_MULTIDIM_ELEM(Mscore, n)<< " Zscore_signal= " << Zscore_signal << " Zscore_nonsignal= " << Zscore_nonsignal + << " Msum= " << DIRECT_MULTIDIM_ELEM(Msum, n) << " Mnonsum= " << DIRECT_MULTIDIM_ELEM(Mnonsum, n) + << " angle= " << DIRECT_MULTIDIM_ELEM(Mangle, n) << " nonangle= " << DIRECT_MULTIDIM_ELEM(Mnonangle, n)< circle; From 2bed7fcbac32184463299d7496a1d34a8a266f06 Mon Sep 17 00:00:00 2001 From: scheres Date: Mon, 27 Jan 2025 14:54:39 +0000 Subject: [PATCH 057/189] give more info about z_perc errors --- src/helix.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/helix.cpp b/src/helix.cpp index 56760c902..0cbb85e56 100644 --- a/src/helix.cpp +++ b/src/helix.cpp @@ -829,7 +829,11 @@ bool checkParametersFor3DHelicalReconstruction( if (z_percentage_min > z_percentage_max) { if (verboseOutput) - std::cout << " ERROR! The range of Z percentage is invalid! To decrease the lower bound, make maximum rise smaller or box size larger. To increase the upper bound, make the particle diameter (along with the box size) larger and the outer tube diameter smaller!" << std::endl; + { + std::cout << " z_percentage_min= " << z_percentage_min << " z_percentage_max= " << z_percentage_max << std::endl; + std::cout << " particle_diameter_A= " << particle_diameter_A << " tube_outer_diameter_A= " << tube_outer_diameter_A << " nr_units_min= " << nr_units_min << " box_len= " << box_len < z_percentage_max) ) { if (verboseOutput) - std::cout << " ERROR! Z percentage is out of range under current settings!" << std::endl; + { + std::cout << " z_percentage_min= " << z_percentage_min << " z_percentage_max= " << z_percentage_max << std::endl; + std::cout << " particle_diameter_A= " << particle_diameter_A << " tube_outer_diameter_A= " << tube_outer_diameter_A << " nr_units_min= " << nr_units_min << " box_len= " << box_len < Date: Wed, 5 Feb 2025 08:29:00 +0000 Subject: [PATCH 058/189] added same filament tracing code to find_amyloid as was present in modified topaz, with tweaking of some internal, hardcoded parameters --- CMakeLists.txt | 1 + scripts/trace_amyloids.in | 251 +++++++++++++++++ src/amyloid_finder.cpp | 559 +++---------------------------------- src/amyloid_finder.h | 29 +- src/amyloid_finder_mpi.cpp | 5 +- src/gui_jobwindow.cpp | 1 + src/pipeline_jobs.cpp | 15 +- 7 files changed, 316 insertions(+), 545 deletions(-) create mode 100755 scripts/trace_amyloids.in diff --git a/CMakeLists.txt b/CMakeLists.txt index c6ba58e69..d9d4518de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -559,6 +559,7 @@ list(APPEND RELION_PYTHON_WRAPPERS python_tomo_get_particle_poses python_tomo_denoise filament_selection + trace_amyloids ) foreach (SCRIPT_FILE ${RELION_PYTHON_WRAPPERS}) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in new file mode 100755 index 000000000..1c45e0d2b --- /dev/null +++ b/scripts/trace_amyloids.in @@ -0,0 +1,251 @@ +#!@PYTHON_EXE_PATH@ + +import numpy as np +import mrcfile +import starfile +from skimage.filters import gaussian +from skimage.transform import probabilistic_hough_line +from skimage.morphology import skeletonize +import math +import pandas as pd +import argparse + + +def load_mrc(mrc_fn: str, multiply_global_origin: bool = True): + mrc_file_handle = mrcfile.open(mrc_fn, "r") + voxel_size = float(mrc_file_handle.voxel_size.x) + + if voxel_size <= 0: + raise RuntimeError(f"Seems like the MRC file: {mrc_fn} does not have a header.") + + c = mrc_file_handle.header["mapc"] + r = mrc_file_handle.header["mapr"] + s = mrc_file_handle.header["maps"] + + global_origin = mrc_file_handle.header["origin"] + global_origin = np.array([global_origin.x, global_origin.y, global_origin.z]) + global_origin[0] += mrc_file_handle.header["nxstart"] + global_origin[1] += mrc_file_handle.header["nystart"] + global_origin[2] += mrc_file_handle.header["nzstart"] + + if multiply_global_origin: + global_origin *= mrc_file_handle.voxel_size.x + + if c == 1 and r == 2 and s == 3: + grid = mrc_file_handle.data + elif c == 3 and r == 2 and s == 1: + grid = np.moveaxis(mrc_file_handle.data, [0, 1, 2], [2, 1, 0]) + elif c == 2 and r == 1 and s == 3: + grid = np.moveaxis(mrc_file_handle.data, [1, 2, 0], [2, 1, 0]) + else: + raise RuntimeError("MRC file axis arrangement not supported!") + + return grid, voxel_size, global_origin + +def is_in_between(point, line): + dx = line[1][0] - line[0][0] + dy = line[1][1] - line[0][1] + dotp = (point[0] - line[0][0])*dx + (point[1] - line[0][1])*dy + return 0 <= dotp and dotp <= dx*dx + dy*dy + +def distance_point_line(point, line): + x1=line[0][0] + x2=line[1][0] + y1=line[0][1] + y2=line[1][1] + x0=point[0] + y0=point[1] + dd = abs( (x2-x1)*(y1-y0) - (x1-x0)*(y2-y1) ) / np.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ) + if not is_in_between(point,line): + closest = min(distance_point_point(point,line[0]), distance_point_point(point,line[1])) + return max(closest, dd) + else: + return dd + +def distance_point_point(point1, point2): + x1=point1[0] + y1=point1[1] + x2=point2[0] + y2=point2[1] + return np.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)) + +def angle_line_line(line1, line2): + a1 = np.arctan2(line1[1][1]-line1[0][1], line1[1][0]-line1[0][0]) + a2 = np.arctan2(line2[1][1]-line2[0][1], line2[1][0]-line2[0][0]) + return abs(a2-a1) + +def prune_lines(inlines, mind, min_ang, max_merge): + import math + lengths = [] + merged = [] + lines = np.asarray(inlines) + for line in lines: + lengths = np.append(lengths, np.linalg.norm(line[0]-line[1])) + merged = np.append(merged, 1) + sortidx = np.argsort(lengths); + + min_ang_rad = math.radians(min_ang) + NN = len(sortidx) + idx = NN - 1 + while idx >= 0: + i1 = sortidx[idx] + + for i2 in range(NN): + if (i1 != i2 and lengths[i2] > 0. and lengths[i1] > 0.): + + d11 = distance_point_line(lines[i1][0],lines[i2]) + d12 = distance_point_line(lines[i1][1],lines[i2]) + d21 = distance_point_line(lines[i2][0],lines[i1]) + d22 = distance_point_line(lines[i2][1],lines[i1]) + ang = angle_line_line(lines[i1], lines[i2]) + sum = 0 + if (d11 < mind): + sum = sum + 1 + if (d12 < mind): + sum = sum + 1 + if (d21 < mind): + sum = sum + 1 + if (d22 < mind): + sum = sum + 1 + + # merge lines if they haven't been merged too often already, they overlap two or more points and are parallel + if ( (merged[i1] + merged[i2] < max_merge) and (sum >= 2) and (ang < min_ang_rad or abs(ang-math.pi) < min_ang_rad) ): + + # select the two points with the furthest distance + maxd=0 + for i in range(2): + for j in range(2): + d = distance_point_point(lines[i1][i], lines[i2][j]) + if (d > maxd): + maxd = d + mymax0 = lines[i1][i] + mymax1 = lines[i2][j] + # perhaps original one was longer? + if maxd > lengths[i1]: + lines[i1][0] = mymax0 + lines[i1][1] = mymax1 + lengths[i1] = np.linalg.norm(lines[i1][0]-lines[i1][1]) + merged[i1] = merged[i1] + merged[i2] + + lines[i2][0][0] = -9999 + lines[i2][0][1] = -9999 + lines[i2][1][0] = -9999 + lines[i2][1][1] = -9999 + lengths[i2] = 0 + merged[i2] = 0 + idx = idx + 1 + break + + # remove smaller lines with both points close to longer one + elif (d21 < 2*mind and d22 < 2*mind): + lines[i2][0][0] = -9999 + lines[i2][0][1] = -9999 + lines[i2][1][0] = -9999 + lines[i2][1][1] = -9999 + lengths[i2] = 0 + + idx = idx - 1 + + return lines + + +def pick_filaments(score, radius, threshold, filaments_length, filaments_plot): + + #Parameters + thr = round(0.1*filaments_length) + line_length = filaments_length + gap = radius + mind= 1.5*radius + min_angle = 10 + max_merge = 10 + + #bin_score = gaussian(score, 3) > threshold + bin_score = score > threshold + edges = skeletonize(bin_score) + houghs = probabilistic_hough_line(edges, threshold=thr, line_length=line_length, line_gap=gap) + lines = prune_lines(houghs, mind=mind, min_ang=min_angle, max_merge=max_merge) + + if filaments_plot: + import matplotlib.pyplot as plt + from matplotlib import cm + fig, axes = plt.subplots(1, 4, figsize=(15, 5), sharex=True, sharey=True) + ax = axes.ravel() + + ax[0].imshow(score, cmap=cm.binary, vmin=-20, vmax=5) + ax[0].imshow(bin_score, alpha=0.5, cmap=cm.Reds) + ax[0].set_title('FOM [-20,5] thr= ' + str(threshold)) + + ax[1].imshow(edges, cmap=cm.gray) + ax[1].set_title('Skeletonize') + + ax[2].imshow(edges * 0) + for hough in houghs: + p0, p1 = hough + ax[2].plot((p0[0], p1[0]), (p0[1], p1[1])) + ax[2].set_xlim((0, score.shape[1])) + ax[2].set_ylim((score.shape[0], 0)) + ax[2].set_title('Hough transform; len= ' + str(line_length) + ' gap= ' + str(gap)) + + ax[3].imshow(edges * 0) + for line in lines: + p0, p1 = line + ax[3].plot((p0[0], p1[0]), (p0[1], p1[1])) + ax[3].set_xlim((0, score.shape[1])) + ax[3].set_ylim((score.shape[0], 0)) + ax[3].set_title('Prune: mind= ' + str(mind)) + + for a in ax: + a.set_axis_off() + + plt.tight_layout() + plt.show() + + oldlines=lines + # Flatten lines into 2D array, as rest of topaz + NN=len(lines) + if NN>0: + lines = lines.reshape(2*NN, 2) + # Remove -9999 coordinates + newlines = [] + for i in range(2*NN): + if (lines[i,0] != -9999 and lines[i,1] != -9999): + newlines = np.append(newlines, lines[i]) + + NN=round(len(newlines)/2) + if (NN>0): + lines = newlines.reshape(NN, 2) + else: + lines = [] + + return lines + +if __name__ == "__main__": + parser = argparse.ArgumentParser('Scripts for picking filaments in FOM maps') + parser.add_argument('-i', '--input', help='input image to pick filaments in') + parser.add_argument('-o', '--output', help='output star file with coordinates') + parser.add_argument('-r', '--radius', type=int, help='radius of the regions to extract (in A)') + parser.add_argument('-t', '--threshold', default=0.5, type=float, help='FOM threshold at which to terminate region extraction (default: 0.5)') + parser.add_argument('-l', '--length', default=-1, type=int, help='minimum length of straight filament segments to be picked (in Angstrom) (default: twice --radius)') + parser.add_argument('-s', '--scale', type=float, default=1, help='Multiply outputcoordinates by this factor. (default: 1)') + parser.add_argument('-fp', '--plot', action='store_true', help='flag for filament start-end picking plus plotting of its intermediate stages (useful for tuning parameters).') + + args = parser.parse_args() + + image, pixel_size, origin = load_mrc(args.input) + + # Set edges to zero to prevent picking too close to the edge + d = round(args.radius/pixel_size) + cropimage = image.copy() # because image was readonly + cropimage[:d, :] = 0 # Top edge + cropimage[-d:, :] = 0 # Bottom edge + cropimage[:, :d] = 0 # Left edge + cropimage[:, -d:] = 0 # Right edge + + coords = pick_filaments(cropimage, round(args.radius/pixel_size), args.threshold, round(args.length/pixel_size), args.plot) + coords = np.round(coords*args.scale).astype(int) + # now write out as starfile... + + if len(coords) > 0: + xy = {'rlnCoordinateX': coords[:, 0], 'rlnCoordinateY': coords[:, 1]} + df = pd.DataFrame(xy) + starfile.write(df, args.output, overwrite=True) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index d8e38d45f..af108fbce 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -30,8 +30,6 @@ void AmyloidFinder::read(int argc, char **argv, int rank) fn_out = parser.getOption("--pickname", "Rootname for coordinate STAR files", "autopick"); fn_odir = parser.getOption("--odir", "Output directory for coordinate files (default is to store next to micrographs)", "AutoPick/"); do_only_unfinished = parser.checkOption("--only_do_unfinished", "Only estimate CTFs for those tomograms for which there is not yet a logfile with Final values."); - do_write_intermediate = parser.checkOption("--write_intermediates", "Write out intermediate FOM & PSI maps for faster testing of tracing parameters?"); - do_only_write_intermediate = !parser.checkOption("--dont_only_write_intermediates", "For now, default is to only intermediate FOM & PSI maps, as picking doesn't work well yet"); nr_threads = textToInteger(parser.getOption("--j", "Number of threads to us in parallel", "1")); int search_section = parser.addSection("Filament searching options "); @@ -41,21 +39,17 @@ void AmyloidFinder::read(int argc, char **argv, int rank) search_filament_length = textToFloat(parser.getOption("--search_filament_length", "Length of searching image (in A)", "250")); int pick_section = parser.addSection("Filament tracing options "); - zscore_threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "0.5")); - nr_rungs_per_segment = textToInteger(parser.getOption("--rungs_per_segment", "Number of new amyloid rungs per segment", "3")); + threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "0.5")); trace_filament_width = textToFloat(parser.getOption("--trace_filament_width", "Minimum width occupied by a traced filaments (in A)", "200")); trace_filament_length = textToFloat(parser.getOption("--trace_filament_length", "Minimum length of traced filaments (in A)", "300")); - RFLOAT kappa = textToFloat(parser.getOption("--kappa", "Curvature parameter kappa ", "0.07")); - if (kappa > 0.101) - REPORT_ERROR("ERROR: for amyloids you cannot use kappa larger than 0.1!"); - psidiff_per_segment = RAD2DEG(kappa*2.); + do_plot = parser.checkOption("--plot", "Display images with intermediate tracing results for each micrograph"); + fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_trace_amyloids"); int expert_section = parser.addSection("Expert options (typically no need to change)"); signal_minres = textToFloat(parser.getOption("--signal_minres", "Minimum resolution value for signal (in A)", "4.85")); signal_maxres = textToFloat(parser.getOption("--signal_maxres", "Maximum resolution value for signal (in A)", "4.65")); nonsignal_minres = textToFloat(parser.getOption("--nonsignal_minres", "Minimum resolution value for non-signal (in A)", "4.4")); nonsignal_maxres = textToFloat(parser.getOption("--nonsignal_maxres", "Maximum resolution value for non-signal (in A)", "4.2")); - inifactor_threshold = textToFloat(parser.getOption("--inifactor_threshold", "What fraction of the Z-score threshold for allowing to grow filaments?", "0.33")); down_angpix = textToFloat(parser.getOption("--down_angpix", "Pixel size for downscaled images (needs to include signal frequency!)", "2.1")); angpix = textToFloat(parser.getOption("--force_angpix", "Force this pixel size, regardless of what is in the image header", "-1")); verb =textToInteger(parser.getOption("--verb", "Verbosity", "1")); @@ -76,10 +70,6 @@ void AmyloidFinder::initialise(bool is_leader) if (fn_odir[fn_odir.length()-1] != '/') fn_odir += "/"; - // Make sure inifactor is <0.1] - if (inifactor_threshold > 1 || inifactor_threshold <= 0.) - REPORT_ERROR("ERROR: --inifactor_threshold should be between <0,1]"); - fn_micrographs.clear(); if (fn_in.isStarFile()) { @@ -98,26 +88,6 @@ void AmyloidFinder::initialise(bool is_leader) fn_micrographs.push_back(fn_in); } - fn_ori_micrographs = fn_micrographs; - // If we're continuing an old run, see which micrographs have not been finished yet... - if (do_only_unfinished && !do_write_intermediate) - { - if (verb > 0) - { - std::cout << " + Skipping those micrographs for which coordinate file already exists" << std::endl; - } - - std::vector fns_todo; - for (long int imic = 0; imic < fn_micrographs.size(); imic++) - { - FileName fn_tmp = getOutputRootName(fn_micrographs[imic]) + "_" + fn_out + ".star"; - if (!exists(fn_tmp)) - fns_todo.push_back(fn_micrographs[imic]); - } - - fn_micrographs = fns_todo; - } - // If there is nothing to do, then go out of initialise if (fn_micrographs.size() == 0) { @@ -180,38 +150,6 @@ void AmyloidFinder::initialise(bool is_leader) std::cout << " ========================== " << std::endl; } - // For filament tracing - // Set up a vector with coordinates of feasible next coordinates regarding distance and psi-angle - amyloid_rung = (signal_minres + signal_maxres)/2.; - int myrad = ROUND(nr_rungs_per_segment * amyloid_rung / angpix); - int myradb = myrad + 1; - float myrad2 = (float)myrad * (float)myrad; - float myradb2 = (float)myradb * (float)myradb; - for (int ii = -myradb; ii <= myradb; ii++) - { - for (int jj = 0; jj <= myradb; jj++) - { - float r2 = (float)(ii*ii) + (float)(jj*jj); - if (r2 > myrad2 && r2 <= myradb2) - { - float myang = RAD2DEG(atan2((float)(ii),(float)(jj))); - if (myang > 90.) myang -= 180.; - if (myang < -90.) myang += 180.; - if (myang < 90. && fabs(myang) < psidiff_per_segment) - { - AmyloidCoordinate circlecoord; - circlecoord.x = (RFLOAT)jj; - circlecoord.y = (RFLOAT)ii; - circlecoord.fom =0.; - circlecoord.psi =myang; - circle.push_back(circlecoord); - //std::cerr << " circlecoord.x= " << circlecoord.x << " circlecoord.y= " << circlecoord.y << " psi= " << circlecoord.psi << std::endl; - } - } - } - } - - } @@ -529,422 +467,24 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi } -std::vector AmyloidFinder::findNextCandidateCoordinates(AmyloidCoordinate &mycoord, MultidimArray &Mscore, MultidimArray &Mpsi) -{ - - std::vector result; - - Matrix2D A2D; - Matrix1D vec_c(2), vec_p(2); - rotation2DMatrix(-mycoord.psi, A2D, false); - - // How many psi-steps away is this segment allowed to be from the previous one: just a bit more than original psi_steps? - RFLOAT max_psidiff = (1.5 * psi_step); - for (int icoor = 0; icoor < circle.size(); icoor++) - { - // Rotate the circle-vector coordinates along the mycoord.psi - XX(vec_c) = (circle[icoor]).x; - YY(vec_c) = (circle[icoor]).y; - vec_p = A2D * vec_c; - - long int jj = ROUND(mycoord.x + XX(vec_p)); - long int ii = ROUND(mycoord.y + YY(vec_p)); - - RFLOAT myscore = A2D_ELEM(Mscore, ii, jj); - RFLOAT mypsi = A2D_ELEM(Mpsi, ii, jj); - // Impose small difference in psi-angle with mycoord - RFLOAT psidiff = getPsiDiff(mycoord.psi, mypsi); - //std::cerr <<" icoor= " << icoor <<"x= "< It; - It()=Mpsi; - It.write("Mpsi.spi"); - It()=Mscore; - It.write("Mscore.spi"); - char c; - std::cerr << "press any key" << std::endl; - std::cin >> c; - */ - - // See how far we can grow in any of the circle directions. - // Note recursive calls to findNextCandidateCoordinates below. - // Let's search 3 layers deep... - std::vector new1coords; - new1coords = findNextCandidateCoordinates(mycoord, Mscore, Mpsi); - - long int N = new1coords.size(); - std::vector max_depths(N, 0); - std::vector max_sumfoms(N, -9999.); - - RFLOAT sumfom = 0.; - RFLOAT max_sumfom = -9999.; - int best_inew1=-1; - for (int inew1 = 0; inew1 < new1coords.size(); inew1++) - { - sumfom = new1coords[inew1].fom; - // Just select for highest score from all candidate coordinates - // TODO: should I avoid changes in psi instead? - if (sumfom > max_sumfom) - { - max_sumfom = sumfom; - best_inew1 = inew1; - } - - std::vector new2coords; - new2coords = findNextCandidateCoordinates(new1coords[inew1], Mscore, Mpsi); - for (int inew2 = 0; inew2 < new2coords.size(); inew2++) - { - - sumfom = new1coords[inew1].fom + new2coords[inew2].fom; - if (sumfom > max_sumfom) - { - max_sumfom = sumfom; - best_inew1 = inew1; - } - - std::vector new3coords; - new3coords = findNextCandidateCoordinates(new2coords[inew2], Mscore, Mpsi); - for (int inew3 = 0; inew3 < new3coords.size(); inew3++) - { - sumfom = new1coords[inew1].fom + new2coords[inew2].fom + new3coords[inew3].fom; - if (sumfom > max_sumfom) - { - max_sumfom = sumfom; - best_inew1 = inew1; - } - - std::vector new4coords; - new4coords = findNextCandidateCoordinates(new3coords[inew3], Mscore, Mpsi); - for (int inew4 = 0; inew4 < new4coords.size(); inew4++) - { - sumfom = new1coords[inew1].fom + new2coords[inew2].fom + new3coords[inew3].fom + new4coords[inew4].fom; - if (sumfom > max_sumfom) - { - max_sumfom = sumfom; - best_inew1 = inew1; - } - } - } - } - } - - if (best_inew1 < 0) - { - return result; - } - else - { - return new1coords[best_inew1]; - } - -} - - -RFLOAT AmyloidFinder::maxIndex_multithreaded(MultidimArray &m, long int &imax, long int &jmax, int skip_sides) +void AmyloidFinder::traceFilaments(FileName &fn_fom, FileName &fn_star) { - RFLOAT maxval= -99.e99; - std::vector maxval_per_thread(nr_threads, -99.e99); - std::vector maxi_per_thread(nr_threads), maxj_per_thread(nr_threads); -#pragma omp parallel for num_threads(nr_threads) - for (long int i=STARTINGY(m)+skip_sides; i<=FINISHINGY(m)-skip_sides; i++) - { - const int tid = omp_get_thread_num(); - for (long int j=STARTINGX(m)+skip_sides; j<=FINISHINGX(m)-skip_sides; j++) - { - const int tid = omp_get_thread_num(); - RFLOAT aux = A2D_ELEM(m, i, j); - if (aux > maxval_per_thread[tid]) - { - maxval_per_thread[tid] = aux; - maxi_per_thread[tid] = i; - maxj_per_thread[tid] = j; + // hardcoded python script for now... + FileName command = fn_exe; - } - } - } + command += " --i " + fn_fom; + command += " --o " + fn_star; + command += " --t " + floatToString(threshold); + command += " --r " + floatToString(trace_filament_width/2); + command += " --l " + floatToString(trace_filament_length); + command += " --s " + floatToString(down_angpix/angpix); + if (do_plot) + command += " --plot "; - for (int tid = 0; tid < nr_threads; tid++) - { - if (maxval_per_thread[tid] > maxval) - { - maxval = maxval_per_thread[tid]; - imax = maxi_per_thread[tid]; - jmax = maxj_per_thread[tid]; - } - } + //std::cerr << command << std::endl; + int res = system(command.c_str()); - return maxval; - -} - -MetaDataTable AmyloidFinder::traceFilaments(MultidimArray &Mscore, MultidimArray &Mpsi) -{ - - // Re-scale and re-box image to original size - int new_box = XMIPP_MAX(YSIZE(Mscore), XSIZE(Mscore)); - Mscore.window(FIRST_XMIPP_INDEX(new_box), FIRST_XMIPP_INDEX(new_box), LAST_XMIPP_INDEX(new_box), - LAST_XMIPP_INDEX(new_box)); - Mpsi.window(FIRST_XMIPP_INDEX(new_box), FIRST_XMIPP_INDEX(new_box), LAST_XMIPP_INDEX(new_box), - LAST_XMIPP_INDEX(new_box)); - Mscore.setXmippOrigin(); - Mpsi.setXmippOrigin(); - - int newsize = (XSIZE(Mpsi)*down_angpix) / angpix; - if (newsize%2 !=0) newsize++; - down_angpix = angpix * XSIZE(Mpsi) / (float)newsize; - resizeMap(Mscore, newsize); - resizeMap(Mpsi, newsize); - Mscore.setXmippOrigin(); - Mpsi.setXmippOrigin(); - - // Again, leave some space near the edges to account for setting the rectangles to zero - int larger_side = CEIL(1.5 * trace_filament_width / angpix); - if (larger_side%2 != 0) larger_side++; - - Mscore.window(FIRST_XMIPP_INDEX(ori_ysize + larger_side), FIRST_XMIPP_INDEX(ori_xsize + larger_side), - LAST_XMIPP_INDEX(ori_ysize + larger_side), LAST_XMIPP_INDEX(ori_xsize + larger_side)); - Mpsi.window(FIRST_XMIPP_INDEX(ori_ysize + larger_side), FIRST_XMIPP_INDEX(ori_xsize + larger_side), - LAST_XMIPP_INDEX(ori_ysize + larger_side), LAST_XMIPP_INDEX(ori_xsize + larger_side)); - Mscore.setXmippOrigin(); - Mpsi.setXmippOrigin(); - - std::vector< std::vector > helices; - bool no_more_ccf_peaks = false; - int nr_fail = 0; - int skip_sides = 25; - while (!no_more_ccf_peaks) - { - long int imax, jmax; - float myscore = maxIndex_multithreaded(Mscore, imax, jmax, skip_sides); - float mypsi = Mpsi(imax, jmax); - // Peaks to seed new filaments have to be at least the zscore_threshold - if (myscore < zscore_threshold ) - { - //std::cerr << " myscore= " << myscore << " nr_fail= " << nr_fail << std::endl; - no_more_ccf_peaks = true; - } - - std::vector helix; - AmyloidCoordinate coord, newcoord; - coord.x = jmax; - coord.y = imax; - coord.fom = myscore; - coord.psi = mypsi; - coord.order = 0; - helix.push_back(coord); - - // Try grow from the start - bool is_done_start = false; - while ( !is_done_start ) - { - //std::cerr << " START-in: x= "<< helix[0].x <<" y= " << helix[0].y << " psi= " << helix[0].psi << " fom= " << helix[0].fom << std::endl; - newcoord = findNextAmyloidCoordinate(helix[0],Mscore, Mpsi); - //std::cerr << " START newcoord.x= " << newcoord.x << " newcoord.y= " << newcoord.y << " newcoord.fom= " << newcoord.fom << " newcoord.psi= " << newcoord.psi << " helix.size()= " << helix.size() << std::endl; - if (newcoord.fom > inifactor_threshold * zscore_threshold) - { - newcoord.order = helix[0].order - 1; - helix.insert(helix.begin(), newcoord); - } - else - { - is_done_start = true; - //std::cerr << " START IS DONE!" << std::endl; - } - } - - // And try grow from the end, but only if any segments were added in growing from the start - if (helix.size() > 1) - { - // Now that we're done with the START, reset the value for Qscore in the original coordinate, otherwise END won't run - A2D_ELEM(Mscore, ROUND(helix[helix.size()-1].y), ROUND(helix[helix.size()-1].x)) = helix[helix.size()-1].fom; - - bool is_done_end = false; - while ( !is_done_end ) - { - //std::cerr << " END-in: x= "<< helix[helix.size()-1].x <<" y= " << helix[helix.size()-1].y << " psi= " << helix[helix.size()-1].psi << " fom= " << helix[helix.size()-1].fom << std::endl; - newcoord = findNextAmyloidCoordinate(helix[helix.size()-1], Mscore, Mpsi); - //std::cerr << " END newcoord.x= " << newcoord.x << " newcoord.y= " << newcoord.y << " newcoord.fom= " << newcoord.fom << " newcoord.psi= " << newcoord.psi << " helix.size()= " << helix.size() << std::endl; - if (newcoord.fom > inifactor_threshold * zscore_threshold) - { - newcoord.order = helix[0].order + 1; - helix.push_back(newcoord); - } - else - { - is_done_end = true; - //std::cerr << " END IS DONE!" << std::endl; - } - - } - } - - - if (nr_rungs_per_segment * amyloid_rung * helix.size() > trace_filament_length) - { - helices.push_back(helix); - -#ifdef DEBUG_TRACE - std::cerr << "PUSHING BACK HELIX " << helices.size() << " << WITH SIZE= " << helix.size() << std::endl; - char c; - std::cerr << " helices.size()= " << helices.size() << std::endl; - std::cerr << "press any key" << std::endl; - std::cin >> c; - Image It; - It()=Mscore; - It.write("Mscore.spi"); -#endif - - } - else - { - nr_fail++; - } - - } // end while (!no_more_ccf_peaks) - - // Now write out in a STAR file - // Write out a STAR file with the coordinates - FileName fn_tmp; - MetaDataTable MDout; - - // Only output STAR header if there are no tubes... - MDout.clear(); - MDout.addLabel(EMDL_IMAGE_COORD_X); - MDout.addLabel(EMDL_IMAGE_COORD_Y); - MDout.addLabel(EMDL_PARTICLE_AUTOPICK_FOM); - MDout.addLabel(EMDL_PARTICLE_HELICAL_TUBE_ID); - MDout.addLabel(EMDL_ORIENT_TILT_PRIOR); - MDout.addLabel(EMDL_ORIENT_PSI_PRIOR); - MDout.addLabel(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM); - MDout.addLabel(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO); - MDout.addLabel(EMDL_ORIENT_ROT_PRIOR_FLIP_RATIO); // KThurber - - - // Write out segments for all helices - int helixid = 0; - for (int ihelix = 0; ihelix < helices.size(); ihelix++) - { - // Sort all coordinates in the helix based on their position - std::sort(helices[ihelix].begin(), helices[ihelix].end(), - [](const AmyloidCoordinate& a, const AmyloidCoordinate& b) { return a.order < b.order;}); - - RFLOAT tube_length = 0.; - RFLOAT mypsi, old_mypsi; - for (long int iseg = 0; iseg < helices[ihelix].size(); iseg++) - { - - RFLOAT xpos = (helices[ihelix][iseg].x ) - (RFLOAT)(FIRST_XMIPP_INDEX(ori_xsize)); - RFLOAT ypos = (helices[ihelix][iseg].y ) - (RFLOAT)(FIRST_XMIPP_INDEX(ori_ysize)); - - // Angle with the next segment (for last one, take mypsi from previous segment!) - if (iseg < helices[ihelix].size()-1) - { - float dx = (float)(helices[ihelix][iseg+1].x - helices[ihelix][iseg].x); - float dy = (float)(helices[ihelix][iseg+1].y - helices[ihelix][iseg].y); - mypsi = -1. * RAD2DEG(atan2(dy,dx)); - if (mypsi < 0.) - { - if (fabs(mypsi+180. - old_mypsi) < 90.) - mypsi+= 180.; - } - } - else - { - mypsi = old_mypsi; - } - - MDout.addObject(); - MDout.setValue(EMDL_IMAGE_COORD_X, xpos); - MDout.setValue(EMDL_IMAGE_COORD_Y, ypos); - MDout.setValue(EMDL_PARTICLE_AUTOPICK_FOM, helices[ihelix][iseg].fom); - MDout.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, ihelix+1); // start counting at 1 - MDout.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); - MDout.setValue(EMDL_ORIENT_PSI_PRIOR, mypsi); - MDout.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, tube_length); - MDout.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, 0.5); - MDout.setValue(EMDL_ORIENT_ROT_PRIOR_FLIP_RATIO, 0.5); // KThurber - - tube_length += nr_rungs_per_segment * amyloid_rung; - } - helixid++; - } - - return MDout; } @@ -959,7 +499,7 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) FileName fn_skew = fn_root + "_" + fn_out + "_skew.star"; MultidimArray Mscore, Mangle; - if (!(do_write_intermediate || do_only_write_intermediate) || !exists(fn_fom) || !exists(fn_psi)) + if (!exists(fn_fom) || !exists(fn_psi)) { Image Iin; @@ -978,16 +518,13 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) MDskew.setValue(EMDL_MICROGRAPH_SCORE_KURTOSIS, kurt); MDskew.write(fn_skew); - if (do_write_intermediate || do_only_write_intermediate) - { - Image Ipsi, Izscore; - Ipsi.setSamplingRateInHeader(down_angpix); - Izscore.setSamplingRateInHeader(down_angpix); - Ipsi()=Mangle; - Izscore()=Mscore; - Ipsi.write(fn_psi); - Izscore.write(fn_fom); - } + Image Ipsi, Izscore; + Ipsi.setSamplingRateInHeader(down_angpix); + Izscore.setSamplingRateInHeader(down_angpix); + Ipsi()=Mangle; + Izscore()=Mscore; + Ipsi.write(fn_psi); + Izscore.write(fn_fom); } else { @@ -1000,26 +537,15 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) Mscore.setXmippOrigin(); } - if (!do_only_write_intermediate) + if (myverb) { - if (myverb) - { - std::cout << " - Tracing filaments ..." << std::endl; - init_progress_bar(1); - } - - MetaDataTable MDmic = traceFilaments(Mscore, Mangle); - - if (myverb) progress_bar(1); - - if (MDmic.numberOfObjects() > 0) - { - if (myverb) std::cout << "Picked " << MDmic.numberOfObjects() << " particles " << std::endl; - FileName fn_pick = getOutputRootName(fn_mic) + "_" + fn_out + ".star"; - MDmic.write(fn_pick); - } + std::cout << " - Tracing filaments ..." << std::endl; + init_progress_bar(1); } + FileName fn_star = getOutputRootName(fn_mic) + "_" + fn_out + ".star"; + traceFilaments(fn_fom, fn_star); + if (myverb) std::cout << "done!" << std::endl; @@ -1075,11 +601,11 @@ void AmyloidFinder::run() void AmyloidFinder::finalise() { - long int barstep = XMIPP_MAX(1, fn_ori_micrographs.size() / 60); + long int barstep = XMIPP_MAX(1, fn_micrographs.size() / 60); if (verb > 0) { std::cout << " Generating output list of coordinate files ... " << std::endl; - init_progress_bar(fn_ori_micrographs.size()); + init_progress_bar(fn_micrographs.size()); } MetaDataTable MDin; @@ -1090,10 +616,10 @@ void AmyloidFinder::finalise() MetaDataTable MDresult; long total_nr_picked = 0; int nr_coord_files = 0; - for (long int imic = 0; imic < fn_ori_micrographs.size(); imic++) + for (long int imic = 0; imic < fn_micrographs.size(); imic++) { - FileName fn_root = getOutputRootName(fn_ori_micrographs[imic]); + FileName fn_root = getOutputRootName(fn_micrographs[imic]); FileName fn_fom = fn_root + "_" + fn_out + "_fom.mrc"; FileName fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; FileName fn_skew = fn_root + "_" + fn_out + "_skew.star"; @@ -1104,7 +630,7 @@ void AmyloidFinder::finalise() { MDcoords.addObject(); - MDcoords.setValue(EMDL_MICROGRAPH_NAME, fn_ori_micrographs[imic]); + MDcoords.setValue(EMDL_MICROGRAPH_NAME, fn_micrographs[imic]); MDcoords.setValue(EMDL_MICROGRAPH_COORDINATES, fn_pick); nr_coord_files++; @@ -1122,7 +648,7 @@ void AmyloidFinder::finalise() avg_fom /= nr_pick; // mis-use MetadataTable to conveniently make histograms and value-plots MDresult.addObject(); - MDresult.setValue(EMDL_MICROGRAPH_NAME, fn_ori_micrographs[imic]); + MDresult.setValue(EMDL_MICROGRAPH_NAME, fn_micrographs[imic]); MDresult.setValue(EMDL_PARTICLE_AUTOPICK_FOM, avg_fom); MDresult.setValue(EMDL_MLMODEL_GROUP_NR_PARTICLES, nr_pick); } @@ -1138,11 +664,8 @@ void AmyloidFinder::finalise() MDin.setValue(EMDL_MICROGRAPH_SCORE_KURTOSIS, kurt, imic); MDin.setValue(EMDL_MICROGRAPH_SCORE_SKEWNESS, skew, imic); - if (do_write_intermediate || do_only_write_intermediate) - { - MDin.setValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_fom, imic); - MDin.setValue(EMDL_MICROGRAPH_AUTOPICK_PSI, fn_psi, imic); - } + MDin.setValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_fom, imic); + MDin.setValue(EMDL_MICROGRAPH_AUTOPICK_PSI, fn_psi, imic); } if (verb > 0 && imic % 60 == 0) progress_bar(imic); @@ -1159,16 +682,16 @@ void AmyloidFinder::finalise() if (verb > 0 ) { - progress_bar(fn_ori_micrographs.size()); + progress_bar(fn_micrographs.size()); std::cout << " Saved list with " << nr_coord_files << " coordinate files in: " << fn_coords << std::endl; } if (verb > 0) { - std::cout << " Total number of particles from " << fn_ori_micrographs.size() << " micrographs is " << total_nr_picked << std::endl; + std::cout << " Total number of particles from " << fn_micrographs.size() << " micrographs is " << total_nr_picked << std::endl; long avg = 0; - if (fn_ori_micrographs.size() > 0) avg = ROUND((RFLOAT)total_nr_picked/fn_ori_micrographs.size()); + if (fn_micrographs.size() > 0) avg = ROUND((RFLOAT)total_nr_picked/fn_micrographs.size()); std::cout << " i.e. on average there were " << avg << " particles per micrograph" << std::endl; std::cout << " Now generating logfile.pdf ... " << std::endl; diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index 927bf8262..164c0c723 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -58,13 +58,10 @@ class AmyloidFinder RFLOAT signal_minres, signal_maxres, nonsignal_minres, nonsignal_maxres; // Threshold for selecting peaks in Zscore map - RFLOAT zscore_threshold, inifactor_threshold; + RFLOAT threshold; - // Number of new amyloid rungs per segment (particle) - int nr_rungs_per_segment; - - // Allowed psi-deviation per segment (particle) - RFLOAT psidiff_per_segment; + // Plot filament tracing results? + bool do_plot; // width and length of filaments (in A) for searching of 4.7A signal RFLOAT search_filament_length, search_filament_width; @@ -72,6 +69,9 @@ class AmyloidFinder // Minimum length for tracing filaments RFLOAT trace_filament_length, trace_filament_width; + // Python executable name + FileName fn_exe; + // Sampling of positions int shift_step, ori_xsize, ori_ysize, down_xsize, down_ysize; @@ -83,18 +83,14 @@ class AmyloidFinder // Some public parameters int iwidthmax, ilengthmax, imin_signal, imax_signal, imin_nonsignal, imax_nonsignal, large_box, crop_box; - RFLOAT amyloid_rung; std::vector circle; // All micrographs to autopick from - std::vector fn_micrographs, fn_ori_micrographs; + std::vector fn_micrographs; // Continue an old run: only estimate CTF if logfile WITH Final Values line does not yet exist, otherwise skip the tomogram bool do_only_unfinished; - // Write out (only) intermediate FOM and Psi maps? - bool do_write_intermediate, do_only_write_intermediate; - public: // Read command line arguments void read(int argc, char **argv, int rank = 0); @@ -118,17 +114,8 @@ class AmyloidFinder void getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, MultidimArray &Mangle, RFLOAT &skew, RFLOAT &kurt, bool myverb = false); - // Find next candidate coordinates around a previous coordinate - std::vector findNextCandidateCoordinates(AmyloidCoordinate &mycoord, MultidimArray &Mscore, MultidimArray &Mpsi); - - // Find next segment in an amyloid - AmyloidCoordinate findNextAmyloidCoordinate(AmyloidCoordinate &mycoord, MultidimArray &Mscore, MultidimArray &Mpsi); - - // Multi-threaded maxIndex - RFLOAT maxIndex_multithreaded(MultidimArray &Mscore, long int &imax, long int &jmax, int skip_sides = 0); - // Trace individual segments in the images based on the scores and angles - MetaDataTable traceFilaments(MultidimArray &Mscore, MultidimArray &Mpsi); + void traceFilaments(FileName &fn_fom, FileName &fn_star); // Run on one micrograph void processOneMicrograph(FileName fn_mic, bool myverb = false); diff --git a/src/amyloid_finder_mpi.cpp b/src/amyloid_finder_mpi.cpp index cac8fc0ce..720bba766 100644 --- a/src/amyloid_finder_mpi.cpp +++ b/src/amyloid_finder_mpi.cpp @@ -35,11 +35,10 @@ void AmyloidFinderMpi::read(int argc, char **argv) if (!node->isLeader()) verb = 0; - if (do_write_intermediate && node->isLeader()) - std::cerr << "WARNING : --write_intermediates is heavy on disc I/O and is not advised in parallel execution." << std::endl; - // Possibly also read parallelisation-dependent variables here + if (do_plot) REPORT_ERROR("ERROR: cannot use --plot in parallel execution!"); + // Print out MPI info printMpiNodesMachineNames(*node); } diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 0abe0cbd9..c96dff028 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -941,6 +941,7 @@ void JobWindow::initialiseAutopickWindow() current_y += STEPY/2; place("amyloid_threshold"); + place("do_amyloid_plot"); group4->end(); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 49b9cb042..163232541 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2050,7 +2050,8 @@ Kappa = 0.3 means that the curvature of the picked helical tubes should not be l Kappa ~ 0.05 is recommended for long and straight tubes (e.g. TMV, VipA/VipB and AChR tubes) while 0.20 ~ 0.40 seems suitable for flexible ones (e.g. ParM and MAVS-CARD filaments)."); joboptions["helical_tube_length_min"] = JobOption("Minimum length (A): ", 400, 100, 1000, 10, "Minimum length (in Angstroms) of helical tubes for auto-picking. \ Helical tubes with shorter lengths will not be picked. Note that a long helical tube seen by human eye might be treated as short broken pieces due to low FOM values or high picking threshold."); - joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 0.5, 0.2, 2, 0.1, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); + joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 0.25, 0.05, 1, 0.05, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); + joboptions["do_amyloid_plot"] = JobOption("Plot results per micrograph?", false, "Set this option to Yes to visualise intermediate results for amyloid tracing, which may help with setting values for filament length, width and picking threshold. Don't use in parallel or in submission to the queue."); } @@ -2310,6 +2311,14 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector 1 || joboptions["do_queue"].getBoolean()) + { + error_message = "You cannot use parallel execution or job submission when plotting intermediate results."; + return false; + } + } command += " --odir " + outputname; command += " --pickname autopick"; @@ -2332,10 +2341,10 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Wed, 5 Feb 2025 12:31:28 +0000 Subject: [PATCH 059/189] make sure schemegui uses python from relion-5.0 conda environment --- CMakeLists.txt | 2 +- scripts/{schemegui.py => schemegui.in} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename scripts/{schemegui.py => schemegui.in} (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d9d4518de..10471e460 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -560,6 +560,7 @@ list(APPEND RELION_PYTHON_WRAPPERS python_tomo_denoise filament_selection trace_amyloids + schemegui ) foreach (SCRIPT_FILE ${RELION_PYTHON_WRAPPERS}) @@ -614,7 +615,6 @@ list(APPEND RELION_SCRIPT_FILES star_datablock_ctfdat qsub.csh it.py - schemegui.py ) add_custom_target(copy_scripts ALL) diff --git a/scripts/schemegui.py b/scripts/schemegui.in similarity index 99% rename from scripts/schemegui.py rename to scripts/schemegui.in index 8a7f0c9dd..c153632f6 100755 --- a/scripts/schemegui.py +++ b/scripts/schemegui.in @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!@PYTHON_EXE_PATH@ """ relion_it.py From 2e8888b710d66c3c3c045c6e91e8f4ef5bb73301 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 6 Feb 2025 09:31:57 +0000 Subject: [PATCH 060/189] dont retrace already traced micrographs in amyloid_finder, and deal with empty micrographs --- scripts/trace_amyloids.in | 3 +++ src/amyloid_finder.cpp | 35 +++++++++++++++++++++++++++-------- src/amyloid_finder.h | 7 +++++-- src/gui_jobwindow.cpp | 1 + src/pipeline_jobs.cpp | 2 ++ 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 1c45e0d2b..943f69b94 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -6,6 +6,7 @@ import starfile from skimage.filters import gaussian from skimage.transform import probabilistic_hough_line from skimage.morphology import skeletonize +from pathlib import Path import math import pandas as pd import argparse @@ -249,3 +250,5 @@ if __name__ == "__main__": xy = {'rlnCoordinateX': coords[:, 0], 'rlnCoordinateY': coords[:, 1]} df = pd.DataFrame(xy) starfile.write(df, args.output, overwrite=True) + else: + Path(args.output).touch() diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index af108fbce..b8e3fb039 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -43,6 +43,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) trace_filament_width = textToFloat(parser.getOption("--trace_filament_width", "Minimum width occupied by a traced filaments (in A)", "200")); trace_filament_length = textToFloat(parser.getOption("--trace_filament_length", "Minimum length of traced filaments (in A)", "300")); do_plot = parser.checkOption("--plot", "Display images with intermediate tracing results for each micrograph"); + do_redo_tracing = parser.checkOption("--redo_all_tracing", "Ignore any autopick.star files already present and redo all tracing."); fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_trace_amyloids"); int expert_section = parser.addSection("Expert options (typically no need to change)"); @@ -88,6 +89,24 @@ void AmyloidFinder::initialise(bool is_leader) fn_micrographs.push_back(fn_in); } + fn_ori_micrographs = fn_micrographs; + // If we're continuing an old run, see which micrographs have not been finished yet... + if (do_only_unfinished && !do_redo_tracing) + { + if (verb > 0) + { + std::cout << " + Skipping those micrographs for which coordinate file already exists" << std::endl; + } + std::vector fns_todo; + for (long int imic = 0; imic < fn_micrographs.size(); imic++) + { + FileName fn_tmp = getOutputRootName(fn_micrographs[imic]) + "_" + fn_out + ".star"; + if (!exists(fn_tmp)) + fns_todo.push_back(fn_micrographs[imic]); + } + fn_micrographs = fns_todo; + } + // If there is nothing to do, then go out of initialise if (fn_micrographs.size() == 0) { @@ -601,11 +620,11 @@ void AmyloidFinder::run() void AmyloidFinder::finalise() { - long int barstep = XMIPP_MAX(1, fn_micrographs.size() / 60); + long int barstep = XMIPP_MAX(1, fn_ori_micrographs.size() / 60); if (verb > 0) { std::cout << " Generating output list of coordinate files ... " << std::endl; - init_progress_bar(fn_micrographs.size()); + init_progress_bar(fn_ori_micrographs.size()); } MetaDataTable MDin; @@ -616,10 +635,10 @@ void AmyloidFinder::finalise() MetaDataTable MDresult; long total_nr_picked = 0; int nr_coord_files = 0; - for (long int imic = 0; imic < fn_micrographs.size(); imic++) + for (long int imic = 0; imic < fn_ori_micrographs.size(); imic++) { - FileName fn_root = getOutputRootName(fn_micrographs[imic]); + FileName fn_root = getOutputRootName(fn_ori_micrographs[imic]); FileName fn_fom = fn_root + "_" + fn_out + "_fom.mrc"; FileName fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; FileName fn_skew = fn_root + "_" + fn_out + "_skew.star"; @@ -630,7 +649,7 @@ void AmyloidFinder::finalise() { MDcoords.addObject(); - MDcoords.setValue(EMDL_MICROGRAPH_NAME, fn_micrographs[imic]); + MDcoords.setValue(EMDL_MICROGRAPH_NAME, fn_ori_micrographs[imic]); MDcoords.setValue(EMDL_MICROGRAPH_COORDINATES, fn_pick); nr_coord_files++; @@ -682,16 +701,16 @@ void AmyloidFinder::finalise() if (verb > 0 ) { - progress_bar(fn_micrographs.size()); + progress_bar(fn_ori_micrographs.size()); std::cout << " Saved list with " << nr_coord_files << " coordinate files in: " << fn_coords << std::endl; } if (verb > 0) { - std::cout << " Total number of particles from " << fn_micrographs.size() << " micrographs is " << total_nr_picked << std::endl; + std::cout << " Total number of particles from " << fn_ori_micrographs.size() << " micrographs is " << total_nr_picked << std::endl; long avg = 0; - if (fn_micrographs.size() > 0) avg = ROUND((RFLOAT)total_nr_picked/fn_micrographs.size()); + if (fn_ori_micrographs.size() > 0) avg = ROUND((RFLOAT)total_nr_picked/fn_ori_micrographs.size()); std::cout << " i.e. on average there were " << avg << " particles per micrograph" << std::endl; std::cout << " Now generating logfile.pdf ... " << std::endl; diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index 164c0c723..edf1eea14 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -69,6 +69,9 @@ class AmyloidFinder // Minimum length for tracing filaments RFLOAT trace_filament_length, trace_filament_width; + // Redo existing tracings? + bool do_redo_tracing; + // Python executable name FileName fn_exe; @@ -85,8 +88,8 @@ class AmyloidFinder int iwidthmax, ilengthmax, imin_signal, imax_signal, imin_nonsignal, imax_nonsignal, large_box, crop_box; std::vector circle; - // All micrographs to autopick from - std::vector fn_micrographs; + // All and selected micrographs to autopick from + std::vector fn_ori_micrographs, fn_micrographs; // Continue an old run: only estimate CTF if logfile WITH Final Values line does not yet exist, otherwise skip the tomogram bool do_only_unfinished; diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index c96dff028..8e740f89e 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -942,6 +942,7 @@ void JobWindow::initialiseAutopickWindow() place("amyloid_threshold"); place("do_amyloid_plot"); + place("redo_all_tracing"); group4->end(); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 163232541..19f372585 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2052,6 +2052,7 @@ Kappa ~ 0.05 is recommended for long and straight tubes (e.g. TMV, VipA/VipB and Helical tubes with shorter lengths will not be picked. Note that a long helical tube seen by human eye might be treated as short broken pieces due to low FOM values or high picking threshold."); joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 0.25, 0.05, 1, 0.05, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); joboptions["do_amyloid_plot"] = JobOption("Plot results per micrograph?", false, "Set this option to Yes to visualise intermediate results for amyloid tracing, which may help with setting values for filament length, width and picking threshold. Don't use in parallel or in submission to the queue."); + joboptions["redo_all_tracing"] = JobOption("Redo tracing for all micrographs?", false, "Set this option to Yes to redo the tracing for all micrographs (and overwrite any existing output coordinate star files that may exist). If set to No, then only micrographs for which the star files don't exist yet will be traced. This is useful for speeding up on-the-fly processing in continuation jobs."); } @@ -2319,6 +2320,7 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Thu, 6 Feb 2025 09:44:29 +0000 Subject: [PATCH 061/189] repaired bug in logfile plot generation --- src/amyloid_finder.cpp | 37 ++++--------------------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index b8e3fb039..104b38629 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -656,21 +656,10 @@ void AmyloidFinder::finalise() MD.read(fn_pick); long nr_pick = MD.numberOfObjects(); total_nr_picked += nr_pick; - if (MD.containsLabel(EMDL_PARTICLE_AUTOPICK_FOM)) - { - RFLOAT fom, avg_fom = 0.; - FOR_ALL_OBJECTS_IN_METADATA_TABLE(MD) - { - MD.getValue(EMDL_PARTICLE_AUTOPICK_FOM, fom); - avg_fom += fom; - } - avg_fom /= nr_pick; - // mis-use MetadataTable to conveniently make histograms and value-plots - MDresult.addObject(); - MDresult.setValue(EMDL_MICROGRAPH_NAME, fn_micrographs[imic]); - MDresult.setValue(EMDL_PARTICLE_AUTOPICK_FOM, avg_fom); - MDresult.setValue(EMDL_MLMODEL_GROUP_NR_PARTICLES, nr_pick); - } + // mis-use MetadataTable to conveniently make histograms and value-plots + MDresult.addObject(); + MDresult.setValue(EMDL_MICROGRAPH_NAME, fn_micrographs[imic]); + MDresult.setValue(EMDL_MLMODEL_GROUP_NR_PARTICLES, nr_pick); } if (exists(fn_skew)) @@ -740,24 +729,6 @@ void AmyloidFinder::finalise() delete plot2D; } - CPlot2D *plot2Dc=new CPlot2D("Average autopick FOM for all micrographs"); - MDresult.addToCPlot2D(plot2Dc, EMDL_UNDEFINED, EMDL_PARTICLE_AUTOPICK_FOM, 1.); - plot2Dc->SetDrawLegend(false); - fn_eps = fn_odir + "all_FOMs.eps"; - plot2Dc->OutputPostScriptPlot(fn_eps); - all_fn_eps.push_back(fn_eps); - delete plot2Dc; - if (MDresult.numberOfObjects() > 3) - { - CPlot2D *plot2Dd=new CPlot2D(""); - MDresult.columnHistogram(EMDL_PARTICLE_AUTOPICK_FOM,histX,histY,0, plot2Dd); - fn_eps = fn_odir + "histogram_FOMs.eps"; - plot2Dd->SetTitle("Histogram of average autopick FOM per micrograph"); - plot2Dd->OutputPostScriptPlot(fn_eps); - all_fn_eps.push_back(fn_eps); - delete plot2Dd; - } - CPlot2D *plot2De=new CPlot2D("Skewness of FOM for all micrographs"); MDin.addToCPlot2D(plot2De, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_SKEWNESS, 1.); plot2De->SetDrawLegend(false); From dcbae9cac5e4f3a820f4141fc2d8b69ceff2be94 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 11 Feb 2025 08:49:28 +0000 Subject: [PATCH 062/189] added local symmetry expansion --- src/apps/particle_symmetry_expand.cpp | 127 ++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 20 deletions(-) diff --git a/src/apps/particle_symmetry_expand.cpp b/src/apps/particle_symmetry_expand.cpp index ecdea938d..30065b882 100644 --- a/src/apps/particle_symmetry_expand.cpp +++ b/src/apps/particle_symmetry_expand.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -29,14 +30,15 @@ class particle_symmetry_expand_parameters { public: - FileName fn_sym, fn_in, fn_out; + FileName fn_sym, fn_localsym, fn_in, fn_out; // Helical symmetry - bool do_helix, do_ignore_optics; + bool do_helix, do_localsym, do_ignore_optics, is_tomo; RFLOAT twist, rise, angpix; int nr_asu, frac_sampling; RFLOAT frac_range; ObservationModel obsModel; + std::vector > locsym_operators; // I/O Parser IOParser parser; @@ -68,6 +70,18 @@ class particle_symmetry_expand_parameters frac_range = textToFloat(parser.getOption("--frac_range", "Range of the rise [-0.5, 0.5> to be sampled", "0.5")); do_ignore_optics = parser.checkOption("--ignore_optics", "Provide this option for relion-3.0 functionality, without optics groups"); + // Local symmetry + int localsym_section = parser.addSection("Local symmetry"); + fn_localsym = parser.getOption("--local_sym", "STAR file with local symmetry operators", ""); + do_localsym = (fn_localsym == "") ? false : true; + + if (do_localsym) + { + if (fn_sym != "C1") + REPORT_ERROR("Provide either --local_sym OR --helix, but not both!"); + + } + // Check for errors in the command-line option if (parser.checkForErrors()) REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); @@ -85,12 +99,29 @@ class particle_symmetry_expand_parameters void run() { MetaDataTable DFi, DFo; - RFLOAT rot, tilt, psi, x, y; - RFLOAT rotp, tiltp, psip, xp, yp; + RFLOAT rot, tilt, psi, x, y, z = 0; + RFLOAT rotp, tiltp, psip, xp, yp, zp = 0; Matrix2D L(3,3), R(3,3); // A matrix from the list RFLOAT z_start, z_stop, z_step; // for helices SymList SL; + if (do_ignore_optics) + { + DFi.read(fn_in); + } + else + { + ObservationModel::loadSafely(fn_in, obsModel, DFi, "particles", 1, false); + if (obsModel.opticsMdt.numberOfObjects() == 0) + { + std::cerr << " + WARNING: could not read optics groups table, proceeding without it ..." << std::endl; + DFi.read(fn_in); + do_ignore_optics = true; + } + } + + bool do_z = DFi.containsLabel(EMDL_ORIENT_ORIGIN_Z_ANGSTROM); + // For helices, pre-calculate expansion range if (do_helix) { @@ -111,6 +142,22 @@ class particle_symmetry_expand_parameters } std::cout << " Helical: z_start= " << z_start << " z_stop= " << z_stop << " z_step= " << z_step << std::endl; } + else if (do_localsym) + { + std::vector fn_mask_list; + std::vector > > op_list; + // set angpix to 1, so translations keep being in Angstroms + readRelionFormatMasksAndOperators(fn_localsym, fn_mask_list, op_list, 1., false); + for (int imask = 0; imask < fn_mask_list.size(); imask++) + { + for (int iop = 0; iop < op_list[imask].size(); iop++) + { + Matrix2D op_mat; + Localsym_operator2matrix(op_list[imask][iop], op_mat, false); + locsym_operators.push_back(op_mat); + } + } + } else { SL.read_sym_file(fn_sym); @@ -118,20 +165,6 @@ class particle_symmetry_expand_parameters REPORT_ERROR("ERROR Nothing to do. Provide a point group with symmetry!"); } - if (do_ignore_optics) - { - DFi.read(fn_in); - } - else - { - ObservationModel::loadSafely(fn_in, obsModel, DFi, "particles", 1, false); - if (obsModel.opticsMdt.numberOfObjects() == 0) - { - std::cerr << " + WARNING: could not read optics groups table, proceeding without it ..." << std::endl; - DFi.read(fn_in); - do_ignore_optics = true; - } - } int barstep = XMIPP_MAX(1, DFi.numberOfObjects()/ 60); init_progress_bar(DFi.numberOfObjects()); @@ -146,13 +179,18 @@ class particle_symmetry_expand_parameters DFi.getValue(EMDL_ORIENT_PSI, psi); DFi.getValue(EMDL_ORIENT_ORIGIN_X_ANGSTROM, x); DFi.getValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, y); + if (do_z) DFi.getValue(EMDL_ORIENT_ORIGIN_Z_ANGSTROM, z); if (do_helix) { + if (do_z) + { + // TODO: make this work for 3D coordinates too!!!! + REPORT_ERROR("ERROR: helical symmetry expansion has not yet been implemented for 3D images..."); + } for (RFLOAT z_pos = z_start; z_pos <= z_stop; z_pos += z_step) { - // TMP - //if (fabs(z_pos) > 0.01) + if (fabs(z_pos) > 0.01) { // Translation along the X-axis in the rotated image is along the helical axis in 3D. // Tilted images shift less: sin(tilt) @@ -168,6 +206,55 @@ class particle_symmetry_expand_parameters } } } + else if (do_localsym) + { + // Get the original line from the STAR file + DFo.addObject(); + DFo.setObject(DFi.getObject()); + + // And loop over all symmetry mates + for (int ilocsym = 0; ilocsym < locsym_operators.size(); ilocsym++) + { + DFo.addObject(); + DFo.setObject(DFi.getObject()); + + // Get rotation from euler angles of this particle + Matrix2D euler(3, 3), neweuler(3, 3), my_op(3,3); + Euler_angles2matrix(rot, tilt, psi, euler, false); + my_op = locsym_operators[ilocsym]; + + // get com of local symmetry mask, and rotate it + Matrix1D com(3), newcom(3); + XX(com) = my_op(0,3); + YY(com) = my_op(1,3); + ZZ(com) = my_op(2,3); + newcom = euler * com; + xp = x + XX(newcom); + yp = y + YY(newcom); + zp = z + ZZ(newcom); + DFo.setValue(EMDL_ORIENT_ORIGIN_X_ANGSTROM, xp); + DFo.setValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, yp); + if (do_z) DFo.setValue(EMDL_ORIENT_ORIGIN_Z_ANGSTROM, zp); + + // Apply rotation to the Euler angles too + my_op.resize(3,3); + neweuler = euler * my_op.inv(); + Euler_matrix2angles(neweuler, rotp, tiltp, psip); + DFo.setValue(EMDL_ORIENT_ROT, rotp); + DFo.setValue(EMDL_ORIENT_TILT, tiltp); + DFo.setValue(EMDL_ORIENT_PSI, psip); + + if (do_z) + { + // For subtomograms: give each particle a new partname + FileName partname; + DFo.getValue(EMDL_TOMO_PARTICLE_NAME, partname); + partname += "-" + integerToString(ilocsym); + DFo.setValue(EMDL_TOMO_PARTICLE_NAME, partname); + } + } + + } else { // Get the original line from the STAR file From 9fc60715cac3abb391501378d2358176aef822e8 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 11 Feb 2025 13:50:15 +0000 Subject: [PATCH 063/189] default psistep in find_amyloid to 5 degrees to prevent picking ice; also grow mask for nonsignal --- src/amyloid_finder.cpp | 181 ++++++++++++++++++++++++++++++----------- src/amyloid_finder.h | 3 + 2 files changed, 137 insertions(+), 47 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 104b38629..18ecec20f 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -33,7 +33,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) nr_threads = textToInteger(parser.getOption("--j", "Number of threads to us in parallel", "1")); int search_section = parser.addSection("Filament searching options "); - psi_step = textToFloat(parser.getOption("--psi_step", "Angular sampling rate (in degrees)", "10.")); + psi_step = textToFloat(parser.getOption("--psi_step", "Angular sampling rate (in degrees)", "5.")); shift_step = textToInteger(parser.getOption("--shift_step", "Step in shifts to search (in downscaled pixels)", "5")); search_filament_width = textToFloat(parser.getOption("--search_filament_width", "Width of searching image (in A)", "50")); search_filament_length = textToFloat(parser.getOption("--search_filament_length", "Length of searching image (in A)", "250")); @@ -191,7 +191,43 @@ RFLOAT AmyloidFinder::getPsiDiff(RFLOAT psi1, RFLOAT psi2) return fabs(psidiff); } +MultidimArray AmyloidFinder::growNonSignalMask(MultidimArray &inmask, int extend_size) +{ + + MultidimArray Mresult = inmask; + RFLOAT extend_ini_mask2 = extend_size * extend_size; + +#pragma omp parallel for num_threads(nr_threads) + for (long int i=STARTINGY(inmask)+extend_size; i<=FINISHINGY(inmask)-extend_size; i++) + { + for (long int j=STARTINGX(inmask)+extend_size; j<=FINISHINGX(inmask)-extend_size; j++) + { + // only extend from 1 values + if (A2D_ELEM(inmask, i, j) > 0.99) + { + for (long int ip = i - extend_size; ip <= i + extend_size; ip++) + { + for (long int jp = j - extend_size; jp <= j + extend_size; jp++) + { + // only check distance if neighbouring pixel is zero + if (A2D_ELEM(inmask, ip, jp) < 0.01) + { + RFLOAT r2 = (RFLOAT)( (ip-i)*(ip-i)+ (jp-j)*(jp-j) ); + // Set original voxel to 1 if a neghouring with Im()=1 is within distance extend_ini_mask + if (r2 < extend_ini_mask2) + { + A2D_ELEM(Mresult, ip, jp) = 1.; + } + } + } + } + } + } + } + return Mresult; + +} void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, MultidimArray &Mangle, RFLOAT &skew, RFLOAT &kurt, bool myverb) { @@ -310,19 +346,24 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi } // end loop ypos } // end for xpos - //Image It0; - //It0()= rotated_scores_perline[ipsi]; - //FileName fnt0="It0_psi"+ integerToString(ipsi)+".spi"; - //It0.write(fnt0); - //std::cerr <<" written: "< It0; + It0()= rotated_scores_perline[ipsi]; + FileName fnt0="It0_scores_psi"+ integerToString(ipsi)+".spi"; + It0.write(fnt0); + std::cerr <<" written: "< &image, Multi } // end loop ipass } // end loop xpos } // end for ipassy - } // end for ypos + } // end for ypos - //Image It; - //It()= rotated_scores[ipsi]; - //FileName fnt="It_psi"+ integerToString(ipsi)+".spi"; - //It.write(fnt); - //std::cerr <<" written: "< &image, Multi RFLOAT psi = getPsiAngle(ipsi); selfRotate(rotated_scores[ipsi], -psi); selfRotate(rotated_nonscores[ipsi], -psi); + + /* + Image It; + It()= rotated_scores[ipsi]; + FileName fnt="It_scores_psi"+ integerToString(ipsi)+".spi"; + It.write(fnt); + std::cerr <<" written: "< Msum, Mnonsum, Mnonscore; + MultidimArray Msum, Mnonsum, Mnonscore, Mneighbour, Mneighbour2; Msum.resize(Mangle); Mnonsum.resize(Mangle); Mnonscore.resize(Mangle); + Mneighbour.resize(Mangle); + Mneighbour2.resize(Mangle); // This can't be parallelised efficiently because need to protect Msums, Mscore and Mangle from simultaneous writing... // Calculate Z-scores over psi: (max_psi - avg_psi) /stddev_psi @@ -405,57 +456,102 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi { A2D_ELEM(Mscore, cen_ypos, cen_xpos) = myscore; A2D_ELEM(Mangle, cen_ypos, cen_xpos) = mypsi; + int ipsi_nb = (ipsi == 0) ? nr_psi - 1 : ipsi - 1; + A2D_ELEM(Mneighbour, cen_ypos, cen_xpos) = A2D_ELEM(rotated_scores[ipsi_nb], cen_ypos/shift_step, cen_xpos/shift_step); + ipsi_nb = (ipsi == nr_psi - 1) ? 0 : ipsi + 1; + A2D_ELEM(Mneighbour, cen_ypos, cen_xpos) += A2D_ELEM(rotated_scores[ipsi_nb], cen_ypos/shift_step, cen_xpos/shift_step); } if (mynonscore > A2D_ELEM(Mnonscore, cen_ypos, cen_xpos)) { A2D_ELEM(Mnonscore, cen_ypos, cen_xpos) = mynonscore; + int ipsi_nb = (ipsi == 0) ? nr_psi - 1 : ipsi - 1; + A2D_ELEM(Mneighbour2, cen_ypos, cen_xpos) = A2D_ELEM(rotated_nonscores[ipsi_nb], cen_ypos/shift_step, cen_xpos/shift_step); + ipsi_nb = (ipsi == nr_psi - 1) ? 0 : ipsi + 1; + A2D_ELEM(Mneighbour2, cen_ypos, cen_xpos) += A2D_ELEM(rotated_nonscores[ipsi_nb], cen_ypos/shift_step, cen_xpos/shift_step); } } } } - // Now need to subtract the Mscore for the best psi, as the mean and stddev should be calculated for the non-signal! - RFLOAT sum=0., sum2=0.; +//#define DEBUG_FOM +#ifdef DEBUG_FOM + Image It, It2; + It()= Mscore; + FileName fnt="Mscore.spi"; + It.write(fnt); + std::cerr <<" written: "< 0.) Zscore_signal = (DIRECT_MULTIDIM_ELEM(Mscore, n) - DIRECT_MULTIDIM_ELEM(Msum, n)) / DIRECT_MULTIDIM_ELEM(Msum, n); + DIRECT_MULTIDIM_ELEM(Mscore, n) = Zscore_signal; + +#ifdef DEBUG_FOM + DIRECT_MULTIDIM_ELEM(It(), n) = Zscore_signal; +#endif // Also for non-signal - DIRECT_MULTIDIM_ELEM(Mnonsum, n) -= DIRECT_MULTIDIM_ELEM(Mnonscore, n); - DIRECT_MULTIDIM_ELEM(Mnonsum, n) /= (RFLOAT)(nr_psi-1); + DIRECT_MULTIDIM_ELEM(Mnonsum, n) -= DIRECT_MULTIDIM_ELEM(Mnonscore, n) + DIRECT_MULTIDIM_ELEM(Mneighbour2, n); + DIRECT_MULTIDIM_ELEM(Mnonsum, n) /= (RFLOAT)(nr_psi-3); RFLOAT Zscore_nonsignal = 0.; if (DIRECT_MULTIDIM_ELEM(Mnonsum, n) > 0.) + { Zscore_nonsignal = (DIRECT_MULTIDIM_ELEM(Mnonscore, n) - DIRECT_MULTIDIM_ELEM(Mnonsum, n)) / DIRECT_MULTIDIM_ELEM(Mnonsum, n); - // Avoid negative non-signals - //if (Zscore_nonsignal < 0.) Zscore_nonsignal = 0.; - // Calculate a combined score that emphasizes high signals, but low non-signals: - // Z_signal * (1 - Z_nonsignal) - // If lower than 0, just set score to 0 - DIRECT_MULTIDIM_ELEM(Mscore, n) = XMIPP_MAX(0., Zscore_signal * (1. - Zscore_nonsignal)); - /* - if (DIRECT_MULTIDIM_ELEM(Mscore, n)>0.5) - { - std::cerr << "score= " << DIRECT_MULTIDIM_ELEM(Mscore, n)<< " Zscore_signal= " << Zscore_signal << " Zscore_nonsignal= " << Zscore_nonsignal - << " Msum= " << DIRECT_MULTIDIM_ELEM(Msum, n) << " Mnonsum= " << DIRECT_MULTIDIM_ELEM(Mnonsum, n) - << " angle= " << DIRECT_MULTIDIM_ELEM(Mangle, n) << " nonangle= " << DIRECT_MULTIDIM_ELEM(Mnonangle, n)< &image, Multi kurt *= n*(n+1)/((n-1)*(n-2)*(n-3)); skew *= n/((n-1)*(n-2)); - /* - std::cerr << " skewness= " << skew << " excess kurtosis= " << kurt -3 << std::endl; - Image Itt; - Itt()=Msum; - Itt.write("Mscore_avg_over_psi.spi"); - Itt()=Mscore; - Itt.write("Mscore_Zscore_over_psi.spi"); - */ - if (myverb) progress_bar(nr_psi); } diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index edf1eea14..cd9ff67c2 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -113,6 +113,9 @@ class AmyloidFinder // Difference between two psi angles that range from 0-180 RFLOAT getPsiDiff(RFLOAT psi1, RFLOAT psi2); + // grow nonsignal mask + MultidimArray growNonSignalMask(MultidimArray &inmask, int dist); + // Loop over all psi-angles and coordinates to get accumulated score and angle image for a given micrograph void getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, MultidimArray &Mangle, RFLOAT &skew, RFLOAT &kurt, bool myverb = false); From e5a8b7bf8fd5884548d5c6a87b84b65185c09955 Mon Sep 17 00:00:00 2001 From: jshi7 Date: Thu, 13 Feb 2025 11:37:48 +0000 Subject: [PATCH 064/189] =?UTF-8?q?Jenny's=20new=20pi=20=F0=9F=A5=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/trace_amyloids.in | 183 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 178 insertions(+), 5 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 943f69b94..f9847a433 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -10,7 +10,161 @@ from pathlib import Path import math import pandas as pd import argparse +""" Full assembly of the parts to form the complete network """ +""" Parts of the U-Net model """ +import lightning as L +import torch +import torch.nn as nn +import torch.nn.functional as F + +class DoubleConv(nn.Module): + """(convolution => [BN] => ReLU) * 2""" + + def __init__(self, in_channels, out_channels, mid_channels=None): + super().__init__() + + if not mid_channels: + mid_channels = out_channels + self.double_conv = nn.Sequential( + nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False), + nn.BatchNorm2d(mid_channels), + nn.ReLU(inplace=True), + nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False), + nn.BatchNorm2d(out_channels), + nn.ReLU(inplace=True) + ) + + def forward(self, x): + return self.double_conv(x) + + +class Down(nn.Module): + """Downscaling with maxpool then double conv""" + + def __init__(self, in_channels, out_channels): + super().__init__() + self.maxpool_conv = nn.Sequential( + nn.MaxPool2d(2), + DoubleConv(in_channels, out_channels) + ) + + def forward(self, x): + return self.maxpool_conv(x) + + +class Up(nn.Module): + """Upscaling then double conv""" + + def __init__(self, in_channels, out_channels, bilinear=True): + super().__init__() + + # if bilinear, use the normal convolutions to reduce the number of channels + if bilinear: + self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True) + self.conv = DoubleConv(in_channels, out_channels, in_channels // 2) + else: + self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2) + self.conv = DoubleConv(in_channels, out_channels) + + def forward(self, x1, x2): + x1 = self.up(x1) + # input is CHW + diffY = x2.size()[2] - x1.size()[2] + diffX = x2.size()[3] - x1.size()[3] + + x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2, + diffY // 2, diffY - diffY // 2]) + # if you have padding issues, see + # https://github.com/HaiyongJiang/U-Net-Pytorch-Unstructured-Buggy/commit/0e854509c2cea854e247a9c615f175f76fbb2e3a + # https://github.com/xiaopeng-liao/Pytorch-UNet/commit/8ebac70e633bac59fc22bb5195e513d5832fb3bd + x = torch.cat([x2, x1], dim=1) + return self.conv(x) + + +class OutConv(nn.Module): + def __init__(self, in_channels, out_channels): + super(OutConv, self).__init__() + self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1) + + def forward(self, x): + return self.conv(x) + + +class UNet(L.LightningModule): + def __init__(self, n_channels, n_classes, bilinear=False): + super(UNet, self).__init__() + self.n_channels = n_channels + self.n_classes = n_classes + self.bilinear = bilinear + #self.accuracy = BinaryAccuracy() + + + self.inc = (DoubleConv(n_channels, 64)) + self.down1 = (Down(64, 128)) + self.down2 = (Down(128, 256)) + self.down3 = (Down(256, 512)) + factor = 2 if bilinear else 1 + self.down4 = (Down(512, 1024 // factor)) + self.up1 = (Up(1024, 512 // factor, bilinear)) + self.up2 = (Up(512, 256 // factor, bilinear)) + self.up3 = (Up(256, 128 // factor, bilinear)) + self.up4 = (Up(128, 64, bilinear)) + self.outc = (OutConv(64, n_classes)) + + def forward(self, x): + x1 = self.inc(x) + x2 = self.down1(x1) + x3 = self.down2(x2) + x4 = self.down3(x3) + x5 = self.down4(x4) + x = self.up1(x5, x4) + x = self.up2(x, x3) + x = self.up3(x, x2) + x = self.up4(x, x1) + logits = self.outc(x) + return logits + + def use_checkpointing(self): + self.inc = torch.utils.checkpoint(self.inc) + self.down1 = torch.utils.checkpoint(self.down1) + self.down2 = torch.utils.checkpoint(self.down2) + self.down3 = torch.utils.checkpoint(self.down3) + self.down4 = torch.utils.checkpoint(self.down4) + self.up1 = torch.utils.checkpoint(self.up1) + self.up2 = torch.utils.checkpoint(self.up2) + self.up3 = torch.utils.checkpoint(self.up3) + self.up4 = torch.utils.checkpoint(self.up4) + self.outc = torch.utils.checkpoint(self.outc) + + def training_step(self, batch, batch_idx): + # training_step defines the train loop. + x, y = batch + pred = self(x) + loss = F.binary_cross_entropy_with_logits(pred, y, pos_weight=torch.tensor(50)) + self.log("train_loss", loss) + return loss + + def validation_step(self, batch, batch_idx): + # training_step defines the train loop. + x, y = batch + pred = self(x) + loss = F.binary_cross_entropy_with_logits(pred, y, pos_weight=torch.tensor(50)) + self.log("val_loss", loss) + return loss + + def test_step(self, batch, batch_idx): + x, y = batch + pred = self(x) + loss = F.binary_cross_entropy_with_logits(pred, y, pos_weight=torch.tensor(50)) + self.log("test_loss", loss) + return loss + + def configure_optimizers(self): + optimizer = torch.optim.Adam(self.parameters(), lr=1e-3) + return optimizer + +#------------------------------------------------------------------------------------------ def load_mrc(mrc_fn: str, multiply_global_origin: bool = True): mrc_file_handle = mrcfile.open(mrc_fn, "r") @@ -83,7 +237,7 @@ def prune_lines(inlines, mind, min_ang, max_merge): for line in lines: lengths = np.append(lengths, np.linalg.norm(line[0]-line[1])) merged = np.append(merged, 1) - sortidx = np.argsort(lengths); + sortidx = np.argsort(lengths) min_ang_rad = math.radians(min_ang) NN = len(sortidx) @@ -222,21 +376,40 @@ def pick_filaments(score, radius, threshold, filaments_length, filaments_plot): if __name__ == "__main__": parser = argparse.ArgumentParser('Scripts for picking filaments in FOM maps') - parser.add_argument('-i', '--input', help='input image to pick filaments in') + parser.add_argument('-if', '--input_fom', help='input fom to pick filaments in') + parser.add_argument('-ip', '--input_psi', help='input psi to pick filaments in') + parser.add_argument('-m', '--model_path', help="path to model") parser.add_argument('-o', '--output', help='output star file with coordinates') parser.add_argument('-r', '--radius', type=int, help='radius of the regions to extract (in A)') parser.add_argument('-t', '--threshold', default=0.5, type=float, help='FOM threshold at which to terminate region extraction (default: 0.5)') parser.add_argument('-l', '--length', default=-1, type=int, help='minimum length of straight filament segments to be picked (in Angstrom) (default: twice --radius)') parser.add_argument('-s', '--scale', type=float, default=1, help='Multiply outputcoordinates by this factor. (default: 1)') parser.add_argument('-fp', '--plot', action='store_true', help='flag for filament start-end picking plus plotting of its intermediate stages (useful for tuning parameters).') + parser.add_argument('-d', '--device', help="Which GPU to use", default="cuda:0") args = parser.parse_args() - image, pixel_size, origin = load_mrc(args.input) + fom_image, pixel_size, origin = load_mrc(args.input_fom) + psi_image, _, _ = load_mrc(args.input_psi) + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + + # For Jenny, load network add path to weights as an argument (can use the curreent one as default) + model = UNet.load_from_checkpoint(args.model_path, n_channels=2, n_classes=1) + model.eval() + # FOr Jenny, put on device + model = model.to(device) + # For Jenny, concatenate the two images and run network + fp = torch.from_numpy(np.stack((fom_image, psi_image), axis=0)).unsqueeze(0).to(device) + # Add GPU stuff to the arguments, look at model-angelo for help + with torch.no_grad(): + output_segmentation = model(fp).cpu().numpy() + + # Use threshold as defined by arguments to binarize + binarized_output = (output_segmentation.reshape(*output_segmentation.shape[-2:]) > args.threshold) # Set edges to zero to prevent picking too close to the edge d = round(args.radius/pixel_size) - cropimage = image.copy() # because image was readonly + cropimage = binarized_output.copy() # because image was readonly cropimage[:d, :] = 0 # Top edge cropimage[-d:, :] = 0 # Bottom edge cropimage[:, :d] = 0 # Left edge @@ -251,4 +424,4 @@ if __name__ == "__main__": df = pd.DataFrame(xy) starfile.write(df, args.output, overwrite=True) else: - Path(args.output).touch() + Path(args.output).touch() \ No newline at end of file From d9635f86ed95df5ca0e5b3faa99490bc55c5a1f8 Mon Sep 17 00:00:00 2001 From: jshi7 Date: Thu, 13 Feb 2025 12:24:35 +0000 Subject: [PATCH 065/189] removed lightning dependency --- scripts/trace_amyloids.in | 46 +++++++++++---------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index f9847a433..45bcc4bdd 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -13,7 +13,6 @@ import argparse """ Full assembly of the parts to form the complete network """ """ Parts of the U-Net model """ -import lightning as L import torch import torch.nn as nn import torch.nn.functional as F @@ -91,7 +90,7 @@ class OutConv(nn.Module): return self.conv(x) -class UNet(L.LightningModule): +class UNet(nn.Module): def __init__(self, n_channels, n_classes, bilinear=False): super(UNet, self).__init__() self.n_channels = n_channels @@ -137,32 +136,17 @@ class UNet(L.LightningModule): self.up4 = torch.utils.checkpoint(self.up4) self.outc = torch.utils.checkpoint(self.outc) - def training_step(self, batch, batch_idx): - # training_step defines the train loop. - x, y = batch - pred = self(x) - loss = F.binary_cross_entropy_with_logits(pred, y, pos_weight=torch.tensor(50)) - self.log("train_loss", loss) - return loss - - def validation_step(self, batch, batch_idx): - # training_step defines the train loop. - x, y = batch - pred = self(x) - loss = F.binary_cross_entropy_with_logits(pred, y, pos_weight=torch.tensor(50)) - self.log("val_loss", loss) - return loss - - def test_step(self, batch, batch_idx): - x, y = batch - pred = self(x) - loss = F.binary_cross_entropy_with_logits(pred, y, pos_weight=torch.tensor(50)) - self.log("test_loss", loss) - return loss - - def configure_optimizers(self): - optimizer = torch.optim.Adam(self.parameters(), lr=1e-3) - return optimizer + def load_from_checkpoint(model_path: str, n_channels: int, n_classes: int): + model = UNet(n_channels=n_channels, n_classes=n_classes) + lightning_dict = torch.load(model_path, map_location="cpu") + state_dict = lightning_dict['state_dict'] + try: + model.load_state_dict(state_dict, weights_only=True) + except: + model.load_state_dict(state_dict) + return model + + #------------------------------------------------------------------------------------------ @@ -391,16 +375,12 @@ if __name__ == "__main__": fom_image, pixel_size, origin = load_mrc(args.input_fom) psi_image, _, _ = load_mrc(args.input_psi) - device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + device = torch.device(args.device) - # For Jenny, load network add path to weights as an argument (can use the curreent one as default) model = UNet.load_from_checkpoint(args.model_path, n_channels=2, n_classes=1) model.eval() - # FOr Jenny, put on device model = model.to(device) - # For Jenny, concatenate the two images and run network fp = torch.from_numpy(np.stack((fom_image, psi_image), axis=0)).unsqueeze(0).to(device) - # Add GPU stuff to the arguments, look at model-angelo for help with torch.no_grad(): output_segmentation = model(fp).cpu().numpy() From 0380638fca365c089a162894194f7191e8e260ee Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 20 Feb 2025 15:18:46 +0000 Subject: [PATCH 066/189] changes made with Jenny to run her model from relion_find_amyloid --- scripts/trace_amyloids.in | 9 ++++---- src/amyloid_finder.cpp | 39 +++++++++++++++++++++++++++++++---- src/amyloid_finder.h | 19 +++++++++++++++-- src/amyloid_finder_mpi.cpp | 25 ++++++++++++++++++++++ src/apps/find_amyloid.cpp | 1 + src/apps/find_amyloid_mpi.cpp | 1 + src/pipeline_jobs.cpp | 7 +++++++ 7 files changed, 91 insertions(+), 10 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 45bcc4bdd..e4ba8afa6 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -310,7 +310,7 @@ def pick_filaments(score, radius, threshold, filaments_length, filaments_plot): fig, axes = plt.subplots(1, 4, figsize=(15, 5), sharex=True, sharey=True) ax = axes.ravel() - ax[0].imshow(score, cmap=cm.binary, vmin=-20, vmax=5) + ax[0].imshow(score, cmap=cm.binary, vmin=0, vmax=1) ax[0].imshow(bin_score, alpha=0.5, cmap=cm.Reds) ax[0].set_title('FOM [-20,5] thr= ' + str(threshold)) @@ -382,10 +382,11 @@ if __name__ == "__main__": model = model.to(device) fp = torch.from_numpy(np.stack((fom_image, psi_image), axis=0)).unsqueeze(0).to(device) with torch.no_grad(): - output_segmentation = model(fp).cpu().numpy() + output_segmentation = model(fp).sigmoid().cpu().numpy() # Use threshold as defined by arguments to binarize - binarized_output = (output_segmentation.reshape(*output_segmentation.shape[-2:]) > args.threshold) + #binarized_output = (output_segmentation.reshape(*output_segmentation.shape[-2:]) > args.threshold) + binarized_output = output_segmentation.reshape(*output_segmentation.shape[-2:]) # Set edges to zero to prevent picking too close to the edge d = round(args.radius/pixel_size) @@ -404,4 +405,4 @@ if __name__ == "__main__": df = pd.DataFrame(xy) starfile.write(df, args.output, overwrite=True) else: - Path(args.output).touch() \ No newline at end of file + Path(args.output).touch() diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 18ecec20f..9f233eda8 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -45,6 +45,9 @@ void AmyloidFinder::read(int argc, char **argv, int rank) do_plot = parser.checkOption("--plot", "Display images with intermediate tracing results for each micrograph"); do_redo_tracing = parser.checkOption("--redo_all_tracing", "Ignore any autopick.star files already present and redo all tracing."); fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_trace_amyloids"); + fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing"); + do_gpu = parser.checkOption("--gpu", "Use GPU acceleration when availiable"); + gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); int expert_section = parser.addSection("Expert options (typically no need to change)"); signal_minres = textToFloat(parser.getOption("--signal_minres", "Minimum resolution value for signal (in A)", "4.85")); @@ -172,6 +175,28 @@ void AmyloidFinder::initialise(bool is_leader) } + +void AmyloidFinder::deviceInitialise() +{ + int devCount; + accGPUGetDeviceCount(&devCount); + + std::vector < std::vector < std::string > > allThreadIDs; + untangleDeviceIDs(gpu_ids, allThreadIDs); + + // Sequential initialisation of GPUs on all ranks + if (!std::isdigit(*gpu_ids.begin())) + device_id = 0; + else + device_id = textToInteger((allThreadIDs[0][0]).c_str()); + + if (verb>0) + { + std::cout << " + Using GPU device " << device_id << std::endl; + } +} + + FileName AmyloidFinder::getOutputRootName(FileName fn_mic) { FileName fn_pre, fn_jobnr, fn_post; @@ -573,13 +598,19 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi } -void AmyloidFinder::traceFilaments(FileName &fn_fom, FileName &fn_star) +void AmyloidFinder::traceFilaments(FileName &fn_fom, FileName &fn_psi, FileName &fn_star) { // hardcoded python script for now... FileName command = fn_exe; - command += " --i " + fn_fom; + command += " -if " + fn_fom; + command += " -ip " + fn_psi; + command += " -m " + fn_model_path; + if (do_gpu) + command += " -d cuda:" + integerToString(device_id); + else + command += " -d cpu"; command += " --o " + fn_star; command += " --t " + floatToString(threshold); command += " --r " + floatToString(trace_filament_width/2); @@ -588,7 +619,7 @@ void AmyloidFinder::traceFilaments(FileName &fn_fom, FileName &fn_star) if (do_plot) command += " --plot "; - //std::cerr << command << std::endl; + std::cerr << command << std::endl; int res = system(command.c_str()); @@ -650,7 +681,7 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) } FileName fn_star = getOutputRootName(fn_mic) + "_" + fn_out + ".star"; - traceFilaments(fn_fom, fn_star); + traceFilaments(fn_fom, fn_psi, fn_star); if (myverb) std::cout << "done!" << std::endl; diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index cd9ff67c2..2bc1e4e5d 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -71,7 +71,19 @@ class AmyloidFinder // Redo existing tracings? bool do_redo_tracing; - + + // FileName for Jenny's model + FileName fn_model_path; + + // Use GPU to execute the model? + bool do_gpu; + + // Which GPU devices to use? + std::string gpu_ids; + + // GPU Device ID + int device_id = -1; + // Python executable name FileName fn_exe; @@ -104,6 +116,9 @@ class AmyloidFinder // Initialise some stuff after reading void initialise(bool is_leader = true); + // Which device to use? + void deviceInitialise(); + // Get the output coordinate filename given the micrograph filename FileName getOutputRootName(FileName fn_mic); @@ -121,7 +136,7 @@ class AmyloidFinder MultidimArray &Mangle, RFLOAT &skew, RFLOAT &kurt, bool myverb = false); // Trace individual segments in the images based on the scores and angles - void traceFilaments(FileName &fn_fom, FileName &fn_star); + void traceFilaments(FileName &fn_fom, FileName &fn_fpsi, FileName &fn_star); // Run on one micrograph void processOneMicrograph(FileName fn_mic, bool myverb = false); diff --git a/src/amyloid_finder_mpi.cpp b/src/amyloid_finder_mpi.cpp index 720bba766..5d5cb00be 100644 --- a/src/amyloid_finder_mpi.cpp +++ b/src/amyloid_finder_mpi.cpp @@ -43,6 +43,31 @@ void AmyloidFinderMpi::read(int argc, char **argv) printMpiNodesMachineNames(*node); } +void AmyloidFinderMpi::deviceInitialise() +{ + int devCount; + accGPUGetDeviceCount(&devCount); + + std::vector < std::vector < std::string > > allThreadIDs; + untangleDeviceIDs(gpu_ids, allThreadIDs); + + // Sequential initialisation of GPUs on all ranks + if (!std::isdigit(*gpu_ids.begin())) + device_id = node->rank%devCount; + else + device_id = textToInteger((allThreadIDs[node->rank][0]).c_str()); + + for (int follower = 0; follower < node->size; follower++) + { + if (follower == node->rank) + { + std::cout << " + Using GPU device: " << device_id << " on MPI node: " << node->rank << std::endl; + std::cout.flush(); + } + node->barrierWait(); + } +} + void AmyloidFinderMpi::run() { // Each node does part of the work diff --git a/src/apps/find_amyloid.cpp b/src/apps/find_amyloid.cpp index 737185be0..89b76f65a 100644 --- a/src/apps/find_amyloid.cpp +++ b/src/apps/find_amyloid.cpp @@ -28,6 +28,7 @@ int main(int argc, char *argv[]) prm.read(argc, argv); prm.initialise(); + if (prm.do_gpu) prm.deviceInitialise(); prm.run(); diff --git a/src/apps/find_amyloid_mpi.cpp b/src/apps/find_amyloid_mpi.cpp index 0c4629959..f487b7780 100644 --- a/src/apps/find_amyloid_mpi.cpp +++ b/src/apps/find_amyloid_mpi.cpp @@ -28,6 +28,7 @@ int main(int argc, char *argv[]) prm.read(argc, argv); prm.initialise(prm.getRank()); + if (prm.do_gpu) prm.deviceInitialise(); prm.run(); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 19f372585..a3820aaa8 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2349,6 +2349,13 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Thu, 20 Feb 2025 16:18:28 +0000 Subject: [PATCH 067/189] write out message when reading in blush arguments from environment variable --- src/ml_optimiser.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 5682a9a88..44971ff6c 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -2026,7 +2026,14 @@ void MlOptimiser::initialiseGeneral(int rank) char *env_blush_args = getenv("RELION_BLUSH_ARGS"); if (env_blush_args != nullptr) + { blush_args += std::string(env_blush_args); + if (verb > 0) + { + std::cout << " + Picking up from environment variable $RELION_BLUSH_ARGS: " << std::endl; + std::cout << " + " << std::string(env_blush_args) << std::endl; + } + } if (skip_spectral_trailing) blush_args += " --skip-spectral-trailing "; From 3b5b1f3d23edf78ee7ed121d1aa7d9297dfa0fb3 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 20 Feb 2025 16:18:56 +0000 Subject: [PATCH 068/189] raise error when input tomogram.star file does not contain correct label for reconstructed tomogram --- src/pipeline_jobs.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 19f372585..8ee1bd76b 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -7011,6 +7011,20 @@ bool RelionJob::getCommandsTomoPickTomogramsJob(std::string &outputname, std::ve if (error_message != "") return false; + if (joboptions["in_tomoset"].getString() == "") + { + error_message = "ERROR: you need to provide an input STAR file"; + return false; + } + + MetaDataTable MDtest; + MDtest.read(joboptions["in_tomoset"].getString(), "global"); + if (!MDtest.containsLabel(EMDL_TOMO_RECONSTRUCTED_TOMOGRAM_FILE_NAME)) + { + error_message = "ERROR: the tomogram star file does not contain the label rlnTomoReconstructedTomogram"; + return false; + } + if (joboptions["pick_mode"].getString() == "particles" && joboptions["in_star_file"].getString().length() > 0) { command0 = "`which relion_python_tomo_get_particle_poses`"; @@ -7028,12 +7042,6 @@ bool RelionJob::getCommandsTomoPickTomogramsJob(std::string &outputname, std::ve command="`which relion_python_tomo_pick` "; - if (joboptions["in_tomoset"].getString() == "") - { - error_message = "ERROR: you need to provide an input STAR file"; - return false; - } - /* if (joboptions["pick_mode"].getString() == "surfaces" || joboptions["pick_mode"].getString() == "filaments") { From 09dd2cc01818d7563ddd9218823cbef239c6d0a9 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 27 Feb 2025 10:12:54 +0000 Subject: [PATCH 069/189] added forgotton option to set only_do_unfinished --- src/jaz/tomography/programs/align.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jaz/tomography/programs/align.cpp b/src/jaz/tomography/programs/align.cpp index db1f7f085..8a8bce0a6 100644 --- a/src/jaz/tomography/programs/align.cpp +++ b/src/jaz/tomography/programs/align.cpp @@ -72,6 +72,7 @@ void AlignProgram::parseInput() do_anisotropy = parser.checkOption("--aniso", "Assume an anisotropic projection model"); per_tilt_anisotropy = parser.checkOption("--per_tilt_aniso", "Fit independent view anisotropy for each tilt image"); num_iters = textToInteger(parser.getOption("--it", "Max. number of iterations", "10000")); + only_do_unfinished =parser.checkOption("--only_do_unfinished", "Skip tomograms for which intermediate projections.star files exist"); int motion_section = parser.addSection("Motion estimation options"); From e20029e7d57d0f7ba642bfdf13217313d708c466 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 27 Feb 2025 10:13:21 +0000 Subject: [PATCH 070/189] added option to read in specific blush model --- src/ml_optimiser.cpp | 17 ++++++++++++----- src/ml_optimiser.h | 3 ++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 44971ff6c..04e7a813f 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -583,6 +583,7 @@ void MlOptimiser::parseContinue(int argc, char **argv) skip_realspace_helical_sym = parser.checkOption("--skip_realspace_helical_sym", "", "false", true); do_blush = parser.checkOption("--blush", "Perform the reconstruction with the Blush algorithm."); + blush_model = parser.getOption("--blush_model", "File location of a non-standard blush model", ""); skip_spectral_trailing = parser.checkOption("--blush_skip_spectral_trailing", "Skip spectral trailing during Blush reconstruction (WARNING: This may inflate resolution estimates)"); do_external_reconstruct = parser.checkOption("--external_reconstruct", "Perform the reconstruction step outside relion_refine, e.g. for learned priors?)"); @@ -1004,6 +1005,7 @@ void MlOptimiser::parseInitial(int argc, char **argv) failsafe_threshold = textToInteger(parser.getOption("--failsafe_threshold", "Maximum number of particles permitted to be handled by fail-safe mode, due to zero sum of weights, before exiting with an error (GPU only).", "40")); do_blush = parser.checkOption("--blush", "Perform the reconstruction with the Blush algorithm."); + blush_model = parser.getOption("--blush_model", "File location of a non-standard blush model", ""); skip_spectral_trailing = parser.checkOption("--blush_skip_spectral_trailing", "Skip spectral trailing during Blush reconstruction (WARNING: This may inflate resolution estimates)"); do_external_reconstruct = parser.checkOption("--external_reconstruct", "Perform the reconstruction step outside relion_refine, e.g. for learned priors?)"); @@ -2028,16 +2030,21 @@ void MlOptimiser::initialiseGeneral(int rank) if (env_blush_args != nullptr) { blush_args += std::string(env_blush_args); - if (verb > 0) - { - std::cout << " + Picking up from environment variable $RELION_BLUSH_ARGS: " << std::endl; - std::cout << " + " << std::string(env_blush_args) << std::endl; - } } + if (blush_model != "") + blush_args += std::string(" -m " + blush_model); + if (skip_spectral_trailing) blush_args += " --skip-spectral-trailing "; + if (verb > 0 && blush_args != "") + { + std::cout << " + Passing the below additional arguments to blush: " << std::endl; + std::cout << " + " << blush_args << std::endl; + } + + if (do_gpu) { blush_args += " --gpu "; diff --git a/src/ml_optimiser.h b/src/ml_optimiser.h index 3004cdedf..50f6ebd30 100644 --- a/src/ml_optimiser.h +++ b/src/ml_optimiser.h @@ -505,7 +505,8 @@ class MlOptimiser /** Perform reconstruction using the Blush algorithm */ bool do_blush; - bool skip_spectral_trailing; + bool skip_spectral_trailing; + std::string blush_model; /** Argumetns to pass to the Blush call */ std::string blush_args; From 4b825c79733cde8aba988947036f72ecadde358e Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 11 Mar 2025 12:11:42 +0000 Subject: [PATCH 071/189] Richards suggestion to calculate ice ring density relative to backgroun --- src/ctffind_runner.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index dce5af69f..de69feb2f 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -966,6 +966,7 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT in2.close(); // Also try and get rlnIceRingDensity, as suggested by Rafael Leiro from the CNIO in Madrid + // SHWS 11mar2024: modified to subtract and divide by average power from bands above and below the water ring FileName fn_avrot = fn_root + "_avrot.txt"; std::ifstream av(fn_avrot.data(), std::ios_base::in); icering = 0.; @@ -979,8 +980,15 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT // Now get lines 6 and 7 std::getline(av,s1); tokenize(s1, words); + int ilow = -999; int imin = -999; int imax = -999; + int ihigh = -999; + for (int i = 0; i < words.size(); i++) + if (ilow < 0 && textToFloat(words[i]) >= 0.22222) { + ilow = i; + break; + } for (int i = 0; i < words.size(); i++) if (imin < 0 && textToFloat(words[i]) >= 0.25) { imin = i; @@ -991,12 +999,37 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT imax = i; break; } + for (int i = 0; i < words.size(); i++) + if (ihigh < 0 && textToFloat(words[i]) >= 0.33333) { + ihigh = i; + break; + } + RFLOAT lowring = 0.; + RFLOAT highring = 0.; std::getline(av,s2); tokenize(s2, words); + for (int i = ilow; i < imin; i++) + { + lowring += fabs(textToFloat(words[i])); + } for (int i = imin; i < imax; i++) { icering += fabs(textToFloat(words[i])); } + for (int i = imax; i < ihigh; i++) + { + highring += fabs(textToFloat(words[i])); + } + RFLOAT avg_sides = (lowring + highring) / 2.; + std::cerr << " lowring= " << lowring << " icering= " << icering << " highring= " << highring << " avg_sides= " << avg_sides << " fraction= " << (icering - avg_sides) / avg_sides << std::endl; + if (avg_sides > 0.) + { + icering = (icering - avg_sides) / avg_sides; + } + else + { + icering = 0.; + } } return Final_is_found; From ee511de193d366963a9450a00e6f4992a93df813 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 12 Mar 2025 08:26:36 +0000 Subject: [PATCH 072/189] reverted back from Richard's to Rafa's way of calculating ice ring --- src/ctffind_runner.cpp | 64 +++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index de69feb2f..2c00c7ed6 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -980,15 +980,15 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT // Now get lines 6 and 7 std::getline(av,s1); tokenize(s1, words); - int ilow = -999; + //int ilow = -999; int imin = -999; int imax = -999; - int ihigh = -999; - for (int i = 0; i < words.size(); i++) - if (ilow < 0 && textToFloat(words[i]) >= 0.22222) { - ilow = i; - break; - } + //int ihigh = -999; + //for (int i = 0; i < words.size(); i++) + // if (ilow < 0 && textToFloat(words[i]) >= 0.22222) { + // ilow = i; + // break; + // } for (int i = 0; i < words.size(); i++) if (imin < 0 && textToFloat(words[i]) >= 0.25) { imin = i; @@ -999,37 +999,37 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT imax = i; break; } - for (int i = 0; i < words.size(); i++) - if (ihigh < 0 && textToFloat(words[i]) >= 0.33333) { - ihigh = i; - break; - } - RFLOAT lowring = 0.; - RFLOAT highring = 0.; + //for (int i = 0; i < words.size(); i++) + // if (ihigh < 0 && textToFloat(words[i]) >= 0.33333) { + // ihigh = i; + // break; + // } + //RFLOAT lowring = 0.; + //RFLOAT highring = 0.; std::getline(av,s2); tokenize(s2, words); - for (int i = ilow; i < imin; i++) - { - lowring += fabs(textToFloat(words[i])); - } + //for (int i = ilow; i < imin; i++) + //{ + // lowring += fabs(textToFloat(words[i])); + //} for (int i = imin; i < imax; i++) { icering += fabs(textToFloat(words[i])); } - for (int i = imax; i < ihigh; i++) - { - highring += fabs(textToFloat(words[i])); - } - RFLOAT avg_sides = (lowring + highring) / 2.; - std::cerr << " lowring= " << lowring << " icering= " << icering << " highring= " << highring << " avg_sides= " << avg_sides << " fraction= " << (icering - avg_sides) / avg_sides << std::endl; - if (avg_sides > 0.) - { - icering = (icering - avg_sides) / avg_sides; - } - else - { - icering = 0.; - } + //for (int i = imax; i < ihigh; i++) + //{ + // highring += fabs(textToFloat(words[i])); + //} + //RFLOAT avg_sides = (lowring + highring) / 2.; + //std::cerr << " lowring= " << lowring << " icering= " << icering << " highring= " << highring << " avg_sides= " << avg_sides << " fraction= " << (icering - avg_sides) / avg_sides << std::endl; + //if (avg_sides > 0.) + //{ + // icering = (icering - avg_sides) / avg_sides; + //} + //else + //{ + // icering = 0.; + //} } return Final_is_found; From 06e165275b84aea07ec016c33417291534a4c27a Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 19 Mar 2025 16:05:10 +0000 Subject: [PATCH 073/189] take tomo-subtomo priors into account --- src/apps/star_handler.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/apps/star_handler.cpp b/src/apps/star_handler.cpp index 2a96b9b6c..41d1b3bd1 100644 --- a/src/apps/star_handler.cpp +++ b/src/apps/star_handler.cpp @@ -1012,13 +1012,22 @@ class star_handler_parameters MD.getValue(EMDL_ORIENT_TILT, tilt); MD.getValue(EMDL_ORIENT_PSI, psi); - xoff /= angpix; - yoff /= angpix; + Euler_angles2matrix(rot, tilt, psi, A3D, false); + if (MD.containsLabel(EMDL_TOMO_SUBTOMOGRAM_ROT)) + { + const double phi = MD.getAngleInRad(EMDL_TOMO_SUBTOMOGRAM_ROT); + const double theta = MD.getAngleInRad(EMDL_TOMO_SUBTOMOGRAM_TILT); + const double psi = MD.getAngleInRad(EMDL_TOMO_SUBTOMOGRAM_PSI); + Matrix2D Asubtomo; + Euler_angles2matrix( phi, theta, psi, Asubtomo, false); + A3D = Asubtomo * A3D; + } - // Project the center-coordinates - Euler_angles2matrix(rot, tilt, psi, A3D, false); + // Project the recenter-coordinates my_projected_center = A3D * my_center; + xoff /= angpix; + yoff /= angpix; xoff -= XX(my_projected_center); yoff -= YY(my_projected_center); From 9c3e5f9649955c3d69d51c71065c3340311109af Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 19 Mar 2025 16:06:44 +0000 Subject: [PATCH 074/189] add option --keep_priors_fixed to deal with priors in subtomogram averaging --- src/acc/acc_ml_optimiser_impl.h | 6 +++--- src/ml_optimiser.cpp | 12 +++++++++--- src/ml_optimiser.h | 3 +++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/acc/acc_ml_optimiser_impl.h b/src/acc/acc_ml_optimiser_impl.h index c29c2401b..fa3552d75 100644 --- a/src/acc/acc_ml_optimiser_impl.h +++ b/src/acc/acc_ml_optimiser_impl.h @@ -144,11 +144,11 @@ void getFourierTransformsAndCtfs(long int part_id, // If there were no defined priors (i.e. their values were 999.), then use the "normal" angles // Also do this for local angular searches when not doing helical refinement (e.g. for subtomograms picked on certain geometries) // Note that helical refinement deals with priors in a special manner... So leave that untouched... - if (prior_rot > 998.99 && prior_rot < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches) ) + if (prior_rot > 998.99 && prior_rot < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches && !baseMLO->keep_angular_priors_fixed ) ) prior_rot = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT); - if (prior_tilt > 998.99 && prior_tilt < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches) ) + if (prior_tilt > 998.99 && prior_tilt < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches && !baseMLO->keep_angular_priors_fixed ) ) prior_tilt = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT); - if (prior_psi > 998.99 && prior_psi < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches) ) + if (prior_psi > 998.99 && prior_psi < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches && !baseMLO->keep_angular_priors_fixed ) ) prior_psi = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI); if (prior_psi_flip_ratio > 998.99 && prior_psi_flip_ratio < 999.01) prior_psi_flip_ratio = 0.5; diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 04e7a813f..6c1a6249d 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -429,6 +429,11 @@ void MlOptimiser::parseContinue(int argc, char **argv) else do_center_classes = false; + if (parser.checkOption("--keep_priors_fixed", "Keep priors fixed, even for local angular searches?")) + keep_angular_priors_fixed = true; + else + keep_angular_priors_fixed = false; + do_skip_maximization = parser.checkOption("--skip_maximize", "Skip maximization step (only write out data.star file)?"); int corrections_section = parser.addSection("Corrections"); @@ -765,6 +770,7 @@ void MlOptimiser::parseInitial(int argc, char **argv) do_skip_rotate = parser.checkOption("--skip_rotate", "Skip rotational assignment (only translate and classify)?"); do_bimodal_psi = parser.checkOption("--bimodal_psi", "Do bimodal searches of psi angle?"); // Oct07,2015 - Shaoda, bimodal psi do_skip_maximization = false; + keep_angular_priors_fixed = parser.checkOption("--keep_priors_fixed", "Keep priors fixed, even for local angular searches?"); // Helical reconstruction int helical_section = parser.addSection("Helical reconstruction (in development...)"); @@ -5993,11 +5999,11 @@ void MlOptimiser::getFourierTransformsAndCtfs( // If there were no defined priors (i.e. their values were 999.), then use the "normal" angles // Also do this for local angular searches when not doing helical refinement (e.g. for subtomograms picked on certain geometries) // Note that helical refinement deals with priors in a special manner... So leave that untouched... - if ( (prior_rot > 998.99 && prior_rot < 999.01) || (!do_helical_refine && do_local_angular_searches) ) + if ( (prior_rot > 998.99 && prior_rot < 999.01) || (!do_helical_refine && do_local_angular_searches && !keep_angular_priors_fixed) ) prior_rot = DIRECT_A2D_ELEM(exp_metadata, metadata_offset, METADATA_ROT); - if ( (prior_tilt > 998.99 && prior_tilt < 999.01) || (!do_helical_refine && do_local_angular_searches) ) + if ( (prior_tilt > 998.99 && prior_tilt < 999.01) || (!do_helical_refine && do_local_angular_searches && !keep_angular_priors_fixed) ) prior_tilt = DIRECT_A2D_ELEM(exp_metadata, metadata_offset, METADATA_TILT); - if ( (prior_psi > 998.99 && prior_psi < 999.01) || (!do_helical_refine && do_local_angular_searches) ) + if ( (prior_psi > 998.99 && prior_psi < 999.01) || (!do_helical_refine && do_local_angular_searches && !keep_angular_priors_fixed) ) prior_psi = DIRECT_A2D_ELEM(exp_metadata, metadata_offset, METADATA_PSI); if (prior_psi_flip_ratio > 998.99 && prior_psi_flip_ratio < 999.01) prior_psi_flip_ratio = 0.5; diff --git a/src/ml_optimiser.h b/src/ml_optimiser.h index 50f6ebd30..e75e880e2 100644 --- a/src/ml_optimiser.h +++ b/src/ml_optimiser.h @@ -532,6 +532,9 @@ class MlOptimiser */ RFLOAT adaptive_fraction; + // Keep rot, tilt psi priors fixed relative to entries in particle starfile, even when performing local angular searches? + bool keep_angular_priors_fixed; + // Seed for random number generator int random_seed; From 853a238f9b392f625cb426711da01d8aac9cb039 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 19 Mar 2025 16:14:24 +0000 Subject: [PATCH 075/189] add Z-score for ice-ring calculation --- src/ctffind_runner.cpp | 80 +++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index 2c00c7ed6..093cba520 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -965,8 +965,53 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT in2.close(); + // Use Richard's ice ring density instead of Rafa's. + // Be careful, because avgrot files from CTFFIND-4.1 are somehow normalized badly, so re-read power spectrum image + FileName fn_ps = fn_root + ".mrc"; + Image Ips; + Ips.read(fn_ps); + Ips().setXmippOrigin(); + RFLOAT mysize = angpix*XSIZE(Ips()); + RFLOAT r2low_min=mysize*mysize/(4.5*4.5); + RFLOAT r2low_max=mysize*mysize/(4.0*4.0); + RFLOAT r2ice_min=mysize*mysize/(3.9*3.9); + RFLOAT r2ice_max=mysize*mysize/(3.5*3.5); + RFLOAT r2high_min=mysize*mysize/(3.4*3.4); + RFLOAT r2high_max=mysize*mysize/(3.0*3.0); + RFLOAT sumband = 0., sum2band = 0., band_n = 0.; + RFLOAT sumice = 0., ice_n = 0.; + + FOR_ALL_ELEMENTS_IN_ARRAY2D(Ips()) + { + RFLOAT r2 = RFLOAT(i*i + j*j); + if (r2 > r2low_min && r2 < r2low_max || r2 > r2high_min && r2 < r2high_max) + { + sumband += A2D_ELEM(Ips(), i, j); + sum2band += A2D_ELEM(Ips(), i, j) * A2D_ELEM(Ips(), i, j); + band_n += 1.; + } + else if (r2 > r2ice_min && r2 < r2ice_max) + { + sumice += A2D_ELEM(Ips(), i, j); + ice_n += 1.; + } + } + + if (band_n > 0. && ice_n > 0.) + { + sumice /= ice_n; + sumband /= band_n; + sum2band = sqrt( (sum2band / band_n) - (sumband * sumband) ); + if (sum2band > 0.) icering = (sumice - sumband) / sum2band; + else icering = 0.; + } + else + { + icering = 0.; + } + + /* // Also try and get rlnIceRingDensity, as suggested by Rafael Leiro from the CNIO in Madrid - // SHWS 11mar2024: modified to subtract and divide by average power from bands above and below the water ring FileName fn_avrot = fn_root + "_avrot.txt"; std::ifstream av(fn_avrot.data(), std::ios_base::in); icering = 0.; @@ -980,15 +1025,8 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT // Now get lines 6 and 7 std::getline(av,s1); tokenize(s1, words); - //int ilow = -999; int imin = -999; int imax = -999; - //int ihigh = -999; - //for (int i = 0; i < words.size(); i++) - // if (ilow < 0 && textToFloat(words[i]) >= 0.22222) { - // ilow = i; - // break; - // } for (int i = 0; i < words.size(); i++) if (imin < 0 && textToFloat(words[i]) >= 0.25) { imin = i; @@ -999,38 +1037,14 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT imax = i; break; } - //for (int i = 0; i < words.size(); i++) - // if (ihigh < 0 && textToFloat(words[i]) >= 0.33333) { - // ihigh = i; - // break; - // } - //RFLOAT lowring = 0.; - //RFLOAT highring = 0.; std::getline(av,s2); tokenize(s2, words); - //for (int i = ilow; i < imin; i++) - //{ - // lowring += fabs(textToFloat(words[i])); - //} for (int i = imin; i < imax; i++) { icering += fabs(textToFloat(words[i])); } - //for (int i = imax; i < ihigh; i++) - //{ - // highring += fabs(textToFloat(words[i])); - //} - //RFLOAT avg_sides = (lowring + highring) / 2.; - //std::cerr << " lowring= " << lowring << " icering= " << icering << " highring= " << highring << " avg_sides= " << avg_sides << " fraction= " << (icering - avg_sides) / avg_sides << std::endl; - //if (avg_sides > 0.) - //{ - // icering = (icering - avg_sides) / avg_sides; - //} - //else - //{ - // icering = 0.; - //} } + */ return Final_is_found; } From a9215de75697bb389f2ee647c4c47d5fecd6f557 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 19 Mar 2025 16:05:10 +0000 Subject: [PATCH 076/189] take tomo-subtomo priors into account --- src/apps/star_handler.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/apps/star_handler.cpp b/src/apps/star_handler.cpp index 2a96b9b6c..41d1b3bd1 100644 --- a/src/apps/star_handler.cpp +++ b/src/apps/star_handler.cpp @@ -1012,13 +1012,22 @@ class star_handler_parameters MD.getValue(EMDL_ORIENT_TILT, tilt); MD.getValue(EMDL_ORIENT_PSI, psi); - xoff /= angpix; - yoff /= angpix; + Euler_angles2matrix(rot, tilt, psi, A3D, false); + if (MD.containsLabel(EMDL_TOMO_SUBTOMOGRAM_ROT)) + { + const double phi = MD.getAngleInRad(EMDL_TOMO_SUBTOMOGRAM_ROT); + const double theta = MD.getAngleInRad(EMDL_TOMO_SUBTOMOGRAM_TILT); + const double psi = MD.getAngleInRad(EMDL_TOMO_SUBTOMOGRAM_PSI); + Matrix2D Asubtomo; + Euler_angles2matrix( phi, theta, psi, Asubtomo, false); + A3D = Asubtomo * A3D; + } - // Project the center-coordinates - Euler_angles2matrix(rot, tilt, psi, A3D, false); + // Project the recenter-coordinates my_projected_center = A3D * my_center; + xoff /= angpix; + yoff /= angpix; xoff -= XX(my_projected_center); yoff -= YY(my_projected_center); From 02cd848f97b69d9ff867f782ed4f97df8724960d Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 19 Mar 2025 16:06:44 +0000 Subject: [PATCH 077/189] add option --keep_priors_fixed to deal with priors in subtomogram averaging --- src/acc/acc_ml_optimiser_impl.h | 6 +++--- src/ml_optimiser.cpp | 12 +++++++++--- src/ml_optimiser.h | 3 +++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/acc/acc_ml_optimiser_impl.h b/src/acc/acc_ml_optimiser_impl.h index c29c2401b..fa3552d75 100644 --- a/src/acc/acc_ml_optimiser_impl.h +++ b/src/acc/acc_ml_optimiser_impl.h @@ -144,11 +144,11 @@ void getFourierTransformsAndCtfs(long int part_id, // If there were no defined priors (i.e. their values were 999.), then use the "normal" angles // Also do this for local angular searches when not doing helical refinement (e.g. for subtomograms picked on certain geometries) // Note that helical refinement deals with priors in a special manner... So leave that untouched... - if (prior_rot > 998.99 && prior_rot < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches) ) + if (prior_rot > 998.99 && prior_rot < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches && !baseMLO->keep_angular_priors_fixed ) ) prior_rot = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_ROT); - if (prior_tilt > 998.99 && prior_tilt < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches) ) + if (prior_tilt > 998.99 && prior_tilt < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches && !baseMLO->keep_angular_priors_fixed ) ) prior_tilt = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_TILT); - if (prior_psi > 998.99 && prior_psi < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches) ) + if (prior_psi > 998.99 && prior_psi < 999.01 || (!baseMLO->do_helical_refine && do_local_angular_searches && !baseMLO->keep_angular_priors_fixed ) ) prior_psi = DIRECT_A2D_ELEM(baseMLO->exp_metadata, op.metadata_offset, METADATA_PSI); if (prior_psi_flip_ratio > 998.99 && prior_psi_flip_ratio < 999.01) prior_psi_flip_ratio = 0.5; diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 04e7a813f..6c1a6249d 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -429,6 +429,11 @@ void MlOptimiser::parseContinue(int argc, char **argv) else do_center_classes = false; + if (parser.checkOption("--keep_priors_fixed", "Keep priors fixed, even for local angular searches?")) + keep_angular_priors_fixed = true; + else + keep_angular_priors_fixed = false; + do_skip_maximization = parser.checkOption("--skip_maximize", "Skip maximization step (only write out data.star file)?"); int corrections_section = parser.addSection("Corrections"); @@ -765,6 +770,7 @@ void MlOptimiser::parseInitial(int argc, char **argv) do_skip_rotate = parser.checkOption("--skip_rotate", "Skip rotational assignment (only translate and classify)?"); do_bimodal_psi = parser.checkOption("--bimodal_psi", "Do bimodal searches of psi angle?"); // Oct07,2015 - Shaoda, bimodal psi do_skip_maximization = false; + keep_angular_priors_fixed = parser.checkOption("--keep_priors_fixed", "Keep priors fixed, even for local angular searches?"); // Helical reconstruction int helical_section = parser.addSection("Helical reconstruction (in development...)"); @@ -5993,11 +5999,11 @@ void MlOptimiser::getFourierTransformsAndCtfs( // If there were no defined priors (i.e. their values were 999.), then use the "normal" angles // Also do this for local angular searches when not doing helical refinement (e.g. for subtomograms picked on certain geometries) // Note that helical refinement deals with priors in a special manner... So leave that untouched... - if ( (prior_rot > 998.99 && prior_rot < 999.01) || (!do_helical_refine && do_local_angular_searches) ) + if ( (prior_rot > 998.99 && prior_rot < 999.01) || (!do_helical_refine && do_local_angular_searches && !keep_angular_priors_fixed) ) prior_rot = DIRECT_A2D_ELEM(exp_metadata, metadata_offset, METADATA_ROT); - if ( (prior_tilt > 998.99 && prior_tilt < 999.01) || (!do_helical_refine && do_local_angular_searches) ) + if ( (prior_tilt > 998.99 && prior_tilt < 999.01) || (!do_helical_refine && do_local_angular_searches && !keep_angular_priors_fixed) ) prior_tilt = DIRECT_A2D_ELEM(exp_metadata, metadata_offset, METADATA_TILT); - if ( (prior_psi > 998.99 && prior_psi < 999.01) || (!do_helical_refine && do_local_angular_searches) ) + if ( (prior_psi > 998.99 && prior_psi < 999.01) || (!do_helical_refine && do_local_angular_searches && !keep_angular_priors_fixed) ) prior_psi = DIRECT_A2D_ELEM(exp_metadata, metadata_offset, METADATA_PSI); if (prior_psi_flip_ratio > 998.99 && prior_psi_flip_ratio < 999.01) prior_psi_flip_ratio = 0.5; diff --git a/src/ml_optimiser.h b/src/ml_optimiser.h index 50f6ebd30..e75e880e2 100644 --- a/src/ml_optimiser.h +++ b/src/ml_optimiser.h @@ -532,6 +532,9 @@ class MlOptimiser */ RFLOAT adaptive_fraction; + // Keep rot, tilt psi priors fixed relative to entries in particle starfile, even when performing local angular searches? + bool keep_angular_priors_fixed; + // Seed for random number generator int random_seed; From a552487d724191dfed4af87367552f2caf7b4ecd Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 19 Mar 2025 16:14:24 +0000 Subject: [PATCH 078/189] add Z-score for ice-ring calculation --- src/ctffind_runner.cpp | 80 +++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index 2c00c7ed6..093cba520 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -965,8 +965,53 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT in2.close(); + // Use Richard's ice ring density instead of Rafa's. + // Be careful, because avgrot files from CTFFIND-4.1 are somehow normalized badly, so re-read power spectrum image + FileName fn_ps = fn_root + ".mrc"; + Image Ips; + Ips.read(fn_ps); + Ips().setXmippOrigin(); + RFLOAT mysize = angpix*XSIZE(Ips()); + RFLOAT r2low_min=mysize*mysize/(4.5*4.5); + RFLOAT r2low_max=mysize*mysize/(4.0*4.0); + RFLOAT r2ice_min=mysize*mysize/(3.9*3.9); + RFLOAT r2ice_max=mysize*mysize/(3.5*3.5); + RFLOAT r2high_min=mysize*mysize/(3.4*3.4); + RFLOAT r2high_max=mysize*mysize/(3.0*3.0); + RFLOAT sumband = 0., sum2band = 0., band_n = 0.; + RFLOAT sumice = 0., ice_n = 0.; + + FOR_ALL_ELEMENTS_IN_ARRAY2D(Ips()) + { + RFLOAT r2 = RFLOAT(i*i + j*j); + if (r2 > r2low_min && r2 < r2low_max || r2 > r2high_min && r2 < r2high_max) + { + sumband += A2D_ELEM(Ips(), i, j); + sum2band += A2D_ELEM(Ips(), i, j) * A2D_ELEM(Ips(), i, j); + band_n += 1.; + } + else if (r2 > r2ice_min && r2 < r2ice_max) + { + sumice += A2D_ELEM(Ips(), i, j); + ice_n += 1.; + } + } + + if (band_n > 0. && ice_n > 0.) + { + sumice /= ice_n; + sumband /= band_n; + sum2band = sqrt( (sum2band / band_n) - (sumband * sumband) ); + if (sum2band > 0.) icering = (sumice - sumband) / sum2band; + else icering = 0.; + } + else + { + icering = 0.; + } + + /* // Also try and get rlnIceRingDensity, as suggested by Rafael Leiro from the CNIO in Madrid - // SHWS 11mar2024: modified to subtract and divide by average power from bands above and below the water ring FileName fn_avrot = fn_root + "_avrot.txt"; std::ifstream av(fn_avrot.data(), std::ios_base::in); icering = 0.; @@ -980,15 +1025,8 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT // Now get lines 6 and 7 std::getline(av,s1); tokenize(s1, words); - //int ilow = -999; int imin = -999; int imax = -999; - //int ihigh = -999; - //for (int i = 0; i < words.size(); i++) - // if (ilow < 0 && textToFloat(words[i]) >= 0.22222) { - // ilow = i; - // break; - // } for (int i = 0; i < words.size(); i++) if (imin < 0 && textToFloat(words[i]) >= 0.25) { imin = i; @@ -999,38 +1037,14 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT imax = i; break; } - //for (int i = 0; i < words.size(); i++) - // if (ihigh < 0 && textToFloat(words[i]) >= 0.33333) { - // ihigh = i; - // break; - // } - //RFLOAT lowring = 0.; - //RFLOAT highring = 0.; std::getline(av,s2); tokenize(s2, words); - //for (int i = ilow; i < imin; i++) - //{ - // lowring += fabs(textToFloat(words[i])); - //} for (int i = imin; i < imax; i++) { icering += fabs(textToFloat(words[i])); } - //for (int i = imax; i < ihigh; i++) - //{ - // highring += fabs(textToFloat(words[i])); - //} - //RFLOAT avg_sides = (lowring + highring) / 2.; - //std::cerr << " lowring= " << lowring << " icering= " << icering << " highring= " << highring << " avg_sides= " << avg_sides << " fraction= " << (icering - avg_sides) / avg_sides << std::endl; - //if (avg_sides > 0.) - //{ - // icering = (icering - avg_sides) / avg_sides; - //} - //else - //{ - // icering = 0.; - //} } + */ return Final_is_found; } From 07740b1976c373d2bee48a45cde831cf23f75403 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 20 Mar 2025 09:52:50 +0000 Subject: [PATCH 079/189] use kurtosis for icering density, but only if MotionCorr had calculated power spectrum, otherwise fall back on Rafa's method --- src/ctffind_runner.cpp | 144 +++++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 71 deletions(-) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index 093cba520..ef999acd3 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -450,8 +450,7 @@ void CtffindRunner::joinCtffindResults() if (fabs(valscore + 999.) > 0.) MDctf.setValue(EMDL_CTF_VALIDATIONSCORE, valscore); - if (icering > 0.) - MDctf.setValue(EMDL_CTF_ICERINGDENSITY, icering); + MDctf.setValue(EMDL_CTF_ICERINGDENSITY, icering); if (is_tomo) { @@ -965,86 +964,89 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT in2.close(); - // Use Richard's ice ring density instead of Rafa's. // Be careful, because avgrot files from CTFFIND-4.1 are somehow normalized badly, so re-read power spectrum image + // This only works if given the power spectrum from RELION's motioncorr. Otherwise, fall back on Rafa's approach below FileName fn_ps = fn_root + ".mrc"; - Image Ips; - Ips.read(fn_ps); - Ips().setXmippOrigin(); - RFLOAT mysize = angpix*XSIZE(Ips()); - RFLOAT r2low_min=mysize*mysize/(4.5*4.5); - RFLOAT r2low_max=mysize*mysize/(4.0*4.0); - RFLOAT r2ice_min=mysize*mysize/(3.9*3.9); - RFLOAT r2ice_max=mysize*mysize/(3.5*3.5); - RFLOAT r2high_min=mysize*mysize/(3.4*3.4); - RFLOAT r2high_max=mysize*mysize/(3.0*3.0); - RFLOAT sumband = 0., sum2band = 0., band_n = 0.; - RFLOAT sumice = 0., ice_n = 0.; - - FOR_ALL_ELEMENTS_IN_ARRAY2D(Ips()) + if (use_given_ps) { - RFLOAT r2 = RFLOAT(i*i + j*j); - if (r2 > r2low_min && r2 < r2low_max || r2 > r2high_min && r2 < r2high_max) - { - sumband += A2D_ELEM(Ips(), i, j); - sum2band += A2D_ELEM(Ips(), i, j) * A2D_ELEM(Ips(), i, j); - band_n += 1.; - } - else if (r2 > r2ice_min && r2 < r2ice_max) - { - sumice += A2D_ELEM(Ips(), i, j); - ice_n += 1.; - } - } + // Use standard deviation in the diagnostics image for ice ring density instead of Rafa's. + // Calculate stddev of power, relative to stddev of power in a band with lower resolution than ice ring + // Richard suggested using a higher band as well, but some strange ice artefacts extend well into the higher band... + Image Ips; + Ips.read(fn_ps); + Ips().setXmippOrigin(); + RFLOAT mysize = angpix*XSIZE(Ips()); + RFLOAT r2ice_min=4*mysize*mysize/(3.9*3.9); + RFLOAT r2ice_max=4*mysize*mysize/(3.5*3.5); + RFLOAT n=0., mean=0., M2=0., M3=0., M4=0.; + for (long int i=0; i<=FINISHINGY(Ips()); i++) // Use only bottom half of the diagnostic image, as top contains model and averaged spectra!! + for (long int j=STARTINGX(Ips()); j<=FINISHINGX(Ips()); j++) + { + RFLOAT r2 = RFLOAT(i*i + j*j); + if (r2 > r2ice_min && r2 < r2ice_max) + { + n += 1; + mean += A2D_ELEM(Ips(), i, j); + } + } + mean /= n; + for (long int i=0; i<=FINISHINGY(Ips()); i++) // Use only bottom half of the diagnostic image, as top contains model and averaged spectra!! + for (long int j=STARTINGX(Ips()); j<=FINISHINGX(Ips()); j++) + { + RFLOAT r2 = RFLOAT(i*i + j*j); + if (r2 > r2ice_min && r2 < r2ice_max) + { + RFLOAT delta = A2D_ELEM(Ips(), i, j) - mean; + M2 += delta*delta; + //M3 += delta*delta*delta; + M4 += delta*delta*delta*delta; + } + } + + RFLOAT s2 = M2 / (n - 1); + RFLOAT s = sqrt(s2); + //RFLOAT skewness = (s > 0.) ? (n / ((n - 1) * (n - 2))) * (M3 / (s * s * s)) : 0.; + RFLOAT kurtosis = (n * (n + 1) * M4) / ((n - 1) * (n - 2) * (n - 3) * (s2 * s2)) - (3 * (n - 1) * (n - 1)) / ((n - 2) * (n - 3)); + //std::cerr << " s2= " << s2 << " skew= " << skewness << " kurtosis= " << kurtosis << std::endl; + icering = XMIPP_MIN(6., kurtosis); - if (band_n > 0. && ice_n > 0.) - { - sumice /= ice_n; - sumband /= band_n; - sum2band = sqrt( (sum2band / band_n) - (sumband * sumband) ); - if (sum2band > 0.) icering = (sumice - sumband) / sum2band; - else icering = 0.; } else { + // Get rlnIceRingDensity, as suggested by Rafael Leiro from the CNIO in Madrid + FileName fn_avrot = fn_root + "_avrot.txt"; + std::ifstream av(fn_avrot.data(), std::ios_base::in); icering = 0.; - } - - /* - // Also try and get rlnIceRingDensity, as suggested by Rafael Leiro from the CNIO in Madrid - FileName fn_avrot = fn_root + "_avrot.txt"; - std::ifstream av(fn_avrot.data(), std::ios_base::in); - icering = 0.; - if (!av.fail()) - { - std::string s1, s2; - //skip 5 lines - for(int i = 0; i < 5; ++i) - std::getline(av, s1); - - // Now get lines 6 and 7 - std::getline(av,s1); - tokenize(s1, words); - int imin = -999; - int imax = -999; - for (int i = 0; i < words.size(); i++) - if (imin < 0 && textToFloat(words[i]) >= 0.25) { - imin = i; - break; - } - for (int i = imin; i < words.size(); i++) - if (imax < 0 && imin > 0 && textToFloat(words[i]) > 0.28) { - imax = i; - break; - } - std::getline(av,s2); - tokenize(s2, words); - for (int i = imin; i < imax; i++) + if (!av.fail()) { - icering += fabs(textToFloat(words[i])); + std::string s1, s2; + //skip 5 lines + for(int i = 0; i < 5; ++i) + std::getline(av, s1); + + // Now get lines 6 and 7 + std::getline(av,s1); + tokenize(s1, words); + int imin = -999; + int imax = -999; + for (int i = 0; i < words.size(); i++) + if (imin < 0 && textToFloat(words[i]) >= 0.25) { + imin = i; + break; + } + for (int i = imin; i < words.size(); i++) + if (imax < 0 && imin > 0 && textToFloat(words[i]) > 0.28) { + imax = i; + break; + } + std::getline(av,s2); + tokenize(s2, words); + for (int i = imin; i < imax; i++) + { + icering += fabs(textToFloat(words[i])); + } } } - */ return Final_is_found; } From 532bd43489907d7f623239de5e1f5022013c610f Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 21 Mar 2025 16:32:24 +0000 Subject: [PATCH 080/189] implemented icepowerfraction and icekurtosis --- src/ctffind_runner.cpp | 117 ++++++++++++++++++++++++----------------- src/ctffind_runner.h | 4 +- src/metadata_label.h | 8 ++- 3 files changed, 78 insertions(+), 51 deletions(-) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index ef999acd3..1777ed82d 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -265,9 +265,9 @@ void CtffindRunner::initialise(bool is_leader) if (continue_old) { FileName fn_microot = fn_mic_ctf_given_all[imic].withoutExtension(); - RFLOAT defU, defV, defAng, CC, HT, CS, AmpCnst, XMAG, DStep, maxres=-1., valscore = -1., phaseshift = 0., icering = 0.; + RFLOAT defU, defV, defAng, CC, HT, CS, AmpCnst, XMAG, DStep, maxres=-1., valscore = -1., phaseshift = 0., icering = 0., icepowfrac = 0., icekurtosis = 0.; if (getCtffindResults(fn_microot, defU, defV, defAng, CC, - HT, CS, AmpCnst, XMAG, DStep, maxres, valscore, phaseshift, icering, false)) // false: dont warn if not found Final values + HT, CS, AmpCnst, XMAG, DStep, maxres, valscore, phaseshift, icering, icepowfrac, icekurtosis, false)) // false: dont warn if not found Final values { process_this = false; // already done } @@ -415,9 +415,10 @@ void CtffindRunner::joinCtffindResults() { FileName fn_microot = fn_micrographs_ctf_all[imic].withoutExtension(); RFLOAT defU, defV, defAng, CC, HT, CS, AmpCnst, XMAG, DStep; - RFLOAT maxres = -999., valscore = -999., phaseshift = -999., icering = 0.; + RFLOAT maxres = -999., valscore = -999., phaseshift = -999., icering = 0., icepowerfrac = 0., icekurtosis = 0.; bool has_this_ctf = getCtffindResults(fn_microot, defU, defV, defAng, CC, - HT, CS, AmpCnst, XMAG, DStep, maxres, valscore, phaseshift, icering); + HT, CS, AmpCnst, XMAG, DStep, maxres, valscore, phaseshift, + icering, icepowerfrac, icekurtosis); if (!has_this_ctf) { @@ -451,6 +452,8 @@ void CtffindRunner::joinCtffindResults() MDctf.setValue(EMDL_CTF_VALIDATIONSCORE, valscore); MDctf.setValue(EMDL_CTF_ICERINGDENSITY, icering); + if (use_given_ps) MDctf.setValue(EMDL_CTF_ICERINGPOWERFRACTION, icepowerfrac); + if (use_given_ps) MDctf.setValue(EMDL_CTF_ICERINGKURTOSIS, icekurtosis); if (is_tomo) { @@ -511,6 +514,11 @@ void CtffindRunner::joinCtffindResults() plot_labels.push_back(EMDL_CTF_FOM); plot_labels.push_back(EMDL_CTF_VALIDATIONSCORE); plot_labels.push_back(EMDL_CTF_ICERINGDENSITY); + if (use_given_ps) + { + plot_labels.push_back(EMDL_CTF_ICERINGPOWERFRACTION); + plot_labels.push_back(EMDL_CTF_ICERINGKURTOSIS); + } FileName fn_eps, fn_eps_root = fn_out+"micrographs_ctf"; std::vector all_fn_eps; for (int i = 0; i < plot_labels.size(); i++) @@ -531,7 +539,8 @@ void CtffindRunner::joinCtffindResults() // Histogram std::vector histX, histY; CPlot2D *plot2D=new CPlot2D(""); - MDctf.columnHistogram(label,histX,histY,0, plot2D); + if (label == EMDL_CTF_ICERINGKURTOSIS) MDctf.columnHistogram(label,histX,histY,0, plot2D, -1, -1, 6); + else MDctf.columnHistogram(label,histX,histY,0, plot2D); fn_eps = fn_eps_root + "_hist_" + EMDL::label2Str(label) + ".eps"; plot2D->OutputPostScriptPlot(fn_eps); all_fn_eps.push_back(fn_eps); @@ -787,16 +796,16 @@ void CtffindRunner::executeCtffind4(long int imic) bool CtffindRunner::getCtffindResults(FileName fn_microot, RFLOAT &defU, RFLOAT &defV, RFLOAT &defAng, RFLOAT &CC, RFLOAT &HT, RFLOAT &CS, RFLOAT &AmpCnst, RFLOAT &XMAG, RFLOAT &DStep, - RFLOAT &maxres, RFLOAT &valscore, RFLOAT &phaseshift, RFLOAT &icering, bool do_warn) + RFLOAT &maxres, RFLOAT &valscore, RFLOAT &phaseshift, RFLOAT &icering, RFLOAT &icepowerfrac, RFLOAT &icekurtosis, bool do_warn) { if (is_ctffind4) { return getCtffind4Results(fn_microot, defU, defV, defAng, CC, HT, CS, AmpCnst, XMAG, DStep, - maxres, phaseshift, icering, do_warn); + maxres, phaseshift, icering, icepowerfrac, icekurtosis, do_warn); } else { - icering = 0.; + icering = icepowerfrac = icekurtosis = 0.; return getCtffind3Results(fn_microot, defU, defV, defAng, CC, HT, CS, AmpCnst, XMAG, DStep, maxres, phaseshift, valscore, do_warn); } @@ -879,7 +888,7 @@ bool CtffindRunner::getCtffind3Results(FileName fn_microot, RFLOAT &defU, RFLOAT bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT &defV, RFLOAT &defAng, RFLOAT &CC, RFLOAT &HT, RFLOAT &CS, RFLOAT &AmpCnst, RFLOAT &XMAG, RFLOAT &DStep, - RFLOAT &maxres, RFLOAT &phaseshift, RFLOAT &icering, bool do_warn) + RFLOAT &maxres, RFLOAT &phaseshift, RFLOAT &icering, RFLOAT &icepowerfrac, RFLOAT &icekurtosis, bool do_warn) { FileName fn_root = getOutputFileWithNewUniqueDate(fn_microot, fn_out); FileName fn_log = fn_root + "_ctffind4.log"; @@ -964,6 +973,42 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT in2.close(); + + // Get rlnIceRingDensity, as suggested by Rafael Leiro from the CNIO in Madrid + FileName fn_avrot = fn_root + "_avrot.txt"; + std::ifstream av(fn_avrot.data(), std::ios_base::in); + icering = 0.; + if (!av.fail()) + { + std::string s1, s2; + //skip 5 lines + for(int i = 0; i < 5; ++i) + std::getline(av, s1); + + // Now get lines 6 and 7 + std::getline(av,s1); + tokenize(s1, words); + int imin = -999; + int imax = -999; + for (int i = 0; i < words.size(); i++) + if (imin < 0 && textToFloat(words[i]) >= 0.25) { + imin = i; + break; + } + for (int i = imin; i < words.size(); i++) + if (imax < 0 && imin > 0 && textToFloat(words[i]) > 0.28) { + imax = i; + break; + } + std::getline(av,s2); + tokenize(s2, words); + for (int i = imin; i < imax; i++) + { + icering += fabs(textToFloat(words[i])); + } + } + + // Be careful, because avgrot files from CTFFIND-4.1 are somehow normalized badly, so re-read power spectrum image // This only works if given the power spectrum from RELION's motioncorr. Otherwise, fall back on Rafa's approach below FileName fn_ps = fn_root + ".mrc"; @@ -978,18 +1023,31 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT RFLOAT mysize = angpix*XSIZE(Ips()); RFLOAT r2ice_min=4*mysize*mysize/(3.9*3.9); RFLOAT r2ice_max=4*mysize*mysize/(3.5*3.5); - RFLOAT n=0., mean=0., M2=0., M3=0., M4=0.; + RFLOAT r2low_min=4*mysize*mysize/(4.5*4.5); + RFLOAT r2low_max=4*mysize*mysize/(4.0*4.0); + RFLOAT r2high_min=4*mysize*mysize/(3.4*3.4); + RFLOAT r2high_max=4*mysize*mysize/(3.0*3.0); + RFLOAT n=0., mean=0., pow_ice=0., pow_band=0., n_band= 0., M2=0., M3=0., M4=0.; for (long int i=0; i<=FINISHINGY(Ips()); i++) // Use only bottom half of the diagnostic image, as top contains model and averaged spectra!! for (long int j=STARTINGX(Ips()); j<=FINISHINGX(Ips()); j++) { RFLOAT r2 = RFLOAT(i*i + j*j); if (r2 > r2ice_min && r2 < r2ice_max) { - n += 1; + n += 1.; mean += A2D_ELEM(Ips(), i, j); } + else if (r2 > r2low_min && r2 < r2low_max || r2 > r2high_min && r2 < r2high_max) + { + n_band += 1.; + pow_band += A2D_ELEM(Ips(), i, j); + } } mean /= n; + pow_band /= n_band; + if (pow_band > 0.) icepowerfrac = (mean - pow_band) / pow_band; + else icepowerfrac = 0.; + for (long int i=0; i<=FINISHINGY(Ips()); i++) // Use only bottom half of the diagnostic image, as top contains model and averaged spectra!! for (long int j=STARTINGX(Ips()); j<=FINISHINGX(Ips()); j++) { @@ -1006,46 +1064,11 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT RFLOAT s2 = M2 / (n - 1); RFLOAT s = sqrt(s2); //RFLOAT skewness = (s > 0.) ? (n / ((n - 1) * (n - 2))) * (M3 / (s * s * s)) : 0.; - RFLOAT kurtosis = (n * (n + 1) * M4) / ((n - 1) * (n - 2) * (n - 3) * (s2 * s2)) - (3 * (n - 1) * (n - 1)) / ((n - 2) * (n - 3)); - //std::cerr << " s2= " << s2 << " skew= " << skewness << " kurtosis= " << kurtosis << std::endl; - icering = XMIPP_MIN(6., kurtosis); + icekurtosis = (n * (n + 1) * M4) / ((n - 1) * (n - 2) * (n - 3) * (s2 * s2)) - (3 * (n - 1) * (n - 1)) / ((n - 2) * (n - 3)); } else { - // Get rlnIceRingDensity, as suggested by Rafael Leiro from the CNIO in Madrid - FileName fn_avrot = fn_root + "_avrot.txt"; - std::ifstream av(fn_avrot.data(), std::ios_base::in); - icering = 0.; - if (!av.fail()) - { - std::string s1, s2; - //skip 5 lines - for(int i = 0; i < 5; ++i) - std::getline(av, s1); - - // Now get lines 6 and 7 - std::getline(av,s1); - tokenize(s1, words); - int imin = -999; - int imax = -999; - for (int i = 0; i < words.size(); i++) - if (imin < 0 && textToFloat(words[i]) >= 0.25) { - imin = i; - break; - } - for (int i = imin; i < words.size(); i++) - if (imax < 0 && imin > 0 && textToFloat(words[i]) > 0.28) { - imax = i; - break; - } - std::getline(av,s2); - tokenize(s2, words); - for (int i = imin; i < imax; i++) - { - icering += fabs(textToFloat(words[i])); - } - } } return Final_is_found; diff --git a/src/ctffind_runner.h b/src/ctffind_runner.h index 4c8e34b52..f2b31114d 100644 --- a/src/ctffind_runner.h +++ b/src/ctffind_runner.h @@ -180,13 +180,13 @@ class CtffindRunner // Get micrograph metadata bool getCtffindResults(FileName fn_mic, RFLOAT &defU, RFLOAT &defV, RFLOAT &defAng, RFLOAT &CC, RFLOAT &HT, RFLOAT &CS, RFLOAT &AmpCnst, RFLOAT &XMAG, RFLOAT &DStep, - RFLOAT &maxres, RFLOAT &valscore, RFLOAT &phaseshift, RFLOAT &icering, bool do_warn = true); + RFLOAT &maxres, RFLOAT &valscore, RFLOAT &phaseshift, RFLOAT &icering, RFLOAT &icepowerfrac, RFLOAT &icekurtosis, bool do_warn = true); bool getCtffind3Results(FileName fn_mic, RFLOAT &defU, RFLOAT &defV, RFLOAT &defAng, RFLOAT &CC, RFLOAT &HT, RFLOAT &CS, RFLOAT &AmpCnst, RFLOAT &XMAG, RFLOAT &DStep, RFLOAT &maxres, RFLOAT &phaseshift, RFLOAT &valscore, bool do_warn = true); bool getCtffind4Results(FileName fn_mic, RFLOAT &defU, RFLOAT &defV, RFLOAT &defAng, RFLOAT &CC, RFLOAT &HT, RFLOAT &CS, RFLOAT &AmpCnst, RFLOAT &XMAG, RFLOAT &DStep, - RFLOAT &maxres, RFLOAT &phaseshift, RFLOAT &icering, bool do_warn = true); + RFLOAT &maxres, RFLOAT &phaseshift, RFLOAT &icering, RFLOAT &icepowerfrac, RFLOAT &icekurtosis, bool do_warn = true); }; diff --git a/src/metadata_label.h b/src/metadata_label.h index 4ef6d1965..bf215596a 100644 --- a/src/metadata_label.h +++ b/src/metadata_label.h @@ -156,7 +156,9 @@ enum EMDLabel EMDL_CTF_ENERGY_LOSS, ///< Energy loss EMDL_CTF_FOM, ///< ctffind FOM (CC) for quality of CTF-fit EMDL_CTF_IMAGE, ///< name of an image describing the CTF model - EMDL_CTF_ICERINGDENSITY, ///< sum of image power in ice ring + EMDL_CTF_ICERINGDENSITY, ///< sum of image power in ice ring + EMDL_CTF_ICERINGPOWERFRACTION, ///< fraction of power in ice ring, relative to above and below bands + EMDL_CTF_ICERINGKURTOSIS, ///< kurtosis of power values in ice ring EMDL_CTF_LENS_STABILITY, ///< Lens stability EMDL_CTF_MAGNIFICATION, ///< Magnification used for CTF-determination (deprecated) EMDL_CTF_PHASESHIFT, ///< Phase-shift from a phase plate @@ -888,7 +890,9 @@ class StaticInitialization EMDL::addLabel(EMDL_CTF_ENERGY_LOSS, EMDL_DOUBLE, "rlnEnergyLoss", "Energy loss (in eV)"); EMDL::addLabel(EMDL_CTF_FOM, EMDL_DOUBLE, "rlnCtfFigureOfMerit", "Figure of merit for the fit of the CTF (not used inside relion_refine)"); EMDL::addLabel(EMDL_CTF_IMAGE, EMDL_STRING, "rlnCtfImage", "Name of an image with all CTF values"); - EMDL::addLabel(EMDL_CTF_ICERINGDENSITY, EMDL_DOUBLE, "rlnCtfIceRingDensity", "Power of the image in the ice ring frequency range (0.25-0.28 A-1)"); + EMDL::addLabel(EMDL_CTF_ICERINGDENSITY, EMDL_DOUBLE, "rlnCtfIceRingDensity", "Power of the image in the ice ring frequency range (0.25-0.28 A-1)"); + EMDL::addLabel(EMDL_CTF_ICERINGPOWERFRACTION, EMDL_DOUBLE, "rlnCtfIceRingPowerFraction", "Fraction of power in ice ring, relative to above and below resolution bands"); + EMDL::addLabel(EMDL_CTF_ICERINGKURTOSIS, EMDL_DOUBLE, "rlnCtfIceRingKurtosis", "Kurtosis of power values within the ice ring"); EMDL::addLabel(EMDL_CTF_LENS_STABILITY, EMDL_DOUBLE, "rlnLensStability", "Lens stability (in ppm)"); EMDL::addLabel(EMDL_CTF_MAGNIFICATION, EMDL_DOUBLE, "rlnMagnification", "Magnification at the detector (in times)"); EMDL::addLabel(EMDL_CTF_PHASESHIFT, EMDL_DOUBLE, "rlnPhaseShift", "Phase-shift from a phase-plate (in degrees)"); From 130f6a6b9a95da961abe30a89c2cf733c9d1e387 Mon Sep 17 00:00:00 2001 From: scheres Date: Mon, 24 Mar 2025 15:53:21 +0000 Subject: [PATCH 081/189] debugged and changed Jennys segmentation and made everything work from GUI --- environment.yml | 1 + scripts/trace_amyloids.in | 662 ++++++++++++++++++++------------------ src/amyloid_finder.cpp | 16 +- src/amyloid_finder.h | 2 +- src/gui_mainwindow.cpp | 7 +- src/manualpicker.cpp | 1 - 6 files changed, 373 insertions(+), 316 deletions(-) diff --git a/environment.yml b/environment.yml index ab0acb755..b443541aa 100644 --- a/environment.yml +++ b/environment.yml @@ -27,6 +27,7 @@ dependencies: - dill==0.3.7 - numpy==1.26.1 - scipy==1.11.2 + - skan==0.12.2 - git+https://github.com/3dem/relion-classranker - git+https://github.com/3dem/relion-blush - git+https://github.com/3dem/DynaMight diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index e4ba8afa6..b3f58baec 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -1,37 +1,41 @@ #!@PYTHON_EXE_PATH@ -import numpy as np -import mrcfile -import starfile -from skimage.filters import gaussian -from skimage.transform import probabilistic_hough_line -from skimage.morphology import skeletonize -from pathlib import Path -import math -import pandas as pd import argparse -""" Full assembly of the parts to form the complete network """ +import math +import os +from pathlib import Path -""" Parts of the U-Net model """ +import numpy as np +import pandas as pd +import starfile import torch import torch.nn as nn import torch.nn.functional as F -class DoubleConv(nn.Module): - """(convolution => [BN] => ReLU) * 2""" +import mrcfile +import matplotlib.pyplot as plt + +from skimage.morphology import remove_small_objects, skeletonize +from skimage.measure import regionprops, label as label_connected_component +from skan import Skeleton, summarize + +############################################################################### +# U-Net Model +############################################################################### +class DoubleConv(nn.Module): + """(convolution => [InstanceNorm] => GELU) * 2""" def __init__(self, in_channels, out_channels, mid_channels=None): super().__init__() - if not mid_channels: mid_channels = out_channels self.double_conv = nn.Sequential( nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False), - nn.BatchNorm2d(mid_channels), - nn.ReLU(inplace=True), + nn.InstanceNorm2d(mid_channels), + nn.GELU(), nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False), - nn.BatchNorm2d(out_channels), - nn.ReLU(inplace=True) + nn.InstanceNorm2d(out_channels), + nn.GELU() ) def forward(self, x): @@ -39,8 +43,6 @@ class DoubleConv(nn.Module): class Down(nn.Module): - """Downscaling with maxpool then double conv""" - def __init__(self, in_channels, out_channels): super().__init__() self.maxpool_conv = nn.Sequential( @@ -53,12 +55,8 @@ class Down(nn.Module): class Up(nn.Module): - """Upscaling then double conv""" - def __init__(self, in_channels, out_channels, bilinear=True): super().__init__() - - # if bilinear, use the normal convolutions to reduce the number of channels if bilinear: self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True) self.conv = DoubleConv(in_channels, out_channels, in_channels // 2) @@ -68,22 +66,20 @@ class Up(nn.Module): def forward(self, x1, x2): x1 = self.up(x1) - # input is CHW diffY = x2.size()[2] - x1.size()[2] diffX = x2.size()[3] - x1.size()[3] - x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2, - diffY // 2, diffY - diffY // 2]) - # if you have padding issues, see - # https://github.com/HaiyongJiang/U-Net-Pytorch-Unstructured-Buggy/commit/0e854509c2cea854e247a9c615f175f76fbb2e3a - # https://github.com/xiaopeng-liao/Pytorch-UNet/commit/8ebac70e633bac59fc22bb5195e513d5832fb3bd + x1 = F.pad( + x1, + [diffX // 2, diffX - diffX // 2, diffY // 2, diffY - diffY // 2] + ) x = torch.cat([x2, x1], dim=1) return self.conv(x) class OutConv(nn.Module): def __init__(self, in_channels, out_channels): - super(OutConv, self).__init__() + super().__init__() self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1) def forward(self, x): @@ -91,25 +87,31 @@ class OutConv(nn.Module): class UNet(nn.Module): - def __init__(self, n_channels, n_classes, bilinear=False): - super(UNet, self).__init__() + def __init__(self, n_channels, n_classes, embedding_dim=32, bilinear=False): + super().__init__() self.n_channels = n_channels self.n_classes = n_classes self.bilinear = bilinear - #self.accuracy = BinaryAccuracy() - - self.inc = (DoubleConv(n_channels, 64)) - self.down1 = (Down(64, 128)) - self.down2 = (Down(128, 256)) - self.down3 = (Down(256, 512)) + self.base_channel_sz = 128 + + self.inc = DoubleConv(n_channels, self.base_channel_sz) + self.down1 = Down(self.base_channel_sz, 2 * self.base_channel_sz) + self.down2 = Down(2 * self.base_channel_sz, 4 * self.base_channel_sz) + self.down3 = Down(4 * self.base_channel_sz, 8 * self.base_channel_sz) factor = 2 if bilinear else 1 - self.down4 = (Down(512, 1024 // factor)) - self.up1 = (Up(1024, 512 // factor, bilinear)) - self.up2 = (Up(512, 256 // factor, bilinear)) - self.up3 = (Up(256, 128 // factor, bilinear)) - self.up4 = (Up(128, 64, bilinear)) - self.outc = (OutConv(64, n_classes)) + self.down4 = Down(8 * self.base_channel_sz, 16 * self.base_channel_sz // factor) + + self.up1 = Up(16 * self.base_channel_sz, 8 * self.base_channel_sz // factor, bilinear) + self.up2 = Up(8 * self.base_channel_sz, 4 * self.base_channel_sz // factor, bilinear) + self.up3 = Up(4 * self.base_channel_sz, 2 * self.base_channel_sz // factor, bilinear) + self.up4 = Up(2 * self.base_channel_sz, self.base_channel_sz, bilinear) + + self.outc = OutConv(self.base_channel_sz, n_classes) + self.embedding_head = nn.Sequential( + nn.Conv2d(self.base_channel_sz, embedding_dim, kernel_size=1), + nn.Sigmoid() + ) def forward(self, x): x1 = self.inc(x) @@ -117,292 +119,340 @@ class UNet(nn.Module): x3 = self.down2(x2) x4 = self.down3(x3) x5 = self.down4(x4) + x = self.up1(x5, x4) x = self.up2(x, x3) x = self.up3(x, x2) x = self.up4(x, x1) - logits = self.outc(x) - return logits - - def use_checkpointing(self): - self.inc = torch.utils.checkpoint(self.inc) - self.down1 = torch.utils.checkpoint(self.down1) - self.down2 = torch.utils.checkpoint(self.down2) - self.down3 = torch.utils.checkpoint(self.down3) - self.down4 = torch.utils.checkpoint(self.down4) - self.up1 = torch.utils.checkpoint(self.up1) - self.up2 = torch.utils.checkpoint(self.up2) - self.up3 = torch.utils.checkpoint(self.up3) - self.up4 = torch.utils.checkpoint(self.up4) - self.outc = torch.utils.checkpoint(self.outc) + seg_logits = self.outc(x) + embedding = self.embedding_head(x) + return seg_logits, embedding + + @staticmethod def load_from_checkpoint(model_path: str, n_channels: int, n_classes: int): model = UNet(n_channels=n_channels, n_classes=n_classes) lightning_dict = torch.load(model_path, map_location="cpu") state_dict = lightning_dict['state_dict'] try: - model.load_state_dict(state_dict, weights_only=True) + model.load_state_dict(state_dict, strict=False) except: model.load_state_dict(state_dict) return model - - -#------------------------------------------------------------------------------------------ +############################################################################### +# MRC + instance segmentation +############################################################################### def load_mrc(mrc_fn: str, multiply_global_origin: bool = True): - mrc_file_handle = mrcfile.open(mrc_fn, "r") - voxel_size = float(mrc_file_handle.voxel_size.x) - - if voxel_size <= 0: - raise RuntimeError(f"Seems like the MRC file: {mrc_fn} does not have a header.") - - c = mrc_file_handle.header["mapc"] - r = mrc_file_handle.header["mapr"] - s = mrc_file_handle.header["maps"] - - global_origin = mrc_file_handle.header["origin"] - global_origin = np.array([global_origin.x, global_origin.y, global_origin.z]) - global_origin[0] += mrc_file_handle.header["nxstart"] - global_origin[1] += mrc_file_handle.header["nystart"] - global_origin[2] += mrc_file_handle.header["nzstart"] - - if multiply_global_origin: - global_origin *= mrc_file_handle.voxel_size.x - - if c == 1 and r == 2 and s == 3: - grid = mrc_file_handle.data - elif c == 3 and r == 2 and s == 1: - grid = np.moveaxis(mrc_file_handle.data, [0, 1, 2], [2, 1, 0]) - elif c == 2 and r == 1 and s == 3: - grid = np.moveaxis(mrc_file_handle.data, [1, 2, 0], [2, 1, 0]) - else: - raise RuntimeError("MRC file axis arrangement not supported!") - - return grid, voxel_size, global_origin - -def is_in_between(point, line): - dx = line[1][0] - line[0][0] - dy = line[1][1] - line[0][1] - dotp = (point[0] - line[0][0])*dx + (point[1] - line[0][1])*dy - return 0 <= dotp and dotp <= dx*dx + dy*dy - -def distance_point_line(point, line): - x1=line[0][0] - x2=line[1][0] - y1=line[0][1] - y2=line[1][1] - x0=point[0] - y0=point[1] - dd = abs( (x2-x1)*(y1-y0) - (x1-x0)*(y2-y1) ) / np.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ) - if not is_in_between(point,line): - closest = min(distance_point_point(point,line[0]), distance_point_point(point,line[1])) - return max(closest, dd) - else: - return dd - -def distance_point_point(point1, point2): - x1=point1[0] - y1=point1[1] - x2=point2[0] - y2=point2[1] - return np.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)) - -def angle_line_line(line1, line2): - a1 = np.arctan2(line1[1][1]-line1[0][1], line1[1][0]-line1[0][0]) - a2 = np.arctan2(line2[1][1]-line2[0][1], line2[1][0]-line2[0][0]) - return abs(a2-a1) - -def prune_lines(inlines, mind, min_ang, max_merge): - import math - lengths = [] - merged = [] - lines = np.asarray(inlines) - for line in lines: - lengths = np.append(lengths, np.linalg.norm(line[0]-line[1])) - merged = np.append(merged, 1) - sortidx = np.argsort(lengths) - - min_ang_rad = math.radians(min_ang) - NN = len(sortidx) - idx = NN - 1 - while idx >= 0: - i1 = sortidx[idx] - - for i2 in range(NN): - if (i1 != i2 and lengths[i2] > 0. and lengths[i1] > 0.): - - d11 = distance_point_line(lines[i1][0],lines[i2]) - d12 = distance_point_line(lines[i1][1],lines[i2]) - d21 = distance_point_line(lines[i2][0],lines[i1]) - d22 = distance_point_line(lines[i2][1],lines[i1]) - ang = angle_line_line(lines[i1], lines[i2]) - sum = 0 - if (d11 < mind): - sum = sum + 1 - if (d12 < mind): - sum = sum + 1 - if (d21 < mind): - sum = sum + 1 - if (d22 < mind): - sum = sum + 1 - - # merge lines if they haven't been merged too often already, they overlap two or more points and are parallel - if ( (merged[i1] + merged[i2] < max_merge) and (sum >= 2) and (ang < min_ang_rad or abs(ang-math.pi) < min_ang_rad) ): - - # select the two points with the furthest distance - maxd=0 - for i in range(2): - for j in range(2): - d = distance_point_point(lines[i1][i], lines[i2][j]) - if (d > maxd): - maxd = d - mymax0 = lines[i1][i] - mymax1 = lines[i2][j] - # perhaps original one was longer? - if maxd > lengths[i1]: - lines[i1][0] = mymax0 - lines[i1][1] = mymax1 - lengths[i1] = np.linalg.norm(lines[i1][0]-lines[i1][1]) - merged[i1] = merged[i1] + merged[i2] - - lines[i2][0][0] = -9999 - lines[i2][0][1] = -9999 - lines[i2][1][0] = -9999 - lines[i2][1][1] = -9999 - lengths[i2] = 0 - merged[i2] = 0 - idx = idx + 1 - break - - # remove smaller lines with both points close to longer one - elif (d21 < 2*mind and d22 < 2*mind): - lines[i2][0][0] = -9999 - lines[i2][0][1] = -9999 - lines[i2][1][0] = -9999 - lines[i2][1][1] = -9999 - lengths[i2] = 0 - - idx = idx - 1 - - return lines - - -def pick_filaments(score, radius, threshold, filaments_length, filaments_plot): - - #Parameters - thr = round(0.1*filaments_length) - line_length = filaments_length - gap = radius - mind= 1.5*radius - min_angle = 10 - max_merge = 10 - - #bin_score = gaussian(score, 3) > threshold - bin_score = score > threshold - edges = skeletonize(bin_score) - houghs = probabilistic_hough_line(edges, threshold=thr, line_length=line_length, line_gap=gap) - lines = prune_lines(houghs, mind=mind, min_ang=min_angle, max_merge=max_merge) - - if filaments_plot: - import matplotlib.pyplot as plt - from matplotlib import cm - fig, axes = plt.subplots(1, 4, figsize=(15, 5), sharex=True, sharey=True) - ax = axes.ravel() - - ax[0].imshow(score, cmap=cm.binary, vmin=0, vmax=1) - ax[0].imshow(bin_score, alpha=0.5, cmap=cm.Reds) - ax[0].set_title('FOM [-20,5] thr= ' + str(threshold)) - - ax[1].imshow(edges, cmap=cm.gray) - ax[1].set_title('Skeletonize') - - ax[2].imshow(edges * 0) - for hough in houghs: - p0, p1 = hough - ax[2].plot((p0[0], p1[0]), (p0[1], p1[1])) - ax[2].set_xlim((0, score.shape[1])) - ax[2].set_ylim((score.shape[0], 0)) - ax[2].set_title('Hough transform; len= ' + str(line_length) + ' gap= ' + str(gap)) - - ax[3].imshow(edges * 0) - for line in lines: - p0, p1 = line - ax[3].plot((p0[0], p1[0]), (p0[1], p1[1])) - ax[3].set_xlim((0, score.shape[1])) - ax[3].set_ylim((score.shape[0], 0)) - ax[3].set_title('Prune: mind= ' + str(mind)) - - for a in ax: - a.set_axis_off() - - plt.tight_layout() - plt.show() - - oldlines=lines - # Flatten lines into 2D array, as rest of topaz - NN=len(lines) - if NN>0: - lines = lines.reshape(2*NN, 2) - # Remove -9999 coordinates - newlines = [] - for i in range(2*NN): - if (lines[i,0] != -9999 and lines[i,1] != -9999): - newlines = np.append(newlines, lines[i]) - - NN=round(len(newlines)/2) - if (NN>0): - lines = newlines.reshape(NN, 2) + with mrcfile.open(mrc_fn, "r") as mrc_file_handle: + voxel_size = float(mrc_file_handle.voxel_size.x) + if voxel_size <= 0: + raise RuntimeError(f"MRC file {mrc_fn} does not have a valid header.") + + c = mrc_file_handle.header["mapc"] + r = mrc_file_handle.header["mapr"] + s = mrc_file_handle.header["maps"] + + global_origin = mrc_file_handle.header["origin"] + global_origin = np.array([global_origin.x, global_origin.y, global_origin.z]) + global_origin[0] += mrc_file_handle.header["nxstart"] + global_origin[1] += mrc_file_handle.header["nystart"] + global_origin[2] += mrc_file_handle.header["nzstart"] + + if multiply_global_origin: + global_origin *= mrc_file_handle.voxel_size.x + + raw_data = mrc_file_handle.data + if c == 1 and r == 2 and s == 3: + grid = raw_data + elif c == 3 and r == 2 and s == 1: + grid = np.moveaxis(raw_data, [0, 1, 2], [2, 1, 0]) + elif c == 2 and r == 1 and s == 3: + grid = np.moveaxis(raw_data, [1, 2, 0], [2, 1, 0]) else: - lines = [] - - return lines + raise RuntimeError("MRC file axis arrangement not supported!") + return grid, voxel_size, global_origin -if __name__ == "__main__": - parser = argparse.ArgumentParser('Scripts for picking filaments in FOM maps') - parser.add_argument('-if', '--input_fom', help='input fom to pick filaments in') - parser.add_argument('-ip', '--input_psi', help='input psi to pick filaments in') - parser.add_argument('-m', '--model_path', help="path to model") - parser.add_argument('-o', '--output', help='output star file with coordinates') - parser.add_argument('-r', '--radius', type=int, help='radius of the regions to extract (in A)') - parser.add_argument('-t', '--threshold', default=0.5, type=float, help='FOM threshold at which to terminate region extraction (default: 0.5)') - parser.add_argument('-l', '--length', default=-1, type=int, help='minimum length of straight filament segments to be picked (in Angstrom) (default: twice --radius)') - parser.add_argument('-s', '--scale', type=float, default=1, help='Multiply outputcoordinates by this factor. (default: 1)') - parser.add_argument('-fp', '--plot', action='store_true', help='flag for filament start-end picking plus plotting of its intermediate stages (useful for tuning parameters).') - parser.add_argument('-d', '--device', help="Which GPU to use", default="cuda:0") +class InstanceConfig: + thres_foreground = 0.5 + obj_min_size = 50 + + +def instance_from_layered_embedding(raw, config): + """ + Build an instance segmentation from a 'layered_embedding' + 'foreground'. + Return: + - instances: list of binary masks + - layered_objects: multi-channel binary assignment + """ + from skimage.measure import label as label_cc + + embedding = np.copy(np.squeeze(raw['layered_embedding'])) + embedding = np.transpose(embedding, (1, 2, 0)) # shape: (H, W, embedding_dim) + fg = np.copy(np.squeeze(raw['foreground'] > config.thres_foreground)) + + # Remove small objects + for r in regionprops(label_cc(fg)): + if r.area < config.obj_min_size: + fg[r.coords[:, 0], r.coords[:, 1]] = 0 + + # layering foreground + layered_objects = np.zeros(embedding.shape, bool) + rr, cc = np.nonzero(fg) + # threshold embedding => + layered_objects[rr, cc, :] = embedding[rr, cc, :] > config.thres_foreground + # also pick the channel with max embedding => set True + idx = np.argmax(embedding[rr, cc, :], axis=-1) + layered_objects[rr, cc, idx] = 1 + + instances = [] + for i_ch in range(layered_objects.shape[-1]): + L = label_cc(layered_objects[:, :, i_ch]) + for r in regionprops(L): + if r.area < config.obj_min_size: + layered_objects[r.coords[:, 0], r.coords[:, 1], i_ch] = 0 + else: + instances.append(L == r.label) + + return instances, layered_objects + + + +############################################################################### +# Sample coordinates from the final pruned label, including IDs +############################################################################### +def sample_coordinates_with_ids(label_image, step=50): + + labels = np.unique(label_image) + labels = labels[labels != 0] + labels.sort() # ensure ascending + results = [] + + filament_counter = 1 + for lbl in labels: + bin_mask = (label_image == lbl) + sk = Skeleton(bin_mask) + if sk.n_paths == 0: + continue + + # For each path in this label, sample coordinates + for p_idx in range(sk.n_paths): + coords = sk.path_coordinates(p_idx).astype(int) + if len(coords) == 0: + continue + for i in range(0, len(coords), step): + r, c = coords[i] + results.append([c, r, filament_counter]) + # ensure final coordinate is included + if (len(coords) - 1) % step != 0: + r, c = coords[-1] + results.append([c, r, filament_counter]) + + filament_counter += 1 + + return np.array(results, dtype=int) + + +############################################################################### +# Visualization Helpers +############################################################################### +import matplotlib.colors as mcolors + +def color_nonrandom(label_image): + H, W = label_image.shape + colorized = np.full((H, W, 3), 127, dtype=np.uint8) + + labels = np.unique(label_image) + labels = labels[labels != 0] + i = 0 + for lbl in labels: + if i%6==0: + r= 255 + g= 0 + b =0 + elif i%6==1: + r= 0 + g= 255 + b =0 + elif i%6==2: + r= 0 + g= 0 + b =255 + elif i%6==3: + r= 255 + g= 255 + b =0 + elif i%6==4: + r= 0 + g= 255 + b =255 + elif i%6==5: + r= 255 + g= 0 + b =255 + mask = (label_image == lbl) + colorized[mask] = (r, g, b) + i=i+1 + return colorized + +def color_foreground(grey_image, threshold): + colorized = np.stack([grey_image] * 3, axis=-1) + + mask = (grey_image > threshold) + colorized[mask] = (255, 0, 0) + return colorized + + +############################################################################### +# Sjors go at pruning the raw skeleton image +############################################################################### + +def prune_skeleton(skeleton, min_length=10): + """Prune skeleton branches that too short.""" + skel = Skeleton(skeleton) + df = summarize(skel, separator='-') + + labeled_skel = np.zeros_like(skeleton, dtype=np.int32) + path_id = 1 + for idx, row in df.iterrows(): + branch_coords = skel.path_coordinates(idx) # Get coordinates of the branch + + # Compute Euclidean length of the branch + length = np.sum(np.sqrt(np.sum(np.diff(branch_coords, axis=0) ** 2, axis=1))) + + if length >= min_length: + for x, y in branch_coords: + labeled_skel[int(x), int(y)] = path_id # Assign path label + path_id += 1 # Increment label for next path + + return labeled_skel + + + + +############################################################################### +# MAIN +############################################################################### +def main(): + parser = argparse.ArgumentParser("Skeleton-based filament picking with instance merging, branch pruning, and ID labeling") + parser.add_argument("-if", "--input_fom", help="Input FOM MRC file") + parser.add_argument("-ip", "--input_psi", help="Input PSI MRC file") + parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint") + parser.add_argument("-o", "--output", help="Output star file with coordinates") + parser.add_argument("-r", "--radius", type=int, help="Radius of region to exclude near edges (in A)") + parser.add_argument("-l", "--minimum_length", type=int, default=250, + help="Remove final segments smaller than this size in Angstroms (default=250)") + parser.add_argument("-t", "--threshold", default=0.5, type=float, help="Foreground threshold (default=0.5)") + parser.add_argument("-s", "--scale", type=float, default=1.0, + help="Multiply output coordinates by this factor (default=1.0)") + parser.add_argument("-d", "--device", default="cuda:0", help="Which GPU device to use") + + parser.add_argument("--sample_step", type=int, default=100, + help="Sample a coordinate every 'sample_step' Angstroms along each path (default=50)") + parser.add_argument("--plot", action='store_true', + help="If set, display/save a collage figure of the pipeline results.") args = parser.parse_args() + # ------------------- Load MRCs ---------------------- fom_image, pixel_size, origin = load_mrc(args.input_fom) psi_image, _, _ = load_mrc(args.input_psi) + H, W = fom_image.shape device = torch.device(args.device) + # ------------------ Load Model + Run Inference ---------------- model = UNet.load_from_checkpoint(args.model_path, n_channels=2, n_classes=1) - model.eval() - model = model.to(device) - fp = torch.from_numpy(np.stack((fom_image, psi_image), axis=0)).unsqueeze(0).to(device) - with torch.no_grad(): - output_segmentation = model(fp).sigmoid().cpu().numpy() + model.eval().to(device) + + in_tensor = np.stack((fom_image, psi_image), axis=0) # shape (2,H,W) + in_tensor = torch.from_numpy(in_tensor[None]).float().to(device) # shape (1,2,H,W) + + with torch.no_grad(), torch.autocast("cuda", dtype=torch.float16): + seg_logits, embedding = model(in_tensor) + seg_logits, embedding = seg_logits.float(), embedding.float() + foreground = torch.sigmoid(seg_logits).squeeze().cpu().numpy() # => (H,W) + + # 2) Clean + threshold => remove small objects + bin_foreground = (foreground > args.threshold) + min_size = 100 + clean_foreground = remove_small_objects(bin_foreground, min_size=min_size) + + # 3) Crop edges if radius is given + if args.radius is not None: + d = round(args.radius / pixel_size) + clean_foreground[:d, :] = False + clean_foreground[-d:, :] = False + clean_foreground[:, :d] = False + clean_foreground[:, -d:] = False + + # 4) Skeletonize + skeleton_img = skeletonize(clean_foreground) + if not skeleton_img.any(): + print("No skeleton found. Creating empty star file.") + Path(args.output).touch() + return + + # 5) Prune skeleton based on Skan's branches: remove any branch with length below args.final_min_size + pruned_skeleton = prune_skeleton(skeleton_img, min_length=args.minimum_length / pixel_size) - # Use threshold as defined by arguments to binarize - #binarized_output = (output_segmentation.reshape(*output_segmentation.shape[-2:]) > args.threshold) - binarized_output = output_segmentation.reshape(*output_segmentation.shape[-2:]) - - # Set edges to zero to prevent picking too close to the edge - d = round(args.radius/pixel_size) - cropimage = binarized_output.copy() # because image was readonly - cropimage[:d, :] = 0 # Top edge - cropimage[-d:, :] = 0 # Bottom edge - cropimage[:, :d] = 0 # Left edge - cropimage[:, -d:] = 0 # Right edge - - coords = pick_filaments(cropimage, round(args.radius/pixel_size), args.threshold, round(args.length/pixel_size), args.plot) - coords = np.round(coords*args.scale).astype(int) - # now write out as starfile... - - if len(coords) > 0: - xy = {'rlnCoordinateX': coords[:, 0], 'rlnCoordinateY': coords[:, 1]} - df = pd.DataFrame(xy) + # 6) Sample coordinates every 'sample_step' from pruned skeleton, **including filament ID**. + sampled_points = sample_coordinates_with_ids(pruned_skeleton, step=int(args.sample_step / pixel_size)) + # sampled_points shape: (N, 3) => [x, y, filament_id] + if sampled_points.size > 0: + # 7) Scale, round, write star + # scale only the x,y columns + sampled_points[:, 0:2] = np.round(sampled_points[:, 0:2] * args.scale).astype(int) + + df = pd.DataFrame({ + 'rlnCoordinateX': sampled_points[:, 0], + 'rlnCoordinateY': sampled_points[:, 1], + 'rlnParticleSelectionType': sampled_points[:, 2] + }) starfile.write(df, args.output, overwrite=True) - else: - Path(args.output).touch() + #print(f"Done. Wrote {len(df)} points to {args.output}") + + # --------------------------------------------------- + # If plotting is requested, create a collage figure + # --------------------------------------------------- + if args.plot: + colored_pruned = color_nonrandom(pruned_skeleton) + colored_foreground = color_foreground(foreground, args.threshold) + + fig, ax = plt.subplots(2, 3, figsize=(32,20)) + ax = ax.ravel() + + ax[0].imshow(fom_image, cmap='gray') + ax[0].set_title("FOM Image") + ax[0].axis('off') + + ax[1].imshow(psi_image, cmap='gray') + ax[1].set_title("PSI Image") + ax[1].axis('off') + + ax[2].imshow(colored_foreground) + ax[2].set_title("Foreground") + ax[2].axis('off') + + ax[3].imshow(skeleton_img) + ax[3].set_title("Raw Skeleton") + ax[3].axis('off') + + ax[4].imshow(colored_pruned) + ax[4].set_title("Pruned Skeleton") + ax[4].axis('off') + + ax[5].imshow(np.zeros_like(foreground), cmap='gray') # blank background + ax[5].scatter(sampled_points[:,0]/args.scale, sampled_points[:,1]/args.scale, s=2, c='red') + ax[5].set_xlim([0, foreground.shape[1]]) + ax[5].set_ylim([foreground.shape[0], 0]) + ax[5].set_title("Final Sampled Picks (ID shown in STAR)") + ax[5].axis('off') + + plt.tight_layout() + plt.show() + plt.close() + + +if __name__ == "__main__": + main() diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 9f233eda8..4b0006b64 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -45,6 +45,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) do_plot = parser.checkOption("--plot", "Display images with intermediate tracing results for each micrograph"); do_redo_tracing = parser.checkOption("--redo_all_tracing", "Ignore any autopick.star files already present and redo all tracing."); fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_trace_amyloids"); + fn_other_args = parser.getOption("--other_args", "Other arguments for the python script", ""); fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing"); do_gpu = parser.checkOption("--gpu", "Use GPU acceleration when availiable"); gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); @@ -611,14 +612,16 @@ void AmyloidFinder::traceFilaments(FileName &fn_fom, FileName &fn_psi, FileName command += " -d cuda:" + integerToString(device_id); else command += " -d cpu"; - command += " --o " + fn_star; - command += " --t " + floatToString(threshold); - command += " --r " + floatToString(trace_filament_width/2); - command += " --l " + floatToString(trace_filament_length); - command += " --s " + floatToString(down_angpix/angpix); + command += " -o " + fn_star; + command += " -t " + floatToString(threshold); + command += " -r " + floatToString(trace_filament_width/2); + command += " -l " + floatToString(trace_filament_length); + command += " -s " + floatToString(down_angpix/angpix); if (do_plot) command += " --plot "; + command += " " + fn_other_args; + std::cerr << command << std::endl; int res = system(command.c_str()); @@ -768,7 +771,8 @@ void AmyloidFinder::finalise() MDcoords.addObject(); MDcoords.setValue(EMDL_MICROGRAPH_NAME, fn_ori_micrographs[imic]); - MDcoords.setValue(EMDL_MICROGRAPH_COORDINATES, fn_pick); + MDcoords.setValue(EMDL_MICROGRAPH_COORDINATES, fn_pick); + MDcoords.setValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_fom); nr_coord_files++; MD.read(fn_pick); diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index 2bc1e4e5d..e8024c71b 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -46,7 +46,7 @@ class AmyloidFinder int verb; // Output rootname - FileName fn_in, fn_odir, fn_out; + FileName fn_in, fn_odir, fn_out, fn_other_args; // Psi sampling step RFLOAT psi_step; diff --git a/src/gui_mainwindow.cpp b/src/gui_mainwindow.cpp index 28d9326c2..155b8db69 100644 --- a/src/gui_mainwindow.cpp +++ b/src/gui_mainwindow.cpp @@ -1432,7 +1432,11 @@ void GuiMainWindow::cb_display_io_node_i() command += " --black " + manualpickjob.joboptions["black_val"].getString(); command += " --white " + manualpickjob.joboptions["white_val"].getString(); - if (manualpickjob.joboptions["do_startend"].getBoolean()) + if (manualpickjob.joboptions["do_lines"].getBoolean()) + { + command += " --pick_lines "; + } + else if (manualpickjob.joboptions["do_startend"].getBoolean()) { command += " --pick_start_end "; } @@ -1466,7 +1470,6 @@ void GuiMainWindow::cb_display_io_node_i() if (manualpickjob.joboptions["fn_color"].getString().length() > 0) command += " --color_star " + manualpickjob.joboptions["fn_color"].getString(); } - } else { diff --git a/src/manualpicker.cpp b/src/manualpicker.cpp index b5ca2fcc4..a666cc497 100644 --- a/src/manualpicker.cpp +++ b/src/manualpicker.cpp @@ -711,7 +711,6 @@ void ManualPicker::read(int argc, char **argv) global_particle_diameter = textToFloat(parser.getOption("--particle_diameter", "Diameter of the circles that will be drawn around each picked particle (in Angstroms)")); global_pick_startend = parser.checkOption("--pick_start_end", "Pick start-end coordinates of helices"); global_pick_lines = parser.checkOption("--pick_lines", "Pick lines for curvy helices"); - do_allow_save = parser.checkOption("--allow_save", "Allow saving of the selected micrographs"); do_fast_save = parser.checkOption("--fast_save", "Save a default selection of all micrographs immediately"); global_nr_simultaneous = textToInteger(parser.getOption("--open_simultaneous", "Open this many of the next micrographs simultaneously when pressing CTRL and a Pick button", "10")); From dd3088146b3456e3faa51d47ee7bcd35d3824b38 Mon Sep 17 00:00:00 2001 From: scheres Date: Mon, 24 Mar 2025 16:51:46 +0000 Subject: [PATCH 082/189] further optimisation of Jennys code and interaction with mine --- scripts/trace_amyloids.in | 148 ++++++++++++++++---------------------- src/amyloid_finder.cpp | 10 --- 2 files changed, 63 insertions(+), 95 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index b3f58baec..6fa1faeb8 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -19,6 +19,7 @@ from skimage.morphology import remove_small_objects, skeletonize from skimage.measure import regionprops, label as label_connected_component from skan import Skeleton, summarize +import time ############################################################################### # U-Net Model @@ -175,51 +176,6 @@ def load_mrc(mrc_fn: str, multiply_global_origin: bool = True): return grid, voxel_size, global_origin -class InstanceConfig: - thres_foreground = 0.5 - obj_min_size = 50 - - -def instance_from_layered_embedding(raw, config): - """ - Build an instance segmentation from a 'layered_embedding' + 'foreground'. - Return: - - instances: list of binary masks - - layered_objects: multi-channel binary assignment - """ - from skimage.measure import label as label_cc - - embedding = np.copy(np.squeeze(raw['layered_embedding'])) - embedding = np.transpose(embedding, (1, 2, 0)) # shape: (H, W, embedding_dim) - fg = np.copy(np.squeeze(raw['foreground'] > config.thres_foreground)) - - # Remove small objects - for r in regionprops(label_cc(fg)): - if r.area < config.obj_min_size: - fg[r.coords[:, 0], r.coords[:, 1]] = 0 - - # layering foreground - layered_objects = np.zeros(embedding.shape, bool) - rr, cc = np.nonzero(fg) - # threshold embedding => - layered_objects[rr, cc, :] = embedding[rr, cc, :] > config.thres_foreground - # also pick the channel with max embedding => set True - idx = np.argmax(embedding[rr, cc, :], axis=-1) - layered_objects[rr, cc, idx] = 1 - - instances = [] - for i_ch in range(layered_objects.shape[-1]): - L = label_cc(layered_objects[:, :, i_ch]) - for r in regionprops(L): - if r.area < config.obj_min_size: - layered_objects[r.coords[:, 0], r.coords[:, 1], i_ch] = 0 - else: - instances.append(L == r.label) - - return instances, layered_objects - - - ############################################################################### # Sample coordinates from the final pruned label, including IDs ############################################################################### @@ -309,13 +265,17 @@ def color_foreground(grey_image, threshold): # Sjors go at pruning the raw skeleton image ############################################################################### -def prune_skeleton(skeleton, min_length=10): +def prune_skeleton(skeleton, min_length=100, step=50): """Prune skeleton branches that too short.""" + #start_time= time.time() skel = Skeleton(skeleton) + #print(f"Time elapsed after step A: {time.time() - start_time:.2f} seconds") df = summarize(skel, separator='-') + #print(f"Time elapsed after step B: {time.time() - start_time:.2f} seconds") labeled_skel = np.zeros_like(skeleton, dtype=np.int32) - path_id = 1 + filament_counter = 1 + sampled_coords = [] for idx, row in df.iterrows(): branch_coords = skel.path_coordinates(idx) # Get coordinates of the branch @@ -324,18 +284,28 @@ def prune_skeleton(skeleton, min_length=10): if length >= min_length: for x, y in branch_coords: - labeled_skel[int(x), int(y)] = path_id # Assign path label - path_id += 1 # Increment label for next path + labeled_skel[int(x), int(y)] = filament_counter # Assign path label - return labeled_skel + for i in range(0, len(branch_coords), step): + r, c = branch_coords[i] + sampled_coords.append([c, r, filament_counter]) + # ensure final coordinate is included + if (len(branch_coords) - 1) % step != 0: + r, c = branch_coords[-1] + sampled_coords.append([c, r, filament_counter]) + filament_counter += 1 # Increment label for next path + #print(f"Time elapsed after step C: {time.time() - start_time:.2f} seconds") + return labeled_skel, np.array(sampled_coords, dtype=int) ############################################################################### # MAIN ############################################################################### def main(): + + #start_time = time.time() parser = argparse.ArgumentParser("Skeleton-based filament picking with instance merging, branch pruning, and ID labeling") parser.add_argument("-if", "--input_fom", help="Input FOM MRC file") parser.add_argument("-ip", "--input_psi", help="Input PSI MRC file") @@ -360,10 +330,12 @@ def main(): psi_image, _, _ = load_mrc(args.input_psi) H, W = fom_image.shape device = torch.device(args.device) + #print(f"Time elapsed after step 2: {time.time() - start_time:.2f} seconds") # ------------------ Load Model + Run Inference ---------------- model = UNet.load_from_checkpoint(args.model_path, n_channels=2, n_classes=1) model.eval().to(device) + #print(f"Time elapsed after step 3: {time.time() - start_time:.2f} seconds") in_tensor = np.stack((fom_image, psi_image), axis=0) # shape (2,H,W) in_tensor = torch.from_numpy(in_tensor[None]).float().to(device) # shape (1,2,H,W) @@ -372,6 +344,7 @@ def main(): seg_logits, embedding = model(in_tensor) seg_logits, embedding = seg_logits.float(), embedding.float() foreground = torch.sigmoid(seg_logits).squeeze().cpu().numpy() # => (H,W) + #print(f"Time elapsed after step 5: {time.time() - start_time:.2f} seconds") # 2) Clean + threshold => remove small objects bin_foreground = (foreground > args.threshold) @@ -388,36 +361,37 @@ def main(): # 4) Skeletonize skeleton_img = skeletonize(clean_foreground) - if not skeleton_img.any(): - print("No skeleton found. Creating empty star file.") - Path(args.output).touch() - return - - # 5) Prune skeleton based on Skan's branches: remove any branch with length below args.final_min_size - pruned_skeleton = prune_skeleton(skeleton_img, min_length=args.minimum_length / pixel_size) - - # 6) Sample coordinates every 'sample_step' from pruned skeleton, **including filament ID**. - sampled_points = sample_coordinates_with_ids(pruned_skeleton, step=int(args.sample_step / pixel_size)) - # sampled_points shape: (N, 3) => [x, y, filament_id] - if sampled_points.size > 0: - # 7) Scale, round, write star - # scale only the x,y columns - sampled_points[:, 0:2] = np.round(sampled_points[:, 0:2] * args.scale).astype(int) - - df = pd.DataFrame({ - 'rlnCoordinateX': sampled_points[:, 0], - 'rlnCoordinateY': sampled_points[:, 1], - 'rlnParticleSelectionType': sampled_points[:, 2] - }) - starfile.write(df, args.output, overwrite=True) - #print(f"Done. Wrote {len(df)} points to {args.output}") + #print(f"Time elapsed after step 8: {time.time() - start_time:.2f} seconds") + if skeleton_img.any(): + + # 5) Prune skeleton based on Skan's branches: remove any branch with length below args.final_min_size + pruned_skeleton, sampled_points = prune_skeleton(skeleton_img, + min_length=args.minimum_length / pixel_size, + step=int(args.sample_step / pixel_size)) + #print(f"Time elapsed after step 9: {time.time() - start_time:.2f} seconds") + + # 6) Sample coordinates every 'sample_step' from pruned skeleton, **including filament ID**. + #sampled_points = sample_coordinates_with_ids(pruned_skeleton, step=int(args.sample_step / pixel_size)) + # sampled_points shape: (N, 3) => [x, y, filament_id] + if sampled_points.size > 0: + + # 7) Scale, round, write star + # scale only the x,y columns + sampled_points[:, 0:2] = np.round(sampled_points[:, 0:2] * args.scale).astype(int) + + df = pd.DataFrame({ + 'rlnCoordinateX': sampled_points[:, 0], + 'rlnCoordinateY': sampled_points[:, 1], + 'rlnParticleSelectionType': sampled_points[:, 2] + }) + starfile.write(df, args.output, overwrite=True) + #print(f"Done. Wrote {len(df)} points to {args.output}") + #print(f"Time elapsed after step 10: {time.time() - start_time:.2f} seconds") # --------------------------------------------------- # If plotting is requested, create a collage figure # --------------------------------------------------- if args.plot: - colored_pruned = color_nonrandom(pruned_skeleton) - colored_foreground = color_foreground(foreground, args.threshold) fig, ax = plt.subplots(2, 3, figsize=(32,20)) ax = ax.ravel() @@ -430,7 +404,7 @@ def main(): ax[1].set_title("PSI Image") ax[1].axis('off') - ax[2].imshow(colored_foreground) + ax[2].imshow(color_foreground(foreground, args.threshold)) ax[2].set_title("Foreground") ax[2].axis('off') @@ -438,20 +412,24 @@ def main(): ax[3].set_title("Raw Skeleton") ax[3].axis('off') - ax[4].imshow(colored_pruned) - ax[4].set_title("Pruned Skeleton") - ax[4].axis('off') + if skeleton_img.any(): + ax[4].imshow(color_nonrandom(pruned_skeleton)) + ax[4].set_title("Pruned Skeleton") + ax[4].axis('off') - ax[5].imshow(np.zeros_like(foreground), cmap='gray') # blank background - ax[5].scatter(sampled_points[:,0]/args.scale, sampled_points[:,1]/args.scale, s=2, c='red') - ax[5].set_xlim([0, foreground.shape[1]]) - ax[5].set_ylim([foreground.shape[0], 0]) - ax[5].set_title("Final Sampled Picks (ID shown in STAR)") - ax[5].axis('off') + if sampled_points.size > 0: + ax[5].imshow(np.zeros_like(foreground), cmap='gray') # blank background + ax[5].scatter(sampled_points[:,0]/args.scale, sampled_points[:,1]/args.scale, s=2, c='red') + ax[5].set_xlim([0, foreground.shape[1]]) + ax[5].set_ylim([foreground.shape[0], 0]) + ax[5].set_title("Final Sampled Picks (ID shown in STAR)") + ax[5].axis('off') plt.tight_layout() plt.show() plt.close() + #print(f"Time elapsed after step 11: {time.time() - start_time:.2f} seconds") + if __name__ == "__main__": diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 4b0006b64..8d28d3961 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -666,16 +666,6 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) Ipsi.write(fn_psi); Izscore.write(fn_fom); } - else - { - Image Ipsi, Izscore; - Ipsi.read(fn_psi); - Izscore.read(fn_fom); - Mangle=Ipsi(); - Mscore=Izscore(); - Mangle.setXmippOrigin(); - Mscore.setXmippOrigin(); - } if (myverb) { From eea0505ae190be74fd0c1d2c7a4409d86922d0ac Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 25 Mar 2025 10:52:24 +0000 Subject: [PATCH 083/189] remove too-close start-end points of filaments --- scripts/trace_amyloids.in | 102 +++++++++++++++++++++++++++++--------- src/amyloid_finder.cpp | 2 +- 2 files changed, 79 insertions(+), 25 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 6fa1faeb8..0abd27f7f 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -18,6 +18,7 @@ import matplotlib.pyplot as plt from skimage.morphology import remove_small_objects, skeletonize from skimage.measure import regionprops, label as label_connected_component from skan import Skeleton, summarize +from scipy.spatial.distance import cdist import time @@ -254,8 +255,8 @@ def color_nonrandom(label_image): return colorized def color_foreground(grey_image, threshold): - colorized = np.stack([grey_image] * 3, axis=-1) - + grey_image_uint8 = (grey_image * 255).astype(np.uint8) + colorized = np.stack([grey_image_uint8] * 3, axis=-1) mask = (grey_image > threshold) colorized[mask] = (255, 0, 0) return colorized @@ -265,39 +266,91 @@ def color_foreground(grey_image, threshold): # Sjors go at pruning the raw skeleton image ############################################################################### -def prune_skeleton(skeleton, min_length=100, step=50): +def trim_end(branch_coords, trim_length=10, trim_start=False): + """Trims the start or end of a branch by `trim_length` pixels.""" + if len(branch_coords) > trim_length: + return branch_coords[trim_length:] if trim_start else branch_coords[:-trim_length] + return np.array([]) # Remove short branches completely + +def prune_skeleton(skeleton, min_length=100, min_distance=25, step=50): """Prune skeleton branches that too short.""" - #start_time= time.time() + # Most of the compute goes into the Skeleton call; rest of function is cheap skel = Skeleton(skeleton) - #print(f"Time elapsed after step A: {time.time() - start_time:.2f} seconds") df = summarize(skel, separator='-') - #print(f"Time elapsed after step B: {time.time() - start_time:.2f} seconds") - labeled_skel = np.zeros_like(skeleton, dtype=np.int32) - filament_counter = 1 - sampled_coords = [] + trim_length = int(min_distance / 2) + labeled_skeleton = np.zeros_like(skeleton, dtype=np.int32) + accepted_branches = [] # Stores (branch coordinates, start, end) + + # Pass 1: Collect branches and compute pairwise distances + branch_data = [] # Store branch info for pass 2 + endpoints = [] # Store (start, end) tuples for idx, row in df.iterrows(): - branch_coords = skel.path_coordinates(idx) # Get coordinates of the branch - + branch_coords = skel.path_coordinates(idx) + # Compute Euclidean length of the branch length = np.sum(np.sqrt(np.sum(np.diff(branch_coords, axis=0) ** 2, axis=1))) + if length < min_length: + continue # Skip short branches - if length >= min_length: - for x, y in branch_coords: - labeled_skel[int(x), int(y)] = filament_counter # Assign path label + start, end = tuple(branch_coords[0]), tuple(branch_coords[-1]) + branch_data.append((branch_coords, start, end)) + endpoints.append(start) + endpoints.append(end) - for i in range(0, len(branch_coords), step): - r, c = branch_coords[i] - sampled_coords.append([c, r, filament_counter]) - # ensure final coordinate is included - if (len(branch_coords) - 1) % step != 0: - r, c = branch_coords[-1] - sampled_coords.append([c, r, filament_counter]) + if not branch_data: + return labeled_skeleton # No valid branches + + # Compute all pairwise distances between endpoints + endpoints = np.array(endpoints) # Convert to numpy array + dists = cdist(endpoints, endpoints) - filament_counter += 1 # Increment label for next path - #print(f"Time elapsed after step C: {time.time() - start_time:.2f} seconds") + # Mark close endpoint pairs for trimming + #close_pairs = np.where((dists < min_distance) & (dists > 0)) # Exclude self-distances + close_pairs = np.where((dists < min_distance) ) # Exclude self-distances + trim_flags = {} # Dictionary to store which endpoints need trimming + + for i in range(len(close_pairs[0])): + idx1, idx2 = close_pairs[0][i], close_pairs[1][i] + branch_idx1, endpoint_type1 = divmod(idx1, 2) # Get branch index & endpoint type + branch_idx2, endpoint_type2 = divmod(idx2, 2) + + if branch_idx1 == branch_idx2: + continue # Ignore self-pairs + + # Mark both endpoints for trimming + trim_flags.setdefault((branch_idx1, endpoint_type1), True) + trim_flags.setdefault((branch_idx2, endpoint_type2), True) + + # **Pass 2: Trim branches based on identified close endpoints** + final_branches = [] + + for i, (branch_coords, start, end) in enumerate(branch_data): + # Trim start or end if flagged + if (i, 0) in trim_flags: + branch_coords = trim_end(branch_coords, trim_length, trim_start=True) + if (i, 1) in trim_flags: + branch_coords = trim_end(branch_coords, trim_length, trim_start=False) + + # Ensure branch still exists after trimming + if len(branch_coords) > 0: + final_branches.append(branch_coords) + + # Assign unique labels to final pruned branches + sampled_coords = [] + for path_id, branch in enumerate(final_branches, start=1): + for x, y in branch: + labeled_skeleton[int(x), int(y)] = path_id + + for i in range(0, len(branch), step): + r, c = branch[i] + sampled_coords.append([c, r, path_id]) + # ensure final coordinate is included + if (len(branch) - 1) % step != 0: + r, c = branch[-1] + sampled_coords.append([c, r, path_id]) - return labeled_skel, np.array(sampled_coords, dtype=int) + return labeled_skeleton, np.array(sampled_coords, dtype=int) ############################################################################### @@ -367,6 +420,7 @@ def main(): # 5) Prune skeleton based on Skan's branches: remove any branch with length below args.final_min_size pruned_skeleton, sampled_points = prune_skeleton(skeleton_img, min_length=args.minimum_length / pixel_size, + min_distance=2 * args.radius / pixel_size, step=int(args.sample_step / pixel_size)) #print(f"Time elapsed after step 9: {time.time() - start_time:.2f} seconds") diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 8d28d3961..0c1c28007 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -622,7 +622,7 @@ void AmyloidFinder::traceFilaments(FileName &fn_fom, FileName &fn_psi, FileName command += " " + fn_other_args; - std::cerr << command << std::endl; + //std::cerr << command << std::endl; int res = system(command.c_str()); From 76bbb445e75b314fb1bf70ec284c7a77ada2d2f2 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 25 Mar 2025 11:02:45 +0000 Subject: [PATCH 084/189] only run extra ice stats when asked for with --extra_ice_stats option --- src/ctffind_runner.cpp | 12 ++++++++---- src/ctffind_runner.h | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index 4e2e94289..7165ced85 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -66,6 +66,7 @@ void CtffindRunner::read(int argc, char **argv, int rank) int ctffind4_section = parser.addSection("CTFFIND4 parameters"); is_ctffind4 = parser.checkOption("--is_ctffind4", "The provided CTFFIND executable is CTFFIND4 (version 4.1+)"); use_given_ps = parser.checkOption("--use_given_ps", "Use pre-calculated power spectra?"); + do_extra_ice = parser.checkOption("--extra_ice_stats", "When using pre-calculated power spectra, one can calculate extra ice statistics, which may be useful for selection of icy data sets"); do_movie_thon_rings = parser.checkOption("--do_movie_thon_rings", "Calculate Thon rings from movie frames?"); avg_movie_frames = textToInteger(parser.getOption("--avg_movie_frames", "Average over how many movie frames (try to get 4 e-/A2)", "1")); movie_rootname = parser.getOption("--movie_rootname", "Rootname plus extension for movies", "_movie.mrcs"); @@ -452,8 +453,11 @@ void CtffindRunner::joinCtffindResults() MDctf.setValue(EMDL_CTF_VALIDATIONSCORE, valscore); MDctf.setValue(EMDL_CTF_ICERINGDENSITY, icering); - if (use_given_ps) MDctf.setValue(EMDL_CTF_ICERINGPOWERFRACTION, icepowerfrac); - if (use_given_ps) MDctf.setValue(EMDL_CTF_ICERINGKURTOSIS, icekurtosis); + if (use_given_ps && do_extra_ice) + { + MDctf.setValue(EMDL_CTF_ICERINGPOWERFRACTION, icepowerfrac); + MDctf.setValue(EMDL_CTF_ICERINGKURTOSIS, icekurtosis); + } if (is_tomo) { @@ -1011,13 +1015,13 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT // Be careful, because avgrot files from CTFFIND-4.1 are somehow normalized badly, so re-read power spectrum image // This only works if given the power spectrum from RELION's motioncorr. Otherwise, fall back on Rafa's approach below - FileName fn_ps = fn_root + ".mrc"; - if (use_given_ps) + if (use_given_ps && do_extra_ice) { // Use standard deviation in the diagnostics image for ice ring density instead of Rafa's. // Calculate stddev of power, relative to stddev of power in a band with lower resolution than ice ring // Richard suggested using a higher band as well, but some strange ice artefacts extend well into the higher band... Image Ips; + FileName fn_ps = fn_root + ".mrc"; Ips.read(fn_ps); Ips().setXmippOrigin(); RFLOAT mysize = angpix*XSIZE(Ips()); diff --git a/src/ctffind_runner.h b/src/ctffind_runner.h index f2b31114d..7c2e18448 100644 --- a/src/ctffind_runner.h +++ b/src/ctffind_runner.h @@ -88,6 +88,9 @@ class CtffindRunner // Use pre-calculated power spectra bool use_given_ps; + // Calculate extra ice statistics when using pre-calculated power spectra + bool do_extra_ice; + // Calculate Thon rings from movies? bool do_movie_thon_rings; From 73e2718179a7a4bd755708776b125592ec3467c8 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 25 Mar 2025 11:04:02 +0000 Subject: [PATCH 085/189] only run extra ice stats when asked for with --extra_ice_stats option, insert error --- src/ctffind_runner.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index 7165ced85..2f4046f9a 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -114,6 +114,9 @@ void CtffindRunner::initialise(bool is_leader) if (use_given_ps) do_use_without_doseweighting = false; + if (do_extra_ce && !use_given_ps) + REPORT_ERROR("ERROR: You cannot use --extra_ice_stats without also using --use_given_ps") + // Make sure fn_out ends with a slash if (fn_out[fn_out.length()-1] != '/') fn_out += "/"; From e7430d4575371f958b300cf3d850f43d0e02d33b Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 25 Mar 2025 11:05:21 +0000 Subject: [PATCH 086/189] only run extra ice stats when asked for with --extra_ice_stats option, insert error2 --- src/ctffind_runner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index 2f4046f9a..1d123d94e 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -114,7 +114,7 @@ void CtffindRunner::initialise(bool is_leader) if (use_given_ps) do_use_without_doseweighting = false; - if (do_extra_ce && !use_given_ps) + if (do_extra_ice && !use_given_ps) REPORT_ERROR("ERROR: You cannot use --extra_ice_stats without also using --use_given_ps") // Make sure fn_out ends with a slash From 65db694b0bc6fab062b6ba3427ab5c6205fdd779 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 25 Mar 2025 14:04:51 +0000 Subject: [PATCH 087/189] only run extra ice stats when asked for with --extra_ice_stats option, insert error3 --- src/ctffind_runner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index 1d123d94e..ea2524b97 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -115,7 +115,7 @@ void CtffindRunner::initialise(bool is_leader) do_use_without_doseweighting = false; if (do_extra_ice && !use_given_ps) - REPORT_ERROR("ERROR: You cannot use --extra_ice_stats without also using --use_given_ps") + REPORT_ERROR("ERROR: You cannot use --extra_ice_stats without also using --use_given_ps"); // Make sure fn_out ends with a slash if (fn_out[fn_out.length()-1] != '/') From ce7fc0f9a6d22ff6eda2877c9434ab319e40dd95 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 25 Mar 2025 14:23:01 +0000 Subject: [PATCH 088/189] no deviceInitilaise if we dont have CUDA --- src/amyloid_finder_mpi.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/amyloid_finder_mpi.cpp b/src/amyloid_finder_mpi.cpp index 5d5cb00be..c104c40d9 100644 --- a/src/amyloid_finder_mpi.cpp +++ b/src/amyloid_finder_mpi.cpp @@ -43,6 +43,7 @@ void AmyloidFinderMpi::read(int argc, char **argv) printMpiNodesMachineNames(*node); } +#if defined _CUDA_ENABLED void AmyloidFinderMpi::deviceInitialise() { int devCount; @@ -67,6 +68,7 @@ void AmyloidFinderMpi::deviceInitialise() node->barrierWait(); } } +#endif void AmyloidFinderMpi::run() { From 5c2fe9754fc958245110699dc745b63d055ec993 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 25 Mar 2025 14:25:29 +0000 Subject: [PATCH 089/189] no deviceInitilaise if we dont have CUDA --- src/amyloid_finder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 0c1c28007..6eecf89db 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -177,6 +177,7 @@ void AmyloidFinder::initialise(bool is_leader) } +#if defined _CUDA_ENABLED void AmyloidFinder::deviceInitialise() { int devCount; @@ -196,7 +197,7 @@ void AmyloidFinder::deviceInitialise() std::cout << " + Using GPU device " << device_id << std::endl; } } - +#endif FileName AmyloidFinder::getOutputRootName(FileName fn_mic) { From 2e622db34312dafd758de8a68ceb12eeab9c9532 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 25 Mar 2025 16:08:03 +0000 Subject: [PATCH 090/189] skip tracing is no GPUs --- src/amyloid_finder.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 6eecf89db..f8466407d 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -121,6 +121,9 @@ void AmyloidFinder::initialise(bool is_leader) if (verb > 0) std::cout << " + Finding amyloids in the " << fn_micrographs.size() << " micrographs... " << std::endl; + + if (verb > 0 && !do_gpu) std::cout << " + Skipping filament tracing, as no GPU usage defined... Continue an old run with GPU usage to trace filaments later! " << std::endl; + // Read in header of first image Image Iin; Iin.read(fn_micrographs[0], false); @@ -668,14 +671,17 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) Izscore.write(fn_fom); } - if (myverb) + if (do_gpu) { - std::cout << " - Tracing filaments ..." << std::endl; - init_progress_bar(1); - } + if (myverb) + { + std::cout << " - Tracing filaments ..." << std::endl; + init_progress_bar(1); + } - FileName fn_star = getOutputRootName(fn_mic) + "_" + fn_out + ".star"; - traceFilaments(fn_fom, fn_psi, fn_star); + FileName fn_star = getOutputRootName(fn_mic) + "_" + fn_out + ".star"; + traceFilaments(fn_fom, fn_psi, fn_star); + } if (myverb) std::cout << "done!" << std::endl; From a1db1b0a4b4f0ccdc9b1c7d96ffcac98e8b90443 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 25 Mar 2025 16:10:01 +0000 Subject: [PATCH 091/189] still more deviceInitialise in find_amyloid --- src/apps/find_amyloid.cpp | 5 ++++- src/apps/find_amyloid_mpi.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/apps/find_amyloid.cpp b/src/apps/find_amyloid.cpp index 89b76f65a..72f283e00 100644 --- a/src/apps/find_amyloid.cpp +++ b/src/apps/find_amyloid.cpp @@ -28,7 +28,10 @@ int main(int argc, char *argv[]) prm.read(argc, argv); prm.initialise(); - if (prm.do_gpu) prm.deviceInitialise(); + +#if defined _CUDA_ENABLED + if (prm.do_gpu) prm.deviceInitialise(); +#endif prm.run(); diff --git a/src/apps/find_amyloid_mpi.cpp b/src/apps/find_amyloid_mpi.cpp index f487b7780..892c7146c 100644 --- a/src/apps/find_amyloid_mpi.cpp +++ b/src/apps/find_amyloid_mpi.cpp @@ -28,8 +28,11 @@ int main(int argc, char *argv[]) prm.read(argc, argv); prm.initialise(prm.getRank()); - if (prm.do_gpu) prm.deviceInitialise(); +#if defined _CUDA_ENABLED + if (prm.do_gpu) prm.deviceInitialise(); +#endif + prm.run(); MPI_Barrier(MPI_COMM_WORLD); From bbbf73057ce869e53d818768f6f589a6e9e3008b Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 27 Mar 2025 09:17:48 +0000 Subject: [PATCH 092/189] touch empty star files if no filaments are picked, so not always repicking empty micrographs when continuing an old run --- scripts/trace_amyloids.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 0abd27f7f..453571b3e 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -440,6 +440,11 @@ def main(): }) starfile.write(df, args.output, overwrite=True) #print(f"Done. Wrote {len(df)} points to {args.output}") + else: + Path(args.output).touch() + else: + Path(args.output).touch() + #print(f"Time elapsed after step 10: {time.time() - start_time:.2f} seconds") # --------------------------------------------------- From fffa828b504fc547a4911975b6d3eb41340e123a Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 1 Apr 2025 08:10:29 +0100 Subject: [PATCH 093/189] warn instead of die if filament with a single point; TODO: remove this again after fixing Jenny's script! --- src/preprocessing.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/preprocessing.cpp b/src/preprocessing.cpp index f0fb0db0d..507f31cc7 100644 --- a/src/preprocessing.cpp +++ b/src/preprocessing.cpp @@ -697,7 +697,11 @@ void Preprocessing::convertHelicalLineCoordsToMetaDataTable( MD_in.getValue(EMDL_PARTICLE_SELECTION_TYPE, my_type); if (my_type != my_prev_type) { - if (is_first) REPORT_ERROR("ERROR: encountered a filament with only a single point in " + fn_in); + if (is_first) + { + std::cerr << "WARNING: encountered a filament with only a single point in " << fn_in << std::endl; + continue; + } // start a new filament (initialise psi to zero!!) is_first = true; From 683226a5c4b2c8274879145ab638ade88b957ddf Mon Sep 17 00:00:00 2001 From: Dari Kimanius Date: Thu, 10 Apr 2025 19:32:11 -0700 Subject: [PATCH 094/189] Replaced CUB with that distributed by the CUDA toolkit. --- src/acc/cuda/cuda_utils_cub.cuh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/acc/cuda/cuda_utils_cub.cuh b/src/acc/cuda/cuda_utils_cub.cuh index e3ee9860f..426f8d5e7 100644 --- a/src/acc/cuda/cuda_utils_cub.cuh +++ b/src/acc/cuda/cuda_utils_cub.cuh @@ -18,10 +18,10 @@ #include #else // #define CUB_NS_QUALIFIER ::cub // for compatibility with CUDA 11.5 - #include "src/acc/cuda/cub/device/device_radix_sort.cuh" - #include "src/acc/cuda/cub/device/device_reduce.cuh" - #include "src/acc/cuda/cub/device/device_scan.cuh" - #include "src/acc/cuda/cub/device/device_select.cuh" + #include + #include + #include + #include #endif namespace CudaKernels From e99eccb2b3685363710b5236a000b7e3187a5552 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 11 Apr 2025 16:30:08 +0100 Subject: [PATCH 095/189] added weights in local symmetry averaging --- src/apps/particle_symmetry_expand.cpp | 3 +- src/local_symmetry.cpp | 48 +++++++++++++++++++++------ src/local_symmetry.h | 3 ++ src/local_symmetry_mpi.cpp | 8 +++-- src/metadata_label.h | 8 +++-- src/ml_optimiser.cpp | 4 +-- src/ml_optimiser.h | 1 + src/ml_optimiser_mpi.cpp | 4 +-- 8 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/apps/particle_symmetry_expand.cpp b/src/apps/particle_symmetry_expand.cpp index 30065b882..86d8305f4 100644 --- a/src/apps/particle_symmetry_expand.cpp +++ b/src/apps/particle_symmetry_expand.cpp @@ -146,8 +146,9 @@ class particle_symmetry_expand_parameters { std::vector fn_mask_list; std::vector > > op_list; + std::vector > weights; // set angpix to 1, so translations keep being in Angstroms - readRelionFormatMasksAndOperators(fn_localsym, fn_mask_list, op_list, 1., false); + readRelionFormatMasksAndOperators(fn_localsym, fn_mask_list, op_list, weights, 1., false); for (int imask = 0; imask < fn_mask_list.size(); imask++) { for (int iop = 0; iop < op_list[imask].size(); iop++) diff --git a/src/local_symmetry.cpp b/src/local_symmetry.cpp index b5b769bb1..021a8bd0d 100644 --- a/src/local_symmetry.cpp +++ b/src/local_symmetry.cpp @@ -355,12 +355,14 @@ void readRelionFormatMasksAndOperators( FileName fn_info, std::vector& fn_mask_list, std::vector > >& ops, + std::vector >& weights, RFLOAT angpix, bool verb) { MetaDataTable MD; FileName fn_mask; std::vector > dummy; + std::vector dummyw; Matrix1D op, op_i; bool is_maskname_found = false; RFLOAT aa = 0., bb = 0., gg = 0., dx = 0., dy = 0., dz = 0.; @@ -368,8 +370,10 @@ void readRelionFormatMasksAndOperators( // Initialisation fn_mask_list.clear(); ops.clear(); + weights.clear(); MD.clear(); dummy.clear(); + dummyw.clear(); op.clear(); op_i.clear(); @@ -417,7 +421,8 @@ void readRelionFormatMasksAndOperators( op_i.initZeros(NR_LOCALSYM_PARAMETERS); for (int id_mask = 0; id_mask < fn_mask_list.size(); id_mask++) { - dummy.clear(); + dummy.clear(); + dummyw.clear(); if (verb) { std::cout << " * Mask #" << (id_mask + 1) << " = " << fn_mask_list[id_mask] << std::endl; @@ -441,7 +446,11 @@ void readRelionFormatMasksAndOperators( MD.getValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, dy); MD.getValue(EMDL_ORIENT_ORIGIN_Z_ANGSTROM, dz); - // Re-calculate angles so that they follow the conventions in RELION! + // Get weight if present + RFLOAT weight = 1.; + if (MD.containsLabel(EMDL_LOCSYM_WEIGHT)) MD.getValue(EMDL_LOCSYM_WEIGHT, weight); + + // Re-calculate angles so that they follow the conventions in RELION! standardiseEulerAngles(aa, bb, gg, aa, bb, gg); Localsym_composeOperator(op, aa, bb, gg, dx, dy, dz); @@ -460,11 +469,13 @@ void readRelionFormatMasksAndOperators( // Push back the operator dummy.push_back(op); + dummyw.push_back(weight); } if (dummy.size() < 1) REPORT_ERROR("ERROR: Please provide at least one non-identical operator for mask file " + fn_mask_list[id_mask] + " !"); - ops.push_back(dummy); + ops.push_back(dummy); + weights.push_back(dummyw); } // Verify mask filenames and operators (detect duplication) @@ -1151,6 +1162,7 @@ void applyLocalSymmetry(MultidimArray& sym_map, const MultidimArray& ori_map, const std::vector fn_masks, const std::vector > > ops, + const std::vector > weights, RFLOAT radius, RFLOAT cosine_width_pix) { @@ -1186,6 +1198,7 @@ void applyLocalSymmetry(MultidimArray& sym_map, for (int imask = 0; imask < fn_masks.size(); imask++) { vol1 = ori_map; + RFLOAT sumweight = 1.; // Loop over all operators for this mask RFLOAT nr_ops = RFLOAT(ops[imask].size()); @@ -1207,7 +1220,11 @@ void applyLocalSymmetry(MultidimArray& sym_map, translate(ori_map, vol2, trans_vec, DONT_WRAP); selfApplyGeometry(vol2, op_mat, IS_NOT_INV, DONT_WRAP); #endif - vol1 += vol2; + if (weights[imask][iop] > 0.) + { + vol1 += weights[imask][iop] * vol2; + sumweight += weights[imask][iop]; + } } // Load this mask @@ -1231,7 +1248,8 @@ void applyLocalSymmetry(MultidimArray& sym_map, // This voxel is inside the mask if (mask_val > (XMIPP_EQUAL_ACCURACY)) { - DIRECT_A3D_ELEM(vol1, k, i, j) *= mask_val / (nr_ops + 1.); // "mask-weighted sum" - wsum + //DIRECT_A3D_ELEM(vol1, k, i, j) *= mask_val / (nr_ops + 1.); // "mask-weighted sum" - wsum + DIRECT_A3D_ELEM(vol1, k, i, j) *= mask_val / sumweight; // "mask-weighted sum" - wsum } else { @@ -1329,11 +1347,12 @@ void applyLocalSymmetry( MultidimArray& map, const std::vector fn_masks, const std::vector > > ops, + const std::vector > weights, RFLOAT radius, RFLOAT cosine_width_pix) { MultidimArray vol; - applyLocalSymmetry(vol, map, fn_masks, ops, radius, cosine_width_pix); + applyLocalSymmetry(vol, map, fn_masks, ops, weights, radius, cosine_width_pix); map = vol; } @@ -2212,6 +2231,7 @@ void local_symmetry_parameters::run() FileName fn_parsed, fn_tmp; std::vector fn_mask_list; std::vector > > op_list; + std::vector > weights; fn_mask_list.clear(); op_list.clear(); @@ -2251,13 +2271,16 @@ void local_symmetry_parameters::run() // Parse mask info file if (fn_info_in.getExtension() == "star") { - readRelionFormatMasksAndOperators(fn_info_in, fn_mask_list, op_list, angpix_image, do_verb); + readRelionFormatMasksAndOperators(fn_info_in, fn_mask_list, op_list, weights, angpix_image, do_verb); } else { fn_parsed = fn_info_in + std::string(".") + fn_info_in_parsed_ext; parseDMFormatMasksAndOperators(fn_info_in, fn_parsed); readDMFormatMasksAndOperators(fn_parsed, fn_mask_list, op_list, angpix_image, do_verb); + weights.resize(op_list.size()); + for (int i=0; i < op_list.size(); i++) + weights[i].resize(op_list[i].size(), 1.); } unsym_map.clear(); @@ -2267,7 +2290,7 @@ void local_symmetry_parameters::run() int box_size = ((XSIZE(unsym_map())) < (YSIZE(unsym_map()))) ? (XSIZE(unsym_map())) : (YSIZE(unsym_map())); box_size = (box_size < (ZSIZE(unsym_map()))) ? box_size : (ZSIZE(unsym_map())); - applyLocalSymmetry(sym_map(), unsym_map(), fn_mask_list, op_list, (RFLOAT(box_size) * sphere_percentage) / 2., width_edge_pix); + applyLocalSymmetry(sym_map(), unsym_map(), fn_mask_list, op_list, weights, (RFLOAT(box_size) * sphere_percentage) / 2., width_edge_pix); sym_map().setXmippOrigin(); sym_map.setSamplingRateInHeader(angpix_image, angpix_image, angpix_image); @@ -2296,14 +2319,17 @@ void local_symmetry_parameters::run() // Parse mask info file if (fn_info_in.getExtension() == "star") { - readRelionFormatMasksAndOperators(fn_info_in, fn_mask_list, op_list, angpix_image, do_verb); + readRelionFormatMasksAndOperators(fn_info_in, fn_mask_list, op_list, weights, angpix_image, do_verb); } else { fn_parsed = fn_info_in + std::string(".") + fn_info_in_parsed_ext; parseDMFormatMasksAndOperators(fn_info_in, fn_parsed); readDMFormatMasksAndOperators(fn_parsed, fn_mask_list, op_list, angpix_image, do_verb); - } + weights.resize(op_list.size()); + for (int i=0; i < op_list.size(); i++) + weights[i].resize(op_list[i].size(), 1.); + } map_in.clear(); //map_out.clear(); @@ -2408,7 +2434,7 @@ void local_symmetry_parameters::run() { if (fn_info_in.getExtension() == "star") { - readRelionFormatMasksAndOperators(fn_info_in, fn_mask_list, op_list, angpix_image, true); + readRelionFormatMasksAndOperators(fn_info_in, fn_mask_list, op_list, weights, angpix_image, true); } else { diff --git a/src/local_symmetry.h b/src/local_symmetry.h index 6c63a2bad..f08b4cbaa 100644 --- a/src/local_symmetry.h +++ b/src/local_symmetry.h @@ -136,6 +136,7 @@ void readRelionFormatMasksAndOperators( FileName fn_info, std::vector& fn_mask_list, std::vector > >& ops, + std::vector >& weights, RFLOAT angpix = 1., bool verb = false); @@ -183,6 +184,7 @@ void applyLocalSymmetry( const MultidimArray& ori_map, const std::vector fn_masks, const std::vector > > ops, + const std::vector > weights, RFLOAT radius = -1., RFLOAT cosine_width_pix = 5.); @@ -190,6 +192,7 @@ void applyLocalSymmetry( MultidimArray& map, const std::vector fn_masks, const std::vector > > ops, + const std::vector > weights, RFLOAT radius = -1., RFLOAT cosine_width_pix = 5.); diff --git a/src/local_symmetry_mpi.cpp b/src/local_symmetry_mpi.cpp index 2e7f7ba33..e8ae91abd 100644 --- a/src/local_symmetry_mpi.cpp +++ b/src/local_symmetry_mpi.cpp @@ -29,6 +29,7 @@ void local_symmetry_parameters_mpi::run() Matrix1D op_search_ranges, op, com0_int, com1_int, com1_float, com1_diff, vecR3; std::vector fn_mask_list; std::vector > > op_list; + std::vector > weights; std::vector > op_mask_list; std::vector > op_samplings, op_samplings_batch; MultidimArray op_samplings_batch_packed, src_cropped, dest_cropped, mask_cropped; @@ -39,6 +40,7 @@ void local_symmetry_parameters_mpi::run() op_search_ranges.clear(); op.clear(); com0_int.clear(); com1_int.clear(); com1_float.clear(); com1_diff.clear(); vecR3.clear(); fn_mask_list.clear(); op_list.clear(); + weights.clear(); op_mask_list.clear(); op_samplings.clear(); op_samplings_batch.clear(); op_samplings_batch_packed.clear(); src_cropped.clear(); dest_cropped.clear(); mask_cropped.clear(); @@ -107,14 +109,16 @@ void local_symmetry_parameters_mpi::run() { if (fn_info_in.getExtension() == "star") { - readRelionFormatMasksAndOperators(fn_info_in, fn_mask_list, op_list, angpix_image, true); + readRelionFormatMasksAndOperators(fn_info_in, fn_mask_list, op_list, weights, angpix_image, true); } else { fn_parsed = fn_info_in + std::string(".") + fn_info_in_parsed_ext; parseDMFormatMasksAndOperators(fn_info_in, fn_parsed); readDMFormatMasksAndOperators(fn_parsed, fn_mask_list, op_list, angpix_image, true); - } + for (int i=0; i < op_list.size(); i++) + weights[i].resize(op_list[i].size(), 1.); + } } else { diff --git a/src/metadata_label.h b/src/metadata_label.h index bf215596a..cc89bae0f 100644 --- a/src/metadata_label.h +++ b/src/metadata_label.h @@ -232,6 +232,8 @@ enum EMDLabel EMDL_JOBOPTION_DIRECTORY, EMDL_JOBOPTION_MENUOPTIONS, + EMDL_LOCSYM_WEIGHT, + EMDL_MATRIX_1_1, EMDL_MATRIX_1_2, EMDL_MATRIX_1_3, @@ -968,8 +970,10 @@ class StaticInitialization EMDL::addLabel(EMDL_JOBOPTION_DIRECTORY, EMDL_STRING, "rlnJobOptionDirectoryDefault", "Default directory for file browser of a joboption"); EMDL::addLabel(EMDL_JOBOPTION_MENUOPTIONS, EMDL_STRING, "rlnJobOptionMenuOptions", "Options for pull-down menu"); - EMDL::addLabel(EMDL_MATRIX_1_1, EMDL_DOUBLE, "rlnMatrix_1_1", "Matrix element (1,1) of a 3x3 matrix"); - EMDL::addLabel(EMDL_MATRIX_1_2, EMDL_DOUBLE, "rlnMatrix_1_2", "Matrix element (1,2) of a 3x3 matrix"); + EMDL::addLabel(EMDL_LOCSYM_WEIGHT, EMDL_DOUBLE, "rlnLocalSymmetryWeight", "Relative weight for local symmetry operator (main one is 1)"); + + EMDL::addLabel(EMDL_MATRIX_1_1, EMDL_DOUBLE, "rlnMatrix_1_1", "Matrix element (1,1) of a 3x3 matrix"); + EMDL::addLabel(EMDL_MATRIX_1_2, EMDL_DOUBLE, "rlnMatrix_1_2", "Matrix element (1,2) of a 3x3 matrix"); EMDL::addLabel(EMDL_MATRIX_1_3, EMDL_DOUBLE, "rlnMatrix_1_3", "Matrix element (1,3) of a 3x3 matrix"); EMDL::addLabel(EMDL_MATRIX_2_1, EMDL_DOUBLE, "rlnMatrix_2_1", "Matrix element (2,1) of a 3x3 matrix"); EMDL::addLabel(EMDL_MATRIX_2_2, EMDL_DOUBLE, "rlnMatrix_2_2", "Matrix element (2,1) of a 3x3 matrix"); diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 6c1a6249d..1a1043732 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -2130,7 +2130,7 @@ void MlOptimiser::initialiseGeneral(int rank) fn_local_symmetry_masks.clear(); fn_local_symmetry_operators.clear(); if (fn_local_symmetry != "None") - readRelionFormatMasksAndOperators(fn_local_symmetry, fn_local_symmetry_masks, fn_local_symmetry_operators, mymodel.pixel_size, false); + readRelionFormatMasksAndOperators(fn_local_symmetry, fn_local_symmetry_masks, fn_local_symmetry_operators, local_symmetry_weights, mymodel.pixel_size, false); // For multi-body refinement: Read in the masks in the input STAR file, add a soft-edge to them, and write them to disc with the standard name if (do_initialise_bodies) @@ -4824,7 +4824,7 @@ void MlOptimiser::applyLocalSymmetryForEachRef() { // either ibody or iclass can be larger than 0, never 2 at the same time! int ith_recons = (mymodel.nr_bodies > 1) ? ibody : iclass; - applyLocalSymmetry(mymodel.Iref[ith_recons], fn_local_symmetry_masks, fn_local_symmetry_operators); + applyLocalSymmetry(mymodel.Iref[ith_recons], fn_local_symmetry_masks, fn_local_symmetry_operators, local_symmetry_weights); } } } diff --git a/src/ml_optimiser.h b/src/ml_optimiser.h index e75e880e2..118c86cfc 100644 --- a/src/ml_optimiser.h +++ b/src/ml_optimiser.h @@ -734,6 +734,7 @@ class MlOptimiser // Local symmetry - list of operators std::vector > > fn_local_symmetry_operators; + std::vector > local_symmetry_weights; //Maximum number of particles permitted to be drop, due to zero sum of weights, before exiting with an error (GPU only). int failsafe_threshold; diff --git a/src/ml_optimiser_mpi.cpp b/src/ml_optimiser_mpi.cpp index 656ddb786..266cdd503 100644 --- a/src/ml_optimiser_mpi.cpp +++ b/src/ml_optimiser_mpi.cpp @@ -2506,7 +2506,7 @@ void MlOptimiserMpi::maximization() // Apply local symmetry according to a list of masks and their operators if ( (fn_local_symmetry_masks.size() != 0) && (fn_local_symmetry_operators.size() != 0) && (!has_converged) ) - applyLocalSymmetry(mymodel.Iref[ith_recons], fn_local_symmetry_masks, fn_local_symmetry_operators); + applyLocalSymmetry(mymodel.Iref[ith_recons], fn_local_symmetry_masks, fn_local_symmetry_operators, local_symmetry_weights); // Shaoda Jul26,2015 - Helical symmetry local refinement if ( (iter > 1) && (do_helical_refine) && (!ignore_helical_symmetry) && (do_helical_symmetry_local_refinement) && mymodel.ref_dim != 2) @@ -2640,7 +2640,7 @@ void MlOptimiserMpi::maximization() // Apply local symmetry according to a list of masks and their operators if ( (fn_local_symmetry_masks.size() != 0) && (fn_local_symmetry_operators.size() != 0) && (!has_converged) ) - applyLocalSymmetry(mymodel.Iref[ith_recons], fn_local_symmetry_masks, fn_local_symmetry_operators); + applyLocalSymmetry(mymodel.Iref[ith_recons], fn_local_symmetry_masks, fn_local_symmetry_operators, local_symmetry_weights); // Shaoda Jul26,2015 - Helical symmetry local refinement if ( (iter > 1) && (do_helical_refine) && (!ignore_helical_symmetry) && (do_helical_symmetry_local_refinement) && mymodel.ref_dim != 2 ) From d791ff2606abea087b90e262ce676823b89ccd32 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 29 Apr 2025 11:21:26 +0100 Subject: [PATCH 096/189] repiared bad return call for empty micrographs --- scripts/trace_amyloids.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 453571b3e..2d63367f0 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -281,6 +281,7 @@ def prune_skeleton(skeleton, min_length=100, min_distance=25, step=50): trim_length = int(min_distance / 2) labeled_skeleton = np.zeros_like(skeleton, dtype=np.int32) accepted_branches = [] # Stores (branch coordinates, start, end) + sampled_coords = [] # Pass 1: Collect branches and compute pairwise distances branch_data = [] # Store branch info for pass 2 @@ -299,7 +300,7 @@ def prune_skeleton(skeleton, min_length=100, min_distance=25, step=50): endpoints.append(end) if not branch_data: - return labeled_skeleton # No valid branches + return labeled_skeleton, np.array(sampled_coords, dtype=int) # No valid branches # Compute all pairwise distances between endpoints endpoints = np.array(endpoints) # Convert to numpy array @@ -337,7 +338,6 @@ def prune_skeleton(skeleton, min_length=100, min_distance=25, step=50): final_branches.append(branch_coords) # Assign unique labels to final pruned branches - sampled_coords = [] for path_id, branch in enumerate(final_branches, start=1): for x, y in branch: labeled_skeleton[int(x), int(y)] = path_id From 2cd54575eeb94ff28df1405ff8089623ca71daaa Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 29 Apr 2025 15:56:56 +0100 Subject: [PATCH 097/189] put default model_path to /public/EM for now --- src/amyloid_finder.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index f8466407d..b80ac3efc 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -46,7 +46,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) do_redo_tracing = parser.checkOption("--redo_all_tracing", "Ignore any autopick.star files already present and redo all tracing."); fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_trace_amyloids"); fn_other_args = parser.getOption("--other_args", "Other arguments for the python script", ""); - fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing"); + fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing","/public/EM/RELION/amypicker.ckpt"); do_gpu = parser.checkOption("--gpu", "Use GPU acceleration when availiable"); gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); @@ -763,6 +763,7 @@ void AmyloidFinder::finalise() FileName fn_pick = fn_root + "_" + fn_out + ".star"; MetaDataTable MD; + long nr_pick = 0; if (exists(fn_pick)) { @@ -773,7 +774,7 @@ void AmyloidFinder::finalise() nr_coord_files++; MD.read(fn_pick); - long nr_pick = MD.numberOfObjects(); + nr_pick = MD.numberOfObjects(); total_nr_picked += nr_pick; // mis-use MetadataTable to conveniently make histograms and value-plots MDresult.addObject(); @@ -793,6 +794,7 @@ void AmyloidFinder::finalise() MDin.setValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_fom, imic); MDin.setValue(EMDL_MICROGRAPH_AUTOPICK_PSI, fn_psi, imic); + MDin.setValue(EMDL_MLMODEL_GROUP_NR_PARTICLES, nr_pick, imic); } if (verb > 0 && imic % 60 == 0) progress_bar(imic); From e83ba729109ebfb84e624487ab621cae15d5c869 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 1 May 2025 13:56:52 +0100 Subject: [PATCH 098/189] added option to skip tracing in find_amyloids --- src/amyloid_finder.cpp | 5 +++-- src/amyloid_finder.h | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index b80ac3efc..173b8a3e5 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -49,6 +49,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing","/public/EM/RELION/amypicker.ckpt"); do_gpu = parser.checkOption("--gpu", "Use GPU acceleration when availiable"); gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); + do_skip_tracing = parser.checkOption("--skip_tracing", "Skip tracing, only do FOM calculation (useful to later run tracing on faster GPUs)"); int expert_section = parser.addSection("Expert options (typically no need to change)"); signal_minres = textToFloat(parser.getOption("--signal_minres", "Minimum resolution value for signal (in A)", "4.85")); @@ -626,7 +627,7 @@ void AmyloidFinder::traceFilaments(FileName &fn_fom, FileName &fn_psi, FileName command += " " + fn_other_args; - //std::cerr << command << std::endl; + std::cerr << command << std::endl; int res = system(command.c_str()); @@ -671,7 +672,7 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) Izscore.write(fn_fom); } - if (do_gpu) + if (!do_skip_tracing) { if (myverb) { diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index e8024c71b..b18910b59 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -63,6 +63,9 @@ class AmyloidFinder // Plot filament tracing results? bool do_plot; + // Skip tracing call + bool do_skip_tracing; + // width and length of filaments (in A) for searching of 4.7A signal RFLOAT search_filament_length, search_filament_width; From 13d7f42119ead5dea6180fe529b0cbfc67149cee Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 1 May 2025 13:57:12 +0100 Subject: [PATCH 099/189] allow tracing of amyloids on cpu too --- scripts/trace_amyloids.in | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 2d63367f0..efbc7b124 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -383,6 +383,7 @@ def main(): psi_image, _, _ = load_mrc(args.input_psi) H, W = fom_image.shape device = torch.device(args.device) + #print(f"Time elapsed after step 2: {time.time() - start_time:.2f} seconds") # ------------------ Load Model + Run Inference ---------------- @@ -393,10 +394,13 @@ def main(): in_tensor = np.stack((fom_image, psi_image), axis=0) # shape (2,H,W) in_tensor = torch.from_numpy(in_tensor[None]).float().to(device) # shape (1,2,H,W) - with torch.no_grad(), torch.autocast("cuda", dtype=torch.float16): - seg_logits, embedding = model(in_tensor) - seg_logits, embedding = seg_logits.float(), embedding.float() - foreground = torch.sigmoid(seg_logits).squeeze().cpu().numpy() # => (H,W) + if device.type == "cuda": + with torch.autocast(device_type=device.type, dtype=torch.float16): + seg_logits, embedding = model(in_tensor) + else: + with torch.autocast(device_type="cpu", dtype=torch.bfloat16): + seg_logits, embedding = model(in_tensor) + foreground = torch.sigmoid(seg_logits).squeeze().detach().to(dtype=torch.float32).cpu().numpy() #print(f"Time elapsed after step 5: {time.time() - start_time:.2f} seconds") # 2) Clean + threshold => remove small objects From 4e0b73877f6b12301652b69a8d24d2d8981ef573 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 1 May 2025 13:58:33 +0100 Subject: [PATCH 100/189] remove spurious cerr message --- src/amyloid_finder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 173b8a3e5..9f832b0e6 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -627,7 +627,7 @@ void AmyloidFinder::traceFilaments(FileName &fn_fom, FileName &fn_psi, FileName command += " " + fn_other_args; - std::cerr << command << std::endl; + //std::cerr << command << std::endl; int res = system(command.c_str()); From fb99d1c003b57dd32d4495e9f1dfca48246680f0 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 1 May 2025 14:15:48 +0100 Subject: [PATCH 101/189] explicitly define thread number in pytorch on cpu --- scripts/trace_amyloids.in | 5 ++++- src/amyloid_finder.cpp | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index efbc7b124..5151fdaca 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -371,13 +371,16 @@ def main(): parser.add_argument("-s", "--scale", type=float, default=1.0, help="Multiply output coordinates by this factor (default=1.0)") parser.add_argument("-d", "--device", default="cuda:0", help="Which GPU device to use") - + parser.add_argument("-j", "--threads", type=int, default=1) parser.add_argument("--sample_step", type=int, default=100, help="Sample a coordinate every 'sample_step' Angstroms along each path (default=50)") parser.add_argument("--plot", action='store_true', help="If set, display/save a collage figure of the pipeline results.") args = parser.parse_args() + # number of CPU threads allowed + torch.set_num_threads(args.threads) + # ------------------- Load MRCs ---------------------- fom_image, pixel_size, origin = load_mrc(args.input_fom) psi_image, _, _ = load_mrc(args.input_psi) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 9f832b0e6..3a037482b 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -622,6 +622,7 @@ void AmyloidFinder::traceFilaments(FileName &fn_fom, FileName &fn_psi, FileName command += " -r " + floatToString(trace_filament_width/2); command += " -l " + floatToString(trace_filament_length); command += " -s " + floatToString(down_angpix/angpix); + command += " -j " + integerToString(nr_threads); if (do_plot) command += " --plot "; From f04ef90393e7393ef2ce89d93a533da15d1f3706 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 6 May 2025 16:55:23 +0100 Subject: [PATCH 102/189] only do 90-degree rotations: 90-180 is calculated from -y,x swaps --- src/amyloid_finder.cpp | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 3a037482b..77580fb0a 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -47,9 +47,9 @@ void AmyloidFinder::read(int argc, char **argv, int rank) fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_trace_amyloids"); fn_other_args = parser.getOption("--other_args", "Other arguments for the python script", ""); fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing","/public/EM/RELION/amypicker.ckpt"); - do_gpu = parser.checkOption("--gpu", "Use GPU acceleration when availiable"); + do_skip_tracing = parser.checkOption("--skip_tracing", "Skip tracing."); + do_gpu = parser.checkOption("--gpu", "Use GPU acceleration when availiable"); gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); - do_skip_tracing = parser.checkOption("--skip_tracing", "Skip tracing, only do FOM calculation (useful to later run tracing on faster GPUs)"); int expert_section = parser.addSection("Expert options (typically no need to change)"); signal_minres = textToFloat(parser.getOption("--signal_minres", "Minimum resolution value for signal (in A)", "4.85")); @@ -137,7 +137,7 @@ void AmyloidFinder::initialise(bool is_leader) if (verb > 0) std::cout << " - Using pixel size from the header of : " << fn_in << " = " << angpix << std::endl; } - if (signal_maxres < 2*down_angpix) REPORT_ERROR("ERROR: the down_angpix is not enough to support the maximum resolution of the signal!"); + if (nonsignal_maxres < 2*down_angpix) REPORT_ERROR("ERROR: the down_angpix is not enough to support the maximum resolution of the signal!"); if (angpix > down_angpix) REPORT_ERROR("ERROR: this program requires input images with a pixel size of at least down_angpix (" + floatToString(down_angpix) + ")!"); // Width and length in the downscaled pixels @@ -295,7 +295,7 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi } #pragma omp parallel for num_threads(nr_threads) - for (int ipsi = 0; ipsi < nr_psi; ipsi++) + for (int ipsi = 0; ipsi < nr_psi/2; ipsi++) { const int tid = omp_get_thread_num(); RFLOAT psi = getPsiAngle(ipsi); @@ -319,7 +319,28 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi transformer[tid].inverseFourierTransform(FT2, Mrot); Mrot.setXmippOrigin(); rotated_imgs[ipsi] = Mrot; - // Just prepare the rotated_scores vector too + + } + MultidimArray Mzero(crop_box, crop_box); + Mzero.setXmippOrigin(); + for (int ipsi = nr_psi/2; ipsi < nr_psi; ipsi++) + { + rotated_imgs[ipsi] = Mzero; + // stay away from boundary to prevent many if-statements below. Images are cropped in larger box anyway, so boundaries should be zero + for (long int i=STARTINGY(Mzero)+1; i<=FINISHINGY(Mzero)-1; i++) + { + for (long int j=STARTINGX(Mzero)+1; j<=FINISHINGX(Mzero)-1; j++) + { + A2D_ELEM(rotated_imgs[ipsi], i, j) = A2D_ELEM(rotated_imgs[ipsi-nr_psi/2], -j, i); + } + } + } + + if (myverb) progress_bar(nr_psi); + + // Just prepare the rotated_scores vector too + for (int ipsi = 0; ipsi < nr_psi; ipsi++) + { rotated_scores_perline[ipsi].initZeros(crop_box, crop_box); rotated_scores_perline[ipsi].setXmippOrigin(); rotated_nonscores_perline[ipsi].initZeros(crop_box, crop_box); @@ -328,9 +349,7 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi rotated_scores[ipsi].setXmippOrigin(); rotated_nonscores[ipsi].initZeros(crop_box/shift_step, crop_box/shift_step); rotated_nonscores[ipsi].setXmippOrigin(); - } - if (myverb) progress_bar(nr_psi); // Prepare all the transformers for the 1D lines MultidimArray oneline_tmp(ilengthmax); @@ -623,6 +642,7 @@ void AmyloidFinder::traceFilaments(FileName &fn_fom, FileName &fn_psi, FileName command += " -l " + floatToString(trace_filament_length); command += " -s " + floatToString(down_angpix/angpix); command += " -j " + integerToString(nr_threads); + if (do_plot) command += " --plot "; From 294eee578b5ecd533aca5939b4d6d5a23e79eff1 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 14 May 2025 17:50:32 +0100 Subject: [PATCH 103/189] perform amyloid autopick FOM and trace calculations separately, also do tracing over entire star files to prevent loading model each time --- scripts/trace_amyloids.in | 226 ++++++++------- src/amyloid_finder.cpp | 566 +++++++++++++++++++------------------ src/amyloid_finder.h | 23 +- src/amyloid_finder_mpi.cpp | 48 +--- src/gui_jobwindow.cpp | 10 + src/pipeline_jobs.cpp | 48 +++- 6 files changed, 482 insertions(+), 439 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 5151fdaca..714469b40 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -2,7 +2,7 @@ import argparse import math -import os +import sys, os from pathlib import Path import numpy as np @@ -20,6 +20,7 @@ from skimage.measure import regionprops, label as label_connected_component from skan import Skeleton, summarize from scipy.spatial.distance import cdist +from tqdm import tqdm import time ############################################################################### @@ -360,8 +361,7 @@ def main(): #start_time = time.time() parser = argparse.ArgumentParser("Skeleton-based filament picking with instance merging, branch pruning, and ID labeling") - parser.add_argument("-if", "--input_fom", help="Input FOM MRC file") - parser.add_argument("-ip", "--input_psi", help="Input PSI MRC file") + parser.add_argument("-i", "--input", help="Input star file") parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint") parser.add_argument("-o", "--output", help="Output star file with coordinates") parser.add_argument("-r", "--radius", type=int, help="Radius of region to exclude near edges (in A)") @@ -372,6 +372,8 @@ def main(): help="Multiply output coordinates by this factor (default=1.0)") parser.add_argument("-d", "--device", default="cuda:0", help="Which GPU device to use") parser.add_argument("-j", "--threads", type=int, default=1) + parser.add_argument("-a", "--abort", help="Abort if this file exists") + parser.add_argument("-v", "--verb", type=int, help="Verbosity") parser.add_argument("--sample_step", type=int, default=100, help="Sample a coordinate every 'sample_step' Angstroms along each path (default=50)") parser.add_argument("--plot", action='store_true', @@ -379,123 +381,129 @@ def main(): args = parser.parse_args() # number of CPU threads allowed - torch.set_num_threads(args.threads) + #torch.set_num_threads(args.threads) - # ------------------- Load MRCs ---------------------- - fom_image, pixel_size, origin = load_mrc(args.input_fom) - psi_image, _, _ = load_mrc(args.input_psi) - H, W = fom_image.shape + # ------------------- Load model ---------------------- device = torch.device(args.device) - - #print(f"Time elapsed after step 2: {time.time() - start_time:.2f} seconds") - - # ------------------ Load Model + Run Inference ---------------- model = UNet.load_from_checkpoint(args.model_path, n_channels=2, n_classes=1) model.eval().to(device) - #print(f"Time elapsed after step 3: {time.time() - start_time:.2f} seconds") - - in_tensor = np.stack((fom_image, psi_image), axis=0) # shape (2,H,W) - in_tensor = torch.from_numpy(in_tensor[None]).float().to(device) # shape (1,2,H,W) - - if device.type == "cuda": - with torch.autocast(device_type=device.type, dtype=torch.float16): - seg_logits, embedding = model(in_tensor) - else: - with torch.autocast(device_type="cpu", dtype=torch.bfloat16): - seg_logits, embedding = model(in_tensor) - foreground = torch.sigmoid(seg_logits).squeeze().detach().to(dtype=torch.float32).cpu().numpy() - #print(f"Time elapsed after step 5: {time.time() - start_time:.2f} seconds") - - # 2) Clean + threshold => remove small objects - bin_foreground = (foreground > args.threshold) - min_size = 100 - clean_foreground = remove_small_objects(bin_foreground, min_size=min_size) - - # 3) Crop edges if radius is given - if args.radius is not None: - d = round(args.radius / pixel_size) - clean_foreground[:d, :] = False - clean_foreground[-d:, :] = False - clean_foreground[:, :d] = False - clean_foreground[:, -d:] = False - - # 4) Skeletonize - skeleton_img = skeletonize(clean_foreground) - #print(f"Time elapsed after step 8: {time.time() - start_time:.2f} seconds") - if skeleton_img.any(): - - # 5) Prune skeleton based on Skan's branches: remove any branch with length below args.final_min_size - pruned_skeleton, sampled_points = prune_skeleton(skeleton_img, - min_length=args.minimum_length / pixel_size, - min_distance=2 * args.radius / pixel_size, - step=int(args.sample_step / pixel_size)) - #print(f"Time elapsed after step 9: {time.time() - start_time:.2f} seconds") - - # 6) Sample coordinates every 'sample_step' from pruned skeleton, **including filament ID**. - #sampled_points = sample_coordinates_with_ids(pruned_skeleton, step=int(args.sample_step / pixel_size)) - # sampled_points shape: (N, 3) => [x, y, filament_id] - if sampled_points.size > 0: - - # 7) Scale, round, write star - # scale only the x,y columns - sampled_points[:, 0:2] = np.round(sampled_points[:, 0:2] * args.scale).astype(int) - - df = pd.DataFrame({ - 'rlnCoordinateX': sampled_points[:, 0], - 'rlnCoordinateY': sampled_points[:, 1], - 'rlnParticleSelectionType': sampled_points[:, 2] - }) - starfile.write(df, args.output, overwrite=True) - #print(f"Done. Wrote {len(df)} points to {args.output}") - else: - Path(args.output).touch() - else: - Path(args.output).touch() - - #print(f"Time elapsed after step 10: {time.time() - start_time:.2f} seconds") - - # --------------------------------------------------- - # If plotting is requested, create a collage figure - # --------------------------------------------------- - if args.plot: - - fig, ax = plt.subplots(2, 3, figsize=(32,20)) - ax = ax.ravel() - - ax[0].imshow(fom_image, cmap='gray') - ax[0].set_title("FOM Image") - ax[0].axis('off') - ax[1].imshow(psi_image, cmap='gray') - ax[1].set_title("PSI Image") - ax[1].axis('off') + input_star = starfile.read(args.input) - ax[2].imshow(color_foreground(foreground, args.threshold)) - ax[2].set_title("Foreground") - ax[2].axis('off') + # ------------------ Run Inference ---------------- + iterator = tqdm(input_star.iterrows(), total=len(input_star), file=sys.stdout) if args.verb > 0 else input_star.iterrows() + for i, row in iterator: - ax[3].imshow(skeleton_img) - ax[3].set_title("Raw Skeleton") - ax[3].axis('off') + if Path(args.abort).exists(): + print("Abort file exists. Aborting...") + sys.exit() + + # ------------------- Load MRCs ---------------------- + fom_image, pixel_size, origin = load_mrc(row["rlnMicrographFomImage"]) + psi_image, _, _ = load_mrc(row["rlnMicrographPsiImage"]) + outfile = row["rlnMicrographCoordinates"] + directory = Path(outfile).parent + # Check if the directory exists, and create it if not + directory.mkdir(parents=True, exist_ok=True) + + H, W = fom_image.shape + + in_tensor = np.stack((fom_image, psi_image), axis=0) # shape (2,H,W) + in_tensor = torch.from_numpy(in_tensor[None]).float().to(device) # shape (1,2,H,W) + + with torch.no_grad(): + if device.type == "cuda": + with torch.autocast(device_type=device.type, dtype=torch.float16): + seg_logits, embedding = model(in_tensor) + else: + with torch.autocast(device_type="cpu", dtype=torch.bfloat16): + seg_logits, embedding = model(in_tensor) + foreground = torch.sigmoid(seg_logits).squeeze().detach().to(dtype=torch.float32).cpu().numpy() + + # 2) Clean + threshold => remove small objects + bin_foreground = (foreground > args.threshold) + min_size = 100 + clean_foreground = remove_small_objects(bin_foreground, min_size=min_size) + + # 3) Crop edges if radius is given + if args.radius is not None: + d = round(args.radius / pixel_size) + clean_foreground[:d, :] = False + clean_foreground[-d:, :] = False + clean_foreground[:, :d] = False + clean_foreground[:, -d:] = False + + # 4) Skeletonize + skeleton_img = skeletonize(clean_foreground) if skeleton_img.any(): - ax[4].imshow(color_nonrandom(pruned_skeleton)) - ax[4].set_title("Pruned Skeleton") - ax[4].axis('off') - if sampled_points.size > 0: - ax[5].imshow(np.zeros_like(foreground), cmap='gray') # blank background - ax[5].scatter(sampled_points[:,0]/args.scale, sampled_points[:,1]/args.scale, s=2, c='red') - ax[5].set_xlim([0, foreground.shape[1]]) - ax[5].set_ylim([foreground.shape[0], 0]) - ax[5].set_title("Final Sampled Picks (ID shown in STAR)") - ax[5].axis('off') - - plt.tight_layout() - plt.show() - plt.close() - #print(f"Time elapsed after step 11: {time.time() - start_time:.2f} seconds") + # 5) Prune skeleton based on Skan's branches: remove any branch with length below args.final_min_size + pruned_skeleton, sampled_points = prune_skeleton(skeleton_img, + min_length=args.minimum_length / pixel_size, + min_distance=2 * args.radius / pixel_size, + step=int(args.sample_step / pixel_size)) + # 6) Sample coordinates every 'sample_step' from pruned skeleton, **including filament ID**. + #sampled_points = sample_coordinates_with_ids(pruned_skeleton, step=int(args.sample_step / pixel_size)) + # sampled_points shape: (N, 3) => [x, y, filament_id] + if sampled_points.size > 0: + + # 7) Scale, round, write star + # scale only the x,y columns + sampled_points[:, 0:2] = np.round(sampled_points[:, 0:2] * args.scale).astype(int) + + df = pd.DataFrame({ + 'rlnCoordinateX': sampled_points[:, 0], + 'rlnCoordinateY': sampled_points[:, 1], + 'rlnParticleSelectionType': sampled_points[:, 2] + }) + starfile.write(df, outfile, overwrite=True) + else: + Path(outfile).touch() + else: + Path(outfile).touch() + + # --------------------------------------------------- + # If plotting is requested, create a collage figure + # --------------------------------------------------- + if args.plot: + + fig, ax = plt.subplots(2, 3, figsize=(32,20)) + ax = ax.ravel() + + ax[0].imshow(fom_image, cmap='gray') + ax[0].set_title("FOM Image") + ax[0].axis('off') + + ax[1].imshow(psi_image, cmap='gray') + ax[1].set_title("PSI Image") + ax[1].axis('off') + + ax[2].imshow(color_foreground(foreground, args.threshold)) + ax[2].set_title("Foreground") + ax[2].axis('off') + + ax[3].imshow(skeleton_img) + ax[3].set_title("Raw Skeleton") + ax[3].axis('off') + + if skeleton_img.any(): + ax[4].imshow(color_nonrandom(pruned_skeleton)) + ax[4].set_title("Pruned Skeleton") + ax[4].axis('off') + + if sampled_points.size > 0: + ax[5].imshow(np.zeros_like(foreground), cmap='gray') # blank background + ax[5].scatter(sampled_points[:,0]/args.scale, sampled_points[:,1]/args.scale, s=2, c='red') + ax[5].set_xlim([0, foreground.shape[1]]) + ax[5].set_ylim([foreground.shape[0], 0]) + ax[5].set_title("Final Sampled Picks (ID shown in STAR)") + ax[5].axis('off') + + plt.tight_layout() + plt.show() + plt.close() if __name__ == "__main__": diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 77580fb0a..23f639dd8 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -37,6 +37,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) shift_step = textToInteger(parser.getOption("--shift_step", "Step in shifts to search (in downscaled pixels)", "5")); search_filament_width = textToFloat(parser.getOption("--search_filament_width", "Width of searching image (in A)", "50")); search_filament_length = textToFloat(parser.getOption("--search_filament_length", "Length of searching image (in A)", "250")); + do_skip_fom = parser.checkOption("--skip_fom", "Skip FOM calculation."); int pick_section = parser.addSection("Filament tracing options "); threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "0.5")); @@ -76,7 +77,12 @@ void AmyloidFinder::initialise(bool is_leader) if (fn_odir[fn_odir.length()-1] != '/') fn_odir += "/"; - fn_micrographs.clear(); + fn_ori_micrographs.clear(); + fn_ori_micrographs_fom.clear(); + fn_ori_micrographs_psi.clear(); + + todo_micrographs_fom.clear(); + todo_micrographs_tracing.clear(); if (fn_in.isStarFile()) { MetaDataTable MDin; @@ -85,98 +91,136 @@ void AmyloidFinder::initialise(bool is_leader) { FileName fn_mic; MDin.getValue(EMDL_MICROGRAPH_NAME, fn_mic); - fn_micrographs.push_back(fn_mic); + fn_ori_micrographs.push_back(fn_mic); + if (MDin.containsLabel(EMDL_MICROGRAPH_AUTOPICK_FOM) && MDin.containsLabel(EMDL_MICROGRAPH_AUTOPICK_PSI)) + { + MDin.getValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_mic); + fn_ori_micrographs_fom.push_back(fn_mic); + MDin.getValue(EMDL_MICROGRAPH_AUTOPICK_PSI, fn_mic); + fn_ori_micrographs_psi.push_back(fn_mic); + } } } else { // Read a single micrograph - fn_micrographs.push_back(fn_in); + fn_ori_micrographs.push_back(fn_in); } - fn_ori_micrographs = fn_micrographs; - // If we're continuing an old run, see which micrographs have not been finished yet... - if (do_only_unfinished && !do_redo_tracing) + if (!do_only_unfinished) { - if (verb > 0) - { - std::cout << " + Skipping those micrographs for which coordinate file already exists" << std::endl; - } - std::vector fns_todo; - for (long int imic = 0; imic < fn_micrographs.size(); imic++) + if (!do_skip_fom) todo_micrographs_fom = fn_ori_micrographs; + if (!do_skip_tracing) { - FileName fn_tmp = getOutputRootName(fn_micrographs[imic]) + "_" + fn_out + ".star"; - if (!exists(fn_tmp)) - fns_todo.push_back(fn_micrographs[imic]); + todo_micrographs_tracing = fn_ori_micrographs; + idx_todo_micrographs_tracing.resize(todo_micrographs_tracing.size()); + std::iota(idx_todo_micrographs_tracing.begin(), idx_todo_micrographs_tracing.end(), 0); } - fn_micrographs = fns_todo; } - - // If there is nothing to do, then go out of initialise - if (fn_micrographs.size() == 0) + else { - if (verb > 0) - std::cout << " + No new micrographs to do, so exiting finding amyloids ..." << std::endl; - return; - } - - if (verb > 0) std::cout << " + Finding amyloids in the " << fn_micrographs.size() << " micrographs... " << std::endl; + // If we're continuing an old run, see which micrographs have not been finished yet... + // A. for FOM/PSI calculation + if (!do_skip_fom) + { + if (verb > 0) + { + std::cout << " + Skipping those micrographs for which FOM and PSI images already exist" << std::endl; + } + todo_micrographs_fom.clear(); + for (long int imic = 0; imic < fn_ori_micrographs.size(); imic++) + { + FileName fn_fom = getOutputRootName(fn_ori_micrographs[imic]) + "_" + fn_out + "_fom.mrc"; + FileName fn_psi = getOutputRootName(fn_ori_micrographs[imic]) + "_" + fn_out + "_psi.mrc"; + if (!exists(fn_fom) || !exists(fn_psi)) + todo_micrographs_fom.push_back(fn_ori_micrographs[imic]); + } + } + // B. For tracing + if (!do_skip_tracing && !do_redo_tracing) + { + if (do_skip_fom && (fn_ori_micrographs_fom.size() == 0 || fn_ori_micrographs_psi.size() == 0)) + { + REPORT_ERROR("ERROR: you cannot skip FOM calculation without providing autopick FOM and PSI images in the input STAR file!"); + } + if (verb > 0) + { + std::cout << " + Skipping those micrographs for which coordinate file already exists" << std::endl; + } + for (long int imic = 0; imic < fn_ori_micrographs.size(); imic++) + { + FileName fn_tmp = getOutputRootName(fn_ori_micrographs[imic]) + "_" + fn_out + ".star"; + if (!exists(fn_tmp)) + { + todo_micrographs_tracing.push_back(fn_ori_micrographs[imic]); + idx_todo_micrographs_tracing.push_back(imic); + } + } - if (verb > 0 && !do_gpu) std::cout << " + Skipping filament tracing, as no GPU usage defined... Continue an old run with GPU usage to trace filaments later! " << std::endl; + } + } - // Read in header of first image - Image Iin; - Iin.read(fn_micrographs[0], false); - ori_xsize = XSIZE(Iin()); - ori_ysize = YSIZE(Iin()); - Iin().setXmippOrigin(); - if (angpix < 0.) + if (verb > 0) { - angpix = Iin.samplingRateX(); - if (verb > 0) std::cout << " - Using pixel size from the header of : " << fn_in << " = " << angpix << std::endl; + std::cout << " + Calculating FOM images for " << todo_micrographs_fom.size() << " micrographs... " << std::endl; + std::cout << " + Tracing filaments for " << todo_micrographs_tracing.size() << " micrographs... " << std::endl; } - if (nonsignal_maxres < 2*down_angpix) REPORT_ERROR("ERROR: the down_angpix is not enough to support the maximum resolution of the signal!"); - if (angpix > down_angpix) REPORT_ERROR("ERROR: this program requires input images with a pixel size of at least down_angpix (" + floatToString(down_angpix) + ")!"); - - // Width and length in the downscaled pixels - iwidthmax = ROUND(search_filament_width / down_angpix ); - ilengthmax = CEIL(search_filament_length / down_angpix ); - - down_xsize = FLOOR( (ori_xsize * angpix) / down_angpix ); - down_ysize = FLOOR( (ori_ysize * angpix) / down_angpix ); - if (ilengthmax %2 != 0) ilengthmax++; - nr_psi = ROUND(180./psi_step); - psi_step = 180./nr_psi; - - // Calculate Fourier shells for amyloid signal - imin_signal = FLOOR(ilengthmax*down_angpix/signal_minres); - imax_signal = CEIL(ilengthmax*down_angpix/signal_maxres); - imin_nonsignal = FLOOR(ilengthmax*down_angpix/nonsignal_minres); - imax_nonsignal = CEIL(ilengthmax*down_angpix/nonsignal_maxres); - - // Box size, orginal and cropped: set size of rectangular image to largest dimension - large_box = sqrt(2.)*XMIPP_MAX(ori_xsize, ori_ysize); - large_box += ROUND(XMIPP_MAX(search_filament_width, search_filament_length) / angpix); - if (large_box%2 != 0) large_box++; - // Also calculate size of cropped box: - crop_box = large_box * angpix/down_angpix; - if (crop_box%2 != 0) crop_box++; - - // Output some information to the user - if (verb > 0) + if (todo_micrographs_fom.size() > 0) { - std::cout << " + Number of 1D rows for filament width (in downscaled pixels): " << iwidthmax << std::endl; - std::cout << " + Length of 1D rows for filament (in downscaled pixels): " << ilengthmax << std::endl; - std::cout << " + Number of in-plane rotations to sample: " << nr_psi << " with step of " << psi_step << " degrees" << std::endl; - std::cout << " + Original size of the input micrographs: " << ori_xsize << " x " << ori_ysize << " pixels" << std::endl; - std::cout << " + Size of image to sample (in downscaled pixels): " << down_xsize << " x " << down_ysize << std::endl; - std::cout << " + Fourier shells for the amyloid signal (in downscaled pixels): " << imin_signal << " - " << imax_signal << std::endl; - std::cout << " + Fourier shells for the non-signal control (in downscaled pixels): " << imin_nonsignal << " - " << imax_nonsignal << std::endl; - std::cout << " ========================== " << std::endl; - } + // Read in header of first image + Image Iin; + Iin.read(todo_micrographs_fom[0], false); + ori_xsize = XSIZE(Iin()); + ori_ysize = YSIZE(Iin()); + Iin().setXmippOrigin(); + if (angpix < 0.) + { + angpix = Iin.samplingRateX(); + if (verb > 0) std::cout << " - Using pixel size from the header of : " << fn_in << " = " << angpix << std::endl; + } + if (nonsignal_maxres < 2*down_angpix) REPORT_ERROR("ERROR: the down_angpix is not enough to support the maximum resolution of the signal!"); + if (angpix > down_angpix) REPORT_ERROR("ERROR: this program requires input images with a pixel size of at least down_angpix (" + floatToString(down_angpix) + ")!"); + + // Width and length in the downscaled pixels + iwidthmax = ROUND(search_filament_width / down_angpix ); + ilengthmax = CEIL(search_filament_length / down_angpix ); + + down_xsize = FLOOR( (ori_xsize * angpix) / down_angpix ); + down_ysize = FLOOR( (ori_ysize * angpix) / down_angpix ); + if (ilengthmax %2 != 0) ilengthmax++; + nr_psi = ROUND(180./psi_step); + psi_step = 180./nr_psi; + + // Calculate Fourier shells for amyloid signal + imin_signal = FLOOR(ilengthmax*down_angpix/signal_minres); + imax_signal = CEIL(ilengthmax*down_angpix/signal_maxres); + imin_nonsignal = FLOOR(ilengthmax*down_angpix/nonsignal_minres); + imax_nonsignal = CEIL(ilengthmax*down_angpix/nonsignal_maxres); + + // Box size, orginal and cropped: set size of rectangular image to largest dimension + large_box = sqrt(2.)*XMIPP_MAX(ori_xsize, ori_ysize); + large_box += ROUND(XMIPP_MAX(search_filament_width, search_filament_length) / angpix); + if (large_box%2 != 0) large_box++; + // Also calculate size of cropped box: + crop_box = large_box * angpix/down_angpix; + if (crop_box%2 != 0) crop_box++; + + // Output some information to the user + if (verb > 0) + { + std::cout << " + Number of 1D rows for filament width (in downscaled pixels): " << iwidthmax << std::endl; + std::cout << " + Length of 1D rows for filament (in downscaled pixels): " << ilengthmax << std::endl; + std::cout << " + Number of in-plane rotations to sample: " << nr_psi << " with step of " << psi_step << " degrees" << std::endl; + std::cout << " + Original size of the input micrographs: " << ori_xsize << " x " << ori_ysize << " pixels" << std::endl; + std::cout << " + Size of image to sample (in downscaled pixels): " << down_xsize << " x " << down_ysize << std::endl; + std::cout << " + Fourier shells for the amyloid signal (in downscaled pixels): " << imin_signal << " - " << imax_signal << std::endl; + std::cout << " + Fourier shells for the non-signal control (in downscaled pixels): " << imin_nonsignal << " - " << imax_nonsignal << std::endl; + std::cout << " ========================== " << std::endl; + } + } } @@ -215,13 +259,6 @@ RFLOAT AmyloidFinder::getPsiAngle(int ipsi) return 2.3 + ipsi * psi_step; } -RFLOAT AmyloidFinder::getPsiDiff(RFLOAT psi1, RFLOAT psi2) -{ - RFLOAT psidiff = fabs(psi1 - psi2); - if (psidiff > 90.) psidiff -= 180.; - return fabs(psidiff); - -} MultidimArray AmyloidFinder::growNonSignalMask(MultidimArray &inmask, int extend_size) { @@ -623,46 +660,13 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi } -void AmyloidFinder::traceFilaments(FileName &fn_fom, FileName &fn_psi, FileName &fn_star) -{ - - // hardcoded python script for now... - FileName command = fn_exe; - - command += " -if " + fn_fom; - command += " -ip " + fn_psi; - command += " -m " + fn_model_path; - if (do_gpu) - command += " -d cuda:" + integerToString(device_id); - else - command += " -d cpu"; - command += " -o " + fn_star; - command += " -t " + floatToString(threshold); - command += " -r " + floatToString(trace_filament_width/2); - command += " -l " + floatToString(trace_filament_length); - command += " -s " + floatToString(down_angpix/angpix); - command += " -j " + integerToString(nr_threads); - - if (do_plot) - command += " --plot "; - - command += " " + fn_other_args; - - //std::cerr << command << std::endl; - int res = system(command.c_str()); - - -} - - - -void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) +void AmyloidFinder::calculateFOMOneMicrograph(FileName fn_mic, bool myverb) { FileName fn_root = getOutputRootName(fn_mic); FileName fn_fom = fn_root + "_" + fn_out + "_fom.mrc"; FileName fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; - FileName fn_skew = fn_root + "_" + fn_out + "_skew.star"; + FileName fn_skew = fn_root + "_" + fn_out + "_skew.txt"; MultidimArray Mscore, Mangle; if (!exists(fn_fom) || !exists(fn_psi)) @@ -677,12 +681,10 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) RFLOAT skew, kurt; getScoreForOneMicrograph(Iin(), Mscore, Mangle, skew, kurt, myverb); - MetaDataTable MDskew; - MDskew.setIsList(true); - MDskew.addObject(); - MDskew.setValue(EMDL_MICROGRAPH_SCORE_SKEWNESS, skew); - MDskew.setValue(EMDL_MICROGRAPH_SCORE_KURTOSIS, kurt); - MDskew.write(fn_skew); + std::ofstream fh; + fh.open((fn_skew).c_str(), std::ios::out); + fh << skew << " " << kurt << std::endl; + fh.close(); Image Ipsi, Izscore; Ipsi.setSamplingRateInHeader(down_angpix); @@ -693,18 +695,6 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) Izscore.write(fn_fom); } - if (!do_skip_tracing) - { - if (myverb) - { - std::cout << " - Tracing filaments ..." << std::endl; - init_progress_bar(1); - } - - FileName fn_star = getOutputRootName(fn_mic) + "_" + fn_out + ".star"; - traceFilaments(fn_fom, fn_psi, fn_star); - } - if (myverb) std::cout << "done!" << std::endl; @@ -712,51 +702,133 @@ void AmyloidFinder::processOneMicrograph(FileName fn_mic, bool myverb) } -void AmyloidFinder::run() +void AmyloidFinder::runFOMBatch(long int my_first, long int my_last) { - int barstep; - if (verb > 0) - { - std::cout << " Finding amyloids ..." << std::endl; - init_progress_bar(fn_micrographs.size()); - barstep = XMIPP_MAX(1, fn_micrographs.size() / 60); - } - FileName fn_olddir=""; - for (long int imic = 0; imic < fn_micrographs.size(); imic++) - { + long int my_nr = my_last - my_first + 1; + if (my_nr <= 0) return; + + int barstep; + if (verb > 0) + { + std::cout << " Calculating FOMs ..." << std::endl; + init_progress_bar(my_nr); + barstep = XMIPP_MAX(1, my_nr / 60); + } - // Abort through the pipeline_control system - if (pipeline_control_check_abort_job()) - exit(RELION_EXIT_ABORTED); + FileName fn_olddir=""; + for (long int imic = my_first; imic <= my_last; imic++) + { - if (verb > 0 && imic % barstep == 0) - progress_bar(imic); + // Abort through the pipeline_control system + if (pipeline_control_check_abort_job()) + exit(RELION_EXIT_ABORTED); - // Check new-style outputdirectory exists and make it if not! - FileName fn_oroot = getOutputRootName(fn_micrographs[imic]); - FileName fn_dir = fn_oroot.beforeLastOf("/"); - if (fn_dir != fn_olddir) - { - // Make a Particles directory - mktree(fn_dir); - fn_olddir = fn_dir; - } + if (verb > 0 && imic % barstep == 0) + progress_bar(imic); + + // Check new-style outputdirectory exists and make it if not! + FileName fn_oroot = getOutputRootName(todo_micrographs_fom[imic]); + FileName fn_dir = fn_oroot.beforeLastOf("/"); + if (fn_dir != fn_olddir) + { + // Make a Particles directory + mktree(fn_dir); + fn_olddir = fn_dir; + } #ifdef TIMING - timer.tic(TIMING_A5); + timer.tic(TIMING_A5); #endif - processOneMicrograph(fn_micrographs[imic], fn_micrographs.size()==1); - + calculateFOMOneMicrograph(todo_micrographs_fom[imic], todo_micrographs_fom.size() == 1); + if (verb > 0 && (imic-my_first+1)%barstep == 0) progress_bar(imic - my_first + 1); #ifdef TIMING - timer.toc(TIMING_A5); + timer.toc(TIMING_A5); #endif - } + } - if (verb > 0) - progress_bar(fn_micrographs.size()); + if (verb > 0) progress_bar(my_nr); + +} + +void AmyloidFinder::runTracingBatch(long int my_first, long int my_last, int my_rank) +{ + long int my_nr = my_last - my_first + 1; + if (my_nr <= 0) return; + + if (verb > 0) std::cout << " - Tracing filaments ..." << std::endl; + + // TODO!!! Make a temp STAR file with mic, fom and psi names to pass to Jenny's program!!!! + // takes from my_first to my_last! + FileName fn_tracing_star = fn_odir + "input_trace_rank" + integerToString(my_rank) + ".star"; + MetaDataTable MDtrace; + for (long int imic = my_first; imic <= my_last; imic++) + { + MDtrace.addObject(); + FileName fn_root = getOutputRootName(todo_micrographs_tracing[imic]); + FileName fn_fom, fn_psi; + if (do_skip_fom) + { + long int imic_ori = idx_todo_micrographs_tracing[imic]; + fn_fom = fn_ori_micrographs_fom[imic_ori]; + fn_psi = fn_ori_micrographs_psi[imic_ori]; + } + else + { + fn_fom = fn_root + "_" + fn_out + "_fom.mrc"; + fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; + } + // remove leading job number from fn_mic filename + FileName fn_pick = fn_root + "_" + fn_out + ".star"; + MDtrace.setValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_fom); + MDtrace.setValue(EMDL_MICROGRAPH_AUTOPICK_PSI, fn_psi); + MDtrace.setValue(EMDL_MICROGRAPH_COORDINATES, fn_pick); + } + MDtrace.write(fn_tracing_star); + + // hardcoded python script for now... + FileName command = fn_exe; + + command += " -i " + fn_tracing_star; + command += " -m " + fn_model_path; + if (do_gpu) + { + command += " -d cuda:" + integerToString(device_id); + } + else + { + command += " -d cpu"; + command += " -j " + integerToString(nr_threads); + } + command += " -t " + floatToString(threshold); + command += " -r " + floatToString(trace_filament_width/2); + command += " -l " + floatToString(trace_filament_length); + command += " -s " + floatToString(down_angpix/angpix); + command += " -a " + pipeline_control_outputname+RELION_JOB_ABORT_NOW; + command += " -v " + integerToString(verb); + if (do_plot) + command += " --plot "; + + command += " " + fn_other_args; + + //std::cerr << command << std::endl; + int res = system(command.c_str()); + + if (pipeline_control_check_abort_job()) + exit(RELION_EXIT_ABORTED); + else if (res != 0) exit(RELION_EXIT_FAILURE); } + +void AmyloidFinder::run() +{ + + runFOMBatch(0, todo_micrographs_fom.size() - 1); + runTracingBatch(0, todo_micrographs_tracing.size() - 1); + +} + + void AmyloidFinder::finalise() { @@ -780,134 +852,92 @@ void AmyloidFinder::finalise() FileName fn_root = getOutputRootName(fn_ori_micrographs[imic]); FileName fn_fom = fn_root + "_" + fn_out + "_fom.mrc"; - FileName fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; - FileName fn_skew = fn_root + "_" + fn_out + "_skew.star"; - FileName fn_pick = fn_root + "_" + fn_out + ".star"; - MetaDataTable MD; - long nr_pick = 0; - if (exists(fn_pick)) + if (!do_skip_fom) { + FileName fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; + FileName fn_skew = fn_root + "_" + fn_out + "_skew.txt"; - MDcoords.addObject(); - MDcoords.setValue(EMDL_MICROGRAPH_NAME, fn_ori_micrographs[imic]); - MDcoords.setValue(EMDL_MICROGRAPH_COORDINATES, fn_pick); - MDcoords.setValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_fom); - nr_coord_files++; - - MD.read(fn_pick); - nr_pick = MD.numberOfObjects(); - total_nr_picked += nr_pick; - // mis-use MetadataTable to conveniently make histograms and value-plots - MDresult.addObject(); - MDresult.setValue(EMDL_MICROGRAPH_NAME, fn_micrographs[imic]); - MDresult.setValue(EMDL_MLMODEL_GROUP_NR_PARTICLES, nr_pick); - } - - if (exists(fn_skew)) - { - MetaDataTable MDskew; RFLOAT kurt, skew; - MDskew.read(fn_skew); - MDskew.getValue(EMDL_MICROGRAPH_SCORE_KURTOSIS, kurt); - MDskew.getValue(EMDL_MICROGRAPH_SCORE_SKEWNESS, skew); + std::ifstream fin(fn_skew); + if (!(fin >> skew >> kurt)) REPORT_ERROR("Error reading the numbers from the file: " + fn_skew); + MDin.setValue(EMDL_MICROGRAPH_SCORE_KURTOSIS, kurt, imic); MDin.setValue(EMDL_MICROGRAPH_SCORE_SKEWNESS, skew, imic); - MDin.setValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_fom, imic); MDin.setValue(EMDL_MICROGRAPH_AUTOPICK_PSI, fn_psi, imic); - MDin.setValue(EMDL_MLMODEL_GROUP_NR_PARTICLES, nr_pick, imic); } - if (verb > 0 && imic % 60 == 0) progress_bar(imic); + if (!do_skip_tracing) + { - } + FileName fn_pick = fn_root + "_" + fn_out + ".star"; + MDcoords.addObject(); + MDcoords.setValue(EMDL_MICROGRAPH_NAME, fn_ori_micrographs[imic]); + MDcoords.setValue(EMDL_MICROGRAPH_COORDINATES, fn_pick); + MDcoords.setValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_fom); + nr_coord_files++; - FileName fn_coords = fn_odir + fn_out + ".star"; - MDcoords.setName("coordinate_files"); - MDcoords.write(fn_coords); + } - FileName fn_mics = fn_odir + "micrographs_" + fn_out + ".star"; - obsModel.save(MDin, fn_mics, "micrographs"); + if (verb > 0 && imic % 60 == 0) progress_bar(imic); - if (verb > 0 ) - { - progress_bar(fn_ori_micrographs.size()); - std::cout << " Saved list with " << nr_coord_files << " coordinate files in: " << fn_coords << std::endl; } - if (verb > 0) - { - std::cout << " Total number of particles from " << fn_ori_micrographs.size() << " micrographs is " << total_nr_picked << std::endl; + if (verb > 0) progress_bar(fn_ori_micrographs.size()); - long avg = 0; - if (fn_ori_micrographs.size() > 0) avg = ROUND((RFLOAT)total_nr_picked/fn_ori_micrographs.size()); - std::cout << " i.e. on average there were " << avg << " particles per micrograph" << std::endl; - std::cout << " Now generating logfile.pdf ... " << std::endl; - } + if (!do_skip_fom) + { - // Values for all micrographs - FileName fn_eps; - std::vector all_fn_eps; - std::vector histX, histY; - - MDresult.write(fn_odir + "summary.star"); - CPlot2D *plot2Db=new CPlot2D("Nr of picked particles for all micrographs"); - MDresult.addToCPlot2D(plot2Db, EMDL_UNDEFINED, EMDL_MLMODEL_GROUP_NR_PARTICLES, 1.); - plot2Db->SetDrawLegend(false); - fn_eps = fn_odir + "all_nr_parts.eps"; - plot2Db->OutputPostScriptPlot(fn_eps); - all_fn_eps.push_back(fn_eps); - delete plot2Db; - if (MDresult.numberOfObjects() > 3) - { - CPlot2D *plot2D=new CPlot2D(""); - MDresult.columnHistogram(EMDL_MLMODEL_GROUP_NR_PARTICLES,histX,histY,0, plot2D); - fn_eps = fn_odir + "histogram_nrparts.eps"; - plot2D->SetTitle("Histogram of nr of picked particles per micrograph"); - plot2D->OutputPostScriptPlot(fn_eps); - all_fn_eps.push_back(fn_eps); - delete plot2D; - } + FileName fn_mics = fn_odir + "micrographs_" + fn_out + ".star"; + obsModel.save(MDin, fn_mics, "micrographs"); + if (verb > 0) std::cout << " Saved output micrograph STAR file with FOM images in: " << fn_mics << std::endl; - CPlot2D *plot2De=new CPlot2D("Skewness of FOM for all micrographs"); - MDin.addToCPlot2D(plot2De, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_SKEWNESS, 1.); - plot2De->SetDrawLegend(false); - fn_eps = fn_odir + "all_FOM_skew.eps"; - plot2De->OutputPostScriptPlot(fn_eps); - all_fn_eps.push_back(fn_eps); - delete plot2De; - if (MDin.numberOfObjects() > 3) - { - CPlot2D *plot2Df=new CPlot2D(""); - MDin.columnHistogram(EMDL_MICROGRAPH_SCORE_SKEWNESS,histX,histY,0, plot2Df); - fn_eps = fn_odir + "histogram_FOM_skew.eps"; - plot2Df->SetTitle("Histogram of FOM skewness per micrograph"); - plot2Df->OutputPostScriptPlot(fn_eps); + // Make histograms of skewness and kurtosis of FOM values for all micrographs + FileName fn_eps; + std::vector all_fn_eps; + std::vector histX, histY; + + CPlot2D *plot2De=new CPlot2D("Skewness of FOM for all micrographs"); + MDin.addToCPlot2D(plot2De, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_SKEWNESS, 1.); + plot2De->SetDrawLegend(false); + fn_eps = fn_odir + "all_FOM_skew.eps"; + plot2De->OutputPostScriptPlot(fn_eps); all_fn_eps.push_back(fn_eps); - delete plot2Df; - } + delete plot2De; + if (MDin.numberOfObjects() > 3) + { + CPlot2D *plot2Df=new CPlot2D(""); + MDin.columnHistogram(EMDL_MICROGRAPH_SCORE_SKEWNESS,histX,histY,0, plot2Df); + fn_eps = fn_odir + "histogram_FOM_skew.eps"; + plot2Df->SetTitle("Histogram of FOM skewness per micrograph"); + plot2Df->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2Df; + } - CPlot2D *plot2Dg=new CPlot2D("Kurtosis of FOM for all micrographs"); - MDin.addToCPlot2D(plot2Dg, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_KURTOSIS, 1.); - plot2Dg->SetDrawLegend(false); - fn_eps = fn_odir + "all_FOM_kurt.eps"; - plot2Dg->OutputPostScriptPlot(fn_eps); - all_fn_eps.push_back(fn_eps); - delete plot2Dg; - if (MDin.numberOfObjects() > 3) - { - CPlot2D *plot2Dh=new CPlot2D(""); - MDin.columnHistogram(EMDL_MICROGRAPH_SCORE_KURTOSIS,histX,histY,0, plot2Dh); - fn_eps = fn_odir + "histogram_FOM_kurt.eps"; - plot2Dh->SetTitle("Histogram of FOM kurtosis per micrograph"); - plot2Dh->OutputPostScriptPlot(fn_eps); + CPlot2D *plot2Dg=new CPlot2D("Kurtosis of FOM for all micrographs"); + MDin.addToCPlot2D(plot2Dg, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_KURTOSIS, 1.); + plot2Dg->SetDrawLegend(false); + fn_eps = fn_odir + "all_FOM_kurt.eps"; + plot2Dg->OutputPostScriptPlot(fn_eps); all_fn_eps.push_back(fn_eps); - delete plot2Dh; + delete plot2Dg; + if (MDin.numberOfObjects() > 3) + { + CPlot2D *plot2Dh=new CPlot2D(""); + MDin.columnHistogram(EMDL_MICROGRAPH_SCORE_KURTOSIS,histX,histY,0, plot2Dh); + fn_eps = fn_odir + "histogram_FOM_kurt.eps"; + plot2Dh->SetTitle("Histogram of FOM kurtosis per micrograph"); + plot2Dh->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2Dh; + } + + joinMultipleEPSIntoSinglePDF(fn_odir + "logfile.pdf", all_fn_eps); } - joinMultipleEPSIntoSinglePDF(fn_odir + "logfile.pdf", all_fn_eps); } \ No newline at end of file diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index b18910b59..930755263 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -29,6 +29,7 @@ #include #include #include +#include // for std::iota struct AmyloidCoordinate { @@ -63,8 +64,8 @@ class AmyloidFinder // Plot filament tracing results? bool do_plot; - // Skip tracing call - bool do_skip_tracing; + // Skip FOM calculation or tracing call + bool do_skip_fom, do_skip_tracing; // width and length of filaments (in A) for searching of 4.7A signal RFLOAT search_filament_length, search_filament_width; @@ -104,7 +105,8 @@ class AmyloidFinder std::vector circle; // All and selected micrographs to autopick from - std::vector fn_ori_micrographs, fn_micrographs; + std::vector fn_ori_micrographs, fn_ori_micrographs_fom, fn_ori_micrographs_psi, todo_micrographs_fom, todo_micrographs_tracing; + std::vector idx_todo_micrographs_tracing; // Continue an old run: only estimate CTF if logfile WITH Final Values line does not yet exist, otherwise skip the tomogram bool do_only_unfinished; @@ -128,9 +130,6 @@ class AmyloidFinder // Make sure all pieces of code use same psi angles from ipsi RFLOAT getPsiAngle(int ipsi); - // Difference between two psi angles that range from 0-180 - RFLOAT getPsiDiff(RFLOAT psi1, RFLOAT psi2); - // grow nonsignal mask MultidimArray growNonSignalMask(MultidimArray &inmask, int dist); @@ -138,13 +137,15 @@ class AmyloidFinder void getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, MultidimArray &Mangle, RFLOAT &skew, RFLOAT &kurt, bool myverb = false); - // Trace individual segments in the images based on the scores and angles - void traceFilaments(FileName &fn_fom, FileName &fn_fpsi, FileName &fn_star); - // Run on one micrograph - void processOneMicrograph(FileName fn_mic, bool myverb = false); + void calculateFOMOneMicrograph(FileName fn_mic, bool myverb = false); + + // Calculate FOM for all micrographs in the indicated batch + void runFOMBatch(long int my_first, long int my_last); + + // Trace filaments for all micrographs in the indicated batch + void runTracingBatch(long int my_first, long int my_last, int my_rank = 0); - // Execute all CTFFIND jobs to get CTF parameters void run(); // Write out combined star files, etc diff --git a/src/amyloid_finder_mpi.cpp b/src/amyloid_finder_mpi.cpp index c104c40d9..6a4477d08 100644 --- a/src/amyloid_finder_mpi.cpp +++ b/src/amyloid_finder_mpi.cpp @@ -73,44 +73,18 @@ void AmyloidFinderMpi::deviceInitialise() void AmyloidFinderMpi::run() { // Each node does part of the work - long int my_first_micrograph, my_last_micrograph, my_nr_micrographs; - divide_equally(fn_micrographs.size(), node->size, node->rank, my_first_micrograph, my_last_micrograph); - my_nr_micrographs = my_last_micrograph - my_first_micrograph + 1; - - int barstep; - if (verb > 0) - { - std::cout << " Finding amyloids ..." << std::endl; - init_progress_bar(my_nr_micrographs); - barstep = XMIPP_MAX(1, my_nr_micrographs / 60); - } - - FileName fn_olddir=""; - for (long int imic = my_first_micrograph; imic <= my_last_micrograph; imic++) - { - // Abort through the pipeline_control system - if (pipeline_control_check_abort_job()) - MPI_Abort(MPI_COMM_WORLD, RELION_EXIT_ABORTED); - - // Check new-style outputdirectory exists and make it if not! - FileName fn_dir = getOutputRootName(fn_micrographs[imic]); - fn_dir = fn_dir.beforeLastOf("/"); - if (fn_dir != fn_olddir) - { - // Make a Particles directory - mktree(fn_dir); - fn_olddir = fn_dir; - } - - processOneMicrograph(fn_micrographs[imic], fn_micrographs.size()==1); - - if (verb > 0 && ((imic-my_first_micrograph) % barstep == 0)) - { - progress_bar(imic-my_first_micrograph); - } + if (todo_micrographs_fom.size() > 0) + { + long int my_first_fom, my_last_fom, my_nr_micrographs; + divide_equally(todo_micrographs_fom.size(), node->size, node->rank, my_first_fom, my_last_fom); + runFOMBatch(my_first_fom, my_last_fom); + } + if (todo_micrographs_tracing.size() > 0) + { + long int my_first_tracing, my_last_tracing, my_nr_micrographs; + divide_equally(todo_micrographs_tracing.size(), node->size, node->rank, my_first_tracing, my_last_tracing); + runTracingBatch(my_first_tracing, my_last_tracing, node->rank); } - if (verb > 0) - progress_bar(my_nr_micrographs); } diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 8e740f89e..c8b018a37 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -940,10 +940,20 @@ void JobWindow::initialiseAutopickWindow() current_y += STEPY/2; + place("do_amyloid_fom"); + + group5 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group5->end(); + place("do_amyloid_tracing", TOGGLE_LEAVE_ACTIVE, group5); + + group5->begin(); place("amyloid_threshold"); place("do_amyloid_plot"); place("redo_all_tracing"); + group5->end(); + guientries["do_amyloid_tracing"].cb_menu_i(); + group4->end(); guientries["do_pick_helical_segments"].cb_menu_i(); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 86e9e481d..a9bd4a3c9 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2050,6 +2050,9 @@ Kappa = 0.3 means that the curvature of the picked helical tubes should not be l Kappa ~ 0.05 is recommended for long and straight tubes (e.g. TMV, VipA/VipB and AChR tubes) while 0.20 ~ 0.40 seems suitable for flexible ones (e.g. ParM and MAVS-CARD filaments)."); joboptions["helical_tube_length_min"] = JobOption("Minimum length (A): ", 400, 100, 1000, 10, "Minimum length (in Angstroms) of helical tubes for auto-picking. \ Helical tubes with shorter lengths will not be picked. Note that a long helical tube seen by human eye might be treated as short broken pieces due to low FOM values or high picking threshold."); + + joboptions["do_amyloid_fom"] = JobOption("Calculate amyloid FOM images? ", true, "Calculate FOM images with 4.7A Fourier signal for all input micrographs? Note this code runs only on the CPU, and best multi-threaded."); + joboptions["do_amyloid_tracing"] = JobOption("Trace amyloids in FOM images? ", true, "Trace amyloids (as lines) in the FOM images? Note this code runs only most efficiently on the GPU."); joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 0.25, 0.05, 1, 0.05, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); joboptions["do_amyloid_plot"] = JobOption("Plot results per micrograph?", false, "Set this option to Yes to visualise intermediate results for amyloid tracing, which may help with setting values for filament length, width and picking threshold. Don't use in parallel or in submission to the queue."); joboptions["redo_all_tracing"] = JobOption("Redo tracing for all micrographs?", false, "Set this option to Yes to redo the tracing for all micrographs (and overwrite any existing output coordinate star files that may exist). If set to No, then only micrographs for which the star files don't exist yet will be traced. This is useful for speeding up on-the-fly processing in continuation jobs."); @@ -2325,28 +2328,45 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Wed, 21 May 2025 16:34:57 +0100 Subject: [PATCH 104/189] updated with picking lines, startend and partcles defined in autopick.star files, also updates to jenny's script --- scripts/trace_amyloids.in | 5 ++-- src/amyloid_finder.cpp | 55 ++++++++++++++++++++++++------------ src/autopicker.cpp | 13 ++++++++- src/gui_jobwindow.cpp | 14 ---------- src/manualpicker.cpp | 59 +++++++++++++++++++++++++++++++++++---- src/metadata_label.h | 2 ++ src/metadata_table.cpp | 24 ++++++++++++++++ src/metadata_table.h | 3 ++ src/pipeline_jobs.cpp | 26 ++++++----------- src/preprocessing.cpp | 34 ++++++++++++++++++++-- src/preprocessing.h | 5 +++- 11 files changed, 177 insertions(+), 63 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 714469b40..2efebd624 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -436,9 +436,10 @@ def main(): # 4) Skeletonize skeleton_img = skeletonize(clean_foreground) - if skeleton_img.any(): + # if there is only a single non-zero pixel, Skeleton will give an error. Let's check for at least 10 non-zero pixels + if np.count_nonzero(skeleton_img) > 10: - # 5) Prune skeleton based on Skan's branches: remove any branch with length below args.final_min_size + # 5) Prune skeleton based on Skan's branches: remove any branch with length below args.final_min_size pruned_skeleton, sampled_points = prune_skeleton(skeleton_img, min_length=args.minimum_length / pixel_size, min_distance=2 * args.radius / pixel_size, diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 23f639dd8..639f46e1a 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -44,7 +44,6 @@ void AmyloidFinder::read(int argc, char **argv, int rank) trace_filament_width = textToFloat(parser.getOption("--trace_filament_width", "Minimum width occupied by a traced filaments (in A)", "200")); trace_filament_length = textToFloat(parser.getOption("--trace_filament_length", "Minimum length of traced filaments (in A)", "300")); do_plot = parser.checkOption("--plot", "Display images with intermediate tracing results for each micrograph"); - do_redo_tracing = parser.checkOption("--redo_all_tracing", "Ignore any autopick.star files already present and redo all tracing."); fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_trace_amyloids"); fn_other_args = parser.getOption("--other_args", "Other arguments for the python script", ""); fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing","/public/EM/RELION/amypicker.ckpt"); @@ -137,7 +136,7 @@ void AmyloidFinder::initialise(bool is_leader) } } // B. For tracing - if (!do_skip_tracing && !do_redo_tracing) + if (!do_skip_tracing) { if (do_skip_fom && (fn_ori_micrographs_fom.size() == 0 || fn_ori_micrographs_psi.size() == 0)) { @@ -167,22 +166,23 @@ void AmyloidFinder::initialise(bool is_leader) std::cout << " + Tracing filaments for " << todo_micrographs_tracing.size() << " micrographs... " << std::endl; } + // Read in header of first image + Image Iin; + Iin.read(fn_ori_micrographs[0], false); + ori_xsize = XSIZE(Iin()); + ori_ysize = YSIZE(Iin()); + Iin().setXmippOrigin(); + if (angpix < 0.) + { + angpix = Iin.samplingRateX(); + if (verb > 0) std::cout << " - Using pixel size from the header of : " << fn_in << " = " << angpix << std::endl; + } + if (nonsignal_maxres < 2*down_angpix) REPORT_ERROR("ERROR: the down_angpix is not enough to support the maximum resolution of the signal!"); + if (angpix > down_angpix) REPORT_ERROR("ERROR: this program requires input images with a pixel size of at least down_angpix (" + floatToString(down_angpix) + ")!"); + if (todo_micrographs_fom.size() > 0) { - // Read in header of first image - Image Iin; - Iin.read(todo_micrographs_fom[0], false); - ori_xsize = XSIZE(Iin()); - ori_ysize = YSIZE(Iin()); - Iin().setXmippOrigin(); - if (angpix < 0.) - { - angpix = Iin.samplingRateX(); - if (verb > 0) std::cout << " - Using pixel size from the header of : " << fn_in << " = " << angpix << std::endl; - } - if (nonsignal_maxres < 2*down_angpix) REPORT_ERROR("ERROR: the down_angpix is not enough to support the maximum resolution of the signal!"); - if (angpix > down_angpix) REPORT_ERROR("ERROR: this program requires input images with a pixel size of at least down_angpix (" + floatToString(down_angpix) + ")!"); // Width and length in the downscaled pixels iwidthmax = ROUND(search_filament_width / down_angpix ); @@ -843,15 +843,16 @@ void AmyloidFinder::finalise() ObservationModel obsModel; ObservationModel::loadSafely(fn_in, obsModel, MDin, "micrographs", verb); - MetaDataTable MDcoords; - MetaDataTable MDresult; + MetaDataTable MDcoords; + MDcoords.setName("coordinate_files"); + MetaDataTable MDresult; long total_nr_picked = 0; int nr_coord_files = 0; for (long int imic = 0; imic < fn_ori_micrographs.size(); imic++) { FileName fn_root = getOutputRootName(fn_ori_micrographs[imic]); - FileName fn_fom = fn_root + "_" + fn_out + "_fom.mrc"; + FileName fn_fom = (do_skip_fom) ? fn_ori_micrographs_fom[imic] : fn_root + "_" + fn_out + "_fom.mrc"; if (!do_skip_fom) { @@ -939,5 +940,23 @@ void AmyloidFinder::finalise() joinMultipleEPSIntoSinglePDF(fn_odir + "logfile.pdf", all_fn_eps); } + if (!do_skip_tracing) + { + + FileName fn_coords = fn_odir + fn_out + ".star"; + + MetaDataTable MDhead; + MDhead.setName("general"); + MDhead.setIsList(true); + MDhead.addObject(); + MDhead.setValue(EMDL_MICROGRAPH_PICKTYPE, "lines"); + + std::vector MDins; + MDins.push_back(MDhead); + MDins.push_back(MDcoords); + writeMultipleTablesToStar(MDins, fn_coords); + + } + } \ No newline at end of file diff --git a/src/autopicker.cpp b/src/autopicker.cpp index 81c7af3fb..34540b989 100644 --- a/src/autopicker.cpp +++ b/src/autopicker.cpp @@ -1083,7 +1083,18 @@ void AutoPicker::generatePDFLogfile() FileName fn_coords = fn_odir + fn_out + ".star"; MDcoords.setName("coordinate_files"); - MDcoords.write(fn_coords); + + MetaDataTable MDhead; + MDhead.setName("general"); + MDhead.setIsList(true); + MDhead.addObject(); + std::string type = (autopick_helical_segments) ? "particles" : "startend"; + MDhead.setValue(EMDL_MICROGRAPH_PICKTYPE, type); + + std::vector MDins; + MDins.push_back(MDhead); + MDins.push_back(MDcoords); + writeMultipleTablesToStar(MDins, fn_coords); if (verb > 0 ) { diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index c8b018a37..cb323681b 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -949,7 +949,6 @@ void JobWindow::initialiseAutopickWindow() group5->begin(); place("amyloid_threshold"); place("do_amyloid_plot"); - place("redo_all_tracing"); group5->end(); guientries["do_amyloid_tracing"].cb_menu_i(); @@ -1069,22 +1068,9 @@ Pixels values higher than this many times the image stddev will be replaced with current_y += STEPY/2; place("helical_bimodal_angular_priors", TOGGLE_DEACTIVATE); - - group6 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); - group6->end(); - - current_y += STEPY/2; - place("do_extract_helical_tubes", TOGGLE_DEACTIVATE, group6); - - group6->begin(); - place("do_lines", TOGGLE_DEACTIVATE); place("helical_nr_asu", TOGGLE_DEACTIVATE); place("helical_rise", TOGGLE_DEACTIVATE); - group6->end(); - - guientries["do_extract_helical_tubes"].cb_menu_i(); - group5->end(); guientries["do_extract_helix"].cb_menu_i(); diff --git a/src/manualpicker.cpp b/src/manualpicker.cpp index a666cc497..676651342 100644 --- a/src/manualpicker.cpp +++ b/src/manualpicker.cpp @@ -475,7 +475,8 @@ void manualpickerGuiWindow::writeOutputStarfiles(bool verb) if (!do_allow_save) return; MDcoords.clear(); - MetaDataTable MDmics, MDselect; + MDcoords.setName("coordinate_files"); + MetaDataTable MDmics, MDselect; int c = 0; for (int imic = 0; imic < selected.size(); imic++) { @@ -513,10 +514,23 @@ void manualpickerGuiWindow::writeOutputStarfiles(bool verb) FileName fn_select = global_fn_odir + "local_selection.star"; MDselect.write(fn_select); - FileName fn_coords = global_fn_odir + global_pickname + ".star"; - MDcoords.setName("coordinate_files"); - MDcoords.write(fn_coords); - if (verb) std::cout << " Saved list with " << c << " coordinate files in: " << fn_coords << std::endl; + + std::string type = "particles"; + if (global_pick_lines) type = "lines"; + else if (global_pick_startend) type = "startend"; + MetaDataTable MDhead; + MDhead.setName("general"); + MDhead.setIsList(true); + MDhead.addObject(); + MDhead.setValue(EMDL_MICROGRAPH_PICKTYPE, type); + + std::vector MDins; + MDins.push_back(MDhead); + MDins.push_back(MDcoords); + FileName fn_coords = global_fn_odir + global_pickname + ".star"; + writeMultipleTablesToStar(MDins, fn_coords); + + if (verb) std::cout << " Saved list with " << c << " coordinate files in: " << fn_coords << std::endl; } void manualpickerGuiWindow::cb_menubar_save(Fl_Widget* w, void* v) @@ -746,7 +760,40 @@ void ManualPicker::initialise() { if (fn_in.isStarFile()) { - // First try 2-column list of coordinate files as in relion-3.2+ + MetaDataTable MDhead; + if (MDhead.read(fn_in, "general")) + { + std::string picktype; + MDhead.getValue(EMDL_MICROGRAPH_PICKTYPE, picktype); + if (picktype == "particles") + { + if (global_pick_startend || global_pick_lines) + std::cerr << "WARNING: coordinate file states these are particles, ignoring --from_startend or --from_lines "<< std::endl; + global_pick_startend = global_pick_lines = false; + } + else if (picktype == "startend") + { + if (global_pick_lines) + std::cerr << "WARNING: coordinate file states these are startend, ignoring --from_lines "<< std::endl; + global_pick_startend = true; + global_pick_lines = false; + } + else if (picktype == "lines") + { + if (global_pick_startend) + std::cerr << "WARNING: coordinate file states these are lines, ignoring --from_startend "<< std::endl; + global_pick_startend = false; + global_pick_lines = true; + } + else + { + REPORT_ERROR("ERROR: unrecognised micrograph picktype from the general table of the coordinate file"); + } + + } + + + // First try 2-column list of coordinate files as in relion-3.2+ MDin.read(fn_in, "coordinate_files"); if (MDin.numberOfObjects() > 0) { diff --git a/src/metadata_label.h b/src/metadata_label.h index cc89bae0f..9168c4ff7 100644 --- a/src/metadata_label.h +++ b/src/metadata_label.h @@ -272,6 +272,7 @@ enum EMDLabel EMDL_MICROGRAPH_SHIFT_Y, EMDL_MICROGRAPH_AUTOPICK_FOM, EMDL_MICROGRAPH_AUTOPICK_PSI, + EMDL_MICROGRAPH_PICKTYPE, EMDL_MICROGRAPH_SCORE_KURTOSIS, EMDL_MICROGRAPH_SCORE_SKEWNESS, EMDL_MICROGRAPH_MOTION_COEFFS_IDX, @@ -1010,6 +1011,7 @@ class StaticInitialization EMDL::addLabel(EMDL_MICROGRAPH_SHIFT_Y, EMDL_DOUBLE, "rlnMicrographShiftY", "Y shift of a (patch of) micrograph"); EMDL::addLabel(EMDL_MICROGRAPH_AUTOPICK_FOM, EMDL_STRING, "rlnMicrographFomImage", "Filename of the FOM image of micrograph autopicking "); EMDL::addLabel(EMDL_MICROGRAPH_AUTOPICK_PSI, EMDL_STRING, "rlnMicrographPsiImage", "Filename of the PSI image of micrograph autopicking"); + EMDL::addLabel(EMDL_MICROGRAPH_PICKTYPE, EMDL_STRING, "rlnMicrographPickType", "Type of micrograph coordinates (particles, startend or lines)"); EMDL::addLabel(EMDL_MICROGRAPH_SCORE_KURTOSIS, EMDL_DOUBLE, "rlnMicrographScoreKurtosis", "(Excess) kurtosis in micrograph autopick FOM scores"); EMDL::addLabel(EMDL_MICROGRAPH_SCORE_SKEWNESS, EMDL_DOUBLE, "rlnMicrographScoreSkewness", "Skewness in micrograph autopick FOM scores"); EMDL::addLabel(EMDL_MICROGRAPH_MOTION_COEFFS_IDX, EMDL_INT, "rlnMotionModelCoeffsIdx", "Index of a coefficient of a motion model"); diff --git a/src/metadata_table.cpp b/src/metadata_table.cpp index 7bb1ae740..254af063f 100644 --- a/src/metadata_table.cpp +++ b/src/metadata_table.cpp @@ -2214,4 +2214,28 @@ MetaDataTable removeDuplicatedParticles(MetaDataTable &MDin, EMDLabel mic_label, return MDout; } +void writeMultipleTablesToStar(std::vector &MDins, FileName fn_out) +{ + + // Write to temporary file first + FileName fn_tmp = fn_out + ".tmp"; + + std::ofstream fh; + fh.open((fn_tmp).c_str(), std::ios::out); + if (!fh) + REPORT_ERROR( (std::string)"MetaDataTable::write: cannot write to file: " + fn_tmp); + + // Write all the tables + for (int i = 0; i < MDins.size(); i++) + { + MDins[i].write(fh); + } + + fh.close(); + + // Rename to prevent errors with programs in pipeliner reading in incomplete STAR files + std::rename(fn_tmp.c_str(), fn_out.c_str()); + + +} diff --git a/src/metadata_table.h b/src/metadata_table.h index 78c94aa26..c528756e6 100644 --- a/src/metadata_table.h +++ b/src/metadata_table.h @@ -390,6 +390,9 @@ MetaDataTable subsetMetaDataTable(MetaDataTable &MDin, EMDLabel label, std::stri // OriginX/Y are multiplied by origin_scale before added to CoordinateX/Y to compensate for down-sampling MetaDataTable removeDuplicatedParticles(MetaDataTable &MDin, EMDLabel mic_label, RFLOAT threshold, RFLOAT origin_scale=1.0, FileName fn_removed="", bool verb=true); +// Write multiple tables into a star file, with writing to .tmp file for file completion checking in pipeliner +void writeMultipleTablesToStar(std::vector &MDins, FileName fn_out); + // This flag should be enabled via "cmake -DMDT_TYPE_CHECK=ON" #ifdef METADATA_TABLE_TYPE_CHECK //#pragma message("typecheck enabled") diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index a9bd4a3c9..dd8beb012 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2055,7 +2055,6 @@ Helical tubes with shorter lengths will not be picked. Note that a long helical joboptions["do_amyloid_tracing"] = JobOption("Trace amyloids in FOM images? ", true, "Trace amyloids (as lines) in the FOM images? Note this code runs only most efficiently on the GPU."); joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 0.25, 0.05, 1, 0.05, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); joboptions["do_amyloid_plot"] = JobOption("Plot results per micrograph?", false, "Set this option to Yes to visualise intermediate results for amyloid tracing, which may help with setting values for filament length, width and picking threshold. Don't use in parallel or in submission to the queue."); - joboptions["redo_all_tracing"] = JobOption("Redo tracing for all micrographs?", false, "Set this option to Yes to redo the tracing for all micrographs (and overwrite any existing output coordinate star files that may exist). If set to No, then only micrographs for which the star files don't exist yet will be traced. This is useful for speeding up on-the-fly processing in continuation jobs."); } @@ -2323,7 +2322,6 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Thu, 22 May 2025 09:57:45 +0100 Subject: [PATCH 105/189] Compile model, bfloat16 for cuda --- scripts/trace_amyloids.in | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 2efebd624..9aa982c0b 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -390,6 +390,10 @@ def main(): input_star = starfile.read(args.input) + @torch.compile + def run_compiled_model(data): + return model(data) + # ------------------ Run Inference ---------------- iterator = tqdm(input_star.iterrows(), total=len(input_star), file=sys.stdout) if args.verb > 0 else input_star.iterrows() for i, row in iterator: @@ -413,11 +417,11 @@ def main(): with torch.no_grad(): if device.type == "cuda": - with torch.autocast(device_type=device.type, dtype=torch.float16): - seg_logits, embedding = model(in_tensor) + with torch.autocast(device_type=device.type, dtype=torch.bfloat16): + seg_logits, embedding = run_compiled_model(in_tensor) else: with torch.autocast(device_type="cpu", dtype=torch.bfloat16): - seg_logits, embedding = model(in_tensor) + seg_logits, embedding = run_compiled_model(in_tensor) foreground = torch.sigmoid(seg_logits).squeeze().detach().to(dtype=torch.float32).cpu().numpy() # 2) Clean + threshold => remove small objects From 662ea7c50cbe1cd02c7ff882afe844343898a76d Mon Sep 17 00:00:00 2001 From: jamaliki Date: Thu, 22 May 2025 10:12:06 +0100 Subject: [PATCH 106/189] Small optimisations --- scripts/trace_amyloids.in | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 9aa982c0b..9ef2caeaf 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -392,7 +392,9 @@ def main(): @torch.compile def run_compiled_model(data): - return model(data) + seg_logits, embedding = model(data) + seg_prob = torch.sigmoid(seg_logits.squeeze()) + return seg_prob # ------------------ Run Inference ---------------- iterator = tqdm(input_star.iterrows(), total=len(input_star), file=sys.stdout) if args.verb > 0 else input_star.iterrows() @@ -413,16 +415,16 @@ def main(): H, W = fom_image.shape in_tensor = np.stack((fom_image, psi_image), axis=0) # shape (2,H,W) - in_tensor = torch.from_numpy(in_tensor[None]).float().to(device) # shape (1,2,H,W) + in_tensor = torch.from_numpy(in_tensor[None]).to(device=device, dtype=torch.bfloat16) # shape (1,2,H,W) with torch.no_grad(): if device.type == "cuda": with torch.autocast(device_type=device.type, dtype=torch.bfloat16): - seg_logits, embedding = run_compiled_model(in_tensor) + seg_prob = run_compiled_model(in_tensor) else: with torch.autocast(device_type="cpu", dtype=torch.bfloat16): - seg_logits, embedding = run_compiled_model(in_tensor) - foreground = torch.sigmoid(seg_logits).squeeze().detach().to(dtype=torch.float32).cpu().numpy() + seg_prob = run_compiled_model(in_tensor) + foreground = seg_prob.to(dtype=torch.float32, device="cpu").numpy() # 2) Clean + threshold => remove small objects bin_foreground = (foreground > args.threshold) From 4e9483fb75b0384d76927010c57038e79d068899 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 22 May 2025 14:57:04 +0100 Subject: [PATCH 107/189] by default do not know polarity of filaments --- .../get_particle_poses/filaments.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tomography_python_programs/get_particle_poses/filaments.py b/src/tomography_python_programs/get_particle_poses/filaments.py index 01f599dc9..69839e5cb 100644 --- a/src/tomography_python_programs/get_particle_poses/filaments.py +++ b/src/tomography_python_programs/get_particle_poses/filaments.py @@ -28,8 +28,8 @@ def get_poses_along_filament_backbones( ..., help="spacing between particles along filaments in angstroms." ), filament_polarity_known: bool = typer.Option( - True, help="Whether filament polarity from annotations should be fixed " - "during refinement." + False, help="Whether filament polarity from annotations should be fixed " + "during refinement (i.e. filaments have been picked front->end)." ) ): global_df = starfile.read(tilt_series_star_file, From 6c45e9fc956ff45e81371ae47c2ef6aabb98465d Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 22 May 2025 17:59:59 +0100 Subject: [PATCH 108/189] for amyprep and amyproc schemes --- src/amyloid_finder.cpp | 44 ++++++++--- src/amyloid_finder_mpi.cpp | 4 +- src/apps/import.cpp | 12 ++- src/apps/star_handler.cpp | 3 +- src/autopicker.cpp | 4 +- src/manualpicker.cpp | 8 +- src/pipeline_jobs.cpp | 155 +++++++++++++++++++------------------ 7 files changed, 131 insertions(+), 99 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 639f46e1a..4780b1777 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -724,9 +724,6 @@ void AmyloidFinder::runFOMBatch(long int my_first, long int my_last) if (pipeline_control_check_abort_job()) exit(RELION_EXIT_ABORTED); - if (verb > 0 && imic % barstep == 0) - progress_bar(imic); - // Check new-style outputdirectory exists and make it if not! FileName fn_oroot = getOutputRootName(todo_micrographs_fom[imic]); FileName fn_dir = fn_oroot.beforeLastOf("/"); @@ -845,7 +842,6 @@ void AmyloidFinder::finalise() MetaDataTable MDcoords; MDcoords.setName("coordinate_files"); - MetaDataTable MDresult; long total_nr_picked = 0; int nr_coord_files = 0; for (long int imic = 0; imic < fn_ori_micrographs.size(); imic++) @@ -874,6 +870,12 @@ void AmyloidFinder::finalise() FileName fn_pick = fn_root + "_" + fn_out + ".star"; + MetaDataTable MD; + MD.read(fn_pick); + long nr_pick = MD.numberOfObjects(); + total_nr_picked += nr_pick; + MDin.setValue(EMDL_MLMODEL_GROUP_NR_PARTICLES, nr_pick, imic); + MDcoords.addObject(); MDcoords.setValue(EMDL_MICROGRAPH_NAME, fn_ori_micrographs[imic]); MDcoords.setValue(EMDL_MICROGRAPH_COORDINATES, fn_pick); @@ -888,6 +890,10 @@ void AmyloidFinder::finalise() if (verb > 0) progress_bar(fn_ori_micrographs.size()); + // Make histograms of skewness and kurtosis of FOM values for all micrographs + FileName fn_eps; + std::vector all_fn_eps; + std::vector histX, histY; if (!do_skip_fom) { @@ -896,11 +902,6 @@ void AmyloidFinder::finalise() obsModel.save(MDin, fn_mics, "micrographs"); if (verb > 0) std::cout << " Saved output micrograph STAR file with FOM images in: " << fn_mics << std::endl; - // Make histograms of skewness and kurtosis of FOM values for all micrographs - FileName fn_eps; - std::vector all_fn_eps; - std::vector histX, histY; - CPlot2D *plot2De=new CPlot2D("Skewness of FOM for all micrographs"); MDin.addToCPlot2D(plot2De, EMDL_UNDEFINED, EMDL_MICROGRAPH_SCORE_SKEWNESS, 1.); plot2De->SetDrawLegend(false); @@ -937,19 +938,37 @@ void AmyloidFinder::finalise() delete plot2Dh; } - joinMultipleEPSIntoSinglePDF(fn_odir + "logfile.pdf", all_fn_eps); } if (!do_skip_tracing) { + CPlot2D *plot2De=new CPlot2D("Nr of picked particles for all micrographs"); + MDin.addToCPlot2D(plot2De, EMDL_UNDEFINED, EMDL_MLMODEL_GROUP_NR_PARTICLES, 1.); + plot2De->SetDrawLegend(false); + fn_eps = fn_odir + "all_nrparts.eps"; + plot2De->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2De; + if (MDin.numberOfObjects() > 3) + { + CPlot2D *plot2Df=new CPlot2D(""); + MDin.columnHistogram(EMDL_MLMODEL_GROUP_NR_PARTICLES,histX,histY,0, plot2Df); + fn_eps = fn_odir + "histogram_nrparts.eps"; + plot2Df->SetTitle("Histogram of nr of picked particles per micrograph"); + plot2Df->OutputPostScriptPlot(fn_eps); + all_fn_eps.push_back(fn_eps); + delete plot2Df; + } + FileName fn_coords = fn_odir + fn_out + ".star"; MetaDataTable MDhead; - MDhead.setName("general"); MDhead.setIsList(true); + MDhead.setName("general"); MDhead.addObject(); - MDhead.setValue(EMDL_MICROGRAPH_PICKTYPE, "lines"); + std::string picktype = "lines"; + MDhead.setValue(EMDL_MICROGRAPH_PICKTYPE, picktype); std::vector MDins; MDins.push_back(MDhead); @@ -958,5 +977,6 @@ void AmyloidFinder::finalise() } + joinMultipleEPSIntoSinglePDF(fn_odir + "logfile.pdf", all_fn_eps); } \ No newline at end of file diff --git a/src/amyloid_finder_mpi.cpp b/src/amyloid_finder_mpi.cpp index 6a4477d08..18bb14a54 100644 --- a/src/amyloid_finder_mpi.cpp +++ b/src/amyloid_finder_mpi.cpp @@ -75,14 +75,14 @@ void AmyloidFinderMpi::run() // Each node does part of the work if (todo_micrographs_fom.size() > 0) { - long int my_first_fom, my_last_fom, my_nr_micrographs; + long int my_first_fom, my_last_fom; divide_equally(todo_micrographs_fom.size(), node->size, node->rank, my_first_fom, my_last_fom); runFOMBatch(my_first_fom, my_last_fom); } if (todo_micrographs_tracing.size() > 0) { - long int my_first_tracing, my_last_tracing, my_nr_micrographs; + long int my_first_tracing, my_last_tracing; divide_equally(todo_micrographs_tracing.size(), node->size, node->rank, my_first_tracing, my_last_tracing); runTracingBatch(my_first_tracing, my_last_tracing, node->rank); } diff --git a/src/apps/import.cpp b/src/apps/import.cpp index 954eab98d..e34d7d45f 100644 --- a/src/apps/import.cpp +++ b/src/apps/import.cpp @@ -30,6 +30,7 @@ class import_parameters bool do_write_types, do_continue, do_movies, do_micrographs, do_coordinates, do_halfmaps, do_particles, do_other; FileName optics_group_name, node_type, particles_optics_group_name; RFLOAT kV, Cs, Q0, beamtilt_x, beamtilt_y, pixel_size; + long int do_at_most; // I/O Parser IOParser parser; @@ -65,6 +66,7 @@ class import_parameters beamtilt_x = textToFloat(parser.getOption("--beamtilt_x", "Beam tilt (X; mrad)", "0.0")); beamtilt_y = textToFloat(parser.getOption("--beamtilt_y", "Beam tilt (Y; mrad)", "0.0")); do_continue = parser.checkOption("--continue", "Continue and old run, add more files to the same import directory"); + do_at_most = textToInteger(parser.getOption("--do_at_most", "Add at most this many new movies or micrographs", "-1")); // Check for errors in the command-line option if (parser.checkForErrors()) @@ -83,7 +85,8 @@ class import_parameters MetaDataTable MDout, MDopt; std::vector fns_in; long nr_input_files = fn_in.globFiles(fns_in); - std::ofstream fh; + long int nr_added = 0; + std::ofstream fh; int nr_count = 0; if (do_movies) nr_count++; @@ -96,6 +99,10 @@ class import_parameters } std::cout << " importing..." << std::endl; + if (do_at_most > 0) + { + std::cout << " - do at most " << do_at_most << " instances" << std::endl; + } // For micrographs or movies if (do_movies || do_micrographs) { @@ -182,11 +189,12 @@ class import_parameters break; } } - if (!already_there) + if (!already_there && !(do_at_most > 0 && nr_added >= do_at_most)) { MDout.addObject(); MDout.setValue(mylabel, fns_in[i]); MDout.setValue(EMDL_IMAGE_OPTICS_GROUP, optics_group_number); + nr_added++; } } diff --git a/src/apps/star_handler.cpp b/src/apps/star_handler.cpp index 41d1b3bd1..7d030a1d7 100644 --- a/src/apps/star_handler.cpp +++ b/src/apps/star_handler.cpp @@ -833,7 +833,8 @@ class star_handler_parameters fnt0 = integerToString(nr_split); for (int isplit = 0; isplit < nr_split; isplit ++) { - FileName fnt = fn_out.insertBeforeExtension("_split"+integerToString(isplit+1)); + FileName fnt; + fnt.compose(fn_out.withoutExtension() + "_split",isplit+1, "star", 5); MDouts[isplit].setName(MD.getName()); write_check_ignore_optics(MDouts[isplit], fnt, MD.getName()); std::cout << " Written: " < MDins; MDins.push_back(MDhead); diff --git a/src/manualpicker.cpp b/src/manualpicker.cpp index 676651342..66e0de1b8 100644 --- a/src/manualpicker.cpp +++ b/src/manualpicker.cpp @@ -515,14 +515,14 @@ void manualpickerGuiWindow::writeOutputStarfiles(bool verb) MDselect.write(fn_select); - std::string type = "particles"; - if (global_pick_lines) type = "lines"; - else if (global_pick_startend) type = "startend"; + std::string picktype = "particles"; + if (global_pick_lines) picktype = "lines"; + else if (global_pick_startend) picktype = "startend"; MetaDataTable MDhead; MDhead.setName("general"); MDhead.setIsList(true); MDhead.addObject(); - MDhead.setValue(EMDL_MICROGRAPH_PICKTYPE, type); + MDhead.setValue(EMDL_MICROGRAPH_PICKTYPE, picktype); std::vector MDins; MDins.push_back(MDhead); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index dd8beb012..39bdbefcc 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -1443,6 +1443,9 @@ bool RelionJob::getCommandsImportJob(std::string &outputname, std::vector 1) + command="`which relion_find_amyloid_mpi`"; + else + command="`which relion_find_amyloid`"; + if (error_message != "") return false; + + if (joboptions["do_amyloid_plot"].getBoolean()) + { + if (joboptions["nr_mpi"].getNumber(error_message) > 1 || joboptions["do_queue"].getBoolean()) + { + error_message = "You cannot use parallel execution or job submission when plotting intermediate results."; + return false; + } + } + + command += " --odir " + outputname; + command += " --pickname autopick"; + + if (joboptions["do_amyloid_fom"].getBoolean()) + { + // Also output micrographs.star file with kurtosis and skewness of the autopicking scores + Node node3c(outputname + "micrographs_autopick.star", joboptions["fn_input_autopick"].node_type); + outputNodes.push_back(node3c); + + } + else + { + command += " --skip_fom "; + } + + + if (joboptions["do_amyloid_tracing"].getBoolean()) + { + // Output new version: no longer save coords_suffix nodetype, but 2-column list of micrographs and coordinate files + Node node3(outputname + "autopick.star", LABEL_AUTOPICK_COORDS); + outputNodes.push_back(node3); + + command += " --trace_filament_length " + joboptions["helical_tube_length_min"].getString(); + command += " --trace_filament_width " + joboptions["helical_tube_outer_diameter"].getString(); + command += " --threshold " + joboptions["amyloid_threshold"].getString(); + + if (joboptions["do_amyloid_plot"].getBoolean()) command += " --plot "; + + } + else + { + command += " --skip_tracing "; + } + + command += " --i " + joboptions["fn_input_autopick"].getString(); + Node node(joboptions["fn_input_autopick"].getString(), joboptions["fn_input_autopick"].node_type); + inputNodes.push_back(node); + + // PDF with histograms of the eigenvalues + Node node3b(outputname + "logfile.pdf", LABEL_AUTOPICK_LOG); + outputNodes.push_back(node3b); + + // GPU-stuff + if (joboptions["use_gpu"].getBoolean()) + { + // for the moment always use --shrink 0 with GPUs ... + command += " --gpu \"" + joboptions["gpu_ids"].getString() + "\""; + } + else + { + command += " --j " + joboptions["nr_threads"].getString(); + } + + + } + else { // Run autopicking @@ -2303,81 +2381,6 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector 1) - command="`which relion_find_amyloid_mpi`"; - else - command="`which relion_find_amyloid`"; - if (error_message != "") return false; - - if (joboptions["do_amyloid_plot"].getBoolean()) - { - if (joboptions["nr_mpi"].getNumber(error_message) > 1 || joboptions["do_queue"].getBoolean()) - { - error_message = "You cannot use parallel execution or job submission when plotting intermediate results."; - return false; - } - } - - command += " --odir " + outputname; - command += " --pickname autopick"; - - if (joboptions["do_amyloid_fom"].getBoolean()) - { - // Also output micrographs.star file with kurtosis and skewness of the autopicking scores - Node node3c(outputname + "micrographs_autopick.star", joboptions["fn_input_autopick"].node_type); - outputNodes.push_back(node3c); - - } - else - { - command += " --skip_fom "; - } - - - if (joboptions["do_amyloid_tracing"].getBoolean()) - { - // Output new version: no longer save coords_suffix nodetype, but 2-column list of micrographs and coordinate files - Node node3(outputname + "autopick.star", LABEL_AUTOPICK_COORDS); - outputNodes.push_back(node3); - - command += " --trace_filament_length " + joboptions["helical_tube_length_min"].getString(); - command += " --trace_filament_width " + joboptions["helical_tube_outer_diameter"].getString(); - command += " --threshold " + joboptions["amyloid_threshold"].getString(); - - if (joboptions["do_amyloid_plot"].getBoolean()) command += " --plot "; - - } - else - { - command += " --skip_tracing "; - } - - command += " --i " + joboptions["fn_input_autopick"].getString(); - Node node(joboptions["fn_input_autopick"].getString(), joboptions["fn_input_autopick"].node_type); - inputNodes.push_back(node); - - // PDF with histograms of the eigenvalues - Node node3b(outputname + "logfile.pdf", LABEL_AUTOPICK_LOG); - outputNodes.push_back(node3b); - - // GPU-stuff - if (joboptions["use_gpu"].getBoolean()) - { - // for the moment always use --shrink 0 with GPUs ... - command += " --gpu \"" + joboptions["gpu_ids"].getString() + "\""; - } - else - { - command += " --j " + joboptions["nr_threads"].getString(); - } - - - } else if (joboptions["do_refs"].getBoolean()) { if (joboptions["do_ref3d"].getBoolean()) From b365bd5116b4d393e9d7cf7b6234943ec5be056e Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 27 May 2025 08:59:50 +0100 Subject: [PATCH 109/189] repaired bug in setting picktype --- src/autopicker.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/autopicker.cpp b/src/autopicker.cpp index 4f07130fb..f1c2fe467 100644 --- a/src/autopicker.cpp +++ b/src/autopicker.cpp @@ -1088,7 +1088,8 @@ void AutoPicker::generatePDFLogfile() MDhead.setName("general"); MDhead.setIsList(true); MDhead.addObject(); - std::string picktype = (autopick_helical_segments) ? "particles" : "startend"; + std::string picktype("particles"); + if (autopick_helical_segments) picktype = "startend"; MDhead.setValue(EMDL_MICROGRAPH_PICKTYPE, picktype); std::vector MDins; From b04051b07882cd6f4bc162f9ea33657e86de3f88 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 27 May 2025 09:00:12 +0100 Subject: [PATCH 110/189] repaired bug in setting picktype --- src/manualpicker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manualpicker.cpp b/src/manualpicker.cpp index 66e0de1b8..c3e21c39d 100644 --- a/src/manualpicker.cpp +++ b/src/manualpicker.cpp @@ -515,7 +515,7 @@ void manualpickerGuiWindow::writeOutputStarfiles(bool verb) MDselect.write(fn_select); - std::string picktype = "particles"; + std::string picktype("particles"); if (global_pick_lines) picktype = "lines"; else if (global_pick_startend) picktype = "startend"; MetaDataTable MDhead; From c520fcccb76966ac6139a1e5f81ddb25c9f0ef84 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 28 May 2025 12:12:12 +0100 Subject: [PATCH 111/189] code suggestion Kent Thurber --- src/helix.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/helix.cpp b/src/helix.cpp index 0cbb85e56..62fe7952b 100644 --- a/src/helix.cpp +++ b/src/helix.cpp @@ -4142,15 +4142,24 @@ void updatePriorsForOneHelicalTube( nr_wrong_polarity = nr_opposite_polarity - 1; // Change the polarity of the entire helix if psi_flip_ratio is larger than 0.5 - if (psi_flip_ratio > 0.5) - { - for (int id = sid; id <= eid; id++) - { - flipPsiTiltForHelicalSegment(list[id].psi_prior_deg, list[id].tilt_prior_deg, - list[id].psi_prior_deg, list[id].tilt_prior_deg); - list[id].psi_flip_ratio = (1. - psi_flip_ratio); - } - } + if (psi_flip_ratio > 0.5) + { + for (int id = sid; id <= eid; id++) + { + flipPsiTiltForHelicalSegment(list[id].psi_prior_deg, list[id].tilt_prior_deg, + list[id].psi_prior_deg, list[id].tilt_prior_deg); + flipPsiTiltForHelicalSegment(list[id].psi_deg, list[id].tilt_deg, + list[id].psi_deg, list[id].tilt_deg); + list[id].psi_flip_ratio = (1. - psi_flip_ratio); + } + } + else + { + for (int id = sid; id <= eid; id++) + { + list[id].psi_flip_ratio = psi_flip_ratio; + } + } // Calculate new distance-averaged angular priors // SHWS 27042020: do two passes: one normal and one with opposite distances and find out which one is the best From d1c245f40139699910c67dfa681b53c06eb78987 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 28 May 2025 15:00:09 +0100 Subject: [PATCH 112/189] This implements pull request #1267 from 3dem/relion by Leandro Estrozi --- src/backprojector.cpp | 184 ++++++++++++++++++++---------------------- src/backprojector.h | 2 +- src/ml_optimiser.cpp | 4 +- src/reconstructor.cpp | 3 +- src/reconstructor.h | 2 +- 5 files changed, 93 insertions(+), 102 deletions(-) diff --git a/src/backprojector.cpp b/src/backprojector.cpp index e76ff72ad..8a0d2a77c 100644 --- a/src/backprojector.cpp +++ b/src/backprojector.cpp @@ -2140,7 +2140,7 @@ void BackProjector::symmetrise(int nr_helical_asu, RFLOAT helical_twist, RFLOAT enforceHermitianSymmetry(); // Then apply helical and point group symmetry (order irrelevant?) - applyHelicalSymmetry(nr_helical_asu, helical_twist, helical_rise); + applyHelicalSymmetry(nr_helical_asu, helical_twist, helical_rise, threads); applyPointGroupSymmetry(threads); } @@ -2164,7 +2164,7 @@ void BackProjector::enforceHermitianSymmetry() } } -void BackProjector::applyHelicalSymmetry(int nr_helical_asu, RFLOAT helical_twist, RFLOAT helical_rise) +void BackProjector::applyHelicalSymmetry(int nr_helical_asu, RFLOAT helical_twist, RFLOAT helical_rise, int threads) { if ( (nr_helical_asu < 2) || (ref_dim != 3) ) return; @@ -2172,110 +2172,94 @@ void BackProjector::applyHelicalSymmetry(int nr_helical_asu, RFLOAT helical_twis int rmax2 = ROUND(r_max * padding_factor) * ROUND(r_max * padding_factor); Matrix2D R(4, 4); // A matrix from the list - MultidimArray sum_weight; - MultidimArray sum_data; - RFLOAT x, y, z, fx, fy, fz, xp, yp, zp, r2; - bool is_neg_x; - int x0, x1, y0, y1, z0, z1; - Complex d000, d001, d010, d011, d100, d101, d110, d111; - Complex dx00, dx01, dx10, dx11, dxy0, dxy1, ddd; - RFLOAT dd000, dd001, dd010, dd011, dd100, dd101, dd110, dd111; - RFLOAT ddx00, ddx01, ddx10, ddx11, ddxy0, ddxy1; + MultidimArray sum_weight(weight); + MultidimArray sum_data(data); // First symmetry operator (not stored in SL) is the identity matrix - sum_weight = weight; - sum_data = data; int h_min = -nr_helical_asu/2; int h_max = -h_min + nr_helical_asu%2; for (int hh = h_min; hh < h_max; hh++) { - if (hh != 0) // h==0 is done before the for loop (where sum_data = data) + if (hh == 0) continue; // h==0 is done before the for loop (where sum_data = data) + + RFLOAT rot_ang = hh * (-helical_twist); + rotation3DMatrix(rot_ang, 'Z', R); + R.setSmallValuesToZero(); // TODO: invert rotation matrix? + + //Only the positive half of the z-range has to be calculated. + //The other half is calculated by the x<0 part of the x-loop. + #pragma omp parallel for schedule(dynamic) num_threads(threads) default(none) shared(data,weight,sum_data,sum_weight,rmax2,helical_twist,helical_rise,ori_size,padding_factor) firstprivate(hh,R) + for (long int k=STARTINGZ(sum_weight); k<=FINISHINGZ(sum_weight); k++) { - RFLOAT rot_ang = hh * (-helical_twist); - rotation3DMatrix(rot_ang, 'Z', R); - R.setSmallValuesToZero(); // TODO: invert rotation matrix? + // Allocate minimal 2D slices per thread instead of full 3D arrays + MultidimArray slice_weight(YSIZE(data), XSIZE(data)); + MultidimArray slice_data(YSIZE(data), XSIZE(data)); + slice_weight.xdim = sum_weight.xdim; + slice_weight.ydim = sum_weight.ydim; + slice_weight.yxdim = sum_weight.yxdim; + slice_weight.xinit = sum_weight.xinit; + slice_weight.yinit = sum_weight.yinit; + slice_data.xdim = sum_data.xdim; + slice_data.ydim = sum_data.ydim; + slice_data.yxdim = sum_data.yxdim; + slice_data.xinit = sum_data.xinit; + slice_data.yinit = sum_data.yinit; + + slice_weight.initZeros(weight.ydim,weight.xdim); + slice_data.initZeros(data.ydim,data.xdim); - // Loop over all points in the output (i.e. rotated, or summed) array - FOR_ALL_ELEMENTS_IN_ARRAY3D(sum_weight) + for (long int i=STARTINGY(sum_weight); i<=FINISHINGY(sum_weight); i++) + for (long int j=STARTINGX(sum_weight); j<=FINISHINGX(sum_weight); j++) { - x = (RFLOAT)j; // STARTINGX(sum_weight) is zero! - y = (RFLOAT)i; - z = (RFLOAT)k; - r2 = x*x + y*y + z*z; + RFLOAT x = (RFLOAT)j; // STARTINGX(sum_weight) is zero! + RFLOAT y = (RFLOAT)i; + RFLOAT z = (RFLOAT)k; + RFLOAT r2 = x*x + y*y + z*z; + if (r2 <= rmax2) { // coords_output(x,y) = A * coords_input (xp,yp) - xp = x * R(0, 0) + y * R(0, 1) + z * R(0, 2); - yp = x * R(1, 0) + y * R(1, 1) + z * R(1, 2); - zp = x * R(2, 0) + y * R(2, 1) + z * R(2, 2); + RFLOAT xp = x * R(0, 0) + y * R(0, 1); + RFLOAT yp = x * R(1, 0) + y * R(1, 1); + RFLOAT zp = z; // Z remains unchanged // Only asymmetric half is stored - if (xp < 0) + bool is_neg_x = xp < 0; + if (is_neg_x) { - // Get complex conjugated hermitian symmetry pair xp = -xp; yp = -yp; zp = -zp; - is_neg_x = true; - } - else - { - is_neg_x = false; } // Trilinear interpolation (with physical coords) // Subtract STARTINGY and STARTINGZ to accelerate access to data (STARTINGX=0) // In that way use DIRECT_A3D_ELEM, rather than A3D_ELEM - x0 = FLOOR(xp); - fx = xp - x0; - x1 = x0 + 1; + int x0 = FLOOR(xp); + RFLOAT fx = xp - x0; + int x1 = x0 + 1; - y0 = FLOOR(yp); - fy = yp - y0; - y0 -= STARTINGY(data); - y1 = y0 + 1; + int y0 = FLOOR(yp); + RFLOAT fy = yp - y0; + y0 -= STARTINGY(data); + int y1 = y0 + 1; - z0 = FLOOR(zp); - fz = zp - z0; + int z0 = FLOOR(zp); z0 -= STARTINGZ(data); - z1 = z0 + 1; -#ifdef CHECK_SIZE - if (x0 < 0 || y0 < 0 || z0 < 0 || - x1 < 0 || y1 < 0 || z1 < 0 || - x0 >= XSIZE(data) || y0 >= YSIZE(data) || z0 >= ZSIZE(data) || - x1 >= XSIZE(data) || y1 >= YSIZE(data) || z1 >= ZSIZE(data) ) - { - std::cerr << " x0= " << x0 << " y0= " << y0 << " z0= " << z0 << std::endl; - std::cerr << " x1= " << x1 << " y1= " << y1 << " z1= " << z1 << std::endl; - data.printShape(); - REPORT_ERROR("BackProjector::applyPointGroupSymmetry: checksize!!!"); - } -#endif // First interpolate (complex) data - d000 = DIRECT_A3D_ELEM(data, z0, y0, x0); - d001 = DIRECT_A3D_ELEM(data, z0, y0, x1); - d010 = DIRECT_A3D_ELEM(data, z0, y1, x0); - d011 = DIRECT_A3D_ELEM(data, z0, y1, x1); - d100 = DIRECT_A3D_ELEM(data, z1, y0, x0); - d101 = DIRECT_A3D_ELEM(data, z1, y0, x1); - d110 = DIRECT_A3D_ELEM(data, z1, y1, x0); - d111 = DIRECT_A3D_ELEM(data, z1, y1, x1); - - dx00 = LIN_INTERP(fx, d000, d001); - dx01 = LIN_INTERP(fx, d100, d101); - dx10 = LIN_INTERP(fx, d010, d011); - dx11 = LIN_INTERP(fx, d110, d111); - dxy0 = LIN_INTERP(fy, dx00, dx10); - dxy1 = LIN_INTERP(fy, dx01, dx11); + Complex d00 = DIRECT_A3D_ELEM(data, z0, y0, x0); + Complex d01 = DIRECT_A3D_ELEM(data, z0, y0, x1); + Complex d10 = DIRECT_A3D_ELEM(data, z0, y1, x0); + Complex d11 = DIRECT_A3D_ELEM(data, z0, y1, x1); + Complex dx00 = LIN_INTERP(fx ,d00, d01); + Complex dx10 = LIN_INTERP(fx ,d10, d11); // Take complex conjugated for half with negative x - ddd = LIN_INTERP(fz, dxy0, dxy1); + Complex ddd = LIN_INTERP(fy, dx00, dx10); - if (is_neg_x) - ddd = conj(ddd); + if (is_neg_x) ddd = conj(ddd); - // Also apply a phase shift for helical translation along Z if (ABS(helical_rise) > 0.) { RFLOAT zshift = hh * helical_rise; @@ -2291,31 +2275,37 @@ void BackProjector::applyHelicalSymmetry(int nr_helical_asu, RFLOAT helical_twis ddd = Complex(ac - bd, ab_cd - ac - bd); } // Accumulated sum of the data term - A3D_ELEM(sum_data, k, i, j) += ddd; + A2D_ELEM(slice_data,i,j) += ddd; // Then interpolate (real) weight - dd000 = DIRECT_A3D_ELEM(weight, z0, y0, x0); - dd001 = DIRECT_A3D_ELEM(weight, z0, y0, x1); - dd010 = DIRECT_A3D_ELEM(weight, z0, y1, x0); - dd011 = DIRECT_A3D_ELEM(weight, z0, y1, x1); - dd100 = DIRECT_A3D_ELEM(weight, z1, y0, x0); - dd101 = DIRECT_A3D_ELEM(weight, z1, y0, x1); - dd110 = DIRECT_A3D_ELEM(weight, z1, y1, x0); - dd111 = DIRECT_A3D_ELEM(weight, z1, y1, x1); - - ddx00 = LIN_INTERP(fx, dd000, dd001); - ddx01 = LIN_INTERP(fx, dd100, dd101); - ddx10 = LIN_INTERP(fx, dd010, dd011); - ddx11 = LIN_INTERP(fx, dd110, dd111); - ddxy0 = LIN_INTERP(fy, ddx00, ddx10); - ddxy1 = LIN_INTERP(fy, ddx01, ddx11); - - A3D_ELEM(sum_weight, k, i, j) += LIN_INTERP(fz, ddxy0, ddxy1); - - } // end if r2 <= rmax2 - } // end loop over all elements of sum_weight - } // end if hh!=0 - } // end loop over hh + RFLOAT dd00 = DIRECT_A3D_ELEM(weight, z0, y0, x0); + RFLOAT dd01 = DIRECT_A3D_ELEM(weight, z0, y0, x1); + RFLOAT dd10 = DIRECT_A3D_ELEM(weight, z0, y1, x0); + RFLOAT dd11 = DIRECT_A3D_ELEM(weight, z0, y1, x1); + RFLOAT ddx00 = LIN_INTERP(fx, dd00, dd01); + RFLOAT ddx10 = LIN_INTERP(fx, dd10, dd11); + + A2D_ELEM(slice_weight,i,j) += LIN_INTERP(fy, ddx00, ddx10); + } + } + #pragma omp critical + { + for (long int i=STARTINGY(sum_weight); i<=FINISHINGY(sum_weight); i++) + for (long int j=STARTINGX(sum_weight); j<=FINISHINGX(sum_weight); j++) + { + RFLOAT x = (RFLOAT)j; // STARTINGX(sum_weight) is zero! + RFLOAT y = (RFLOAT)i; + RFLOAT z = (RFLOAT)k; + RFLOAT r2 = x*x + y*y + z*z; + if (r2 <= rmax2) + { + A3D_ELEM(sum_data,k,i,j) += A2D_ELEM(slice_data,i,j); + A3D_ELEM(sum_weight,k,i,j) += A2D_ELEM(slice_weight,i,j); + } + } + } + } + } data = sum_data; weight = sum_weight; diff --git a/src/backprojector.h b/src/backprojector.h index 2933274f6..48b6d97f4 100644 --- a/src/backprojector.h +++ b/src/backprojector.h @@ -372,7 +372,7 @@ class BackProjector: public Projector /* Applies helical symmetry. Note that helical_rise is in PIXELS here, as BackProjector doesn't know angpix */ - void applyHelicalSymmetry(int nr_helical_asu = 1, RFLOAT helical_twist = 0., RFLOAT helical_rise = 0.); + void applyHelicalSymmetry(int nr_helical_asu = 1, RFLOAT helical_twist = 0., RFLOAT helical_rise = 0., int threads = 1); /* Applies the symmetry from the SymList object to the weight and the data array */ diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 1a1043732..9904edd8e 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -4763,7 +4763,7 @@ void MlOptimiser::symmetriseReconstructions() wsum_model.BPref[ith_recons].applyHelicalSymmetry( mymodel.helical_nr_asu, mymodel.helical_twist[ith_recons], - mymodel.helical_rise[ith_recons] / mymodel.pixel_size); + mymodel.helical_rise[ith_recons] / mymodel.pixel_size, nr_threads); if (fn_multi_sym.size() > ith_recons) // Always false if size=0 { @@ -4787,7 +4787,7 @@ void MlOptimiser::symmetriseReconstructions() wsum_model.BPref[iclass_half].applyHelicalSymmetry( mymodel.helical_nr_asu, mymodel.helical_twist[ith_recons], - mymodel.helical_rise[ith_recons] / mymodel.pixel_size); + mymodel.helical_rise[ith_recons] / mymodel.pixel_size, nr_threads); if (fn_multi_sym.size() > ith_recons) // Always false if size=0 { diff --git a/src/reconstructor.cpp b/src/reconstructor.cpp index dacb8d87f..f21eedcfd 100644 --- a/src/reconstructor.cpp +++ b/src/reconstructor.cpp @@ -90,6 +90,7 @@ void Reconstructor::read(int argc, char **argv) do_debug = parser.checkOption("--write_debug_output", "Write out arrays with data and weight terms prior to reconstruct"); do_external_reconstruct = parser.checkOption("--external_reconstruct", "Write out BP denominator and numerator for external_reconstruct program"); verb = textToInteger(parser.getOption("--verb", "Verbosity", "1")); + nr_threads = textToInteger(parser.getOption("--j", "Number of threads (only for symmetrisation for now)", "1")); // Hidden r_min_nn = textToInteger(getParameter(argc, argv, "--r_min_nn", "10")); @@ -768,7 +769,7 @@ void Reconstructor::reconstruct() if (verb > 0) std::cout << " + Starting the reconstruction ..." << std::endl; - backprojector.symmetrise(nr_helical_asu, helical_twist, helical_rise/angpix); + backprojector.symmetrise(nr_helical_asu, helical_twist, helical_rise/angpix, nr_threads); if (do_reconstruct_ctf) { diff --git a/src/reconstructor.h b/src/reconstructor.h index fb3aa426c..a06890db2 100644 --- a/src/reconstructor.h +++ b/src/reconstructor.h @@ -45,7 +45,7 @@ class Reconstructor int r_max, r_min_nn, blob_order, ref_dim, interpolator, iter, debug_ori_size, debug_size, ctf_dim, nr_helical_asu, newbox, width_mask_edge, nr_sectors, subset, chosen_class, - data_dim, output_boxsize, verb; + data_dim, output_boxsize, verb, nr_threads; RFLOAT blob_radius, blob_alpha, angular_error, shift_error, angpix, maxres, helical_rise, helical_twist; From 8e5128ad0397c74370411ec5cef0646369902ac6 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 30 May 2025 17:21:24 +0100 Subject: [PATCH 113/189] put nstart on gui for local averaging --- src/gui_jobwindow.cpp | 16 +----- src/pipeline_jobs.cpp | 126 +++++++++++++++++++++--------------------- 2 files changed, 65 insertions(+), 77 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index cb323681b..351200ba4 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -1614,21 +1614,15 @@ void JobWindow::initialiseClass3DWindow() group5->begin(); place2("helical_tube_inner_diameter", "helical_tube_outer_diameter", "Tube diameter - inner, outer (A):", TOGGLE_DEACTIVATE); place3("range_rot", "range_tilt", "range_psi", "Angular search range - rot, tilt, psi (deg):", TOGGLE_DEACTIVATE); - place("helical_range_distance", TOGGLE_DEACTIVATE); + place2("helical_range_distance", "helical_nstart", "Local averaging - range (box), N-start:", TOGGLE_DEACTIVATE); place("keep_tilt_prior_fixed", TOGGLE_DEACTIVATE); // Add a little spacer current_y += STEPY/2; - group8 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); - group8->end(); - place("do_apply_helical_symmetry", TOGGLE_DEACTIVATE, group8); - group8->begin(); place("helical_nr_asu", TOGGLE_DEACTIVATE); place2("helical_twist_initial", "helical_rise_initial", "Initial twist (deg), rise (A):", TOGGLE_DEACTIVATE); place("helical_z_percentage", TOGGLE_DEACTIVATE); - group8->end(); - guientries["do_apply_helical_symmetry"].cb_menu_i(); // to make default effective // Add a little spacer current_y += STEPY/2; @@ -1776,21 +1770,15 @@ void JobWindow::initialiseAutorefineWindow() group2->begin(); place2("helical_tube_inner_diameter", "helical_tube_outer_diameter", "Tube diameter - inner, outer (A):",TOGGLE_DEACTIVATE); place3("range_rot", "range_tilt", "range_psi", "Angular search range - rot, tilt, psi (deg):", TOGGLE_DEACTIVATE); - place("helical_range_distance", TOGGLE_DEACTIVATE); + place2("helical_range_distance", "helical_nstart", "Local averaging - range (box), N-start:", TOGGLE_DEACTIVATE); place("keep_tilt_prior_fixed", TOGGLE_DEACTIVATE); // Add a little spacer current_y += STEPY/2; - group5 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); - group5->end(); - place("do_apply_helical_symmetry", TOGGLE_DEACTIVATE, group5); - group5->begin(); place("helical_nr_asu", TOGGLE_DEACTIVATE); place2("helical_twist_initial", "helical_rise_initial", "Initial twist (deg), rise (A):",TOGGLE_DEACTIVATE); place("helical_z_percentage", TOGGLE_DEACTIVATE); - group5->end(); - guientries["do_apply_helical_symmetry"].cb_menu_i(); // to make default effective // Add a little spacer current_y += STEPY/2; diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 39bdbefcc..57c97f696 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -3835,7 +3835,6 @@ in the previous iteration will get higher weights than those further away.\n\nTh rot, tilt and psi angles in the first few iterations (global searches for orientations) in 3D helical reconstruction. \ Values of 9 or 15 degrees are commonly used. Higher values are recommended for more flexible structures and more memory and computation time will be used. \ A range of 15 degrees means sigma = 5 degrees.\n\nThese options will be invalid if you choose to perform local angular searches or not to perform image alignment on 'Sampling' tab."); - joboptions["do_apply_helical_symmetry"] = JobOption("Apply helical symmetry?", true, "If set to Yes, helical symmetry will be applied in every iteration. Set to No if you have just started a project, helical symmetry is unknown or not yet estimated."); joboptions["helical_nr_asu"] = JobOption("Number of unique asymmetrical units:", 1, 1, 100, 1, "Number of unique helical asymmetrical units in each segment box. If the inter-box distance (set in segment picking step) \ is 100 Angstroms and the estimated helical rise is ~20 Angstroms, then set this value to 100 / 20 = 5 (nearest integer). This integer should not be less than 1. The correct value is essential in measuring the \ signal to noise ratio in helical reconstruction."); @@ -3866,8 +3865,10 @@ does not guarantee convergence. The program cannot find a reasonable symmetry if joboptions["helical_rise_inistep"] = JobOption("Helical rise search (A) - Step:", std::string("0"), "Minimum, maximum and initial step for helical rise search. Helical rise is a positive value in Angstroms. \ Generally it is not necessary for the user to provide an initial step (less than 1% the initial helical rise, 5~1000 samplings as default). But it needs to be set manually if the default value \ does not guarantee convergence. The program cannot find a reasonable symmetry if the true helical parameters fall out of the given ranges. Note that the final reconstruction can still converge if wrong helical and point group symmetry are provided."); - joboptions["helical_range_distance"] = JobOption("Range factor of local averaging:", -1., 1., 5., 0.1, "Local averaging of orientations and translations will be performed within a range of +/- this value * the box size. Polarities are also set to be the same for segments coming from the same tube during local refinement. \ -Values of ~ 2.0 are recommended for flexible structures such as MAVS-CARD filaments, ParM, MamK, etc. This option might not improve the reconstructions of helices formed from curled 2D lattices (TMV and VipA/VipB). Set to negative to disable this option."); + joboptions["helical_range_distance"] = JobOption("Local averaging - range (box)", std::string("-1"), "Local averaging of orientations and translations will be performed within a range of +/- this value * the box size. This also requires providing the N-start number of the helical symmetry (e.g. 2 for tau PHFs and 1 for tau SFs). Polarities are also set to be the same for segments coming from the same tube during local refinement. \ +Values of ~ 2.0 are recommended for the range. Set the range to negative to disable local averaging (in which case the Nstart number is also ignored)."); + joboptions["helical_nstart"] = JobOption("Local averaging - N-start symmetry", std::string("1"), "Local averaging of orientations and translations will be performed within a range of +/- this value * the box size. This also requires providing the N-start number of the helical symmetry (e.g. 2 for tau PHFs and 1 for tau SFs). Polarities are also set to be the same for segments coming from the same tube during local refinement. \ +Values of ~ 2.0 are recommended for the range. Set the range to negative to disable local averaging (in which case the Nstart number is also ignored)."); joboptions["keep_tilt_prior_fixed"] = JobOption("Keep tilt-prior fixed:", true, "If set to yes, the tilt prior will not change during the optimisation. If set to No, at each iteration the tilt prior will move to the optimal tilt value for that segment from the previous iteration."); joboptions["do_parallel_discio"] = JobOption("Use parallel disc I/O?", true, "If set to Yes, all MPI followers will read their own images from disc. \ @@ -4109,38 +4110,33 @@ bool RelionJob::getCommandsClass3DJob(std::string &outputname, std::vector 0.) - command += " --helical_twist_inistep " + joboptions["helical_twist_inistep"].getString(); + float twist_inistep = joboptions["helical_twist_inistep"].getNumber(error_message); + if (error_message != "") return false; + if (twist_inistep > 0.) + command += " --helical_twist_inistep " + joboptions["helical_twist_inistep"].getString(); - command += " --helical_rise_min " + joboptions["helical_rise_min"].getString(); - command += " --helical_rise_max " + joboptions["helical_rise_max"].getString(); + command += " --helical_rise_min " + joboptions["helical_rise_min"].getString(); + command += " --helical_rise_max " + joboptions["helical_rise_max"].getString(); - float rise_inistep = joboptions["helical_rise_inistep"].getNumber(error_message); - if (error_message != "") return false; - if (rise_inistep > 0.) - command += " --helical_rise_inistep " + joboptions["helical_rise_inistep"].getString(); - } - } - else - command += " --ignore_helical_symmetry"; + float rise_inistep = joboptions["helical_rise_inistep"].getNumber(error_message); + if (error_message != "") return false; + if (rise_inistep > 0.) + command += " --helical_rise_inistep " + joboptions["helical_rise_inistep"].getString(); + } if (joboptions["keep_tilt_prior_fixed"].getBoolean()) command += " --helical_keep_tilt_prior_fixed"; if ( (joboptions["dont_skip_align"].getBoolean()) && (!joboptions["do_local_ang_searches"].getBoolean()) ) @@ -4166,7 +4162,11 @@ bool RelionJob::getCommandsClass3DJob(std::string &outputname, std::vector 0.) + { command += " --helical_sigma_distance " + floatToString(val / 3.); + int nstart = joboptions["helical_nstart"].getNumber(error_message); + command += " --helical_nstart " + floatToString(nstart); + } } } @@ -4323,7 +4323,6 @@ in the previous iteration will get higher weights than those further away.\n\nTh rot, tilt and psi angles in the first few iterations (global searches for orientations) in 3D helical reconstruction. \ Values of 9 or 15 degrees are commonly used. Higher values are recommended for more flexible structures and more memory and computation time will be used. \ A range of 15 degrees means sigma = 5 degrees.\n\nThese options will be invalid if you choose to perform local angular searches or not to perform image alignment on 'Sampling' tab."); - joboptions["do_apply_helical_symmetry"] = JobOption("Apply helical symmetry?", true, "If set to Yes, helical symmetry will be applied in every iteration. Set to No if you have just started a project, helical symmetry is unknown or not yet estimated."); joboptions["helical_nr_asu"] = JobOption("Number of unique asymmetrical units:", 1, 1, 100, 1, "Number of unique helical asymmetrical units in each segment box. If the inter-box distance (set in segment picking step) \ is 100 Angstroms and the estimated helical rise is ~20 Angstroms, then set this value to 100 / 20 = 5 (nearest integer). This integer should not be less than 1. The correct value is essential in measuring the \ signal to noise ratio in helical reconstruction."); @@ -4354,8 +4353,10 @@ does not guarantee convergence. The program cannot find a reasonable symmetry if joboptions["helical_rise_inistep"] = JobOption("Helical rise search (A) - Step:", std::string("0"), "Minimum, maximum and initial step for helical rise search. Helical rise is a positive value in Angstroms. \ Generally it is not necessary for the user to provide an initial step (less than 1% the initial helical rise, 5~1000 samplings as default). But it needs to be set manually if the default value \ does not guarantee convergence. The program cannot find a reasonable symmetry if the true helical parameters fall out of the given ranges. Note that the final reconstruction can still converge if wrong helical and point group symmetry are provided."); - joboptions["helical_range_distance"] = JobOption("Range factor of local averaging:", -1., 1., 5., 0.1, "Local averaging of orientations and translations will be performed within a range of +/- this value * the box size. Polarities are also set to be the same for segments coming from the same tube during local refinement. \ -Values of ~ 2.0 are recommended for flexible structures such as MAVS-CARD filaments, ParM, MamK, etc. This option might not improve the reconstructions of helices formed from curled 2D lattices (TMV and VipA/VipB). Set to negative to disable this option."); + joboptions["helical_range_distance"] = JobOption("Local averaging - range (box)", std::string("-1"), "Local averaging of orientations and translations will be performed within a range of +/- this value * the box size. This also requires providing the N-start number of the helical symmetry (e.g. 2 for tau PHFs and 1 for tau SFs). Polarities are also set to be the same for segments coming from the same tube during local refinement. \ +Values of ~ 2.0 are recommended for the range. Set the range to negative to disable local averaging (in which case the Nstart number is also ignored)."); + joboptions["helical_nstart"] = JobOption("Local averaging - N-start symmetry", std::string("1"), "Local averaging of orientations and translations will be performed within a range of +/- this value * the box size. This also requires providing the N-start number of the helical symmetry (e.g. 2 for tau PHFs and 1 for tau SFs). Polarities are also set to be the same for segments coming from the same tube during local refinement. \ +Values of ~ 2.0 are recommended for the range. Set the range to negative to disable local averaging (in which case the Nstart number is also ignored)."); joboptions["keep_tilt_prior_fixed"] = JobOption("Keep tilt-prior fixed:", true, "If set to yes, the tilt prior will not change during the optimisation. If set to No, at each iteration the tilt prior will move to the optimal tilt value for that segment from the previous iteration."); joboptions["do_parallel_discio"] = JobOption("Use parallel disc I/O?", true, "If set to Yes, all MPI followers will read their own images from disc. \ @@ -4591,38 +4592,33 @@ bool RelionJob::getCommandsAutorefineJob(std::string &outputname, std::vector 0.) - command += " --helical_twist_inistep " + joboptions["helical_twist_inistep"].getString(); - - command += " --helical_rise_min " + joboptions["helical_rise_min"].getString(); - command += " --helical_rise_max " + joboptions["helical_rise_max"].getString(); - - float rise_inistep = joboptions["helical_rise_inistep"].getNumber(error_message); - if (error_message != "") return false; - if (rise_inistep > 0.) - command += " --helical_rise_inistep " + joboptions["helical_rise_inistep"].getString(); - } - } - else - command += " --ignore_helical_symmetry"; + if (joboptions["do_local_search_helical_symmetry"].getBoolean()) + { + command += " --helical_symmetry_search"; + command += " --helical_twist_min " + joboptions["helical_twist_min"].getString(); + command += " --helical_twist_max " + joboptions["helical_twist_max"].getString(); + + float twist_inistep = joboptions["helical_twist_inistep"].getNumber(error_message); + if (error_message != "") return false; + if (twist_inistep > 0.) + command += " --helical_twist_inistep " + joboptions["helical_twist_inistep"].getString(); + + command += " --helical_rise_min " + joboptions["helical_rise_min"].getString(); + command += " --helical_rise_max " + joboptions["helical_rise_max"].getString(); + + float rise_inistep = joboptions["helical_rise_inistep"].getNumber(error_message); + if (error_message != "") return false; + if (rise_inistep > 0.) + command += " --helical_rise_inistep " + joboptions["helical_rise_inistep"].getString(); + } float val; if (sampling != auto_local_sampling) @@ -4649,7 +4645,11 @@ bool RelionJob::getCommandsAutorefineJob(std::string &outputname, std::vector 0.) + { command += " --helical_sigma_distance " + floatToString(val / 3.); + int nstart = joboptions["helical_nstart"].getNumber(error_message); + command += " --helical_nstart " + floatToString(nstart); + } if (joboptions["keep_tilt_prior_fixed"].getBoolean()) command += " --helical_keep_tilt_prior_fixed"; From 8267c25e5bc9f7662826653aee94141afc51e58b Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 3 Jun 2025 16:43:53 +0100 Subject: [PATCH 114/189] forgot only_unfinished for tracing of amyloids# --- src/pipeline_jobs.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 57c97f696..b90b18d01 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2229,6 +2229,10 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Wed, 4 Jun 2025 09:04:15 +0100 Subject: [PATCH 115/189] check for availability of bfloat16 on older cards --- scripts/trace_amyloids.in | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 9ef2caeaf..cd0fad341 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -398,6 +398,7 @@ def main(): # ------------------ Run Inference ---------------- iterator = tqdm(input_star.iterrows(), total=len(input_star), file=sys.stdout) if args.verb > 0 else input_star.iterrows() + half_dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16 for i, row in iterator: if Path(args.abort).exists(): @@ -415,14 +416,14 @@ def main(): H, W = fom_image.shape in_tensor = np.stack((fom_image, psi_image), axis=0) # shape (2,H,W) - in_tensor = torch.from_numpy(in_tensor[None]).to(device=device, dtype=torch.bfloat16) # shape (1,2,H,W) + in_tensor = torch.from_numpy(in_tensor[None]).to(device=device, dtype=half_dtype) # shape (1,2,H,W) with torch.no_grad(): if device.type == "cuda": - with torch.autocast(device_type=device.type, dtype=torch.bfloat16): + with torch.autocast(device_type=device.type, dtype=half_dtype): seg_prob = run_compiled_model(in_tensor) else: - with torch.autocast(device_type="cpu", dtype=torch.bfloat16): + with torch.autocast(device_type="cpu", dtype=half_dtype): seg_prob = run_compiled_model(in_tensor) foreground = seg_prob.to(dtype=torch.float32, device="cpu").numpy() From 61de272d0b6fd6ab974e16c32dc2148cb129c353 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 4 Jun 2025 10:05:59 +0100 Subject: [PATCH 116/189] deal with N-start helices and local helical prior averaging: switch off rot-prior updates for N-start or C2 symmetry helices; todo: check whether C2 might still work --- src/helix.cpp | 61 +++++++++++++++++++++++++++++----------- src/helix.h | 11 ++++++-- src/ml_optimiser.cpp | 7 +++-- src/ml_optimiser_mpi.cpp | 6 ++-- 4 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/helix.cpp b/src/helix.cpp index 62fe7952b..755a8a404 100644 --- a/src/helix.cpp +++ b/src/helix.cpp @@ -611,11 +611,14 @@ RFLOAT getHelicalSigma2Rot( RFLOAT helical_twist_deg, RFLOAT helical_offset_step_Angst, RFLOAT rot_step_deg, - RFLOAT old_sigma2_rot) + RFLOAT old_sigma2_rot, + int helical_nstart) { if ( (helical_offset_step_Angst < 0.) || (rot_step_deg < 0.) || (old_sigma2_rot < 0.) ) REPORT_ERROR("helix.cpp::getHelicalSigma2Rot: Helical offset step, rot step or sigma2_rot cannot be negative!"); + getNstartHelicalTwistAndRise(helical_twist_deg, helical_rise_Angst, helical_nstart); + RFLOAT nr_samplings_along_helical_axis = (fabs(helical_rise_Angst)) / helical_offset_step_Angst; RFLOAT rot_search_range = (fabs(helical_twist_deg)) / nr_samplings_along_helical_axis; RFLOAT new_rot_step = rot_search_range / 6.; @@ -629,9 +632,26 @@ RFLOAT getHelicalSigma2Rot( // factor = factor_max; new_sigma2_rot *= factor * factor; } + //std::cerr <<" N-start twist= " << helical_twist_deg << " rise= " << helical_rise_Angst << " old sigma2rot= " << old_sigma2_rot << " new sigma2rot= " << new_sigma2_rot<< std::endl; return new_sigma2_rot; }; +void getNstartHelicalTwistAndRise(RFLOAT &twist, + RFLOAT &rise, + int helical_nstart) +{ + + // Shaoda's formula (which need to be inverted, as we want original N-start rise and twist back) + // rise_1-start = rise / N + // twist_1-start = (twist+360)/N if twist>0 + // twist_1-start = (twist-360)/N if twist<0 + rise *= helical_nstart; + twist *= helical_nstart; + while (twist > 180.) twist -= 360.; + while (twist < -180.) twist += 360.; + +} + bool checkParametersFor3DHelicalReconstruction( bool ignore_symmetry, bool do_symmetry_local_refinement, @@ -3567,7 +3587,7 @@ void sortHelicalTubeID(MetaDataTable& MD) std::vector dummy; updatePriorsForHelicalReconstruction( MD, 1.,dummy, dummy, 1, - false, false, + false, false,false, 0., 0., 0., 1., false, 1); list.clear(); @@ -4080,6 +4100,7 @@ void updatePriorsForOneHelicalTube( RFLOAT sigma_segment_dist, std::vector helical_rise, std::vector helical_twist, + int helical_nstart, bool is_3D_data, bool do_auto_refine, RFLOAT sigma2_rot, // KThurber @@ -4198,8 +4219,8 @@ void updatePriorsForOneHelicalTube( sum_ang_vec = this_ang_vec * this_w; // rotation angle all new KThurber - this_rot = list[id].rot_deg; // KThurber - this_rot_vec(0) = cos(DEG2RAD(this_rot)); + this_rot = list[id].rot_deg; // KThurber + this_rot_vec(0) = cos(DEG2RAD(this_rot)); this_rot_vec(1) = sin(DEG2RAD(this_rot)); sum_rot_vec = this_rot_vec * this_w; // for adjusting rot angle by shift along helix @@ -4254,10 +4275,12 @@ void updatePriorsForOneHelicalTube( // In the second pass, check the direction from large to small distances RFLOAT sign = (iflip == 1) ? 1. : -1.; - this_rot = list[idd].rot_deg + sign*(180./pitch)*(this_pos - center_pos - this_x_helix + center_x_helix); + this_rot = list[idd].rot_deg + sign*(180./pitch)*(this_pos - center_pos - this_x_helix + center_x_helix); } else - this_rot = list[idd].rot_deg; + { + this_rot = list[idd].rot_deg; + } this_rot_vec(0) = cos(DEG2RAD(this_rot)); this_rot_vec(1) = sin(DEG2RAD(this_rot)); @@ -4387,9 +4410,10 @@ void updatePriorsForHelicalReconstruction( RFLOAT sigma_segment_dist, std::vector helical_rise, std::vector helical_twist, - int helical_nstart, + int helical_nstart, bool is_3D_data, bool do_auto_refine, + bool update_rot_prior, RFLOAT sigma2_rot, RFLOAT sigma2_tilt, RFLOAT sigma2_psi, @@ -4436,16 +4460,9 @@ void updatePriorsForHelicalReconstruction( if (helical_nstart > 1) { // Assume same N-start for all classes - // Shaoda's formula (which need to be inverted, as we want original N-start rise and twist back) - // rise_1-start = rise / N - // twist_1-start = (twist+360)/N if twist>0 - // twist_1-start = (twist-360)/N if twist<0 - for (int iclass=0; iclass < helical_rise.size(); iclass++) { - helical_rise[iclass] *= helical_nstart; - RFLOAT aux = helical_twist[iclass] * helical_nstart; - helical_twist[iclass] = (aux > 360.) ? aux - 360. : aux + 360.; + getNstartHelicalTwistAndRise(helical_twist[iclass], helical_rise[iclass], helical_nstart); if (verb > 0) std::cout << " + for rotational priors go back to " << helical_nstart << "-start helical twist= " << helical_twist[iclass] << " and rise= " << helical_rise[iclass] << std::endl; } @@ -4522,7 +4539,7 @@ void updatePriorsForHelicalReconstruction( // Real work... bool reverse_direction; - updatePriorsForOneHelicalTube(list, sid, eid, nr_opposite_polarity, reverse_direction, sigma_segment_dist, helical_rise, helical_twist, + updatePriorsForOneHelicalTube(list, sid, eid, nr_opposite_polarity, reverse_direction, sigma_segment_dist, helical_rise, helical_twist,helical_nstart, is_3D_data, do_auto_refine, sigma2_rot, sigma2_tilt, sigma2_psi, sigma2_offset); total_opposite_polarity += nr_opposite_polarity; if (reverse_direction) total_opposite_rot += 1; @@ -4537,7 +4554,17 @@ void updatePriorsForHelicalReconstruction( MD.setValue(EMDL_ORIENT_TILT_PRIOR, list[id].tilt_prior_deg, list[id].MDobjectID); MD.setValue(EMDL_ORIENT_PSI_PRIOR, list[id].psi_prior_deg, list[id].MDobjectID); MD.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, list[id].psi_flip_ratio, list[id].MDobjectID); - MD.setValue(EMDL_ORIENT_ROT_PRIOR, list[id].rot_prior_deg, list[id].MDobjectID); // KThurber + if (update_rot_prior) + { + MD.setValue(EMDL_ORIENT_ROT_PRIOR, list[id].rot_prior_deg, list[id].MDobjectID); // KThurber + } + else + { + // Just set prior to the rot angle from the previous iteration + RFLOAT val; + MD.getValue(EMDL_ORIENT_ROT, val, list[id].MDobjectID); + MD.setValue(EMDL_ORIENT_ROT_PRIOR, val, list[id].MDobjectID); + } MD.setValue(EMDL_ORIENT_ORIGIN_X_ANGSTROM, list[id].dx_prior_A, list[id].MDobjectID); MD.setValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, list[id].dy_prior_A, list[id].MDobjectID); if (is_3D_data) diff --git a/src/helix.h b/src/helix.h index 18af6483b..a66e4c077 100644 --- a/src/helix.h +++ b/src/helix.h @@ -106,7 +106,12 @@ RFLOAT getHelicalSigma2Rot( RFLOAT helical_twist_deg, RFLOAT helical_offset_step_Angst, RFLOAT rot_step_deg, - RFLOAT old_sigma2_rot); + RFLOAT old_sigma2_rot, + int helical_nstart); + +void getNstartHelicalTwistAndRise(RFLOAT &twist, + RFLOAT &rise, + int helical_nstart); bool checkParametersFor3DHelicalReconstruction( bool ignore_symmetry, @@ -428,7 +433,7 @@ class HelicalSegmentPriorInfoEntry ~HelicalSegmentPriorInfoEntry() { clear(); }; - void checkPsiPolarity(); + void checkPsiPolarity(); bool operator<(const HelicalSegmentPriorInfoEntry &rhs) const; }; @@ -449,6 +454,7 @@ void updatePriorsForOneHelicalTube( RFLOAT sigma_segment_dist, std::vector helical_rise, std::vector helical_twist, + int nfold_ambiguity, bool is_3D_data, bool do_auto_refine, RFLOAT sigma2_rot, // KThurber @@ -465,6 +471,7 @@ void updatePriorsForHelicalReconstruction( int helical_nstart, bool is_3D, bool do_auto_refine, + bool update_rot_prior, RFLOAT sigma2_rot, RFLOAT sigma2_tilt, RFLOAT sigma2_psi, diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 9904edd8e..ae8eecfad 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -2376,7 +2376,7 @@ void MlOptimiser::initialiseGeneral(int rank) { // Aug20,2015 - Shaoda, Helical refinement RFLOAT rottilt_step = sampling.getAngularSampling(adaptive_oversampling); - mymodel.sigma2_rot = getHelicalSigma2Rot(helical_rise_initial, helical_twist_initial, sampling.helical_offset_step, rottilt_step, mymodel.sigma2_rot); + mymodel.sigma2_rot = getHelicalSigma2Rot(helical_rise_initial, helical_twist_initial, sampling.helical_offset_step, rottilt_step, mymodel.sigma2_rot, helical_nstart); } if (particle_diameter < 0.) @@ -3454,6 +3454,8 @@ void MlOptimiser::iterate() if ( (!do_skip_align) && (!do_skip_rotate) ) { + // Only update rot priors for 1-start C1 filaments + bool update_rot_prior = (sampling.L_repository.size() == 1 && helical_nstart == 1); updatePriorsForHelicalReconstruction( mydata.MDimg, helical_sigma_distance * ((RFLOAT)(mymodel.ori_size)), @@ -3462,6 +3464,7 @@ void MlOptimiser::iterate() helical_nstart, (mymodel.data_dim == 3 || mydata.is_tomo), (do_auto_refine || do_auto_sampling), + update_rot_prior, mymodel.sigma2_rot, mymodel.sigma2_tilt, mymodel.sigma2_psi, @@ -9946,7 +9949,7 @@ void MlOptimiser::updateAngularSampling(bool myverb) // Aug20,2015 - Shaoda, Helical refinement if ( (do_helical_refine) && (!ignore_helical_symmetry) ) - mymodel.sigma2_rot = getHelicalSigma2Rot(helical_rise_initial, helical_twist_initial, sampling.helical_offset_step, new_rottilt_step, mymodel.sigma2_rot); + mymodel.sigma2_rot = getHelicalSigma2Rot(helical_rise_initial, helical_twist_initial, sampling.helical_offset_step, new_rottilt_step, mymodel.sigma2_rot, helical_nstart); } } } diff --git a/src/ml_optimiser_mpi.cpp b/src/ml_optimiser_mpi.cpp index 266cdd503..c11467e53 100644 --- a/src/ml_optimiser_mpi.cpp +++ b/src/ml_optimiser_mpi.cpp @@ -3809,7 +3809,7 @@ void MlOptimiserMpi::updateAngularSamplingGrad(long int my_first_part_id, long i if ((do_helical_refine) && (!ignore_helical_symmetry)) mymodel.sigma2_rot = getHelicalSigma2Rot(helical_rise_initial, helical_twist_initial, sampling.helical_offset_step, new_rottilt_step, - mymodel.sigma2_rot); + mymodel.sigma2_rot, helical_nstart); } // Reset iteration counter @@ -4125,7 +4125,8 @@ void MlOptimiserMpi::iterate() { if ( (do_helical_refine) && (!do_skip_align) && (!do_skip_rotate) && mymodel.ref_dim == 3) { - updatePriorsForHelicalReconstruction( + bool update_rot_prior = (sampling.L_repository.size() == 1 && helical_nstart == 1); + updatePriorsForHelicalReconstruction( mydata.MDimg, helical_sigma_distance * ((RFLOAT)(mymodel.ori_size)), mymodel.helical_rise, @@ -4133,6 +4134,7 @@ void MlOptimiserMpi::iterate() helical_nstart, (mymodel.data_dim == 3), (do_auto_refine || do_auto_sampling), + update_rot_prior, mymodel.sigma2_rot, mymodel.sigma2_tilt, mymodel.sigma2_psi, From b100d39d9e46b2af0958ed703880037bc93ae3d6 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 4 Jun 2025 12:52:46 +0100 Subject: [PATCH 117/189] go back to wider rot searches for 2-start helices, and do update rot prior for Cn symmetry --- src/helix.cpp | 3 ++- src/ml_optimiser.cpp | 4 ++-- src/ml_optimiser_mpi.cpp | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/helix.cpp b/src/helix.cpp index 755a8a404..0ed381565 100644 --- a/src/helix.cpp +++ b/src/helix.cpp @@ -617,7 +617,8 @@ RFLOAT getHelicalSigma2Rot( if ( (helical_offset_step_Angst < 0.) || (rot_step_deg < 0.) || (old_sigma2_rot < 0.) ) REPORT_ERROR("helix.cpp::getHelicalSigma2Rot: Helical offset step, rot step or sigma2_rot cannot be negative!"); - getNstartHelicalTwistAndRise(helical_twist_deg, helical_rise_Angst, helical_nstart); + // SHWS 04062025: this seemed like the correct thing to do, but difficult 2-start refinement go worse with this smaller sigma2rot... + //getNstartHelicalTwistAndRise(helical_twist_deg, helical_rise_Angst, helical_nstart); RFLOAT nr_samplings_along_helical_axis = (fabs(helical_rise_Angst)) / helical_offset_step_Angst; RFLOAT rot_search_range = (fabs(helical_twist_deg)) / nr_samplings_along_helical_axis; diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index ae8eecfad..d14936534 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -3454,8 +3454,8 @@ void MlOptimiser::iterate() if ( (!do_skip_align) && (!do_skip_rotate) ) { - // Only update rot priors for 1-start C1 filaments - bool update_rot_prior = (sampling.L_repository.size() == 1 && helical_nstart == 1); + // Only update rot priors in local averaging for 1-start filaments, otherwise centre around rot from previous iteration + bool update_rot_prior = (helical_nstart == 1); updatePriorsForHelicalReconstruction( mydata.MDimg, helical_sigma_distance * ((RFLOAT)(mymodel.ori_size)), diff --git a/src/ml_optimiser_mpi.cpp b/src/ml_optimiser_mpi.cpp index c11467e53..bf9850ae8 100644 --- a/src/ml_optimiser_mpi.cpp +++ b/src/ml_optimiser_mpi.cpp @@ -4125,7 +4125,8 @@ void MlOptimiserMpi::iterate() { if ( (do_helical_refine) && (!do_skip_align) && (!do_skip_rotate) && mymodel.ref_dim == 3) { - bool update_rot_prior = (sampling.L_repository.size() == 1 && helical_nstart == 1); + // Only update rot priors in local averaging for 1-start filaments, otherwise centre around rot from previous iteration + bool update_rot_prior = (helical_nstart == 1); updatePriorsForHelicalReconstruction( mydata.MDimg, helical_sigma_distance * ((RFLOAT)(mymodel.ori_size)), From 77c39a3c459f70b3a1152998b57314de87c7704f Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 5 Jun 2025 14:33:08 +0100 Subject: [PATCH 118/189] dont do init blobs for helical 2D classification --- src/pipeline_jobs.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index b90b18d01..8b351570d 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -3384,6 +3384,7 @@ bool RelionJob::getCommandsClass2DJob(std::string &outputname, std::vector Date: Thu, 5 Jun 2025 14:33:45 +0100 Subject: [PATCH 119/189] removed again option to switch off rot prior updates --- src/helix.cpp | 33 +++++++++++---------------------- src/helix.h | 1 - src/ml_optimiser.cpp | 3 --- src/ml_optimiser_mpi.cpp | 2 -- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/src/helix.cpp b/src/helix.cpp index 0ed381565..aa01408e1 100644 --- a/src/helix.cpp +++ b/src/helix.cpp @@ -618,7 +618,7 @@ RFLOAT getHelicalSigma2Rot( REPORT_ERROR("helix.cpp::getHelicalSigma2Rot: Helical offset step, rot step or sigma2_rot cannot be negative!"); // SHWS 04062025: this seemed like the correct thing to do, but difficult 2-start refinement go worse with this smaller sigma2rot... - //getNstartHelicalTwistAndRise(helical_twist_deg, helical_rise_Angst, helical_nstart); + getNstartHelicalTwistAndRise(helical_twist_deg, helical_rise_Angst, helical_nstart); RFLOAT nr_samplings_along_helical_axis = (fabs(helical_rise_Angst)) / helical_offset_step_Angst; RFLOAT rot_search_range = (fabs(helical_twist_deg)) / nr_samplings_along_helical_axis; @@ -3588,7 +3588,7 @@ void sortHelicalTubeID(MetaDataTable& MD) std::vector dummy; updatePriorsForHelicalReconstruction( MD, 1.,dummy, dummy, 1, - false, false,false, + false, false, 0., 0., 0., 1., false, 1); list.clear(); @@ -4221,8 +4221,8 @@ void updatePriorsForOneHelicalTube( // rotation angle all new KThurber this_rot = list[id].rot_deg; // KThurber - this_rot_vec(0) = cos(DEG2RAD(this_rot)); - this_rot_vec(1) = sin(DEG2RAD(this_rot)); + this_rot_vec(0) = cos(DEG2RAD(helical_nstart * this_rot)); + this_rot_vec(1) = sin(DEG2RAD(helical_nstart * this_rot)); sum_rot_vec = this_rot_vec * this_w; // for adjusting rot angle by shift along helix center_x_helix = list[id].dx_A * cos(DEG2RAD(this_psi)) - list[id].dy_A * sin(DEG2RAD(this_psi)); @@ -4283,8 +4283,8 @@ void updatePriorsForOneHelicalTube( this_rot = list[idd].rot_deg; } - this_rot_vec(0) = cos(DEG2RAD(this_rot)); - this_rot_vec(1) = sin(DEG2RAD(this_rot)); + this_rot_vec(0) = cos(DEG2RAD(helical_nstart * this_rot)); + this_rot_vec(1) = sin(DEG2RAD(helical_nstart * this_rot)); sum_rot_vec += this_rot_vec * this_w; this_psi = list[idd].psi_deg; @@ -4316,12 +4316,12 @@ void updatePriorsForOneHelicalTube( { sum_rot_vec(0) = sum_rot_vec(0) / length_rot_vec; sum_rot_vec(1) = sum_rot_vec(1) / length_rot_vec; - this_rot = RAD2DEG(acos(sum_rot_vec(0))); - if (sum_rot_vec(1) < 0.) - this_rot = -1. * this_rot; // if sign negative, angle is negative + this_rot = RAD2DEG(atan2(sum_rot_vec(1), sum_rot_vec(0) )) / helical_nstart; } else - this_rot = list[id].rot_deg; // don't change prior if average fails + { + this_rot = list[id].rot_deg; // don't change prior if average fails + } // KThurber end new section if (iflip == 0) @@ -4414,7 +4414,6 @@ void updatePriorsForHelicalReconstruction( int helical_nstart, bool is_3D_data, bool do_auto_refine, - bool update_rot_prior, RFLOAT sigma2_rot, RFLOAT sigma2_tilt, RFLOAT sigma2_psi, @@ -4555,17 +4554,7 @@ void updatePriorsForHelicalReconstruction( MD.setValue(EMDL_ORIENT_TILT_PRIOR, list[id].tilt_prior_deg, list[id].MDobjectID); MD.setValue(EMDL_ORIENT_PSI_PRIOR, list[id].psi_prior_deg, list[id].MDobjectID); MD.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, list[id].psi_flip_ratio, list[id].MDobjectID); - if (update_rot_prior) - { - MD.setValue(EMDL_ORIENT_ROT_PRIOR, list[id].rot_prior_deg, list[id].MDobjectID); // KThurber - } - else - { - // Just set prior to the rot angle from the previous iteration - RFLOAT val; - MD.getValue(EMDL_ORIENT_ROT, val, list[id].MDobjectID); - MD.setValue(EMDL_ORIENT_ROT_PRIOR, val, list[id].MDobjectID); - } + MD.setValue(EMDL_ORIENT_ROT_PRIOR, list[id].rot_prior_deg, list[id].MDobjectID); // KThurber MD.setValue(EMDL_ORIENT_ORIGIN_X_ANGSTROM, list[id].dx_prior_A, list[id].MDobjectID); MD.setValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, list[id].dy_prior_A, list[id].MDobjectID); if (is_3D_data) diff --git a/src/helix.h b/src/helix.h index a66e4c077..a4df772b1 100644 --- a/src/helix.h +++ b/src/helix.h @@ -471,7 +471,6 @@ void updatePriorsForHelicalReconstruction( int helical_nstart, bool is_3D, bool do_auto_refine, - bool update_rot_prior, RFLOAT sigma2_rot, RFLOAT sigma2_tilt, RFLOAT sigma2_psi, diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index d14936534..02bacf253 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -3454,8 +3454,6 @@ void MlOptimiser::iterate() if ( (!do_skip_align) && (!do_skip_rotate) ) { - // Only update rot priors in local averaging for 1-start filaments, otherwise centre around rot from previous iteration - bool update_rot_prior = (helical_nstart == 1); updatePriorsForHelicalReconstruction( mydata.MDimg, helical_sigma_distance * ((RFLOAT)(mymodel.ori_size)), @@ -3464,7 +3462,6 @@ void MlOptimiser::iterate() helical_nstart, (mymodel.data_dim == 3 || mydata.is_tomo), (do_auto_refine || do_auto_sampling), - update_rot_prior, mymodel.sigma2_rot, mymodel.sigma2_tilt, mymodel.sigma2_psi, diff --git a/src/ml_optimiser_mpi.cpp b/src/ml_optimiser_mpi.cpp index bf9850ae8..c74ba964f 100644 --- a/src/ml_optimiser_mpi.cpp +++ b/src/ml_optimiser_mpi.cpp @@ -4126,7 +4126,6 @@ void MlOptimiserMpi::iterate() if ( (do_helical_refine) && (!do_skip_align) && (!do_skip_rotate) && mymodel.ref_dim == 3) { // Only update rot priors in local averaging for 1-start filaments, otherwise centre around rot from previous iteration - bool update_rot_prior = (helical_nstart == 1); updatePriorsForHelicalReconstruction( mydata.MDimg, helical_sigma_distance * ((RFLOAT)(mymodel.ori_size)), @@ -4135,7 +4134,6 @@ void MlOptimiserMpi::iterate() helical_nstart, (mymodel.data_dim == 3), (do_auto_refine || do_auto_sampling), - update_rot_prior, mymodel.sigma2_rot, mymodel.sigma2_tilt, mymodel.sigma2_psi, From ede07613050b25291d820b27671867275165d0d4 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 6 Jun 2025 08:54:32 +0100 Subject: [PATCH 120/189] dont die on broken micrographs --- src/amyloid_finder.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 4780b1777..3df661f1a 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -855,9 +855,12 @@ void AmyloidFinder::finalise() FileName fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; FileName fn_skew = fn_root + "_" + fn_out + "_skew.txt"; - RFLOAT kurt, skew; + RFLOAT kurt = 0., skew = 0.; std::ifstream fin(fn_skew); - if (!(fin >> skew >> kurt)) REPORT_ERROR("Error reading the numbers from the file: " + fn_skew); + if (!(fin >> skew >> kurt)) + { + std::cerr << "Error reading skew numbers for: " << fn_ori_micrographs[imic] << std::endl; + } MDin.setValue(EMDL_MICROGRAPH_SCORE_KURTOSIS, kurt, imic); MDin.setValue(EMDL_MICROGRAPH_SCORE_SKEWNESS, skew, imic); From 9ff2c4f331269ed5705c4a0ea389bc7938c49a46 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 6 Jun 2025 08:54:56 +0100 Subject: [PATCH 121/189] also write class_averages.star when selecting from Davids filament selection tool --- src/pipeline_jobs.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 8b351570d..71f7f8ae4 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -3049,9 +3049,9 @@ bool RelionJob::getCommandsSelectJob(std::string &outputname, std::vector Date: Mon, 9 Jun 2025 16:47:47 +0100 Subject: [PATCH 122/189] further tweaks to autopicker for amyloids --- src/gui_jobwindow.cpp | 28 +++++++++++++++++++++------- src/pipeline_jobs.cpp | 40 +++++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 351200ba4..390b6bc1a 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -754,7 +754,7 @@ void JobWindow::initialiseManualpickWindow() void JobWindow::initialiseAutopickWindow() { - setupTabs(6); + setupTabs(7); tab1->begin(); tab1->label("I/O"); @@ -888,7 +888,7 @@ void JobWindow::initialiseAutopickWindow() tab4->end(); tab5->begin(); - tab5->label("autopicking"); + tab5->label("Refs 2"); resetHeight(); place("threshold_autopick"); @@ -938,26 +938,40 @@ void JobWindow::initialiseAutopickWindow() place("helical_nr_asu"); place("helical_rise"); + group4->end(); + + guientries["do_pick_helical_segments"].cb_menu_i(); + + tab6->end(); + + tab7->begin(); + tab7->label("Amyloid"); + resetHeight(); + current_y += STEPY/2; + + place("amyloid_length"); + place("amyloid_width"); + current_y += STEPY/2; place("do_amyloid_fom"); + current_y += STEPY/2; + group5 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); group5->end(); + place("do_amyloid_tracing", TOGGLE_LEAVE_ACTIVE, group5); group5->begin(); place("amyloid_threshold"); + place("amyloid_gpu_ids"); place("do_amyloid_plot"); group5->end(); guientries["do_amyloid_tracing"].cb_menu_i(); - group4->end(); - - guientries["do_pick_helical_segments"].cb_menu_i(); - - tab6->end(); + tab7->end(); } void JobWindow::initialiseExtractWindow() diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 71f7f8ae4..1d81d915d 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2038,10 +2038,9 @@ The samplings are approximate numbers and vary slightly over the sphere.\n\n For joboptions["shrink"] = JobOption("Shrink factor:", 0, 0, 1, 0.1, "This is useful to speed up the calculations, and to make them less memory-intensive. The micrographs will be downscaled (shrunk) to calculate the cross-correlations, and peak searching will be done in the downscaled FOM maps. When set to 0, the micrographs will de downscaled to the lowpass filter of the references, a value between 0 and 1 will downscale the micrographs by that factor. Note that the results will not be exactly the same when you shrink micrographs!\ \n\nIn the Laplacian-of-Gaussian picker, this option is ignored and the shrink factor always becomes 0."); joboptions["use_gpu"] = JobOption("Use GPU acceleration?", false, "If set to Yes, the job will try to use GPU acceleration. The Laplacian-of-Gaussian picker does not support GPU."); - joboptions["gpu_ids"] = JobOption("Which GPUs to use:", std::string(""), "This argument is not necessary. If left empty, the job itself will try to allocate available GPU resources. You can override the default allocation by providing a list of which GPUs (0,1,2,3, etc) to use. MPI-processes are separated by ':'. For example: 0:1:0:1:0:1"); + joboptions["gpu_ids"] = JobOption("Which GPUs to use:", std::string(""), "This argument is not necessary. If left empty, the job itself will try to allocate available GPU resources. You can override the default allocation by providing a list of which GPUs (0,1,2,3, etc) to use. MPI-processes are separated by ':'. For example: 0:1:0:1:0:1"); joboptions["do_pick_helical_segments"] = JobOption("Pick 2D helical segments?", false, "Set to Yes if you want to pick 2D helical segments. Note this will run the old algorithms for reference-based helical segment picking, as described by He & Scheres, J Struct Biol, 2017. Often, we now run filament picking from the Topaz tab instead...."); - joboptions["do_amyloid"] = JobOption("Pick amyloid segments?", false, "Set to Yes if you want to use the find_amyloid program that was developed specifically for picking amyloids. Note this is the only option that will use threads!"); joboptions["helical_tube_outer_diameter"] = JobOption("Tube diameter (A): ", 200, 100, 1000, 10, "Outer diameter (in Angstroms) of helical tubes. \ This value should be slightly larger than the actual width of the tubes."); @@ -2054,10 +2053,15 @@ Kappa ~ 0.05 is recommended for long and straight tubes (e.g. TMV, VipA/VipB and joboptions["helical_tube_length_min"] = JobOption("Minimum length (A): ", 400, 100, 1000, 10, "Minimum length (in Angstroms) of helical tubes for auto-picking. \ Helical tubes with shorter lengths will not be picked. Note that a long helical tube seen by human eye might be treated as short broken pieces due to low FOM values or high picking threshold."); - joboptions["do_amyloid_fom"] = JobOption("Calculate amyloid FOM images? ", true, "Calculate FOM images with 4.7A Fourier signal for all input micrographs? Note this code runs only on the CPU, and best multi-threaded."); - joboptions["do_amyloid_tracing"] = JobOption("Trace amyloids in FOM images? ", true, "Trace amyloids (as lines) in the FOM images? Note this code runs only most efficiently on the GPU."); + // find_amyloid + joboptions["do_amyloid"] = JobOption("Pick amyloid segments?", false, "Set to Yes if you want to use the find_amyloid program that was developed specifically for picking amyloids. Note that autopicking of amyloids needs to be split into two separate jobs: the first job calculates FOM/PSI maps for all filaments. This job needs to be run multi-threaded (any n, where 36/n is an integer is a good number) on the CPU. The job job uses a neural network to trace filaments. This job takes the micrographs_autopick.star output file from the first job as input (or a subset selection thereof, e.g. with rlnMicrographScoreSkewness>1). And this second job only runs on GPUs."); + joboptions["do_amyloid_fom"] = JobOption("Calculate amyloid FOM images (on CPU)? ", true, "Calculate FOM images with 4.7A Fourier signal for all input micrographs? Note this code runs only on the CPU, and best multi-threaded (18 threads is a good number)."); + joboptions["do_amyloid_tracing"] = JobOption("Trace amyloids in FOM images (on GPU)? ", true, "Trace amyloids (as lines) in the FOM images? Note this code runs on the GPU. This job takes the micrographs_autopick.star output file from the first job as input (or a subset selection thereof, e.g. with rlnMicrographScoreSkewness>1)."); + joboptions["amyloid_gpu_ids"] = JobOption("Which GPUs to use for tracing:", std::string(""), "This argument is not necessary. If left empty, the job itself will try to allocate available GPU resources. You can override the default allocation by providing a list of which GPUs (0,1,2,3, etc) to use. MPI-processes are separated by ':'. For example: 0:1:0:1:0:1. Note, only the amyloid tracing part of the job is run on the GPU; FOM calculation has only been implemented for the CPU."); joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 0.25, 0.05, 1, 0.05, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); joboptions["do_amyloid_plot"] = JobOption("Plot results per micrograph?", false, "Set this option to Yes to visualise intermediate results for amyloid tracing, which may help with setting values for filament length, width and picking threshold. Don't use in parallel or in submission to the queue."); + joboptions["amyloid_width"] = JobOption("Amyloid width (A): ", 200, 100, 1000, 10, "Width (in Angstroms) used for FOM calculation and/or tracing. "); + joboptions["amyloid_length"] = JobOption("Amyloid minimum length (A): ", 500, 100, 1000, 10, "Length (in Angstroms) used for FOM calculation, and minimum length of filaments for tracing."); } @@ -2179,8 +2183,20 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Mon, 9 Jun 2025 17:15:12 +0100 Subject: [PATCH 123/189] by default switch off GPU tracing of amyloids --- src/pipeline_jobs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 1d81d915d..cadab3887 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2056,7 +2056,7 @@ Helical tubes with shorter lengths will not be picked. Note that a long helical // find_amyloid joboptions["do_amyloid"] = JobOption("Pick amyloid segments?", false, "Set to Yes if you want to use the find_amyloid program that was developed specifically for picking amyloids. Note that autopicking of amyloids needs to be split into two separate jobs: the first job calculates FOM/PSI maps for all filaments. This job needs to be run multi-threaded (any n, where 36/n is an integer is a good number) on the CPU. The job job uses a neural network to trace filaments. This job takes the micrographs_autopick.star output file from the first job as input (or a subset selection thereof, e.g. with rlnMicrographScoreSkewness>1). And this second job only runs on GPUs."); joboptions["do_amyloid_fom"] = JobOption("Calculate amyloid FOM images (on CPU)? ", true, "Calculate FOM images with 4.7A Fourier signal for all input micrographs? Note this code runs only on the CPU, and best multi-threaded (18 threads is a good number)."); - joboptions["do_amyloid_tracing"] = JobOption("Trace amyloids in FOM images (on GPU)? ", true, "Trace amyloids (as lines) in the FOM images? Note this code runs on the GPU. This job takes the micrographs_autopick.star output file from the first job as input (or a subset selection thereof, e.g. with rlnMicrographScoreSkewness>1)."); + joboptions["do_amyloid_tracing"] = JobOption("Trace amyloids in FOM images (on GPU)? ", false, "Trace amyloids (as lines) in the FOM images? Note this code runs on the GPU. This job takes the micrographs_autopick.star output file from the first job as input (or a subset selection thereof, e.g. with rlnMicrographScoreSkewness>1)."); joboptions["amyloid_gpu_ids"] = JobOption("Which GPUs to use for tracing:", std::string(""), "This argument is not necessary. If left empty, the job itself will try to allocate available GPU resources. You can override the default allocation by providing a list of which GPUs (0,1,2,3, etc) to use. MPI-processes are separated by ':'. For example: 0:1:0:1:0:1. Note, only the amyloid tracing part of the job is run on the GPU; FOM calculation has only been implemented for the CPU."); joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 0.25, 0.05, 1, 0.05, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); joboptions["do_amyloid_plot"] = JobOption("Plot results per micrograph?", false, "Set this option to Yes to visualise intermediate results for amyloid tracing, which may help with setting values for filament length, width and picking threshold. Don't use in parallel or in submission to the queue."); From 4414e860d4571232d13153dc27aa5d50a45576ab Mon Sep 17 00:00:00 2001 From: scheres Date: Mon, 9 Jun 2025 17:26:23 +0100 Subject: [PATCH 124/189] more GUI tweaks to GPU tracing of amyloids --- src/pipeline_jobs.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index cadab3887..e7a5fa8d7 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2055,9 +2055,9 @@ Helical tubes with shorter lengths will not be picked. Note that a long helical // find_amyloid joboptions["do_amyloid"] = JobOption("Pick amyloid segments?", false, "Set to Yes if you want to use the find_amyloid program that was developed specifically for picking amyloids. Note that autopicking of amyloids needs to be split into two separate jobs: the first job calculates FOM/PSI maps for all filaments. This job needs to be run multi-threaded (any n, where 36/n is an integer is a good number) on the CPU. The job job uses a neural network to trace filaments. This job takes the micrographs_autopick.star output file from the first job as input (or a subset selection thereof, e.g. with rlnMicrographScoreSkewness>1). And this second job only runs on GPUs."); - joboptions["do_amyloid_fom"] = JobOption("Calculate amyloid FOM images (on CPU)? ", true, "Calculate FOM images with 4.7A Fourier signal for all input micrographs? Note this code runs only on the CPU, and best multi-threaded (18 threads is a good number)."); - joboptions["do_amyloid_tracing"] = JobOption("Trace amyloids in FOM images (on GPU)? ", false, "Trace amyloids (as lines) in the FOM images? Note this code runs on the GPU. This job takes the micrographs_autopick.star output file from the first job as input (or a subset selection thereof, e.g. with rlnMicrographScoreSkewness>1)."); - joboptions["amyloid_gpu_ids"] = JobOption("Which GPUs to use for tracing:", std::string(""), "This argument is not necessary. If left empty, the job itself will try to allocate available GPU resources. You can override the default allocation by providing a list of which GPUs (0,1,2,3, etc) to use. MPI-processes are separated by ':'. For example: 0:1:0:1:0:1. Note, only the amyloid tracing part of the job is run on the GPU; FOM calculation has only been implemented for the CPU."); + joboptions["do_amyloid_fom"] = JobOption("Calculate amyloid FOM images (on CPU)? ", true, "Calculate FOM images with 4.7A Fourier signal for all input micrographs? Note this code runs only on the CPU, and best multi-threaded (any n with 36/n being an integer is a good number; we often use 18 threads on our cluster). You need to run a first job to calculate FOM images using this option, before you can use its output as input for a second job to trace the filaments, using the option below."); + joboptions["do_amyloid_tracing"] = JobOption("Trace amyloids in FOM images (on GPU)? ", false, "Trace amyloids (as lines) in the FOM images that were calculated in a first (CPU-based) job? Note this part of the code runs on the GPU. This job takes the micrographs_autopick.star output file from the first job as input (or a subset selection thereof, e.g. with rlnMicrographScoreSkewness>1)."); + joboptions["amyloid_gpu_ids"] = JobOption("Which GPUs to use for tracing:", std::string(""), "This argument is not necessary. If left empty, the job itself will try to allocate available GPU resources. You can override the default allocation by providing a list of which GPUs to use. MPI-processes are separated by ':'. For example: 0:1:2:3. Note, only the amyloid tracing part of the job is run on the GPU; FOM calculation has only been implemented for the CPU."); joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 0.25, 0.05, 1, 0.05, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); joboptions["do_amyloid_plot"] = JobOption("Plot results per micrograph?", false, "Set this option to Yes to visualise intermediate results for amyloid tracing, which may help with setting values for filament length, width and picking threshold. Don't use in parallel or in submission to the queue."); joboptions["amyloid_width"] = JobOption("Amyloid width (A): ", 200, 100, 1000, 10, "Width (in Angstroms) used for FOM calculation and/or tracing. "); From ae0cd9c34891b2282d8e5ebdced12fd8e526bdb6 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 11 Jun 2025 12:33:38 +0100 Subject: [PATCH 125/189] output downscaled fom and psi images --- src/amyloid_finder.cpp | 48 +++++++++++++++++++++--------------------- src/gui_jobwindow.cpp | 8 ++----- src/pipeline_jobs.cpp | 2 -- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 3df661f1a..f59ba2514 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -512,7 +512,7 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi - Mangle.resize(down_ysize, down_xsize); + Mangle.resize(down_ysize/shift_step, down_xsize/shift_step); Mangle.setXmippOrigin(); Mscore.resize(Mangle); MultidimArray Msum, Mnonsum, Mnonscore, Mneighbour, Mneighbour2; @@ -524,18 +524,20 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi // This can't be parallelised efficiently because need to protect Msums, Mscore and Mangle from simultaneous writing... // Calculate Z-scores over psi: (max_psi - avg_psi) /stddev_psi + int xsize = XSIZE(Mscore); + int ysize = YSIZE(Mscore); for (int ipsi = 0; ipsi < nr_psi; ipsi++) { RFLOAT mypsi = getPsiAngle(ipsi); - for (int ypos = 0; ypos < down_ysize; ypos ++) + for (int ypos = 0; ypos < ysize; ypos ++) { - int cen_ypos = ypos - down_ysize/2; - for (int xpos = 0; xpos < down_xsize; xpos ++) + int cen_ypos = ypos - ysize/2; + for (int xpos = 0; xpos < xsize; xpos ++) { - int cen_xpos = xpos - down_xsize/2; + int cen_xpos = xpos - xsize/2; - RFLOAT myscore = A2D_ELEM(rotated_scores[ipsi], cen_ypos/shift_step, cen_xpos/shift_step); - RFLOAT mynonscore = A2D_ELEM(rotated_nonscores[ipsi], cen_ypos/shift_step, cen_xpos/shift_step); + RFLOAT myscore = A2D_ELEM(rotated_scores[ipsi], cen_ypos, cen_xpos); + RFLOAT mynonscore = A2D_ELEM(rotated_nonscores[ipsi], cen_ypos, cen_xpos); A2D_ELEM(Msum, cen_ypos, cen_xpos) += myscore; A2D_ELEM(Mnonsum, cen_ypos, cen_xpos) += mynonscore; @@ -544,18 +546,18 @@ void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, Multi A2D_ELEM(Mscore, cen_ypos, cen_xpos) = myscore; A2D_ELEM(Mangle, cen_ypos, cen_xpos) = mypsi; int ipsi_nb = (ipsi == 0) ? nr_psi - 1 : ipsi - 1; - A2D_ELEM(Mneighbour, cen_ypos, cen_xpos) = A2D_ELEM(rotated_scores[ipsi_nb], cen_ypos/shift_step, cen_xpos/shift_step); + A2D_ELEM(Mneighbour, cen_ypos, cen_xpos) = A2D_ELEM(rotated_scores[ipsi_nb], cen_ypos, cen_xpos); ipsi_nb = (ipsi == nr_psi - 1) ? 0 : ipsi + 1; - A2D_ELEM(Mneighbour, cen_ypos, cen_xpos) += A2D_ELEM(rotated_scores[ipsi_nb], cen_ypos/shift_step, cen_xpos/shift_step); + A2D_ELEM(Mneighbour, cen_ypos, cen_xpos) += A2D_ELEM(rotated_scores[ipsi_nb], cen_ypos, cen_xpos); } if (mynonscore > A2D_ELEM(Mnonscore, cen_ypos, cen_xpos)) { A2D_ELEM(Mnonscore, cen_ypos, cen_xpos) = mynonscore; int ipsi_nb = (ipsi == 0) ? nr_psi - 1 : ipsi - 1; - A2D_ELEM(Mneighbour2, cen_ypos, cen_xpos) = A2D_ELEM(rotated_nonscores[ipsi_nb], cen_ypos/shift_step, cen_xpos/shift_step); + A2D_ELEM(Mneighbour2, cen_ypos, cen_xpos) = A2D_ELEM(rotated_nonscores[ipsi_nb], cen_ypos, cen_xpos); ipsi_nb = (ipsi == nr_psi - 1) ? 0 : ipsi + 1; - A2D_ELEM(Mneighbour2, cen_ypos, cen_xpos) += A2D_ELEM(rotated_nonscores[ipsi_nb], cen_ypos/shift_step, cen_xpos/shift_step); + A2D_ELEM(Mneighbour2, cen_ypos, cen_xpos) += A2D_ELEM(rotated_nonscores[ipsi_nb], cen_ypos, cen_xpos); } } @@ -681,16 +683,14 @@ void AmyloidFinder::calculateFOMOneMicrograph(FileName fn_mic, bool myverb) RFLOAT skew, kurt; getScoreForOneMicrograph(Iin(), Mscore, Mangle, skew, kurt, myverb); - std::ofstream fh; - fh.open((fn_skew).c_str(), std::ios::out); - fh << skew << " " << kurt << std::endl; - fh.close(); - Image Ipsi, Izscore; - Ipsi.setSamplingRateInHeader(down_angpix); - Izscore.setSamplingRateInHeader(down_angpix); + Ipsi.setSamplingRateInHeader(down_angpix*shift_step); + Izscore.setSamplingRateInHeader(down_angpix*shift_step); Ipsi()=Mangle; Izscore()=Mscore; + // Set the skewness and kurtosis in the header of the FOM image + Izscore.MDMainHeader.setValue(EMDL_IMAGE_STATS_AVG, skew); + Izscore.MDMainHeader.setValue(EMDL_IMAGE_STATS_STDDEV, kurt); Ipsi.write(fn_psi); Izscore.write(fn_fom); } @@ -853,14 +853,14 @@ void AmyloidFinder::finalise() if (!do_skip_fom) { FileName fn_psi = fn_root + "_" + fn_out + "_psi.mrc"; - FileName fn_skew = fn_root + "_" + fn_out + "_skew.txt"; + Image Ifom; + Ifom.read(fn_fom, false); RFLOAT kurt = 0., skew = 0.; - std::ifstream fin(fn_skew); - if (!(fin >> skew >> kurt)) - { - std::cerr << "Error reading skew numbers for: " << fn_ori_micrographs[imic] << std::endl; - } + Ifom.MDMainHeader.getValue(EMDL_IMAGE_STATS_AVG, skew); + Ifom.MDMainHeader.getValue(EMDL_IMAGE_STATS_STDDEV, kurt); + if (isnan(skew)) skew = 0.; + if (isnan(kurt)) kurt = 0.; MDin.setValue(EMDL_MICROGRAPH_SCORE_KURTOSIS, kurt, imic); MDin.setValue(EMDL_MICROGRAPH_SCORE_SKEWNESS, skew, imic); diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 390b6bc1a..f0711d3d6 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -947,12 +947,6 @@ void JobWindow::initialiseAutopickWindow() tab7->begin(); tab7->label("Amyloid"); resetHeight(); - current_y += STEPY/2; - - place("amyloid_length"); - place("amyloid_width"); - - current_y += STEPY/2; place("do_amyloid_fom"); @@ -965,6 +959,8 @@ void JobWindow::initialiseAutopickWindow() group5->begin(); place("amyloid_threshold"); + place("amyloid_length"); + place("amyloid_width"); place("amyloid_gpu_ids"); place("do_amyloid_plot"); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index e7a5fa8d7..edf667a40 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2193,8 +2193,6 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Mon, 16 Jun 2025 09:37:00 +0100 Subject: [PATCH 126/189] better error handling --- src/amyloid_finder.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index f59ba2514..aa9d97b03 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -86,6 +86,11 @@ void AmyloidFinder::initialise(bool is_leader) { MetaDataTable MDin; MDin.read(fn_in, "micrographs"); + + if (do_skip_fom && !(MDin.containsLabel(EMDL_MICROGRAPH_AUTOPICK_FOM) && MDin.containsLabel(EMDL_MICROGRAPH_AUTOPICK_PSI))) + { + REPORT_ERROR("ERROR: You are skipping FOM calculation, but the input STAR file does not contain the FOM/PSI images."); + } FOR_ALL_OBJECTS_IN_METADATA_TABLE(MDin) { FileName fn_mic; From bce685314c7ea906eb99953cce11d844d4897572 Mon Sep 17 00:00:00 2001 From: scheres Date: Mon, 16 Jun 2025 09:37:30 +0100 Subject: [PATCH 127/189] try going back to wider sigma2rot as Sofia reported problems with new version --- src/helix.cpp | 39 +++++++++++++++++---------------------- src/helix.h | 14 +++++++------- src/ml_optimiser.cpp | 4 ++-- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/helix.cpp b/src/helix.cpp index aa01408e1..51c350fa2 100644 --- a/src/helix.cpp +++ b/src/helix.cpp @@ -611,15 +611,11 @@ RFLOAT getHelicalSigma2Rot( RFLOAT helical_twist_deg, RFLOAT helical_offset_step_Angst, RFLOAT rot_step_deg, - RFLOAT old_sigma2_rot, - int helical_nstart) + RFLOAT old_sigma2_rot) { if ( (helical_offset_step_Angst < 0.) || (rot_step_deg < 0.) || (old_sigma2_rot < 0.) ) REPORT_ERROR("helix.cpp::getHelicalSigma2Rot: Helical offset step, rot step or sigma2_rot cannot be negative!"); - // SHWS 04062025: this seemed like the correct thing to do, but difficult 2-start refinement go worse with this smaller sigma2rot... - getNstartHelicalTwistAndRise(helical_twist_deg, helical_rise_Angst, helical_nstart); - RFLOAT nr_samplings_along_helical_axis = (fabs(helical_rise_Angst)) / helical_offset_step_Angst; RFLOAT rot_search_range = (fabs(helical_twist_deg)) / nr_samplings_along_helical_axis; RFLOAT new_rot_step = rot_search_range / 6.; @@ -633,26 +629,9 @@ RFLOAT getHelicalSigma2Rot( // factor = factor_max; new_sigma2_rot *= factor * factor; } - //std::cerr <<" N-start twist= " << helical_twist_deg << " rise= " << helical_rise_Angst << " old sigma2rot= " << old_sigma2_rot << " new sigma2rot= " << new_sigma2_rot<< std::endl; return new_sigma2_rot; }; -void getNstartHelicalTwistAndRise(RFLOAT &twist, - RFLOAT &rise, - int helical_nstart) -{ - - // Shaoda's formula (which need to be inverted, as we want original N-start rise and twist back) - // rise_1-start = rise / N - // twist_1-start = (twist+360)/N if twist>0 - // twist_1-start = (twist-360)/N if twist<0 - rise *= helical_nstart; - twist *= helical_nstart; - while (twist > 180.) twist -= 360.; - while (twist < -180.) twist += 360.; - -} - bool checkParametersFor3DHelicalReconstruction( bool ignore_symmetry, bool do_symmetry_local_refinement, @@ -4091,6 +4070,22 @@ void flipPsiTiltForHelicalSegment( new_tilt = 180. - old_tilt; } +void getNstartHelicalTwistAndRise(RFLOAT &twist, + RFLOAT &rise, + int helical_nstart) +{ + + // Shaoda's formula (which need to be inverted, as we want original N-start rise and twist back) + // rise_1-start = rise / N + // twist_1-start = (twist+360)/N if twist>0 + // twist_1-start = (twist-360)/N if twist<0 + rise *= helical_nstart; + twist *= helical_nstart; + while (twist > 180.) twist -= 360.; + while (twist < -180.) twist += 360.; + +} + //#define DEBUG_HELICAL_UPDATE_ANGULAR_PRIORS void updatePriorsForOneHelicalTube( std::vector& list, diff --git a/src/helix.h b/src/helix.h index a4df772b1..7d4c71d34 100644 --- a/src/helix.h +++ b/src/helix.h @@ -106,12 +106,7 @@ RFLOAT getHelicalSigma2Rot( RFLOAT helical_twist_deg, RFLOAT helical_offset_step_Angst, RFLOAT rot_step_deg, - RFLOAT old_sigma2_rot, - int helical_nstart); - -void getNstartHelicalTwistAndRise(RFLOAT &twist, - RFLOAT &rise, - int helical_nstart); + RFLOAT old_sigma2_rot); bool checkParametersFor3DHelicalReconstruction( bool ignore_symmetry, @@ -433,7 +428,7 @@ class HelicalSegmentPriorInfoEntry ~HelicalSegmentPriorInfoEntry() { clear(); }; - void checkPsiPolarity(); + void checkPsiPolarity(); bool operator<(const HelicalSegmentPriorInfoEntry &rhs) const; }; @@ -445,6 +440,11 @@ void flipPsiTiltForHelicalSegment( RFLOAT& new_psi, RFLOAT& new_tilt); +void getNstartHelicalTwistAndRise(RFLOAT &twist, + RFLOAT &rise, + int helical_nstart); + + void updatePriorsForOneHelicalTube( std::vector& list, int sid, diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 02bacf253..9904edd8e 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -2376,7 +2376,7 @@ void MlOptimiser::initialiseGeneral(int rank) { // Aug20,2015 - Shaoda, Helical refinement RFLOAT rottilt_step = sampling.getAngularSampling(adaptive_oversampling); - mymodel.sigma2_rot = getHelicalSigma2Rot(helical_rise_initial, helical_twist_initial, sampling.helical_offset_step, rottilt_step, mymodel.sigma2_rot, helical_nstart); + mymodel.sigma2_rot = getHelicalSigma2Rot(helical_rise_initial, helical_twist_initial, sampling.helical_offset_step, rottilt_step, mymodel.sigma2_rot); } if (particle_diameter < 0.) @@ -9946,7 +9946,7 @@ void MlOptimiser::updateAngularSampling(bool myverb) // Aug20,2015 - Shaoda, Helical refinement if ( (do_helical_refine) && (!ignore_helical_symmetry) ) - mymodel.sigma2_rot = getHelicalSigma2Rot(helical_rise_initial, helical_twist_initial, sampling.helical_offset_step, new_rottilt_step, mymodel.sigma2_rot, helical_nstart); + mymodel.sigma2_rot = getHelicalSigma2Rot(helical_rise_initial, helical_twist_initial, sampling.helical_offset_step, new_rottilt_step, mymodel.sigma2_rot); } } } From 5a49fc45c63dcd6eae3585c497dcc9dd12ace0a3 Mon Sep 17 00:00:00 2001 From: scheres Date: Mon, 16 Jun 2025 09:42:22 +0100 Subject: [PATCH 128/189] also gigms2rot in mpi version --- src/ml_optimiser_mpi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ml_optimiser_mpi.cpp b/src/ml_optimiser_mpi.cpp index c74ba964f..e84666a81 100644 --- a/src/ml_optimiser_mpi.cpp +++ b/src/ml_optimiser_mpi.cpp @@ -3809,7 +3809,7 @@ void MlOptimiserMpi::updateAngularSamplingGrad(long int my_first_part_id, long i if ((do_helical_refine) && (!ignore_helical_symmetry)) mymodel.sigma2_rot = getHelicalSigma2Rot(helical_rise_initial, helical_twist_initial, sampling.helical_offset_step, new_rottilt_step, - mymodel.sigma2_rot, helical_nstart); + mymodel.sigma2_rot); } // Reset iteration counter From 6e29463f69c284e9f38a9ec75c99012036b2f9c9 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 19 Jun 2025 09:46:35 +0100 Subject: [PATCH 129/189] patch from nvidia to allow compilation with cuda13 --- CMakeLists.txt | 2 +- cmake/BuildTypes.cmake | 2 +- src/acc/cuda/cuda_benchmark_utils.h | 2 +- src/acc/cuda/cuda_kernels/helper.cu | 172 --------------------------- src/acc/cuda/cuda_kernels/helper.cuh | 163 ++++++++++++++++++++++++- src/acc/utilities.h | 1 + src/ml_optimiser.cpp | 2 +- tests/CMakeLists.txt | 2 +- 8 files changed, 166 insertions(+), 180 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 10471e460..0993425ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -388,7 +388,7 @@ else() set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") set(CMAKE_C_FLAGS "-std=c99 ${CMAKE_C_FLAGS}") - set(CMAKE_CXX_FLAGS "-fPIC -std=c++14 ${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS "-fPIC -std=c++17 ${CMAKE_CXX_FLAGS}") endif() # ----------------------------------------------------------Intel Compiler support -- diff --git a/cmake/BuildTypes.cmake b/cmake/BuildTypes.cmake index e11c9c9f7..82beb4fd4 100644 --- a/cmake/BuildTypes.cmake +++ b/cmake/BuildTypes.cmake @@ -4,7 +4,7 @@ # So -D__INTEL_COMPILER just manages to avoid compiling float128-targets (see fftw3.h, for instance). # Add -G to allow cuda-gdb to break inside kernels. if(CUDA) - set(EXTRA_NVCC_FLAGS "-D__INTEL_COMPILER --default-stream per-thread --std=c++14") +# set(EXTRA_NVCC_FLAGS "-D__INTEL_COMPILER --default-stream per-thread --std=c++14") set(RELION_NVCC_FLAGS "${CUDARCH} ${WARN_DBL} ${EXTRA_NVCC_FLAGS}" CACHE STRING "" FORCE) elseif (HIP) if (${HIP_VERSION} VERSION_LESS "5.3" ) diff --git a/src/acc/cuda/cuda_benchmark_utils.h b/src/acc/cuda/cuda_benchmark_utils.h index 97493b198..f08fdec19 100644 --- a/src/acc/cuda/cuda_benchmark_utils.h +++ b/src/acc/cuda/cuda_benchmark_utils.h @@ -19,7 +19,7 @@ #define GTOC(timer,timing) (timer.cuda_gpu_toc(timing)) #define GATHERGPUTIMINGS(timer) (timer.cuda_gpu_printtictoc()) #elif defined CUDA_PROFILING - #include + #include #define CTIC(timer,timing) (nvtxRangePush(timing)) #define CTOC(timer,timing) (nvtxRangePop()) #define GTIC(timer,timing) diff --git a/src/acc/cuda/cuda_kernels/helper.cu b/src/acc/cuda/cuda_kernels/helper.cu index ff449dae7..c28258f96 100644 --- a/src/acc/cuda/cuda_kernels/helper.cu +++ b/src/acc/cuda/cuda_kernels/helper.cu @@ -778,178 +778,6 @@ __global__ void cuda_kernel_square( A[pixel] = A[pixel]*A[pixel]; } -template -__global__ void cuda_kernel_make_eulers_2D( - XFLOAT *alphas, - XFLOAT *eulers, - unsigned orientation_num) -{ - unsigned oid = blockIdx.x * blockDim.x + threadIdx.x; //Orientation id - - if (oid >= orientation_num) - return; - - XFLOAT ca, sa; - XFLOAT a = alphas[oid] * (XFLOAT)PI / (XFLOAT)180.0; - -#ifdef ACC_DOUBLE_PRECISION - sincos(a, &sa, &ca); -#else - sincosf(a, &sa, &ca); -#endif - - if(!invert) - { - eulers[9 * oid + 0] = ca;//00 - eulers[9 * oid + 1] = sa;//01 - eulers[9 * oid + 2] = 0 ;//02 - eulers[9 * oid + 3] =-sa;//10 - eulers[9 * oid + 4] = ca;//11 - eulers[9 * oid + 5] = 0 ;//12 - eulers[9 * oid + 6] = 0 ;//20 - eulers[9 * oid + 7] = 0 ;//21 - eulers[9 * oid + 8] = 1 ;//22 - } - else - { - eulers[9 * oid + 0] = ca;//00 - eulers[9 * oid + 1] =-sa;//10 - eulers[9 * oid + 2] = 0 ;//20 - eulers[9 * oid + 3] = sa;//01 - eulers[9 * oid + 4] = ca;//11 - eulers[9 * oid + 5] = 0 ;//21 - eulers[9 * oid + 6] = 0 ;//02 - eulers[9 * oid + 7] = 0 ;//12 - eulers[9 * oid + 8] = 1 ;//22 - } -} - -template -__global__ void cuda_kernel_make_eulers_3D( - XFLOAT *alphas, - XFLOAT *betas, - XFLOAT *gammas, - XFLOAT *eulers, - unsigned orientation_num, - XFLOAT *L, - XFLOAT *R) -{ - XFLOAT a(0.f),b(0.f),g(0.f), A[9],B[9]; - XFLOAT ca, sa, cb, sb, cg, sg, cc, cs, sc, ss; - - unsigned oid = blockIdx.x * blockDim.x + threadIdx.x; //Orientation id - - if (oid >= orientation_num) - return; - - for (int i = 0; i < 9; i ++) - B[i] = (XFLOAT) 0.f; - - a = alphas[oid] * (XFLOAT)PI / (XFLOAT)180.0; - b = betas[oid] * (XFLOAT)PI / (XFLOAT)180.0; - g = gammas[oid] * (XFLOAT)PI / (XFLOAT)180.0; - -#ifdef ACC_DOUBLE_PRECISION - sincos(a, &sa, &ca); - sincos(b, &sb, &cb); - sincos(g, &sg, &cg); -#else - sincosf(a, &sa, &ca); - sincosf(b, &sb, &cb); - sincosf(g, &sg, &cg); -#endif - - cc = cb * ca; - cs = cb * sa; - sc = sb * ca; - ss = sb * sa; - - A[0] = ( cg * cc - sg * sa);//00 - A[1] = ( cg * cs + sg * ca);//01 - A[2] = (-cg * sb ) ;//02 - A[3] = (-sg * cc - cg * sa);//10 - A[4] = (-sg * cs + cg * ca);//11 - A[5] = ( sg * sb ) ;//12 - A[6] = ( sc ) ;//20 - A[7] = ( ss ) ;//21 - A[8] = ( cb ) ;//22 - - if (doR) - { - for (int i = 0; i < 9; i++) - B[i] = 0.f; - - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - for (int k = 0; k < 3; k++) - B[i * 3 + j] += A[i * 3 + k] * R[k * 3 + j]; - } - else - for (int i = 0; i < 9; i++) - B[i] = A[i]; - - if (doL) - { - if (doR) - for (int i = 0; i < 9; i++) - A[i] = B[i]; - - for (int i = 0; i < 9; i++) - B[i] = 0.f; - - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - for (int k = 0; k < 3; k++) - B[i * 3 + j] += L[i * 3 + k] * A[k * 3 + j]; - } - - if(invert) - { - - if (doL) // this could have anisotropy, so inverse neq transpose!!! - { - XFLOAT det; - det = B[0] * (B[4] * B[8] - B[7] * B[5]) - - B[1] * (B[3] * B[8] - B[6] * B[5]) - + B[2] * (B[3] * B[7] - B[6] * B[4]); - - eulers[9 * oid + 0] = (B[4] * B[8] - B[7] * B[5]) / det; - eulers[9 * oid + 1] = (B[7] * B[2] - B[1] * B[8]) / det; - eulers[9 * oid + 2] = (B[1] * B[5] - B[4] * B[2]) / det; - eulers[9 * oid + 3] = (B[5] * B[6] - B[8] * B[3]) / det; - eulers[9 * oid + 4] = (B[8] * B[0] - B[2] * B[6]) / det; - eulers[9 * oid + 5] = (B[2] * B[3] - B[5] * B[0]) / det; - eulers[9 * oid + 6] = (B[3] * B[7] - B[6] * B[4]) / det; - eulers[9 * oid + 7] = (B[6] * B[1] - B[0] * B[7]) / det; - eulers[9 * oid + 8] = (B[0] * B[4] - B[3] * B[1]) / det; - } - else - { - - eulers[9 * oid + 0] = B[0];//00 - eulers[9 * oid + 1] = B[3];//01 - eulers[9 * oid + 2] = B[6];//02 - eulers[9 * oid + 3] = B[1];//10 - eulers[9 * oid + 4] = B[4];//11 - eulers[9 * oid + 5] = B[7];//12 - eulers[9 * oid + 6] = B[2];//20 - eulers[9 * oid + 7] = B[5];//21 - eulers[9 * oid + 8] = B[8];//22 - } - } - else - { - eulers[9 * oid + 0] = B[0];//00 - eulers[9 * oid + 1] = B[1];//10 - eulers[9 * oid + 2] = B[2];//20 - eulers[9 * oid + 3] = B[3];//01 - eulers[9 * oid + 4] = B[4];//11 - eulers[9 * oid + 5] = B[5];//21 - eulers[9 * oid + 6] = B[6];//02 - eulers[9 * oid + 7] = B[7];//12 - eulers[9 * oid + 8] = B[8];//22 - } -} __global__ void cuda_kernel_allweights_to_mweights( unsigned long * d_iorient, diff --git a/src/acc/cuda/cuda_kernels/helper.cuh b/src/acc/cuda/cuda_kernels/helper.cuh index 9651d342d..70798848f 100644 --- a/src/acc/cuda/cuda_kernels/helper.cuh +++ b/src/acc/cuda/cuda_kernels/helper.cuh @@ -668,9 +668,49 @@ template __global__ void cuda_kernel_make_eulers_2D( XFLOAT *alphas, XFLOAT *eulers, - unsigned orientation_num); + unsigned orientation_num) +{ + unsigned oid = blockIdx.x * blockDim.x + threadIdx.x; //Orientation id + + if (oid >= orientation_num) + return; + + XFLOAT ca, sa; + XFLOAT a = alphas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + +#ifdef ACC_DOUBLE_PRECISION + sincos(a, &sa, &ca); +#else + sincosf(a, &sa, &ca); +#endif + + if(!invert) + { + eulers[9 * oid + 0] = ca;//00 + eulers[9 * oid + 1] = sa;//01 + eulers[9 * oid + 2] = 0 ;//02 + eulers[9 * oid + 3] =-sa;//10 + eulers[9 * oid + 4] = ca;//11 + eulers[9 * oid + 5] = 0 ;//12 + eulers[9 * oid + 6] = 0 ;//20 + eulers[9 * oid + 7] = 0 ;//21 + eulers[9 * oid + 8] = 1 ;//22 + } + else + { + eulers[9 * oid + 0] = ca;//00 + eulers[9 * oid + 1] =-sa;//10 + eulers[9 * oid + 2] = 0 ;//20 + eulers[9 * oid + 3] = sa;//01 + eulers[9 * oid + 4] = ca;//11 + eulers[9 * oid + 5] = 0 ;//21 + eulers[9 * oid + 6] = 0 ;//02 + eulers[9 * oid + 7] = 0 ;//12 + eulers[9 * oid + 8] = 1 ;//22 + } +} -template +template __global__ void cuda_kernel_make_eulers_3D( XFLOAT *alphas, XFLOAT *betas, @@ -678,7 +718,124 @@ __global__ void cuda_kernel_make_eulers_3D( XFLOAT *eulers, unsigned orientation_num, XFLOAT *L, - XFLOAT *R); + XFLOAT *R) +{ + XFLOAT a(0.f),b(0.f),g(0.f), A[9],B[9]; + XFLOAT ca, sa, cb, sb, cg, sg, cc, cs, sc, ss; + + unsigned oid = blockIdx.x * blockDim.x + threadIdx.x; //Orientation id + + if (oid >= orientation_num) + return; + + for (int i = 0; i < 9; i ++) + B[i] = (XFLOAT) 0.f; + + a = alphas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + b = betas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + g = gammas[oid] * (XFLOAT)PI / (XFLOAT)180.0; + +#ifdef ACC_DOUBLE_PRECISION + sincos(a, &sa, &ca); + sincos(b, &sb, &cb); + sincos(g, &sg, &cg); +#else + sincosf(a, &sa, &ca); + sincosf(b, &sb, &cb); + sincosf(g, &sg, &cg); +#endif + + cc = cb * ca; + cs = cb * sa; + sc = sb * ca; + ss = sb * sa; + + A[0] = ( cg * cc - sg * sa);//00 + A[1] = ( cg * cs + sg * ca);//01 + A[2] = (-cg * sb ) ;//02 + A[3] = (-sg * cc - cg * sa);//10 + A[4] = (-sg * cs + cg * ca);//11 + A[5] = ( sg * sb ) ;//12 + A[6] = ( sc ) ;//20 + A[7] = ( ss ) ;//21 + A[8] = ( cb ) ;//22 + + if (doR) + { + for (int i = 0; i < 9; i++) + B[i] = 0.f; + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + for (int k = 0; k < 3; k++) + B[i * 3 + j] += A[i * 3 + k] * R[k * 3 + j]; + } + else + for (int i = 0; i < 9; i++) + B[i] = A[i]; + + if (doL) + { + if (doR) + for (int i = 0; i < 9; i++) + A[i] = B[i]; + + for (int i = 0; i < 9; i++) + B[i] = 0.f; + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + for (int k = 0; k < 3; k++) + B[i * 3 + j] += L[i * 3 + k] * A[k * 3 + j]; + } + + if(invert) + { + + if (doL) // this could have anisotropy, so inverse neq transpose!!! + { + XFLOAT det; + det = B[0] * (B[4] * B[8] - B[7] * B[5]) + - B[1] * (B[3] * B[8] - B[6] * B[5]) + + B[2] * (B[3] * B[7] - B[6] * B[4]); + + eulers[9 * oid + 0] = (B[4] * B[8] - B[7] * B[5]) / det; + eulers[9 * oid + 1] = (B[7] * B[2] - B[1] * B[8]) / det; + eulers[9 * oid + 2] = (B[1] * B[5] - B[4] * B[2]) / det; + eulers[9 * oid + 3] = (B[5] * B[6] - B[8] * B[3]) / det; + eulers[9 * oid + 4] = (B[8] * B[0] - B[2] * B[6]) / det; + eulers[9 * oid + 5] = (B[2] * B[3] - B[5] * B[0]) / det; + eulers[9 * oid + 6] = (B[3] * B[7] - B[6] * B[4]) / det; + eulers[9 * oid + 7] = (B[6] * B[1] - B[0] * B[7]) / det; + eulers[9 * oid + 8] = (B[0] * B[4] - B[3] * B[1]) / det; + } + else + { + + eulers[9 * oid + 0] = B[0];//00 + eulers[9 * oid + 1] = B[3];//01 + eulers[9 * oid + 2] = B[6];//02 + eulers[9 * oid + 3] = B[1];//10 + eulers[9 * oid + 4] = B[4];//11 + eulers[9 * oid + 5] = B[7];//12 + eulers[9 * oid + 6] = B[2];//20 + eulers[9 * oid + 7] = B[5];//21 + eulers[9 * oid + 8] = B[8];//22 + } + } + else + { + eulers[9 * oid + 0] = B[0];//00 + eulers[9 * oid + 1] = B[1];//10 + eulers[9 * oid + 2] = B[2];//20 + eulers[9 * oid + 3] = B[3];//01 + eulers[9 * oid + 4] = B[4];//11 + eulers[9 * oid + 5] = B[5];//21 + eulers[9 * oid + 6] = B[6];//02 + eulers[9 * oid + 7] = B[7];//12 + eulers[9 * oid + 8] = B[8];//22 + } +} #define INIT_VALUE_BLOCK_SIZE 512 template< typename T> diff --git a/src/acc/utilities.h b/src/acc/utilities.h index be04bb474..f407bdbae 100644 --- a/src/acc/utilities.h +++ b/src/acc/utilities.h @@ -709,6 +709,7 @@ void acc_make_eulers_2D(int grid_size, int block_size, #endif } + template void acc_make_eulers_3D(int grid_size, int block_size, deviceStream_t stream, diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 9904edd8e..43d13ec82 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -45,7 +45,7 @@ #include "src/ml_optimiser.h" #ifdef _CUDA_ENABLED #include "src/acc/cuda/cuda_ml_optimiser.h" -#include +#include #include #elif _HIP_ENABLED #include "src/acc/hip/hip_ml_optimiser.h" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 47d77090a..9a501492d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,7 +7,7 @@ target_link_libraries(tests relion_lib) target_link_libraries(tests ${FFTW_LIBRARIES}) target_link_libraries(tests ${TIFF_LIBRARIES}) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") include_directories(tests PRIVATE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/tests) include_directories(tests PRIVATE ${FFTW_INCLUDES}) From 514f17104abb749902a1e8621813632d992a559f Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 20 Jun 2025 12:43:28 +0100 Subject: [PATCH 130/189] make dependence on c++17 explicit --- cmake/BuildTypes.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/BuildTypes.cmake b/cmake/BuildTypes.cmake index 82beb4fd4..81637764e 100644 --- a/cmake/BuildTypes.cmake +++ b/cmake/BuildTypes.cmake @@ -4,7 +4,7 @@ # So -D__INTEL_COMPILER just manages to avoid compiling float128-targets (see fftw3.h, for instance). # Add -G to allow cuda-gdb to break inside kernels. if(CUDA) -# set(EXTRA_NVCC_FLAGS "-D__INTEL_COMPILER --default-stream per-thread --std=c++14") + set(EXTRA_NVCC_FLAGS "-D__INTEL_COMPILER --default-stream per-thread --std=c++17") set(RELION_NVCC_FLAGS "${CUDARCH} ${WARN_DBL} ${EXTRA_NVCC_FLAGS}" CACHE STRING "" FORCE) elseif (HIP) if (${HIP_VERSION} VERSION_LESS "5.3" ) From 88752b127168b8ad238dfb8a6b2aa38ebfd6d7d8 Mon Sep 17 00:00:00 2001 From: scheres Date: Mon, 23 Jun 2025 17:43:47 +0100 Subject: [PATCH 131/189] implemented option to paint masks in 2D images --- src/displayer.cpp | 289 ++++++++++++++++++++++++++++++++++++++++++++++ src/displayer.h | 48 ++++++++ 2 files changed, 337 insertions(+) diff --git a/src/displayer.cpp b/src/displayer.cpp index f0e5fc2ae..3b2db0753 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -420,6 +420,95 @@ void DisplayBox::setData(MultidimArray &img, MultidimArray &fom_ } +void DisplayBox::setData(MultidimArray &img, MultidimArray &mask_img, int _ipos, + RFLOAT _minval, RFLOAT _maxval, RFLOAT _scale, bool do_relion_scale) +{ + scale = _scale; + minval = _minval; + maxval = _maxval; + ipos = _ipos; + selected = NOTSELECTED; + fom_is_grey_instead = false; + + // For volumes only show the central slice + if (ZSIZE(img) > 1) + { + MultidimArray slice; + img.getSlice(ZSIZE(img)/2, slice); + img=slice; + } + + // create array for the scaled image data + xsize_data = CEIL(XSIZE(img) * scale); + ysize_data = CEIL(YSIZE(img) * scale); + xoff = (xsize_data < w() ) ? (w() - xsize_data) / 2 : 0; + yoff = (ysize_data < h() ) ? (h() - ysize_data) / 2 : 0; + img_data = new unsigned char [3 * xsize_data * ysize_data]; + RFLOAT range = maxval - minval; + RFLOAT step = range / 255; // 8-bit scaling range from 0 to 255 + RFLOAT* old_ptr=NULL; + long int n; + + // For micrographs use relion-scaling to avoid bias in down-sampled positions + // For multi-image viewers, do not use this scaling as it is slower... + if (do_relion_scale && ABS(scale - 1.0) > 0.01) + { + selfScaleToSize(img, xsize_data, ysize_data); + selfScaleToSize(mask_img, xsize_data, ysize_data); + } + + // Use the same nearest-neighbor algorithm as in the copy function of Fl_Image... + if (ABS(scale - 1.0) > 0.01 && !do_relion_scale) + { + int xmod = XSIZE(img) % xsize_data; + int xstep = XSIZE(img) / xsize_data; + int ymod = YSIZE(img) % ysize_data; + int ystep = YSIZE(img) / ysize_data; + int line_d = XSIZE(img); + int dx, dy, sy, xerr, yerr; + + for (dy = ysize_data, sy = 0, yerr = ysize_data, n = 0; dy > 0; dy --) + { + for (dx = xsize_data, xerr = xsize_data, old_ptr = img.data + sy * line_d; dx > 0; dx --, n++) + { + RFLOAT val = (DIRECT_MULTIDIM_ELEM(img, n) - minval) / range; + img_data[3*n] = FLOOR(127*val); + img_data[3*n+1] = FLOOR(127*val); + img_data[3*n+2] = FLOOR(127*val); + if (DIRECT_MULTIDIM_ELEM(mask_img, n) > 0.5) img_data[3*n] += 127; + + old_ptr += xstep; + xerr -= xmod; + if (xerr <= 0) + { + xerr += xsize_data; + old_ptr += 1; + } + } + + sy += ystep; + yerr -= ymod; + if (yerr <= 0) + { + yerr += ysize_data; + sy ++; + } + } + } + else + { + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(img) + { + RFLOAT val = (DIRECT_MULTIDIM_ELEM(img, n) - minval) / range; + img_data[3*n] = FLOOR(127*val); + img_data[3*n+1] = FLOOR(127*val); + img_data[3*n+2] = FLOOR(127*val); + if (DIRECT_MULTIDIM_ELEM(mask_img, n) > 0.5) img_data[3*n] += 127; + } + } +} + + @@ -582,6 +671,28 @@ int basisViewerWindow::fillPickerViewerCanvas(MultidimArray image, Multi return Fl::run(); } + +int basisViewerWindow::fillMaskerViewerCanvas(MultidimArray image, MultidimArray mask_image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, + RFLOAT _scale, int _pencil_radius, FileName _fn_mask) +{ + // Scroll bars + Fl_Scroll scroll(0, 0, w(), h()); + int xsize_canvas = CEIL(XSIZE(image)*_scale); + int ysize_canvas = CEIL(YSIZE(image)*_scale); + maskerViewerCanvas canvas(0, 0, xsize_canvas, ysize_canvas); + canvas.pencil_radius = _pencil_radius; + canvas.mask_scale = _scale; + canvas.SetScroll(&scroll); + if (mask_image.xdim > 0) canvas.Imask() = mask_image; + else canvas.Imask().resize(image); + canvas.fill(image, canvas.Imask(), _minval, _maxval, _sigma_contrast, _scale); + + canvas.fn_mask = _fn_mask; + resizable(*this); + show(); + return Fl::run(); +} + int basisViewerWindow::fillSingleViewerCanvas(MultidimArray image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale) { // Scroll bars @@ -829,6 +940,20 @@ void basisViewerCanvas::fill(MultidimArray &image, MultidimArray boxes.push_back(my_box); } +void basisViewerCanvas::fill(MultidimArray &image, MultidimArray &mask_image, + RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale) +{ + xoff = yoff = 0; + nrow = ncol = 1; + getImageContrast(image, _minval, _maxval, _sigma_contrast); + xsize_box = CEIL(_scale * XSIZE(image)); + ysize_box = CEIL(_scale * YSIZE(image)); + DisplayBox* my_box = new DisplayBox(0, 0, xsize_box, ysize_box, "dummy"); + my_box->setData(image, mask_image, 0, _minval, _maxval, _scale, true); + my_box->redraw(); + boxes.push_back(my_box); +} + void basisViewerCanvas::draw() { for (int ipos = 0 ; ipos < boxes.size(); ipos++) @@ -1783,6 +1908,7 @@ void basisViewerCanvas::setFOMThreshold() } + int popupSelectionTypeWindow::fill() { color(GUI_BACKGROUND_COLOR); @@ -2419,6 +2545,151 @@ void pickerViewerCanvas::printHelp() std::cout <<" + Refresh picked particles by moving out of the window and back in again ..." << std::endl; } +int maskerViewerCanvas::handle(int ev) +{ + const int button = Fl::event_button() ; + const bool with_shift = (Fl::event_shift() != 0); + const bool with_control = (Fl::event_ctrl() != 0); + const int key = Fl::event_key(); + has_dragged = false; + + int xc = (int)Fl::event_x() - scroll->x() + scroll->hscrollbar.value(); + int yc = (int)Fl::event_y() - scroll->y() + scroll->scrollbar.value(); + if ((ev==FL_PUSH || ev==FL_DRAG) && (button == FL_LEFT_MOUSE || button == FL_MIDDLE_MOUSE)) + { + RFLOAT myval = (button == FL_LEFT_MOUSE) ? 1. : 0.; + int xstart = xc - pencil_radius; + xstart = std::max(0, xstart); + int xstop = xc + pencil_radius; + xstop = std::min((int)(Imask().xdim), xstop); + int ystart = yc - pencil_radius; + ystart = std::max(0, ystart); + int ystop = yc + pencil_radius; + ystop = std::min((int)(Imask().ydim), ystop); + for (int y=ystart; y < ystop; y++) + { + int dy2 = (y-yc)*(y-yc); + for (int x=xstart; x < xstop; x++) + { + int d2 = dy2 + (x-xc)*(x-xc); + if (d2 < pencil_radius*pencil_radius) + { + if (button == FL_LEFT_MOUSE && DIRECT_A2D_ELEM(Imask(), y, x) < 0.5 ) + { + long n = Imask().xdim * y + x; + boxes[0]->img_data[3*n] += 127; + DIRECT_A2D_ELEM(Imask(), y, x) = 1.; + } + else if (button == FL_MIDDLE_MOUSE && DIRECT_A2D_ELEM(Imask(), y, x) > 0.5 ) + { + long n = Imask().xdim * y + x; + boxes[0]->img_data[3*n] -= 127; + DIRECT_A2D_ELEM(Imask(), y, x) = 0.; + } + } + } + } + boxes[0]->redraw(); + return 1; + } + else if ((button == FL_RIGHT_MOUSE) || (button == FL_LEFT_MOUSE && with_control)) + { + redraw(); + Fl_Menu_Item rclick_menu[] = { + { "Save image with mask (CTRL-s)" }, + { "Set pencil radius" }, + { "Help" }, + { "Quit (CTRL-q)" }, + { 0 } + }; + const Fl_Menu_Item *m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, 0); + if ( !m ) + return 0; + else if ( strcmp(m->label(), "Save image with mask (CTRL-s)") == 0 ) + saveMask(); + else if ( strcmp(m->label(), "Set pencil radius") == 0 ) + setPencilRadius(); + else if ( strcmp(m->label(), "Help") == 0 ) + printHelp(); + else if ( strcmp(m->label(), "Quit (CTRL-q)") == 0 ) + exit(0); + redraw(); + return 1; // (tells caller we handled this event) + } + // Update the drawing every time something happens .... + else if (ev==FL_RELEASE || ev==FL_LEAVE || ev==FL_ENTER || ev==FL_MOVE || ev == FL_FOCUS || ev == FL_UNFOCUS) + { + redraw(); + return 1; + } + // CTRL-s will save the coordinates in a picker window + else if (with_control) + { + if (key == 's') + { + saveMask(); + sleep(1); // to prevent multiple saves... dirty but don't know how to do this otherwise... + return 1; // (tells caller we handled this event) + } + else if (key == 'q') + { + sleep(1); + exit(0); + return 1; // (tells caller we handled this event) + } + else if (key >= '1' && key <= '6') + { + std::cout << "debug key = " << key << std::endl; + current_selection_type = key - '0'; + return 1; + } + } + return 0; +} + + +void maskerViewerCanvas::saveMask() +{ + if (fn_mask == "") + { + char *newfile; + newfile = fl_file_chooser("Save mask image as?", "*.mrc", ""); + if (newfile == NULL) + return; + FileName fn_tmp(newfile); + fn_mask = fn_tmp; + } + + FileName fn_dirs = fn_mask.beforeLastOf("/"); + if (!(exists(fn_dirs))) + { + std::string command = "mkdir -p " + fn_dirs; + int res = system(command.c_str()); + } + Imask.write(fn_mask); + + std::cout << "Saved mask as "<< fn_mask << std::endl; + +} + +void maskerViewerCanvas::setPencilRadius() +{ + const char *rad; + std::string currentval = floatToString(pencil_radius/mask_scale); + rad = fl_input("Pencil radius (pixel): ", currentval.c_str()); + if (rad == NULL) + return; + std::string newval(rad); + pencil_radius = mask_scale * textToFloat(rad); + +} + +void maskerViewerCanvas::printHelp() +{ + std::cout <<" + Left-mouse click: add to mask " << std::endl; + std::cout <<" + Middle-mouse click: delete from mask " << std::endl; +} + void singleViewerCanvas::printMetaData() { boxes[0]->MDimg.write(std::cout); @@ -2913,6 +3184,10 @@ void Displayer::read(int argc, char **argv) fom_min = textToFloat(parser.getOption("--fom_min", "Pixel value for lowest FOM value (black)", "0")); fom_max = textToFloat(parser.getOption("--fom_max", "Pixel value for highest FOM value (yellow)", "0")); + int mask_section = parser.addSection("Mask design options"); + do_mask = parser.checkOption("--paint_mask", "Paint mask in input image"); + fn_mask = parser.getOption("--fn_mask", "Name for MRC file of the designed mask", ""); + verb = textToInteger(parser.getOption("--verb", "Verbosity", "1")); // Check for errors in the command-line option @@ -3206,6 +3481,20 @@ void Displayer::run() win.fillPickerViewerCanvas(img(), fom_img(), minval, maxval, sigma_contrast, scale, coord_scale, ROUND(scale*particle_radius), do_pick_startend, do_pick_lines, fn_coords, fn_color, fn_in, color_label, color_blue_value, color_red_value, minimum_pick_fom, fom_min, fom_max); } + else if (do_mask) + { + colour_scheme = BLACKGREYREDSCALE; + + Image img, Imask; + img.read(fn_in); + if (fn_mask != "") + { + Imask.read(fn_mask); + } + basisViewerWindow win(CEIL(scale*XSIZE(img())), CEIL(scale*YSIZE(img())), fn_in.c_str()); + win.fillMaskerViewerCanvas(img(), Imask(), minval, maxval, sigma_contrast, scale, ROUND(scale*particle_radius), fn_mask); + + } else if (fn_in.isStarFile()) { if (fn_in.contains("_optimiser.star")) diff --git a/src/displayer.h b/src/displayer.h index 244bd1497..6b2ae6737 100644 --- a/src/displayer.h +++ b/src/displayer.h @@ -118,6 +118,8 @@ class DisplayBox : public Fl_Box void setData(MultidimArray &img, MultidimArray &fom_img, MetaDataContainer *MDCin, int ipos, RFLOAT minval, RFLOAT maxval, RFLOAT _fom_min, RFLOAT _fom_max, RFLOAT _scale, bool do_relion_scale = false); + void setData(MultidimArray &img, MultidimArray &mask_img, int _ipos, + RFLOAT _minval, RFLOAT _maxval, RFLOAT _scale, bool do_relion_scale); // Destructor ~DisplayBox() @@ -158,6 +160,9 @@ class basisViewerWindow : public Fl_Window int _particle_radius, bool do_startend = false, bool do_lines = false, FileName _fn_coords = "", FileName _fn_color = "", FileName _fn_mic= "", FileName _color_label = "", RFLOAT _color_blue_value = 0., RFLOAT _color_red_value = 1., RFLOAT _minimum_pick_fom = -999., RFLOAT _min_fom = 0., RFLOAT _max_fom = 0.); + int fillMaskerViewerCanvas(MultidimArray image, MultidimArray mask_image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, + RFLOAT _scale, int _pencil_radius, FileName _fn_mask); + }; class basisViewerCanvas : public Fl_Widget @@ -198,6 +203,8 @@ class basisViewerCanvas : public Fl_Widget RFLOAT lowpass = -1.0, RFLOAT highpass = -1.0); void fill(MultidimArray &image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale = 1.); void fill(MultidimArray &image, MultidimArray &fom_image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _fom_min, RFLOAT _fom_max, RFLOAT _sigma_contrast, RFLOAT _scale = 1.); + void fill(MultidimArray &image, MultidimArray &mask_image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, RFLOAT _scale); + void setSelectionType(); void setFOMThreshold(); }; @@ -442,6 +449,40 @@ class pickerViewerCanvas : public basisViewerCanvas void viewExtractedParticles(); }; + +class maskerViewerCanvas : public basisViewerCanvas +{ +protected: + int handle(int ev); + void draw() {}; + +public: + + int pencil_radius; + + // Scale for pencil_radius + RFLOAT mask_scale; + + // Filename of the mask + FileName fn_mask; + + // Image with the mask + Image Imask; + + // Constructor with w x h size of the window and a title + maskerViewerCanvas(int X, int Y, int W, int H, const char* title=0): basisViewerCanvas(X,Y,W, H, title) { } + +private: + + // Functionalities for popup menu + void saveMask(); + void setPencilRadius(); + void clearMask(); + void printHelp(); + +}; + + // This class only puts scrollbars around the resizable canvas class displayerGuiWindow : public Fl_Window { @@ -669,6 +710,13 @@ class Displayer // Shell for calling Topaz FileName fn_shell; + // Flag to design mask + bool do_mask; + + // Filename for coordinates star file + FileName fn_mask; + + public: // Read command line arguments void read(int argc, char **argv); From 1e5e57442491b877e6fcfd104db2e60ca78d67aa Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 24 Jun 2025 09:24:19 +0100 Subject: [PATCH 132/189] made amypicker2 work --- scripts/trace_amyloids.in | 23 +++++++++++++++++------ src/amyloid_finder.cpp | 8 ++++++-- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index cd0fad341..1b83ed15c 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -90,12 +90,12 @@ class OutConv(nn.Module): class UNet(nn.Module): - def __init__(self, n_channels, n_classes, embedding_dim=32, bilinear=False): + def __init__(self, n_channels, n_classes, embedding_dim=32, dropout=0.25, bilinear=False): super().__init__() self.n_channels = n_channels self.n_classes = n_classes self.bilinear = bilinear - + self.dropout = dropout self.base_channel_sz = 128 self.inc = DoubleConv(n_channels, self.base_channel_sz) @@ -104,7 +104,7 @@ class UNet(nn.Module): self.down3 = Down(4 * self.base_channel_sz, 8 * self.base_channel_sz) factor = 2 if bilinear else 1 self.down4 = Down(8 * self.base_channel_sz, 16 * self.base_channel_sz // factor) - + self.dropout_layer = nn.Dropout2d(p=dropout) self.up1 = Up(16 * self.base_channel_sz, 8 * self.base_channel_sz // factor, bilinear) self.up2 = Up(8 * self.base_channel_sz, 4 * self.base_channel_sz // factor, bilinear) self.up3 = Up(4 * self.base_channel_sz, 2 * self.base_channel_sz // factor, bilinear) @@ -118,14 +118,24 @@ class UNet(nn.Module): def forward(self, x): x1 = self.inc(x) + x1 = self.dropout_layer(x1) x2 = self.down1(x1) + x2 = self.dropout_layer(x2) x3 = self.down2(x2) + x3 = self.dropout_layer(x3) x4 = self.down3(x3) + x4 = self.dropout_layer(x4) x5 = self.down4(x4) + # Apply dropout at bottleneck + x5 = self.dropout_layer(x5) + x = self.up1(x5, x4) + x = self.dropout_layer(x) x = self.up2(x, x3) + x = self.dropout_layer(x) x = self.up3(x, x2) + x = self.dropout_layer(x) x = self.up4(x, x1) seg_logits = self.outc(x) @@ -385,7 +395,7 @@ def main(): # ------------------- Load model ---------------------- device = torch.device(args.device) - model = UNet.load_from_checkpoint(args.model_path, n_channels=2, n_classes=1) + model = UNet.load_from_checkpoint(args.model_path, n_channels=3, n_classes=1) model.eval().to(device) input_star = starfile.read(args.input) @@ -415,8 +425,9 @@ def main(): H, W = fom_image.shape - in_tensor = np.stack((fom_image, psi_image), axis=0) # shape (2,H,W) - in_tensor = torch.from_numpy(in_tensor[None]).to(device=device, dtype=half_dtype) # shape (1,2,H,W) + rad_psi = np.deg2rad(psi_image * 2) + in_tensor = np.stack((fom_image, np.sin(rad_psi), np.cos(rad_psi)), axis=0) + in_tensor = torch.from_numpy(in_tensor[None]).to(device=device, dtype=half_dtype) # shape (1,3,H,W) with torch.no_grad(): if device.type == "cuda": diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index aa9d97b03..95f956da8 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -46,7 +46,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) do_plot = parser.checkOption("--plot", "Display images with intermediate tracing results for each micrograph"); fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_trace_amyloids"); fn_other_args = parser.getOption("--other_args", "Other arguments for the python script", ""); - fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing","/public/EM/RELION/amypicker.ckpt"); + fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing","/public/EM/RELION/amypicker2.ckpt"); do_skip_tracing = parser.checkOption("--skip_tracing", "Skip tracing."); do_gpu = parser.checkOption("--gpu", "Use GPU acceleration when availiable"); gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); @@ -763,11 +763,11 @@ void AmyloidFinder::runTracingBatch(long int my_first, long int my_last, int my_ // takes from my_first to my_last! FileName fn_tracing_star = fn_odir + "input_trace_rank" + integerToString(my_rank) + ".star"; MetaDataTable MDtrace; + FileName fn_fom, fn_psi; for (long int imic = my_first; imic <= my_last; imic++) { MDtrace.addObject(); FileName fn_root = getOutputRootName(todo_micrographs_tracing[imic]); - FileName fn_fom, fn_psi; if (do_skip_fom) { long int imic_ori = idx_todo_micrographs_tracing[imic]; @@ -787,6 +787,10 @@ void AmyloidFinder::runTracingBatch(long int my_first, long int my_last, int my_ } MDtrace.write(fn_tracing_star); + Image It; + It.read(fn_fom, false); + down_angpix = It.samplingRateX(); + // hardcoded python script for now... FileName command = fn_exe; From d2d1fc22596eecc69b9fb0819c0828351a016251 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 24 Jun 2025 09:24:34 +0100 Subject: [PATCH 133/189] further tweaks to paint_mask in relion_display --- src/displayer.cpp | 3 ++- src/displayer.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/displayer.cpp b/src/displayer.cpp index 3b2db0753..4b68d8d19 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -3187,6 +3187,7 @@ void Displayer::read(int argc, char **argv) int mask_section = parser.addSection("Mask design options"); do_mask = parser.checkOption("--paint_mask", "Paint mask in input image"); fn_mask = parser.getOption("--fn_mask", "Name for MRC file of the designed mask", ""); + pencil_radius = textToFloat(parser.getOption("--pencil_radius", "Pencil radius to draw masks (in original pixels)", "10")); verb = textToInteger(parser.getOption("--verb", "Verbosity", "1")); @@ -3492,7 +3493,7 @@ void Displayer::run() Imask.read(fn_mask); } basisViewerWindow win(CEIL(scale*XSIZE(img())), CEIL(scale*YSIZE(img())), fn_in.c_str()); - win.fillMaskerViewerCanvas(img(), Imask(), minval, maxval, sigma_contrast, scale, ROUND(scale*particle_radius), fn_mask); + win.fillMaskerViewerCanvas(img(), Imask(), minval, maxval, sigma_contrast, scale, ROUND(scale*pencil_radius), fn_mask); } else if (fn_in.isStarFile()) diff --git a/src/displayer.h b/src/displayer.h index 6b2ae6737..a68585063 100644 --- a/src/displayer.h +++ b/src/displayer.h @@ -716,6 +716,8 @@ class Displayer // Filename for coordinates star file FileName fn_mask; + // pencil radius for drawing masks + int pencil_radius; public: // Read command line arguments From 26f66ea01ab0d35b99bcb7ef24fdd5be64ca9a1c Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 24 Jun 2025 14:57:56 +0100 Subject: [PATCH 134/189] now add_paint works ok --- src/displayer.cpp | 146 ++++++++++++++++++++++++---------------------- src/displayer.h | 5 +- 2 files changed, 81 insertions(+), 70 deletions(-) diff --git a/src/displayer.cpp b/src/displayer.cpp index 4b68d8d19..5e3619f00 100644 --- a/src/displayer.cpp +++ b/src/displayer.cpp @@ -673,7 +673,7 @@ int basisViewerWindow::fillPickerViewerCanvas(MultidimArray image, Multi int basisViewerWindow::fillMaskerViewerCanvas(MultidimArray image, MultidimArray mask_image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, - RFLOAT _scale, int _pencil_radius, FileName _fn_mask) + RFLOAT _scale, int _pencil_radius, FileName _fn_mask, RFLOAT _angpix) { // Scroll bars Fl_Scroll scroll(0, 0, w(), h()); @@ -682,11 +682,15 @@ int basisViewerWindow::fillMaskerViewerCanvas(MultidimArray image, Multi maskerViewerCanvas canvas(0, 0, xsize_canvas, ysize_canvas); canvas.pencil_radius = _pencil_radius; canvas.mask_scale = _scale; + canvas.angpix = _angpix; canvas.SetScroll(&scroll); if (mask_image.xdim > 0) canvas.Imask() = mask_image; else canvas.Imask().resize(image); canvas.fill(image, canvas.Imask(), _minval, _maxval, _sigma_contrast, _scale); + int xsize_data = CEIL(image.xdim * _scale); + canvas.Imask.setSamplingRateInHeader(_angpix * image.xdim / xsize_data); + canvas.fn_mask = _fn_mask; resizable(*this); show(); @@ -2555,67 +2559,72 @@ int maskerViewerCanvas::handle(int ev) int xc = (int)Fl::event_x() - scroll->x() + scroll->hscrollbar.value(); int yc = (int)Fl::event_y() - scroll->y() + scroll->scrollbar.value(); - if ((ev==FL_PUSH || ev==FL_DRAG) && (button == FL_LEFT_MOUSE || button == FL_MIDDLE_MOUSE)) + if (ev==FL_PUSH || ev==FL_DRAG) { - RFLOAT myval = (button == FL_LEFT_MOUSE) ? 1. : 0.; - int xstart = xc - pencil_radius; - xstart = std::max(0, xstart); - int xstop = xc + pencil_radius; - xstop = std::min((int)(Imask().xdim), xstop); - int ystart = yc - pencil_radius; - ystart = std::max(0, ystart); - int ystop = yc + pencil_radius; - ystop = std::min((int)(Imask().ydim), ystop); - for (int y=ystart; y < ystop; y++) + + if ((ev==FL_PUSH || ev==FL_DRAG) && (button == FL_LEFT_MOUSE || button == FL_MIDDLE_MOUSE)) { - int dy2 = (y-yc)*(y-yc); - for (int x=xstart; x < xstop; x++) + RFLOAT myval = (button == FL_LEFT_MOUSE) ? 1. : 0.; + int xstart = xc - pencil_radius; + xstart = std::max(0, xstart); + int xstop = xc + pencil_radius; + xstop = std::min((int)(Imask().xdim), xstop); + int ystart = yc - pencil_radius; + ystart = std::max(0, ystart); + int ystop = yc + pencil_radius; + ystop = std::min((int)(Imask().ydim), ystop); + for (int y=ystart; y < ystop; y++) { - int d2 = dy2 + (x-xc)*(x-xc); - if (d2 < pencil_radius*pencil_radius) + int dy2 = (y-yc)*(y-yc); + for (int x=xstart; x < xstop; x++) { - if (button == FL_LEFT_MOUSE && DIRECT_A2D_ELEM(Imask(), y, x) < 0.5 ) + int d2 = dy2 + (x-xc)*(x-xc); + if (d2 < pencil_radius*pencil_radius) { - long n = Imask().xdim * y + x; - boxes[0]->img_data[3*n] += 127; - DIRECT_A2D_ELEM(Imask(), y, x) = 1.; - } - else if (button == FL_MIDDLE_MOUSE && DIRECT_A2D_ELEM(Imask(), y, x) > 0.5 ) - { - long n = Imask().xdim * y + x; - boxes[0]->img_data[3*n] -= 127; - DIRECT_A2D_ELEM(Imask(), y, x) = 0.; + if (button == FL_LEFT_MOUSE && DIRECT_A2D_ELEM(Imask(), y, x) < 0.5 ) + { + long n = Imask().xdim * y + x; + boxes[0]->img_data[3*n] += 127; + DIRECT_A2D_ELEM(Imask(), y, x) = 1.; + } + else if (button == FL_MIDDLE_MOUSE && DIRECT_A2D_ELEM(Imask(), y, x) > 0.5 ) + { + long n = Imask().xdim * y + x; + boxes[0]->img_data[3*n] -= 127; + DIRECT_A2D_ELEM(Imask(), y, x) = 0.; + } } } } + boxes[0]->redraw(); + return 1; } - boxes[0]->redraw(); - return 1; - } - else if ((button == FL_RIGHT_MOUSE) || (button == FL_LEFT_MOUSE && with_control)) - { - redraw(); - Fl_Menu_Item rclick_menu[] = { - { "Save image with mask (CTRL-s)" }, - { "Set pencil radius" }, - { "Help" }, - { "Quit (CTRL-q)" }, - { 0 } - }; - const Fl_Menu_Item *m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, 0); - if ( !m ) - return 0; - else if ( strcmp(m->label(), "Save image with mask (CTRL-s)") == 0 ) - saveMask(); - else if ( strcmp(m->label(), "Set pencil radius") == 0 ) - setPencilRadius(); - else if ( strcmp(m->label(), "Help") == 0 ) - printHelp(); - else if ( strcmp(m->label(), "Quit (CTRL-q)") == 0 ) - exit(0); - redraw(); - return 1; // (tells caller we handled this event) - } + else if ((button == FL_RIGHT_MOUSE) || (button == FL_LEFT_MOUSE && with_control)) + { + redraw(); + Fl_Menu_Item rclick_menu[] = { + { "Save image with mask (CTRL-s)" }, + { "Set pencil radius" }, + { "Help" }, + { "Quit (CTRL-q)" }, + { 0 } + }; + const Fl_Menu_Item *m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, 0); + if ( !m ) + return 0; + else if ( strcmp(m->label(), "Save image with mask (CTRL-s)") == 0 ) + saveMask(); + else if ( strcmp(m->label(), "Set pencil radius") == 0 ) + setPencilRadius(); + else if ( strcmp(m->label(), "Help") == 0 ) + printHelp(); + else if ( strcmp(m->label(), "Quit (CTRL-q)") == 0 ) + exit(0); + redraw(); + return 1; // (tells caller we handled this event) + } + return 0; + } // Update the drawing every time something happens .... else if (ev==FL_RELEASE || ev==FL_LEAVE || ev==FL_ENTER || ev==FL_MOVE || ev == FL_FOCUS || ev == FL_UNFOCUS) { @@ -2666,6 +2675,7 @@ void maskerViewerCanvas::saveMask() std::string command = "mkdir -p " + fn_dirs; int res = system(command.c_str()); } + Imask.write(fn_mask); std::cout << "Saved mask as "<< fn_mask << std::endl; @@ -3187,7 +3197,7 @@ void Displayer::read(int argc, char **argv) int mask_section = parser.addSection("Mask design options"); do_mask = parser.checkOption("--paint_mask", "Paint mask in input image"); fn_mask = parser.getOption("--fn_mask", "Name for MRC file of the designed mask", ""); - pencil_radius = textToFloat(parser.getOption("--pencil_radius", "Pencil radius to draw masks (in original pixels)", "10")); + pencil_radius = textToFloat(parser.getOption("--pencil_radius", "Pencil radius to draw masks (in original pixels)", "25")); verb = textToInteger(parser.getOption("--verb", "Verbosity", "1")); @@ -3452,7 +3462,7 @@ void Displayer::run() basisViewerWindow win(CEIL(scale*XSIZE(img())), CEIL(scale*YSIZE(img())), fnt.c_str()); win.fillSingleViewerCanvas(img(), 0., 255., 0., scale); } - else if (do_pick || do_pick_startend || do_pick_lines) + else if (do_pick || do_pick_startend || do_pick_lines || do_mask) { Image img, fom_img; @@ -3479,23 +3489,21 @@ void Displayer::run() basisViewerWindow win(CEIL(scale*XSIZE(img())), CEIL(scale*YSIZE(img())), fn_in.c_str()); if (fn_coords=="") fn_coords = fn_in.withoutExtension()+"_coords.star"; - win.fillPickerViewerCanvas(img(), fom_img(), minval, maxval, sigma_contrast, scale, coord_scale, ROUND(scale*particle_radius), do_pick_startend, do_pick_lines, fn_coords, - fn_color, fn_in, color_label, color_blue_value, color_red_value, minimum_pick_fom, fom_min, fom_max); - } - else if (do_mask) - { - colour_scheme = BLACKGREYREDSCALE; - Image img, Imask; - img.read(fn_in); - if (fn_mask != "") + if (do_mask) { - Imask.read(fn_mask); + colour_scheme = BLACKGREYREDSCALE; + RFLOAT angpix = img.samplingRateX(); + Image Imask; + if (fn_mask != "" && exists(fn_mask)) Imask.read(fn_mask); + win.fillMaskerViewerCanvas(img(), Imask(), minval, maxval, sigma_contrast, scale, ROUND(scale*pencil_radius), fn_mask, angpix); } - basisViewerWindow win(CEIL(scale*XSIZE(img())), CEIL(scale*YSIZE(img())), fn_in.c_str()); - win.fillMaskerViewerCanvas(img(), Imask(), minval, maxval, sigma_contrast, scale, ROUND(scale*pencil_radius), fn_mask); - - } + else + { + win.fillPickerViewerCanvas(img(), fom_img(), minval, maxval, sigma_contrast, scale, coord_scale, ROUND(scale*particle_radius), do_pick_startend, do_pick_lines, fn_coords, + fn_color, fn_in, color_label, color_blue_value, color_red_value, minimum_pick_fom, fom_min, fom_max); + } + } else if (fn_in.isStarFile()) { if (fn_in.contains("_optimiser.star")) diff --git a/src/displayer.h b/src/displayer.h index a68585063..335550914 100644 --- a/src/displayer.h +++ b/src/displayer.h @@ -161,7 +161,7 @@ class basisViewerWindow : public Fl_Window FileName _fn_color = "", FileName _fn_mic= "", FileName _color_label = "", RFLOAT _color_blue_value = 0., RFLOAT _color_red_value = 1., RFLOAT _minimum_pick_fom = -999., RFLOAT _min_fom = 0., RFLOAT _max_fom = 0.); int fillMaskerViewerCanvas(MultidimArray image, MultidimArray mask_image, RFLOAT _minval, RFLOAT _maxval, RFLOAT _sigma_contrast, - RFLOAT _scale, int _pencil_radius, FileName _fn_mask); + RFLOAT _scale, int _pencil_radius, FileName _fn_mask, RFLOAT angpix); }; @@ -463,6 +463,9 @@ class maskerViewerCanvas : public basisViewerCanvas // Scale for pencil_radius RFLOAT mask_scale; + // Original angpix + RFLOAT angpix; + // Filename of the mask FileName fn_mask; From 35adad1ce987bfa33d260f52fb4d42317a60eae3 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 2 Jul 2025 14:56:18 +0100 Subject: [PATCH 135/189] added carbon detection to amyloid tracing --- environment.yml | 1 + scripts/trace_amyloids.in | 202 +++++++++++++++++++++++++++++++++----- src/amyloid_finder.cpp | 9 ++ src/amyloid_finder.h | 9 ++ src/gui_jobwindow.cpp | 15 +++ src/pipeline_jobs.cpp | 8 +- 6 files changed, 218 insertions(+), 26 deletions(-) diff --git a/environment.yml b/environment.yml index b443541aa..04869c584 100644 --- a/environment.yml +++ b/environment.yml @@ -28,6 +28,7 @@ dependencies: - numpy==1.26.1 - scipy==1.11.2 - skan==0.12.2 + - opencv-python-headless - git+https://github.com/3dem/relion-classranker - git+https://github.com/3dem/relion-blush - git+https://github.com/3dem/DynaMight diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 1b83ed15c..f4dde3431 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -19,6 +19,7 @@ from skimage.morphology import remove_small_objects, skeletonize from skimage.measure import regionprops, label as label_connected_component from skan import Skeleton, summarize from scipy.spatial.distance import cdist +import cv2 from tqdm import tqdm import time @@ -26,6 +27,50 @@ import time ############################################################################### # U-Net Model ############################################################################### +class DownCarbon(nn.Module): + """Downscaling with maxpool then double conv""" + + def __init__(self, in_channels, out_channels): + super().__init__() + self.maxpool_conv = nn.Sequential( + nn.MaxPool2d(2), + DoubleConv(in_channels, out_channels) + ) + + def forward(self, x): + return self.maxpool_conv(x) + + +class UpCarbon(nn.Module): + """Upscaling then double conv""" + + def __init__(self, in_channels, out_channels): + super().__init__() + + # 1) spatial upsample + # 2) reduce channels + self.up = nn.Sequential( + nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True), + nn.Conv2d(in_channels, in_channels // 2, kernel_size=1), + ) + # after cat: (in_channels//2 + in_channels//2) == in_channels + self.conv = DoubleConv(in_channels, out_channels) + + def forward(self, x1, x2): + x1 = self.up(x1) + # input is CHW + diffY = x2.size()[2] - x1.size()[2] + diffX = x2.size()[3] - x1.size()[3] + + x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2, + diffY // 2, diffY - diffY // 2]) + # if you have padding issues, see + # https://github.com/HaiyongJiang/U-Net-Pytorch-Unstructured-Buggy/commit/0e854509c2cea854e247a9c615f175f76fbb2e3a + # https://github.com/xiaopeng-liao/Pytorch-UNet/commit/8ebac70e633bac59fc22bb5195e513d5832fb3bd + x = torch.cat([x2, x1], dim=1) + return self.conv(x) + + class DoubleConv(nn.Module): """(convolution => [InstanceNorm] => GELU) * 2""" def __init__(self, in_channels, out_channels, mid_channels=None): @@ -90,12 +135,12 @@ class OutConv(nn.Module): class UNet(nn.Module): - def __init__(self, n_channels, n_classes, embedding_dim=32, dropout=0.25, bilinear=False): + def __init__(self, n_channels, n_classes, embedding_dim=32, bilinear=False): super().__init__() self.n_channels = n_channels self.n_classes = n_classes self.bilinear = bilinear - self.dropout = dropout + self.base_channel_sz = 128 self.inc = DoubleConv(n_channels, self.base_channel_sz) @@ -104,7 +149,7 @@ class UNet(nn.Module): self.down3 = Down(4 * self.base_channel_sz, 8 * self.base_channel_sz) factor = 2 if bilinear else 1 self.down4 = Down(8 * self.base_channel_sz, 16 * self.base_channel_sz // factor) - self.dropout_layer = nn.Dropout2d(p=dropout) + self.up1 = Up(16 * self.base_channel_sz, 8 * self.base_channel_sz // factor, bilinear) self.up2 = Up(8 * self.base_channel_sz, 4 * self.base_channel_sz // factor, bilinear) self.up3 = Up(4 * self.base_channel_sz, 2 * self.base_channel_sz // factor, bilinear) @@ -118,24 +163,14 @@ class UNet(nn.Module): def forward(self, x): x1 = self.inc(x) - x1 = self.dropout_layer(x1) x2 = self.down1(x1) - x2 = self.dropout_layer(x2) x3 = self.down2(x2) - x3 = self.dropout_layer(x3) x4 = self.down3(x3) - x4 = self.dropout_layer(x4) x5 = self.down4(x4) - # Apply dropout at bottleneck - x5 = self.dropout_layer(x5) - x = self.up1(x5, x4) - x = self.dropout_layer(x) x = self.up2(x, x3) - x = self.dropout_layer(x) x = self.up3(x, x2) - x = self.dropout_layer(x) x = self.up4(x, x1) seg_logits = self.outc(x) @@ -154,6 +189,53 @@ class UNet(nn.Module): return model +class UNetCarbon(nn.Module): + def __init__(self, n_channels, n_classes, dropout=0.25): + super(UNetCarbon, self).__init__() + self.n_channels = n_channels + self.n_classes = n_classes + self.base_channel_sz = 64 + + self.inc = (DoubleConv(n_channels, self.base_channel_sz)) + self.down1 = (DownCarbon(self.base_channel_sz, 2 * self.base_channel_sz)) + self.down2 = (DownCarbon(2 * self.base_channel_sz, 4 * self.base_channel_sz)) + self.down3 = (DownCarbon(4 * self.base_channel_sz, 8 * self.base_channel_sz)) + self.down4 = (DownCarbon(8 * self.base_channel_sz, 16 * self.base_channel_sz )) + self.up1 = (UpCarbon(16 * self.base_channel_sz, 8 * self.base_channel_sz)) + self.up2 = (UpCarbon(8 * self.base_channel_sz, 4 * self.base_channel_sz)) + self.up3 = (UpCarbon(4 * self.base_channel_sz, 2 * self.base_channel_sz)) + self.up4 = (UpCarbon(2 * self.base_channel_sz, self.base_channel_sz)) + self.outc = (OutConv(self.base_channel_sz, n_classes)) + + + def forward(self, x): + x1 = self.inc(x) + x2 = self.down1(x1) + x3 = self.down2(x2) + x4 = self.down3(x3) + x5 = self.down4(x4) + + x = self.up1(x5, x4) + x = self.up2(x, x3) + x = self.up3(x, x2) + x = self.up4(x, x1) + logits = self.outc(x) + + return logits + + @staticmethod + def load_from_checkpoint(model_path: str, n_channels: int, n_classes: int): + model = UNetCarbon(n_channels=n_channels, n_classes=n_classes) + lightning_dict = torch.load(model_path, map_location="cpu") + state_dict = lightning_dict['state_dict'] + try: + model.load_state_dict(state_dict, strict=False) + except: + model.load_state_dict(state_dict) + return model + + + ############################################################################### # MRC + instance segmentation ############################################################################### @@ -363,6 +445,24 @@ def prune_skeleton(skeleton, min_length=100, min_distance=25, step=50): return labeled_skeleton, np.array(sampled_coords, dtype=int) +def resize_and_norm(image, mysize, angpix, do_norm=True): + ys, xs = image.shape + if (xs > ys): + scale = ys/mysize + else: + scale = xs/mysize + + xsp = int(xs/scale) + ysp = int(ys/scale) + resized_angpix = angpix *scale + resized = cv2.resize(image.astype(np.float32), (xsp, ysp), interpolation=cv2.INTER_AREA) + if do_norm: + max = resized.max() + min = resized.min() + if (max > min): + resized = (resized - min) / (max - min) + + return resized, resized_angpix ############################################################################### # MAIN @@ -373,11 +473,13 @@ def main(): parser = argparse.ArgumentParser("Skeleton-based filament picking with instance merging, branch pruning, and ID labeling") parser.add_argument("-i", "--input", help="Input star file") parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint") + parser.add_argument("-c", "--carbon_model_path", help="Path to PyTorch model checkpoint for carbon detection", default="") parser.add_argument("-o", "--output", help="Output star file with coordinates") parser.add_argument("-r", "--radius", type=int, help="Radius of region to exclude near edges (in A)") parser.add_argument("-l", "--minimum_length", type=int, default=250, help="Remove final segments smaller than this size in Angstroms (default=250)") parser.add_argument("-t", "--threshold", default=0.5, type=float, help="Foreground threshold (default=0.5)") + parser.add_argument("-ct", "--carbon_threshold", default=0.9, type=float, help="Carbon detection threshold (default=0.9)") parser.add_argument("-s", "--scale", type=float, default=1.0, help="Multiply output coordinates by this factor (default=1.0)") parser.add_argument("-d", "--device", default="cuda:0", help="Which GPU device to use") @@ -397,9 +499,25 @@ def main(): device = torch.device(args.device) model = UNet.load_from_checkpoint(args.model_path, n_channels=3, n_classes=1) model.eval().to(device) + if args.carbon_model_path == "": + if args.verb > 0: + print(" + Not doing carbon detection") + do_carbon = False + else: + do_carbon = True + if args.verb > 0: + print(" + Doing carbon detection") + carbon_model = UNetCarbon.load_from_checkpoint(args.carbon_model_path, n_channels=1, n_classes=1) + carbon_model.eval().to(device) input_star = starfile.read(args.input) + @torch.compile + def run_compiled_carbon_model(data): + seg_logits = carbon_model(data) + carbon_prob = torch.sigmoid(seg_logits.squeeze()) + return carbon_prob + @torch.compile def run_compiled_model(data): seg_logits, embedding = model(data) @@ -418,6 +536,28 @@ def main(): # ------------------- Load MRCs ---------------------- fom_image, pixel_size, origin = load_mrc(row["rlnMicrographFomImage"]) psi_image, _, _ = load_mrc(row["rlnMicrographPsiImage"]) + + #--------------------- Carbon detection ----------------- + if do_carbon: + #in_tensor = np.stack(mic_image, axis=0) + #in_tensor = torch.from_numpy(in_tensor[None,None]).to(device=device, dtype=half_dtype) # shape (1,1,H,W) + mic_image, ori_pixel_size, _ = load_mrc(row["rlnMicrographName"]) + mic_image, resized_angpix = resize_and_norm(mic_image, 64, ori_pixel_size) + in_tensor = torch.from_numpy(mic_image[None,None]).to(device=device, dtype=half_dtype) # shape (1,1,H,W) + with torch.no_grad(): + if device.type == "cuda": + with torch.autocast(device_type=device.type, dtype=half_dtype): + carbon_prob = run_compiled_carbon_model(in_tensor) + else: + with torch.autocast(device_type="cpu", dtype=half_dtype): + carbon_prob = run_compiled_carbon_model(in_tensor) + carbon_prob = carbon_prob.to(dtype=torch.float32, device="cpu").numpy() + xsf, ysf= fom_image.shape + if carbon_prob.shape != fom_image.shape: + carbon_prob = cv2.resize(carbon_prob.astype(np.float32), (ysf, xsf), interpolation=cv2.INTER_AREA) + carbon_mask = (carbon_prob>args.carbon_threshold) + + #--------------------- Filament segmentation ----------------- outfile = row["rlnMicrographCoordinates"] directory = Path(outfile).parent # Check if the directory exists, and create it if not @@ -426,7 +566,7 @@ def main(): H, W = fom_image.shape rad_psi = np.deg2rad(psi_image * 2) - in_tensor = np.stack((fom_image, np.sin(rad_psi), np.cos(rad_psi)), axis=0) + in_tensor = np.stack((fom_image, np.sin(rad_psi), np.cos(rad_psi)), axis=0) # shape (3,H,W) in_tensor = torch.from_numpy(in_tensor[None]).to(device=device, dtype=half_dtype) # shape (1,3,H,W) with torch.no_grad(): @@ -437,9 +577,12 @@ def main(): with torch.autocast(device_type="cpu", dtype=half_dtype): seg_prob = run_compiled_model(in_tensor) foreground = seg_prob.to(dtype=torch.float32, device="cpu").numpy() - + # 2) Clean + threshold => remove small objects bin_foreground = (foreground > args.threshold) + if do_carbon: + bin_foreground = bin_foreground & (~carbon_mask) + min_size = 100 clean_foreground = remove_small_objects(bin_foreground, min_size=min_size) @@ -488,7 +631,7 @@ def main(): # --------------------------------------------------- if args.plot: - fig, ax = plt.subplots(2, 3, figsize=(32,20)) + fig, ax = plt.subplots(2, 4, figsize=(20,15)) ax = ax.ravel() ax[0].imshow(fom_image, cmap='gray') @@ -507,18 +650,27 @@ def main(): ax[3].set_title("Raw Skeleton") ax[3].axis('off') - if skeleton_img.any(): - ax[4].imshow(color_nonrandom(pruned_skeleton)) - ax[4].set_title("Pruned Skeleton") + if do_carbon: + ax[4].imshow(mic_image, cmap='gray') + ax[4].set_title("micrograph") ax[4].axis('off') + ax[5].imshow(color_foreground(carbon_prob, args.carbon_threshold)) + ax[5].set_title("carbon detection") + ax[5].axis('off') + + if skeleton_img.any(): + ax[6].imshow(color_nonrandom(pruned_skeleton)) + ax[6].set_title("Pruned Skeleton") + ax[6].axis('off') + if sampled_points.size > 0: - ax[5].imshow(np.zeros_like(foreground), cmap='gray') # blank background - ax[5].scatter(sampled_points[:,0]/args.scale, sampled_points[:,1]/args.scale, s=2, c='red') - ax[5].set_xlim([0, foreground.shape[1]]) - ax[5].set_ylim([foreground.shape[0], 0]) - ax[5].set_title("Final Sampled Picks (ID shown in STAR)") - ax[5].axis('off') + ax[7].imshow(np.zeros_like(foreground), cmap='gray') # blank background + ax[7].scatter(sampled_points[:,0]/args.scale, sampled_points[:,1]/args.scale, s=2, c='red') + ax[7].set_xlim([0, foreground.shape[1]]) + ax[7].set_ylim([foreground.shape[0], 0]) + ax[7].set_title("Final Sampled Picks (ID shown in STAR)") + ax[7].axis('off') plt.tight_layout() plt.show() diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 95f956da8..9958add82 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -47,6 +47,9 @@ void AmyloidFinder::read(int argc, char **argv, int rank) fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_trace_amyloids"); fn_other_args = parser.getOption("--other_args", "Other arguments for the python script", ""); fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing","/public/EM/RELION/amypicker2.ckpt"); + do_carbon = parser.checkOption("--detect_carbon", "Detect carbon and ignore filaments on there."); + fn_carbon_model_path = parser.getOption("--carbon_model_path", "Name of the model to execute for carbon detection","/public/EM/RELION/carbon.ckpt"); + carbon_threshold = textToFloat(parser.getOption("--carbon_threshold", "Threshold for carbon detection", "0.9")); do_skip_tracing = parser.checkOption("--skip_tracing", "Skip tracing."); do_gpu = parser.checkOption("--gpu", "Use GPU acceleration when availiable"); gpu_ids = parser.getOption("--gpu", "Device ids for each MPI-thread","default"); @@ -781,6 +784,7 @@ void AmyloidFinder::runTracingBatch(long int my_first, long int my_last, int my_ } // remove leading job number from fn_mic filename FileName fn_pick = fn_root + "_" + fn_out + ".star"; + MDtrace.setValue(EMDL_MICROGRAPH_NAME, todo_micrographs_tracing[imic]); MDtrace.setValue(EMDL_MICROGRAPH_AUTOPICK_FOM, fn_fom); MDtrace.setValue(EMDL_MICROGRAPH_AUTOPICK_PSI, fn_psi); MDtrace.setValue(EMDL_MICROGRAPH_COORDINATES, fn_pick); @@ -811,6 +815,11 @@ void AmyloidFinder::runTracingBatch(long int my_first, long int my_last, int my_ command += " -s " + floatToString(down_angpix/angpix); command += " -a " + pipeline_control_outputname+RELION_JOB_ABORT_NOW; command += " -v " + integerToString(verb); + if (do_carbon) + { + command += " -c " + fn_carbon_model_path; + command += " --carbon_threshold " + floatToString(carbon_threshold); + } if (do_plot) command += " --plot "; diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index 930755263..6c5f0482a 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -76,6 +76,15 @@ class AmyloidFinder // Redo existing tracings? bool do_redo_tracing; + // Detect carbon in filament tracing to ignore filaments on carbon + bool do_carbon; + + // threshold for carbon detection + RFLOAT carbon_threshold; + + // FileName for carbon detection model + FileName fn_carbon_model_path; + // FileName for Jenny's model FileName fn_model_path; diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index f0711d3d6..2203198fb 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -964,9 +964,24 @@ void JobWindow::initialiseAutopickWindow() place("amyloid_gpu_ids"); place("do_amyloid_plot"); + current_y += STEPY/2; + + group6 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group6->end(); + + place("do_amyloid_carbon", TOGGLE_DEACTIVATE, group6); + + group6->begin(); + place("amyloid_carbon_threshold", TOGGLE_DEACTIVATE); + + group6->end(); + guientries["do_amyloid_carbon"].cb_menu_i(); + group5->end(); guientries["do_amyloid_tracing"].cb_menu_i(); + + tab7->end(); } diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index edf667a40..6b637ca94 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2062,7 +2062,8 @@ Helical tubes with shorter lengths will not be picked. Note that a long helical joboptions["do_amyloid_plot"] = JobOption("Plot results per micrograph?", false, "Set this option to Yes to visualise intermediate results for amyloid tracing, which may help with setting values for filament length, width and picking threshold. Don't use in parallel or in submission to the queue."); joboptions["amyloid_width"] = JobOption("Amyloid width (A): ", 200, 100, 1000, 10, "Width (in Angstroms) used for FOM calculation and/or tracing. "); joboptions["amyloid_length"] = JobOption("Amyloid minimum length (A): ", 500, 100, 1000, 10, "Length (in Angstroms) used for FOM calculation, and minimum length of filaments for tracing."); - + joboptions["do_amyloid_carbon"] = JobOption("Ignore filaments on carbon?", false, "Set to Yes if you want to use automated carbon detection to avoid picking filaments on carbon."); + joboptions["amyloid_carbon_threshold"] = JobOption("Carbon threshold: ", 0.9, 0.05, 1, 0.05, "At what probability should pixels in the micrographs be deemed carbon? Use lower values to have more areas exclude from the picking."); } @@ -2217,6 +2218,11 @@ bool RelionJob::getCommandsAutopickJob(std::string &outputname, std::vector Date: Wed, 2 Jul 2025 15:36:45 +0100 Subject: [PATCH 136/189] added amyprep and amyproc Schemes --- scripts/Schemes/amyprep/autopick_fom/job.star | 83 ++++++++++++ scripts/Schemes/amyprep/ctffind/job.star | 41 ++++++ scripts/Schemes/amyprep/import/job.star | 39 ++++++ scripts/Schemes/amyprep/motioncorr/job.star | 50 +++++++ scripts/Schemes/amyprep/scheme.star | 106 +++++++++++++++ scripts/Schemes/amyprep/select_mics/job.star | 45 +++++++ .../Schemes/amyproc/autopick_trace/job.star | 88 +++++++++++++ scripts/Schemes/amyproc/class2d/job.star | 58 +++++++++ scripts/Schemes/amyproc/extract/job.star | 50 +++++++ scripts/Schemes/amyproc/scheme.star | 123 ++++++++++++++++++ scripts/Schemes/amyproc/split/job.star | 51 ++++++++ 11 files changed, 734 insertions(+) create mode 100644 scripts/Schemes/amyprep/autopick_fom/job.star create mode 100644 scripts/Schemes/amyprep/ctffind/job.star create mode 100644 scripts/Schemes/amyprep/import/job.star create mode 100644 scripts/Schemes/amyprep/motioncorr/job.star create mode 100644 scripts/Schemes/amyprep/scheme.star create mode 100644 scripts/Schemes/amyprep/select_mics/job.star create mode 100644 scripts/Schemes/amyproc/autopick_trace/job.star create mode 100644 scripts/Schemes/amyproc/class2d/job.star create mode 100644 scripts/Schemes/amyproc/extract/job.star create mode 100644 scripts/Schemes/amyproc/scheme.star create mode 100644 scripts/Schemes/amyproc/split/job.star diff --git a/scripts/Schemes/amyprep/autopick_fom/job.star b/scripts/Schemes/amyprep/autopick_fom/job.star new file mode 100644 index 000000000..6f2da1f3c --- /dev/null +++ b/scripts/Schemes/amyprep/autopick_fom/job.star @@ -0,0 +1,83 @@ + +# version 50001 + +data_job + +_rlnJobTypeLabel relion.autopick.amypick +_rlnJobIsContinue 1 +_rlnJobIsTomo 0 + + +# version 50001 + +data_joboptions_values + +loop_ +_rlnJobOptionVariable #1 +_rlnJobOptionValue #2 +amyloid_threshold .25 + angpix -1 +angpix_ref -1 +continue_manual No +do_amyloid Yes +do_amyloid_fom Yes +do_amyloid_plot No +do_amyloid_tracing No +do_ctf_autopick Yes +do_ignore_first_ctfpeak_autopick No +do_invert_refs Yes + do_log No +do_pick_helical_segments Yes + do_queue No +do_read_fom_maps No + do_ref3d No + do_refs No + do_topaz No +do_topaz_filaments No +do_topaz_pick No +do_topaz_train No +do_topaz_train_parts No +do_write_fom_maps No +fn_input_autopick Schemes/amyprep/ctffind/micrographs_ctf.star +fn_ref3d_autopick "" +fn_refs_autopick "" +fn_topaz_exe relion_python_topaz + gpu_ids "" +helical_nr_asu 3 +helical_rise 4.75 +helical_tube_kappa_max 0.07 +helical_tube_length_min 500 +helical_tube_outer_diameter 200 + highpass -1 +log_adjust_thr 0 +log_diam_max 250 +log_diam_min 200 +log_invert No +log_maxres 20 +log_upper_thr 999 + lowpass 20 +maxstddevnoise_autopick 1.1 +min_dedicated 112 +minavgnoise_autopick -999 +mindist_autopick 100 + nr_mpi 6 +nr_threads 18 +other_args "" +psi_sampling_autopick 5 + qsub sbatch +qsubscript /public/EM/RELION/relion-slurm-cpu-devel.sh + queuename openmpi +ref3d_sampling "30 degrees" +ref3d_symmetry C1 + shrink 0 +threshold_autopick 0.05 +topaz_filament_threshold -5 +topaz_hough_length -1 +topaz_model "" +topaz_nr_particles -1 +topaz_other_args "" +topaz_particle_diameter -1 +topaz_train_parts "" +topaz_train_picks "" + use_gpu No + diff --git a/scripts/Schemes/amyprep/ctffind/job.star b/scripts/Schemes/amyprep/ctffind/job.star new file mode 100644 index 000000000..e8da13310 --- /dev/null +++ b/scripts/Schemes/amyprep/ctffind/job.star @@ -0,0 +1,41 @@ + +# version 50001 + +data_job + +_rlnJobTypeLabel relion.ctffind.ctffind4 +_rlnJobIsContinue 0 +_rlnJobIsTomo 0 + + +# version 50001 + +data_joboptions_values + +loop_ +_rlnJobOptionVariable #1 +_rlnJobOptionValue #2 + box 512 + ctf_win -1 + dast 100 + dfmax 50000 + dfmin 5000 + dfstep 500 +do_phaseshift No + do_queue No +fn_ctffind_exe /public/EM/ctffind/ctffind.exe +input_star_mics Schemes/amyprep/motioncorr/corrected_micrographs.star +min_dedicated 112 + nr_mpi 24 +other_args "" + phase_max 180 + phase_min 0 +phase_step 10 + qsub sbatch +qsubscript /public/EM/RELION/relion-slurm-cpu-devel.sh + queuename openmpi + resmax 5 + resmin 30 +slow_search No +use_given_ps Yes + use_noDW No diff --git a/scripts/Schemes/amyprep/import/job.star b/scripts/Schemes/amyprep/import/job.star new file mode 100644 index 000000000..afd6beba9 --- /dev/null +++ b/scripts/Schemes/amyprep/import/job.star @@ -0,0 +1,39 @@ + +# version 50001 + +data_job + +_rlnJobTypeLabel relion.import.movies +_rlnJobIsContinue 0 +_rlnJobIsTomo 0 + + +# version 50001 + +data_joboptions_values + +loop_ +_rlnJobOptionVariable #1 +_rlnJobOptionValue #2 + Cs 2.7 + Q0 0.1 + angpix 0.824 +beamtilt_x 0 +beamtilt_y 0 + do_other No + do_queue No + do_raw Yes +fn_in_other ref.mrc + fn_in_raw Tiff/*/*/*.tiff + fn_mtf "" +is_multiframe Yes + kV 300.0 +min_dedicated 24 + node_type "Particle coordinates (*.box, *_pick.star)" +optics_group_name opticsGroup1 +optics_group_particles "" +other_args " --do_at_most $$do_at_most" + qsub sbatch +qsubscript /public/EM/RELION/relion-slurm-cpu-devel.sh + queuename openmpi + diff --git a/scripts/Schemes/amyprep/motioncorr/job.star b/scripts/Schemes/amyprep/motioncorr/job.star new file mode 100644 index 000000000..3bf9b9743 --- /dev/null +++ b/scripts/Schemes/amyprep/motioncorr/job.star @@ -0,0 +1,50 @@ + +# version 50001 + +data_job + +_rlnJobTypeLabel relion.motioncorr +_rlnJobIsContinue 0 +_rlnJobIsTomo 0 + + +# version 50001 + +data_joboptions_values + +loop_ +_rlnJobOptionVariable #1 +_rlnJobOptionValue #2 + bfactor 150 +bin_factor 1 +do_dose_weighting Yes +do_float16 Yes +do_own_motioncor Yes + do_queue No +do_save_noDW No +do_save_ps Yes +dose_per_frame 1.0 +eer_grouping -1 +first_frame_sum 1 + fn_defect "" +fn_gain_ref gain.mrc +fn_motioncor2_exe /public/EM/MOTIONCOR2/MotionCor2 + gain_flip "No flipping (0)" + gain_rot "No rotation (0)" + gpu_ids 0 +group_for_ps 4 +group_frames 1 +input_star_mics Schemes/amyprep/import/movies.star +last_frame_sum -1 +min_dedicated 24 + nr_mpi 9 +nr_threads 12 +other_args "--do_at_most $$do_at_most" +other_motioncor2_args "" + patch_x 4 + patch_y 4 +pre_exposure 0 + qsub sbatch +qsubscript /public/EM/RELION/relion-slurm-cpu-devel.sh + queuename openmpi + diff --git a/scripts/Schemes/amyprep/scheme.star b/scripts/Schemes/amyprep/scheme.star new file mode 100644 index 000000000..aed8d12e4 --- /dev/null +++ b/scripts/Schemes/amyprep/scheme.star @@ -0,0 +1,106 @@ + +# version 50001 + +data_scheme_general + +_rlnSchemeName Schemes/amyprep/ +_rlnSchemeCurrentNodeName WAIT + + +# version 50001 + +data_scheme_floats + +loop_ +_rlnSchemeFloatVariableName #1 +_rlnSchemeFloatVariableValue #2 +_rlnSchemeFloatVariableResetValue #3 +current_nr_movies 0.000000 0.000000 +do_at_most 25000.000000 25000.000000 +maxtime_hr 48.000000 48.000000 +min_fom_skew 1.000000 1.000000 +prev_nr_movies 0.000000 0.000000 + wait_sec 300.000000 300.000000 + + +# version 50001 + +data_scheme_bools + +loop_ +_rlnSchemeBooleanVariableName #1 +_rlnSchemeBooleanVariableValue #2 +_rlnSchemeBooleanVariableResetValue #3 +has_increased 0 0 +has_larger_nr_mics 1 1 + has_mics 1 1 +has_movies 0 0 + + +# version 50001 + +data_scheme_strings + +loop_ +_rlnSchemeStringVariableName #1 +_rlnSchemeStringVariableValue #2 +_rlnSchemeStringVariableResetValue #3 +input_movies Schemes/amyprep/import/movies.star Schemes/amyprep/import/movies.star + movies movies movies + + +# version 50001 + +data_scheme_operators + +loop_ +_rlnSchemeOperatorName #1 +_rlnSchemeOperatorType #2 +_rlnSchemeOperatorOutput #3 +_rlnSchemeOperatorInput1 #4 +_rlnSchemeOperatorInput2 #5 +COUNT_movies float=count_images current_nr_movies input_movies movies +EXIT_maxtime exit_maxtime undefined maxtime_hr undefined +HAS_increased bool=gt has_increased current_nr_movies prev_nr_movies +SET_prev_nr_movies float=set prev_nr_movies current_nr_movies undefined + WAIT wait undefined wait_sec undefined + + +# version 50001 + +data_scheme_jobs + +loop_ +_rlnSchemeJobNameOriginal #1 +_rlnSchemeJobName #2 +_rlnSchemeJobMode #3 +_rlnSchemeJobHasStarted #4 +autopick_fom autopick_fom continue 0 + ctffind ctffind continue 0 + import import continue 0 +importmovies importmovies "" 0 +motioncorr motioncorr continue 0 +select_mics select_mics continue 0 + + +# version 50001 + +data_scheme_edges + +loop_ +_rlnSchemeEdgeInputNodeName #1 +_rlnSchemeEdgeOutputNodeName #2 +_rlnSchemeEdgeIsFork #3 +_rlnSchemeEdgeOutputNodeNameIfTrue #4 +_rlnSchemeEdgeBooleanVariable #5 + WAIT EXIT_maxtime 0 undefined undefined +EXIT_maxtime import 0 undefined undefined + import COUNT_movies 0 undefined undefined +COUNT_movies HAS_increased 0 undefined undefined +HAS_increased WAIT 1 SET_prev_nr_movies has_larger_nr_mics +SET_prev_nr_movies motioncorr 0 undefined undefined +motioncorr ctffind 0 undefined undefined + ctffind autopick_fom 0 undefined undefined +autopick_fom select_mics 0 undefined undefined +select_mics WAIT 0 undefined undefined + diff --git a/scripts/Schemes/amyprep/select_mics/job.star b/scripts/Schemes/amyprep/select_mics/job.star new file mode 100644 index 000000000..38ceddbfd --- /dev/null +++ b/scripts/Schemes/amyprep/select_mics/job.star @@ -0,0 +1,45 @@ + +# version 30001 + +data_job + +_rlnJobTypeLabel relion.select +_rlnJobIsContinue 0 + + +# version 30001 + +data_joboptions_values + +loop_ +_rlnJobOptionVariable #1 +_rlnJobOptionValue #2 +discard_label rlnImageName +discard_sigma 4 +do_class_ranker No +do_discard No + do_queue No + do_random No +do_recenter No +do_regroup No +do_remove_duplicates No +do_select_values Yes + do_split No +duplicate_threshold 30 + fn_data "" + fn_mic Schemes/amyprep/autopick_fom/micrographs_autopick.star + fn_model "" +image_angpix -1 +min_dedicated 24 + nr_groups 1 + nr_split -1 +other_args "" + qsub qsub +qsubscript /public/EM/RELION/relion/bin/relion_qsub.csh + queuename openmpi +rank_threshold 0.35 +select_label rlnMicrographScoreSkewness +select_maxval 9999. +select_minval 1. +split_size 100 + diff --git a/scripts/Schemes/amyproc/autopick_trace/job.star b/scripts/Schemes/amyproc/autopick_trace/job.star new file mode 100644 index 000000000..9be89c059 --- /dev/null +++ b/scripts/Schemes/amyproc/autopick_trace/job.star @@ -0,0 +1,88 @@ + +# version 50001 + +data_job + +_rlnJobTypeLabel relion.autopick.amypick +_rlnJobIsContinue 0 +_rlnJobIsTomo 0 + + +# version 50001 + +data_joboptions_values + +loop_ +_rlnJobOptionVariable #1 +_rlnJobOptionValue #2 +amyloid_carbon_threshold 0.9 +amyloid_gpu_ids 0:1:2:3 +amyloid_length 500 +amyloid_threshold 0.5 +amyloid_width 200 + angpix -1 +angpix_ref -1 +continue_manual No +do_amyloid Yes +do_amyloid_carbon Yes +do_amyloid_fom No +do_amyloid_plot No +do_amyloid_tracing Yes +do_ctf_autopick Yes +do_ignore_first_ctfpeak_autopick No +do_invert_refs Yes + do_log No +do_pick_helical_segments No + do_queue No +do_read_fom_maps No + do_ref3d No + do_refs No + do_topaz No +do_topaz_filaments No +do_topaz_pick No +do_topaz_train No +do_topaz_train_parts No +do_write_fom_maps No +fn_input_autopick $$input_mics +fn_ref3d_autopick "" +fn_refs_autopick "" +fn_topaz_exe relion_python_topaz + gpu_ids 0:1:2:3 +helical_nr_asu 3 +helical_rise 4.75 +helical_tube_kappa_max 0.07 +helical_tube_length_min 500 +helical_tube_outer_diameter 200 + highpass -1 +log_adjust_thr 0 +log_diam_max 250 +log_diam_min 200 +log_invert No +log_maxres 20 +log_upper_thr 999 + lowpass 20 +maxstddevnoise_autopick 1.1 +min_dedicated 112 +minavgnoise_autopick -999 +mindist_autopick 100 + nr_mpi 4 +nr_threads 1 +other_args "" +psi_sampling_autopick 5 + qsub sbatch +qsubscript /public/EM/RELION/relion-slurm-gpu-devel.csh + queuename openmpi +ref3d_sampling "30 degrees" +ref3d_symmetry C1 + shrink 0 +threshold_autopick 0.05 +topaz_filament_threshold -5 +topaz_hough_length -1 +topaz_model "" +topaz_nr_particles -1 +topaz_other_args "" +topaz_particle_diameter -1 +topaz_train_parts "" +topaz_train_picks "" + use_gpu Yes + diff --git a/scripts/Schemes/amyproc/class2d/job.star b/scripts/Schemes/amyproc/class2d/job.star new file mode 100644 index 000000000..28a1c638e --- /dev/null +++ b/scripts/Schemes/amyproc/class2d/job.star @@ -0,0 +1,58 @@ + +# version 50001 + +data_job + +_rlnJobTypeLabel relion.class2d.helical +_rlnJobIsContinue 0 +_rlnJobIsTomo 0 + + +# version 50001 + +data_joboptions_values + +loop_ +_rlnJobOptionVariable #1 +_rlnJobOptionValue #2 +allow_coarser No +ctf_intact_first_peak Yes +do_bimodal_psi Yes + do_center No +do_combine_thru_disc No +do_ctf_correction Yes + do_em Yes + do_grad No + do_helix Yes +do_parallel_discio Yes +do_preread_images No + do_queue No +do_restrict_xoff Yes +do_zero_mask Yes +dont_skip_align Yes + fn_cont "" + fn_img $$mybatch + gpu_ids 0:1:2:3 +helical_rise 4.75 +helical_tube_outer_diameter 200 +highres_limit -1 +min_dedicated 24 +nr_classes 100 +nr_iter_em 25 +nr_iter_grad 200 + nr_mpi 5 + nr_pool 30 +nr_threads 6 +offset_range 6 +offset_step 1 +other_args "" +particle_diameter 600 +psi_sampling 2 + qsub sbatch +qsubscript /public/EM/RELION/relion-slurm-gpu-4.0.csh + queuename openmpi + range_psi 6 +scratch_dir /ssd + tau_fudge 2 + use_gpu Yes + diff --git a/scripts/Schemes/amyproc/extract/job.star b/scripts/Schemes/amyproc/extract/job.star new file mode 100644 index 000000000..0e289cda6 --- /dev/null +++ b/scripts/Schemes/amyproc/extract/job.star @@ -0,0 +1,50 @@ + +# version 50001 + +data_job + +_rlnJobTypeLabel relion.extract.helical +_rlnJobIsContinue 0 +_rlnJobIsTomo 0 + + +# version 50001 + +data_joboptions_values + +loop_ +_rlnJobOptionVariable #1 +_rlnJobOptionValue #2 +bg_diameter -1 +black_dust -1 +coords_suffix Schemes/amyproc/autopick_trace/autopick.star +do_extract_helix Yes +do_float16 Yes +do_fom_threshold No + do_invert Yes + do_norm Yes + do_queue No +do_recenter No +do_reextract No +do_rescale Yes +do_reset_offsets No +extract_size 768 +fndata_reextract "" +helical_bimodal_angular_priors Yes +helical_nr_asu 3 +helical_rise 4.75 +helical_tube_outer_diameter 200 +min_dedicated 24 +minimum_pick_fom 0 + nr_mpi 12 +other_args "" + qsub sbatch +qsubscript /public/EM/RELION/relion-slurm-cpu-devel.sh + queuename openmpi +recenter_x 0 +recenter_y 0 +recenter_z 0 + rescale 128 + star_mics $$input_mics +white_dust -1 + diff --git a/scripts/Schemes/amyproc/scheme.star b/scripts/Schemes/amyproc/scheme.star new file mode 100644 index 000000000..49f7c3619 --- /dev/null +++ b/scripts/Schemes/amyproc/scheme.star @@ -0,0 +1,123 @@ + +# version 50001 + +data_scheme_general + +_rlnSchemeName Schemes/amyproc/ +_rlnSchemeCurrentNodeName WAIT + + +# version 50001 + +data_scheme_floats + +loop_ +_rlnSchemeFloatVariableName #1 +_rlnSchemeFloatVariableValue #2 +_rlnSchemeFloatVariableResetValue #3 + batchsize 100000.00000 100000.00000 +current_batch 1.000000 1.000000 +current_nr_mics 0.000000 0.000000 +maxtime_hr 48.000000 48.000000 +mybatchsize 0.000000 0.000000 +nr_batches 0.000000 0.000000 + one 1.000000 1.000000 +prev_nr_mics 0.000000 0.000000 + wait_sec 300.000000 300.000000 + + +# version 50001 + +data_scheme_bools + +loop_ +_rlnSchemeBooleanVariableName #1 +_rlnSchemeBooleanVariableValue #2 +_rlnSchemeBooleanVariableResetValue #3 + has_mics 0 0 +has_mics_increased 0 0 +is_batch_full 1 1 +is_full_batch 0 0 + + +# version 50001 + +data_scheme_strings + +loop_ +_rlnSchemeStringVariableName #1 +_rlnSchemeStringVariableValue #2 +_rlnSchemeStringVariableResetValue #3 +batchnames Schemes/amyproc/split/particles_split*.star Schemes/amyproc/split/particles_split*.star +input_mics Schemes/amyprep/select_mics/micrographs.star Schemes/amyprep/select_mics/micrographs.star +micrographs micrographs micrographs + mybatch "" "" + mybatches "" "" + particles particles particles + + +# version 50001 + +data_scheme_operators + +loop_ +_rlnSchemeOperatorName #1 +_rlnSchemeOperatorType #2 +_rlnSchemeOperatorOutput #3 +_rlnSchemeOperatorInput1 #4 +_rlnSchemeOperatorInput2 #5 +COUNT_mics float=count_images current_nr_mics input_mics micrographs +COUNT_mybatchsize float=count_images mybatchsize mybatch particles +EXIT_maxtime exit_maxtime undefined maxtime_hr undefined +GLOB_batchnames string=glob mybatches batchnames undefined + HAS_mics bool=file_exists has_mics input_mics undefined +HAS_mics_increased bool=gt has_mics_increased current_nr_mics prev_nr_mics +INCR_batch float=plus current_batch current_batch one +IS_batchfull bool=eq is_full_batch mybatchsize batchsize +SET_mybatch string=nth_word mybatch mybatches current_batch +SET_prev_nr_mics float=set prev_nr_mics current_nr_mics undefined + WAIT wait undefined wait_sec undefined + + +# version 50001 + +data_scheme_jobs + +loop_ +_rlnSchemeJobNameOriginal #1 +_rlnSchemeJobName #2 +_rlnSchemeJobMode #3 +_rlnSchemeJobHasStarted #4 +autopick_trace autopick_trace continue 0 + class2d class2d new 0 + extract extract continue 0 + split split continue 0 + + +# version 50001 + +data_scheme_edges + +loop_ +_rlnSchemeEdgeInputNodeName #1 +_rlnSchemeEdgeOutputNodeName #2 +_rlnSchemeEdgeIsFork #3 +_rlnSchemeEdgeOutputNodeNameIfTrue #4 +_rlnSchemeEdgeBooleanVariable #5 + WAIT EXIT_maxtime 0 undefined undefined +EXIT_maxtime HAS_mics 0 undefined undefined + HAS_mics WAIT 1 COUNT_mics has_mics +COUNT_mics HAS_mics_increased 0 undefined undefined +HAS_mics_increased WAIT 1 SET_prev_nr_mics has_mics_increased + WAIT EXIT_maxtime 0 undefined undefined +SET_prev_nr_mics autopick_trace 0 undefined undefined +autopick_trace extract 0 undefined undefined + extract split 0 undefined undefined + split GLOB_batchnames 0 undefined undefined +GLOB_batchnames SET_mybatch 0 undefined undefined +SET_mybatch COUNT_mybatchsize 0 undefined undefined +COUNT_mybatchsize IS_batchfull 0 undefined undefined +IS_batchfull WAIT 1 class2d is_full_batch + class2d INCR_batch 0 undefined undefined +INCR_batch WAIT 0 undefined undefined + diff --git a/scripts/Schemes/amyproc/split/job.star b/scripts/Schemes/amyproc/split/job.star new file mode 100644 index 000000000..d7f6a8716 --- /dev/null +++ b/scripts/Schemes/amyproc/split/job.star @@ -0,0 +1,51 @@ + +# version 50001 + +data_job + +_rlnJobTypeLabel relion.select.split +_rlnJobIsContinue 0 +_rlnJobIsTomo 0 + + +# version 50001 + +data_joboptions_values + +loop_ +_rlnJobOptionVariable #1 +_rlnJobOptionValue #2 +dendrogram_minclass 1000 +dendrogram_threshold 0.8 +discard_label rlnImageName +discard_sigma 4 +do_class_ranker No +do_discard No +do_filaments No + do_queue No + do_random No +do_recenter No +do_regroup No +do_remove_duplicates No +do_select_values No + do_split Yes +duplicate_threshold 30 + fn_data Schemes/amyproc/extract/particles.star + fn_mic "" + fn_model "" +image_angpix -1 +min_dedicated 24 + nr_groups 1 + nr_split -1 +other_args "" + qsub sbatch +qsubscript /public/EM/RELION/relion-slurm-gpu-4.0.csh + queuename openmpi +rank_threshold 0.5 +select_label rlnMicrographScoreSkewness +select_maxval 9999. +select_minval 1 +select_nr_classes -1 +select_nr_parts -1 +split_size $$batchsize + From e128777244172bd084d6f9407f007fc99b871769 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 2 Jul 2025 15:50:45 +0100 Subject: [PATCH 137/189] try copying standard Scheme from relion scripts directory if it doesn't exist --- scripts/schemegui.in | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/schemegui.in b/scripts/schemegui.in index c153632f6..7912e508e 100755 --- a/scripts/schemegui.in +++ b/scripts/schemegui.in @@ -92,6 +92,17 @@ def load_star(filename): return datasets +def copy_scheme(schemename): + + ## Only copy the Scheme directory structure from the RELION installation directory if it doesn't exist yet + if not os.path.isdir('Schemes/'+schemename): + try: + mydir = os.environ['RELION_SCRIPT_DIRECTORY'] + except KeyError: + raise KeyError("Environment variable $RELION_SCRIPT_DIRECTORY has not been set. This is required to copy RELION-distributed schemes from.") + print(' Copying Schemes/' + schemename + ' from: ' + mydir) + copytree(mydir+'/Schemes/'+schemename, 'Schemes/'+schemename) + class SchemeGui(object): def __init__(self, main_window, schemename): @@ -407,7 +418,11 @@ def main(): else: schemename = sys.argv[1] - print(' RELION_IT: launching GUI...') + schemestar = 'Schemes/' + schemename + '/scheme.star' + if not path.exists(schemestar): + print(' Scheme does not yet exist, try copying from RELION scripts directory... ') + copy_scheme(schemename) + print(' Launching GUI...') tk_root = tk.Tk() titlegui = "Scheme: " + schemename tk_root.title(titlegui) From 9d001db0ea0566651dcc96a1c59aebcf8250f374 Mon Sep 17 00:00:00 2001 From: Dari Kimanius Date: Tue, 8 Jul 2025 14:42:25 -0700 Subject: [PATCH 138/189] Bugfix in subtomo to SPA expansion --- src/apps/pseudotomo_expansion.cpp | 6 +++--- src/exp_model.cpp | 2 +- src/jaz/tomography/tomogram_set.cpp | 4 ++-- src/jaz/tomography/tomogram_set.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/apps/pseudotomo_expansion.cpp b/src/apps/pseudotomo_expansion.cpp index 2cfa46583..7faaeee76 100644 --- a/src/apps/pseudotomo_expansion.cpp +++ b/src/apps/pseudotomo_expansion.cpp @@ -119,9 +119,9 @@ class pseudotomo_expansion shift3d_x, shift3d_y, shift3d_z, shift2d_x, shift2d_y, shift2d_z ); - md.setValue(EMDL_ORIENT_ORIGIN_X_ANGSTROM, shift3d_x); - md.setValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, shift3d_y); - md.setValue(EMDL_ORIENT_ORIGIN_Z_ANGSTROM, shift3d_z); + md.setValue(EMDL_ORIENT_ORIGIN_X_ANGSTROM, shift2d_x); + md.setValue(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, shift2d_y); + md.setValue(EMDL_ORIENT_ORIGIN_Z_ANGSTROM, shift2d_z); } } diff --git a/src/exp_model.cpp b/src/exp_model.cpp index f5145f9e6..601179ff8 100644 --- a/src/exp_model.cpp +++ b/src/exp_model.cpp @@ -989,7 +989,7 @@ bool Experiment::read(FileName fn_exp, FileName fn_tomo, FileName fn_motion, if (tomo_name != prev_tomo_name) { - tomogram = tomogramSet.loadTomogram(tomo_id, false); + tomogram = tomogramSet.loadTomogram(tomo_id, false, false, false, -999, -999, -999, true); // Ignore tomo size prev_tomo_name = tomo_name; // Tomogram sanity checks diff --git a/src/jaz/tomography/tomogram_set.cpp b/src/jaz/tomography/tomogram_set.cpp index dceb6c65d..913ba083f 100644 --- a/src/jaz/tomography/tomogram_set.cpp +++ b/src/jaz/tomography/tomogram_set.cpp @@ -151,7 +151,7 @@ void TomogramSet::removeTomogram(std::string tomogramName) } Tomogram TomogramSet::loadTomogram(int index, bool loadImageData, bool loadEvenFramesOnly, bool loadOddFramesOnly, - int _w0, int _h0, int _d0) const //Set loadEven/OddFramesOnly to True to loadImageData from rlnTomoMicrographNameEven/Odd rather than rlnMicrographName + int _w0, int _h0, int _d0, bool ignore_tomo_size) const //Set loadEven/OddFramesOnly to True to loadImageData from rlnTomoMicrographNameEven/Odd rather than rlnMicrographName { Tomogram out; @@ -175,7 +175,7 @@ Tomogram TomogramSet::loadTomogram(int index, bool loadImageData, bool loadEvenF globalTable.getValueSafely(EMDL_TOMO_SIZE_Y, out.h0, index); globalTable.getValueSafely(EMDL_TOMO_SIZE_Z, out.d0, index); } - else + else if (!ignore_tomo_size) { REPORT_ERROR("ERROR loadTomogram: tomogram sizes have not been set for tomogram " + tomoName); } diff --git a/src/jaz/tomography/tomogram_set.h b/src/jaz/tomography/tomogram_set.h index b3d16962c..391dc1e59 100644 --- a/src/jaz/tomography/tomogram_set.h +++ b/src/jaz/tomography/tomogram_set.h @@ -27,7 +27,7 @@ class TomogramSet void removeTomogram(std::string tomogramName); // If max_dose is positive, then only images with cumulativeDose less than or equal to max_dose will be loaded. - Tomogram loadTomogram(int index, bool loadImageData, bool loadEvenFrames = false, bool loadOddFrames = false, int w0 = -999, int h0 =-999, int d0 = -999 ) const; + Tomogram loadTomogram(int index, bool loadImageData, bool loadEvenFrames = false, bool loadOddFrames = false, int w0 = -999, int h0 =-999, int d0 = -999, bool ignore_tomo_size = false) const; int size() const; void setProjectionAngles(int tomogramIndex, int frame, RFLOAT xtilt, RFLOAT ytilt, RFLOAT zrot, RFLOAT xshift_angst, RFLOAT yshift_angst); From de08e7df803fef82194607bf7a3a87b6b614ae70 Mon Sep 17 00:00:00 2001 From: Dari Kimanius Date: Tue, 8 Jul 2025 14:43:34 -0700 Subject: [PATCH 139/189] relion_project overwrite flag --- src/apps/project.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/apps/project.cpp b/src/apps/project.cpp index e18160244..9998a5398 100644 --- a/src/apps/project.cpp +++ b/src/apps/project.cpp @@ -43,7 +43,7 @@ class project_parameters RFLOAT rot, tilt, psi, xoff, yoff, zoff, angpix, maxres, stddev_white_noise, particle_diameter, ana_prob_range, ana_prob_step, sigma_offset; int padding_factor; int r_max, r_min_nn, interpolator, nr_uniform; - bool do_only_one, do_ctf, do_ctf2, ctf_phase_flipped, do_ctf_intact_1st_peak, do_timing, do_add_noise, do_subtract_exp, do_ignore_particle_name, do_3d_rot, write_float16; + bool do_only_one, do_ctf, do_ctf2, ctf_phase_flipped, do_ctf_intact_1st_peak, do_timing, do_add_noise, do_subtract_exp, do_overwrite_exp, do_ignore_particle_name, do_3d_rot, write_float16; bool do_simulate; RFLOAT simulate_SNR; // I/O Parser @@ -81,6 +81,7 @@ class project_parameters stddev_white_noise = textToFloat(parser.getOption("--white_noise", "Standard deviation of added white Gaussian noise", "0")); fn_model = parser.getOption("--model_noise", "Model STAR file with power spectra for coloured Gaussian noise", ""); do_subtract_exp = parser.checkOption("--subtract_exp", "Subtract projections from experimental images (in --ang)"); + do_overwrite_exp = parser.checkOption("--overwrite_exp", "Overwrite experimental images with projections (in --ang)"); do_ignore_particle_name = parser.checkOption("--ignore_particle_name", "Ignore the rlnParticleName column (in --ang)"); do_only_one = (fn_ang == "None" && nr_uniform < 0); do_3d_rot = parser.checkOption("--3d_rot", "Perform 3D rotations instead of projection into 2D images"); @@ -641,19 +642,31 @@ class project_parameters } else { - // Write this particle to the stack on disc - // First particle: write stack in overwrite mode, from then on just append to it - fn_img.compose(imgno+1,fn_out+".mrcs"); - if (imgno == 0) - img.write(fn_img, -1, false, WRITE_OVERWRITE, write_float16 ? Float16: Float); + if (do_overwrite_exp) + { + DataType dt = static_cast(expimg.dataType()); + img.write(fn_expimg, -1, false, WRITE_REPLACE, dt); + } else - img.write(fn_img, -1, false, WRITE_APPEND, write_float16 ? Float16: Float); + { + // Write this particle to the stack on disc + // First particle: write stack in overwrite mode, from then on just append to it + fn_img.compose(imgno+1,fn_out+".mrcs"); + if (imgno == 0) + img.write(fn_img, -1, false, WRITE_OVERWRITE, write_float16 ? Float16: Float); + else + img.write(fn_img, -1, false, WRITE_APPEND, write_float16 ? Float16: Float); + } } // Set the image name to the output STAR file DFo.addObject(); DFo.setObject(MDang.getObject()); - DFo.setValue(EMDL_IMAGE_NAME,fn_img); + + if (do_overwrite_exp) + DFo.setValue(EMDL_IMAGE_NAME, fn_expimg); + else + DFo.setValue(EMDL_IMAGE_NAME, fn_img); if (do_simulate) { From b4a616f06d5120f98cb4c8267fbbc10ebb55b9fb Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 15 Jul 2025 17:45:21 +0100 Subject: [PATCH 140/189] clarified output better --- src/apps/star_handler.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/apps/star_handler.cpp b/src/apps/star_handler.cpp index 7d030a1d7..10c319640 100644 --- a/src/apps/star_handler.cpp +++ b/src/apps/star_handler.cpp @@ -217,9 +217,9 @@ class star_handler_parameters compareMetaDataTable(MD1, MD2, MDboth, MDonly1, MDonly2, label1, eps, label2, label3); - std::cout << MDboth.numberOfObjects() << " entries occur in both input STAR files." << std::endl; - std::cout << MDonly1.numberOfObjects() << " entries occur only in the 1st input STAR file." << std::endl; - std::cout << MDonly2.numberOfObjects() << " entries occur only in the 2nd input STAR file." << std::endl; + std::cout << MDboth.numberOfObjects() << " entries from the 1st input STAR file also have an entry in the 2nd STAR file." << std::endl; + std::cout << MDonly1.numberOfObjects() << " entries from the 1st input STAR file do NOT have an entry in the 2nd STAR file." << std::endl; + std::cout << MDonly2.numberOfObjects() << " entries from the 2nd Sinput STAR file do NOT have an entry in the 1st STAR file." << std::endl; write_check_ignore_optics(MDboth, fn_out.insertBeforeExtension("_both"), MD1.getName()); std::cout << " Written: " << fn_out.insertBeforeExtension("_both") << std::endl; From 27178abd687c008ab43dde14733a77f12e2179c7 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 16 Jul 2025 15:22:33 +0100 Subject: [PATCH 141/189] introduce threshold on psi-jumps in tracing of amyloids --- scripts/trace_amyloids.in | 75 +++++++++++++++++++++++++++++++++++---- src/amyloid_finder.cpp | 4 ++- src/amyloid_finder.h | 3 ++ src/gui_jobwindow.cpp | 14 ++++---- 4 files changed, 81 insertions(+), 15 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index f4dde3431..7b63c6832 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -365,7 +365,60 @@ def trim_end(branch_coords, trim_length=10, trim_start=False): return branch_coords[trim_length:] if trim_start else branch_coords[:-trim_length] return np.array([]) # Remove short branches completely -def prune_skeleton(skeleton, min_length=100, min_distance=25, step=50): +def angular_diff(a, b, period=180): + """Minimal circular difference between angles a and b on [0, period).""" + raw = abs(a - b) + return min(raw, period - raw) + +def segment_orientation(p0, p1): + """ + Compute the orientation (in degrees) and fold it into [0, 180). + p0, p1 are (row, col) integer tuples or arrays. + """ + # Note: row = y, col = x + dy = p1[0] - p0[0] + dx = p1[1] - p0[1] + # conventions of y-axis is reversed in RELION, so use -dy! + ang = np.degrees(np.arctan2(-dy, dx)) # gives [-180, 180) + + # fold into [0, 180): + if ang < 0: + ang += 180 + elif ang >= 180: + ang -= 180 + + return ang + +def split_branch_on_psi_mismatch(branch_coords, psi_image, threshold=20): + + segments = [] + current_seg = [tuple(branch_coords[0])] + prev_pt = branch_coords[0] + + # step through each next point + for i in range(1, len(branch_coords)): + pt = branch_coords[i] + psi_ang = psi_image[pt[0], pt[1]] + + # compute seg_ang between i-3 and i, fallback to 0-i for i<3 + if i >= 3: + p0 = branch_coords[i - 3] + seg_ang = segment_orientation(p0, pt) + + # split if mismatch > threshold + if angular_diff(seg_ang, psi_ang) > threshold: + segments.append(np.array(current_seg)) + current_seg = [] + + current_seg.append(tuple(pt)) + + # append last segment + if current_seg: + segments.append(np.array(current_seg)) + + return segments + +def prune_skeleton(skeleton, psi_image, min_length=100, min_distance=25, step=50, psi_threshold=20): """Prune skeleton branches that too short.""" # Most of the compute goes into the Skeleton call; rest of function is cheap skel = Skeleton(skeleton) @@ -376,11 +429,17 @@ def prune_skeleton(skeleton, min_length=100, min_distance=25, step=50): accepted_branches = [] # Stores (branch coordinates, start, end) sampled_coords = [] + # Pass 0: split branches with jumps in psi-angle values + all_splits = [] + for idx, row in df.iterrows(): + coords = skel.path_coordinates(idx) + pieces = split_branch_on_psi_mismatch(coords, psi_image, threshold=psi_threshold) + all_splits.extend(pieces) + # Pass 1: Collect branches and compute pairwise distances branch_data = [] # Store branch info for pass 2 endpoints = [] # Store (start, end) tuples - for idx, row in df.iterrows(): - branch_coords = skel.path_coordinates(idx) + for branch_coords in all_splits: # Compute Euclidean length of the branch length = np.sum(np.sqrt(np.sum(np.diff(branch_coords, axis=0) ** 2, axis=1))) @@ -400,7 +459,6 @@ def prune_skeleton(skeleton, min_length=100, min_distance=25, step=50): dists = cdist(endpoints, endpoints) # Mark close endpoint pairs for trimming - #close_pairs = np.where((dists < min_distance) & (dists > 0)) # Exclude self-distances close_pairs = np.where((dists < min_distance) ) # Exclude self-distances trim_flags = {} # Dictionary to store which endpoints need trimming @@ -478,6 +536,7 @@ def main(): parser.add_argument("-r", "--radius", type=int, help="Radius of region to exclude near edges (in A)") parser.add_argument("-l", "--minimum_length", type=int, default=250, help="Remove final segments smaller than this size in Angstroms (default=250)") + parser.add_argument("-p", "--psi_jump_threshold", default=45, type=float, help="Maximum difference in degrees of psi angles for consecutive points in skeletons (default=20)") parser.add_argument("-t", "--threshold", default=0.5, type=float, help="Foreground threshold (default=0.5)") parser.add_argument("-ct", "--carbon_threshold", default=0.9, type=float, help="Carbon detection threshold (default=0.9)") parser.add_argument("-s", "--scale", type=float, default=1.0, @@ -596,15 +655,17 @@ def main(): # 4) Skeletonize skeleton_img = skeletonize(clean_foreground) - + # if there is only a single non-zero pixel, Skeleton will give an error. Let's check for at least 10 non-zero pixels if np.count_nonzero(skeleton_img) > 10: - # 5) Prune skeleton based on Skan's branches: remove any branch with length below args.final_min_size + # 5) Prune skeleton based on Skan's branches: splits branches with jumps in psi-angles, remove any branch with length below args.final_min_size, and enforce minimal distance between end points pruned_skeleton, sampled_points = prune_skeleton(skeleton_img, + psi_image, min_length=args.minimum_length / pixel_size, min_distance=2 * args.radius / pixel_size, - step=int(args.sample_step / pixel_size)) + step=int(args.sample_step / pixel_size), + psi_threshold=args.psi_jump_threshold) # 6) Sample coordinates every 'sample_step' from pruned skeleton, **including filament ID**. #sampled_points = sample_coordinates_with_ids(pruned_skeleton, step=int(args.sample_step / pixel_size)) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 9958add82..1185772e8 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -43,6 +43,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "0.5")); trace_filament_width = textToFloat(parser.getOption("--trace_filament_width", "Minimum width occupied by a traced filaments (in A)", "200")); trace_filament_length = textToFloat(parser.getOption("--trace_filament_length", "Minimum length of traced filaments (in A)", "300")); + psi_jump_threshold = textToFloat(parser.getOption("--psi_jump_threshold", "Maximum difference in PSI values between consecutive elements of a skeletonised branch (in degrees)", "45")); do_plot = parser.checkOption("--plot", "Display images with intermediate tracing results for each micrograph"); fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_trace_amyloids"); fn_other_args = parser.getOption("--other_args", "Other arguments for the python script", ""); @@ -812,6 +813,7 @@ void AmyloidFinder::runTracingBatch(long int my_first, long int my_last, int my_ command += " -t " + floatToString(threshold); command += " -r " + floatToString(trace_filament_width/2); command += " -l " + floatToString(trace_filament_length); + command += " -p " + floatToString(psi_jump_threshold); command += " -s " + floatToString(down_angpix/angpix); command += " -a " + pipeline_control_outputname+RELION_JOB_ABORT_NOW; command += " -v " + integerToString(verb); @@ -825,7 +827,7 @@ void AmyloidFinder::runTracingBatch(long int my_first, long int my_last, int my_ command += " " + fn_other_args; - //std::cerr << command << std::endl; + std::cerr << command << std::endl; int res = system(command.c_str()); if (pipeline_control_check_abort_job()) diff --git a/src/amyloid_finder.h b/src/amyloid_finder.h index 6c5f0482a..8b55cd2ec 100644 --- a/src/amyloid_finder.h +++ b/src/amyloid_finder.h @@ -82,6 +82,9 @@ class AmyloidFinder // threshold for carbon detection RFLOAT carbon_threshold; + // threshold for psi-jumps in tracing skeletons + RFLOAT psi_jump_threshold; + // FileName for carbon detection model FileName fn_carbon_model_path; diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 2203198fb..0eb3099fa 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -948,21 +948,21 @@ void JobWindow::initialiseAutopickWindow() tab7->label("Amyloid"); resetHeight(); - place("do_amyloid_fom"); + place("do_amyloid_fom", TOGGLE_LEAVE_ACTIVE); current_y += STEPY/2; group5 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); group5->end(); - place("do_amyloid_tracing", TOGGLE_LEAVE_ACTIVE, group5); + place("do_amyloid_tracing", TOGGLE_DEACTIVATE, group5); group5->begin(); - place("amyloid_threshold"); - place("amyloid_length"); - place("amyloid_width"); - place("amyloid_gpu_ids"); - place("do_amyloid_plot"); + place("amyloid_threshold", TOGGLE_DEACTIVATE); + place("amyloid_length", TOGGLE_DEACTIVATE); + place("amyloid_width", TOGGLE_DEACTIVATE); + place("amyloid_gpu_ids", TOGGLE_DEACTIVATE); + place("do_amyloid_plot", TOGGLE_DEACTIVATE); current_y += STEPY/2; From 6fda9765f55cc287bbffb57931f41439adf73a2c Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 16 Jul 2025 18:18:24 +0100 Subject: [PATCH 142/189] further changes to psi-angle tracing and defaults on gui for amyloid tracing --- scripts/trace_amyloids.in | 28 ++++++++++++++++++---------- src/amyloid_finder.cpp | 2 +- src/pipeline_jobs.cpp | 4 ++-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/scripts/trace_amyloids.in b/scripts/trace_amyloids.in index 7b63c6832..e7001d7dd 100755 --- a/scripts/trace_amyloids.in +++ b/scripts/trace_amyloids.in @@ -394,31 +394,39 @@ def split_branch_on_psi_mismatch(branch_coords, psi_image, threshold=20): segments = [] current_seg = [tuple(branch_coords[0])] prev_pt = branch_coords[0] - + prev_ang = psi_image[prev_pt[0], prev_pt[1]] + # step through each next point for i in range(1, len(branch_coords)): pt = branch_coords[i] psi_ang = psi_image[pt[0], pt[1]] - # compute seg_ang between i-3 and i, fallback to 0-i for i<3 - if i >= 3: + # split if jump in neighbour psi image values > threshold + do_split = False + if angular_diff(prev_ang, psi_ang) > threshold: + do_split = True + # compute seg_ang between i-3 and i + elif i >= 3: p0 = branch_coords[i - 3] seg_ang = segment_orientation(p0, pt) - # split if mismatch > threshold if angular_diff(seg_ang, psi_ang) > threshold: - segments.append(np.array(current_seg)) - current_seg = [] + do_split = True + if do_split: + segments.append(np.array(current_seg)) + current_seg = [] + current_seg.append(tuple(pt)) - + prev_ang = psi_ang + # append last segment if current_seg: segments.append(np.array(current_seg)) return segments -def prune_skeleton(skeleton, psi_image, min_length=100, min_distance=25, step=50, psi_threshold=20): +def prune_skeleton(skeleton, psi_image, min_length=100, min_distance=25, step=50, psi_threshold=45): """Prune skeleton branches that too short.""" # Most of the compute goes into the Skeleton call; rest of function is cheap skel = Skeleton(skeleton) @@ -533,10 +541,10 @@ def main(): parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint") parser.add_argument("-c", "--carbon_model_path", help="Path to PyTorch model checkpoint for carbon detection", default="") parser.add_argument("-o", "--output", help="Output star file with coordinates") - parser.add_argument("-r", "--radius", type=int, help="Radius of region to exclude near edges (in A)") + parser.add_argument("-r", "--radius", type=int, help="Minimum distance between ends of filaments (in A)") parser.add_argument("-l", "--minimum_length", type=int, default=250, help="Remove final segments smaller than this size in Angstroms (default=250)") - parser.add_argument("-p", "--psi_jump_threshold", default=45, type=float, help="Maximum difference in degrees of psi angles for consecutive points in skeletons (default=20)") + parser.add_argument("-p", "--psi_jump_threshold", default=45, type=float, help="Maximum difference in degrees of psi angles for consecutive points in skeletons (default=45)") parser.add_argument("-t", "--threshold", default=0.5, type=float, help="Foreground threshold (default=0.5)") parser.add_argument("-ct", "--carbon_threshold", default=0.9, type=float, help="Carbon detection threshold (default=0.9)") parser.add_argument("-s", "--scale", type=float, default=1.0, diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 1185772e8..612063b3a 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -41,7 +41,7 @@ void AmyloidFinder::read(int argc, char **argv, int rank) int pick_section = parser.addSection("Filament tracing options "); threshold = textToFloat(parser.getOption("--threshold", "Threshold in Z-scores for coordinate picking", "0.5")); - trace_filament_width = textToFloat(parser.getOption("--trace_filament_width", "Minimum width occupied by a traced filaments (in A)", "200")); + trace_filament_width = textToFloat(parser.getOption("--trace_filament_width", "Minimum width occupied by a traced filaments (in A)", "100")); trace_filament_length = textToFloat(parser.getOption("--trace_filament_length", "Minimum length of traced filaments (in A)", "300")); psi_jump_threshold = textToFloat(parser.getOption("--psi_jump_threshold", "Maximum difference in PSI values between consecutive elements of a skeletonised branch (in degrees)", "45")); do_plot = parser.checkOption("--plot", "Display images with intermediate tracing results for each micrograph"); diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 6b637ca94..2b9c8287b 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2060,8 +2060,8 @@ Helical tubes with shorter lengths will not be picked. Note that a long helical joboptions["amyloid_gpu_ids"] = JobOption("Which GPUs to use for tracing:", std::string(""), "This argument is not necessary. If left empty, the job itself will try to allocate available GPU resources. You can override the default allocation by providing a list of which GPUs to use. MPI-processes are separated by ':'. For example: 0:1:2:3. Note, only the amyloid tracing part of the job is run on the GPU; FOM calculation has only been implemented for the CPU."); joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 0.25, 0.05, 1, 0.05, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); joboptions["do_amyloid_plot"] = JobOption("Plot results per micrograph?", false, "Set this option to Yes to visualise intermediate results for amyloid tracing, which may help with setting values for filament length, width and picking threshold. Don't use in parallel or in submission to the queue."); - joboptions["amyloid_width"] = JobOption("Amyloid width (A): ", 200, 100, 1000, 10, "Width (in Angstroms) used for FOM calculation and/or tracing. "); - joboptions["amyloid_length"] = JobOption("Amyloid minimum length (A): ", 500, 100, 1000, 10, "Length (in Angstroms) used for FOM calculation, and minimum length of filaments for tracing."); + joboptions["amyloid_width"] = JobOption("Minimum distance between filaments (A): ", 100, 50, 1000, 10, "Minimum distance between ends of traced filaments (in Angstroms). "); + joboptions["amyloid_length"] = JobOption("Minimum filament length (A): ", 500, 100, 1000, 10, "Length (in Angstroms) used for FOM calculation, and minimum length of filaments for tracing."); joboptions["do_amyloid_carbon"] = JobOption("Ignore filaments on carbon?", false, "Set to Yes if you want to use automated carbon detection to avoid picking filaments on carbon."); joboptions["amyloid_carbon_threshold"] = JobOption("Carbon threshold: ", 0.9, 0.05, 1, 0.05, "At what probability should pixels in the micrographs be deemed carbon? Use lower values to have more areas exclude from the picking."); From b918cf270b8b65faf264e411c65e8a362aea509a Mon Sep 17 00:00:00 2001 From: Bogdan Toader Date: Wed, 17 Sep 2025 19:33:29 +0100 Subject: [PATCH 143/189] fix rounding of box size when Fourier cropping image stacks in FourierCrop_fftwHalfStack --- src/jaz/image/resampling.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jaz/image/resampling.h b/src/jaz/image/resampling.h index b59b8afd0..eb8d06527 100644 --- a/src/jaz/image/resampling.h +++ b/src/jaz/image/resampling.h @@ -451,9 +451,9 @@ BufferedImage Resampling::FourierCrop_fftwHalfStack(const BufferedImage& i const int w0 = (wh0 - 1) * 2; const int h0 = img.ydim; - const int w1 = w0 / factor; + const int w1 = (int)(w0 / factor + 0.5); const int wh1 = w1/2 + 1; - const int h1 = h0 / factor; + const int h1 = (int)(h0 / factor + 0.5); const int fc = img.zdim; From a1d1a6c06ed9c7c08cae40d026c2357966b383c2 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 19 Sep 2025 09:45:39 +0100 Subject: [PATCH 144/189] pinned version for opencv-python which is necessary for imprt cv2 in trace_amyloids --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 04869c584..ed0770e67 100644 --- a/environment.yml +++ b/environment.yml @@ -28,7 +28,7 @@ dependencies: - numpy==1.26.1 - scipy==1.11.2 - skan==0.12.2 - - opencv-python-headless + - opencv-python==4.10.0.84 - git+https://github.com/3dem/relion-classranker - git+https://github.com/3dem/relion-blush - git+https://github.com/3dem/DynaMight From b12c63eb03ba3ec8df0a2bc7fd37515a0e34db1a Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 19 Sep 2025 14:20:22 +0100 Subject: [PATCH 145/189] dont save relion_it_options.py in schemegui --- scripts/schemegui.in | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/scripts/schemegui.in b/scripts/schemegui.in index 7912e508e..17bd31330 100755 --- a/scripts/schemegui.in +++ b/scripts/schemegui.in @@ -28,7 +28,6 @@ import tkinter as tk #from Tkinter import scrolledtext from tkinter import messagebox -OPTIONS_FILE = 'relion_it_options.py' IS_RUNNING = False # Colour definitions @@ -282,17 +281,6 @@ class SchemeGui(object): pass self.schemevar_value_var.set(myval) - def update_relion_it_options(self, myoption): - with open(OPTIONS_FILE) as file: - user_opts = collections.OrderedDict(ast.literal_eval(file.read())) - user_opts.update(myoption) - with open(OPTIONS_FILE, 'w') as file: - file.write("{\n") - for k,v in user_opts.items(): - file.write("'%s' : '%s', \n" % (k, v)) - file.write("}\n") - print(' RELION_IT: updated ', OPTIONS_FILE) - def set_joboption(self, *args_ignored, **kwargs_ignored): jobstar = 'Schemes/' + self.schemename + '/' + self.jobname_var.get() + '/job.star' command = 'relion_pipeliner --editJob ' + jobstar + ' --editOption ' + self.joboption_var.get() + ' --editValue \"' + str(self.joboption_value_entry.get()) + '\"' @@ -302,17 +290,11 @@ class SchemeGui(object): command2 = 'relion_schemer --scheme ' + self.schemename + ' --set_has_started ' + self.jobname_var.get() + ' --value False' print(' RELION_IT: excuting: ', command2) os.system(command2) - myoption = self.schemename + '__' + self.jobname_var.get() + '__' + self.joboption_var.get() - mydic = {myoption : self.joboption_value_entry.get()} - self.update_relion_it_options(mydic) def set_schemevar(self, *args_ignored, **kwargs_ignored): command = 'relion_schemer --scheme ' + self.schemename + ' --set_var ' + self.schemevar_var.get() + ' --value \"' + self.schemevar_value_var.get() + '\"' + ' --original_value \"' + self.schemevar_value_var.get() + '\"' print(' RELION_IT: excuting: ', command) os.system(command) - myoption = self.schemename + '__' + self.schemevar_var.get() - mydic = {myoption : self.schemevar_value_var.get()} - self.update_relion_it_options(mydic) def abort_scheme(self, *args_ignored, **kwargs_ignored): command = 'relion_schemer --scheme ' + self.schemename + ' --abort' From 1e2c625e3c5a13b66bd2d5ed5dc791df200794b2 Mon Sep 17 00:00:00 2001 From: Takanori Nakane Date: Fri, 17 Oct 2025 17:06:18 +0900 Subject: [PATCH 146/189] FindCUDA is removed in newer CMake versions (GitHub issue #1016, #1161, PR #1077). This is work in progress. DO NOT USE!!! - Compilation succeeds with CUDA=ON, CUDA=OFF or ALTCPU=ON. - CUDA compilation flags have not been ported yet. - HIP and SYCL have not been tested yet. --- CMakeLists.txt | 22 +++--- src/apps/CMakeLists.txt | 144 +++++++++++++--------------------------- 2 files changed, 57 insertions(+), 109 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b8cd5060..62d6f2690 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,5 @@ -cmake_minimum_required(VERSION 2.8 FATAL_ERROR) +cmake_minimum_required(VERSION 3.20 FATAL_ERROR) -#if(POLICY CMP0048) -# cmake_policy(SET CMP0048 NEW) -#endif() project(Relion) # Use new policy for OS X @rpath @@ -176,15 +173,15 @@ endif() if(CUDA) # -----------------------------------------------------------------------------CUDA-- - # DOC: http://www.cmake.org/cmake/help/v3.0/module/FindCUDA.html - FIND_PACKAGE(CUDA) + FIND_PACKAGE(CUDAToolkit) + enable_language(CUDA) # "-arch=sm_XX" alone is equal to "-arch=compute_XX -code=compute_XX,sm_XX" # compute_XX generates PTX; thus GPUs newer than XX can also use the binary. # See https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/#gpu-architecture-arch. set(CUDA_ARCH "" CACHE STRING "CUDA architecture to use") if (CUDA_ARCH STREQUAL "") - if (${CUDA_VERSION} VERSION_LESS "13.0") # Pascal (e.g. 1080) + if (${CUDAToolkit_VERSION} VERSION_LESS "13.0") # Pascal (e.g. 1080) message(STATUS "Setting fallback CUDA_ARCH=50 (Pascal)") set(CUDARCH "-arch=sm_50") else() # CUDA >= 13.0 no longer supports Pascal. 75 = Turing (e.g. 2080) @@ -196,7 +193,7 @@ if(CUDA) set(CUDARCH "-arch=sm_${CUDA_ARCH}") endif () - if(CUDA_FOUND) + if(CUDAToolkit_FOUND) message(STATUS "Using cuda wrapper to compile....") if( (NOT ${CUDA_VERSION} VERSION_LESS "7.5") AND (NOT DoublePrec_ACC) ) message(STATUS "Cuda version is >= 7.5 and single-precision build, enable double usage warning.") @@ -209,9 +206,9 @@ if(CUDA) if(CUDA) add_definitions(-D_CUDA_ENABLED) endif() - else(CUDA_FOUND) + else(CUDAToolkit_FOUND) message(FATAL_ERROR "CUDA enabled but unlable to locate packages...") - endif(CUDA_FOUND) + endif(CUDAToolkit_FOUND) elseif(HIP) # ------------------------------------------------------------------------------HIP-- if (DEFINED ENV{ROCM_PATH}) @@ -675,6 +672,11 @@ message(STATUS "CMAKE_CXX_FLAGS : ${CMAKE_CXX_FLAGS}") #message(STATUS "MPI_C_COMPILER : ${MPI_C_COMPILER}") #message(STATUS "MPI_CXX_COMPILER : ${MPI_CXX_COMPILER}") #message(STATUS "CMAKE_EXE_LINKER_FLAGS : ${CMAKE_EXE_LINKER_FLAGS}") +#message(STATUS "CUDAToolkit_FOUND: ${CUDAToolkit_FOUND}") +#get_target_property(CUDART_LIB CUDA::cudart IMPORTED_LOCATION) +#message(STATUS "CUDA::cudart location: ${CUDART_LIB}") +#get_target_property(CUDART_INC CUDA::cudart INTERFACE_INCLUDE_DIRECTORIES) +#message(STATUS "CUDA::cudart includes: ${CUDART_INC}") # -----------------------------------------------------------------------------TESTS-- # Include testing flag(s) as precomiler diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index 1a57330a5..06edec8fe 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -223,7 +223,7 @@ endif() # FFTW_LIBRARIES is used even with MKLFFT. # This is empty for ICC. # See https://github.com/3dem/relion/issues/958#issuecomment-1498521352. -target_link_libraries(relion_lib ${FFTW_LIBRARIES}) +target_link_libraries(relion_lib PUBLIC ${FFTW_LIBRARIES}) if(NOT MKLFFT) if(BUILD_OWN_FFTW) add_dependencies(relion_lib own_fftw_lib) @@ -235,20 +235,20 @@ endif(NOT MKLFFT) if(GUI) include_directories("${FLTK_INCLUDE_DIR}") - target_link_libraries(relion_gui_lib relion_lib ${FLTK_LIBRARIES}) + target_link_libraries(relion_gui_lib PUBLIC relion_lib ${FLTK_LIBRARIES}) if(BUILD_OWN_FLTK) add_dependencies(relion_gui_lib OWN_FLTK) endif() endif(GUI) -if (CUDA_FOUND) +if (CUDAToolkit_FOUND) file(GLOB REL_CUDA_SRC "${CMAKE_SOURCE_DIR}/src/acc/cuda/*.cu" "${CMAKE_SOURCE_DIR}/src/acc/cuda/cuda_kernels/*.cu" ) - cuda_add_library(relion_gpu_util ${REL_CUDA_SRC}) + add_library(relion_gpu_util ${REL_CUDA_SRC}) if (${CMAKE_BUILD_TYPE_LOWER} STREQUAL "profiling") - find_library(NV_TOOLS_LIBRARIES NAMES nvToolsExt PATHS ${CUDA_TOOLKIT_ROOT_DIR}/lib ${CUDA_TOOLKIT_ROOT_DIR}/lib64) + find_library(NV_TOOLS_LIBRARIES NAMES nvToolsExt PATHS ${CUDA_TOOLKIT_ROOT_DIR}/lib ${CUDA_TOOLKIT_ROOT_DIR}/lib64) list(APPEND EXTRA_LIBS "${NV_TOOLS_LIBRARIES}") - target_link_libraries(relion_gpu_util ${NV_TOOLS_LIBRARIES}) + target_link_libraries(relion_gpu_util PUBLIC ${NV_TOOLS_LIBRARIES}) message(STATUS "Adding extra library for NVIDIA profiling: ${NV_TOOLS_LIBRARIES}") endif() @@ -264,32 +264,23 @@ if (CUDA_FOUND) set_target_properties(relion_gpu_util PROPERTIES LINK_FLAGS "${new_link_flags}") endif() - list(APPEND EXTRA_LIBS "${CUDA_CUFFT_LIBRARIES}") if(BUILD_SHARED_LIBS) install (TARGETS relion_gpu_util LIBRARY DESTINATION lib) else() - target_link_libraries(relion_gpu_util relion_lib) - target_link_libraries(relion_gpu_util ${CUDA_CUFFT_LIBRARIES}) + target_link_libraries(relion_gpu_util PUBLIC relion_lib) endif() - target_link_libraries(relion_lib relion_gpu_util ${CUDA_CUFFT_LIBRARIES}) - target_link_libraries(relion_lib relion_gpu_util ${CUDA_CUFFT_LIBRARIES} ${CUDA_curand_LIBRARY}) - - - file(GLOB REL_JAZ_CUDA_SRC "${CMAKE_SOURCE_DIR}/src/jaz/cuda/*.cu") - cuda_add_library(relion_jaz_gpu_util ${REL_JAZ_CUDA_SRC}) + add_library(relion_jaz_gpu_util ${REL_JAZ_CUDA_SRC}) - list(APPEND EXTRA_LIBS "${CUDA_CUFFT_LIBRARIES}") if(BUILD_SHARED_LIBS) install (TARGETS relion_jaz_gpu_util LIBRARY DESTINATION lib) else() - target_link_libraries(relion_jaz_gpu_util relion_lib) - target_link_libraries(relion_jaz_gpu_util ${CUDA_CUFFT_LIBRARIES}) + target_link_libraries(relion_jaz_gpu_util PUBLIC relion_lib) endif() - target_link_libraries(relion_lib relion_jaz_gpu_util ${CUDA_CUFFT_LIBRARIES}) - target_link_libraries(relion_lib relion_jaz_gpu_util ${CUDA_CUFFT_LIBRARIES} ${CUDA_curand_LIBRARY}) + target_link_libraries(relion_lib PUBLIC relion_gpu_util relion_jaz_gpu_util) + target_link_libraries(relion_lib PUBLIC CUDA::cudart CUDA::cufft CUDA::curand) elseif (HIP_FOUND) file(GLOB REL_HIP_SRC "${CMAKE_SOURCE_DIR}/src/acc/hip/*.cpp" "${CMAKE_SOURCE_DIR}/src/acc/hip/hip_kernels/*.cpp" ) @@ -298,7 +289,7 @@ elseif (HIP_FOUND) if (${CMAKE_BUILD_TYPE_LOWER} STREQUAL "profiling") find_library(ROCM_TRACER_LIB NAMES roctx64 PATHS ${HIP_ROOT_DIR}/lib ${HIP_ROOT_DIR}/../lib REQUIRED) list(APPEND EXTRA_LIBS "${ROCM_TRACER_LIB}") - target_link_libraries(relion_gpu_util ${ROCM_TRACER_LIB}) + target_link_libraries(relion_gpu_util PUBLIC ${ROCM_TRACER_LIB}) message(STATUS "Adding extra library for AMD profiling: ${ROCM_TRACER_LIB}") endif() @@ -318,28 +309,19 @@ elseif (HIP_FOUND) if(BUILD_SHARED_LIBS) install (TARGETS relion_gpu_util LIBRARY DESTINATION lib) else() - target_link_libraries(relion_gpu_util relion_lib) - target_link_libraries(relion_gpu_util ${HIPFFT_LIBRARIES}) + target_link_libraries(relion_gpu_util PUBLIC relion_lib) endif() - target_link_libraries(relion_lib relion_gpu_util ${HIPFFT_LIBRARIES}) - target_link_libraries(relion_lib relion_gpu_util ${HIPFFT_LIBRARIES} ${HIPRAND_LIBRARIES}) - target_link_libraries(relion_lib relion_gpu_util ${HIPCUB_LIBRARIES}) + target_link_libraries(relion_lib PUBLIC relion_gpu_util ${HIPFFT_LIBRARIES}) + target_link_libraries(relion_lib PUBLIC relion_gpu_util ${HIPFFT_LIBRARIES} ${HIPRAND_LIBRARIES}) + target_link_libraries(relion_lib PUBLIC relion_gpu_util ${HIPCUB_LIBRARIES}) file(GLOB REL_JAZ_HIP_SRC "${CMAKE_SOURCE_DIR}/src/jaz/hip/*.cpp" "${CMAKE_SOURCE_DIR}/src/jaz/hip/kernels/*.cpp" ) hip_add_library(relion_jaz_gpu_util ${REL_JAZ_HIP_SRC}) - #list(APPEND EXTRA_LIBS "${HIPFFT_LIBRARIES}") - #if(BUILD_SHARED_LIBS) - # install (TARGETS relion_jaz_gpu_util LIBRARY DESTINATION lib) - #else() - # target_link_libraries(relion_jaz_gpu_util relion_lib) - # target_link_libraries(relion_jaz_gpu_util ${HIPFFT_LIBRARIES}) - #endif() - - target_link_libraries(relion_lib relion_jaz_gpu_util ${HIPFFT_LIBRARIES}) - target_link_libraries(relion_lib relion_jaz_gpu_util ${HIPFFT_LIBRARIES} ${HIPRAND_LIBRARIES}) - target_link_libraries(relion_lib relion_gpu_util ${HIPCUB_LIBRARIES}) + target_link_libraries(relion_lib PUBLIC relion_jaz_gpu_util ${HIPFFT_LIBRARIES}) + target_link_libraries(relion_lib PUBLIC relion_jaz_gpu_util ${HIPFFT_LIBRARIES} ${HIPRAND_LIBRARIES}) + target_link_libraries(relion_lib PUBLIC relion_gpu_util ${HIPCUB_LIBRARIES}) elseif(SYCL) @@ -387,41 +369,28 @@ elseif(SYCL) set_source_files_properties(${_source} PROPERTIES COMPILE_FLAGS -qno-openmp) endif() endforeach() - target_link_libraries(relion_sycl_util sycl OpenCL relion_lib) + target_link_libraries(relion_sycl_util PUBLIC sycl OpenCL relion_lib) endif() if(TIFF_FOUND) include_directories(${TIFF_INCLUDE_DIRS}) - target_link_libraries(relion_lib ${TIFF_LIBRARIES}) + target_link_libraries(relion_lib PUBLIC ${TIFF_LIBRARIES}) endif() if(PNG_FOUND) include_directories(${PNG_INCLUDE_DIRS}) - target_link_libraries(relion_lib ${PNG_LIBRARY}) -else() - find_library(PNG_LIBRARY NAMES png PATHS /share/modules/libpng/1.6.37/lib/ /usr/local/lib) - if(PNG_LIBRARY) - message("Trying manual search for PNG") - add_definitions(-DHAVE_PNG) - message(STATUS "PNG_LIBRARY: ${PNG_LIBRARY}") - target_link_libraries(relion_lib ${PNG_LIBRARY}) - endif() + target_link_libraries(relion_lib PUBLIC ${PNG_LIBRARY}) endif() if(JPEG_FOUND) include_directories(${JPEG_INCLUDE_DIRS}) - target_link_libraries(relion_lib ${JPEG_LIBRARY}) -else() - find_library(JPEG_LIBRARY NAMES jpeg PATHS /share/modules/libjpeg/62.2.0/lib64/ /usr/local/lib) - if(JPEG_LIBRARY) - message("Trying manual search for JPEG") - add_definitions(-DHAVE_JPEG) - message(STATUS "JPEG_LIBRARY: ${JPEG_LIBRARY}") - target_link_libraries(relion_lib ${JPEG_LIBRARY}) - endif() + target_link_libraries(relion_lib PUBLIC ${JPEG_LIBRARY}) endif() +#get_target_property(RELION_LIB_LINKS relion_lib LINK_LIBRARIES) +#message(STATUS "relion_lib links to: ${RELION_LIB_LINKS}") + if(BUILD_OWN_TBB) add_dependencies(relion_lib OWN_TBB) endif() @@ -437,22 +406,16 @@ foreach (_target ${RELION_TARGETS}) set_target_properties(${_target} PROPERTIES PREFIX "relion_") endif() - set(LIB relion_lib) - add_dependencies(${_target} relion_lib) if(NOT MKLFFT) - target_link_libraries(${_target} ${LIB} ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) + target_link_libraries(${_target} PUBLIC relion_lib ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) else() - target_link_libraries(${_target} ${LIB} ${FFTW_LIBRARIES} ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) + target_link_libraries(${_target} PUBLIC relion_lib ${FFTW_LIBRARIES} ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) endif(NOT MKLFFT) - if(CUDA_FOUND OR HIP_FOUND) - target_link_libraries(${_target} relion_gpu_util) - endif() - if(SYCL) - target_link_libraries(${_target} relion_sycl_util sycl OpenCL relion_lib) + target_link_libraries(${_target} PUBLIC relion_sycl_util sycl OpenCL relion_lib) list(FIND SYCL_TARGETS ${_target} IS_SYCL_TARGET) if(NOT ${IS_SYCL_TARGET} LESS 0) if(DEFINED SYCL_CUDA_COMPILE OR DEFINED SYCL_HIP_COMPILE) @@ -470,7 +433,7 @@ foreach (_target ${RELION_TARGETS}) endif(SYCL) if (ALTCPU) - target_link_libraries(${_target} ${TBB_LIBRARIES}) + target_link_libraries(${_target} PUBLIC ${TBB_LIBRARIES}) endif(ALTCPU) set_target_properties(${_target} PROPERTIES @@ -480,63 +443,47 @@ foreach (_target ${RELION_TARGETS}) list(FIND GUI_TARGETS ${_target} IS_GUI_TARGET) if((NOT ${IS_GUI_TARGET} LESS 0) OR (${_target} STREQUAL "relion")) add_dependencies(${_target} relion_gui_lib) - target_link_libraries(${_target} relion_gui_lib ${FLTK_LIBRARIES} ${X11}) - endif() - - if(TIFF_FOUND) - target_link_libraries(${_target} ${TIFF_LIBRARIES}) + target_link_libraries(${_target} PUBLIC relion_gui_lib ${FLTK_LIBRARIES} ${X11}) endif() - list(FIND RELION_MPI_TARGETS ${_target}.cpp IS_MPI_TARGET) - if(NOT ${IS_MPI_TARGET} LESS 0) - target_link_libraries(${_target} ${MPI_LIBRARIES}) - endif() + install(TARGETS ${_target} RUNTIME DESTINATION bin) - install (TARGETS ${_target} RUNTIME DESTINATION bin) + #get_target_property(EXE_LINKS ${_target} LINK_LIBRARIES) + #message(STATUS "${_target} links to: ${EXE_LINKS}") endforeach() foreach (_target ${RELION_JAZ_SPA_TARGETS}) GET_FILENAME_COMPONENT(_target "relion_${_target}" NAME_WE) add_executable(${_target} ${CMAKE_SOURCE_DIR}/src/jaz/single_particle/apps/${_target}.cpp) - set(LIB relion_lib) add_dependencies(${_target} relion_lib) set_target_properties(${_target} PROPERTIES PREFIX "relion_") - target_link_libraries(${_target} ${LIB} ${FFTW_LIBRARIES} ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) + target_link_libraries(${_target} PUBLIC relion_lib ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) set_target_properties(${_target} PROPERTIES CXX_EXTENSIONS OFF ) - if(TIFF_FOUND) - target_link_libraries(${_target} ${TIFF_LIBRARIES}) - endif() - - if(SYCL) - target_link_libraries(${_target} sycl OpenCL relion_lib) - endif(SYCL) + if(SYCL) + target_link_libraries(${_target} PUBLIC sycl OpenCL relion_lib) + endif(SYCL) if (ALTCPU) - target_link_libraries(${_target} ${TBB_LIBRARIES}) + target_link_libraries(${_target} PUBLIC ${TBB_LIBRARIES}) endif(ALTCPU) - install (TARGETS ${_target} RUNTIME DESTINATION bin) + install(TARGETS ${_target} RUNTIME DESTINATION bin) endforeach() # former Dynamite programs: foreach (_target ${RELION_JAZ_TOMO_TARGETS}) GET_FILENAME_COMPONENT(_target "relion_tomo_${_target}" NAME_WE) add_executable(${_target} ${CMAKE_SOURCE_DIR}/src/jaz/tomography/apps/${_target}.cpp) - set(LIB relion_lib) add_dependencies(${_target} relion_lib) set_target_properties(${_target} PROPERTIES PREFIX "relion_tomo_") - target_link_libraries(${_target} ${LIB} ${FFTW_LIBRARIES} ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) + target_link_libraries(${_target} PUBLIC relion_lib ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) set_target_properties(${_target} PROPERTIES CXX_EXTENSIONS OFF ) - if(TIFF_FOUND) - target_link_libraries(${_target} ${TIFF_LIBRARIES}) - endif() - - if(SYCL) - target_link_libraries(${_target} sycl OpenCL relion_lib) - endif(SYCL) + if(SYCL) + target_link_libraries(${_target} PUBLIC sycl OpenCL relion_lib) + endif(SYCL) install (TARGETS ${_target} RUNTIME DESTINATION bin) endforeach() @@ -544,10 +491,9 @@ endforeach() foreach (_target ${RELION_JAZ_EXPERIMENTAL_TARGETS}) GET_FILENAME_COMPONENT(_target "relion_exp_${_target}" NAME_WE) add_executable(${_target} ${CMAKE_SOURCE_DIR}/src/jaz/scripts/${_target}.cpp) - set(LIB relion_lib) add_dependencies(${_target} relion_lib) set_target_properties(${_target} PROPERTIES PREFIX "relion_exp_") - target_link_libraries(${_target} ${LIB} ${FFTW_LIBRARIES} ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) + target_link_libraries(${_target} relion_lib ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) set_target_properties(${_target} PROPERTIES CXX_EXTENSIONS OFF ) if(TIFF_FOUND) From 19785d7781e942b74de59c2fdf36f2813ce16e7b Mon Sep 17 00:00:00 2001 From: Takanori Nakane Date: Mon, 20 Oct 2025 18:10:48 +0900 Subject: [PATCH 147/189] CMake: improved NVCC discovery and set C/C++ standards and CUDA_ARCH properly. WIP: NVCC FLAGS (most importantly, OpenMP flags) are not respected yet. THE RESULTING BINARY SHOULD NOT BE USED! --- CMakeLists.txt | 84 ++++++++++++++++++++---------------------- cmake/BuildTypes.cmake | 4 +- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 62d6f2690..42ffe857c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,13 +69,12 @@ endif() # ----------------------------------------------------------------SET CXX STANDARD-- set(CXX_STANDARD_REQUIRED ON) -if(SYCL) - set(CMAKE_CXX_STANDARD 17) -else() - set(CMAKE_CXX_STANDARD 14) -endif() +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_EXTENSIONS OFF) +set(C_STANDARD_REQUIRED ON) +set(CMAKE_C_STANDARD 99) + # ------------------OPTIONS WHICH ARE NEEDED TO SET BUILD-TYPES (COMPILATION FLAGS)-- # -------------------------------------------------------------------FURTHER OPTIONS-- @@ -174,41 +173,40 @@ endif() if(CUDA) # -----------------------------------------------------------------------------CUDA-- FIND_PACKAGE(CUDAToolkit) - enable_language(CUDA) - - # "-arch=sm_XX" alone is equal to "-arch=compute_XX -code=compute_XX,sm_XX" - # compute_XX generates PTX; thus GPUs newer than XX can also use the binary. - # See https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/#gpu-architecture-arch. - set(CUDA_ARCH "" CACHE STRING "CUDA architecture to use") - if (CUDA_ARCH STREQUAL "") - if (${CUDAToolkit_VERSION} VERSION_LESS "13.0") # Pascal (e.g. 1080) - message(STATUS "Setting fallback CUDA_ARCH=50 (Pascal)") - set(CUDARCH "-arch=sm_50") - else() # CUDA >= 13.0 no longer supports Pascal. 75 = Turing (e.g. 2080) - message(STATUS "Setting fallback CUDA_ARCH=75 (Turing)") - set(CUDARCH "-arch=sm_75") - endif() - else () - message(STATUS "Using provided CUDA_ARCH=${CUDA_ARCH}") - set(CUDARCH "-arch=sm_${CUDA_ARCH}") - endif () - - if(CUDAToolkit_FOUND) - message(STATUS "Using cuda wrapper to compile....") - if( (NOT ${CUDA_VERSION} VERSION_LESS "7.5") AND (NOT DoublePrec_ACC) ) - message(STATUS "Cuda version is >= 7.5 and single-precision build, enable double usage warning.") - set(WARN_DBL "--ptxas-options=-warn-double-usage") # cuda>=7.5 - elseif( ${CUDA_VERSION} VERSION_LESS "7.0") - message(WARNING "Cuda version is less than 7.0, so relion will be compiled without GPU support.") - message(STATUS "Using non-cuda compilation....") - set(CUDA OFF) - endif() - if(CUDA) - add_definitions(-D_CUDA_ENABLED) - endif() - else(CUDAToolkit_FOUND) - message(FATAL_ERROR "CUDA enabled but unlable to locate packages...") - endif(CUDAToolkit_FOUND) + + if((CUDAToolkit_FOUND) AND ("${CUDAToolkit_VERSION}" VERSION_GREATER_EQUAL "10.0")) + message("-- CUDAToolkit discovered CUDA " ${CUDAToolkit_VERSION} " at " ${CUDAToolkit_BIN_DIR}) + set(CMAKE_CUDA_STANDARD 17) + + # Very puzzling but `CUDAToolkit` package and `enable_language(CUDA)` use + # different methods to find `nvcc`. The latter does NOT respect `PATH` (`which nvcc`) and + # can pick up /usr/bin/nvcc. We can override this by setting `CMAKE_CUDA_COMPILER`. + # Another useful variable is `CMAKE_CUDA_HOST_COMPILER`. + set(CMAKE_CUDA_COMPILER "${CUDAToolkit_BIN_DIR}/nvcc" CACHE FILEPATH "Path to the CUDA compiler (nvcc)") + enable_language(CUDA) + + # "-arch=sm_XX" alone is equal to "-arch=compute_XX -code=compute_XX,sm_XX" + # compute_XX generates PTX; thus GPUs newer than XX can also use the binary. + # See https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/#gpu-architecture-arch. + set(CUDA_ARCH "" CACHE STRING "CUDA architecture to use") + if (CUDA_ARCH STREQUAL "") + if (${CUDAToolkit_VERSION} VERSION_LESS "13.0") # Pascal (e.g. 1080) + message(STATUS "Setting fallback CUDA_ARCH=50 (Pascal)") + set(CMAKE_CUDA_ARCHITECTURES 50) + else() # CUDA >= 13.0 no longer supports Pascal. 75 = Turing (e.g. 2080) + message(STATUS "Setting fallback CUDA_ARCH=75 (Turing)") + set(CMAKE_CUDA_ARCHITECTURES 75) + endif() + else () + message(STATUS "Using provided CUDA_ARCH=${CUDA_ARCH}") + set(CMAKE_CUDA_ARCHITECTURES ${CUDA_ARCH}) + endif () + + add_definitions(-D_CUDA_ENABLED) + else() + message("!! CUDA was enabled but CUDA >= 10.0 cannot be located. CUDA is set to OFF.") + set(CUDA OFF) + endif() elseif(HIP) # ------------------------------------------------------------------------------HIP-- if (DEFINED ENV{ROCM_PATH}) @@ -380,16 +378,14 @@ if(SYCL) # Intel DPC++ specific set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fiopenmp") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fiopenmp") - set(CMAKE_C_FLAGS "-std=c99 ${CMAKE_C_FLAGS}") - set(CMAKE_CXX_FLAGS "-fPIC -std=c++17 ${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS "-fPIC ${CMAKE_CXX_FLAGS}") else() FIND_PACKAGE(OpenMP REQUIRED) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") - set(CMAKE_C_FLAGS "-std=c99 ${CMAKE_C_FLAGS}") - set(CMAKE_CXX_FLAGS "-fPIC -std=c++17 ${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS "-fPIC ${CMAKE_CXX_FLAGS}") endif() # ----------------------------------------------------------Intel Compiler support -- diff --git a/cmake/BuildTypes.cmake b/cmake/BuildTypes.cmake index 63e5ac257..1d28f8a78 100644 --- a/cmake/BuildTypes.cmake +++ b/cmake/BuildTypes.cmake @@ -5,8 +5,8 @@ # Add -G to allow cuda-gdb to break inside kernels. if(CUDA) - set(EXTRA_NVCC_FLAGS "-D__INTEL_COMPILER --default-stream per-thread --std=c++17") - set(RELION_NVCC_FLAGS "${CUDARCH} ${WARN_DBL} ${EXTRA_NVCC_FLAGS}" CACHE STRING "" FORCE) + set(EXTRA_NVCC_FLAGS "-D__INTEL_COMPILER --default-stream per-thread") + set(RELION_NVCC_FLAGS "${WARN_DBL} ${EXTRA_NVCC_FLAGS}" CACHE STRING "" FORCE) elseif (HIP) if (${HIP_VERSION} VERSION_LESS "5.3" ) set(EXTRA_HIPCC_FLAGS "-fgpu-default-stream=legacy -fno-gpu-rdc -munsafe-fp-atomics") From 2d9c9e9eda80ace2cae2d6e2cbcb716627456982 Mon Sep 17 00:00:00 2001 From: Takanori Nakane Date: Tue, 21 Oct 2025 16:40:10 +0900 Subject: [PATCH 148/189] CMake: properly set NVCC flags --- CMakeLists.txt | 49 ++++++++++++++++++++---------------------- cmake/BuildTypes.cmake | 17 +++++++-------- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 42ffe857c..7400c516e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,7 +166,7 @@ else(DoublePrec_ACC) endif(DoublePrec_ACC) if(MDT_TYPE_CHECK) - message("-- MetaDataTable type check is enabled.") + message(STATUS "MetaDataTable type check is enabled.") add_definitions(-DMETADATA_TABLE_TYPE_CHECK) endif() @@ -175,7 +175,7 @@ if(CUDA) FIND_PACKAGE(CUDAToolkit) if((CUDAToolkit_FOUND) AND ("${CUDAToolkit_VERSION}" VERSION_GREATER_EQUAL "10.0")) - message("-- CUDAToolkit discovered CUDA " ${CUDAToolkit_VERSION} " at " ${CUDAToolkit_BIN_DIR}) + message(STATUS "CUDAToolkit discovered CUDA " ${CUDAToolkit_VERSION} " at " ${CUDAToolkit_BIN_DIR}) set(CMAKE_CUDA_STANDARD 17) # Very puzzling but `CUDAToolkit` package and `enable_language(CUDA)` use @@ -252,6 +252,24 @@ elseif(ALTCPU) endif() endif() +# ----------------------------------------------------------------- OpenMP ---------- +# This must come before including BuildTypes.cmake to properly set OpenMP flags to NVCC. +# CUDA support by the OpenMP package came at CMake 3.31 (too new). + +if(SYCL) # Intel DPC++ specific; I don't know when the OpenMP package supports SYCL + set(OpenMP_C_FLAGS "-fiopenmp") + set(OpenMP_CXX_FLAGS "-fiopenmp") + set(OpenMP_EXE_LINKER_FLAGS "-fiopenmp") +else() + FIND_PACKAGE(OpenMP REQUIRED) +endif() + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") +set(CMAKE_CXX_FLAGS "-fPIC ${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") + +#message(STATUS "OpenMP_CXX_FLAGS: ${OpenMP_CXX_FLAGS}") + # ----------------------------------------------------------INCLUDE ALL BUILD TYPES--- # This has to be AFTER project() and find_package(HIP). # Up to RELION 5.0.0, this must come BEFORE find_package(CUDA) as discussed in issue #1032. @@ -367,27 +385,6 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -# ----------------------------------------------------------------- OpenMP ---------- -# This MUST be set before all cuda_add_executable/library calls. -# Otherwise the -Xcompiler argument to NVCC does not contain -fopenmp -# and #pragma omp critical is SILENTLY ignored, giving a wrong binary!!! -# https://github.com/3dem/relion/issues/1016 - -if(SYCL) # Intel DPC++ specific - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fiopenmp") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fiopenmp") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fiopenmp") - - set(CMAKE_CXX_FLAGS "-fPIC ${CMAKE_CXX_FLAGS}") -else() - FIND_PACKAGE(OpenMP REQUIRED) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") - - set(CMAKE_CXX_FLAGS "-fPIC ${CMAKE_CXX_FLAGS}") -endif() - # ----------------------------------------------------------Intel Compiler support -- # ---------------------------------- and build flags including MKL and IPP --------- @@ -479,9 +476,9 @@ if(GUI) endif(NOT FLTK_FOUND) else(X11_FOUND) - message( STATUS "\n-- ------------------ YOU HAVE NO X11-LIBS ------------------") - message( STATUS "CCmake found no X11-libs on your system, which are required for the GUI.") - message( STATUS " You CAN add the flag -DGUI=OFF to avoid using X11" ) + message(STATUS "\n-- ------------------ YOU HAVE NO X11-LIBS ------------------") + message(STATUS "CCmake found no X11-libs on your system, which are required for the GUI.") + message(STATUS " You CAN add the flag -DGUI=OFF to avoid using X11" ) message(FATAL_ERROR "X11 is required for GUI.") endif(X11_FOUND) diff --git a/cmake/BuildTypes.cmake b/cmake/BuildTypes.cmake index 1d28f8a78..4749cecfc 100644 --- a/cmake/BuildTypes.cmake +++ b/cmake/BuildTypes.cmake @@ -5,8 +5,7 @@ # Add -G to allow cuda-gdb to break inside kernels. if(CUDA) - set(EXTRA_NVCC_FLAGS "-D__INTEL_COMPILER --default-stream per-thread") - set(RELION_NVCC_FLAGS "${WARN_DBL} ${EXTRA_NVCC_FLAGS}" CACHE STRING "" FORCE) + set(RELION_NVCC_FLAGS "-D__INTEL_COMPILER --default-stream per-thread -Xcompiler ${OpenMP_CXX_FLAGS} " CACHE STRING "" FORCE) elseif (HIP) if (${HIP_VERSION} VERSION_LESS "5.3" ) set(EXTRA_HIPCC_FLAGS "-fgpu-default-stream=legacy -fno-gpu-rdc -munsafe-fp-atomics") @@ -45,7 +44,7 @@ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${RELION_FLAGS_DEBUG}") set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} ${RELION_LINKER_FLAGS_DEBUG}") if(CUDA) - set(CUDA_NVCC_FLAGS_DEBUG "${RELION_NVCC_FLAGS_DEBUG}" CACHE STRING "" FORCE) + set(CMAKE_CUDA_FLAGS_DEBUG "${RELION_NVCC_FLAGS_DEBUG}" CACHE STRING "" FORCE) elseif(HIP) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${RELION_HIPCC_FLAGS_DEBUG}") endif() @@ -61,7 +60,7 @@ endif() # message(STATUS "Set the extra flags for Debug build type") # message(STATUS "RELION_NVCC_FLAGS_DEBUG : ${RELION_NVCC_FLAGS_DEBUG}") -# message(STATUS "CUDA_NVCC_FLAGS_DEBUG : ${CUDA_NVCC_FLAGS_DEBUG}") +# message(STATUS "CMAKE_CUDA_FLAGS_DEBUG : ${CMAKE_CUDA_FLAGS_DEBUG}") # message(STATUS "CMAKE_CXX_FLAGS_DEBUG : ${CMAKE_CXX_FLAGS_DEBUG}") #-------------------------------------------------------------------- @@ -84,7 +83,7 @@ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${R set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${RELION_FLAGS_RELWITHDEBINFO}") set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} ${RELION_LINKER_FLAGS_RELWITHDEBINFO}") if(CUDA) - set(CUDA_NVCC_FLAGS_RELWITHDEBINFO "${RELION_NVCC_FLAGS_RELWITHDEBINFO}" CACHE STRING "" FORCE) + set(CMAKE_CUDA_FLAGS_RELWITHDEBINFO "${RELION_NVCC_FLAGS_RELWITHDEBINFO}" CACHE STRING "" FORCE) # -- Add preprocessor defintions ------------------------------------ set(RELION_DEFINITIONS_RELWITHDEBINFO "-DDEBUG_CUDA") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${RELION_DEFINITIONS_RELWITHDEBINFO}") @@ -97,7 +96,7 @@ endif() # message(STATUS "Set the extra flags for RELWITHDEBINFO build type") # message(STATUS "RELION_NVCC_FLAGS_RELWITHDEBINFO : ${RELION_NVCC_FLAGS_RELWITHDEBINFO}") -# message(STATUS "CUDA_NVCC_FLAGS_RELWITHDEBINFO : ${CUDA_NVCC_FLAGS_RELWITHDEBINFO}") +# message(STATUS "CMAKE_CUDA_FLAGS_RELWITHDEBINFO : ${CMAKE_CUDA_FLAGS_RELWITHDEBINFO}") # message(STATUS "CMAKE_CXX_FLAGS_RELWITHDEBINFO : ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") #-------------------------------------------------------------------- @@ -132,7 +131,7 @@ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_FLAGS_RE set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${RELION_FLAGS_RELEASE}") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${RELION_LINKER_FLAGS_RELEASE}") if(CUDA) - set(CUDA_NVCC_FLAGS_RELEASE "${RELION_NVCC_FLAGS_RELEASE}" CACHE STRING "" FORCE) + set(CMAKE_CUDA_FLAGS_RELEASE "${RELION_NVCC_FLAGS_RELEASE}" CACHE STRING "" FORCE) elseif(HIP) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_HIPCC_FLAGS_RELEASE}") endif() @@ -168,7 +167,7 @@ if(CUDA) set(CMAKE_CXX_FLAGS_PROFILING "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_FLAGS_PROFILING}" CACHE STRING "") set(CMAKE_C_FLAGS_PROFILING "${CMAKE_C_FLAGS_RELEASE} ${RELION_FALAGS_PROFILING}" CACHE STRING "") set(CMAKE_EXE_LINKER_FLAGS_PROFILING "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${RELION_LINKER_FLAGS_PROFILING}" CACHE STRING "") - set(CUDA_NVCC_FLAGS_PROFILING "${RELION_NVCC_FLAGS_PROFILING}" CACHE STRING "" FORCE) + set(CMAKE_CUDA_FLAGS_PROFILING "${RELION_NVCC_FLAGS_PROFILING}" CACHE STRING "" FORCE) # -- Add preprocessor defintions ------------------------------------ set(RELION_DEFINITIONS_PROFILING "-DCUDA_PROFILING") @@ -225,7 +224,7 @@ set(CMAKE_CXX_FLAGS_BENCHMARKING "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_FLA set(CMAKE_C_FLAGS_BENCHMARKING "${CMAKE_C_FLAGS_RELEASE} ${RELION_FLAGS_BENCHMARKING}" CACHE STRING "" FORCE) set(CMAKE_EXE_LINKER_FLAGS_BENCHMARKING "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${RELION_LINKER_FLAGS_BENCHMARKING}" CACHE STRING "" FORCE) if(CUDA) - set(CUDA_NVCC_FLAGS_BENCHMARKING "${RELION_NVCC_FLAGS_BENCHMARKING}" CACHE STRING "" FORCE) + set(CMAKE_CUDA_FLAGS_BENCHMARKING "${RELION_NVCC_FLAGS_BENCHMARKING}" CACHE STRING "" FORCE) elseif(HIP) set(CMAKE_CXX_FLAGS_BENCHMARKING "${CMAKE_CXX_FLAGS_RELEASE} ${RELION_HIPCC_FLAGS_BENCHMARKING}") endif() From d97c63bbf6f7408a52b2520152ce4fd06157a369 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 11 Nov 2025 09:32:19 +0000 Subject: [PATCH 149/189] implemented min/max for ice rings for Yue Zhang --- src/ctffind_runner.cpp | 6 ++++-- src/ctffind_runner.h | 3 +++ src/particle_subtractor.cpp | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index ea2524b97..72e3f3e45 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -66,6 +66,8 @@ void CtffindRunner::read(int argc, char **argv, int rank) int ctffind4_section = parser.addSection("CTFFIND4 parameters"); is_ctffind4 = parser.checkOption("--is_ctffind4", "The provided CTFFIND executable is CTFFIND4 (version 4.1+)"); use_given_ps = parser.checkOption("--use_given_ps", "Use pre-calculated power spectra?"); + ice_min = textToFloat(parser.getOption("--ice_min", "Minimum frequency for ice ring density (in 1/A)", "0.25")); + ice_max = textToFloat(parser.getOption("--ice_max", "Maximum frequency for ice ring density (in 1/A)", "0.28")); do_extra_ice = parser.checkOption("--extra_ice_stats", "When using pre-calculated power spectra, one can calculate extra ice statistics, which may be useful for selection of icy data sets"); do_movie_thon_rings = parser.checkOption("--do_movie_thon_rings", "Calculate Thon rings from movie frames?"); avg_movie_frames = textToInteger(parser.getOption("--avg_movie_frames", "Average over how many movie frames (try to get 4 e-/A2)", "1")); @@ -998,12 +1000,12 @@ bool CtffindRunner::getCtffind4Results(FileName fn_microot, RFLOAT &defU, RFLOAT int imin = -999; int imax = -999; for (int i = 0; i < words.size(); i++) - if (imin < 0 && textToFloat(words[i]) >= 0.25) { + if (imin < 0 && textToFloat(words[i]) >= ice_min) { imin = i; break; } for (int i = imin; i < words.size(); i++) - if (imax < 0 && imin > 0 && textToFloat(words[i]) > 0.28) { + if (imax < 0 && imin > 0 && textToFloat(words[i]) > ice_max) { imax = i; break; } diff --git a/src/ctffind_runner.h b/src/ctffind_runner.h index 7c2e18448..ba8799822 100644 --- a/src/ctffind_runner.h +++ b/src/ctffind_runner.h @@ -139,6 +139,9 @@ class CtffindRunner RFLOAT angpix; + // Resolution band for Rafa's ice ring statistics + RFLOAT ice_min, ice_max; + // Flag to only join results into a star file bool do_only_join_results; diff --git a/src/particle_subtractor.cpp b/src/particle_subtractor.cpp index 888eef923..c85cc309a 100644 --- a/src/particle_subtractor.cpp +++ b/src/particle_subtractor.cpp @@ -398,7 +398,7 @@ void ParticleSubtractor::saveStarFile(int myrank) } #ifdef DEBUG - std::cout << "myrank = " << myrank << " size = " << size << " my_first = " << my_first << " my_last = " << my_last << " num_items = " << MD.numberOfObjects() << " writing to " << fn_star << std::endl; + std::cout << "myrank = " << myrank << " size = " << size << std::endl; #endif } From 41451e60e1ea8fa64aa4c1cc3508651edf5cb8ce Mon Sep 17 00:00:00 2001 From: Takanori Nakane Date: Wed, 12 Nov 2025 10:21:04 +0900 Subject: [PATCH 150/189] At the moment, build fails with CMake <= 3.20. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7400c516e..45f015b8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20 FATAL_ERROR) +cmake_minimum_required(VERSION 3.21 FATAL_ERROR) project(Relion) From 80ffe1872ff87b981d1243da38d602d888ba8172 Mon Sep 17 00:00:00 2001 From: scheres Date: Mon, 15 Dec 2025 15:23:52 +0000 Subject: [PATCH 151/189] add downloading of amyblush, amy-v1.0 weights --- scripts/python_fetch_weights.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/python_fetch_weights.in b/scripts/python_fetch_weights.in index b0de26dc3..61eaa9b8e 100755 --- a/scripts/python_fetch_weights.in +++ b/scripts/python_fetch_weights.in @@ -39,6 +39,8 @@ if [ -x "$target_path" ]; then # Unsuccessful, trap the EXIT signal to call the custom exit function trap 'exit_with_error' EXIT fi + # Execute the target binary for amy-v1.0 + "$target_path" -m amy-v1.0 else echo "Error: $target_binary not found or not executable in $script_dir." exit 1 From 603bc587677db4e50e61fcd6e2bb283caeeeba0f Mon Sep 17 00:00:00 2001 From: scheres Date: Mon, 15 Dec 2025 15:24:56 +0000 Subject: [PATCH 152/189] added amyblush on GUI, and format os subtomos as a dropdown menu --- src/gui_jobwindow.cpp | 34 +++++++++++++++++------- src/gui_jobwindow.h | 2 +- src/pipeline_jobs.cpp | 62 ++++++++++++++++++++----------------------- src/pipeline_jobs.h | 11 ++++++++ 4 files changed, 66 insertions(+), 43 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index 0eb3099fa..c97e91196 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -1581,7 +1581,14 @@ void JobWindow::initialiseClass3DWindow() // Add a little spacer current_y += STEPY/2; - place("do_blush", TOGGLE_DEACTIVATE); + // Blush group + group9 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group9->end(); + place("do_blush", TOGGLE_DEACTIVATE, group9); + group9->begin(); + place("blush_version"); + group9->end(); + guientries["do_blush"].cb_menu_i(); // This is to make the default effective tab4->end(); @@ -1760,7 +1767,14 @@ void JobWindow::initialiseAutorefineWindow() // Add a little spacer current_y += STEPY/2; - place("do_blush", TOGGLE_DEACTIVATE); + // Blush group + group9 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group9->end(); + place("do_blush", TOGGLE_DEACTIVATE, group9); + group9->begin(); + place("blush_version"); + group9->end(); + guientries["do_blush"].cb_menu_i(); // This is to make the default effective tab4->end(); tab5->begin(); @@ -1870,7 +1884,14 @@ void JobWindow::initialiseMultiBodyWindow() // Add a little spacer current_y += STEPY/2; - place("do_blush", TOGGLE_DEACTIVATE); + // Blush group + group9 = new Fl_Group(WCOL0, MENUHEIGHT, 550, 600-MENUHEIGHT, ""); + group9->end(); + place("do_blush", TOGGLE_DEACTIVATE, group9); + group9->begin(); + place("blush_version"); + group9->end(); + guientries["do_blush"].cb_menu_i(); // This is to make the default effective tab1->end(); tab2->begin(); @@ -2909,12 +2930,7 @@ void JobWindow::initialiseTomoSubtomoWindow() current_y += STEPY /2 ; - place("do_stack2d", TOGGLE_DEACTIVATE); - place("do_pseudo3d", TOGGLE_DEACTIVATE); - place("do_real_subtomo", TOGGLE_DEACTIVATE); - - current_y += STEPY /2 ; - + place("subtomo_format", TOGGLE_DEACTIVATE); place("do_float16", TOGGLE_DEACTIVATE); tab2->end(); diff --git a/src/gui_jobwindow.h b/src/gui_jobwindow.h index 25c5be6d1..43de02a56 100644 --- a/src/gui_jobwindow.h +++ b/src/gui_jobwindow.h @@ -56,7 +56,7 @@ class JobWindow : public Fl_Box Fl_Group *tab1, *tab2, *tab3, *tab4, *tab5, *tab6, *tab7, *runtab; // Groups - Fl_Group *group0, *group1, *group2, *group3, *group4, *group5, *group6, *group7, *group8, *queue_group; + Fl_Group *group0, *group1, *group2, *group3, *group4, *group5, *group6, *group7, *group8, *group9, *queue_group; public: // Constructor with x, y, w, h and a title diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index 2b9c8287b..f1dbf3f79 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -3790,7 +3790,8 @@ High-resolution refinements (e.g. ribosomes or other large complexes in 3D auto- joboptions["highres_limit"] = JobOption("Limit resolution E-step to (A): ", -1, -1, 20, 1, "If set to a positive number, then the expectation step (i.e. the alignment) will be done only including the Fourier components up to this resolution (in Angstroms). \ This is useful to prevent overfitting, as the classification runs in RELION are not to be guaranteed to be 100% overfitting-free (unlike the 3D auto-refine with its gold-standard FSC). In particular for very difficult data sets, e.g. of very small or featureless particles, this has been shown to give much better class averages. \ In such cases, values in the range of 7-12 Angstroms have proven useful."); - joboptions["do_blush"] = JobOption("Use Blush regularisation?", false, "If set to Yes, relion_refine will use a neural network to perform regularisation by denoising at every iteration, instead of the standard smoothness regularisation."); + joboptions["do_blush"] = JobOption("Use Blush regularisation?", false, "If set to Yes, relion_refine will use a neural network to perform regularisation by denoising at every iteration, instead of the standard smoothness regularisation."); + joboptions["blush_version"] = JobOption("Blush network version:", job_blush_version_options, 0, "Which version of the Blush network to use. v1.0 is the original version published in Kiamnius et al (2024) Nature Methods; amy-v1.0 is a newer version that was trained specifically for use with amyloid filaments."); joboptions["dont_skip_align"] = JobOption("Perform image alignment?", true, "If set to No, then rather than \ performing both alignment and classification, only classification will be performed. This allows the use of very focused masks.\ @@ -4057,7 +4058,10 @@ bool RelionJob::getCommandsClass3DJob(std::string &outputname, std::vector 0) { @@ -4285,6 +4289,7 @@ High-resolution refinements (e.g. ribosomes or other large complexes in 3D auto- masked half-maps are used and a post-processing-like correction of the FSC curves (with phase-randomisation) is performed every iteration. This only works when a reference mask is provided on the I/O tab. \ This may yield higher-resolution maps, especially when the mask contains only a relatively small volume inside the box."); joboptions["do_blush"] = JobOption("Use Blush regularisation?", false, "If set to Yes, relion_refine will use a neural network to perform regularisation by denoising at every iteration, instead of the standard smoothness regularisation."); + joboptions["blush_version"] = JobOption("Blush network version:", job_blush_version_options, 0, "Which version of the Blush network to use. v1.0 is the original version published in Kiamnius et al (2024) Nature Methods; amy-v1.0 is a newer version that was trained specifically for use with amyloid filaments."); joboptions["sampling"] = JobOption("Initial angular sampling:", job_sampling_options, 2, "There are only a few discrete \ angular samplings possible because we use the HealPix library to generate the sampling of the first two Euler angles on the sphere. \ @@ -4506,6 +4511,7 @@ bool RelionJob::getCommandsAutorefineJob(std::string &outputname, std::vector 1) - { - error_message = "You have to choose either to write output as 2D stacks, 3D pseudo-subtomos, or to extract real subtomograms from the tomographic reconstruction."; - return false; - } - if (joboptions["nr_mpi"].getNumber(error_message) > 1) command="`which relion_tomo_subtomo_mpi`"; else @@ -7236,25 +7233,24 @@ bool RelionJob::getCommandsTomoSubtomoJob(std::string &outputname, std::vector 0.) command += " --min_frames " + joboptions["min_frames"].getString(); - if (joboptions["do_float16"].getBoolean()) - { - command += " --float16 "; - } - - if (joboptions["do_stack2d"].getBoolean()) - { - command += " --stack2d "; - } + if (strcmp((joboptions["subtomo_format"].getString()).c_str(), job_subtomo_format_options[0].c_str()) == 0) + { + command += " --stack2d "; + } + else if (strcmp((joboptions["subtomo_format"].getString()).c_str(), job_subtomo_format_options[2].c_str()) == 0) + { + command += " --real_subtomo "; - if (joboptions["do_real_subtomo"].getBoolean()) - { - command += " --real_subtomo "; + Node node2(outputname+"particles_for_class2d.star", LABEL_CLASS2D_PARTS); + outputNodes.push_back(node2); + } - Node node2(outputname+"particles_for_class2d.star", LABEL_CLASS2D_PARTS); - outputNodes.push_back(node2); - } + if (joboptions["do_float16"].getBoolean()) + { + command += " --float16 "; + } - if (is_continue) + if (is_continue) { command += " --only_do_unfinished "; } diff --git a/src/pipeline_jobs.h b/src/pipeline_jobs.h index b9a1e4201..b7b0eb228 100644 --- a/src/pipeline_jobs.h +++ b/src/pipeline_jobs.h @@ -159,6 +159,17 @@ static const std::vector job_modelangelo_alphabet_options{ "RNA" }; +static const std::vector job_subtomo_format_options{ + "2D stacks", + "3D pseudo-subtomos", + "3D subtomos" +}; + +static const std::vector job_blush_version_options{ + "ver1.0", + "amy-ver1.0" +}; + // To have a line on the GUI to change the minimum number of dedicated in a job static bool do_allow_change_minimum_dedicated; From 4d79820537571589f318699c6cab2e3303b99bec Mon Sep 17 00:00:00 2001 From: scheres Date: Mon, 15 Dec 2025 16:32:35 +0000 Subject: [PATCH 153/189] repaired small bug in blush versions --- src/pipeline_jobs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pipeline_jobs.h b/src/pipeline_jobs.h index b7b0eb228..e9ef7fbb4 100644 --- a/src/pipeline_jobs.h +++ b/src/pipeline_jobs.h @@ -166,8 +166,8 @@ static const std::vector job_subtomo_format_options{ }; static const std::vector job_blush_version_options{ - "ver1.0", - "amy-ver1.0" + "v1.0", + "amy-v1.0" }; // To have a line on the GUI to change the minimum number of dedicated in a job From f09dd599318953f5ec123210109a476f7c8ace99 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 16 Dec 2025 13:20:41 +0000 Subject: [PATCH 154/189] deal with amyloid picker weights --- scripts/python_fetch_weights.in | 19 +++++ ...e_amyloids.in => python_trace_amyloids.in} | 82 +++++++++++++++++-- src/amyloid_finder.cpp | 6 +- 3 files changed, 97 insertions(+), 10 deletions(-) rename scripts/{trace_amyloids.in => python_trace_amyloids.in} (90%) mode change 100755 => 100644 diff --git a/scripts/python_fetch_weights.in b/scripts/python_fetch_weights.in index 61eaa9b8e..05190174d 100755 --- a/scripts/python_fetch_weights.in +++ b/scripts/python_fetch_weights.in @@ -46,6 +46,25 @@ else exit 1 fi +# Call amypicker weight download ##################################################### +echo "Attempting to download weights for amyloid filament picker..." + +target_path="$script_dir/relion_python_amyloid_tracer" + +if [ -x "$target_path" ]; then + # Execute the target binary + "$target_path" + if [ $? -ne 0 ]; then + # Unsuccessful, trap the EXIT signal to call the custom exit function + trap 'exit_with_error' EXIT + fi + # Uncomment in case one also wanted to download the older amytracer-v1.0 weights.... + #"$target_path" -m amytracer-v1.0 +else + echo "Error: $target_binary not found or not executable in $script_dir." + exit 1 +fi + # ModelAngelo weight download #################################################### echo "Attempting to download weights for ModelAngelo..." target_path="$script_dir/relion_python_modelangelo" diff --git a/scripts/trace_amyloids.in b/scripts/python_trace_amyloids.in old mode 100755 new mode 100644 similarity index 90% rename from scripts/trace_amyloids.in rename to scripts/python_trace_amyloids.in index e7001d7dd..c0b378b57 --- a/scripts/trace_amyloids.in +++ b/scripts/python_trace_amyloids.in @@ -530,6 +530,64 @@ def resize_and_norm(image, mysize, angpix, do_norm=True): return resized, resized_angpix + +def install_model( + name: str, + verbose: bool = False, +): + model_list = { + "amytracer-v1.0": [ + "https://zenodo.org/records/17949642/files/amytracer-v1.0.ckpt.gz", + "d83777816d899d64aef593c97888e88f56d49e2b652cdc460de554f59d6f94ed" + ], + "amytracer-v2.0": [ + "https://zenodo.org/records/17949642/files/amytracer-v1.0.ckpt.gz", + "57f0cd566ca1779641691f7daf4a6147ccdb4f509f01c7472861e32bb7dbe14c" + ], + "carbonpicker-v1.0": [ + "https://zenodo.org/records/17949642/files/carbonpicker-v1.0.ckpt.gz", + "e25702cc84850339b46ac5b7d8e19ebdd55ad420c15bda8c9f9a584f59e7fd6b" + ] + } + + if name in model_list.keys(): + dest_dir = os.path.join(torch.hub.get_dir(), "checkpoints", "relion_trace_amyloids") + model_path = os.path.join(dest_dir, f"{name}.ckpt") + model_path_gz = model_path + ".gz" + completed_check_path = os.path.join(dest_dir, f"{name}_installed.txt") + + # Download file and install it if not already done + if not os.path.isfile(completed_check_path): + if verbose: + print(f"Installing amyloid picker model ({name})...") + os.makedirs(dest_dir, exist_ok=True) + + source_url = model_list[name][0] + + if verbose: + print(f"Downloading model weights from:\n {source_url}") + + import gzip, shutil + torch.hub.download_url_to_file(source_url, model_path_gz, hash_prefix=model_list[name][1]) + with gzip.open(model_path_gz, 'rb') as f_in: + with open(model_path, 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + os.remove(model_path_gz) + + with open(completed_check_path, "w") as f: + f.write("Successfully downloaded model") + + if verbose: + print(f"Amyloid picker model ({name}) successfully installed in {dest_dir}") + + else: + model_path = name + + return model_path + + + + ############################################################################### # MAIN ############################################################################### @@ -537,11 +595,11 @@ def main(): #start_time = time.time() parser = argparse.ArgumentParser("Skeleton-based filament picking with instance merging, branch pruning, and ID labeling") - parser.add_argument("-i", "--input", help="Input star file") - parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint") - parser.add_argument("-c", "--carbon_model_path", help="Path to PyTorch model checkpoint for carbon detection", default="") - parser.add_argument("-o", "--output", help="Output star file with coordinates") - parser.add_argument("-r", "--radius", type=int, help="Minimum distance between ends of filaments (in A)") + parser.add_argument("-i", "--input", help="Input star file", default="None") + parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint", default="amytracer-v2.0") + parser.add_argument("-c", "--carbon_model_path", help="Path to PyTorch model checkpoint for carbon detection", default="carbonpicker-v1.0") + parser.add_argument("-o", "--output", help="Output star file with coordinates", default="out") + parser.add_argument("-r", "--radius", type=int, help="Minimum distance between ends of filaments (in A)", default=25) parser.add_argument("-l", "--minimum_length", type=int, default=250, help="Remove final segments smaller than this size in Angstroms (default=250)") parser.add_argument("-p", "--psi_jump_threshold", default=45, type=float, help="Maximum difference in degrees of psi angles for consecutive points in skeletons (default=45)") @@ -564,7 +622,16 @@ def main(): # ------------------- Load model ---------------------- device = torch.device(args.device) - model = UNet.load_from_checkpoint(args.model_path, n_channels=3, n_classes=1) + + # This is for when we call this program without any arguments upon installation of the relion package (in scripts/python_fetch_weights.in) + if (args.input == "None"): + my_model_path = install_model(args.model_path, verbose=True) + my_carbon_model_path = install_model(args.carbon_model_path, verbose=True) + sys.exit(0) + + # Load checkpoint file + my_model_path = install_model(args.model_path) + model = UNet.load_from_checkpoint(my_model_path, n_channels=3, n_classes=1) model.eval().to(device) if args.carbon_model_path == "": if args.verb > 0: @@ -574,7 +641,8 @@ def main(): do_carbon = True if args.verb > 0: print(" + Doing carbon detection") - carbon_model = UNetCarbon.load_from_checkpoint(args.carbon_model_path, n_channels=1, n_classes=1) + my_carbon_model_path = install_model(args.carbon_model_path) + carbon_model = UNetCarbon.load_from_checkpoint(my_carbon_model_path, n_channels=1, n_classes=1) carbon_model.eval().to(device) input_star = starfile.read(args.input) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 612063b3a..399e1f1cc 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -45,11 +45,11 @@ void AmyloidFinder::read(int argc, char **argv, int rank) trace_filament_length = textToFloat(parser.getOption("--trace_filament_length", "Minimum length of traced filaments (in A)", "300")); psi_jump_threshold = textToFloat(parser.getOption("--psi_jump_threshold", "Maximum difference in PSI values between consecutive elements of a skeletonised branch (in degrees)", "45")); do_plot = parser.checkOption("--plot", "Display images with intermediate tracing results for each micrograph"); - fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_trace_amyloids"); + fn_exe = parser.getOption("--exe", "Name of python script for filament tracing", "relion_python_trace_amyloids"); fn_other_args = parser.getOption("--other_args", "Other arguments for the python script", ""); - fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing","/public/EM/RELION/amypicker2.ckpt"); + fn_model_path = parser.getOption("--model_path", "Name of the model to execute for filament tracing","amytracer-v2.0"); do_carbon = parser.checkOption("--detect_carbon", "Detect carbon and ignore filaments on there."); - fn_carbon_model_path = parser.getOption("--carbon_model_path", "Name of the model to execute for carbon detection","/public/EM/RELION/carbon.ckpt"); + fn_carbon_model_path = parser.getOption("--carbon_model_path", "Name of the model to execute for carbon detection","carbonpicker-v1.0"); carbon_threshold = textToFloat(parser.getOption("--carbon_threshold", "Threshold for carbon detection", "0.9")); do_skip_tracing = parser.checkOption("--skip_tracing", "Skip tracing."); do_gpu = parser.checkOption("--gpu", "Use GPU acceleration when availiable"); From 86b939f77673cb1ad7c3032d3a3e42f269ef3581 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 16 Dec 2025 13:44:47 +0000 Subject: [PATCH 155/189] debugging fetching weights for amypicker --- CMakeLists.txt | 2 +- scripts/python_fetch_weights.in | 2 +- scripts/python_trace_amyloids.in | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) mode change 100644 => 100755 scripts/python_trace_amyloids.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cf31ec12..e785238c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -559,7 +559,7 @@ list(APPEND RELION_PYTHON_WRAPPERS python_tomo_get_particle_poses python_tomo_denoise filament_selection - trace_amyloids + python_trace_amyloids schemegui ) diff --git a/scripts/python_fetch_weights.in b/scripts/python_fetch_weights.in index 05190174d..2ab67ce4d 100755 --- a/scripts/python_fetch_weights.in +++ b/scripts/python_fetch_weights.in @@ -49,7 +49,7 @@ fi # Call amypicker weight download ##################################################### echo "Attempting to download weights for amyloid filament picker..." -target_path="$script_dir/relion_python_amyloid_tracer" +target_path="$script_dir/relion_python_trace_amyloids" if [ -x "$target_path" ]; then # Execute the target binary diff --git a/scripts/python_trace_amyloids.in b/scripts/python_trace_amyloids.in old mode 100644 new mode 100755 index c0b378b57..cb55e2110 --- a/scripts/python_trace_amyloids.in +++ b/scripts/python_trace_amyloids.in @@ -541,7 +541,7 @@ def install_model( "d83777816d899d64aef593c97888e88f56d49e2b652cdc460de554f59d6f94ed" ], "amytracer-v2.0": [ - "https://zenodo.org/records/17949642/files/amytracer-v1.0.ckpt.gz", + "https://zenodo.org/records/17949642/files/amytracer-v2.0.ckpt.gz", "57f0cd566ca1779641691f7daf4a6147ccdb4f509f01c7472861e32bb7dbe14c" ], "carbonpicker-v1.0": [ From 2fb54a4915fa8a1a01f315558cbe1a8b58f1b622 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 16 Dec 2025 14:55:59 +0000 Subject: [PATCH 156/189] import specific libraries later, so can re-use script for fetching weights too --- scripts/python_trace_amyloids.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/python_trace_amyloids.in b/scripts/python_trace_amyloids.in index cb55e2110..c0b378b57 100755 --- a/scripts/python_trace_amyloids.in +++ b/scripts/python_trace_amyloids.in @@ -541,7 +541,7 @@ def install_model( "d83777816d899d64aef593c97888e88f56d49e2b652cdc460de554f59d6f94ed" ], "amytracer-v2.0": [ - "https://zenodo.org/records/17949642/files/amytracer-v2.0.ckpt.gz", + "https://zenodo.org/records/17949642/files/amytracer-v1.0.ckpt.gz", "57f0cd566ca1779641691f7daf4a6147ccdb4f509f01c7472861e32bb7dbe14c" ], "carbonpicker-v1.0": [ From 2b65918ba9850f9556e985f6a60ddecb9c8da8a3 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 16 Dec 2025 14:59:12 +0000 Subject: [PATCH 157/189] import specific libraries later, so can re-use script for fetching weights too --- scripts/python_trace_amyloids.in | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/scripts/python_trace_amyloids.in b/scripts/python_trace_amyloids.in index c0b378b57..1e51ce5c7 100755 --- a/scripts/python_trace_amyloids.in +++ b/scripts/python_trace_amyloids.in @@ -7,23 +7,10 @@ from pathlib import Path import numpy as np import pandas as pd -import starfile import torch import torch.nn as nn import torch.nn.functional as F -import mrcfile -import matplotlib.pyplot as plt - -from skimage.morphology import remove_small_objects, skeletonize -from skimage.measure import regionprops, label as label_connected_component -from skan import Skeleton, summarize -from scipy.spatial.distance import cdist -import cv2 - -from tqdm import tqdm -import time - ############################################################################### # U-Net Model ############################################################################### @@ -541,7 +528,7 @@ def install_model( "d83777816d899d64aef593c97888e88f56d49e2b652cdc460de554f59d6f94ed" ], "amytracer-v2.0": [ - "https://zenodo.org/records/17949642/files/amytracer-v1.0.ckpt.gz", + "https://zenodo.org/records/17949642/files/amytracer-v2.0.ckpt.gz", "57f0cd566ca1779641691f7daf4a6147ccdb4f509f01c7472861e32bb7dbe14c" ], "carbonpicker-v1.0": [ @@ -628,7 +615,20 @@ def main(): my_model_path = install_model(args.model_path, verbose=True) my_carbon_model_path = install_model(args.carbon_model_path, verbose=True) sys.exit(0) - + + import starfile + import mrcfile + import matplotlib.pyplot as plt + + from skimage.morphology import remove_small_objects, skeletonize + from skimage.measure import regionprops, label as label_connected_component + from skan import Skeleton, summarize + from scipy.spatial.distance import cdist + import cv2 + + from tqdm import tqdm + import time + # Load checkpoint file my_model_path = install_model(args.model_path) model = UNet.load_from_checkpoint(my_model_path, n_channels=3, n_classes=1) From e8e0e4c294afdf8773fa3b340cbd181de2d6c5cc Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 16 Dec 2025 16:41:32 +0000 Subject: [PATCH 158/189] set version to 5.1.0 --- src/macros.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/macros.h b/src/macros.h index c2102debd..6afeeb5ae 100644 --- a/src/macros.h +++ b/src/macros.h @@ -45,7 +45,7 @@ #ifndef MACROS_H #define MACROS_H -#define RELION_SHORT_VERSION "devel" +#define RELION_SHORT_VERSION "5.1.0" extern const char *g_RELION_VERSION; #include From e93d2578d190222721f68029ff0a2e82ec61a1f8 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 16 Dec 2025 16:53:47 +0000 Subject: [PATCH 159/189] hmmm, getting trouble with local definitions of imports --- scripts/python_trace_amyloids.in | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/python_trace_amyloids.in b/scripts/python_trace_amyloids.in index 1e51ce5c7..c9f18813e 100755 --- a/scripts/python_trace_amyloids.in +++ b/scripts/python_trace_amyloids.in @@ -1,4 +1,4 @@ -#!@PYTHON_EXE_PATH@ +#!/public/EM/anaconda3/envs/relion-5.0/bin/python import argparse import math @@ -227,6 +227,7 @@ class UNetCarbon(nn.Module): # MRC + instance segmentation ############################################################################### def load_mrc(mrc_fn: str, multiply_global_origin: bool = True): + import mrcfile with mrcfile.open(mrc_fn, "r") as mrc_file_handle: voxel_size = float(mrc_file_handle.voxel_size.x) if voxel_size <= 0: @@ -499,6 +500,7 @@ def prune_skeleton(skeleton, psi_image, min_length=100, min_distance=25, step=50 return labeled_skeleton, np.array(sampled_coords, dtype=int) def resize_and_norm(image, mysize, angpix, do_norm=True): + import cv2 ys, xs = image.shape if (xs > ys): scale = ys/mysize @@ -580,7 +582,6 @@ def install_model( ############################################################################### def main(): - #start_time = time.time() parser = argparse.ArgumentParser("Skeleton-based filament picking with instance merging, branch pruning, and ID labeling") parser.add_argument("-i", "--input", help="Input star file", default="None") parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint", default="amytracer-v2.0") @@ -617,17 +618,13 @@ def main(): sys.exit(0) import starfile - import mrcfile - import matplotlib.pyplot as plt from skimage.morphology import remove_small_objects, skeletonize from skimage.measure import regionprops, label as label_connected_component from skan import Skeleton, summarize from scipy.spatial.distance import cdist - import cv2 from tqdm import tqdm - import time # Load checkpoint file my_model_path = install_model(args.model_path) @@ -689,6 +686,7 @@ def main(): carbon_prob = carbon_prob.to(dtype=torch.float32, device="cpu").numpy() xsf, ysf= fom_image.shape if carbon_prob.shape != fom_image.shape: + import cv2 carbon_prob = cv2.resize(carbon_prob.astype(np.float32), (ysf, xsf), interpolation=cv2.INTER_AREA) carbon_mask = (carbon_prob>args.carbon_threshold) @@ -768,6 +766,8 @@ def main(): # --------------------------------------------------- if args.plot: + import matplotlib.pyplot as plt + fig, ax = plt.subplots(2, 4, figsize=(20,15)) ax = ax.ravel() From df8c560d67d8911119a6e191cb6a272957b83d81 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 16 Dec 2025 16:56:45 +0000 Subject: [PATCH 160/189] repaired indentation --- scripts/python_trace_amyloids.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/python_trace_amyloids.in b/scripts/python_trace_amyloids.in index c9f18813e..3d96c8af2 100755 --- a/scripts/python_trace_amyloids.in +++ b/scripts/python_trace_amyloids.in @@ -566,8 +566,8 @@ def install_model( with open(completed_check_path, "w") as f: f.write("Successfully downloaded model") - if verbose: - print(f"Amyloid picker model ({name}) successfully installed in {dest_dir}") + if verbose: + print(f"Amyloid picker model ({name}) successfully installed in {dest_dir}") else: model_path = name From d168c02814f9249c1afd1a122595e7765f1a2e17 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 16 Dec 2025 17:02:16 +0000 Subject: [PATCH 161/189] go back to imports at the beginning --- scripts/python_trace_amyloids.in | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/scripts/python_trace_amyloids.in b/scripts/python_trace_amyloids.in index 3d96c8af2..4c4133773 100755 --- a/scripts/python_trace_amyloids.in +++ b/scripts/python_trace_amyloids.in @@ -7,10 +7,23 @@ from pathlib import Path import numpy as np import pandas as pd +import starfile import torch import torch.nn as nn import torch.nn.functional as F +import mrcfile +import matplotlib.pyplot as plt + +from skimage.morphology import remove_small_objects, skeletonize +from skimage.measure import regionprops, label as label_connected_component +from skan import Skeleton, summarize +from scipy.spatial.distance import cdist +import cv2 + +from tqdm import tqdm +import time + ############################################################################### # U-Net Model ############################################################################### @@ -227,7 +240,6 @@ class UNetCarbon(nn.Module): # MRC + instance segmentation ############################################################################### def load_mrc(mrc_fn: str, multiply_global_origin: bool = True): - import mrcfile with mrcfile.open(mrc_fn, "r") as mrc_file_handle: voxel_size = float(mrc_file_handle.voxel_size.x) if voxel_size <= 0: @@ -500,7 +512,6 @@ def prune_skeleton(skeleton, psi_image, min_length=100, min_distance=25, step=50 return labeled_skeleton, np.array(sampled_coords, dtype=int) def resize_and_norm(image, mysize, angpix, do_norm=True): - import cv2 ys, xs = image.shape if (xs > ys): scale = ys/mysize @@ -616,16 +627,7 @@ def main(): my_model_path = install_model(args.model_path, verbose=True) my_carbon_model_path = install_model(args.carbon_model_path, verbose=True) sys.exit(0) - - import starfile - - from skimage.morphology import remove_small_objects, skeletonize - from skimage.measure import regionprops, label as label_connected_component - from skan import Skeleton, summarize - from scipy.spatial.distance import cdist - - from tqdm import tqdm - + # Load checkpoint file my_model_path = install_model(args.model_path) model = UNet.load_from_checkpoint(my_model_path, n_channels=3, n_classes=1) @@ -686,7 +688,6 @@ def main(): carbon_prob = carbon_prob.to(dtype=torch.float32, device="cpu").numpy() xsf, ysf= fom_image.shape if carbon_prob.shape != fom_image.shape: - import cv2 carbon_prob = cv2.resize(carbon_prob.astype(np.float32), (ysf, xsf), interpolation=cv2.INTER_AREA) carbon_mask = (carbon_prob>args.carbon_threshold) @@ -766,8 +767,6 @@ def main(): # --------------------------------------------------- if args.plot: - import matplotlib.pyplot as plt - fig, ax = plt.subplots(2, 4, figsize=(20,15)) ax = ax.ravel() From 2bf85d1fac3d5c5c4f817fada316311bda569dd7 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 16 Dec 2025 17:32:37 +0000 Subject: [PATCH 162/189] only do carbon when asked for --- scripts/python_trace_amyloids.in | 7 ++++--- src/amyloid_finder.cpp | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/python_trace_amyloids.in b/scripts/python_trace_amyloids.in index 4c4133773..0bf2f2a85 100755 --- a/scripts/python_trace_amyloids.in +++ b/scripts/python_trace_amyloids.in @@ -595,19 +595,20 @@ def main(): parser = argparse.ArgumentParser("Skeleton-based filament picking with instance merging, branch pruning, and ID labeling") parser.add_argument("-i", "--input", help="Input star file", default="None") - parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint", default="amytracer-v2.0") - parser.add_argument("-c", "--carbon_model_path", help="Path to PyTorch model checkpoint for carbon detection", default="carbonpicker-v1.0") parser.add_argument("-o", "--output", help="Output star file with coordinates", default="out") parser.add_argument("-r", "--radius", type=int, help="Minimum distance between ends of filaments (in A)", default=25) parser.add_argument("-l", "--minimum_length", type=int, default=250, help="Remove final segments smaller than this size in Angstroms (default=250)") parser.add_argument("-p", "--psi_jump_threshold", default=45, type=float, help="Maximum difference in degrees of psi angles for consecutive points in skeletons (default=45)") parser.add_argument("-t", "--threshold", default=0.5, type=float, help="Foreground threshold (default=0.5)") + parser.add_argument("-c", "--carbon", default=False, type=bool, help="Ignore filaments on carbon") parser.add_argument("-ct", "--carbon_threshold", default=0.9, type=float, help="Carbon detection threshold (default=0.9)") parser.add_argument("-s", "--scale", type=float, default=1.0, help="Multiply output coordinates by this factor (default=1.0)") parser.add_argument("-d", "--device", default="cuda:0", help="Which GPU device to use") parser.add_argument("-j", "--threads", type=int, default=1) + parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint", default="amytracer-v2.0") + parser.add_argument("-cm", "--carbon_model_path", help="Path to PyTorch model checkpoint for carbon detection", default="carbonpicker-v1.0") parser.add_argument("-a", "--abort", help="Abort if this file exists") parser.add_argument("-v", "--verb", type=int, help="Verbosity") parser.add_argument("--sample_step", type=int, default=100, @@ -632,7 +633,7 @@ def main(): my_model_path = install_model(args.model_path) model = UNet.load_from_checkpoint(my_model_path, n_channels=3, n_classes=1) model.eval().to(device) - if args.carbon_model_path == "": + if not args.carbon: if args.verb > 0: print(" + Not doing carbon detection") do_carbon = False diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 399e1f1cc..1f1b238ba 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -819,7 +819,8 @@ void AmyloidFinder::runTracingBatch(long int my_first, long int my_last, int my_ command += " -v " + integerToString(verb); if (do_carbon) { - command += " -c " + fn_carbon_model_path; + command += " -c "; + command += " -cm " + fn_carbon_model_path; command += " --carbon_threshold " + floatToString(carbon_threshold); } if (do_plot) From 81aa7f6c1dd921d7db88c17f50b19c9c565f6066 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 16 Dec 2025 17:58:01 +0000 Subject: [PATCH 163/189] write PNGs instead of plotting to screen for trace amyloids --- scripts/python_trace_amyloids.in | 11 ++++++++--- src/pipeline_jobs.cpp | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/python_trace_amyloids.in b/scripts/python_trace_amyloids.in index 0bf2f2a85..9c976e159 100755 --- a/scripts/python_trace_amyloids.in +++ b/scripts/python_trace_amyloids.in @@ -13,6 +13,8 @@ import torch.nn as nn import torch.nn.functional as F import mrcfile +import matplotlib +matplotlib.use("Agg") # non-GUI backend, no Qt/X11 needed import matplotlib.pyplot as plt from skimage.morphology import remove_small_objects, skeletonize @@ -601,7 +603,7 @@ def main(): help="Remove final segments smaller than this size in Angstroms (default=250)") parser.add_argument("-p", "--psi_jump_threshold", default=45, type=float, help="Maximum difference in degrees of psi angles for consecutive points in skeletons (default=45)") parser.add_argument("-t", "--threshold", default=0.5, type=float, help="Foreground threshold (default=0.5)") - parser.add_argument("-c", "--carbon", default=False, type=bool, help="Ignore filaments on carbon") + parser.add_argument("-c", "--carbon", action='store_true', help="Ignore filaments on carbon") parser.add_argument("-ct", "--carbon_threshold", default=0.9, type=float, help="Carbon detection threshold (default=0.9)") parser.add_argument("-s", "--scale", type=float, default=1.0, help="Multiply output coordinates by this factor (default=1.0)") @@ -645,6 +647,8 @@ def main(): carbon_model = UNetCarbon.load_from_checkpoint(my_carbon_model_path, n_channels=1, n_classes=1) carbon_model.eval().to(device) + if args.plot: + print(" + Writing PNG file with intermediate results for every micrograph") input_star = starfile.read(args.input) @torch.compile @@ -796,7 +800,7 @@ def main(): ax[5].set_title("carbon detection") ax[5].axis('off') - if skeleton_img.any(): + if skeleton_img.any() and np.count_nonzero(skeleton_img) > 10: ax[6].imshow(color_nonrandom(pruned_skeleton)) ax[6].set_title("Pruned Skeleton") ax[6].axis('off') @@ -810,7 +814,8 @@ def main(): ax[7].axis('off') plt.tight_layout() - plt.show() + out_png = os.path.splitext(outfile)[0] + ".png" + plt.savefig(out_png, dpi=200, bbox_inches="tight") plt.close() diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index f1dbf3f79..a462a008c 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2059,7 +2059,7 @@ Helical tubes with shorter lengths will not be picked. Note that a long helical joboptions["do_amyloid_tracing"] = JobOption("Trace amyloids in FOM images (on GPU)? ", false, "Trace amyloids (as lines) in the FOM images that were calculated in a first (CPU-based) job? Note this part of the code runs on the GPU. This job takes the micrographs_autopick.star output file from the first job as input (or a subset selection thereof, e.g. with rlnMicrographScoreSkewness>1)."); joboptions["amyloid_gpu_ids"] = JobOption("Which GPUs to use for tracing:", std::string(""), "This argument is not necessary. If left empty, the job itself will try to allocate available GPU resources. You can override the default allocation by providing a list of which GPUs to use. MPI-processes are separated by ':'. For example: 0:1:2:3. Note, only the amyloid tracing part of the job is run on the GPU; FOM calculation has only been implemented for the CPU."); joboptions["amyloid_threshold"] = JobOption("Amyloid pick threshold (sigma): ", 0.25, 0.05, 1, 0.05, "How many sigma does the peaks need to be above the mean for the filament tracing to include a coordinate?"); - joboptions["do_amyloid_plot"] = JobOption("Plot results per micrograph?", false, "Set this option to Yes to visualise intermediate results for amyloid tracing, which may help with setting values for filament length, width and picking threshold. Don't use in parallel or in submission to the queue."); + joboptions["do_amyloid_plot"] = JobOption("Plot results per micrograph?", false, "Set this option to Yes to visualise intermediate results for amyloid tracing, which may help with setting values for filament length, width and picking threshold. This writes a PNG file for each micrograph, next to its output star file, so you may want to limit the number of input micrographs."); joboptions["amyloid_width"] = JobOption("Minimum distance between filaments (A): ", 100, 50, 1000, 10, "Minimum distance between ends of traced filaments (in Angstroms). "); joboptions["amyloid_length"] = JobOption("Minimum filament length (A): ", 500, 100, 1000, 10, "Length (in Angstroms) used for FOM calculation, and minimum length of filaments for tracing."); joboptions["do_amyloid_carbon"] = JobOption("Ignore filaments on carbon?", false, "Set to Yes if you want to use automated carbon detection to avoid picking filaments on carbon."); From 144b7d10d79768bf34b7a6c5de4e68a0a3b712df Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 17 Dec 2025 10:03:24 +0000 Subject: [PATCH 164/189] changed buttons to teal colour for relion-5.1 --- scripts/schemegui.in | 8 ++++---- src/displayer.h | 2 +- src/gui_entries.h | 24 +++++++++++------------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/scripts/schemegui.in b/scripts/schemegui.in index 17bd31330..06e204332 100755 --- a/scripts/schemegui.in +++ b/scripts/schemegui.in @@ -33,10 +33,10 @@ IS_RUNNING = False # Colour definitions # Yellowish background for entries entry_bg = '#ffffe6' -# reddish colour for Browse buttons -button_bg = '#c8506e' -# Darker red for run buttons -runbutton_bg = '#a01e3c' +# relion-5.1: tealish colour for Browse buttons +button_bg = '#66b2b2' +# Darker teal for run buttons +runbutton_bg = '#008080' def load_star(filename): from collections import OrderedDict diff --git a/src/displayer.h b/src/displayer.h index 335550914..cafabef67 100644 --- a/src/displayer.h +++ b/src/displayer.h @@ -49,7 +49,7 @@ #define GUI_BACKGROUND_COLOR (fl_rgb_color(240,240,240)) #define GUI_INPUT_COLOR (fl_rgb_color(255,255,230)) -#define GUI_RUNBUTTON_COLOR (fl_rgb_color(100, 60, 255)) +#define GUI_RUNBUTTON_COLOR (fl_rgb_color(100, 178, 178)) diff --git a/src/gui_entries.h b/src/gui_entries.h index e50a35989..892726b02 100644 --- a/src/gui_entries.h +++ b/src/gui_entries.h @@ -126,20 +126,18 @@ extern bool create_scheduler_gui; //version-3.1 #define GUI_BUTTON_COLOR (fl_rgb_color(238,130,238)) //version-3.1 #define GUI_BUTTON_DARK_COLOR (fl_rgb_color(200, 110, 200)) //version-3.1 #define GUI_RUNBUTTON_COLOR (fl_rgb_color(170, 0, 170)) -// Dont forget GUI runbutton colour in src/displayer.h! -//version-4.0 -//#define GUI_BUTTON_COLOR (fl_rgb_color(200,80,110)) -//#define GUI_BUTTON_DARK_COLOR (fl_rgb_color(170, 40, 70)) -//#define GUI_RUNBUTTON_COLOR (fl_rgb_color(160, 30, 60)) -#define GUI_BACKGROUND_COLOR (fl_rgb_color(230,230,240)) // slightly blue because of blue buttons in 2.0! -#define GUI_BACKGROUND_COLOR2 (fl_rgb_color(180,180,190)) // slightly blue because of blue buttons in 2.0! -//version-5.0 -#define GUI_BUTTON_COLOR (fl_rgb_color(120, 120, 255)) -#define GUI_BUTTON_DARK_COLOR (fl_rgb_color(80, 80, 255)) +//version-4.0 #define GUI_BUTTON_COLOR (fl_rgb_color(200,80,110)) +//version-4.0 #define GUI_BUTTON_DARK_COLOR (fl_rgb_color(170, 40, 70)) +//version-4.0 #define GUI_RUNBUTTON_COLOR (fl_rgb_color(160, 30, 60)) +//version-5.0 #define GUI_BUTTON_COLOR (fl_rgb_color(120, 120, 255)) +//version-5.0 #define GUI_BUTTON_DARK_COLOR (fl_rgb_color(80, 80, 255)) +//version-5.1 +#define GUI_BUTTON_COLOR (fl_rgb_color(100, 178, 178)) +#define GUI_BUTTON_DARK_COLOR (fl_rgb_color(50, 128, 128)) // Dont forget to change runbutton in src/displayer.h too!! -#define GUI_RUNBUTTON_COLOR (fl_rgb_color(100, 60, 255)) -#define GUI_BACKGROUND_COLOR (fl_rgb_color(230,230,240)) // slightly blue because of blue buttons in 2.0! -#define GUI_BACKGROUND_COLOR2 (fl_rgb_color(180,180,190)) // slightly blue because of blue buttons in 2.0! +#define GUI_RUNBUTTON_COLOR (fl_rgb_color(50, 128, 128)) +#define GUI_BACKGROUND_COLOR (fl_rgb_color(235,235,230)) // slightly yellow in relion-5.1 +#define GUI_BACKGROUND_COLOR2 (fl_rgb_color(185,185,180)) // slightly yellow in relion-5.1 //possible?#define GUI_BUTTON_COLOR (fl_rgb_color(50, 200, 255)) //devel-version //possible #define GUI_RUNBUTTON_COLOR (fl_rgb_color(205,0,155)) From 3e1edb02a2dd4677c1edd409840943330f3a1c65 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 2 Jan 2026 12:08:02 +0000 Subject: [PATCH 165/189] make sure trace_amyloids.py uses the correct torch home and relion executable --- CMakeLists.txt | 1 + scripts/python_trace_amyloids.in | 868 ++----------------------------- scripts/python_trace_amyloids.py | 818 +++++++++++++++++++++++++++++ 3 files changed, 865 insertions(+), 822 deletions(-) create mode 100755 scripts/python_trace_amyloids.py diff --git a/CMakeLists.txt b/CMakeLists.txt index e785238c0..3b9fc07ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -615,6 +615,7 @@ list(APPEND RELION_SCRIPT_FILES star_datablock_ctfdat qsub.csh it.py + trace_amyloids.py ) add_custom_target(copy_scripts ALL) diff --git a/scripts/python_trace_amyloids.in b/scripts/python_trace_amyloids.in index 9c976e159..5c37e5edc 100755 --- a/scripts/python_trace_amyloids.in +++ b/scripts/python_trace_amyloids.in @@ -1,823 +1,47 @@ -#!/public/EM/anaconda3/envs/relion-5.0/bin/python +#!/usr/bin/env bash + +print_error() { +echo " +---------------------------------- PYTHON ERROR --------------------------------- + Has RELION been provided a Python interpreter with the correct environment? + The interpreter can be passed to RELION either during Cmake configuration by + using the Cmake flag -DPYTHON_EXE_PATH=. + NOTE: For some modules TORCH_HOME needs to be set to find pretrained models +--------------------------------------------------------------------------------- + + Using python executable: $1 +" +} + +# Set the Python executable path +python_executable="@PYTHON_EXE_PATH@" +torch_home="@TORCH_HOME_PATH@" +relion_home="@Relion_BINARY_DIR@" + +# Check if the python executable exists +if [ ! -x "$python_executable" ]; then + # Check for default python executable + python_executable=$(command -v python) + + if [ -z "$python_executable" ]; then + print_error "$python_executable" + exit 1 + fi +fi + +# Run the Python script with forwarded arguments +if [ -n "$torch_home" ]; then + TORCH_HOME="$torch_home" "$python_executable" "$relion_home"/bin/relion_trace_amyloids.py "$@" +else + "$python_executable" "$relion_home"/bin/relion_trace_amyloids.py "$@" +fi + +# Check the return status of the python command +if [ $? -ne 0 ]; then + print_error "$python_executable" + exit 2 +fi + +# Exit +exit 0 -import argparse -import math -import sys, os -from pathlib import Path - -import numpy as np -import pandas as pd -import starfile -import torch -import torch.nn as nn -import torch.nn.functional as F - -import mrcfile -import matplotlib -matplotlib.use("Agg") # non-GUI backend, no Qt/X11 needed -import matplotlib.pyplot as plt - -from skimage.morphology import remove_small_objects, skeletonize -from skimage.measure import regionprops, label as label_connected_component -from skan import Skeleton, summarize -from scipy.spatial.distance import cdist -import cv2 - -from tqdm import tqdm -import time - -############################################################################### -# U-Net Model -############################################################################### -class DownCarbon(nn.Module): - """Downscaling with maxpool then double conv""" - - def __init__(self, in_channels, out_channels): - super().__init__() - self.maxpool_conv = nn.Sequential( - nn.MaxPool2d(2), - DoubleConv(in_channels, out_channels) - ) - - def forward(self, x): - return self.maxpool_conv(x) - - -class UpCarbon(nn.Module): - """Upscaling then double conv""" - - def __init__(self, in_channels, out_channels): - super().__init__() - - # 1) spatial upsample - # 2) reduce channels - self.up = nn.Sequential( - nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True), - nn.Conv2d(in_channels, in_channels // 2, kernel_size=1), - ) - # after cat: (in_channels//2 + in_channels//2) == in_channels - self.conv = DoubleConv(in_channels, out_channels) - - def forward(self, x1, x2): - x1 = self.up(x1) - # input is CHW - diffY = x2.size()[2] - x1.size()[2] - diffX = x2.size()[3] - x1.size()[3] - - x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2, - diffY // 2, diffY - diffY // 2]) - # if you have padding issues, see - # https://github.com/HaiyongJiang/U-Net-Pytorch-Unstructured-Buggy/commit/0e854509c2cea854e247a9c615f175f76fbb2e3a - # https://github.com/xiaopeng-liao/Pytorch-UNet/commit/8ebac70e633bac59fc22bb5195e513d5832fb3bd - x = torch.cat([x2, x1], dim=1) - return self.conv(x) - - -class DoubleConv(nn.Module): - """(convolution => [InstanceNorm] => GELU) * 2""" - def __init__(self, in_channels, out_channels, mid_channels=None): - super().__init__() - if not mid_channels: - mid_channels = out_channels - self.double_conv = nn.Sequential( - nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False), - nn.InstanceNorm2d(mid_channels), - nn.GELU(), - nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False), - nn.InstanceNorm2d(out_channels), - nn.GELU() - ) - - def forward(self, x): - return self.double_conv(x) - - -class Down(nn.Module): - def __init__(self, in_channels, out_channels): - super().__init__() - self.maxpool_conv = nn.Sequential( - nn.MaxPool2d(2), - DoubleConv(in_channels, out_channels) - ) - - def forward(self, x): - return self.maxpool_conv(x) - - -class Up(nn.Module): - def __init__(self, in_channels, out_channels, bilinear=True): - super().__init__() - if bilinear: - self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True) - self.conv = DoubleConv(in_channels, out_channels, in_channels // 2) - else: - self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2) - self.conv = DoubleConv(in_channels, out_channels) - - def forward(self, x1, x2): - x1 = self.up(x1) - diffY = x2.size()[2] - x1.size()[2] - diffX = x2.size()[3] - x1.size()[3] - - x1 = F.pad( - x1, - [diffX // 2, diffX - diffX // 2, diffY // 2, diffY - diffY // 2] - ) - x = torch.cat([x2, x1], dim=1) - return self.conv(x) - - -class OutConv(nn.Module): - def __init__(self, in_channels, out_channels): - super().__init__() - self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1) - - def forward(self, x): - return self.conv(x) - - -class UNet(nn.Module): - def __init__(self, n_channels, n_classes, embedding_dim=32, bilinear=False): - super().__init__() - self.n_channels = n_channels - self.n_classes = n_classes - self.bilinear = bilinear - - self.base_channel_sz = 128 - - self.inc = DoubleConv(n_channels, self.base_channel_sz) - self.down1 = Down(self.base_channel_sz, 2 * self.base_channel_sz) - self.down2 = Down(2 * self.base_channel_sz, 4 * self.base_channel_sz) - self.down3 = Down(4 * self.base_channel_sz, 8 * self.base_channel_sz) - factor = 2 if bilinear else 1 - self.down4 = Down(8 * self.base_channel_sz, 16 * self.base_channel_sz // factor) - - self.up1 = Up(16 * self.base_channel_sz, 8 * self.base_channel_sz // factor, bilinear) - self.up2 = Up(8 * self.base_channel_sz, 4 * self.base_channel_sz // factor, bilinear) - self.up3 = Up(4 * self.base_channel_sz, 2 * self.base_channel_sz // factor, bilinear) - self.up4 = Up(2 * self.base_channel_sz, self.base_channel_sz, bilinear) - - self.outc = OutConv(self.base_channel_sz, n_classes) - self.embedding_head = nn.Sequential( - nn.Conv2d(self.base_channel_sz, embedding_dim, kernel_size=1), - nn.Sigmoid() - ) - - def forward(self, x): - x1 = self.inc(x) - x2 = self.down1(x1) - x3 = self.down2(x2) - x4 = self.down3(x3) - x5 = self.down4(x4) - - x = self.up1(x5, x4) - x = self.up2(x, x3) - x = self.up3(x, x2) - x = self.up4(x, x1) - - seg_logits = self.outc(x) - embedding = self.embedding_head(x) - return seg_logits, embedding - - @staticmethod - def load_from_checkpoint(model_path: str, n_channels: int, n_classes: int): - model = UNet(n_channels=n_channels, n_classes=n_classes) - lightning_dict = torch.load(model_path, map_location="cpu") - state_dict = lightning_dict['state_dict'] - try: - model.load_state_dict(state_dict, strict=False) - except: - model.load_state_dict(state_dict) - return model - - -class UNetCarbon(nn.Module): - def __init__(self, n_channels, n_classes, dropout=0.25): - super(UNetCarbon, self).__init__() - self.n_channels = n_channels - self.n_classes = n_classes - self.base_channel_sz = 64 - - self.inc = (DoubleConv(n_channels, self.base_channel_sz)) - self.down1 = (DownCarbon(self.base_channel_sz, 2 * self.base_channel_sz)) - self.down2 = (DownCarbon(2 * self.base_channel_sz, 4 * self.base_channel_sz)) - self.down3 = (DownCarbon(4 * self.base_channel_sz, 8 * self.base_channel_sz)) - self.down4 = (DownCarbon(8 * self.base_channel_sz, 16 * self.base_channel_sz )) - self.up1 = (UpCarbon(16 * self.base_channel_sz, 8 * self.base_channel_sz)) - self.up2 = (UpCarbon(8 * self.base_channel_sz, 4 * self.base_channel_sz)) - self.up3 = (UpCarbon(4 * self.base_channel_sz, 2 * self.base_channel_sz)) - self.up4 = (UpCarbon(2 * self.base_channel_sz, self.base_channel_sz)) - self.outc = (OutConv(self.base_channel_sz, n_classes)) - - - def forward(self, x): - x1 = self.inc(x) - x2 = self.down1(x1) - x3 = self.down2(x2) - x4 = self.down3(x3) - x5 = self.down4(x4) - - x = self.up1(x5, x4) - x = self.up2(x, x3) - x = self.up3(x, x2) - x = self.up4(x, x1) - logits = self.outc(x) - - return logits - - @staticmethod - def load_from_checkpoint(model_path: str, n_channels: int, n_classes: int): - model = UNetCarbon(n_channels=n_channels, n_classes=n_classes) - lightning_dict = torch.load(model_path, map_location="cpu") - state_dict = lightning_dict['state_dict'] - try: - model.load_state_dict(state_dict, strict=False) - except: - model.load_state_dict(state_dict) - return model - - - -############################################################################### -# MRC + instance segmentation -############################################################################### -def load_mrc(mrc_fn: str, multiply_global_origin: bool = True): - with mrcfile.open(mrc_fn, "r") as mrc_file_handle: - voxel_size = float(mrc_file_handle.voxel_size.x) - if voxel_size <= 0: - raise RuntimeError(f"MRC file {mrc_fn} does not have a valid header.") - - c = mrc_file_handle.header["mapc"] - r = mrc_file_handle.header["mapr"] - s = mrc_file_handle.header["maps"] - - global_origin = mrc_file_handle.header["origin"] - global_origin = np.array([global_origin.x, global_origin.y, global_origin.z]) - global_origin[0] += mrc_file_handle.header["nxstart"] - global_origin[1] += mrc_file_handle.header["nystart"] - global_origin[2] += mrc_file_handle.header["nzstart"] - - if multiply_global_origin: - global_origin *= mrc_file_handle.voxel_size.x - - raw_data = mrc_file_handle.data - if c == 1 and r == 2 and s == 3: - grid = raw_data - elif c == 3 and r == 2 and s == 1: - grid = np.moveaxis(raw_data, [0, 1, 2], [2, 1, 0]) - elif c == 2 and r == 1 and s == 3: - grid = np.moveaxis(raw_data, [1, 2, 0], [2, 1, 0]) - else: - raise RuntimeError("MRC file axis arrangement not supported!") - return grid, voxel_size, global_origin - - -############################################################################### -# Sample coordinates from the final pruned label, including IDs -############################################################################### -def sample_coordinates_with_ids(label_image, step=50): - - labels = np.unique(label_image) - labels = labels[labels != 0] - labels.sort() # ensure ascending - results = [] - - filament_counter = 1 - for lbl in labels: - bin_mask = (label_image == lbl) - sk = Skeleton(bin_mask) - if sk.n_paths == 0: - continue - - # For each path in this label, sample coordinates - for p_idx in range(sk.n_paths): - coords = sk.path_coordinates(p_idx).astype(int) - if len(coords) == 0: - continue - for i in range(0, len(coords), step): - r, c = coords[i] - results.append([c, r, filament_counter]) - # ensure final coordinate is included - if (len(coords) - 1) % step != 0: - r, c = coords[-1] - results.append([c, r, filament_counter]) - - filament_counter += 1 - - return np.array(results, dtype=int) - - -############################################################################### -# Visualization Helpers -############################################################################### -import matplotlib.colors as mcolors - -def color_nonrandom(label_image): - H, W = label_image.shape - colorized = np.full((H, W, 3), 127, dtype=np.uint8) - - labels = np.unique(label_image) - labels = labels[labels != 0] - i = 0 - for lbl in labels: - if i%6==0: - r= 255 - g= 0 - b =0 - elif i%6==1: - r= 0 - g= 255 - b =0 - elif i%6==2: - r= 0 - g= 0 - b =255 - elif i%6==3: - r= 255 - g= 255 - b =0 - elif i%6==4: - r= 0 - g= 255 - b =255 - elif i%6==5: - r= 255 - g= 0 - b =255 - mask = (label_image == lbl) - colorized[mask] = (r, g, b) - i=i+1 - return colorized - -def color_foreground(grey_image, threshold): - grey_image_uint8 = (grey_image * 255).astype(np.uint8) - colorized = np.stack([grey_image_uint8] * 3, axis=-1) - mask = (grey_image > threshold) - colorized[mask] = (255, 0, 0) - return colorized - - -############################################################################### -# Sjors go at pruning the raw skeleton image -############################################################################### - -def trim_end(branch_coords, trim_length=10, trim_start=False): - """Trims the start or end of a branch by `trim_length` pixels.""" - if len(branch_coords) > trim_length: - return branch_coords[trim_length:] if trim_start else branch_coords[:-trim_length] - return np.array([]) # Remove short branches completely - -def angular_diff(a, b, period=180): - """Minimal circular difference between angles a and b on [0, period).""" - raw = abs(a - b) - return min(raw, period - raw) - -def segment_orientation(p0, p1): - """ - Compute the orientation (in degrees) and fold it into [0, 180). - p0, p1 are (row, col) integer tuples or arrays. - """ - # Note: row = y, col = x - dy = p1[0] - p0[0] - dx = p1[1] - p0[1] - # conventions of y-axis is reversed in RELION, so use -dy! - ang = np.degrees(np.arctan2(-dy, dx)) # gives [-180, 180) - - # fold into [0, 180): - if ang < 0: - ang += 180 - elif ang >= 180: - ang -= 180 - - return ang - -def split_branch_on_psi_mismatch(branch_coords, psi_image, threshold=20): - - segments = [] - current_seg = [tuple(branch_coords[0])] - prev_pt = branch_coords[0] - prev_ang = psi_image[prev_pt[0], prev_pt[1]] - - # step through each next point - for i in range(1, len(branch_coords)): - pt = branch_coords[i] - psi_ang = psi_image[pt[0], pt[1]] - - # split if jump in neighbour psi image values > threshold - do_split = False - if angular_diff(prev_ang, psi_ang) > threshold: - do_split = True - # compute seg_ang between i-3 and i - elif i >= 3: - p0 = branch_coords[i - 3] - seg_ang = segment_orientation(p0, pt) - # split if mismatch > threshold - if angular_diff(seg_ang, psi_ang) > threshold: - do_split = True - - if do_split: - segments.append(np.array(current_seg)) - current_seg = [] - - current_seg.append(tuple(pt)) - prev_ang = psi_ang - - # append last segment - if current_seg: - segments.append(np.array(current_seg)) - - return segments - -def prune_skeleton(skeleton, psi_image, min_length=100, min_distance=25, step=50, psi_threshold=45): - """Prune skeleton branches that too short.""" - # Most of the compute goes into the Skeleton call; rest of function is cheap - skel = Skeleton(skeleton) - df = summarize(skel, separator='-') - - trim_length = int(min_distance / 2) - labeled_skeleton = np.zeros_like(skeleton, dtype=np.int32) - accepted_branches = [] # Stores (branch coordinates, start, end) - sampled_coords = [] - - # Pass 0: split branches with jumps in psi-angle values - all_splits = [] - for idx, row in df.iterrows(): - coords = skel.path_coordinates(idx) - pieces = split_branch_on_psi_mismatch(coords, psi_image, threshold=psi_threshold) - all_splits.extend(pieces) - - # Pass 1: Collect branches and compute pairwise distances - branch_data = [] # Store branch info for pass 2 - endpoints = [] # Store (start, end) tuples - for branch_coords in all_splits: - - # Compute Euclidean length of the branch - length = np.sum(np.sqrt(np.sum(np.diff(branch_coords, axis=0) ** 2, axis=1))) - if length < min_length: - continue # Skip short branches - - start, end = tuple(branch_coords[0]), tuple(branch_coords[-1]) - branch_data.append((branch_coords, start, end)) - endpoints.append(start) - endpoints.append(end) - - if not branch_data: - return labeled_skeleton, np.array(sampled_coords, dtype=int) # No valid branches - - # Compute all pairwise distances between endpoints - endpoints = np.array(endpoints) # Convert to numpy array - dists = cdist(endpoints, endpoints) - - # Mark close endpoint pairs for trimming - close_pairs = np.where((dists < min_distance) ) # Exclude self-distances - trim_flags = {} # Dictionary to store which endpoints need trimming - - for i in range(len(close_pairs[0])): - idx1, idx2 = close_pairs[0][i], close_pairs[1][i] - branch_idx1, endpoint_type1 = divmod(idx1, 2) # Get branch index & endpoint type - branch_idx2, endpoint_type2 = divmod(idx2, 2) - - if branch_idx1 == branch_idx2: - continue # Ignore self-pairs - - # Mark both endpoints for trimming - trim_flags.setdefault((branch_idx1, endpoint_type1), True) - trim_flags.setdefault((branch_idx2, endpoint_type2), True) - - # **Pass 2: Trim branches based on identified close endpoints** - final_branches = [] - - for i, (branch_coords, start, end) in enumerate(branch_data): - # Trim start or end if flagged - if (i, 0) in trim_flags: - branch_coords = trim_end(branch_coords, trim_length, trim_start=True) - if (i, 1) in trim_flags: - branch_coords = trim_end(branch_coords, trim_length, trim_start=False) - - # Ensure branch still exists after trimming - if len(branch_coords) > 0: - final_branches.append(branch_coords) - - # Assign unique labels to final pruned branches - for path_id, branch in enumerate(final_branches, start=1): - for x, y in branch: - labeled_skeleton[int(x), int(y)] = path_id - - for i in range(0, len(branch), step): - r, c = branch[i] - sampled_coords.append([c, r, path_id]) - # ensure final coordinate is included - if (len(branch) - 1) % step != 0: - r, c = branch[-1] - sampled_coords.append([c, r, path_id]) - - return labeled_skeleton, np.array(sampled_coords, dtype=int) - -def resize_and_norm(image, mysize, angpix, do_norm=True): - ys, xs = image.shape - if (xs > ys): - scale = ys/mysize - else: - scale = xs/mysize - - xsp = int(xs/scale) - ysp = int(ys/scale) - resized_angpix = angpix *scale - resized = cv2.resize(image.astype(np.float32), (xsp, ysp), interpolation=cv2.INTER_AREA) - if do_norm: - max = resized.max() - min = resized.min() - if (max > min): - resized = (resized - min) / (max - min) - - return resized, resized_angpix - - -def install_model( - name: str, - verbose: bool = False, -): - model_list = { - "amytracer-v1.0": [ - "https://zenodo.org/records/17949642/files/amytracer-v1.0.ckpt.gz", - "d83777816d899d64aef593c97888e88f56d49e2b652cdc460de554f59d6f94ed" - ], - "amytracer-v2.0": [ - "https://zenodo.org/records/17949642/files/amytracer-v2.0.ckpt.gz", - "57f0cd566ca1779641691f7daf4a6147ccdb4f509f01c7472861e32bb7dbe14c" - ], - "carbonpicker-v1.0": [ - "https://zenodo.org/records/17949642/files/carbonpicker-v1.0.ckpt.gz", - "e25702cc84850339b46ac5b7d8e19ebdd55ad420c15bda8c9f9a584f59e7fd6b" - ] - } - - if name in model_list.keys(): - dest_dir = os.path.join(torch.hub.get_dir(), "checkpoints", "relion_trace_amyloids") - model_path = os.path.join(dest_dir, f"{name}.ckpt") - model_path_gz = model_path + ".gz" - completed_check_path = os.path.join(dest_dir, f"{name}_installed.txt") - - # Download file and install it if not already done - if not os.path.isfile(completed_check_path): - if verbose: - print(f"Installing amyloid picker model ({name})...") - os.makedirs(dest_dir, exist_ok=True) - - source_url = model_list[name][0] - - if verbose: - print(f"Downloading model weights from:\n {source_url}") - - import gzip, shutil - torch.hub.download_url_to_file(source_url, model_path_gz, hash_prefix=model_list[name][1]) - with gzip.open(model_path_gz, 'rb') as f_in: - with open(model_path, 'wb') as f_out: - shutil.copyfileobj(f_in, f_out) - os.remove(model_path_gz) - - with open(completed_check_path, "w") as f: - f.write("Successfully downloaded model") - - if verbose: - print(f"Amyloid picker model ({name}) successfully installed in {dest_dir}") - - else: - model_path = name - - return model_path - - - - -############################################################################### -# MAIN -############################################################################### -def main(): - - parser = argparse.ArgumentParser("Skeleton-based filament picking with instance merging, branch pruning, and ID labeling") - parser.add_argument("-i", "--input", help="Input star file", default="None") - parser.add_argument("-o", "--output", help="Output star file with coordinates", default="out") - parser.add_argument("-r", "--radius", type=int, help="Minimum distance between ends of filaments (in A)", default=25) - parser.add_argument("-l", "--minimum_length", type=int, default=250, - help="Remove final segments smaller than this size in Angstroms (default=250)") - parser.add_argument("-p", "--psi_jump_threshold", default=45, type=float, help="Maximum difference in degrees of psi angles for consecutive points in skeletons (default=45)") - parser.add_argument("-t", "--threshold", default=0.5, type=float, help="Foreground threshold (default=0.5)") - parser.add_argument("-c", "--carbon", action='store_true', help="Ignore filaments on carbon") - parser.add_argument("-ct", "--carbon_threshold", default=0.9, type=float, help="Carbon detection threshold (default=0.9)") - parser.add_argument("-s", "--scale", type=float, default=1.0, - help="Multiply output coordinates by this factor (default=1.0)") - parser.add_argument("-d", "--device", default="cuda:0", help="Which GPU device to use") - parser.add_argument("-j", "--threads", type=int, default=1) - parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint", default="amytracer-v2.0") - parser.add_argument("-cm", "--carbon_model_path", help="Path to PyTorch model checkpoint for carbon detection", default="carbonpicker-v1.0") - parser.add_argument("-a", "--abort", help="Abort if this file exists") - parser.add_argument("-v", "--verb", type=int, help="Verbosity") - parser.add_argument("--sample_step", type=int, default=100, - help="Sample a coordinate every 'sample_step' Angstroms along each path (default=50)") - parser.add_argument("--plot", action='store_true', - help="If set, display/save a collage figure of the pipeline results.") - args = parser.parse_args() - - # number of CPU threads allowed - #torch.set_num_threads(args.threads) - - # ------------------- Load model ---------------------- - device = torch.device(args.device) - - # This is for when we call this program without any arguments upon installation of the relion package (in scripts/python_fetch_weights.in) - if (args.input == "None"): - my_model_path = install_model(args.model_path, verbose=True) - my_carbon_model_path = install_model(args.carbon_model_path, verbose=True) - sys.exit(0) - - # Load checkpoint file - my_model_path = install_model(args.model_path) - model = UNet.load_from_checkpoint(my_model_path, n_channels=3, n_classes=1) - model.eval().to(device) - if not args.carbon: - if args.verb > 0: - print(" + Not doing carbon detection") - do_carbon = False - else: - do_carbon = True - if args.verb > 0: - print(" + Doing carbon detection") - my_carbon_model_path = install_model(args.carbon_model_path) - carbon_model = UNetCarbon.load_from_checkpoint(my_carbon_model_path, n_channels=1, n_classes=1) - carbon_model.eval().to(device) - - if args.plot: - print(" + Writing PNG file with intermediate results for every micrograph") - input_star = starfile.read(args.input) - - @torch.compile - def run_compiled_carbon_model(data): - seg_logits = carbon_model(data) - carbon_prob = torch.sigmoid(seg_logits.squeeze()) - return carbon_prob - - @torch.compile - def run_compiled_model(data): - seg_logits, embedding = model(data) - seg_prob = torch.sigmoid(seg_logits.squeeze()) - return seg_prob - - # ------------------ Run Inference ---------------- - iterator = tqdm(input_star.iterrows(), total=len(input_star), file=sys.stdout) if args.verb > 0 else input_star.iterrows() - half_dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16 - for i, row in iterator: - - if Path(args.abort).exists(): - print("Abort file exists. Aborting...") - sys.exit() - - # ------------------- Load MRCs ---------------------- - fom_image, pixel_size, origin = load_mrc(row["rlnMicrographFomImage"]) - psi_image, _, _ = load_mrc(row["rlnMicrographPsiImage"]) - - #--------------------- Carbon detection ----------------- - if do_carbon: - #in_tensor = np.stack(mic_image, axis=0) - #in_tensor = torch.from_numpy(in_tensor[None,None]).to(device=device, dtype=half_dtype) # shape (1,1,H,W) - mic_image, ori_pixel_size, _ = load_mrc(row["rlnMicrographName"]) - mic_image, resized_angpix = resize_and_norm(mic_image, 64, ori_pixel_size) - in_tensor = torch.from_numpy(mic_image[None,None]).to(device=device, dtype=half_dtype) # shape (1,1,H,W) - with torch.no_grad(): - if device.type == "cuda": - with torch.autocast(device_type=device.type, dtype=half_dtype): - carbon_prob = run_compiled_carbon_model(in_tensor) - else: - with torch.autocast(device_type="cpu", dtype=half_dtype): - carbon_prob = run_compiled_carbon_model(in_tensor) - carbon_prob = carbon_prob.to(dtype=torch.float32, device="cpu").numpy() - xsf, ysf= fom_image.shape - if carbon_prob.shape != fom_image.shape: - carbon_prob = cv2.resize(carbon_prob.astype(np.float32), (ysf, xsf), interpolation=cv2.INTER_AREA) - carbon_mask = (carbon_prob>args.carbon_threshold) - - #--------------------- Filament segmentation ----------------- - outfile = row["rlnMicrographCoordinates"] - directory = Path(outfile).parent - # Check if the directory exists, and create it if not - directory.mkdir(parents=True, exist_ok=True) - - H, W = fom_image.shape - - rad_psi = np.deg2rad(psi_image * 2) - in_tensor = np.stack((fom_image, np.sin(rad_psi), np.cos(rad_psi)), axis=0) # shape (3,H,W) - in_tensor = torch.from_numpy(in_tensor[None]).to(device=device, dtype=half_dtype) # shape (1,3,H,W) - - with torch.no_grad(): - if device.type == "cuda": - with torch.autocast(device_type=device.type, dtype=half_dtype): - seg_prob = run_compiled_model(in_tensor) - else: - with torch.autocast(device_type="cpu", dtype=half_dtype): - seg_prob = run_compiled_model(in_tensor) - foreground = seg_prob.to(dtype=torch.float32, device="cpu").numpy() - - # 2) Clean + threshold => remove small objects - bin_foreground = (foreground > args.threshold) - if do_carbon: - bin_foreground = bin_foreground & (~carbon_mask) - - min_size = 100 - clean_foreground = remove_small_objects(bin_foreground, min_size=min_size) - - # 3) Crop edges if radius is given - if args.radius is not None: - d = round(args.radius / pixel_size) - clean_foreground[:d, :] = False - clean_foreground[-d:, :] = False - clean_foreground[:, :d] = False - clean_foreground[:, -d:] = False - - # 4) Skeletonize - skeleton_img = skeletonize(clean_foreground) - - # if there is only a single non-zero pixel, Skeleton will give an error. Let's check for at least 10 non-zero pixels - if np.count_nonzero(skeleton_img) > 10: - - # 5) Prune skeleton based on Skan's branches: splits branches with jumps in psi-angles, remove any branch with length below args.final_min_size, and enforce minimal distance between end points - pruned_skeleton, sampled_points = prune_skeleton(skeleton_img, - psi_image, - min_length=args.minimum_length / pixel_size, - min_distance=2 * args.radius / pixel_size, - step=int(args.sample_step / pixel_size), - psi_threshold=args.psi_jump_threshold) - - # 6) Sample coordinates every 'sample_step' from pruned skeleton, **including filament ID**. - #sampled_points = sample_coordinates_with_ids(pruned_skeleton, step=int(args.sample_step / pixel_size)) - # sampled_points shape: (N, 3) => [x, y, filament_id] - if sampled_points.size > 0: - - # 7) Scale, round, write star - # scale only the x,y columns - sampled_points[:, 0:2] = np.round(sampled_points[:, 0:2] * args.scale).astype(int) - - df = pd.DataFrame({ - 'rlnCoordinateX': sampled_points[:, 0], - 'rlnCoordinateY': sampled_points[:, 1], - 'rlnParticleSelectionType': sampled_points[:, 2] - }) - starfile.write(df, outfile, overwrite=True) - else: - Path(outfile).touch() - else: - Path(outfile).touch() - - # --------------------------------------------------- - # If plotting is requested, create a collage figure - # --------------------------------------------------- - if args.plot: - - fig, ax = plt.subplots(2, 4, figsize=(20,15)) - ax = ax.ravel() - - ax[0].imshow(fom_image, cmap='gray') - ax[0].set_title("FOM Image") - ax[0].axis('off') - - ax[1].imshow(psi_image, cmap='gray') - ax[1].set_title("PSI Image") - ax[1].axis('off') - - ax[2].imshow(color_foreground(foreground, args.threshold)) - ax[2].set_title("Foreground") - ax[2].axis('off') - - ax[3].imshow(skeleton_img) - ax[3].set_title("Raw Skeleton") - ax[3].axis('off') - - if do_carbon: - ax[4].imshow(mic_image, cmap='gray') - ax[4].set_title("micrograph") - ax[4].axis('off') - - ax[5].imshow(color_foreground(carbon_prob, args.carbon_threshold)) - ax[5].set_title("carbon detection") - ax[5].axis('off') - - if skeleton_img.any() and np.count_nonzero(skeleton_img) > 10: - ax[6].imshow(color_nonrandom(pruned_skeleton)) - ax[6].set_title("Pruned Skeleton") - ax[6].axis('off') - - if sampled_points.size > 0: - ax[7].imshow(np.zeros_like(foreground), cmap='gray') # blank background - ax[7].scatter(sampled_points[:,0]/args.scale, sampled_points[:,1]/args.scale, s=2, c='red') - ax[7].set_xlim([0, foreground.shape[1]]) - ax[7].set_ylim([foreground.shape[0], 0]) - ax[7].set_title("Final Sampled Picks (ID shown in STAR)") - ax[7].axis('off') - - plt.tight_layout() - out_png = os.path.splitext(outfile)[0] + ".png" - plt.savefig(out_png, dpi=200, bbox_inches="tight") - plt.close() - - -if __name__ == "__main__": - main() diff --git a/scripts/python_trace_amyloids.py b/scripts/python_trace_amyloids.py new file mode 100755 index 000000000..c0b378b57 --- /dev/null +++ b/scripts/python_trace_amyloids.py @@ -0,0 +1,818 @@ +#!@PYTHON_EXE_PATH@ + +import argparse +import math +import sys, os +from pathlib import Path + +import numpy as np +import pandas as pd +import starfile +import torch +import torch.nn as nn +import torch.nn.functional as F + +import mrcfile +import matplotlib.pyplot as plt + +from skimage.morphology import remove_small_objects, skeletonize +from skimage.measure import regionprops, label as label_connected_component +from skan import Skeleton, summarize +from scipy.spatial.distance import cdist +import cv2 + +from tqdm import tqdm +import time + +############################################################################### +# U-Net Model +############################################################################### +class DownCarbon(nn.Module): + """Downscaling with maxpool then double conv""" + + def __init__(self, in_channels, out_channels): + super().__init__() + self.maxpool_conv = nn.Sequential( + nn.MaxPool2d(2), + DoubleConv(in_channels, out_channels) + ) + + def forward(self, x): + return self.maxpool_conv(x) + + +class UpCarbon(nn.Module): + """Upscaling then double conv""" + + def __init__(self, in_channels, out_channels): + super().__init__() + + # 1) spatial upsample + # 2) reduce channels + self.up = nn.Sequential( + nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True), + nn.Conv2d(in_channels, in_channels // 2, kernel_size=1), + ) + # after cat: (in_channels//2 + in_channels//2) == in_channels + self.conv = DoubleConv(in_channels, out_channels) + + def forward(self, x1, x2): + x1 = self.up(x1) + # input is CHW + diffY = x2.size()[2] - x1.size()[2] + diffX = x2.size()[3] - x1.size()[3] + + x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2, + diffY // 2, diffY - diffY // 2]) + # if you have padding issues, see + # https://github.com/HaiyongJiang/U-Net-Pytorch-Unstructured-Buggy/commit/0e854509c2cea854e247a9c615f175f76fbb2e3a + # https://github.com/xiaopeng-liao/Pytorch-UNet/commit/8ebac70e633bac59fc22bb5195e513d5832fb3bd + x = torch.cat([x2, x1], dim=1) + return self.conv(x) + + +class DoubleConv(nn.Module): + """(convolution => [InstanceNorm] => GELU) * 2""" + def __init__(self, in_channels, out_channels, mid_channels=None): + super().__init__() + if not mid_channels: + mid_channels = out_channels + self.double_conv = nn.Sequential( + nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False), + nn.InstanceNorm2d(mid_channels), + nn.GELU(), + nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False), + nn.InstanceNorm2d(out_channels), + nn.GELU() + ) + + def forward(self, x): + return self.double_conv(x) + + +class Down(nn.Module): + def __init__(self, in_channels, out_channels): + super().__init__() + self.maxpool_conv = nn.Sequential( + nn.MaxPool2d(2), + DoubleConv(in_channels, out_channels) + ) + + def forward(self, x): + return self.maxpool_conv(x) + + +class Up(nn.Module): + def __init__(self, in_channels, out_channels, bilinear=True): + super().__init__() + if bilinear: + self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True) + self.conv = DoubleConv(in_channels, out_channels, in_channels // 2) + else: + self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2) + self.conv = DoubleConv(in_channels, out_channels) + + def forward(self, x1, x2): + x1 = self.up(x1) + diffY = x2.size()[2] - x1.size()[2] + diffX = x2.size()[3] - x1.size()[3] + + x1 = F.pad( + x1, + [diffX // 2, diffX - diffX // 2, diffY // 2, diffY - diffY // 2] + ) + x = torch.cat([x2, x1], dim=1) + return self.conv(x) + + +class OutConv(nn.Module): + def __init__(self, in_channels, out_channels): + super().__init__() + self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1) + + def forward(self, x): + return self.conv(x) + + +class UNet(nn.Module): + def __init__(self, n_channels, n_classes, embedding_dim=32, bilinear=False): + super().__init__() + self.n_channels = n_channels + self.n_classes = n_classes + self.bilinear = bilinear + + self.base_channel_sz = 128 + + self.inc = DoubleConv(n_channels, self.base_channel_sz) + self.down1 = Down(self.base_channel_sz, 2 * self.base_channel_sz) + self.down2 = Down(2 * self.base_channel_sz, 4 * self.base_channel_sz) + self.down3 = Down(4 * self.base_channel_sz, 8 * self.base_channel_sz) + factor = 2 if bilinear else 1 + self.down4 = Down(8 * self.base_channel_sz, 16 * self.base_channel_sz // factor) + + self.up1 = Up(16 * self.base_channel_sz, 8 * self.base_channel_sz // factor, bilinear) + self.up2 = Up(8 * self.base_channel_sz, 4 * self.base_channel_sz // factor, bilinear) + self.up3 = Up(4 * self.base_channel_sz, 2 * self.base_channel_sz // factor, bilinear) + self.up4 = Up(2 * self.base_channel_sz, self.base_channel_sz, bilinear) + + self.outc = OutConv(self.base_channel_sz, n_classes) + self.embedding_head = nn.Sequential( + nn.Conv2d(self.base_channel_sz, embedding_dim, kernel_size=1), + nn.Sigmoid() + ) + + def forward(self, x): + x1 = self.inc(x) + x2 = self.down1(x1) + x3 = self.down2(x2) + x4 = self.down3(x3) + x5 = self.down4(x4) + + x = self.up1(x5, x4) + x = self.up2(x, x3) + x = self.up3(x, x2) + x = self.up4(x, x1) + + seg_logits = self.outc(x) + embedding = self.embedding_head(x) + return seg_logits, embedding + + @staticmethod + def load_from_checkpoint(model_path: str, n_channels: int, n_classes: int): + model = UNet(n_channels=n_channels, n_classes=n_classes) + lightning_dict = torch.load(model_path, map_location="cpu") + state_dict = lightning_dict['state_dict'] + try: + model.load_state_dict(state_dict, strict=False) + except: + model.load_state_dict(state_dict) + return model + + +class UNetCarbon(nn.Module): + def __init__(self, n_channels, n_classes, dropout=0.25): + super(UNetCarbon, self).__init__() + self.n_channels = n_channels + self.n_classes = n_classes + self.base_channel_sz = 64 + + self.inc = (DoubleConv(n_channels, self.base_channel_sz)) + self.down1 = (DownCarbon(self.base_channel_sz, 2 * self.base_channel_sz)) + self.down2 = (DownCarbon(2 * self.base_channel_sz, 4 * self.base_channel_sz)) + self.down3 = (DownCarbon(4 * self.base_channel_sz, 8 * self.base_channel_sz)) + self.down4 = (DownCarbon(8 * self.base_channel_sz, 16 * self.base_channel_sz )) + self.up1 = (UpCarbon(16 * self.base_channel_sz, 8 * self.base_channel_sz)) + self.up2 = (UpCarbon(8 * self.base_channel_sz, 4 * self.base_channel_sz)) + self.up3 = (UpCarbon(4 * self.base_channel_sz, 2 * self.base_channel_sz)) + self.up4 = (UpCarbon(2 * self.base_channel_sz, self.base_channel_sz)) + self.outc = (OutConv(self.base_channel_sz, n_classes)) + + + def forward(self, x): + x1 = self.inc(x) + x2 = self.down1(x1) + x3 = self.down2(x2) + x4 = self.down3(x3) + x5 = self.down4(x4) + + x = self.up1(x5, x4) + x = self.up2(x, x3) + x = self.up3(x, x2) + x = self.up4(x, x1) + logits = self.outc(x) + + return logits + + @staticmethod + def load_from_checkpoint(model_path: str, n_channels: int, n_classes: int): + model = UNetCarbon(n_channels=n_channels, n_classes=n_classes) + lightning_dict = torch.load(model_path, map_location="cpu") + state_dict = lightning_dict['state_dict'] + try: + model.load_state_dict(state_dict, strict=False) + except: + model.load_state_dict(state_dict) + return model + + + +############################################################################### +# MRC + instance segmentation +############################################################################### +def load_mrc(mrc_fn: str, multiply_global_origin: bool = True): + with mrcfile.open(mrc_fn, "r") as mrc_file_handle: + voxel_size = float(mrc_file_handle.voxel_size.x) + if voxel_size <= 0: + raise RuntimeError(f"MRC file {mrc_fn} does not have a valid header.") + + c = mrc_file_handle.header["mapc"] + r = mrc_file_handle.header["mapr"] + s = mrc_file_handle.header["maps"] + + global_origin = mrc_file_handle.header["origin"] + global_origin = np.array([global_origin.x, global_origin.y, global_origin.z]) + global_origin[0] += mrc_file_handle.header["nxstart"] + global_origin[1] += mrc_file_handle.header["nystart"] + global_origin[2] += mrc_file_handle.header["nzstart"] + + if multiply_global_origin: + global_origin *= mrc_file_handle.voxel_size.x + + raw_data = mrc_file_handle.data + if c == 1 and r == 2 and s == 3: + grid = raw_data + elif c == 3 and r == 2 and s == 1: + grid = np.moveaxis(raw_data, [0, 1, 2], [2, 1, 0]) + elif c == 2 and r == 1 and s == 3: + grid = np.moveaxis(raw_data, [1, 2, 0], [2, 1, 0]) + else: + raise RuntimeError("MRC file axis arrangement not supported!") + return grid, voxel_size, global_origin + + +############################################################################### +# Sample coordinates from the final pruned label, including IDs +############################################################################### +def sample_coordinates_with_ids(label_image, step=50): + + labels = np.unique(label_image) + labels = labels[labels != 0] + labels.sort() # ensure ascending + results = [] + + filament_counter = 1 + for lbl in labels: + bin_mask = (label_image == lbl) + sk = Skeleton(bin_mask) + if sk.n_paths == 0: + continue + + # For each path in this label, sample coordinates + for p_idx in range(sk.n_paths): + coords = sk.path_coordinates(p_idx).astype(int) + if len(coords) == 0: + continue + for i in range(0, len(coords), step): + r, c = coords[i] + results.append([c, r, filament_counter]) + # ensure final coordinate is included + if (len(coords) - 1) % step != 0: + r, c = coords[-1] + results.append([c, r, filament_counter]) + + filament_counter += 1 + + return np.array(results, dtype=int) + + +############################################################################### +# Visualization Helpers +############################################################################### +import matplotlib.colors as mcolors + +def color_nonrandom(label_image): + H, W = label_image.shape + colorized = np.full((H, W, 3), 127, dtype=np.uint8) + + labels = np.unique(label_image) + labels = labels[labels != 0] + i = 0 + for lbl in labels: + if i%6==0: + r= 255 + g= 0 + b =0 + elif i%6==1: + r= 0 + g= 255 + b =0 + elif i%6==2: + r= 0 + g= 0 + b =255 + elif i%6==3: + r= 255 + g= 255 + b =0 + elif i%6==4: + r= 0 + g= 255 + b =255 + elif i%6==5: + r= 255 + g= 0 + b =255 + mask = (label_image == lbl) + colorized[mask] = (r, g, b) + i=i+1 + return colorized + +def color_foreground(grey_image, threshold): + grey_image_uint8 = (grey_image * 255).astype(np.uint8) + colorized = np.stack([grey_image_uint8] * 3, axis=-1) + mask = (grey_image > threshold) + colorized[mask] = (255, 0, 0) + return colorized + + +############################################################################### +# Sjors go at pruning the raw skeleton image +############################################################################### + +def trim_end(branch_coords, trim_length=10, trim_start=False): + """Trims the start or end of a branch by `trim_length` pixels.""" + if len(branch_coords) > trim_length: + return branch_coords[trim_length:] if trim_start else branch_coords[:-trim_length] + return np.array([]) # Remove short branches completely + +def angular_diff(a, b, period=180): + """Minimal circular difference between angles a and b on [0, period).""" + raw = abs(a - b) + return min(raw, period - raw) + +def segment_orientation(p0, p1): + """ + Compute the orientation (in degrees) and fold it into [0, 180). + p0, p1 are (row, col) integer tuples or arrays. + """ + # Note: row = y, col = x + dy = p1[0] - p0[0] + dx = p1[1] - p0[1] + # conventions of y-axis is reversed in RELION, so use -dy! + ang = np.degrees(np.arctan2(-dy, dx)) # gives [-180, 180) + + # fold into [0, 180): + if ang < 0: + ang += 180 + elif ang >= 180: + ang -= 180 + + return ang + +def split_branch_on_psi_mismatch(branch_coords, psi_image, threshold=20): + + segments = [] + current_seg = [tuple(branch_coords[0])] + prev_pt = branch_coords[0] + prev_ang = psi_image[prev_pt[0], prev_pt[1]] + + # step through each next point + for i in range(1, len(branch_coords)): + pt = branch_coords[i] + psi_ang = psi_image[pt[0], pt[1]] + + # split if jump in neighbour psi image values > threshold + do_split = False + if angular_diff(prev_ang, psi_ang) > threshold: + do_split = True + # compute seg_ang between i-3 and i + elif i >= 3: + p0 = branch_coords[i - 3] + seg_ang = segment_orientation(p0, pt) + # split if mismatch > threshold + if angular_diff(seg_ang, psi_ang) > threshold: + do_split = True + + if do_split: + segments.append(np.array(current_seg)) + current_seg = [] + + current_seg.append(tuple(pt)) + prev_ang = psi_ang + + # append last segment + if current_seg: + segments.append(np.array(current_seg)) + + return segments + +def prune_skeleton(skeleton, psi_image, min_length=100, min_distance=25, step=50, psi_threshold=45): + """Prune skeleton branches that too short.""" + # Most of the compute goes into the Skeleton call; rest of function is cheap + skel = Skeleton(skeleton) + df = summarize(skel, separator='-') + + trim_length = int(min_distance / 2) + labeled_skeleton = np.zeros_like(skeleton, dtype=np.int32) + accepted_branches = [] # Stores (branch coordinates, start, end) + sampled_coords = [] + + # Pass 0: split branches with jumps in psi-angle values + all_splits = [] + for idx, row in df.iterrows(): + coords = skel.path_coordinates(idx) + pieces = split_branch_on_psi_mismatch(coords, psi_image, threshold=psi_threshold) + all_splits.extend(pieces) + + # Pass 1: Collect branches and compute pairwise distances + branch_data = [] # Store branch info for pass 2 + endpoints = [] # Store (start, end) tuples + for branch_coords in all_splits: + + # Compute Euclidean length of the branch + length = np.sum(np.sqrt(np.sum(np.diff(branch_coords, axis=0) ** 2, axis=1))) + if length < min_length: + continue # Skip short branches + + start, end = tuple(branch_coords[0]), tuple(branch_coords[-1]) + branch_data.append((branch_coords, start, end)) + endpoints.append(start) + endpoints.append(end) + + if not branch_data: + return labeled_skeleton, np.array(sampled_coords, dtype=int) # No valid branches + + # Compute all pairwise distances between endpoints + endpoints = np.array(endpoints) # Convert to numpy array + dists = cdist(endpoints, endpoints) + + # Mark close endpoint pairs for trimming + close_pairs = np.where((dists < min_distance) ) # Exclude self-distances + trim_flags = {} # Dictionary to store which endpoints need trimming + + for i in range(len(close_pairs[0])): + idx1, idx2 = close_pairs[0][i], close_pairs[1][i] + branch_idx1, endpoint_type1 = divmod(idx1, 2) # Get branch index & endpoint type + branch_idx2, endpoint_type2 = divmod(idx2, 2) + + if branch_idx1 == branch_idx2: + continue # Ignore self-pairs + + # Mark both endpoints for trimming + trim_flags.setdefault((branch_idx1, endpoint_type1), True) + trim_flags.setdefault((branch_idx2, endpoint_type2), True) + + # **Pass 2: Trim branches based on identified close endpoints** + final_branches = [] + + for i, (branch_coords, start, end) in enumerate(branch_data): + # Trim start or end if flagged + if (i, 0) in trim_flags: + branch_coords = trim_end(branch_coords, trim_length, trim_start=True) + if (i, 1) in trim_flags: + branch_coords = trim_end(branch_coords, trim_length, trim_start=False) + + # Ensure branch still exists after trimming + if len(branch_coords) > 0: + final_branches.append(branch_coords) + + # Assign unique labels to final pruned branches + for path_id, branch in enumerate(final_branches, start=1): + for x, y in branch: + labeled_skeleton[int(x), int(y)] = path_id + + for i in range(0, len(branch), step): + r, c = branch[i] + sampled_coords.append([c, r, path_id]) + # ensure final coordinate is included + if (len(branch) - 1) % step != 0: + r, c = branch[-1] + sampled_coords.append([c, r, path_id]) + + return labeled_skeleton, np.array(sampled_coords, dtype=int) + +def resize_and_norm(image, mysize, angpix, do_norm=True): + ys, xs = image.shape + if (xs > ys): + scale = ys/mysize + else: + scale = xs/mysize + + xsp = int(xs/scale) + ysp = int(ys/scale) + resized_angpix = angpix *scale + resized = cv2.resize(image.astype(np.float32), (xsp, ysp), interpolation=cv2.INTER_AREA) + if do_norm: + max = resized.max() + min = resized.min() + if (max > min): + resized = (resized - min) / (max - min) + + return resized, resized_angpix + + +def install_model( + name: str, + verbose: bool = False, +): + model_list = { + "amytracer-v1.0": [ + "https://zenodo.org/records/17949642/files/amytracer-v1.0.ckpt.gz", + "d83777816d899d64aef593c97888e88f56d49e2b652cdc460de554f59d6f94ed" + ], + "amytracer-v2.0": [ + "https://zenodo.org/records/17949642/files/amytracer-v1.0.ckpt.gz", + "57f0cd566ca1779641691f7daf4a6147ccdb4f509f01c7472861e32bb7dbe14c" + ], + "carbonpicker-v1.0": [ + "https://zenodo.org/records/17949642/files/carbonpicker-v1.0.ckpt.gz", + "e25702cc84850339b46ac5b7d8e19ebdd55ad420c15bda8c9f9a584f59e7fd6b" + ] + } + + if name in model_list.keys(): + dest_dir = os.path.join(torch.hub.get_dir(), "checkpoints", "relion_trace_amyloids") + model_path = os.path.join(dest_dir, f"{name}.ckpt") + model_path_gz = model_path + ".gz" + completed_check_path = os.path.join(dest_dir, f"{name}_installed.txt") + + # Download file and install it if not already done + if not os.path.isfile(completed_check_path): + if verbose: + print(f"Installing amyloid picker model ({name})...") + os.makedirs(dest_dir, exist_ok=True) + + source_url = model_list[name][0] + + if verbose: + print(f"Downloading model weights from:\n {source_url}") + + import gzip, shutil + torch.hub.download_url_to_file(source_url, model_path_gz, hash_prefix=model_list[name][1]) + with gzip.open(model_path_gz, 'rb') as f_in: + with open(model_path, 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + os.remove(model_path_gz) + + with open(completed_check_path, "w") as f: + f.write("Successfully downloaded model") + + if verbose: + print(f"Amyloid picker model ({name}) successfully installed in {dest_dir}") + + else: + model_path = name + + return model_path + + + + +############################################################################### +# MAIN +############################################################################### +def main(): + + #start_time = time.time() + parser = argparse.ArgumentParser("Skeleton-based filament picking with instance merging, branch pruning, and ID labeling") + parser.add_argument("-i", "--input", help="Input star file", default="None") + parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint", default="amytracer-v2.0") + parser.add_argument("-c", "--carbon_model_path", help="Path to PyTorch model checkpoint for carbon detection", default="carbonpicker-v1.0") + parser.add_argument("-o", "--output", help="Output star file with coordinates", default="out") + parser.add_argument("-r", "--radius", type=int, help="Minimum distance between ends of filaments (in A)", default=25) + parser.add_argument("-l", "--minimum_length", type=int, default=250, + help="Remove final segments smaller than this size in Angstroms (default=250)") + parser.add_argument("-p", "--psi_jump_threshold", default=45, type=float, help="Maximum difference in degrees of psi angles for consecutive points in skeletons (default=45)") + parser.add_argument("-t", "--threshold", default=0.5, type=float, help="Foreground threshold (default=0.5)") + parser.add_argument("-ct", "--carbon_threshold", default=0.9, type=float, help="Carbon detection threshold (default=0.9)") + parser.add_argument("-s", "--scale", type=float, default=1.0, + help="Multiply output coordinates by this factor (default=1.0)") + parser.add_argument("-d", "--device", default="cuda:0", help="Which GPU device to use") + parser.add_argument("-j", "--threads", type=int, default=1) + parser.add_argument("-a", "--abort", help="Abort if this file exists") + parser.add_argument("-v", "--verb", type=int, help="Verbosity") + parser.add_argument("--sample_step", type=int, default=100, + help="Sample a coordinate every 'sample_step' Angstroms along each path (default=50)") + parser.add_argument("--plot", action='store_true', + help="If set, display/save a collage figure of the pipeline results.") + args = parser.parse_args() + + # number of CPU threads allowed + #torch.set_num_threads(args.threads) + + # ------------------- Load model ---------------------- + device = torch.device(args.device) + + # This is for when we call this program without any arguments upon installation of the relion package (in scripts/python_fetch_weights.in) + if (args.input == "None"): + my_model_path = install_model(args.model_path, verbose=True) + my_carbon_model_path = install_model(args.carbon_model_path, verbose=True) + sys.exit(0) + + # Load checkpoint file + my_model_path = install_model(args.model_path) + model = UNet.load_from_checkpoint(my_model_path, n_channels=3, n_classes=1) + model.eval().to(device) + if args.carbon_model_path == "": + if args.verb > 0: + print(" + Not doing carbon detection") + do_carbon = False + else: + do_carbon = True + if args.verb > 0: + print(" + Doing carbon detection") + my_carbon_model_path = install_model(args.carbon_model_path) + carbon_model = UNetCarbon.load_from_checkpoint(my_carbon_model_path, n_channels=1, n_classes=1) + carbon_model.eval().to(device) + + input_star = starfile.read(args.input) + + @torch.compile + def run_compiled_carbon_model(data): + seg_logits = carbon_model(data) + carbon_prob = torch.sigmoid(seg_logits.squeeze()) + return carbon_prob + + @torch.compile + def run_compiled_model(data): + seg_logits, embedding = model(data) + seg_prob = torch.sigmoid(seg_logits.squeeze()) + return seg_prob + + # ------------------ Run Inference ---------------- + iterator = tqdm(input_star.iterrows(), total=len(input_star), file=sys.stdout) if args.verb > 0 else input_star.iterrows() + half_dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16 + for i, row in iterator: + + if Path(args.abort).exists(): + print("Abort file exists. Aborting...") + sys.exit() + + # ------------------- Load MRCs ---------------------- + fom_image, pixel_size, origin = load_mrc(row["rlnMicrographFomImage"]) + psi_image, _, _ = load_mrc(row["rlnMicrographPsiImage"]) + + #--------------------- Carbon detection ----------------- + if do_carbon: + #in_tensor = np.stack(mic_image, axis=0) + #in_tensor = torch.from_numpy(in_tensor[None,None]).to(device=device, dtype=half_dtype) # shape (1,1,H,W) + mic_image, ori_pixel_size, _ = load_mrc(row["rlnMicrographName"]) + mic_image, resized_angpix = resize_and_norm(mic_image, 64, ori_pixel_size) + in_tensor = torch.from_numpy(mic_image[None,None]).to(device=device, dtype=half_dtype) # shape (1,1,H,W) + with torch.no_grad(): + if device.type == "cuda": + with torch.autocast(device_type=device.type, dtype=half_dtype): + carbon_prob = run_compiled_carbon_model(in_tensor) + else: + with torch.autocast(device_type="cpu", dtype=half_dtype): + carbon_prob = run_compiled_carbon_model(in_tensor) + carbon_prob = carbon_prob.to(dtype=torch.float32, device="cpu").numpy() + xsf, ysf= fom_image.shape + if carbon_prob.shape != fom_image.shape: + carbon_prob = cv2.resize(carbon_prob.astype(np.float32), (ysf, xsf), interpolation=cv2.INTER_AREA) + carbon_mask = (carbon_prob>args.carbon_threshold) + + #--------------------- Filament segmentation ----------------- + outfile = row["rlnMicrographCoordinates"] + directory = Path(outfile).parent + # Check if the directory exists, and create it if not + directory.mkdir(parents=True, exist_ok=True) + + H, W = fom_image.shape + + rad_psi = np.deg2rad(psi_image * 2) + in_tensor = np.stack((fom_image, np.sin(rad_psi), np.cos(rad_psi)), axis=0) # shape (3,H,W) + in_tensor = torch.from_numpy(in_tensor[None]).to(device=device, dtype=half_dtype) # shape (1,3,H,W) + + with torch.no_grad(): + if device.type == "cuda": + with torch.autocast(device_type=device.type, dtype=half_dtype): + seg_prob = run_compiled_model(in_tensor) + else: + with torch.autocast(device_type="cpu", dtype=half_dtype): + seg_prob = run_compiled_model(in_tensor) + foreground = seg_prob.to(dtype=torch.float32, device="cpu").numpy() + + # 2) Clean + threshold => remove small objects + bin_foreground = (foreground > args.threshold) + if do_carbon: + bin_foreground = bin_foreground & (~carbon_mask) + + min_size = 100 + clean_foreground = remove_small_objects(bin_foreground, min_size=min_size) + + # 3) Crop edges if radius is given + if args.radius is not None: + d = round(args.radius / pixel_size) + clean_foreground[:d, :] = False + clean_foreground[-d:, :] = False + clean_foreground[:, :d] = False + clean_foreground[:, -d:] = False + + # 4) Skeletonize + skeleton_img = skeletonize(clean_foreground) + + # if there is only a single non-zero pixel, Skeleton will give an error. Let's check for at least 10 non-zero pixels + if np.count_nonzero(skeleton_img) > 10: + + # 5) Prune skeleton based on Skan's branches: splits branches with jumps in psi-angles, remove any branch with length below args.final_min_size, and enforce minimal distance between end points + pruned_skeleton, sampled_points = prune_skeleton(skeleton_img, + psi_image, + min_length=args.minimum_length / pixel_size, + min_distance=2 * args.radius / pixel_size, + step=int(args.sample_step / pixel_size), + psi_threshold=args.psi_jump_threshold) + + # 6) Sample coordinates every 'sample_step' from pruned skeleton, **including filament ID**. + #sampled_points = sample_coordinates_with_ids(pruned_skeleton, step=int(args.sample_step / pixel_size)) + # sampled_points shape: (N, 3) => [x, y, filament_id] + if sampled_points.size > 0: + + # 7) Scale, round, write star + # scale only the x,y columns + sampled_points[:, 0:2] = np.round(sampled_points[:, 0:2] * args.scale).astype(int) + + df = pd.DataFrame({ + 'rlnCoordinateX': sampled_points[:, 0], + 'rlnCoordinateY': sampled_points[:, 1], + 'rlnParticleSelectionType': sampled_points[:, 2] + }) + starfile.write(df, outfile, overwrite=True) + else: + Path(outfile).touch() + else: + Path(outfile).touch() + + # --------------------------------------------------- + # If plotting is requested, create a collage figure + # --------------------------------------------------- + if args.plot: + + fig, ax = plt.subplots(2, 4, figsize=(20,15)) + ax = ax.ravel() + + ax[0].imshow(fom_image, cmap='gray') + ax[0].set_title("FOM Image") + ax[0].axis('off') + + ax[1].imshow(psi_image, cmap='gray') + ax[1].set_title("PSI Image") + ax[1].axis('off') + + ax[2].imshow(color_foreground(foreground, args.threshold)) + ax[2].set_title("Foreground") + ax[2].axis('off') + + ax[3].imshow(skeleton_img) + ax[3].set_title("Raw Skeleton") + ax[3].axis('off') + + if do_carbon: + ax[4].imshow(mic_image, cmap='gray') + ax[4].set_title("micrograph") + ax[4].axis('off') + + ax[5].imshow(color_foreground(carbon_prob, args.carbon_threshold)) + ax[5].set_title("carbon detection") + ax[5].axis('off') + + if skeleton_img.any(): + ax[6].imshow(color_nonrandom(pruned_skeleton)) + ax[6].set_title("Pruned Skeleton") + ax[6].axis('off') + + if sampled_points.size > 0: + ax[7].imshow(np.zeros_like(foreground), cmap='gray') # blank background + ax[7].scatter(sampled_points[:,0]/args.scale, sampled_points[:,1]/args.scale, s=2, c='red') + ax[7].set_xlim([0, foreground.shape[1]]) + ax[7].set_ylim([foreground.shape[0], 0]) + ax[7].set_title("Final Sampled Picks (ID shown in STAR)") + ax[7].axis('off') + + plt.tight_layout() + plt.show() + plt.close() + + +if __name__ == "__main__": + main() From f0119b8a55413213aad6e1a26dc6e098fc9d9451 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 2 Jan 2026 12:13:38 +0000 Subject: [PATCH 166/189] repaired error in name of trace_amyloids.py --- ...on_trace_amyloids.py => trace_amyloids.py} | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) rename scripts/{python_trace_amyloids.py => trace_amyloids.py} (97%) diff --git a/scripts/python_trace_amyloids.py b/scripts/trace_amyloids.py similarity index 97% rename from scripts/python_trace_amyloids.py rename to scripts/trace_amyloids.py index c0b378b57..9c976e159 100755 --- a/scripts/python_trace_amyloids.py +++ b/scripts/trace_amyloids.py @@ -1,4 +1,4 @@ -#!@PYTHON_EXE_PATH@ +#!/public/EM/anaconda3/envs/relion-5.0/bin/python import argparse import math @@ -13,6 +13,8 @@ import torch.nn.functional as F import mrcfile +import matplotlib +matplotlib.use("Agg") # non-GUI backend, no Qt/X11 needed import matplotlib.pyplot as plt from skimage.morphology import remove_small_objects, skeletonize @@ -541,7 +543,7 @@ def install_model( "d83777816d899d64aef593c97888e88f56d49e2b652cdc460de554f59d6f94ed" ], "amytracer-v2.0": [ - "https://zenodo.org/records/17949642/files/amytracer-v1.0.ckpt.gz", + "https://zenodo.org/records/17949642/files/amytracer-v2.0.ckpt.gz", "57f0cd566ca1779641691f7daf4a6147ccdb4f509f01c7472861e32bb7dbe14c" ], "carbonpicker-v1.0": [ @@ -577,8 +579,8 @@ def install_model( with open(completed_check_path, "w") as f: f.write("Successfully downloaded model") - if verbose: - print(f"Amyloid picker model ({name}) successfully installed in {dest_dir}") + if verbose: + print(f"Amyloid picker model ({name}) successfully installed in {dest_dir}") else: model_path = name @@ -593,22 +595,22 @@ def install_model( ############################################################################### def main(): - #start_time = time.time() parser = argparse.ArgumentParser("Skeleton-based filament picking with instance merging, branch pruning, and ID labeling") parser.add_argument("-i", "--input", help="Input star file", default="None") - parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint", default="amytracer-v2.0") - parser.add_argument("-c", "--carbon_model_path", help="Path to PyTorch model checkpoint for carbon detection", default="carbonpicker-v1.0") parser.add_argument("-o", "--output", help="Output star file with coordinates", default="out") parser.add_argument("-r", "--radius", type=int, help="Minimum distance between ends of filaments (in A)", default=25) parser.add_argument("-l", "--minimum_length", type=int, default=250, help="Remove final segments smaller than this size in Angstroms (default=250)") parser.add_argument("-p", "--psi_jump_threshold", default=45, type=float, help="Maximum difference in degrees of psi angles for consecutive points in skeletons (default=45)") parser.add_argument("-t", "--threshold", default=0.5, type=float, help="Foreground threshold (default=0.5)") + parser.add_argument("-c", "--carbon", action='store_true', help="Ignore filaments on carbon") parser.add_argument("-ct", "--carbon_threshold", default=0.9, type=float, help="Carbon detection threshold (default=0.9)") parser.add_argument("-s", "--scale", type=float, default=1.0, help="Multiply output coordinates by this factor (default=1.0)") parser.add_argument("-d", "--device", default="cuda:0", help="Which GPU device to use") parser.add_argument("-j", "--threads", type=int, default=1) + parser.add_argument("-m", "--model_path", help="Path to PyTorch model checkpoint", default="amytracer-v2.0") + parser.add_argument("-cm", "--carbon_model_path", help="Path to PyTorch model checkpoint for carbon detection", default="carbonpicker-v1.0") parser.add_argument("-a", "--abort", help="Abort if this file exists") parser.add_argument("-v", "--verb", type=int, help="Verbosity") parser.add_argument("--sample_step", type=int, default=100, @@ -628,12 +630,12 @@ def main(): my_model_path = install_model(args.model_path, verbose=True) my_carbon_model_path = install_model(args.carbon_model_path, verbose=True) sys.exit(0) - + # Load checkpoint file my_model_path = install_model(args.model_path) model = UNet.load_from_checkpoint(my_model_path, n_channels=3, n_classes=1) model.eval().to(device) - if args.carbon_model_path == "": + if not args.carbon: if args.verb > 0: print(" + Not doing carbon detection") do_carbon = False @@ -645,6 +647,8 @@ def main(): carbon_model = UNetCarbon.load_from_checkpoint(my_carbon_model_path, n_channels=1, n_classes=1) carbon_model.eval().to(device) + if args.plot: + print(" + Writing PNG file with intermediate results for every micrograph") input_star = starfile.read(args.input) @torch.compile @@ -796,7 +800,7 @@ def run_compiled_model(data): ax[5].set_title("carbon detection") ax[5].axis('off') - if skeleton_img.any(): + if skeleton_img.any() and np.count_nonzero(skeleton_img) > 10: ax[6].imshow(color_nonrandom(pruned_skeleton)) ax[6].set_title("Pruned Skeleton") ax[6].axis('off') @@ -810,7 +814,8 @@ def run_compiled_model(data): ax[7].axis('off') plt.tight_layout() - plt.show() + out_png = os.path.splitext(outfile)[0] + ".png" + plt.savefig(out_png, dpi=200, bbox_inches="tight") plt.close() From a61ad651854bd1925f2eb00db96f3d000b3fa893 Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 2 Jan 2026 14:08:21 +0000 Subject: [PATCH 167/189] just comment what typical blush command looks like so we don't need to change code to debug --- src/backprojector.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backprojector.cpp b/src/backprojector.cpp index 8a0d2a77c..6ba1f8919 100644 --- a/src/backprojector.cpp +++ b/src/backprojector.cpp @@ -1287,6 +1287,9 @@ void BackProjector::externalReconstruct(MultidimArray &vol_out, cmd += " " + blush_args; + // The typical command will look something like this: + // relion_python_blush Refine3D/job024/run_it006_half2_class001_external_reconstruct.star -m v1.0 --gpu 1 + FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) throw std::runtime_error("Failed to dispatch command: " + cmd); From 695f3f063c35262796e1697936b7e725fbb410ef Mon Sep 17 00:00:00 2001 From: scheres Date: Mon, 5 Jan 2026 10:18:41 +0000 Subject: [PATCH 168/189] added option to set tilt prior for helical extract jobs --- src/gui_jobwindow.cpp | 8 ++++--- src/healpix_sampling.cpp | 11 ++++++++-- src/helix.cpp | 47 +++++++++++++++++++++++----------------- src/helix.h | 21 ++++++++++++------ src/pipeline_jobs.cpp | 4 +++- src/preprocessing.cpp | 23 ++++++++++---------- src/preprocessing.h | 10 ++++++--- 7 files changed, 77 insertions(+), 47 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index c97e91196..cc1af59e1 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -1088,13 +1088,15 @@ Pixels values higher than this many times the image stddev will be replaced with group5->begin(); - place("helical_tube_outer_diameter", TOGGLE_DEACTIVATE); - - current_y += STEPY/2; + //place("helical_tube_outer_diameter", TOGGLE_DEACTIVATE); + //current_y += STEPY/2; place("helical_bimodal_angular_priors", TOGGLE_DEACTIVATE); place("helical_nr_asu", TOGGLE_DEACTIVATE); place("helical_rise", TOGGLE_DEACTIVATE); + current_y += STEPY/2; + + place("helical_tilt_prior", TOGGLE_DEACTIVATE); group5->end(); diff --git a/src/healpix_sampling.cpp b/src/healpix_sampling.cpp index cede5d133..5c9e425e7 100644 --- a/src/healpix_sampling.cpp +++ b/src/healpix_sampling.cpp @@ -1156,6 +1156,13 @@ void HealpixSampling::selectOrientationsWithNonZeroPriorProbabilityFor3DHelicalR pointer_dir_nonzeroprior.clear(); directions_prior.clear(); + // If prior_tilt is not 90, then these are tilted images: DON'T perform direction2 angular searches + bool skip_direction2 = false; + if ((prior_tilt > 0. && prior_tilt < 90.) || (prior_tilt > 90. && prior_tilt < 180.) ) + { + skip_direction2 = true; + } + if (is_3D) { // If tilt prior is less than 20 or larger than 160 degrees, print a warning message @@ -1197,7 +1204,7 @@ void HealpixSampling::selectOrientationsWithNonZeroPriorProbabilityFor3DHelicalR } } - if (!do_auto_refine_local_searches) + if (!do_auto_refine_local_searches && !skip_direction2) { // Assume tilt = (0, +180) // TODO: Check if "(tilt_angles[idir] > 0.01) && (tilt_angles[idir] < 179.99)" is needed @@ -1319,7 +1326,7 @@ void HealpixSampling::selectOrientationsWithNonZeroPriorProbabilityFor3DHelicalR } } - if (!do_auto_refine_local_searches) + if (!do_auto_refine_local_searches && !skip_direction2) { // Assume tilt = (0, +180) // TODO: Check if "(tilt_angles[idir] > 0.01) && (tilt_angles[idir] < 179.99)" is needed diff --git a/src/helix.cpp b/src/helix.cpp index 51c350fa2..b1c355e48 100644 --- a/src/helix.cpp +++ b/src/helix.cpp @@ -1773,7 +1773,8 @@ void extractHelicalSegmentsFromTubes_Multiple( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors, - bool cut_into_segments) + bool cut_into_segments, + RFLOAT tilt_prior) { int total_segments, total_tubes, nr_segments, nr_tubes; FileName fns_in; @@ -1791,11 +1792,11 @@ void extractHelicalSegmentsFromTubes_Multiple( FileName fn_out; fn_out = fn_in_list[ii].beforeFirstOf(suffix_in) + suffix_out; if (format_tag == RELION_STAR_FORMAT) - convertHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments); + convertHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments, tilt_prior); else if (format_tag == XIMDISP_COORDS_FORMAT) - convertXimdispHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments); + convertXimdispHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments, tilt_prior); else if (format_tag == EMAN2_FORMAT) - convertEmanHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments); + convertEmanHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments, tilt_prior); else REPORT_ERROR("helix.cpp::extractHelicalSegmentsFromTubes_Multiple(): BUG Invalid format tag!"); total_segments += nr_segments; @@ -1819,7 +1820,8 @@ void convertHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors, - bool cut_into_segments) + bool cut_into_segments, + RFLOAT tilt_prior) { int nr_segments, MDobj_id; RFLOAT psi_deg, psi_rad, x1, y1, x2, y2, dx, dy, xp, yp, step_pix, half_box_size_pix, len_pix, psi_prior_flip_ratio, pitch; @@ -1942,7 +1944,7 @@ void convertHelicalTubeCoordsToMetaDataTable( MD_out.setValue(EMDL_IMAGE_COORD_X, ((x1 + x2) * 0.5)); MD_out.setValue(EMDL_IMAGE_COORD_Y, ((y1 + y2) * 0.5)); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, (tube_id + 1)); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, 0.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -1987,7 +1989,7 @@ void convertHelicalTubeCoordsToMetaDataTable( MD_out.setValue(EMDL_IMAGE_COORD_X, xp); MD_out.setValue(EMDL_IMAGE_COORD_Y, yp); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, (tube_id + 1)); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, pixel_size_A * len_pix); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2283,7 +2285,8 @@ void convertHelicalSegmentCoordsToStarFile_Multiple( RFLOAT Xdim, RFLOAT Ydim, RFLOAT boxsize, - bool bimodal_angular_priors) + bool bimodal_angular_priors, + RFLOAT tilt_prior) { int total_segments, nr_segments, total_tubes, nr_tubes; FileName fns_coords; @@ -2301,9 +2304,9 @@ void convertHelicalSegmentCoordsToStarFile_Multiple( FileName fn_out; fn_out = fn_coords_list[ii].beforeFirstOf(suffix_coords) + suffix_out; if (format_tag == XIMDISP_COORDS_FORMAT) - convertXimdispHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors); + convertXimdispHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors, tilt_prior); else if (format_tag == EMAN2_FORMAT) - convertEmanHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors); + convertEmanHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors, tilt_prior); else REPORT_ERROR("helix.cpp::convertHelicalCoordsToStarFile_Multiple(): BUG Invalid format tag!"); total_segments += nr_segments; @@ -2395,7 +2398,8 @@ void convertXimdispHelicalSegmentCoordsToMetaDataTable( RFLOAT Xdim, RFLOAT Ydim, RFLOAT box_size_pix, - bool bimodal_angular_priors) + bool bimodal_angular_priors, + RFLOAT tilt_prior) { int nr_segments_on_edges, nr_segments, nr_tubes; RFLOAT x, y, x_old, y_old, psi_deg_old, psi_deg, half_box_size_pix, len_pix, psi_prior_flip_ratio; @@ -2476,7 +2480,7 @@ void convertXimdispHelicalSegmentCoordsToMetaDataTable( MD_out.setValue(EMDL_IMAGE_COORD_X, x); MD_out.setValue(EMDL_IMAGE_COORD_Y, y); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, pixel_size_A * len_pix); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2502,7 +2506,8 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors, - bool cut_into_segments) + bool cut_into_segments, + RFLOAT tilt_prior) { int nr_segments, nr_tubes; RFLOAT xp, yp, dx, dy, x1, y1, x2, y2, psi_deg, psi_rad, half_box_size_pix, len_pix, psi_prior_flip_ratio; @@ -2584,7 +2589,7 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( MD_out.setValue(EMDL_IMAGE_COORD_X, ((x1 + x2) * 0.5)); MD_out.setValue(EMDL_IMAGE_COORD_Y, ((y1 + y2) * 0.5)); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, 0.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2624,7 +2629,7 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( MD_out.setValue(EMDL_IMAGE_COORD_X, xp); MD_out.setValue(EMDL_IMAGE_COORD_Y, yp); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, pixel_size_A * len_pix); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2651,7 +2656,8 @@ void convertEmanHelicalSegmentCoordsToMetaDataTable( RFLOAT Xdim, RFLOAT Ydim, RFLOAT box_size_pix, - bool bimodal_angular_priors) + bool bimodal_angular_priors, + RFLOAT tilt_prior) { int nr_segments_on_edges, nr_segments, nr_tubes; RFLOAT x, y, x_old, y_old, psi_deg, half_box_size_pix, len_pix, width, psi_prior_flip_ratio; @@ -2743,7 +2749,7 @@ void convertEmanHelicalSegmentCoordsToMetaDataTable( MD_out.setValue(EMDL_IMAGE_COORD_X, x); MD_out.setValue(EMDL_IMAGE_COORD_Y, y); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, pixel_size_A * len_pix); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2770,7 +2776,8 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors, - bool cut_into_segments) + bool cut_into_segments, + RFLOAT tilt_prior) { int nr_segments, nr_tubes; RFLOAT xp, yp, dx, dy, x1, y1, x2, y2, psi_deg, psi_rad, half_box_size_pix, len_pix, psi_prior_flip_ratio; @@ -2869,7 +2876,7 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( MD_out.setValue(EMDL_IMAGE_COORD_X, ((x1 + x2) * 0.5)); MD_out.setValue(EMDL_IMAGE_COORD_Y, ((y1 + y2) * 0.5)); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, 0.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2909,7 +2916,7 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( MD_out.setValue(EMDL_IMAGE_COORD_X, xp); MD_out.setValue(EMDL_IMAGE_COORD_Y, yp); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, pixel_size_A * len_pix); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); diff --git a/src/helix.h b/src/helix.h index 7d4c71d34..68e3b9ae2 100644 --- a/src/helix.h +++ b/src/helix.h @@ -210,7 +210,8 @@ void extractHelicalSegmentsFromTubes_Multiple( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors = true, - bool cut_into_segments = true); + bool cut_into_segments = true, + RFLOAT tilt_prior = 90.); void convertHelicalTubeCoordsToMetaDataTable( FileName& fn_in, @@ -224,7 +225,8 @@ void convertHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors = true, - bool cut_into_segments = true); + bool cut_into_segments = true, + RFLOAT tilt_prior = 90.); void combineParticlePriorsWithKaiLocalCTF( FileName& fn_priors, @@ -260,7 +262,8 @@ void convertHelicalSegmentCoordsToStarFile_Multiple( RFLOAT Xdim, RFLOAT Ydim, RFLOAT boxsize, - bool bimodal_angular_priors = true); + bool bimodal_angular_priors = true, + RFLOAT tilt_prior = 90.); void convertHelicalSegmentCoordsToMetaDataTable( FileName& fn_in, @@ -282,7 +285,8 @@ void convertXimdispHelicalSegmentCoordsToMetaDataTable( RFLOAT Xdim, RFLOAT Ydim, RFLOAT box_size_pix, - bool bimodal_angular_priors = true); + bool bimodal_angular_priors = true, + RFLOAT tilt_prior = 90.); void convertXimdispHelicalTubeCoordsToMetaDataTable( FileName& fn_in, @@ -296,7 +300,8 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors = true, - bool cut_into_segments = true); + bool cut_into_segments = true, + RFLOAT tilt_prior = 90.); void convertEmanHelicalSegmentCoordsToMetaDataTable( FileName& fn_in, @@ -307,7 +312,8 @@ void convertEmanHelicalSegmentCoordsToMetaDataTable( RFLOAT Xdim, RFLOAT Ydim, RFLOAT box_size_pix, - bool bimodal_angular_priors = true); + bool bimodal_angular_priors = true, + RFLOAT tilt_prior = 90.); void convertEmanHelicalTubeCoordsToMetaDataTable( FileName& fn_in, @@ -321,7 +327,8 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors = true, - bool cut_into_segments = true); + bool cut_into_segments = true, + RFLOAT tilt_prior = 90.); void makeHelicalReference2D( MultidimArray& out, diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index a462a008c..a50e71f2b 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2563,7 +2563,8 @@ This value should be slightly larger than the actual width of helical tubes."); Set to No if the 3D helix looks the same when rotated upside down."); joboptions["helical_nr_asu"] = JobOption("Number of unique asymmetrical units:", 1, 1, 100, 1, "Number of unique helical asymmetrical units in each segment box. This integer should not be less than 1. The inter-box distance (pixels) = helical rise (Angstroms) * number of asymmetrical units / pixel size (Angstroms). \ The optimal inter-box distance might also depend on the box size, the helical rise and the flexibility of the structure. In general, an inter-box distance of ~10% * the box size seems appropriate."); - joboptions["helical_rise"] = JobOption("Helical rise (A):", 1, 0, 100, 0.01, "Helical rise in Angstroms. (Please click '?' next to the option above for details about how the inter-box distance is calculated.)"); + joboptions["helical_rise"] = JobOption("Helical rise (A):", 1, 0, 100, 0.01, "Helical rise in Angstroms. (Please click '?' next to the option above for details about how the inter-box distance is calculated.)"); + //joboptions["helical_tilt_prior"] = JobOption("Tilt prior (deg):", 90, 0, 180, 5, "Use this option to set a prior value for the tilt angle. This will typically be 90 degrees, but this value will need to be changed if you tilted your images during data collection. If you tilted the stage 20 degrees, then you could try 70 and 110 degrees as a tilt prior. If you know the handedness of your camera/stage, then you can just use either 70 or 110."); } @@ -2719,6 +2720,7 @@ bool RelionJob::getCommandsExtractJob(std::string &outputname, std::vector Date: Mon, 5 Jan 2026 12:10:53 +0000 Subject: [PATCH 169/189] repaired bug tilt prior on gui --- src/gui_jobwindow.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index cc1af59e1..d4a91650d 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -1088,15 +1088,15 @@ Pixels values higher than this many times the image stddev will be replaced with group5->begin(); - //place("helical_tube_outer_diameter", TOGGLE_DEACTIVATE); - //current_y += STEPY/2; + place("helical_tube_outer_diameter", TOGGLE_DEACTIVATE); + current_y += STEPY/2; place("helical_bimodal_angular_priors", TOGGLE_DEACTIVATE); place("helical_nr_asu", TOGGLE_DEACTIVATE); place("helical_rise", TOGGLE_DEACTIVATE); - current_y += STEPY/2; - place("helical_tilt_prior", TOGGLE_DEACTIVATE); + //current_y += STEPY/2; + //place("helical_tilt_prior", TOGGLE_DEACTIVATE); group5->end(); From 1ea73786e4a16cd2cab2fedc331d8bd28340f90b Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 7 Jan 2026 14:29:03 +0000 Subject: [PATCH 170/189] use pytorh 2.9 in relion conda environment to use RTX50xx and Blackwell GPUs --- CMakeLists.txt | 2 +- environment.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b9fc07ee..675e75f47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -541,7 +541,7 @@ endif(JPEG_FOUND) # -----------------------------------------------------------------PYTHON DEPENDENCIES-- -set(CONDA_ENV_NAME "relion-5.0" CACHE STRING "The Conda environment name") +set(CONDA_ENV_NAME "relion-5.1" CACHE STRING "The Conda environment name") include(${CMAKE_SOURCE_DIR}/cmake/FindCondaPython.cmake) list(APPEND RELION_PYTHON_WRAPPERS diff --git a/environment.yml b/environment.yml index f88abba9f..aba0683c9 100644 --- a/environment.yml +++ b/environment.yml @@ -1,4 +1,4 @@ -name: relion-5.0 +name: relion-5.1 channels: - conda-forge dependencies: @@ -6,8 +6,8 @@ dependencies: - python=3.10 - setuptools=59.5.0 - pip: - - torch==2.0.1 - - torchvision==0.15.2 + - torch==2.9.0 + - torchvision==0.24 - tqdm==4.65.0 - mrcfile==1.4.3 - starfile>=0.5.6 From 4a957457590c9385b6d3aa2b15a26853e88792ab Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 7 Jan 2026 14:29:18 +0000 Subject: [PATCH 171/189] shorter name in titlebar of main gui --- src/apps/maingui.cpp | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/apps/maingui.cpp b/src/apps/maingui.cpp index 74caacb03..01305425f 100644 --- a/src/apps/maingui.cpp +++ b/src/apps/maingui.cpp @@ -35,27 +35,14 @@ int main(int argc, char *argv[]) #define _MAX_PATH 200 char my_dir[_MAX_PATH]; - char short_dir[49]; + char short_dir[39]; char* res = getcwd(my_dir, _MAX_PATH); - // Get last 45 characters of my_dir to fit in titlebar of window - if (strlen(my_dir) > 45) - { - short_dir[0]=short_dir[1]=short_dir[2]='.'; - int j = 3; - for (int i = strlen(my_dir)-45; i < strlen(my_dir); i++, j++) - { - short_dir[j] = my_dir[i]; - } - short_dir[j] = '\0'; - } - else - { - int i; - for (i = 0; i < strlen(my_dir); i++) - short_dir[i] = my_dir[i]; - short_dir[i] = '\0'; - } + std::string s(res); + unsigned long pos = s.find_last_of('/'); + std::string basename = (pos == std::string::npos) ? s : s.substr(pos + 1); + if (basename.size() > 35) + basename = basename.substr(basename.size() - 35); char titletext[256]; snprintf(titletext, 256, "RELION-%s", g_RELION_VERSION); @@ -64,7 +51,7 @@ int main(int argc, char *argv[]) #endif strcat(titletext,": "); - strcat (titletext, short_dir); + strcat (titletext, basename.c_str()); try { From 30f3dac8143f797faec373930ee396ede3cfe42e Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 8 Jan 2026 11:13:31 +0000 Subject: [PATCH 172/189] new image of dynein-dynaction for the initial screen of 5.1 --- src/gui_background.xpm | 1176 ++++++++++++++++++++-------------------- 1 file changed, 588 insertions(+), 588 deletions(-) diff --git a/src/gui_background.xpm b/src/gui_background.xpm index a13604579..b93217159 100644 --- a/src/gui_background.xpm +++ b/src/gui_background.xpm @@ -2,593 +2,593 @@ static const char *gui_background[] = { /* columns rows colors chars-per-pixel */ "598 332 256 2 ", -" c #010101", -". c #09090A", -"X c #100F0F", -"o c #0D0814", -"O c #120C1A", -"+ c #171618", -"@ c #29121C", -"# c #1E201F", -"$ c #170D26", -"% c #1C1427", -"& c #1E1132", -"* c #251B28", -"= c #371927", -"- c #38152C", -"; c #251738", -": c #282528", -"> c #35252C", -", c #2C2436", -"< c #362A36", -"1 c #393639", -"2 c #50172E", -"3 c #5B1530", -"4 c #701236", -"5 c #472837", -"6 c #4D2D3C", -"7 c #2B1946", -"8 c #331D53", -"9 c #362748", -"0 c #3C3445", -"q c #3B2557", -"w c #1F2761", -"e c #3D2363", -"r c #463947", -"t c #583747", -"y c #4D2E50", -"u c #463757", -"i c #752147", -"p c #772949", -"a c #6E344E", -"s c #442969", -"d c #4B3567", -"f c #50376F", -"g c #4A2A77", -"h c #543879", -"j c #3E4242", -"k c #484649", -"l c #504551", -"z c #564956", -"x c #585659", -"c c #674757", -"v c #6E4C5C", -"b c #574768", -"n c #5D5764", -"m c #5B4477", -"M c #675966", -"N c #71526D", -"B c #685777", -"V c #5F6263", -"C c #686669", -"Z c #706770", -"A c #766976", -"S c #787679", -"D c #910538", -"F c #A8013D", -"G c #921746", -"H c #B80345", -"J c #AD134E", -"K c #8E2E55", -"L c #AD275A", -"P c #B71F60", -"I c #953A63", -"U c #B13568", -"Y c #C70249", -"T c #D30B55", -"R c #E80356", -"E c #F30259", -"W c #FD005D", -"Q c #E4125E", -"! c #C4225E", -"~ c #D51963", -"^ c #FF0164", -"/ c #FF006B", -"( c #FC0A68", -") c #E91065", -"_ c #FA156B", -"` c #FF0B73", -"' c #FF1877", -"] c #CF306F", -"[ c #E92D76", -"{ c #FC257A", -"} c #974769", -"| c #924F6B", -" . c #8E536E", -".. c #AB4A72", -"X. c #B84877", -"o. c #A75679", -"O. c #886878", -"+. c #906A7C", -"@. c #C7477A", -"#. c #D5487C", -"$. c #E1467E", -"%. c #80817F", -"&. c #03398C", -"*. c #083D8D", -"=. c #073E91", -"-. c #512D87", -";. c #593787", -":. c #5C3595", -">. c #623C98", -",. c #5F34A0", -"<. c #663BA6", -"1. c #6E3DB7", -"2. c #144996", -"3. c #2E5DA1", -"4. c #654888", -"5. c #6F558B", -"6. c #694797", -"7. c #765898", -"8. c #59609E", -"9. c #7A668E", -"0. c #7E7A83", -"q. c #734BA9", -"w. c #7249AC", -"e. c #7956A7", -"r. c #7648B8", -"t. c #733FC1", -"y. c #7B47C7", -"u. c #7E47D0", -"i. c #FB2082", -"p. c #FF2885", -"a. c #EC3A85", -"s. c #FD3789", -"d. c #FF3A95", -"f. c #B55884", -"g. c #877886", -"h. c #977788", -"j. c #8C7092", -"k. c #AF6F8E", -"l. c #835AB5", -"z. c #8768A7", -"x. c #8E70AF", -"c. c #9877B8", -"v. c #A26FA2", -"b. c #C75788", -"n. c #D5518A", -"m. c #E84987", -"M. c #F8488B", -"N. c #EC528D", -"B. c #FE4798", -"V. c #E85897", -"C. c #FB559A", -"Z. c #CD6A97", -"A. c #E8649C", -"S. c #FF4BA3", -"D. c #E95CA4", -"F. c #FE58A8", -"G. c #FF5CB2", -"H. c #D477A6", -"J. c #FA66AA", -"K. c #E971AA", -"L. c #FF68B7", -"P. c #FC76B9", -"I. c #8757C8", -"U. c #864FD5", -"Y. c #8A55D7", -"T. c #976BCD", -"R. c #A47AD3", -"E. c #9153ED", -"W. c #9C65E7", -"Q. c #AB74F0", -"!. c #DD79C0", -"~. c #FE6EC1", -"^. c #FF79C6", -"/. c #6788B9", -"(. c #7A97C2", -"). c #878689", -"_. c #94848D", -"`. c #8D8B93", -"'. c #978896", -"]. c #979699", -"[. c #A78798", -"{. c #AC8D9D", -"}. c #9987AB", -"|. c #9D9CA2", -" X c #A799A6", -".X c #AF92AF", -"XX c #9EA2A7", -"oX c #A6A5AA", -"OX c #B3A4AE", -"+X c #ADACB2", -"@X c #B6A9B6", -"#X c #B3B2B6", -"$X c #B6B5BA", -"%X c #BABABD", -"&X c #C3849F", -"*X c #D08FB1", -"=X c #EC8ABA", -"-X c #CAA9BB", -";X c #B18FCF", -":X c #90A7CB", -">X c #B6AACD", -",X c #BCB4C6", -" c #B4B4C4", +", c #CCCCDC", +"< c #ECECF9", +"1 c #A4A4AD", +"2 c #C4C4CC", +"3 c #BCBCC4", +"4 c #B4B4BC", +"5 c #D4D4DC", +"6 c #CCCCD4", +"7 c #F4F4FC", +"8 c #DCDCE4", +"9 c #04147E", +"0 c #676A75", +"q c #989BA5", +"w c #051747", +"e c #B4BCD4", +"r c #7F8490", +"t c #042C89", +"y c #8C9098", +"u c #A9ACB2", +"i c #2B559F", +"p c #4467A6", +"a c #C3CCDC", +"s c #134394", +"d c #5878AE", +"f c #6C88B6", +"g c #8098BE", +"h c #585B60", +"j c #93A8C6", +"k c #787C82", +"l c #727477", +"z c #AABED9", +"x c #A7B7CC", +"c c #9BA6B5", +"v c #B8C4D4", +"b c #D0DCEB", +"n c #CCD4DD", +"m c #BCC4CC", +"M c #B4BCC4", +"N c #C4CCD4", +"B c #D5E4F2", +"V c #C4D4E1", +"C c #BCD4E4", +"Z c #E0EEF6", +"A c #9BA5AA", +"S c #ACB4B8", +"D c #ECF4F7", +"F c #244851", +"G c #68A4B2", +"H c #2F5B65", +"J c #041417", +"K c #388896", +"L c #37494C", +"P c #BCCCCE", +"I c #155C60", +"U c #236C6D", +"Y c #346465", +"T c #508C8D", +"R c #2C6464", +"E c #306C6C", +"W c #ACD4D4", +"Q c #B1CCCC", +"! c #CFE9E9", +"~ c #C4DCDC", +"^ c #C2D4D4", +"/ c #C4CCCC", +"( c #BCC4C4", +") c #B4BCBC", +"_ c #F3FCFC", +"` c #D4DCDC", +"' c #CCD4D4", +"] c #DCE4E4", +"[ c #8CC8C6", +"{ c #084C48", +"} c #09544F", +"| c #186F69", +" . c #3BABA4", +".. c #357470", +"X. c #285451", +"o. c #7DBDBA", +"O. c #1A645E", +"+. c #29867E", +"@. c #31938B", +"#. c #174440", +"$. c #163A37", +"%. c #265C58", +"&. c #39847D", +"*. c #367C76", +"=. c #458A85", +"-. c #72B4AF", +";. c #A2BCBA", +":. c #0A5E56", +">. c #0A3E38", +",. c #06211E", +"<. c #0C312D", +"1. c #194C47", +"2. c #1C544E", +"3. c #2A7C73", +"4. c #24645D", +"5. c #2B746C", +"6. c #3A9C92", +"7. c #3B8C83", +"8. c #44948B", +"9. c #579C94", +"0. c #5CA49D", +"q. c #58948E", +"w. c #759C98", +"e. c #86A29F", +"r. c #1C5C54", +"t. c #246C63", +"y. c #43A497", +"u. c #2D6C64", +"i. c #499C92", +"p. c #4FA499", +"a. c #57AEA2", +"s. c #68A8A0", +"d. c #48746E", +"f. c #557C77", +"g. c #658B86", +"h. c #294C47", +"j. c #365C56", +"k. c #3C645E", +"l. c #4B7C74", +"z. c #436C66", +"x. c #35544F", +"c. c #5B8C84", +"v. c #56847D", +"b. c #6B9C94", +"n. c #66948D", +"m. c #77A49D", +"M. c #30645B", +"N. c #26433D", +"B. c #7BACA2", +"V. c #85ACA4", +"C. c #95BCB4", +"Z. c #AFC4C0", +"A. c #ACBCB9", +"S. c #89B4AA", +"D. c #C0E4DC", +"F. c #B6D4CD", +"G. c #46635C", +"H. c #8BBCB0", +"J. c #55766D", +"K. c #9CC4B9", +"L. c #A6CBC1", +"P. c #BADCD3", +"I. c #77978E", +"U. c #ACD4C8", +"Y. c #96B8AD", +"T. c #75A494", +"R. c #B4CCC4", +"E. c #C4DCD4", +"W. c #CCDCD5", +"Q. c #BCCCC4", +"!. c #C7D4CC", +"~. c #F3FCF4", +"^. c #040C04", +"/. c #C4CCC4", +"(. c #D4DCD4", +"). c #ECF4EC", +"_. c #E4ECE4", +"`. c #DCE4DC", +"'. c #4F504F", +"]. c #F4FCEC", +"[. c #656662", +"{. c #6F7241", +"}. c #EDEED5", +"|. c #0C0C04", +" X c #FCFCEC", +".X c #B4B4A9", +"XX c #CCCCC4", +"oX c #BCBCB4", +"OX c #D4D4CC", +"+X c #FCFCF4", +"@X c #F4F4EC", +"#X c #ECECE4", +"$X c #E4E4DC", +"%X c #DACC1B", +"&X c #DED23A", +"*X c #E6E2B1", +"=X c #E1D76F", +"-X c #A8992A", +";X c #A19F90", +":X c #C7B229", +">X c #927D10", +",X c #6D5A0D", +"XyX>X3XQ.d.d.d.d.d.S.G.G.~.~.^.7XtX7XtX7X7X^.^.~.~.G.~.D.].DXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+X=XwXqXwXwXxXmXbXbXzXxX0X,X;X}.}.A U { s.s.s.s.s.M.M.C.C.$.*X*X=X6XwXwXwXwXmXwXVXVXVXSXKXUXUXKXwXwX=XA.&X$.o.vXGXPXGXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX .R E _ ' { d.B.C.G.J.L.L.J.L.G.G.F.L.=X*X-X@X>X.X;X3X;X;X;XS.i.d.d.S.S.S.S.G.G.~.^.7XtX7X8X8X7X4X~.G.G.~.G.D.m JXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX_.wX6X6XwXwXmXmXbXbXvXcXjX,X>XXXz.K $.$.s.{ s.M.M.C.C.C.C.A.$.=X=X6X6X6X6X6XwXwX6XqXqXaXVXKXKXKXmX6X*Xk.o.k.o.C PXGXPXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXhX X X{.O.m.C.F.J.L.P.=XP.^.^.^.L.P.4X5X-X@X.X}.x.;X;X;X;X.XS.d.d.d.d.S.S.G.G.~.~.^.tXtXtX8X7X7X^.~.~.G.G.G.D.}.JXJXJXJXJXJXJXGXoXnXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXJXJXJXJXPXGXGX{.6X6X5XwXwXxXmXnXbXbXMX0X0X0X,X>X9.} $.[ M.M.s.B.C.C.C.C.A.A.=X6X=X6X6X-X=XwX5X6X6XwXwXwXVXVXKXVXwX*X%.kXkXvXGXJXGXGXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX$Xo.A.J.P.=X6X4X=X4X6X4X4X6X5X-X.Xc.j.7.9.z.c.c..X3XF.d.d.d.S.S.S.G.~.~.^.^.tXpXtXtX8X7X^.~.G.G.G.G.f.NXJXJXJXJXJXJXGX .P L +.OXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHX@Xh.| gXJXJXJXJXGXGXPXoX=X6X=X*X5XxXmXnXbXNXNXNXMXMXyX0X>X9...n.m.{ M.B.C.C.A.C.C.$.A.=X=X=X=X*X*X*X*X=X=X5X6X6X6XwXaXVXwX{.nXKXJXJXJXJXPXGXPXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX[.H.=X=X6X6X6X6X6X8X8X6X5X.X Xx.9.4.5.7.7.7.j.>X5XF.d.S.S.S.G.G.G.~.~.^.tXtXaXpXqX8X8X^.~.G.G.G.G.h.JXJXJXJXJXJXFX .R ^ / / ) } X>X>X.Xv.{ s.s.M.C.C.A.A.C.C.$.#.&X#.&X+.k.h.o.&Xo.&X=X=X=X=X=X=XwXoXGXKXJXJXJXJXJXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXHX'.K.=X=X=X6XqXqX6X=X6X=X*X.Xj.m b f h h m 4.m 4..XwXP.G.S.S.S.G.G.G.~.7X8X0XyX0XyX0XwX5X4XK.Z.k.f.&XGXJXJXJXJXJXJXJX+XG H H T ( ( / / / / R .GXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXO.p ] ' p.i.p.{ { ! K K Z FXGXPXGXGXGXPX$X=X6X5X0X-X-X9XmXPXLXFXMX9X9X>X;X@X;XN $.s.$.M.M.C.C.C.$.$.$.$.#.&X+.O.C O.v Z O.k.&X=X=X=X=X$.=XK.%.nXJXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXzX+.C.J.=X4X4X6XqX8X8X4X=X=Xv.j.n n c y r y r r f m v.wX4XL.F.G.G.G.G.^.qXaX;XT.T.T.;X;X-X+.).|.$XzXJXLXJXJXJXJXJXJXJXGX9XK D D F T ^ ( ( ( / / J a OXHXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+Xa.i.i.i.i.' p.' { { P 6 gXJXJXGXGXPXGXPXGXjXoXoXOXg.g.OXxXFXBXNX9X0X9X>X@X>X>X .$.{ [ { { M.M.$.$.$.{ $.#.@.t V Z C C v C o.#.&X$.=X=X=X=X=X=Xk.%XPXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHX.X@.M.C.J.P.^.P.=X4X6X=XK.K.k. .B ).+.I i 2 5 5 5 - u z.4X4XP.P.J.L.L.L.tXZXR.T.I.T.T.;X;X/.GXHXJXJXJXJXJXJXJXJXJXJXJXOX| T ~ ~ G D J Q _ ` ` ( ( / ` ! cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJX .L ' p.i.{ i.' i.' { { { _ U O.vXPXGXGXPXGXGXPXGXGXJXJXvX).|.xXxXlX9X0X9X>X>X>X9X} $.[ ] ] ] $.[ $.] ] ] ] ! U U S C x O.S O.+.o.&XA.&X$.A.A.A.=XA.A._.GXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXvXx ~ { s.B.C.J.P.P.P.=XP.P.H.Z.v ].kXJXh.G p 6 2 2 2 5 q 5.H.4X^.P.L.J.G.=XwXR.I.I.I.T.1X1X'.%XGXJXJXJXJXJXJXJXJXJXJXJXJX> G ! ! ] G 4 J ~ _ ' _ _ / ` / ` {.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX X] P i.i.i.{ p.p.p.p.p.{ { _ [ ! t HXPXGXGXPXGXGXGXPXJXJXJXJX].9X9X0X0X,X>X3X>X>X>X| [ @.] U ! ] ] ] ] ] ! U L L K O.GXkX].S C O.o.o.k.#.Z.Z.Z.Z.N.A.C.X.v +XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX[.{.} ( ] ' p.d.B.F.G.L.J.P.J.J.D.C.n.k.kXJXBXO.a 4 3 5 = = u z.c.v.*XP.L.G.L.3X;XT.T.T.T.R.;Xj.%XJXJXJXJXJXJXJXJXJXJXJXJXJXJXM L ] ] U G G J [ { { ' ' ' ` ` ` o.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXJXJXO.d.L p.{ p.p.{ p.p.s.p.s.{ { [ L O.HXJXJXJXJXHXHXPXGXJXJXJXJXGX|.>X,X>X>X,XyX0X9X>X .#.@.@.U ......I ..U L L K .].GXGXzX-XS C C C | | O.| O.v | | o.n.Z.C.b.O.v -XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXnX..^ ^ / ( _ [ p.s.M.B.S.S.F.F.J.F.F.d.p.] t XXJXHX9XA a 3 2 < b x.;X;X}.H.J.D.c.c.T.T.T.x.T.c.0.vXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX).I U ] U G G ! a.s.{ { ' i.' ' ` N LXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX .d.a.p.p.p.s.s.p.s.{ s.{ s.[ [ @.].GXJXJXJXJXPXHXPXGXJXJXJXJXGXJX+X}.>X>X>XcXjXgX@XN #.#.....I K v K a v v v _.nXPXGX|._.k.%.O.C V v v x v v v V v v N.N.M.M.M.M.@.OXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBX@.^ E W ^ ( _ J { p.d.M.B.B.B.B.B.S.d.' ' ` ) a V FXGXJXoXr + r 9.}..X;X;X}.c.c.c.c.x.x.z.z.9. XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+X| X.U K G U [ a.s.d.s.p.i.' ' ' c JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXk.s.B.s.p.p.s.s.s.s.s.s.s.$.[ @.o.oXJXJXJXGXJXPXHXGXHXJXJXJXJXJXJXJXhX).}.>X>XkXX>X.Xc.x.z.z.7.5.5.).cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX } V.J.J.J.P.L.G.F.S.S.S.S.V.{.JXJXJXJXJXJXJX].OXwX0X>X@X.Xk.h. .} o.U f.n.V.F.F.G.L.J.F.S.M.[ ....j.FXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX#X].cX#XPXPXPXPXPXnXXXoX+XXX+XFXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXgX].PXPXPX].].GXHXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFXNXHXJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX.XU J ~ ) ) OXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX#X+.+.C.J.J.K.K.=XK.H.H.H.K.H.&X*X*XH.P.P.^.^.^.6X*X#XGXHXJXJXJXJXJX+Xh.+._.OX Xn c {.-XKXIXIXUXUXUXIXIXIXmXZ HXGXJXJXJX{.D.G.F.S.F.S.S.S.M.M.n.n.n.V.V.V.H.H.h.c a v 1 `.+.HXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX[.'.z h.4X4XH.b. .v I a a 6 5 v 2 3 I U ] U I 3 a *XlXJXJXJXFXA h. .t 5 K f.P.=X4X6X7X7X^.^.^.^.P.K.'.S JXJXJXJXJXJX]..X;X;X;X.X.X{.{.k.h.j.f.f.f.b.A.V.V.J.L.G.G.S.d.s.{ i.) +.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX].PX#XPXPXPXPXPXPXPXPXPXPXPXBX].|.JXJXJXJXJXJXJXJXJXJXJXJXGXGXJXJXJXJXJXJXJXJXGX$X+XPXPXPXPXPXcXS bXKXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNXJXJXlXOXh.'. X,XcXJXJXlXk.o.I L J J L U k.{.,XBXJXPXPXJXJXJXJXJXJXJXJXJXJXJXJXJXHXBXj.~ / ` / / / I BXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX-Xc m.C.C.J.J.J.K.K.K.=X=XK.*X*X=X*X*X=X=X4X7X7X8X8X6XmX#X+XHXJXJXJXJXJX`.Z 9X@XOXXX`.6 N {.wXSXSXSXSXAXSXSXKXKX].%XPXJXGX[.F.G.G.G.G.F.F.B.M.b.X.I I .A.A.K.K.K.=X5Xh. .b.F.m.].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXoXP.6Xh.qXP.G.D.Z.f.I a a t i 5 k l 6 5 2 3 K K a f.K.Z.9XJXLX_.c.j.N 5 @.Z.*X8XqXqXaXaXaXqX8X8X4X5X;X,X@XXJXJXJXJXFX%.jX,X@X.X}.`./.t c }.iXsXMXpXCXVXCXCXxXxX|.BXLXv.G.G.G.G.G.L.F.F.F.C...K a 6 5 o.Z.H.H.H.P.P.L.F.d.p.{ } JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXh.=Xk.AX8X~.S.S.V.Z.f.I a a a = 1 6 j 6 c a 6 6 c .+.Z.k.+X|.x.z.j.c n.P.4XqXaXVXAXVXVXSXVXpXpXwX>X>XMXCX{.nXJXJX].5.7.9.x.}.}.#X 6 o.f.Z.J.P.L.F.d.` ( E P '.LXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+X+.h.CXaX^.~.G.S.F.A.Z.f.I a p > # 1 1 6 a c v c c v v .b.a 7.l.z.9.N L.7XqXaXSXIXIXIXUXIXIXSXZXiXyXyXZXPXKXC ,XS 5.4.5.5.9.`.|.>XXoXoX|.].h.k.Z.Z.H.H.A.J.J.J.S.d.i.` / / f.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXoXPXPXPXPXPXPXPXPXPXPXPXPXcXXXcXX0X0X 1 6 k c c k z c M .o.a T.z.z.5.b t H.pXVXSXIXIXSXAXSXSXSXZXZXuXiXCXIXIXM 6.4.;.m h b B 9.x.}.;X;Xc.x.}.}.g.A S g.{.&X*X=XH.A.L.J.F.d.p.i./ / f.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXoXGXPXPXPXPXPXPXPXPXPXPXPXPXS %.#XGXJXJXJXJXJXGX].PXPXPXPXPXPXPXPXPXPXPXGXoXkXPX].BXGXPXPXPXPXPXPXPXPXPXoXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXLXFXh./ / / / / / / / ` / / ` / / / / / / / / / / / / ` / / / / / / / K cXJXJXJXJXJXJXJXvX .R R E E W W W W E ^ / / / ` ] h.GXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX[.d.S.F.G.L.J.J.=XJ.=X=X*X&X-X-X9XjX,XoX X>XwXVXSXVXVXCXCXCXZXZXsXiX;Xj.A S XX}.e.}.9.}.`.}.}.j.: 7.c.}.>X@X@X|.`.+.0.m m.G.S.G.G.G.L.G.J.J.J.F.N. .C v v l 5 | K.P.P.L.L.M.[ ~ L k.#XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXg.CXIXSXaXtXtX7X^.4X6X6X*X+.v a ).A 5 1 5 k k l l z z v N v 5.l.c.j.B u < r *XiXZXZXZXdXdXdXAXAXZXiXsXiXZXSXIXA ;.m h f d b 4.5.e.l.z.e.e.5.9.b x z l Z '..X*X-X*XK.K.J.J.S.d.i.` / o.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXcXJXJXJXJXJXJXGXhXkXPXPXPXPXPXPXPXPXPXPXPXPXnXXXGXJXJXJXJXJXJX$XcXPXPXPXPXPXPXPXPXPXJXPXJXJX%X].vX).FXFXGXPXPXPXPXPXPXPXGXoXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX .( / / / / / / ` / / / / / / / / / / / / / / ` ` / / / / / / / / / .LXJXJXJXJXJXJXJXvXI R R R E E E E E ^ ^ / / / / ( o. XDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXh.B.G.G.J.L.J.P.J.K.=X&X&X_._.{.oXOX}.x.j.j.>X0XVXAXCXmXMXZXLXLXZXZXZXLXZX1XZ }.}.}.9.}.x.}.}.}.z d 7.z.9.M r 0.S Z B x N.S.G.G.G.G.G.J.L.J.F.F.C...l N M M : o.K.P.K.V.V.m.] I .c.}.].kXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXgX'.pXSXIXSXAXdXpXtX7X8XqXaX-X+.v C %.t t 6 j k l l k l z z t - b h.h. .v r , 5.z.;XrXrXuXuXpXuXuXpXsXuXsXuXsXSXSXn m 4.m h h f ;.4.6.q.6.6.;.m ).XX+XXX).k A ].OX-X*X*X=XJ.A.C.d.p.i.` I NXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNX`.).].].oXkXBXkXoX).PXPXPXPXPXPXPXPXPXPXPXPXPXJX`.FXJXJXJXJXJXJX].PXPXPXPXPXGXPXPXPXPXPXFXFXFXFXBX_.).%XFXFXPXPXPXPXPXPXPXPX#XnXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXcXK / / / / / / / / / / / / / / / / / / / / / / / / / ` / / / / / / / ) zXJXJXJXJXJXJXJXJXh.Y Y Y R E E E E W ^ ^ ^ / / / / [ f. XDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXh.S.G.J.J.J.P.J.K.K.Z.[.&X%.O.]. XS ).S 0.9.z.;X0XCXMXZXMXZXZXLXIXSXSXSXSXiX}.m 9.e.x.9.x.}.}.x.0.1 b n .XAXAX9.C M n l V.F.G.S.F.G.L.G.G.G.J.F.C.n.c : Z v 5 K.A.D.b.b.f...I v z 9.;XyX}.B V < y M h.O.N c z z 0 z.;X1XrXuXrXrXuXrXrXuXuXuXsXuXsXAXyX0 4.6.6.4.h h ;.;.>.>.;.;.m `.JXJXJXJXJXXXj g.oX9X-X*X*X=XJ.F.S.d.p.' a.#XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXhXXXJXPXPXPXcX+X+XhXFXPXPXFXFXGXFXPXPXPXPXPXPXPXGXGXXXoXJXJXJXJXJXJX].GXGXGXFXGXGXGXGXGXGXGXFXBXBXBXFXFX#Xk vXFXPXPXPXPXPXPXPXPXPXoXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJX", -"LXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX.XU / / / / / / / / / / / / / / / / / / / / / / / / ` / ` / ` / / / / / / n.JXJXJXJXJXJXJXJX%XL Y Y R R R E E W W ^ W ^ / ^ / / / ) ..'.GXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX .F.G.J.J.P.J.J.K.A.Z.Z.h.+.V ].].JXJXJXJX`.5.x.>X,X0XyXZXZXLXZXLXIXSXZXZXZX>X7.m 9.e.x.x.}.}.9.b r .XSXSXIXLXlXn n l q d } V.A.G.L.G.L.G.G.F.F.C.B.p j Z v < a | | | N N a r < 0 5.x.;X1XyX>Xl z BXJXJXJXJXJXJXJXJXJXJXJXcX|.>XCX'.pXSXZXIXIXSXSXIXIXSXIXSXxXOX X'._.k 6 x l x z z k Z j.z.R.c.j.S Z N z u t d 9.R.1XeXrXrXuXrXuXuXuXpXpXsXuXrXeXz h 4.6.6.>.>.h h ;.;.;.h m C JXJXJXJXJXJXvX0 0. XhXxXwX5X=X=XP.J.S.p.i.` ].LXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXhXX0X9X@Xh.IXSXZXIXSXSXSXUXUXPXCX>X0XgX-X-X-X%.6 k z z k S >Xc.z.z.7.B M M M c z 0 5.e.;X2XeXrXuXuXuXuXtXuXdXuXpXuXQ.m m m ;.4.>.>.;.g f -.h ;.c /.JXJXJXJXJXJXJXJXk 9. X9XxXmXwXwX6X=XP.S.d.i.` h.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXkXXXPXPXPXPXPXPXPXPXPXJXPXPXPXPXFXFXFXFXPXPXGXGXFXFXnX%.FXJXJXJXJXJXJXkXGXGXPXFXGXGXPXGXPXGXGXGXFXBXnXvXcXvXnXBXBXFXPXPXPXPXPXPXPXPXPXPXS FXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHX| / / ^ / ^ ^ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / ( / {.JXPXGXGXPXPXGXGX| F F F Y Y R R R E E W W ^ ^ ^ / / / / / / / / L h.GXLXJXJXJXJXJXJXJXJXJXJXJXJX-XZ.L.P.L.P.P.P.P.P.=X=X&Xk.M 0 $XJXJXJXJXJXJXJX`.m 5.5.5.5.}.MXZXZXZXZXiXuXyX1X1X1X>Xm }.B B yXAXAXAXdXZXAXSXZXMX>Xx , 0 m 5.e.;X}.B f.F.G.S.S.d.B.a.U < C C A r r 9 < 9 9 q u d m 5.5.z.x.;X;XyXMXyXA 9XJXJXJXJXJXJXGXZ 9.}.}.}.'.j.n LXZXZXZXLXSXSXPXLXZXyX0XMXMXVXCXmX0X{.x j l M .Xx.z.7.4.m b b N n b y u z.c.R.1XeXrXrXuXuXpXdXuXdXdXsXrX6.h 5.>.m >.;.h h g e e q b XXJXJXJXJXJXJXJXJXNXV B j.>XjXxXmXwXwX8XP.F.d.p.i.b.LXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXKXzX%.PXPXPXPXPXPXPXPXPXPXGXGXPXPXGXGXFXFXGXPXPXGXGXFXBX%.FXGXJXJXJXJXJXJXJX+XcXGXFXJXPXJXJXFXPXFXFXFXFXnXnXcXcXnXnXnXnXFXGXGXPXPXPXPXPXPXPXhXV JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXOX) / / / ^ / ^ / / / / / / / / / / / / / ` / / / / / / / / / / / / / / / ^ ~ %XJXGXGXPXFXGXKX#XT F F F F Y R R R R W W W W W ^ ^ / / / / / / / / ` U ,XJXJXJXJXJXJXJXJXJXJXJXJX$XS.L.P.P.^.4X4X4X=X4X=X&Xk.M k BXJXJXJXJXJXJXJXJX`.B m m 4.7.yXZXZXZXiXuXuX2X1X1XeXyXj.M MXSXSXAXZXdXdXsXZXAXZXiX>X}.1 q f 6.e.c.;X>X}.N m.S.d.d.d.] L 1 Z 9.0.x n u u 0 9 u d h m 4.4.4.e.T.;XyXMXMXlXS nXJXJXJXJXJX0.9.9.9.9.5.B n x j.ZXiXMXZXZXZXZXMXyXyXiXMXCXAXAXwXwX5XOX5 t '.9.5.4.m f u u u b d u , m T.R.1XQ.2XrXuXsXsXuXdXdXsXuXrXT.d m 5.5.>.h h g s e q u A lXJXJXJXJXJXJXJXJXJXhX9.5.}.@XcXCXFXKXVXqX^.F.B.p.i.{.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXcX/.PXPXPXPXPXPXPXPXPXPXPXGXGXGXGXGXGXFXFXFXGXPXGXFXFX|.hXGXJXJXJXJXJXJXJXJXJX#XgXFXFXJXPXGXPXGXPXFXFXFXnXnXcXcXcXcXvXvXvXFXFXGXGXGXPXPXPXPXPXgX`.%XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLX} / / / ^ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / P } +.GXJXGXPXFXPXPXFXU H D D F F Y Y R R E E W W ^ ^ ^ ^ ^ / / / ^ / / / / / ] j.GXJXJXJXJXJXJXJXJXJXGX0XF.P.^.^.P.=X4X4X4X6X=X&Xh.Z k cXJXJXJXJXJXJXJXJXJXXXl b b 4.>XZXZXiXuXrX2X1X1X1X;X>X XIXIXSXAXAXdXsXuXpXsXZXZXiXyX;X`.9 f 6.x.;X;X.X}.c.a n.d.B.a.U p n A A 0.'.A z u r y d d h ;.4.4.4.l.;X>XyXMXMXxXlX).JXJXJXJXoX0.b B m B B n b l l ZXrXuXuXiXiXyX1X1XiXuXyXiXiXyXaXCXwX0XZ : b B m d f d q u y u u 9 m 7.T.Q.1X1XeXrXuXsXZXdXZXdXuX2XT.y.m m z.;.m g s e g n |.zXJXJXJXJXJXJXJXJXJXJXJXoXb 9.}.9XCXVXSXVXVXqX4XG.G.d.p.}.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX].JXPXPXPXPXPXPXPXPXPXPXPXGXPXPXGXGXGXFXFXFXFXBXGXFXnX).PXPXJXJXJXJXJXJXJXJXJXJX%XXXFXFXPXGXPXGXPXGXFXFXnXnXnXcXcXcXcXcXcXvXFXBXFXFXFXPXPXPXPXPXPX#XnXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX.X~ / / / / ^ / / / / / / / / / / / / ` / / ` / / / / / / / / / / / / ) O.XLXZXrX1X2X1XQ.R.T.T.z.9XIXSXSXAXsXsXuXuXuXuXuXiXiX1X>X>X0.9 m T.;X;Xc.c.x.A 5 X.a.a.U 5 B 9.9.`..X{.[.B r 9 q u b m h h 7.T.;X1XyXyXmXxXzX$X$XJXJXvX).9.b u d m b b 0 0 0 x.uX2XrX2X1X2X2X2X2XrX1XrX3XyXZXIXKXmXOX< A 9.u s d u 9 7 9 9 0 u z.x.R.Q.eXQ.eXuXsXZXZXZXAXAXQ.I.r.I.W.7.;.;.f s e s h j.`.hXJXJXcXlXJXJXJXJXJXJXJX`.m 9.+XxXCXVXVXVXVXpX7X~.L.G.f.MXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXhXJXPXPXPXPXPXPXPXPXPXPXPXPXGXGXGXGXGXGXGXFXFXFXvXvXBXoXvXPXGXJXJXJXJXJXJXJXJXJXJXJX#X|.GXPXPXPXPXPXGXPXFXBXC oX%XhXOXX>X7.g u u b b b 9.9.B n y K 3 6 z B 9. X.X[. .O.N .N 9 d f d 4.l.R.;X3XyXMXbXmXxX-X$XGXJX0.0.B d 9 q b b 0 0 9 0 0 rXrX2XeXQ.Q.eX2X2X2X2X2XuXiXZXUXUXKXlXk 5.5.B 9 q s q ; 7 9 7 c.;XR.1XeXeXQ.eXpXAXSXAXSXSXiXr.w.w.r.T.T.e.f q q y g f B .Xk.j.nXC C %XJXJXJXJXJXgX9.B ].-XxXaXaXpXqXaXpXtX7X~.L.j.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXzXGXPXPXPXPXPXPXPXPXPXPXGXPXPXGXGXPXGXPXGXFXFXFXFXFXFXFXoXJXGXGXJXJXJXJXJXJXJXJXJXJXJXJXC GXPXPXPXPXPXPXGXFXFX].FXPXPXPXoXS %XcXcXcXnXBXFXFXGXPXPXPXPXPX$XXXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXGXU / / / / / / / / / / / / / / / / / / / / / / / / / / / ` / / / / ^ T I JXJXJXJXJXJXGXPXPXBXL R Y H F F F D D D F Y T T T Q R Q E W ^ ^ / ^ ^ ^ ^ ^ / / / / P cXJXJXJXJXJXJXJXJXJXJXO.P.4X4X4X4X7X8XqX8XqX5X*Xk.Z x hXJXJXJXJXJXGX0.V NX.e.T.R.;X;XyXyXxXbXwXwX=XOX+XB 5.b u 0 0 l q 9 7 , , 9 5.rXrXrXeXeXQ.2X2X2X2X2XrXiXZXIXUXPXMXl m 5.7.0 9 9 9 , % 7 , c.;XQ.1XQ.eXQ.Q.R.eXiXiXiXyXr.<.<.<.y.I.Q.1X4.7 q e s g f 9..Xk.j u b M ].JXJXJXBXZ b 5.}.-XjXaXaXaXpXdXaXtX7X^.L.'.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXoXPXPXPXPXPXPXPXPXPXGXGXGXFXFXFXFXGXGXGXPXGXGXGXFXGXFXGX|.GXPXPXJXJXJXJXJXJXJXJXJXJXJXGXXXGXGXGXPXGXFXFXGXFXFX].GXGXPXPXPXBXC %.cXcXvXnXBXFXGXGXGXPXPXPXoXFXoXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX X) ^ / / / / / / / / / / / / ^ ^ / / / / / / / / / / / ` / / / / ( R T I oXJXJXJXJXJXPXGXGXo.R Y Y Y Y Y D D 4 D D F Y Y ! Y Y ! T Q ( ^ ^ ( ( ^ ^ ^ ^ / / / / {.JXJXJXJXJXJXJXJXJXJX|.K.^.4X7X7X8X8X8XqXqX6X*Xk.O.x 0.NXJXJXJXcXS V z x k.7X8XtXyX0X;Xu.T.I.Y.Y.T.Y.e.h c.uXuXrXrXrXQ.Q.Q.rXrXQ.rXeXrXiXyXMX>X7.4.4.4.B B M n 0 < A '.j.g.O.b '.}.B 0XUXUXUXSXVXVXc.l.l.l.I.T.R.R.1X0XyXxXmXmX-X,XXX9.b b d q 9 , , , : ; , , 9 ;XrXrXeXeXeXrX2X2X2XrXuXsXSXUXUXIXMXf h ;.5.m ; ; , ; + % ; ;XR.Q.Q.Q.Q.Q.T.I.t.t.l.r.r.<.<.<.y.T.Q.2X2XeXy e s s s f m j.n 0 b 5.5.B ).oX#Xx l 4.9. XX3X-X@X9XkXb k d b q 0 9 , % , , , , 9 0 5.rXrXrXrXrXrXQ.rXrXuXiXAXIXIXLXyXg s h ;.4.9 % , 9 ; ; % b R.R.W.W.W.E.Y.U.U.u.y.y.t.1.t.Y.Q.Q.rXuXuXdXiXz 8 8 q q d ; 0 Z g.}.|..X}._.A B Z j.}.;X,XMXmXAXSXAXAXAXaXtXH.9XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX`.PXPXPXPXPXPXPXPXPXPXPXGXBXvXzXkXzXnXnXGXFXGXFXFXPXPXPXPXPXPXPXoXJXJXJXJXJXJXJXJXJXJXJXJXJXXXGXFXFXFXFXFXFXFXFXgXFXPXPXPXPXPXPXPXPXPXvXnXBXFXGXGXPXJXPXPXPX].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX9X~ / / / / / / / / / ^ ^ W W E E E E E E E E E R R ^ / / / ` / / / ( ( _ ( ( ^ _.JXJXJXGXGXOXT Y Y Y F H F F F D D c OXJXJXJXJXJXBXC i J ! ) _ ' ' ' ' i.i.` ` i.X.cXJXJXJXJXJXJXJXJXJXJXzXk.7X8XtXtXaXaXmXjX$X].g.S C Z '.OXOXOX X).Z x M aXmXwXyXR.R.Y.Y.I.I.Y.T.R.Q.;X7.f d 9 w.Y.Y.W.W.E.Q.Q.Q.Q.uXuXuXiXsXyX;X;XT.;X;X;X+XOX{.&Xk. .u S g.].S +XIXUXUXIXLXCXyX,X}.z.7.l.e.r.e.e.e.l.T.c.x.j.|.GXBXj 0 0 q q 9 , , , * , , , , u d z.uXuXrXuXrXrXrX2XrXuXZXZXiXyX7.e s g ;.;.7 7 ; ; ; ; & ; h T.W.W.E.E.W.E.U.U.U.y.y.I.U.W.Q.Q.Q.uXsXAXyX9 8 q q y f d , 0 n 0.x.;X}.j.V x x B 9.}.}.>XyXMXSXSXIXSXdXtXh.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX`.PXPXPXPXPXPXPXPXPXPXGXFXnXzXkXhXoX).oXnXnXBXBXPXPXPXPXPXPXPXPX%XnXJXJXJXJXJXJXJXJXJXJXJXJX|.FXFXFXFXFXFXFXFXzX].PXPXPXPXPXPXPXPXPXPXvXFXFXGXGXGXJXJXPXPXPX|.BXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX'./ ^ / / ^ / / / / / ^ W W W R R Q Q ~ Q ~ Q E E R ^ / / / / / / ^ ` ' _ ` ( / +.PXJXJXPXPXoX! Y H Y F F H Y F F v gXGXJXJXJXJXJXJXvXM 4 L Q ' ' i.p.p.i.i.i.i.i.p. XJXJXJXJXJXJXJXJXJXJXJX_.8XtXaXaXVXVXBXxX%X|.`.S A C +.h.[.'.g.Z n k z mXCXMXeX1XR.R.Y.u.I.Y.W.R.1Xx.j.m s e 9 >.y.y.I.I.l.Y.T.Q.Q.rXyXsXsXyX;XR.T.R.}.;X.X9X0X5X=XZ.O.r z Z j.5.MXSXSXZXZXiX>X;Xx.7.5.7.6.q.6.;.;.4.h f l Z FXJXJXvX0.b 0 0 9 , , , : , , 9 9 u 4.N 5.yXuXuXuXuXQ.2X2XrXuXeX1Xe.q q d h 4.h 9 ; & ; & & ; O % ;.I.Y.E.E.E.E.E.E.U.U.Y.Y.W.Q.Q.eXuXuXdXsX;X; q q e s h 4.z., 0 k B C Z %.]./.k l k n z.c.;X>XZXIXUXSXAXwX+XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX`.PXPXPXPXPXPXPXPXPXPXGXBXcXkX+XS hXGXhX).].vXnXGXPXPXPXPXPXPXPXGX#XJXJXJXJXJXJXJXJXJXJXJXGXzXoXGXGXGXFXFXFXFX%.GXPXPXPXPXPXPXPXPXPXPXGXFXGXGXGXPXPXPXPXPXPXGX).JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXh./ / / / / / / / ^ ^ W W W W E R Q Q ~ ~ ~ ~ ) E E ^ ^ / / / / / ( ' ' ' ' _ W h.PXJXJXGXGXGX+.Y Y H F F Y F F D v PXGXJXJXJXJXJXJXJX].4 G ! { ' i.i.p.i.d.i.i.i.i.}.JXJXJXJXJXJXJXJXJXJXJXXmXKXqXwX{.A r u M b ;XuXiXiXuXyX;X}.z.5.5.6.6.6.;.e 7 9 q < %XJXJXJXJXJXJXcXx b q 0 9 , * , 9 0 u m 7.z.5.r R.eXuXpXuX1XQ.1XQ.R.I.:.h m 6.e.z.x.0 ; % % $ % ; 7 % 7 <.l.U.E.E.E.E.W.E.E.W.Q.Q.Q.rXrXuXsXsXsXf 7 q s f f h ;.9.].NXX>X}.9.C ].FXnXzX+X).S /.cXj.1XyX1X1XW.Y.T.Y.W.R.eXVX@Xz.m b f g ;.h 9 g r.y.r.r.r.r.Y.Q.ZXAXyX;XR.l.l.e.l.x.}..XyXHXKXmX@Xg.n 0 u b 4.;XeX2X1X;XR.x.z.5.5.6.;.;.e 7 ; q g h `.JXJXJXJXJXJXkX9.T.z.e.d , , , 9 u m 5.j.z.j.A q l.l.R.eX;XR.T.T.I.Y.W.;XT.T.;XyX>Xj.* $ $ $ & ; ; % 7 q r.y.U.Y.E.E.E.Q.Q.Q.Q.Q.eXrXuXpXsXsXyX, 7 s f 4.4.f h v DXJXJXJXJXJXJXhXN k 0 k b Z j.c.;X>XZXZXSXAX|.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX$XcXPXPXPXPXPXPXPXPXPXPXFXcXkX).FXGXGXGXGXPXGX].vXFXPXPXPXPXPXPXPXGX+XGXJXJXJXJXJXJXJXJXJXJXGXJX%XX}.}.'.OXJXJXJXJXJXHXGX`.;XR.1XW.W.W.W.Q.Q.eXmX0X'.B d d d s ;.;.;.- w.y.y.y.y.y.l.yXVXdX0X;XT.l.e.r.e.z.T.c.;XyXKXCX,Xz.B u 9 0 u T.1X1XR.R.T.l.7.5.4.4.h g q ; ; 7 e h 4.,XLXJXJXJXJX0.e.e.e.7.4.q , , 9 u m 5.z.z.}.}.y l.l.l.l.T.c.y.y.W.R.2X2XeXyXyXiXyXA & $ * & ; 7 9 o & 7 -.u.Y.W.E.Q.Q.Q.Q.Q.Q.^.rXuXpXuXuXeXl.l.u e f ;.;.f d /.JXJXJXJXJXJXJX).l 1 k n 0.9.x.}.;XyXZXAXSXAX].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXPXGXBXcXhX].GXPXGXPXGXPXPX].vXFXPXPXPXPXPXPXPXGX+XGXJXJXJXJXJXJXJXJXJXJXJXJXPX].FXFXFXBX).nXFXFXBXFXGXPXPXPXPXPXPXPXPXPXPXPXPXPXJXPXPXPXPXPXPXPXFX).GXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+X) / / / / ^ ^ W W W W E R R R R T Q Q ~ [ [ [ [ [ [ { _ ( ` / / ` ` i.' ` ` _ ) |.JXJXPXGXPXoX! F F F F Y Y H F F .GXJXJXJXJXJXJXJXJXlX6 K ! [ { p.' i.p.d.d.d.p.p.`.JXJXJXJXJXJXJXJXJXJXJX$XbXUXUXUXIXLXiXiXyX1X>X;X0XmX_.JXJXJXJXJXHXPXzX9.R.2XQ.W.W.Q.Q.eXaXAX0Xg.M d f s d -.;.;.8 I.U.U.U.y.U.y.yXSXpX1Xc.l.6.6.6.6.6.7.j.x.;XMXsX>Xj.5.q 9 , 9 ;X1X3XR.T.c.z.z.5.m m g e ; $ ; 7 e s -.9.GXJXJXJX|.5.6.7.5.4.4.b q , & 9 m 5.7.j.x.h.q l.Y.l.I.I.y.y.Y.R.1X2XZXiXZXZXZXyXl ; $ & & ; 7 % $ $ & 8 t.W.E.Q.Q.Q.Q.Q.Q.rXuXrXuXuXuX3XT.l.l.c.u g ;.h e b vXJXJXJXJXJXJXJXn < u b 5.z.x.}.}.1XyXCXAXAXAX'.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXGXFXBXzX%X#XGXGXGXGXPXGXGX#XgXBXGXPXPXPXPXPXPXPX%XvXJXJXJXJXJXJXJXJXGXGXJXJXJXoXnXBXnXnX|.FXBXnXBXBXFXPXPXPXPXPXPXGXGXPXPXPXGXPXJXPXPXPXPXPXPXPXPXGX).FXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXNXU / / ^ / ^ ^ W W E R R R T R T R Q ) Q Q Q [ [ [ { { ' _ ' ' ` ` ` ` ` ' ` ' _ @.gXJXJXJXcX| T H F F F H F H H F D @XJXJXJXJXJXJXJXJXGXM p L ] [ { i.i.i.p.d.p.d.s.f.FXJXJXJXJXJXJXJXJXJXJX#XbXUXUXUXIXZXiXuXrXyXyXyXsXpX.XhXJXJXJXJXHXHXHXO.T.2X2XQ.Q.Q.2XiXAXSX3X9.m f f d g h ;.;.e E.E.U.U.U.I.r.CXSXsX;Xx.q.6.;.4.6.m , < 5.c.3XyX;Xz.5.q , $ , >X1X1XR.T.c.z.7.N b d s 7 $ & 7 e s ;.;.;.).JXJXBXB ;.4.4.4.;.m e e q , 9 h m 5.5.0.O.d Y.T.T.R.W.Y.T.Y.W.2XuXZXZXSXIXZXyX; ; ; $ & ; ; O O . $ 7 i E.E.Q.Q.Q.Q.uXrXrXuXuXuXuX1Xq.>.r.l.T.R.s ;.g e S JXJXJXJXJXJXJXgX0 r b 5.x.c.c.R.;X1XiXNXSXAXAXh.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFXXXPXPXPXPXPXPXPXGXFXFXBXnXkXoXnXGXPXGXPXGXPXPXnX].nXFXGXPXPXPXPXPXPXBX].JXJXJXJXnXXX.h ;.h u ; < f 7.;X1XR.l.4.q ; $ : }.>X1X;X;Xx.e.5.b u 7 7 & 7 8 e -.;.6.6.6.u vXJX0.d h h ;.m e e e b e e , q 4.m b b k 4.I.T.R.Q.W.T.W.W.Q.eXuXZXIXIXIXiX9.7 7 & $ $ & ; o . X . $ 7 q.W.Q.Q.Q.Q.Q.eXeXeXeXyXR.:.;.,.r.I.T.R.h ;.s m `.JXJXJXJXJXJXJXV 9 b 4.x.}.T.;X;X1XrXMXSXSXSXAX`.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX].JXPXPXPXPXPXGXGXFXnXnXvXcXkXXXFXJXGXJXJXJXJXPXGX).cXnXBXGXPXPXPXPXPXPXV JXJXGXkXC V ].].nXnXnXoXx V S kXGXFXBXnXvXzXcXcXnXFXFXGXFXGXGXGXBXFXFXFXFXFXPXPXPXPXPXPXPXPXPXPXPXGX].PXGXPXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXA / / / / / ^ W W E R R Y R Y F Y T T T T T ~ Q [ _ [ _ { ' ' ' _ ' ' ' i.i.i.i.d.i.B JX}.! T Y J H H J H F H H Y H {.GXJXJXJXJXJXJXJXJXJXZ K L ] [ { ' ' p.p.p.d.p.i.j.CXJXJXJXJXJXJXJXJXJXX}.b DXJXJXJXJXJXJXJXj.2XuXuXrXrXrXrXyXeX;X7.h d s s s g h ;.h E.U.u.u.I.y.r.yXuX;Xc.<.>.:.h 4.h q , , d 7.T.1XR.l.m q ; + : 0 @X.X}.9.5.B u , ; 7 7 7 e -.:.:.>.q.l.e.s |.JXb s f h h f f e e e f e m m m b f l , r.T.1XeX3X3XQ.R.Q.2XrXuXsXSXSXZX>Xq 8 8 7 7 $ ; ; . . o X . O 7 W.Q.R.W.T.T.l.e.q.>.h e g :.<.l.T.Q.R.y -.e h ).JXJXJXJXJXJXkX9 l 9.x.;X;X;X;X1XeXiXZXLXIXSXCX|.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX#XgXPXPXPXPXPXPXPXGXFXvXcXzXkXgX+XvXJXPXJXJXJXJXGXPX].zXcXvXFXPXPXPXPXPXPXcX).XXoX%XGXGXFXnXnXnXnXoX%.nXGXGXGXFXBXvXzXkXhXkXzXcXBXBXFXFXGXGXnXnXnXnXBXFXGXGXPXPXPXPXPXPXPXPXPXPXvXoXPXGXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX XJ / / / / / ^ ^ W W E R Y R Y F F J D D D D J ~ Q _ _ _ _ _ ` ' ' ' i.i.i.i.p.d.d.U Z I ! J J H H Y H H H H H Y K jXJXJXJXJXJXJXJXJXJXJXzXM K L ] ) _ ' ' i.i.i.i.i.U cXJXJXJXJXJXJXJXJXJXGX XUXIXIXZXsXiXuXrXuXuXiXyX;X}.b ).JXJXJXJXJXJXGXj.iXsXsXuXeX2XeXeX1X1Xc.5.h g s s s f s q.E.U.U.U.y.y.y.T.1XR.x.<.<.;.>.6.;.y 9 ; h e.T.1XR.l.4.q * O % : < B B l 9 , ; 7 8 8 e g -.:.:.>.w.l.l.l.h XJXb q u s b e e q e b e m ;.;.m f d b % x.T.yXeXeXeXeX1X1XeXuXiXZXZXsXyX5.8 8 e 8 8 7 & ; X o o + O o o 7.T.T.5.q & & 7 7 ; 8 s -.:.<.r.Y.Q.R.8 s 7 s 9.GXJXJXJXJXJXC , m `.>X>X>X>XyXuXZXZXZXIXIXSXOXFXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX`.PXPXPXPXPXPXPXPXGXBXnXcXkXkXkX.6.7.4.u d u 4.l.T.R.R.l.4.q o % + % : 9 0 + 8 8 7 7 q e -.-.:.<.<.w.w.r.r.q.6.oXJXz 7 9 q q e q q e e f -.m ;.m e b b u T.;XmXwXpXuXyXyXeXuXuXiXsXyXeXc.7 7 8 e -.g e & $ % X o O o O $ % 0 O o O $ $ & ; 7 8 g ;.>.1.l.T.R.T.9 ; 7 e 5.+XJXJXJXJX$Xk q 9.>XyXyXiXiXuXiXZXLXPXUXIXSX XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXXXnXPXPXPXPXPXPXPXPXGXFXnXcXzXkXcXkX/.#XzXcXkX.q.7.4.5.6.6.q.I.T.R.T.l.4.7 o # # # , d 9.n 8 7 7 8 e g -.,.<.w.r.w.1.6.6.w.6.'.kXk & 7 9 9 8 8 9 q e e ;.e m -.m f x 9.c.;XwXaXaXsXZXiXiXuXiXiXyX;Xx.9 7 7 e -.-.-.g e o $ X X X o X + $ ; O o o $ & ; 7 e h :.6.l.l.I.T.T.f $ & 7 8 4.j.JXJXJXzXS ; m }.yXNXNXZXZXiXZXLXLXUXUXIX XGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXGXFXBXcXkXgXcXBXBXkX#XoX+X+X].gXGXGXzXoXcXBXPXPXPXPXPXPXPXPXPX].BXnXnXnXnXnXvXx cXGXGXPXPXPXPXoXV hXhXgXhX.h g s s e Q.Q.E.E.E.Y.U.Y.E.W.E.U.y.1.q.q.l.7.q.l.r.l.T.R.T.l.e.h % % : : , b b 9.V 7 7 8 e -.>.w.w.r.r.r.w.q.<.w.<.6.5.).0 % % ; 7 , ; ; 9 e e e -.;.;.;.h z `.x.c.5X8XpXpXmXyXyXyXlX>X}.b & ; 7 7 s :.>.:.:.e % O O o + + o o x.;XQ.h 7 s h -.:.w.r.I.T.R.Q.eX3X3Xj.; ; ; 8 m ).JXJXcXC : q 9..XMXZXZXZXZXZXSXZXZXSXZX.XnXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXPXGXBXcX].C %XcXnXBXBXBXcXhXhX].#X+X%XC %XFXPXPXPXPXPXPXPXPXPXoXnXnXvXvXvXnXvX%.oXGXPXPXPXPXPXPXcX%.).hXkXkX`.PXGXGXnXkX.1.w.<.-.8 o O + + % $ 9.LXZXyXR.q.r.l.I.T.W.W.W.1XeXuXZXAXAXVX*X, & 7 f ].JXlXx O , d 7.}.yXZXZXZXZXZXZXuXrXrXeX|.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXPXGXFXFX).cX%.+XcXcXvXcXkXhXoXV GXFXgXgXFXPXPXPXPXPXPXPXPXPXGX].bXbXnXnXvXnXnX#X_.GXPXPXPXPXPXPXPXPX].kXkXgXoXGXGXGXJXGXPXPXGXcX.w.r.r.r.y.r.y.r.y.r.r.w.<.<.; , : % % + O O O 7 d g -.;.>.>.4.h u u n : A j.9.5.N u 9 , & $ o $ O & 7 8 s >.r.r.r.>.s $ + + $ % 0 JXUXUXZX1X;XR.R.Q.Q.Q.1XeXuXsXZXIXIXIXAXaX!.9 ; u XXgXk $ $ q m 9.;XMXCXZXZXZXiXeX1XR.2X.XvXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX|.PXPXPXPXPXPXPXPXPXPXPXPXPXGXoXPX#X].kXkXkXhX|.`.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFX].cXnXnXvXnXnXnXnX%.FXPXPXPXPXPXPXPXPX].kXzX].BXGXPXPXGXXX).|.+XvXcXkXzXvXnXFXGXPXPXPXPXPXGX%X].kXFXgXzXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXkXK ^ ( ^ ^ ^ W E Y D |.JXJXJXJXJXJXJXJX_.[ P J 4 H Q _ i.p.p.d.d.S.S.S.S.S.S.S.S.S.d.i 4 i i p K G K K L U P U O.JXJXJXJXJXJXJXJXJXJXS p U [ a.[ [ a.[ s.s.s.p.d.d.h.JXJXJXJXJXJXJXJXJXJXJXJX`.sXuXsXuXuXuXrXuXuXrX1X;Xe.m q 9 0 ].DX/.yXyXeXQ.T.U.U.I.y.I.Q.eXT.7.h f s s e e u Q.Q.Q.E.E.Y.E.E.E.E.E.E.U.t.1.<.1.1.w.I.W.Q.R.6.- . o % : z V z l m m ;.>.6.w.w.r.I.I.r.I.I.y.I.I.y.I.1.1.<.9 , : % + + O X + q e -.;.:.4.>.4.f 9 , 9 0 j V z z 0 , % + O O & & $ O ; 8 e <.w.r.w.,.e & X % % : b PXUXUXIXZXrXQ.eXeXQ.eXeXrXiXZXSXIXIXSXAXpX5X; ; l nXn % $ % 9 d 5.}.yXyXiXZXZXuXyX1XW.2X;XhXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXoXPXPXPXPXPXPXPXPXPXPXPXPXPXgX%XJXJX.>.<.<.r.I.Q.W.W.I.r.1.>.; 7 9 7 % + # q g -.g g ;.h f q 7 + % , 9 9 0 x l 8 q s g d 9 , $ O o & 7 e :.:.:.:.g q 7 O % , + q }.MXSXIXIXAXsXuXuXrXrXrXuXsXSXSXSXIXAXaX5X% % 7 f 4.4.9 * 7 7 q h 5.e.c.1XiXAXZXsXuX2XQ.uXOXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXvXXXPXPXPXPXPXPXPXPXPXPXPXGXnX|.JXJXJXJXJXJX.>.>.<.1XZX0Xc.6.d s g g g s e 4.I.Y.I.r.<.,.E.E.Q.Q.E.E.U.U.U.U.U.U.U.r.w.q.* o . . X % % m >XZXZXyXeXR.T.T.I.w.<.g g h >.<.T.2X2XQ.T.I.w.>.e 9 e e q , ; e s s e d s e q 8 & + % ; 9 9 # ; e s 7 u b b u 0 & O o $ 7 e g -.-.g e 8 8 & % % & q 5.;XMXZXZXZXdXsXuXrXeXrXuXAXIXIXSXSXdXaX> O q g ;.7.x.1X0 7 9 q d h 6.x.;XuXZXZXAXZXrXeXuX0XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX%.JXPXPXPXPXPXPXPXPXPXPXFXnXkXXXJXJXJXJXJXJXGXXXPXPXPXPXPXGXGXPXGXGXPXGXGXPXGXGXFXGXgXk %.].gXnXvXcX.uXiX1Xc.4.s s g g g g e s u 7 h <.;.g 1.Q.Q.E.E.E.E.E.E.u.u.t.t.<.>.m $ o . o ; 9 * 5.MXLXZXMXyX;XT.I.l.q.:.s 8 e g >.T.1XeXQ.R.T.q.6.e , s e e q q e e e s e e q 7 ; % + % * 7 : % e g 9 n l 0 7 , : % O o ; 7 q q s g e q q 7 $ O , % 9 d 4.l.;X1XrXeXQ.Q.Q.Q.2XuXsXSXIXIXVXaXN 7 8 e h 4.e.T.1X9.9 q 9 s d 4.7.;XrXZXAXAXAXuXrXuX0XFXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXcX%.GXPXPXPXPXPXPXPXPXPXPXFXFXcX.>.y o o ; 7 ; * 9 }.yXiXiXeXR.T.l.q.>.g 7 & & 7 s 6.R.1X1XR.l.7.;.q % q q q 8 8 e e e e e q 8 7 $ . + # % ; O e -.s 0.|.}.9.5.5.7.e.z.z.e.z.9.9.B d q 8 8 ; o , ).k ; s 4.6.w.l.I.I.I.I.I.I.T.;XyXZXCX,XB h g s s f ;.e.;X;XyXc.q d q 9 u m 7.}.yXAXSXAXAXsXrXsXSXcXJXJXJXJXJXJXJXJXJXJXJXJXJXJXzX%.PXPXPXPXPXPXPXPXPXPXGXFXBXbXcXoXhXJXJXJXJXJXPX+XkXPXPXPXPXPXGXGXGXGXGXPXPXPXHXFXzX%.FXPXPXPXPXPXPXPXPXPXzX#X).`.Z OXX.Q %XBXoXJXJXJXJXGXPXBXGXJXJXkX#XzXkXkXkXkXkXhXkXkXcXBXPXPX+XGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXo.E W W ^ ^ ^ W +.GXnXo.! [ U o.$XGXJXJXJX-X@.J 4 i i G 3 L ] m.s.M.B.B.B.d.d.S.d.d.d.d.d.S.d.S.p a I I } I .I o.o.+._.JXJXJXJXJXJXGXJXS i U m.F.C.C.F.F.F.G.G.G.G.G.G.j.KXJXJXJXJXJXJXJXJX+XUXSXZXuXrXrX^.~.F.b.I N M m 4.4.m e 0 , # l 0 q 0 , 7 , 7 4.2XyX1XI.q.;.-.g e e 8 8 8 ; O X # lXx l.Q.Q.Q.Q.E.U.u.t.1.,.:.:.:.>.:.-.7 ; 7 7 ; & $ m x.;X;X;XR.x.l.>.f ; $ O o O & 9 4.c.R.T.e.5.b 9 9 q 7 7 8 8 q q q e e q 7 & . X + + # + e ,.-.n 9XyXlX>X;X;X1X;X1X;X;X;X;X>XyX>Xz.f 7 $ O 0 FX.g g h -.g g g 5.c.;XZXZXx.d d u 9 9 d B }.yXCXSXSXAXdXuXsXSXkXJXJXJXJXJXJXJXJXJXJXJXJXJXJX).PXPXPXPXPXPXPXPXPXPXPXPXFXnXcXkXXXzXJXJXJXJXGXGX].GXPXGXPXPXPXGXHXPXHXHXnXnXPXHXnX].PXPXPXPXPXPXPXPXPXPXPXPXPXGX-XU E ~ k.zXnXS gXgX.<.6.>.h h ;.6.6.;.7.e.;XZXIXIX4.0 u u u 0 u 9..XMXKXIXIXIXAXsXdXIXkXJXJXJXJXJXJXJXJXJXJXJXJXhXzXPXPXPXPXPXPXPXPXPXPXPXPXPXGXFXvXkX#XgXGXPXGXGXPXvX+XJXPXPXPXPXPXPXPXPXJXPX-XwX%XFXcX].nXPXPXPXPXPXPXPXPXjXb.~ ) ] -XFX].kXhXkX).PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXvXS k +XBXGXGXPXXXzXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX#XG / / ^ ^ ^ W W R 6 ! K K p p p p | FXGXGXGX| Q J G G G 4 i 2 U a.a.m.M.B.B.B.S.B.S.S.d.d.S.S.S.S.G.G.G.3 a a a I } } .O.g.JXJXJXJXJXJXJXGXS 2 K U m.C.F.F.G.L.G.G.~.G.~.L.f.GXJXJXJXJXJXJXJX.;.f s 9 ].JXNX5.Y.W.Y.y.w.<.-.g g g s g s -.;.4.;.h w 8 ; & O O 7 ; & 8 m e 7 o o o $ & 7 e e -.-.e 7 & ; 7 q e d s d q ; ; 7 8 e e 8 7 $ O * * ; g :.:.-.-.q >XMXiXsXsXuXsXsXsXZXsXZXZXSXIXIXIXIXIXLX0 s 0.JXJX< . & 8 g :.,.<.<.,.<.,.<.<.l.l.l.7.q.6.7.j.j.}.;XyXZXUXSX9 9 u u u 0 n g.OXxXIXUXUXUXuXsXdXCXgXJXJXJXJXJXJXJXJXJXJXJXFXoXPXPXPXPXPXPXPXPXPXPXPXPXGXGXGXnXcXkXgXoXGXGXGXPXXJXJXJXJXJXJXJX#XUXSXZXdXsXdXAXAXaX*Xv. .5.5.h 4.4.h d u 9 % 0 0 j 0 0 0 # 6.Y.W.Q.W.Y.U.y.y.r.r.w.w.w.>.h s 0.JXJX5.r.U.y.r.:.-.-.e e s s e g e.T.R.R.}.x.d 7 & O O & * ; ; % , : O o $ 8 e -.-.:.:.:.h e 7 7 8 8 q e e q 9 & & 7 8 e e 8 7 o * X q :.-.-.-.g g q >XZXiXiXuXuXiXsXZXAXSXZXSXIXIXIXIXIXIXUX>X, j nXhX# o 7 8 g -.:.,.,.<.,.,.<.l.I.T.T.l.l.x.x.h.}.>XiXLXIXUXg.& , y u u k B _.@XmXKXUXUXSXsXsXAX9XvXJXJXJXJXJXJXJXJXJXJXJX|.PXPXPXPXPXPXPXPXPXPXPXPXFXnXFXFXnXcXkXgX].PXPXGX).gXPXPXPXPXPXPXPXPXPXPXLXPXgXFXOXwXOXkX%X].PXPXBXY o.@.^ T &XBXPXPXPXHXBX).gXhXoXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXoXPXPXPXPXGXS GXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXPX0XD / ^ / / / ^ W E T | %.a K K p 6 p p O.FXBX| Q Q ! L G 4 4 3 3 2 b.m.N.C.S.F.S.S.S.S.S.S.S.S.S.S.G.G.G.L.L.a 5 a p K a N . .`.JXJXJXJXJXJXJX].= 2 K U a.m.C.F.G.L.G.G.G.~.~.~.].JXJXJXJXJXJXLX].UXSXZXZXsXAXIXIXKXmX*Xx.5.5.;.;.h h h d 0 , 0 0 u u 0 0 7 r.U.E.W.W.Y.U.y.y.r.y.y.I.y.r.>.s r DXgXj <.r.w.,.:.-.e e e s s f ;XyXrXyXyXyXyXyXl & $ o O $ % & 7 9 n $ & 8 e -.:.,.1.r.6.;.e q q 8 q q d q 9 7 & % ; 8 e e e 7 $ * u 6.6.:.-.-.s e 8 ;XuXuXuXuXuXiXZXZXZXAXSXSXSXSXIXIXIXIXIXiXm 7 < k O o 7 e e e -.-.-.:.:.>.1.l.l.T.T.T.v.c.v.k.Z.5XmXIXIX9X0 % ; 9 u l x O.'.@XmXSXIXIXsXuXsXAXoXJXJXJXJXJXJXJXJXJXJXJXhX#XPXPXPXPXPXPXPXPXPXPXPXBXcXcXnXnXvXkXgXgX].PXGX].FXPXPXPXPXPXPXPXPXPXPXFXOX] R -XjX.X*XkXkXS GXPXnXE ( T {.FXPXLXPXPXPXFXnX).gXhX$X%XPXPXPXPXPXPXPXPXPXPXPXPXGXGXPXPXPXPXPXPXGXPXPXoXPXPXPXPXPX$XvXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX{.H / / / / ^ ^ ^ R Y | FX%Xv I p 4 t t a O.C [ _ ! ! L G G i 4 3 = p n.V.B.F.S.F.F.C.S.S.F.S.S.G.S.G.G.G.~.G.G.Z.a t a I v . .Z FXHXGXJXJXJXPXoX= = p L ] m.M.B.F.G.G.G.G.G.~.^.{.JXJXJXJXJXJXJXoXCXSXSXZXZXSXUXUXUXKX0X;Xj.5.>.h h h f u 9 , 0 0 d u j 0 q u.I.Y.W.W.Y.U.r.r.r.y.I.U.I.r.>.g 7 : l < 6.r.w.,.:.-.e e s g m ZXZXZXsXZXZXZXZXyX,X, $ o X O O % % $ $ ; q g -.:.<.r.y.y.w.-.e 8 7 7 8 q 9 7 ; ; X $ % 8 e -.w 7 & $ h q.>.;.-.e e 8 7 0.iXuXrXeX2X1X2XeXrXiXAXZXAXZXSXZXsXZXZXT.6.f ; $ o o 7 e e -.-.-.-.,.<.1.l.I.T.T.T.z.v.j. .H.J.=X0XyXj.* O & 9 9 l m 0.].OX-XwXCXCXAXuXuXsXSX0.JXJXJXJXJXJXJXJXGXJXJX).PXPXPXPXPXPXPXPXPXPXFXBXcXzXkXvXcXcXkXgXXx.z.4.h s d d r > 0 z l b d u < ;.I.y.U.W.W.E.U.y.y.y.y.y.y.U.y.<.h ; O 1 1 h r.y.w.<.:.g g -.u yXSXIXIXIXIXSXZXiXiX>Xb o . o O $ O O $ q g ;.:.:.<.r.y.r.y.6.g 8 7 7 ; & ; & + $ o % O O 8 e e 8 7 & & h e.>.;.g e 8 7 & b iXuXrX2XW.T.T.T.T.1XrXsXiXiXiXyXuXuX2XT.I.r.h o $ o ; e -.-.-.:.:.<.1.I.Y.T.R.R.T.l.5.N f.P.J.C.v.C + o O % < u b B c.>X>X3X>X1X2XrXrXuXAXSXC JXJXJXJXJXJXJXJXPXGXzXXXPXPXPXPXPXPXPXPXPXFXnXzXkXkXkXcXzXkXgXXv.z.4.m d 9 q 9 5 5.9.4.b b u , 6.l.q.I.W.E.Y.I.y.r.y.U.y.y.U.y.<.g ; o * 1 q y.I.y.1.:.g g ;.5.IXIXUXUXUXUXUXIXZXyX>X}.. . o o O O $ 8 ;.:.w.<.<.1.r.r.r.w.:.e 7 ; $ O O O O O O $ O O O ; 7 e 7 ; & ; h 7.4.f e ; * ; ; 9 MXyX2XR.T.r.r.r.q.e.T.1XeXyX;X1X;X1XR.R.Q.T.r.q . o & e g :.,.:.,.1.l.Y.Y.W.Q.R.T.l.m m f.L.G.S.h.JXn O * u 9.[..X;XyXyX1XR.R.1XrXrXsXAX X].JXJXJXJXJXJXJXJXPXGX`.PXPXPXPXPXPXPXPXPXPXnXcXzXkXkXcXzXzXkXgXgX%X%.GX].PXPXPXPXFXZ.Q R U +.{.aXaXaXaXaXaXqXaXqXaXqX{.S GXxXE ^ Y T OXPXPXPXPXPXJXBX).zXvXnXBX%X].PXPXPXPXPXPXPXPXGXcXgXgXgXzXFXGXGXGXGXGXvXC GXGXJXPXPXzX.w.w.1.w.1.1.w.1.<.;.q 7 $ o 0 1 + o X O O , , v -Xv.q 7 & ; * f 4.h s ; y 5.5.m d +X1X1XT.l.q.6.>.:.4.;.6.z.c.T.T.I.T.Y.W.W.Y.y.-.. o $ 8 -.:.,.,.1.I.Y.W.Q.R.W.R.c.z.l.N b.L.G.S.h.DXV . : z g.-XxXmXyXyXeX1XQ.Q.2XrXuXsXSXz FXJXJXJXJXJXJXJXJXGXPX].PXPXPXPXPXPXPXPXPXPXnXcXkXzXzXzXcXzXgXgX.d 8 > _.JX+.'.oXb b % m 4.e.2X2XW.T.Y.Y.U.y.y.y.y.U.r.<.s ; o o + y I.U.r.<.:.-.-.h 9.mXSXSXIXUXIXLXiXyX;Xc.b + . . o O & q -.<.<.w.<.,.<.w.1.>.;.e 8 $ % V kXJX Xk % + ; 7 u wXwXiXMXz & & & & f d 9 9 j.z.5.m f q z.R.T.l.w.<.,.;.g h -.;.;.6.w.r.r.I.Y.Y.U.r.e o o o 7 -.6.<.w.r.T.W.Q.R.R.R.T.l.r.>.a f.^.G.G.j.JXj X n oX.XyXKXSXiXuX2X2XQ.Q.eXrXsXAX}.|.JXJXJXJXJXJXJXJXJXPXGX].PXPXPXPXPXPXPXPXPXPXBXcXzXzXzXzXcXzXhXgXXc.z.z.4.h u , /.JX_.+.O.l 0 ; d f 1XuXrX1XW.T.W.Y.U.y.U.U.I.r.>.e & o o X l.W.Y.I.1.<.:.,.<.:.z.;XyXsXZXiX1X;Xc.e.d , 9 o . . % e -.>.w.<.-.-.g g -.:.:.g 8 ; % V JXJXJXJXnX1 ; ; * u aXaXZXZXiX0 & & $ ; s ; 5.7.z.z.4.b q 7 4.I.r.w.1.6.:.g h ;.;.;.:.<.r.r.y.y.U.y.w.7 . o o & e 6.q.r.T.Q.1XQ.R.R.l.q.q.:.y .H.^.~.G.f.vX: < `.9XDXSXIXSXiXuX2XeXQ.eXrXuXsXyXA JXJXJXJXJXJXJXJXJXJXGXGXXXPXPXPXPXPXPXPXPXPXPXFXvXcXcXzXzXzXzXkXgX%XS hX%.BXFXFXGX] Q -XHX$X*X&X XPXGXnXvX%X[.&X6XwXqXqXqXqXqX*XF Y Y R Y Y R R ! -XGXBXcXzXC kXFXBXBXoXJXPXPXPXPXPXPXcXk kXkX%.].vXnXGXGXPXXX%XcXvXnXvX|.nXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXgXb.C.C.C.C.C.o.C.C.C.C.C.M.C.C.B.C.C.N.b.Z.gXPXGXPXGXHXPXPXJXJXGXPXHXPXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXhXi.` / / / / ^ ^ ^ ^ _ Q _ _ _ [ ~ .PXPXGX-XL L L K i i p p p i t - - 2 5 2 5 2 ..V.J.L.L.L.P.P.^.^.~.~.~.~.~.L.G.G.G.F.F.J.b.5 t a N JXJXJXPXGXC = 2 4 G L P ] ] [ [ s.d.S.F.G.L.L.~.f.|.JXJXJXJXJX9X@XZXsXiXSXIXIXIXZXyX;Xc.z.l.7.4.d 9 n JXS O.k d 0 : 7 b sXZXsXrX1XW.W.W.U.y.U.I.r.q.>.q & o . ; R.Q.W.U.r.1.>.,.<.<.<.4.z.;X;XT.x.e.4.9 % , 7 % X $ e g :.w.<.:.e 7 7 8 e q e 8 & $ $ , 0.BXJXvXx ; 7 0 5.y aXAXAXAXZX}.& % $ o O , B 7.7.7.4.h s q ; e r.1.w.6.:.-.h h -.:.>.<.r.r.r.y.U.y.w.$ o $ $ % 9 4.l.;X1X1X1X1XR.l.<.>.>.s a f.K.7X^.L.[.`.% l XXNXIXIXIXAXpXrX2X2X2XQ.uXuXyXg.GXJXJXJXJXJXJXJXJXJXJXGXPXoXPXPXPXPXPXPXPXPXPXGXBXvXcXcXzXcXzXzXkXgX%XC hX).cXnXBXBX! R ..bX{.{.*X{.JXFXvXvXkXkXhX[.&XqXqXwXqXqXOXo.Y Y Y Y Y Y R R R b.BXcXzXcX).%XnXnX|.GXPXPXPXPXPXGXoXj kXkXS hXvXGXGXPXPXJXS XX#XoXhXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBX+.J.C.C.C.C.C.C.C.B.C.B.C.C.C.M.C.M.M.C.M.C.$.o.-XPXPXGXGXGXJXJXGXPXHXGXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXb./ ` / / / / / ( ( ( ( _ _ _ _ [ U #XBXoXkX| K K K i a p p p a i 3 = = = = - 5 5 V.J.J.P.P.^.^.^.^.^.^.^.^.~.G.G.G.S.S.S.F.J.X.5 t N JXJXJXHXGX%X5 5 t G K P ] ] [ [ s.M.C.G.G.L.L.~.L.N JXJXJXJXJXLX$X>XsXeXSXUXIXSXsX3X;Xc.l.l.z.7.h d z #XA x u d q & , }.UXUXZXiX2XQ.Q.T.Y.I.r.r.w.6.;.8 & o o 5.rXeXQ.Y.l.r.<.,.<.<.<.<.;.f g f g e q % % : , % & e -.:.r.w.:.e 7 $ & & ; ; & $ $ O $ & 7 n vX0 ; ; q 5.4.u .XyXZXZXsX;X0 O O o o , d 4.7.6.4.h e 8 ; $ w.w.1.w.:.;.b b h ;.:.<.r.r.y.I.y.y.w.o $ & & & ; m c.1XuXuXrX1XT.:.>.,.<.s a f.K.^.^.^.v.0 o q }.yXZXSXAXsXrX2X2XQ.2XQ.rX1X].JXJXJXJXJXJXJXJXJXJXJXJXPXGXXXPXPXPXPXPXPXPXPXPXGXBXvXvXcXcXcXkXkXkXhX|.].gX%XoXcXbXvXJ T Q L {.*X*X*XFXnXvXcXcXkXkXzXhX+.*X5X6XOXGXPXo.Y Y Y Y Y T T T T U cXcXcXzX].gXnX).vXPXPXPXPXGXvXV %.kX 6 a K.J.P.P.P.^.4X4X7X7X^.^.~.G.G.G.S.S.S.S.S.G.V.K 6 gXJXJXJXJXGXk - i p G L P ! ] a.a.B.B.S.G.G.L.G.G.k.LXJXJXJXJXJXJXXIXSXSXuXeXR.W.Y.Y.v.l.r.q.6.h q $ $ o y 1XrXQ.T.Y.l.1.<.<.<.<.<.,.-.g g e e g 8 & O O & q -.:.<.r.,.g 7 $ O o o o o o O O & ; ; ; ; z ; 7 & ;.q.5.d t }.iXuXiXc.% $ o o o . ; d 4.4.4.s q 7 & & r.y.r.r.<.9.}.}.j.4.;.w.r.r.U.y.y.r.6.o O & & & & u x.yXZXiXrXR.>.-.w.w.w.-.a f.H.^.7X8Xk.o o % u 9.1XsXsXrXQ.Q.Q.Q.Q.eXR.OXLXJXJXJXJXJXJXJXJXJXJXJXJXPXGXoXPXPXPXPXPXPXPXPXGXFXBXvXcXcXcXcXzXkXhXgXC gXgXhX).zXzXcXL T T ~ J [.*X*XzXvXvXcXcXzXzXzX).oXh.5XOXGXPXHXGXk.J H H Y Y Y H Y R @XcXcXkXzX].cX%.`.PXPXPXGXFX).|.kXkX%.zXFXPXPXPXPXPXPXPXPXPX].+XGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXPXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXvX..C.C.C.C.C.M.C.C.M.C.M.M.M.M.M.$.C.A.$.C.$.C.C.$.C.$._.FXGXHXJXJXJXJXGXPXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX9X` / / ` / / / / / _ ( ( _ ' ' _ ] L U I I I I p 3 G i 3 3 p a K a p = = = = = 5 5 } A.P.L.^.^.4X7X7X7X7X^.^.~.G.F.S.d.d.d.S.F.J.F.V.o._.kXJXJXFX/.= 5 p K L U ] [ [ a.s.B.C.F.G.G.G.~.f.JXJXJXJXJXJXJXLXzXb IXIXIXAXiX1X;XT.l.w.q.6.>.h s q , XOXl 9 0 & B iXsXsXuXeXQ.T.T.1X-X X7.6.4.s 8 & $ O $ x.uXrXQ.T.I.I.1.<.<.,.,.:.-.-.-.:.:.-.-.8 7 7 q e :.<.w.r.:.7 $ O $ $ o o o $ & 9 7 8 7 ; ; $ % $ q 5.7.5.f * b ;XrXrXe.O o X X + ; O , f ;.h s 7 & & % r.y.r.r.}.>XMXLXMX;Xc.e.w.r.1.y.r.w.:.o o $ $ & $ ; 9.;XZXyXe.g -.:.<.<.<.w.y f.A.P.7X8X=X+ O o 7 4.;XpXrXQ.Q.Q.Q.Q.Q.Q.3X#XJXJXJXJXJXJXJXJXJXJXJXJXJXGXPX.>.;.d d CXIX9Xl * q , , ;XeXeX1X1XW.Y.Q.mXmX-X}.5.h q 7 ; & & O z.uXrXeXR.T.I.w.<.,.:.:.-.-.:.:.-.:.:.-.e 8 e e g :.w.1.1.,.; o o o o o o $ & ; B 4.7 8 7 & $ . O f 5.7.;.f ; * x.2X1Xh o + + + O q 7 O 9 s s 9 ; & $ ; r.I.r.T.yXLXIXUXUXLXiX3XI.1.1.1.1.6.-.o o $ $ $ $ $ 0 .X}.s 7 8 e g -.>.:.6.y o.H.=X7X8X4X* $ $ ; ;.T.Q.Q.Q.W.Q.W.W.W.R.ZXoXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXGXV PXPXPXPXPXPXPXFXBXnXvXcXkXkXzXzXkXgX).oX.>.;.s 9.AXSX0Xl O q 0 , l.R.R.Q.Q.T.T.yXSXKX0X*Xj.h q ; 7 & & & m eXuXrXQ.T.I.r.<.:.-.-.g -.-.-.-.-.-.e e e e -.-.:.1.y.<.:.q 0 o o o $ $ & & 4.z.;Xu 7 & $ O . O d ;.4.h f & , R.2X2Xe.O j # + % e s 8 ; ; ; $ $ $ ; ; r.U.r.1XiXSXIXIXUXIXSXiX;Xq.q.w.1.<.:.$ $ O o O O o $ % ; ; 8 8 e g :.<.6.q.r.l.k.H.P.H. .& & ; 7 e e.Y.I.l.Y.Y.W.W.Q.1XZXoXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXhXzXPXPXPXPXPXPXPXFXBXnXvXcXkXgXkXkXgXgX`. > 6 a 6 v b.K.P.^.4X4X^.^.^.L.L.G.G.S.S.d.S.d.S.L.L.L.L.F.S.m.[.JX%X< @ 5 K U ] a.a.B.B.C.F.G.L.~.~.G.f.DXJXJXJXJXJXJXJXJX].UXIXSXSXZXuX1XR.I.l.1.<.>.;.7.yXAXSX0Xk . q q 9 0 e.R.W.W.I.Y.ZXSXVX9X.Xj.m q ; 7 ; ; & 7 R.1XeXQ.T.I.<.:.-.g g g g g g -.;.:.:.-.g :.,.,.1.1.1.<.-.q X o O O $ & & 4.T.1XyXMXwX`.r O . $ 9 h h s , j.2X2X2XQ.4.O # O 8 e e e 8 7 & $ $ & & 4.I.I.l.R.uXsXAXSXSXIXSXdXrXT.l.1.<.6.>.y O O O o o o & 7 7 e s h :.<.w.l.l.T.T.c.Z.v.$ & & 7 7 e g ;.<.1.l.I.Y.W.Q.Q.Q.dXoXGXJXJXJXJXJXJXJXJXJXJXJXJXJXBXoXPXPXPXPXPXPXJXGXFXBXBXvXcXkXgXgXgX+X%.%XgXgXgXgXgX 5 t c 5 .Z.K.P.P.4X^.P.P.L.J.F.S.S.d.S.d.B.d.L.L.L.L.L.F.C.b.NXGXM @ 2 p U U ] m.B.B.C.G.G.L.~.~.~.k.JXJXJXJXJXJXJXJXJX].UXIXSXSXdXiX1XQ.T.I.r.w.6.l.eXuXAXpX>X0 o 7 0 9 , b y.I.I.y.I.AXSXZX3X}.z.;.q 7 8 8 ; & & m c.R.1XT.q.;.g g s :.l.l.r.r.<.<.,.>.>.,.,.<.t.t.t.1.,.-.$ o O $ & & 4.;XyXZXZXAXVXVXaXwX*XO.r 0 y 9 0 9.eXeXeXeXeXR.b o + q e e q q 7 7 & & & ; l.W.I.1.T.eXuXsXZXAXAXAXdXuX1XI.l.6.<.6.>.$ O o o . $ 7 7 e s ;.>.<.l.l.I.T.R.Q.eXrXR.& 7 8 8 8 e g ;.<.q.r.I.T.W.Q.eX2XsX XJXJXJXJXJXJXJXJXJXJXJXJXJXJX).PXPXPXPXPXPXPXGXFXFXBXnXvXzXhX * = 5 t v c t o.Z.V.P.P.P.L.L.J.G.F.S.S.B.d.S.S.d.G.P.L.L.L.G.F.S.h.JXS + = 6 K U n.M.B.F.F.G.L.L.^.^.7Xk.JXJXJXJXJXJXJXJXJX].UXIXSXAXZXiXrXQ.R.Y.I.l.I.Q.eXsXiX0X}., X , 0 9 , , f w.w.r.I.SXIXmX;Xv.5.f q 7 8 e 8 7 7 7 e.T.T.l.6.g e q 6.Q.eXQ.Q.W.E.U.1.>.>.>.<.,.1.t.1.<.-.g $ o o $ $ $ 9 z.eXZXAXSXIXSXVXaXqXqXqXaXwXpXyX3X1XrXrXrXeXrXeXc., ; q e e e q 7 7 ; & & , l.T.I.1.I.Q.rXrXuXsXsXuXpXuXQ.T.l.l.q.q.7.m % o o o $ ; q s g >.6.l.I.I.T.R.3X1XuXuXf & 7 8 e g g -.:.>.1.l.I.T.Q.Q.eXrXyXoXJXJXJXJXJXJXJXJXJXJXJXJXJX|.zXPXPXPXPXPXPXJXGXFXFXBXBXvXzXhX > 5 l c c r .f.b.A.J.J.G.J.F.F.F.S.F.S.S.S.S.S.L.^.^.~.L.L.G.S.h.JX`.% - 2 p U ] m.C.F.G.L.L.^.7X7X8X{.JXJXJXJXJXJXJXJXJX].UXSXSXAXsXsXrX3XQ.T.I.U.W.eXeXuXuX>Xx.& o % q 0 9 9 & -.w.w.T.SXSXZX;Xx.5.h q 8 8 q 8 7 7 7 h l.T.r.;.s 8 q uXtXuXuXrXQ.Q.Y.I.<.:.:.:.,.1.,.,.:.-.e o o O $ & o q z.yXZXAXSXSXSXVXpXtXaXaXVXCXVXZXZXiXrXuXrXuXiXZX0Xx.; 9 q d e e 8 ; ; & % ; z.W.I.r.r.W.Q.eXrXrXrXeXeXQ.W.Y.W.R.R.R.R.R.7.o . o o % 8 s h >.6.r.l.I.T.T.Q.Q.rXuX1X; & 7 e e g -.-.:.>.1.1.I.W.Q.Q.rXuXc.FXJXJXJXJXJXJXJXJXJXJXJXJXFX].PXPXPXPXPXPXPXJXGXFXBXBXnXnXzXhX].kXFXx %XcXkXhXhXXsXZXdXsXuXrXeXQ.W.Y.Y.W.2XrXuXyX1X9.% O , 0 9 9 9 9 q y.y.1XCXIXZX>Xc.5.h s 8 8 q 8 7 & & s l.l.q.-.8 7 ; eXuXuXuXQ.Q.Q.E.1.>.;.-.:.,.,.,.,.-.-.; . o $ & 7 7 7 4.T.1X;X3X0XCXwXwX*XaXVXSXIXSXZXZXiXrXuXuXuXsXZXMX;X0 ; 7 q 7 7 7 $ $ $ $ * 7.T.I.l.w.Y.W.Q.Q.Q.Q.Q.W.W.Y.T.Q.Q.eXQ.eXeXQ.x.@ $ o ; 9 h ;.6.e.I.T.I.Y.R.Q.Q.rXrXq.; ; 7 e e g g ;.;.<.l.l.l.Y.Q.rXrXuX`.JXJXJXJXJXJXJXJXJXJXJXJXJX+XcXPXPXPXPXPXPXPXGXFXFXBXBXnXvXzXhX].GX].cXnXBXzXkXgXM o.[.J / / E ~ @.k.k.K 3 D Y R D =X XPXPXPXPXPXxX^ W E Y J .X9XbXbXvXcXcXcXzXhXcXnXFXPXPXPXGXcX%.cXBX%. 5 < 5 5 k z v : a o.f.n.n.n.C.C.F.F.F.J.J.F.L.L.P.^.4X^.^.^.^.~.L.[.JXFX< = = - p @.N.F.L.L.P.4X7X8XqXqX{.GXJXJXJXJXJXJXJXJXHX'.uXiXsXuXuXrXQ.Q.W.W.E.Q.2XrXrX1X;Xe.% + 9 9 9 0 9 q q Y.Y.1XiXAXAXuX;X7.;.s q 8 8 7 7 $ $ ; l.r.:.q 7 & & x.uXuXuXQ.Q.E.U.1.:.g -.-.-.-.-.-.-.e o o o $ & 7 8 8 y >.5.B b 9.@X@X*Xk {.mXVXCXDXZXsXuXuXrXuXuXZXZXMX1X9.$ % & $ o $ O $ $ $ O B l.l.q.<.w.l.W.W.Q.W.W.I.Y.Y.Y.W.Q.Q.eXeXeXeXeXT.O o ; y h 4.e.l.T.T.T.T.R.R.1X2XQ.-.s e 8 8 e s g ;.:.>.<.1.y.T.Q.Q.uXx.kXJXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXJXPXGXBXBXnXvXvXzXgXoX#X%XzXvX`.kXhXC {.[. .W ^ ! OXFXPXGXFXkXM a I G } {.hXJXPXPXPXPXjXR Y F H H L OXnXnXvXvXcXkXgX%X 5 1 5 6 c v Z z ...f.b.n.V.V.F.F.F.F.L.L.L.^.^.^.7X^.7X^.^.^.P.}.JXJXV @ * 2 i I n.V.F.P.^.7X8XqXaXAXpX+XJXJXJXJXJXJXJXJXJX9X;XrXuXrXrXrXeXQ.Q.Q.Q.Q.Q.rX2X1X;Xe.; $ ; , , 9 u 9 q Y.R.2XrXiXsXuX1Xz.;.g q 8 8 7 7 $ O % r.s $ o o . f 2XeXeXQ.Q.E.t.,.-.g -.g -.g g e 8 o . o O & 7 8 e e 8 8 9 , ; 0 Z `.0.A z -X9XxXlXiXiXuXrXrXrXsXsXZXMX>Xz., O $ o o . % 7 8 8 & d z.e.q.>.>.w.l.I.I.I.I.l.u.U.I.E.Q.Q.rXeXrXuXrXeXT.@ ; q m 7.z.x.T.T.T.T.T.R.W.Q.I.:.:.-.h h f q q h 7.6.>.l.l.W.Q.Q.rXg.JXJXJXGXJXJXJXJXJXJXJXJXJXJXXXPXPXPXPXPXPXPXJXGXFXBXnXvXvXcXzXgXC oXjX#X).|.kXC oXOXk.I E G vXPXHXGXGXGXFX-XA &X=X&X`.S gXGXJXJXJXxXH H F F H H Y U #XnXbXzXgX.-.s e 8 ; & O o s s O O X o X X ; W.eXQ.Q.E.U.1.:.g g g e e e e e 8 . . o O $ & w e g e 7 $ O X . O : |.JXoX+.oX,X>XyXyX2XeXrXrXuXiXsXiX3Xc.9 O O o $ h ;.g s e e d f 4.6.>.;.:.<.r.r.1.1.1.1.r.U.E.W.Q.Q.rXuXpXrXuXrXo & q h q.l.l.l.l.l.l.I.Y.W.Q.y.r.r.<.1.r.l.l.T.1X3X6.l.l.I.W.Q.Q.v.NXJXJXJXJXJXJXJXJXJXJXJXJXJXJX|.PXPXPXPXPXPXPXJXGXGXFXBXvXvXcXkXjXS OX/.].hXhX`.].cXoX[.Y D 4 h.PXHXGXGXGXFXBXkX[.[.].%X%XZ vXBXBXGXKXK H F F F F H Y D _.cXhX.g g y 7 - & <.U.r.% + + X o X o e.W.Y.E.U.t.,.-.g e 8 8 8 8 -.-.e o . . O o $ 8 e s s 8 o . . . o + kXJXoXu k }..X;X;X1XQ.eX2XeXrXuXuX;Xc.f o $ o h 4.;.h g g g -.g s g m ;.-.-.,.<.<.<.<.w.y.y.U.W.Q.Q.Q.uXuXpXuXuX& o 9 h >.l.l.l.<.l.w.r.I.W.W.U.U.U.E.U.Y.Y.W.Q.rXuX9.r.l.Y.T.W.x.nXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJX|.PXPXPXPXPXPXPXPXPXGXFXnXnXcXzXkXzX/.%.].].hX,XA {.*Xv. .F D 4 4 -XPXFXGXGXFXFXnXcXhXgX%X#X%.S cXnXBXPX%XD F F H F Y Y F Y ..%XkXgXkXcXBXGXPXFXBXcXzXgX%XgXXXPXPXPXPXPXPXPXPXPXkXkXkXkXzXnXBXFXGXPXJXGXk JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGX].PXGXFXFXJXPXPXPXGXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXPXPXPXPXPXPXPXPXPXPXPXPXPXJXV oXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX%XN.C.C.C.C.$.C.C.$.A.C.$.$.C.$.$.$.#.#.o.@.$.........U | U ..| | | | | | | ....| ..| o.#.#.&X$.@.L @.M.N.$.@...{.FXHXPXHXHXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXnXM } K K p p $...@.3 a a - 2 K U N.m.m.a.[ [ { { { ' ' { p.p.p.s.B.B.B.X.I o.k.k.k.k.[.}.`.x + l v hXJXJX`.: t } X.n.n.V.F.J.L.P.4X7X7X7X7X8X7X7X7X7X7X8XH.cXJXJXC @ = 2 5 y K f.Z.P.6XaXSXIXUXUXxX#XJXJXJXJXJXJXJXJXJXJXzXR.Q.Q.Q.rXrXQ.^.eXQ.Q.Q.Q.R.I.m ; $ O * , 9 f W.Q.Q.Q.Q.Q.Q.Q.tXuXeXQ.l.:.g s 8 e <.t.U.r.; , * + + + . ; I.U.t.r.<.,.g e 8 7 7 $ 7 e -.g & . o o o o $ 8 8 8 & . . o . . o 1 n q q q 7 9 B R.T.Q.Q.Q.Q.Q.eXeXR.T.4.o $ ; ;.4.;.-.-.g -.-.-.e e e 5 & & g -.:.:.<.1.1.y.U.Y.Q.Q.rXrXuXuXdXuX* o $ y f ;.>.>.>.>.w.r.I.Y.Y.W.Y.E.E.E.E.Q.Q.Q.rXpXj.>.<.r.u.T.zXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXXXPXPXPXPXPXPXPXPXGXGXFXnXBXnXvXFXPXPXKXcX].-X{.tXqXqXqX5XD D D H p vXGXGXGXGXFXBXvXzXhX%XXXx %.#XkXGXPXPX{.F Y Y H Y Y F F Y J hXcXnXFXFXGXPXFXvXzXhX.<.<.:.u y , ; * % . $ 6.,.1.<.:.g g & . . . o $ 8 e e 8 & o . . o & & ; o o o o . . . 9 q q 7 7 ; ; ; q T.W.W.Q.Q.Q.Q.W.c.4.O O y ;.;.4.;.h g g g e e 8 7 ; & & & e g :.<.w.y.u.U.W.W.Q.Q.tXuXsXsX3X. o o O s d f h ;.:.<.q.I.Y.W.E.E.W.E.E.E.Q.Q.Q.tXuX4.s :.1.u.XXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXoXPXPXPXPXPXPXPXPXPXFXPXPXPXPXPXPXPXPXBX$X%.{.aXqXqXtXqX5XF F D D D K {.gXBXGXGXGXnXvXhX$Xx #XPXC FXPXPXPXPX+XG H H F H H F F Y -XFXFXGXGXPXFXBXcXkXXQ.uXrXrXQ.Q.Q.rXrXQ.R.T.l.h 8 & & $ . 3XiXuXQ.Q.Q.T.Y.I.Q.rXeXeXQ.W.y.1.<.:.<.<.1.l.>.h 0 u < , % o & e 1.1.<.,.-.g 7 $ $ o & 7 8 8 e 8 & o . $ $ . & 8 q q q & & ; 7 7 8 7 7 ; ; q T.W.W.W.Q.Q.Q.W.l.;.O $ q h ;.;.h g s e e 8 8 8 8 7 & & $ 8 g <.r.y.Y.Y.Y.W.Q.eXrXpXsXdXuXd . . . o - g h h h ;.>.r.I.T.W.W.W.E.Q.E.E.E.Q.Q.eXuXy g >.y.U.I..<.l.Y.W.W.E.W.E.E.E.Y.W.Q.eX7.e -.<.y.U.W.z.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXhXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXJXcX|.A qXH.+.{..XH.f.T / / / / ( W W W W W E L hXvXkX].GXGXS FXPXPXPXLXxX-Xf.] T R ) / ^ ( W Z.PXHXGXGXFXnXzXhXgX].).%.FXPXPXGXGXGXFXFXnXvXvXnXFXFXFXGXGXPXPXPXPXPXGXGX`.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGX#X%XJXXXFXnXoXhXcXcXkXcX = 2 i a I ..f.Z.H.5XmXKXUXUXUX$XnXJXJXJXJXJXJXJXJXJXGX_.sXdXsXuXrXeXQ.rXQ.eXQ.T.w.>.h e 8 ; & & 5 eXrXQ.W.l.w.:.l.Q.eXeXQ.Q.Q.Y.u.u.U.E.W.Q.Q.Q.I.<.* 1 $ $ & 8 e t.u.u.t.1.,.,.-.g g g g -.-.,.-.e . o o o -.y.U.l.z.c.c.}.;X>X}.M 0 ; 7 7 q ;XR.W.W.W.E.W.W.I.q.;.o o 9 h -.f s e 8 7 & & & & & 7 ; y yXZXiXeX1XR.R.Q.Q.Q.2XrXuXdXdXuXu X X . $ ; q h g s g ;.<.l.T.W.Q.Q.E.U.U.y.U.T.Q.Q.s g -.<.1.U.W.T.hXJXJXJXJXJXJXJXJXJXJXJXJXJXcX].kXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXcX#XZ H.O.A Z JXKXOXT / / / / / / ^ ^ ^ W W W R [.kX#X|.GXcXXXPXPXPX.X] E ( ( ( / / / ` ( W W OXPXFXBXnXBXcXkX#XC ].oX%.GXGXGXGXFXFXFXBXvXvXcXnXBXFXFXFXGXFXGXGXGXGXGXGXXXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX%X].FXhX].%.XXXXGXgXcXcXkXkXcX).x nXPXPXPXPXPXPXPXGXFXFXFXFXBXnXnXnXvXcXnXBXFXFXFXFXGXGXPXPXGXGXPXGXPXPXPXcXhXgXkXzXnXnXFXbXoXPXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX&XA.C.C.A.C.$.C.$.C.$.$.$.$.$.C.U [ [ $.[ [ [ [ [ $.$.L a | +.%.bXFXFXKXFXFXFXkX#X].]._.%.o...o.| U 2 4 D F H T Y F p $.$.C.X.x ] { s.{ { { [ ~ L g.JXJXJXJXJXJXJXJXJXJXHXJX).| K @ A.f.v 6 v .v c < i 6 2 O.JXA n.B.d.d.d.d.d.d.d.B.d.d.M.d.B.B.S.S.B.M.m.n.n.X.I v l c vXJXJXJXS < > + 5 v f.k.Z.H.=X6XqXqXqX8X8X8X8XqXaXVXSXKXKXoXk = = 5 i I I ..X.b.H.*XwXCXIXUXUXPX+XJXJXJXJXJXJXJXJXJXzX}.SXAXsXuXuXrXrXrXeX!.Q.T.q.h s e 8 7 7 ; ; R.R.T.l.r.>.;.r.Q.Q.Q.Q.Q.Q.W.Y.Y.Y.W.Q.Q.Q.Q.W.I.e $ & 7 7 8 ,.u.u.u.t.t.1.,.,.-.-.-.,.,.<.<.,.g 7 . o o O 8 u.u.r.T.;XyXyX|.g. X@X}.Z < 7 ; e.;XQ.Q.W.W.W.W.E.I.6.h o . 7 h h s e 8 7 & & $ $ & & & ; HXUXUXIXCXiXeXeXeXeX1XuXsXZXAXAXuX9 o o o . * 8 g h g g g ;.<.l.W.Q.Q.E.E.y.1.g s ;.4.f s -.:.<.y.U.W.2XoXJXJXJXJXJXJXJXJXJXJXJXFX`.oXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXvX 5 @ < v f.k.H.=X=X6XqX8X6X6X8XtXqXqXaXSXIXUXKXS * = 2 t a } ..f.n.Z.=XwXVXKXIXIXUXoXJXJXJXJXJXJXJXJXJX`.SXSXAXdXpXuXrXrXQ.Q.Q.Q.I.<.g y e 8 8 7 7 ; y l.l.l.>.h >.W.rXeXrXQ.Q.Q.E.Y.Y.W.W.Q.E.Q.W.Y.r.2 & & 7 8 8 -.u.E.u.u.t.t.1.,.-.,.,.1.t.t.t.1.-.8 . . . o O 1.U.I.T.1XuXZXsX>X_.'.$X}./.9.7 9 ;X1XQ.Q.W.Q.W.W.E.Y.w.-.O X O d g s 8 7 & $ o o $ $ $ & O MXIXUXUXIXSXsXuXuXuXiXZXSXSXIXIXAXB + O O $ 8 d h ;.h g h h l.T.Q.Q.Q.E.:.; & ; 7 q q s ;.:.w.y.U.E.Q.eX.XJXJXJXJXJXJXJXJXJXJXJX).BXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXBXgX`._.k.f.h.h.`.GX,X) / ~ X.] L J T R ^ W W E R J zX 2 6 M HXHXjXI m.M.B.M.d.s.M.s.s.s.s.s.d.d.d.B.B.S.F.F.S.C.C.n.I a x JXJXJXkXk 1 > > : > z .k.Z.=X4X6X8X8X6X8X6XqXaXAXAXIXUXUXPXl = 6 p a } ..f.n.A.=X5XaXKXIXIXIXoXJXJXJXJXJXJXJXJXJX`.sXdXsXuXrXeXQ.Q.Q.Q.Q.1XT.e.4.e s e 8 8 8 7 & ; f ;.f s l.eXrXrXQ.W.W.W.W.Y.E.Y.E.Q.W.W.Y.r.>.% & 7 7 8 8 8 t.E.u.u.u.u.t.t.t.t.t.t.u.u.u.t.-.w o o o o & t.:.T.eXiXsXZXsXyX.X'.9X@X.X,X% 9.>X1X2X2XQ.Q.Q.E.W.E.y.>.$ X o ; s 8 ; & $ $ O ; q 9 ; & $ }.ZXIXIXIXIXAXZXsXsXSXIXIXUXUXUXIXyX0 o % ; d h ;.4.;.g ;.;.z.R.eXeXQ.w.; $ $ ; ; 7 9 f >.l.I.Y.W.Q.Q.rX@XJXJXJXJXJXJXJXJXJXJX#XX,XJX].hXBXPXPXPXPXPXPXPXPXPXGXFXcXcXcXkXkXzXgXXX+XPXPXPXPXPXPXPXPXPXXXPXGXhX]. > < @ : , l N f.Z.=X4X6X8X8X6X8XqXaXVXVXSXUXUXUXBX6 5 t K | o.b.Z.Z.K.5XwXVXSXSXUX%XkXJXJXJXJXJXJXJXJX`.uXuXrXeXeXQ.Q.W.W.Q.1X1X}.9.4.e e e e 8 8 7 ; $ $ 7 8 9 uXuXuXrXeXT.Y.T.W.Y.W.W.W.W.W.E.U.,.e $ ; 7 8 8 7 & -.u.E.u.E.u.t.t.u.t.t.u.u.u.u.t.-.7 o O $ $ e ,.T.rXsXsXZXdXZXsXyX9XjXlXMXMXB 0XyXyX1X1X2XQ.Q.Q.Q.E.Y.w.% . X O . $ $ $ & $ 7 h d 9 ; % % b 0XiXMXZXZXZXiXZXZXIXUXUXUXUXUXUXLXyXj.n f h ;.4.6.;.h h ;.l.R.eXsXuXs & $ O $ & ; s ;.w.l.W.W.Q.Q.Q.rX|.JXJXJXJXJXJXJXJXJXPX).PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXFXvXgX$X$X{.k.h.+.h.O.BXU H i FXHXPXJXGXOX-X{.U F F L gX%X#X%. 1 : > * < z .k.H.=X=X6X4X8XqXqXaXaXAXSXUXUXUXUX'.> 6 a I ..f.Z.D.K.5XwXVXSXSXIXIX`.JXJXJXJXJXJXJXJX|.rXrXeXQ.Q.W.Y.W.W.Q.1XyX.X9.m e q e e q 8 7 7 & O o o 3XsXsXuXrXQ.l.U.Y.Y.Y.Y.W.E.E.E.Y.1.h 7 $ 7 8 7 8 7 & & u.u.u.u.u.u.u.u.E.u.u.u.u.t.,.-.& o o $ $ ,.g 1XAXAXAXAXAXZXZXSXbX,X,X}.9.XX>XyX1XeX1X2XQ.Q.Q.Q.E.Y.w.$ . . o & $ $ $ $ 4.;.s 9 ; & & , 4.}.1X1X1X1X1X1XyXiXMXZXPXUXUXUXIXZX0XMXA 4.6.q.q.4.;.6.6.z.;XuXdXdX; % o o $ % 9 f 6.l.T.Q.Q.eXQ.Q.j.BXJXJXJXJXJXJXJXJXJXGXhXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXFXbXjXoX_.{.h.k. . .+.k. XF D 4 _.PXPXPXFXvXFXBXnXjXOX%X%X#X#XS ).|.GXGXFXU W ^ R J k.OX%XlXcXBXBXnX-X*Xg.#X+X+X$X%.gX#XV %.+X).%X%.).+X`.].%.).#XhXkXkXhX > : 1 z .f.K.P.=X4X4X6XqXaXaXVXSXUXUXUXUXUXl 5 d I } f.b.Z.K.=X6XmXVXAXIXSXj.JXJXJXJXJXJXJXJXGX;XeXQ.Q.T.Y.r.Y.W.W.CXCX>Xz.m d e e e e 8 7 ; $ O o , ZXdXsXuX1XQ.Y.I.Y.Y.T.Y.Y.E.W.E.U.<.g $ % & 7 7 7 7 7 e t.u.E.E.u.u.E.u.u.u.u.u.u.1.,.e o o o o & w t.>.;XSXSXAXSXSXZX0Xj.f ; 7 7 7 B }.}.;X1XQ.Q.Q.Q.Q.Q.E.w.o . o O $ o & $ o 7 7.6.d 7 7 & ; 7 ; b 7.e.x.x.c.}.;X;XyXMXCXUXUXIXIXSXMXZXj.5.7.z.l.e.6.7.7.z.;XyXdXSX; $ o o O ; y m l.R.Q.eXrXeXQ.v.HXKXJXJXJXJXJXJXJXJXJXGX < : j x .Z.H.K.=X=X6X8XqXaXVXVXIXUXUXUXUXCX5 6 c .o.Z.Z.H.K.=XaXaXVXSXSXh.DXJXJXJXJXJXJXJXJXCX}.Q.W.U.r.1.y.u.;XIXSX1Xx.4.f s s e e q 9 7 $ o o z AXZXsXrX2XQ.Y.I.U.Y.Y.Y.W.W.E.E.Y.:.q $ O & 7 7 & & & ,.t.E.u.E.E.E.u.u.t.t.u.t.,.,.-.& o O o $ 7 7 1.r.f >XZXZXMXyX0.* & ; ; 7 7 7 7 q m x.R.Q.Q.eXrXQ.Q.W.q.O o O $ O O $ & $ 4.c.z.f , ; 7 7 q q 9 d 7.7.e.z.x.c.}.;X,XxXBXKXVXSXSXIXIXxXm x.x.c.x.l.z.z.c.>X0XAXIXB ;.; X + ; s 7.c.R.2XrXuXQ.9.DXJXJXJXJXJXJXJXJXJXJXJXJX+XPXPXPXPXPXPXPXPXPXPXPXPXGXGXBXNX|.*XqXtX8X8X6X6X=X=X*XH.O.H F D 4 K JXJXGXnXcXzXkXkX.6.s % ; f z.R.eXuXsXsXA PXJXJXJXJXJXJXJXJXJXJXJXJXJXoXPXPXPXHXPXPXPXPXPXPXPXPXFXNXBX wXqX6X*XZ.+.v > z k.k.+. .c = > t l Z S gXJX 1 k x O.h.[.&X*X*X6X6XqXqXaXVXSXUXIXUXIX6XZ.v .h.k.Z.K.P.6X6XqXAXaXtX].JXJXJXJXJXJXJXJXJXJXHXg.I.1.<.<.r.iXAXiX1XI.6.g e e s q q 8 , * O . z ZXsXyX1XQ.W.I.u.U.I.I.Y.Y.W.E.E.l.g 7 O o & 7 7 & & w t.u.E.u.u.u.t.1.,.,.,.-.-.e e o $ $ $ & 7 7 7 7 1.r.r.r.w.-.8 . . o $ $ & & ; 7 7 ; y T.Q.Q.eXrXpXeXR.q.o $ O $ 7 ; O & 9 z.;Xc.9.y 9 q q d e d f v.c.}.}.}.}..XOX.X@XxXxXwXaXVXIXUXA j.z.x.c.c.x.v.x.}.@XwXVXeXI.w.r.r.s , b z.;XuXsXdX@XnXJXJXJXJXJXJXJXJXJXJXJXJXJXJX 6 ..L [ _ [ [ [ [ @. .vXJXJXJXJXJXJXhXX.( ` ` _ _ Q G c v | ..U #.#.#.#.N.N...$.S.S.C.B.V.n.V.n.n.n.n.b.@.X...v ).HXJXJXJXJXJXHX> *XqX6X=XZ.k. .5 < _.[.k.h.N 6 @ 1 1 r x C BX Xg._.| a.B.M.B.M.B.S.F.F.J.L.J.L.J.J.F.C.F.C.V.C.V.A.K.*X.X6 : : < 5 6 1 : 1 r c O.h.[.*X*X=X6X6X6X6XqXVXSXKXSXUXSXqXo.v .f.Z.H.H.4X4XqXqXaX8X'.JXJXJXJXJXJXJXJXJXJXJXJXS R.q.I.Q.AXAXuXeXR.r.;.s e q 7 ; $ % O o + * yXyXeX;X1XR.Y.I.Y.Y.I.I.Y.Y.E.E.U.-.8 ; O o & 7 7 & -.t.E.E.E.u.t.,.-.-.-.-.-.-.e 7 $ $ o & & 7 7 7 & 1.y.U.r.1.e 7 o . o & & ; ; ; 7 7 ; d ;X1XrXuXuXuXuX;Xz.. $ $ q q e q 8 9 z.3X.X9.r q q q q s q j.v..X.X}. X}.+X.X XxXbXmXmXVXVXIXS B 9.j.9.z.7.z.x.x.}.@X3XyXeXQ.W.I.Y.e.* n z.;XpXAXAXZ NXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX].GXFXBXGXPXPXPXPXPXGXnXcXzX].H.O.O. + : > < 6 j : 6 k v +.[.&X*X=X=X5X6X6X8XqXaXVXSXIXIXVX7Xt N .f.k.H.P.4X4X8X8X7X{.JXJXJXJXJXJXJXJXJXJXJXJXN j.Q.rXsXAXdXuXeXQ.I.<.:.g g s 8 $ O o X + + z >X;X}.;XR.Y.I.I.Y.I.I.u.U.Y.E.U.:.q 8 - o O & $ . :.y.U.E.u.t.<.-.-.e -.e 8 8 8 o o $ & 7 7 7 & & & 1.u.I.r.6.s 7 $ o O & 7 ; 7 7 7 7 ; m >XrXiXZXZXZXZX>X}.O % q d f h g e q 9..X}.O.l u 9 9 e s 9 h.v..X.X.X.X{.}.}.0XCXCXCXBXmXaX-X+ n B 9.B B B m 5.z.z.x.R.1XrXeX2XQ.Q.W.u Z `.e.pXSX0Xe.j.|.JXJXJXJXGXJXJXJXJXJXJXPXJXJX`.GXBXFXGXHXJXPXJXGXvXvXzXzXk.k.k.h.+X].).PXPXgXOX&XH.H / / / / / / ^ W W W E T F #X : : 1 S HXS _.].|.%.a n.B.C.S.C.F.J.F.F.J.J.J.L.L.J.J.F.C.B.C.C.D.J.P.H.+.# + + @ : 5 6 : k x S h.{.*X*X-X=X=X4X4X6XtXqXaXSXIXIXaXv x N O.o.f.K.P.4X8X7XtXH.vXJXJXJXJXJXJXJXJXJX.;.g s q O + + + % + x.}.c.T.T.I.I.T.I.l.l.r.U.Y.Y.y.<.s e 8 & $ 0 u % <.y.u.u.1.<.,.-.e 8 8 w 7 & o $ $ & 7 7 7 7 7 7 $ ,.y.y.r.w.e & & o $ 7 8 7 7 8 8 , , B yXZXZXSXIXIXLXyX}.. u b ;.4.;.h s d b S g.Z z q 7 9 e q + h.{.{.&X X{.'.c.>XMXLXSXSXKXKXmX6 9 : 9 0 0 u u u b 5.j.j.c.;XeXrXrXeXeX2X7., 9 q Z AXj.I.Q.yXMXJXJXJXJXJXJXJXJXnXoXoX].).V %XBXBXFXPXPXPXJXBXcXzXzX#XH.k.k.k.|.0.vXPXPXPXPXHXL / / / / ^ W ^ ^ ( W W E T D .:.g q & + + + + : , j.j.x.v.l.l.I.I.l.U.U.E.E.Y.U.<.g s q 7 $ ; b & :.r.y.t.,.,.-.e e 8 8 8 8 w e 8 8 8 8 7 7 & 7 7 $ w.y.I.e.e.>.q ; o $ 7 7 7 8 q q 0 9 j.yXsXAXSXAXSXmX@Xj. m 4.5.7.6.4.;.f f B A Z b 0 9 9 q 9 % < +.h.h._._.}.>XZXAXIXIXIXIXSXVX, y d 9 : 1 0 0 l B 9.j.x.}.;XyXuXuXuXuXuXeX8 ;.>.6.4.6.R.eXZX`.JXJXJXJXJXJXJXJXS JXPXPXPXPXPXPXHXPXPXPXPXJXnXcXzXhXXX*Xk.k.k.h.0.FXPXPXPXPX{.( / E ] f.-X+.D D F T W R Y O..>.;.<.T.W.W.W.W.Q.E.E.l.-.f s 8 ; O , < g <.<.<.:.-.e w e e 8 e e -.,.,.-.-.8 8 7 7 & & q y.T.W.T.T.l.;.8 ; o o $ ; e b 5.B b v.5XqXaXaXaXpX5Xk.t . B B 7.e.e.q.4.m m B B Z n 0 0 < 9 % % @ N +.h. X>XlXZXLXIXIXIXUXUXSXAXv u u * < < 0 0 z B 9.x.}.;X1XyXiXsXsXZXZXZXq ;.6.e.T.T.1XiXCXO.JXJXJXJXJXJXJXGXS PXPXPXPXPXPXPXPXPXPXPXPXGXvXcXkXhX+X&Xk.k.k.h.).JXPXPXPXKXT R k.xXPXPXPX@X| G Y D F Y D -XgXgX).BXPXPX{.*XGXFX].J ( / / / / / / / / F 4 Y K k. ._.%X#XV XXhXvXFXFXGX{.R R W ^ ^ W ^ ^ / ^ ^ ) X.%XBXnXBXvX].GX#XoXzXcXgX:X}.|.#XcX 6 : x A _.[.{.&X&XH.H.D.K.P.4X8XtXpXdXAXaXv.r c v v h.Z.P.^.^.7X7Xv.GXJXJXJXJXJXJXJXoX$XSXpXeXQ.eXQ.Q.Q.uXeXQ.R.Y.I.1.<.<.:.g 7 + # + + + + @ 4.4.h d s T.Q.Q.Q.Q.Q.Q.E.W.I.:.d s e q & $ l 8 :.-.-.-.g 8 7 e -.-.-.-.:.-.-.-.g 8 8 & & & & 1.I.Q.Q.;X;XT.e.m q O O q m 9.}.].S K.4X8X8XtX8X4XH.k.* + B 5.9.9.e.7.7.4.4.5.B M B B n 6 k 6 6 f.*X5X0XxXCXLXIXIXIXIXUXIXSXaX8X5Xq , = < < 1 6 x Z 9.}.;X>XyXiXiXZXAXSXIXIX0 6.6.r.T.;XeXZXOXnXJXJXJXJXJXJXJXnX|.PXPXPXPXPXPXPXPXPXPXPXPXFXvXzXkXkX 6 : M +._.[.k.k.Z.Z.Z.D.P.^.7X7X8XtXaXtX8X5 z c v o.k.P.^.4X4X7XH. < k x S g.oX,XyXiXZXAXSXIXIXUXPXd w.r.T.R.2XuXZXkXJXJXJXJXJXJXJXJXFX+XPXPXPXPXPXPXPXPXPXPXPXJXBXvXzXkXkXgXk.O.O.k.k.k.oXnXFXFXo.D D zXPXPXPXPXxX-X#XzX#X{. XgX b.G.F.] 3 . . .N | v v l 1 1 S JXJX].{. X[.k.+. .+._._.C r V.J.J.J.D.V.K.^.^.^.^.^.^.^.~.L.J.G.F.F.C.C.V.X.v 6 @ @ : > 5 < C h.h.k.k.Z.k.Z.Z.Z.P.^.7X7X8XtX8X7Xk.6 c v } k.K.P.^.^.7X7X_.JXJXJXJXJXGXJXJXhX>XuXQ.Q.E.Q.Q.Q.rXQ.Q.R.Y.U.r.r.r.l.l.h ; @ # + # @ + h h f h 3XuXpXQ.rXQ.Q.Q.Q.W.U.,.s s f f 8 $ 0 l * 7 8 7 & & & 8 e -.g -.,.<.<.w.,.g 8 7 & $ >.T.1XeXuXsXsXiX>X}.e.m o , 5.c.9XxX$X_. .n.D.L.P.L.F.f.- $ O ; A 9.j.x.z.z.7.7.9.9.5.Z C N M O.c A.P.4XqXVXVXSXIXIXSXSXSXAXaX7XL.L.v.u * 5 < : < 6 Z +.{..XlXZXLXIXIXIXIXUXUX.Xq.l.T.Q.2XuXsX,XJXJXJXJXJXJXJXJXJXJX#XPXPXPXPXPXPXPXPXPXPXPXJXGXcXkXjXzXkX0.K.H.f. .h.+.#XvXcXL 4 D v GXPXPXPXJX-X&XzXhXgXgXgX k.k.N.&X+.k.| o.o.k.#.k.&Xf.Z.d n.S.D.n.X.U I I X.n.V.n.A.D.H.K.*X&X[.+.C nXJXGXg.6 y F.F.n.i N . . . .Z N z r 0 k hXJXvX+. X{.h.k.+.+.].`.Z z a D.K.J.J.D.V.~.^.7X^.7X^.7X^.^.~.L.L.L.L.P.P.J.L.K.X.5 : < 6 6 < Z h.h.k.f.f.f.f.Z.A.P.^.^.7X7X7X7X^.t c c N o.Z.P.~.^.^.7Xg.GXJXJXJXJXJXJXGXGX,XtXrXQ.Q.Q.rXrXrXrXQ.W.T.I.r.y.I.I.T.4.9 + * # @ # = - h f 7.uXpXuXuXQ.Q.Q.Q.W.E.U.,.g e s e & % n z < ; $ o $ $ & 7 e e 8 1.R.R.I.l.l.>.8 & & 7 l.R.eXuXAXSXSXSXiX;Xc.9.o O B ;X0X9X XA O = U @.n.m.X.9 g 7 $ O $ 0 B 9.z.z.j.9.j.9.0.N M c v +.c b.P.=X6XqXVXAXAXSXAXaXaXpXtX^.d.^.N f < 3 = = > k .k.*X0XxXLXIXIXIXUXUXLX9Xc.x.T.R.2XrXiXOXGXJXJXJXJXJXJXJXJXJXJX$XPXPXPXPXPXPXPXPXPXPXPXGXBXvX9XOX.X.Xk.5X=XH.*XZ.o.Z oXzXI D D D .JXPXJXnX{.*X{.{.#X = < d s .XyXiXrX1XQ.R.W.W.W.Y.y.<.g e q 7 $ r N c r * & $ $ $ & & 8 w d ;XyXpX0X;XR.r.-.& ; q T.1XyXdXSXIXIXUXCXyX.Xx.O X 1 .X@X Xx * $ $ * 2 p G 7 g g q ; ; ; ; , , 0 B 5.9.9.j.0.Z z c O.+.N a A.L.P.8XtXtXaXAXaXqXtX8X^.L.p.N b b < 2 = = 5 t .Z.*X6XVXKXUXUXIXBXg.n z.c.c.;X1XiXiX XJXJXJXJXJXJXJXJXJXJXJXJX#XPXPXPXPXPXPXPXPXPXPXHXGXzX.X5X5X*X.XP.4X=X=X=X=XH.k. .XX&XF D D D | bXjX5XwXwXwXwX[.$X#X#X$XkXFXFXBXnXhXk.'.cXU H D D ..#XbX%Xo.o.+.+.o.%.].oX+X#X#X#X#X#X#X%X].%.#X#X#X#X].4 F F F Y Y @.PXPXGXGXFXBXS %X%X#X0.6.;.6.9.`.7.r.u.Y.E.Y.E.Y.Y.Y.u.Y.7.S PXPXPXPXPXPXPXPXPXPXPXPXPXPXGXS oX].zXFXGXkXS S ).%.).|.BXFXBXnXcXkX).C %.].hXcXkXgXhX = 5 5 k l 9 -XxXyX1XR.W.T.W.Y.Y.U.t.,.-.e 7 % ; .N c r $ $ o o $ & 7 7 & z.0XKXSXaXyX1XT.q.8 & g T.3XMXSXIXUXUXUXPXMX>X}.% , 0 '.{.: o o $ & & ; & ; g -.g q 7 7 9 0 b V r X * z B 0.j.O.c c O.O.O.t X.F.P.^.7X8XtXaXVXVXwXwX5Xf.a B m n zXM 5 = - 3 I b.J.8XVXKXKXHXmXz 9.z.}.}.;XyXMXZX}.JXJXJXJXJXJXJXJXJXJXJXJXJXXXPXPXPXPXPXPXPXPXPXHXbX-X*X5X.X,XgX#XH.4X=X=XH.=XH.H.H.[.nXG F F F D 4 |.&XwXwXwX6X&XzXzXcXcX > 5 5 z kX|., A g.A 7.T.I.I.I.U.I.U.y.<.,.-.-.e y 9 * > 5 % $ o o & & 7 7 & ).0XIXKXmXyX;Xq.g 7 o o m c.lXCXVXSXKXKXKXMX>XA k l 1 h.h.= $ $ $ ; ; ; 7 q g g g 8 7 9 q d 5.9.Z 0 % ; < l +.g.6 Z O.O.O.v t b.J.P.4X6X8XwXmXmXxXZ u b B B b -XJXGX).a 2 4 U D.L.4XqXVXwXmXwXZ z |.@X>X,XLXZX1X0.GXJXJXJXJXJXJXJXJXJXJXJXJXXX].PXPXPXPXPXPXGXjX@X5X5X.XgXnXcXkXoXH.5X5XK.*XH.*X=X=X.XLX} D H T R ^ E E T L Z.wX-X-XFXPXPXV V 1 ).`.=XH.=XK.=XP R Y Y Y Y ..kXO.o.Z.h. < t c x .C Z S O.%.h.g.A B b u u < 9 1 k |.].`.O.O.OXoX].0.Z M M a K.D.D.Z.Z.H.4X7X8X8X8X7X7X4X4X^.^.L.~.P.^.^.^.^.^.^.P.X.y x 1 a } @.f.f.b.n.f.b.@.V.D.J.L.L.~.~.L.n.5 I ..n.C.F.L.L.L.~..XJXJXJXJXJXJXJXJXJXGXoXiXdXdXsXrXQ.Q.T.T.I.I.l.<.6.7.h 9 % > 5 t g.BXJXBXA wXpXiX>Xx.x.I.r.l.y.U.U.t.t.t.1.1.1.<.<.-.9 % $ o & 7 7 7 7 & B XKXCXyX3XT.;.7 O o O b .XxXwXqXaXVXBX0X+X1 0.n < v v < O & 7 q s q q 8 e g g q 8 8 e h 5.j.9.0 + 0 r 1 1 z c Z A Z Z I a y f.A.P.=X5XwX XM u n B B B b M oXJXJXGXoXt K ] B.F.P.6X5XqXqX*XH.k.Z $XcXMXrX1XuX}.JXJXJXJXJXJXJXJXJXJXJXJXJX).vXPXPXPXPXPXPX-XwX5X@XzXGXBXnXvXkX+XS A k.=XK.H.H.=X[.9Xf.E / / / / / / ^ W ( F 5XwX{.FXGXgX%.oX#XXXf.=X4X6X6X6XZ.R R Y R R R L o.&Xk.S S %.V %.V oXgXhXkXL Y ..{.kXcXcXcXcXzXhX} F D D H nXGXBXnXnXoXS %X#X#X5.6.6.q.e.9.kXzXzXcXnX|.7.y.Y.w.XXcX%.BXBXFXPXPXPXPXPXPXPXPXPXPXPXnX].zXzXXXkXkXzXcXzXzXkXzXkXcXkXkXkXgXoX.7 $ $ & 7 8 8 7 7 u g.-X0X0X;Xq.7 o . . . o o u .X-X=X=XwX0X-X).oXz 0 X : 5 @ $ ; y d h m d q e g g e 8 8 s ;.7.x.b o.*X*X-XOXoXO.n z M M v a v K = a .k.{.x M 0.g.9.B B N z g.h.FXJXJXJXA J [ s.d.G.P.4X4X4X=X=X6X5Xh.c.T.T.1XuX@XgXJXJXJXJXJXJXJXJXJXJXBX`.BXPXPXPXPXPXPXPXkXkXFXGXGXFXBXnXcXhX).oX#X#Xg.k.H.*Xk.h.Q / / / / / / / ^ ^ W E Y -X5X*XzXbX].%X$X#Xh.6X4X6X8X8X8X8X! R R R R R E J k.].gXgXhXkXkXzXcXcXcX..Y R E E T ..[.OX-XkXjXhXG D H ] nXnXvXvXzX%.gXhX 5 z c z x C S _.).S B n d u 0 9 > : j x /.O.Z x B ).'.g.g.O.O.O.a ..Z.b.f.f.H.6X8XtX8XtX7X7X4X^.^.^.L.L.L.~.~.G.G.~.~.G.p k 2 a I U U b.X.n.@.n.n.V.C.G.G.L.L.L.L.G.p I U n.B.F.F.F.G.Z.zXJXJXJXJXJXJXJXJXJXoXIXIXZXiXrX1XR.T.l.I.I.l.6.h e 9 O 6 a t NXJXJXGXk.aXSXSXSXZXZXuXrXeXT.r.y.y.u.u.u.u.u.u.U.I.Y.y.;.$ $ 7 7 8 8 8 7 , Z X-X@Xc.9 o o . . . . . 0 X-X*X*X*Xx + k , . . @ 2 o * q d 4.7.7.4.s 8 s g 8 e e g 4.7.5.a J.P.=X=X-XOX|.j.B M x c a I , , * 5 A _.C _.].'.j.0.B u A A h. XGXJXJXFX+.U { a.n.D.P.P.^.P.4X4X7X6XN l.R.1XiX.XlXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXPXGXJXGXFXFXBXnXzXhXS $X#X#X$XS *Xv.9XP / / / / / ^ / / ^ ^ W R L 5X-X-X-XzX|.gX$X#X[.4X6X6XtXtX8X7XZ.Y Y R Y Y R E K kXkXkXzXcXvXnXnXnX{.Y Y E E E E E ^ ( ( R ) T T E W +.zXzXzXkXXXS kXkXgX + p I I U f.] n.U n.n.n.N.S.G.G.G.G.L.G.F.i U a.B.F.F.S.F.F.|.HXHXJXJXJXJXJXJXJX].IXIXAXiXeX1XQ.T.T.T.I.v.q.h y ; o } N $XJXJXJX+X5XAXSXAXAXAXZXsXuXyX1XI.r.y.y.U.U.U.U.E.Y.I.I.T.:.$ $ & 7 8 8 8 8 8 9 B '.x.6.& . o o . . o & & & x {.k.v 7 ; $ . o o o o = o 7 d 4.5.z.c.q.s 8 e e e 8 e h ;.7.4.K K.P.4X6X5X-X-Xh.g.v.h.g.v + O $ .k.&X*X+.g.|.}.}.9.B z Z Z g._.JXJXJXJXJX Xt a X.Z.J.P.P.P.P.^.4X6X6XN ;XeXZX|.JXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXPXPXPXGXPXGXFXFXFXFXnXkXgXS $X#X#X#X).PXJXOX/ / / / / W ( ^ ^ ^ ^ W R I -XwX-XOXvX].gX%X#X`. . .k.6X8X8X8X4XU H F F H Y Y T [.cXzXcXnXBXFXFXnXF Y R E E E E ( W ( / / / / W W U 6 j t N +.+.S S A B b d u l k k + k 1 : k C ).0.z l b O.h.h.h.k.h.g.O.x l .k.4X8X8X8X7X7X^.^.~.L.G.G.G.S.G.G.G.G.G.G.m.o 2 a I U U X.X.n.U ] n.N.C.S.S.G.G.G.G.G.B.i n.m.B.B.C.S.S.h.HXHXHXJXJXJXJXJXJX%XjXLXZXiXyXeX1XT.T.T.T.Y.l.4.5.n > < FXJXJXJXLX[.qXIXSXSXAXAXsXuXuXuXeX;XI.y.I.y.u.E.W.Q.eX1X}.T.q.& & & 7 8 e e e e s g g :.-.& $ o o o $ & 7 7 & ; u q s s 8 & o o O $ . @ ; q h 6.l.c.R.l.g 8 8 8 8 8 s g ;.6.5.v A.P.P.4X5X5X*X&XH.Z.k.H.&XH.Z.f.K.*X=X=X*XS ].}.`.9.k B C Z O.k.cXJXJXJXJXS - a ..f.V.J.J.J.P.^.^.4X6X=Xz.iX>XhXJXJXJXJXJXJXJXJXJXPXGX|.PXPXPXPXPXPXPXPXGXGXPXGXGXGXGXGXnXkX#X%.$X#X#X#X%.FXGXk./ / ] k.-XjXjXOXa D D Y Y k.5X*X*XOXvXx gX.;.m ;.4.6.}.vXvXcXkX).k zXGXDXPXGXPXPXHXGXGXGXJXHXFXcXhXkXkXzXzXvXcX%. = @ 5 1 k c . .A S A Z b b d z u r # < + : * x S _.S n l Z h.v.h.[.h._.g.g.S z c k.7X8X7X7X7X^.^.L.L.G.F.S.S.G.S.G.G.S.S.S.I - a K I L U U X.] ] n.m.N.B.B.S.F.G.G.G.G.B.i a.S.B.B.d.S.f.cXJXJXJXJXJXJXJXJXJX'.SXMXyX;X1X1XR.c.T.T.x.R.yXmX@Xg.V gXJXJXJXJXh.aXIXIXSXAXZXsXsXuXuXyX1XT.I.u.Y.Y.W.2XrXuXdXsX3X}.; & 7 8 e e e g g g -.-.,.g & & $ $ $ $ & 7 7 7 q d h ;.;.s ; $ $ & & & & 8 s h q.x.T.R.l.h ; 7 7 7 q e g ;.7.7.p D.J.P.P.P.K.P.^.^.L.G.C.Z.K.A.K.H.K.K.=X*X{.O.j.9.z x B Z Z S *XXXJXJXJXkXj 5 p ..b.n.D.G.J.P.~.~.^.^.^.!.dX'.JXJXJXJXJXJXJXJXJXJXJXcX#XPXPXPXPXPXGXPXGXGXGXGXGXGXGXGXGXBXkX%.+X#X#X#X$X%.vXnX] ^ k.FXGXJXPXHXGXPXjX{.o.| wX5X-X-X{.mXk |. 6 6 6 .+. . .Z Z Z B N b d u 0 # : : : : 1 N g._.S x S |.[.v.[.[.'._.).S S M 6 H.7X8X7X7X7X^.L.L.F.F.S.S.S.S.S.S.S.S.S.d.- t p K K U U U X.] ] a.M.B.B.B.S.S.S.S.G.S.n.p d.B.B.B.S.D.|.JXJXJXJXJXJXJXJXJXzX>XyX>X;X}.;XT.R.R.c.R.yXCXmX9X].C ).JXJXJXBXN VXUXIXSXAXZXsXuXsXuXuXeX1XT.T.R.W.Q.eXuXdXAXSXSX9Xs % 7 7 8 e g e g -.-.-.-.g 7 $ $ $ & & 7 7 8 y f ;.6.q.7.h ; $ & 7 & 7 7 8 s ;.q.l.c.l.l.h 7 ; 7 7 8 e e f 6.9.u f.A.K.K.J.D.K.^.^.~.~.G.D.K.A.A.A.K.P.P.K.K.&XO.z , B B C A h..XvXJXJXnX< @ a I f.b.n.V.S.F.F.G.L.~.~.L.v. XNXJXJXJXJXJXJXJXJXJXJXJXzXgXPXPXPXPXPXGXFXGXGXGXGXFXGXGXPXGXcXgX%.$X#X#X$X$X/.vXvXR F [.HXGXPXGXPXPXPXPXPXPX-XOX{.&XjXvXzXS k S %.].$X).h.k.h. .| K ^ / / / ^ / ^ ^ ! FXGXGXFXFXnXhX%X%X%.D D D F Y R W W W W T o.#XgXgXhXhXkXkX9.u.u.u.u.u.E.u.E.E.E.E.E.E.w.:.8.hXkX%X).nXFXPXPXPXPXPXPXJXPXPXJXPXPXPXJXBXvXvXcXcXcXS $X$X$X$X$X%XS oXvXvXnXFXFXGXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXBX].gXJXJXJXJXJXPXGXGXGXPXGXGXJXJXJXJXJXS cXBXFXFXvXcXnXPXPXGXoXJXJXJXJXJX", -"JXJX].p.p.{ p.{ { { Q ! ! ! L L J L L L ! _ { [ s.s.M.s.M.B.S.S.C.F.C.F.C.V.n.X.I v o.k.a b.~.L.G.J.L.G.L.G.G.G.L.G.L.L.L.J.L.P.=X=X*X&X.Xk.k.k.H.f.I c c v c c c k 5 c | O.PXGX X-XwXVXwXmXmX-X-X*XOX&X&X&X*X=X*X*X{._.O.v 6 < > > 6 6 6 .+.o.O.N Z C Z B B m l 0 0 k : # # # l Z A O.M A }.'.'.'.'.'._.).+.S Z V r K.7X7X7X^.^.~.L.F.S.S.S.B.d.d.d.d.d.S.d.[ * 2 i K L U ] ] U ] a.a.M.d.d.B.d.S.S.G.S.S.U L B.B.d.B.d.h.JXJXJXJXJXJXJXJXJXJXoXyX;X}..X;X;X;X;X;XyXKXCXmX9X XA C oXFXHXHX%X.XSXSXSXZXsXsXiXuXuXuXrXeX1XR.1X1X1XrXZXSXSXSXmXz.;.; ; 7 8 8 e e g -.-.:.:.-.e 7 ; 7 7 8 8 e g h 6.z.r.l.e.h & $ & & 7 & 7 e g g >.l.l.l.7.6.s 7 ; 7 q 9 % 9 b B 0 = b.D.K.J.K.V.D.G.L.G.A.D.J.J.L.J.J.L.P.P.P.K.H.k.r u A 9.)..X+XJXJX#Xk 5 3 K I ..b.N.C.F.B.S.S.F.G.S.a.9.,XJXJXJXJXJXJXJXJXJXJXJXJXcXkXGXGXGXPXPXGXGXGXFXFXFXBXBXFXGXFXkX].].$X#X#X#X#X).cXX;X>X;X3XyXyXCXSXKXVXxX-X{.S x z x zXHXHXoX,XyXZXZXsXiXuXiXiXiXZXiXeX1X1XrXrXiXZXIXVXVX@X4.>.s & & 7 8 e e e -.-.,.:.:.-.e e e e d -.:.q.l.l.l.l.l.6.g & & & 7 & 7 7 e -.-.>.q.e.l.l.q.h d q 9 7 q % , ; : ; * 5 Z.K.J.K.K.A.J.F.J.J.J.L.G.G.G.G.L.L.L.P.P.J.D.V.f.a n '.Z JX#XV 5 2 6 K I U U n.V.F.C.B.d.d.d.d.i.I S FXJXJXJXJXJXJXJXJXJXJXJXJXBXhXFXFXFXGXGXGXFXGXGXFXBXvXvXnXFXvXhXV %X$X$X$X#XS gXvX{.F 4 D O.GXPXGXPXPXPXPXPXPXHX-X*XoX#XoX#X 1 t t l 6 . . .+.h.+.g.A B y a u f u r V C 0 # # : , : # 9.}.`.j.`.`.`.].`.).+.A N x z a K.^.^.^.L.G.G.S.d.d.p.p.p.p.p.p.p.p.i.! z M M l K G L ! ! ! [ { s.s.s.d.d.d.S.d.d.p.~ = s.s.s.d.S.'.JXJXJXJXJXJXJXLXGX C zXHX).yXSXLXZXZXiXiXZXZXZXNXZXyXiXiXZXZXHXSXVX0X9.6.e.6.7 & & 7 8 8 e g g :.:.>.:.;.-.h h h 6.e.T.T.T.T.l.l.>.g % & & & & & - s ;.;.>.6.6.l.l.l.>.>.m b u q & 9 9 7 , ; % a n.K.J.D.J.L.J.J.L.J.G.G.G.G.F.F.G.G.G.S.G.S.B.V.C.M.K +XZ @ @ 2 4 K K ] #.m.m.V.C.C.d.d.p.i.' ` v h.h.k.GXJXJXJXJXJXJXJXJXXX`.%X#XFXFXnXFXGXFXGXGXFXFXBXcXzXzXzXgX%.].%X$X%X%X#X%.zXzX{.F D D D [.GXGXJXPXPXPXPXJXvX{.[.OX%X|.|.oXgXhXhXcXo.E I BX[.&Xk.j.R / / W ( W Y OXnXnXvXvXcX%XV +X#X#X#X#X#XO.D D F F Y Y +.`.).`.oX#X%X%X].;.6.4.4.,X jX'.VXVXmXMXmXMXiXMXZXZXLXLXDXMXNXNXNXCXVXwXv.;.l.q.6.7 & & 7 7 7 8 e ;.;.,.>.q.6.7.6.q.q.l.T.R.R.R.T.l.6.s $ & $ $ $ o $ 7 g :.>.6.>.q.l.l.l.l.z.9.A 9.b , 9 , , * * * * U V.V.F.J.J.P.P.L.L.L.L.G.G.G.G.G.S.F.F.S.S.S.F.S.M.[.gX5 - 3 4 G L ] [ s.d.S.B.B.s.s.p.' ` / a *X=XK.v.JXJXJXJXJXJXJXJXJXoXPX#X`.FXGXFXFXFXFXFXGXGXFXvXzXhXgXgX].`.%X%X%X.>.>.0.gXhXlXhX$X}.e.y.E.E.e.].#X$X%XX+X'.9.x 0 , + X @ S {.pXwXVXmXmXxX9X0XyXMXZXLXZXZXMXMXyXMXwX3XN m e.s 7 7 & & & & $ & 7 ;.6.w.l.T.T.T.I.T.c.T.Q.;XR.c.l.f & . O O O o o o & e g :.>.q.6.l.q.l.l.l.z.x.|.XX`.l ; ; % ; , ; , y K m.B.S.G.G.L.^.4X4X6XP.P.F.J.G.G.G.S.S.S.G.G.F.f.xXk 3 3 4 D T [ { p.s.d.M.s.s.p.' ' / ) I O.wX6X[.nXJXJXJXJXJXJXJXJXJX#XPXPXPXGXGXGXPXGXGXFXFXFXBXcXhXgX%X%.). > z A O.g.g.g.g.g.m ..L.D.a 1 l j 1 , + : 1 1 , 0 9.}.;X}.}.{. X.X{.k.o. . . .v v a 9 n.F.S.d.d.p.i.' ` ` / ' ` ' i.` ( p Z Z C XHX].3 D J ~ ) ' ' { i.i.i.i.i.i.` / ) ] p.s.p.s.d.].JXJXJXJXJXJXJXJX,XK.7XaXVXVXVXCXaXsXmXyX0X>X@X].9.b 0 = + @ @ 6 6XqXqXaXaXVXqX*X*X0XMXMXZXZXZXZXMX0XyX3Xj.h f m & $ $ & $ $ $ O $ 7 h 6.l.T.Q.eX2XQ.Q.Q.Q.;XR.T.;.* o o o . o o o o & 8 s g h ;.6.<.<.<.l.l.c.c.;X>XlX.0.zXkXkXgX%X$X+X%.XX#X#X$X%X + < z Z S 0./.+.0.5.v P.P.A.t 0 x k k 6 # : 1 : # B }..X.X.X.X&X.X&XZ... . .N v a I v 2 n.M.d.p.{ { ' ` / / ` ` ` i.i.` G V S Z %.JXJXS 3 F Y R _ _ _ _ _ ' ' ' ` ` / / [ a.{ p.p.d.[.JXJXJXJXJXJXJXJXJX@X6XqXwXxXMXyXyXyXyX>X;X;X.X}.9.b 9 * + @ = } 7XtXtXtXtX^.7X^.J.m.Z.yXiXZXZXiXiXyX>Xj.0 q d u O $ O $ $ $ O o O 7 s 6.x.Q.eXeXQ.eXQ.eX1X;Xl.u O O + + % + o o O 7 8 8 e q s g ;.>.6.q.w.l.c.;XyXxXBXNXhX% ; 9 9 u m 5.9.}.;X}.}.}.`.x.z.9.d 3 G L n.n.V.V.B.B.d.p.d.U +.jXjX .3 F Y E _ ( / / ) i 5 6 6 t | X.H.qX{.9XVX_.GXHXJXJXJXJXJXJXJXJXFXcXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXoXk ].].S oXhXnXGXJX].XXkXGXJXk.R / / / / / / / / / E I oX%XC H.8X6X=X=X=X=X=X=XF Y Y H Y Y Y G oX > r r 0 6 v M Z A 0.GXLXGX%.Z -X-XgXgXgXgX-X-X-X-X_.O.c c l r r , + + < z 9.9.j.j.9.5.a ^.P.J.n.6 0 M z k t 6 : # : : N }..X{..X*X&XZ.k.j.f.I } I I a v a 5 ] a.{ { ' _ ` / / / / / / ` ` E t S Z S JXJXHXv 4 F T R W ( ( _ _ _ ` ` ` / ` [ [ { { { s.k.JXJXJXJXJXJXJXJXJXhX5XwX0X9X9X0X0X0X>X;X;X.X;X.X9.b 0 ; O @ = Z.^.^.^.^.^.^.^.~.F.s.T 0XyXiXiXiXiXyX>Xx.B d f m 0 o o $ & $ $ o $ 7 u 7.T.2XeXeXeXQ.2XQ.Q.c.q o O O O * % * O ; q q e q q s s g h >.6.q.l.l.R.1XyXyXMXMX`.% , 9 b B 9.x.e.e.5.5.5.4.4.4.5.6.4.3 G ~ a.m.N.a.p.` _ U y `.$XhXwX*X> h.a 4 Y Y G 3 < x x x V .f.H.6X0XiXCXKX'.JXJXJXJXJXJXJXJXJXJXXXPXPXPXPXPXPXPXPXPXPXPXPXPXPXJXBXkXS FXvXGXPXJXJXJXJXS V %XGXo./ / ` / / / / ` / / ^ R | gX%X%.h.*X6X=X=X=XK.=X=X} Y Y Y Y Y Y T L {.jXgXhXzXzXgXK T R E E E E E E W W W T ! @.Z.+.F Y Y ].XX].,X$X'.z.9.5.h e ;.;.;.4.5.hXgX v v 6 J.L.P.4X4X8XaXAXVXSXVXAXSXSXIXIXUXUXUXPXPXBXmX9X-X[.h.t M S Z b 1 1 l x x Z S BXGX%.Z &X-XgXgX-X-XOXOX*X[.+. .Z C x l 5 1 k o % 0 n 5.7.5.5.5.f ^.P.P.J.b.a 1 x c l k j 5 # + : 9. X X{.{.k.k.f.f.I I I N v a a a p 6 L ] _ _ ( ( W / / ` / / ` ` / 6 O.S C DXJXHXGXC D F R E W W ( W ( ( ( / / ( ] { { { { s.H.NXHXJXJXJXJXJXJXJXlX*X5XpXlXyX>XyXyXfX>X;X;X>XXX9.b 0 ; > A $XO.^.^.^.^.~.~.~.G.G.d._ 3XyXMXiXZXiXMXyX;X}.z.6.7.e.z 0 O $ $ $ $ & - s 7.R.Q.eXeX1XQ.Q.R.T.r O o X X X + % O 7 s h f h f d s h f h ;.>.w.r.l.T.;X1XyXyX>X, , 9 d 4.z.c.l.b j : # # 9 9 d h h 4.y J [ [ p.[ p.i.i.K m u @X-X-X5Xh.cXoXx . + , 1 k z x V C A o.Z.*X@X>XyXCXKX].HXJXJXJXJXJXJXJXJX#X#XPXPXGXGXPXPXPXPXPXPXPXJXJXJXFXnXzXXXGXJXJXGXPXJXzX].oXGX+X..^ / / ^ ^ / / / ` / / ^ T {.hX%XS S #X[.k.H.=X*X=X=X&XG Y Y Y F Y Y Y Y X.#XkXkXhX#XF Y R E E E E E W W W ^ W W ^ W W W R I -X%X%X`.:.1.1.r.U.E.u.>.-.e 4.6.9.gXX;X.X}.g.b < * 1 ).).gXv.^.^.^.~.~.~.G.S.d.' .X>XyXiXiXiXyXiXyX;X.X;Xx.c.c.j # o $ $ $ ; 8 f e.T.Q.Q.1XQ.R.R.x.9 % o . X o + o + % s m 4.5.7.5.4.4.;.>.;.;.>.w.r.l.T.Q.1X1X;Xy * 9 u ;.7.c.x.4.k x 0 , : , 0 e f m ;.h 2 ( _ ) G 2 ) G y B b 9X-X-X5X].Z = = = = @ > : k x Z A g.[.k.>X;X>X>XNXPX].HXJXJXJXJXJXJXJXgX).PXPXPXGXGXGXPXPXPXPXPXGXGXGXFXBXcXhX$XGXJXJXJXGXXX%XPXPXPXgXR ^ / / ) Q ~ Y Y H Y R W L zXgX%X%.C +X$XC |.N o.k.K.*XZ.F Y Y Y H H Y Y T L hXzXhX#XL Y R E E E E E E W W ^ ^ ^ W W W R I oX#X#XZ <.<.t.t.t.u.u.u.u.u.u.,.-.h 9.oX].nXnXnXvXnXvXvXnXvXhX].oXzXgXC kXzXhX].5.q.r.r.u.u.Y.E.4.;XXX|.q.e.e.w.e.9.S .I K p a a p K p K I @ 3 L J H H ( / / / / / / / G A O.S #XJXJXJXJXGXS D F Y R R W W ^ ^ ^ ^ ^ P [ { { { d.F.L.k.@XJXJXJXJXJXJX].BXSXZX0XyXyXyXyXeX;X;X}.}.`.0.b > + > = * x }.K.~.~.~.G.G.S.d.p._ v.>X0XyXyXiXyXsXiXyX9X>X;X;X;XB k $ $ $ & 7 s h q.T.Q.2XR.R.T.e.* % O X X o O X O X 9 f 5.z.c.T.x.l.e.q.6.6.:.:.6.q.r.T.T.R.R.5.$ , q d 4.z.x.7.n j , : # , 9 q d f f h m d 3 H 4 y B , 0 b B B {.*X=Xk.S @ = 2 2 3 4 i a 6 x C O._.}. X;X;X>X>XyXHX].JXJXzX|.].hXJXoXoXJXPXPXPXFXGXGXGXGXPXPXPXGXFXnXnXvXzXhXoXJXJXJXGX`.NXPXPXPXPXk./ T o.-XzXFXHXbX[.J T H T h.jXhX$X).%X1 1 gXGXgXk.&XZ.+.jXjXU H Y Y Y Y Y H R OXzXhX$X#X%.D H E E E W W W W W W W R R T o.BXS oXC :.<.1.t.t.u.u.u.u.u.u.u.u.y.r.:.`.vXBXnXnXvXvXvXvXvXhX`.$X+X}.}.}.z.}.}.7.7.6.q.r.I.Y.u.Y.T.}.+X,X6.6.q.e.e.e.S X>X;X+X.Xj.Z c < * > : @ @ 9Xh.D.G.G.S.F.d.p.' [ +X.X>XyXMXiXMXyXsXiXMXlX0X0X>X0.% $ & & 8 e f 6.l.R.2XQ.Q.R.T.y O % O O O O + O O o q m j.v.c.;X;XR.x.l.6.4.:.4.:.6.e.l.T.;XT., ; 9 9 d m 5.e.4.f 1 , , , 9 q b e f b m h h d u b B 5.b , u b 9.O.*X[.zXV @ 2 3 p K K L K I 5 C Z S g.}.@X>X0XyXyXCXnXoXGXJXPXJX$XS oXPXPXPXPXPXGXGXPXPXGXGXGXGXFXFXnXvXcXhXgXS GXJXJXC GXKXPXPXPXPX] Y '.cXFXHXPXJXPXHXFX{.k.{.kXkXhX%X).kXx $XDXcX{.=X*X*X{.GXKXPXo.F T R Y Y W R -XvXgX%.oX#X%.G 4 D D F Y Y T T P U o.OXcXPXPXBXV -.<.<.t.t.u.u.u.u.E.u.u.u.u.u.u.y.8.nXnXvXvXcXcXcXcXzX).|.R.;X}.T.}.}.x.}.7.4.h w.y.u.Y.Y.e.yXr.w.`.4.8.4.6.r.e.V %XzXJX : @ @ `.LX@X'. .v v a c .X>X.X.X>X0XpXmXwXtX8X8XyXbXMXlXyX9.% & 7 8 e s ;.e.I.Q.Q.eXQ.R.l.$ O O O X + % + $ % o u 5.z.c.;X;X1X;XR.l.6.:.4.;.6.7.e.z.c.R.6.% , 9 q u b 4.7.5.f 9 , 9 9 9 u f b f h ;.m h b b b n B x b : l 9.k +XGXGX6 = 2 D G ! ] ] ] ] K 6 C C g.'.>X9XyXyXMXSXUXoX].PXJXPXPXPXPXPXPXPXPXGXPXGXPXPXGXGXFXFXFXFXnXcXkXhX c x O.4X6X8XqXqXVXVXVXSXSXSXIXIXVXVXVXaX6X6X=X=X=X=XK.H.H.k.h.B u + # + : , , k M 9X-X).V 1 > 1 c v +.{.{.{. Xz l B b 7 0.JXJXJX|.b N .X.X}.c 4X4X4X7X4X4X4XP.=Xk.O.: 6 6 j k k x n y ..f.f.X...X...X.X.U I L I L K l k k x v t D E / / / / / / / c +.S JX%XJXJXJXJXJXGXoXK F R W W W W W E R W ^ T G s.M.B.S.F.L.L.g.JXJX @ S JXJXJXg.>X}.7.b < $X-X-X3XwXmX6X8X7X7X4X4XwXCXMXlX7.& 8 8 e e g >.e.T.R.1XQ.R.x.m + o X O O O $ $ O O O f 7.}..X;X;X1X1XR.l.7.4.4.5.5.9.9.9.j.z.9 , , 9 0 d f 4.6.7.4.h 9 0 9 u q f f m m m m b m b b b n B C x r M `.JXJX$Xk 2 4 D D ! ~ ! ~ Q [ ] 6 M A ].$X0XMXMXMXIXUXUX.X[.#XPXPXPXPXPXPXGXGXPXPXPXPXPXGXGXFXFXFXBXvXcXhXX(.1.u.E.E.E.E.9.zXzXzXkXkXkXhXhX l x v =X=X=X=X=XaXaXVXSXSXSXSXAXaXqXqX8X6X6X6X4X=XP.P.A.f.k.0.z ; + % : > : : n >X>X0.k , < k l c O.]. XOX Xk * N d 9 O.JXJXJXJX0.5.OX.X}.5.k.^.4X4X7X7X7X7X=XK.k.z < j j k k x z r } f.f.n.X.f.@.@.X.U U I U I L N 1 r l V 6 Y W ^ / / / / / / v +.Z GXGXJXJXJXJXJXJXJXZ F R W W W W W W E ^ / / Y ] s.B.C.F.L.L.o.g.h.*XtXtXpXaXVXVXKXKXVXqX6XP.P.J.J.n.b.I a * + : # @ C GXJXGXC OX}.5.z * g.9X-X5X=X=X=XP.P.L.P.P.P.mXnXlXb 7 7 8 e e g >.l.T.R.1X;XT.7., X o O O O O O O o o 9 m 9.v.}.;X;X;X;XT.l.e.7.7.9.z.9.9.9.9.e.* ; , q l d f 4.5.7.5.4.e q q u d f h m ;.b m m b b b n n B Z S C M @XJXLX0.5 2 4 D J ~ ) ) ) _ { { 6 M S {.-XmXmXKXHXKXUXUXaX6X8X_.PXPXPXPXPXPXPXPXGXPXPXGXFXFXFXFXFXnXcXkXgXX@XPXPX].hXhXzXcXzX).].].#XhX k x x {.h. .O.[.5XaXAXSXSXSXAXaXqXqX6X4X4X7X7XP.=XP.P.Z.f.j.A u + : : < 1 # 9.>X}.x 1 : < 1 k x O._. XoXoXg.* } ..< C JXJXJXJX).'..X}.R.c.v K.P.=X7X7X8X7X4X4X&X+.1 r 6 1 k u l 0 a I f.b.n.b.n.n.n.n.U U U L P +.g.j l V D E W / / / / / / / a +.v GXGXJXJXJXJXJXJXHX].D R E W ^ W ^ W ^ ^ / / / J s.M.B.S.F.J.b.4X^.7X^.tX8XtXaXaXaXaX6X7X^.P.J.J.V.b.X.} c ; % + + @ r oXJXJX|._.'.A l 9 N 0X*X=XP.J.G.G.G.G.L.4X=XMX,Xj.7 7 7 8 8 e f 6.v.;XR.R.T.7.u . O O X O O o % % % f 5.6.e.z.x.x.c.;XT.e.e.e.x.x.}.}..X.X}.}.7.d : 9 q q d f m 6.e.7.5.m l d d f b f d b f b b b b b n u x n x x ).HXJX9Xt 3 3 D F T Q _ _ ' ' ! 2 k v h.*X-XmXVXKXSXIXUXSXqX6X6X1 PXPXPXPXPXPXPXPXPXPXGXGXFXFXFXFXBXvXzXhXX;Xl : : 1 1 k c Z _.]. X|.g.r 6 Z.v 1 X>X3X0X-X*X}.7.; 9 0 e b b m 6.7.7.e.5.b m b d b d u b m d b u l b 0 n x B A C FXHXoXz 3 p D D H T _ _ ` _ G * 5 z .k.*XwXVXKXSXSXSXSXVX8X4X7Xj PXPXPXPXPXPXPXPXPXPXPXPXFXFXnXnXvXcXhXX.X>X Xm m 4.m e.x.zX S JXJXJXJXhX[.AXSXAXAXaXaX8X7X4XP.=XP.4XP.P.L.L.J.V.b. .g.n 1 * : : 1 ;X;X>Xg.0 j : 1 6 l V O.].'.}.j.B , .Z.r 6 kXJXJX0.v.x.x.z.l.e.7.v Z.H.^.4X4X4X4X4XH.k.r < 0 0 0 q u 5 } f.b.n.n.D.V.V.m.a.n.U U P ] _.kXx v 4 W ^ / / / / / / ( I o.K n._.JXJXJXJXGXGXPXHX_.F E W ^ ^ ^ ^ E H G G ! p.{ M.d.B.S.F.G.f.^.^.^.^.^.^.^.^.^.^.~.L.L.L.F.V.f.f.| N 0 * , 7 , , < < 1 S vXLX`.).Z M r A.s.' ' i.i.i.p.p.p.K.0X}.q e e q 7 8 q b j.v.3XqX=X=Xz.q 7 & o O X o * q b 4.7.l.r.l.l.w.6.6.6.6.6.5.7.c.>X0XyXwXsXaXwX5XK.z.4., q b q d b 4.4.7.7.5.m m f u 0 u w u d b d l b b n n V 0.C zX+XC # a p K L F R R _ { { J # > 6 z | Z.=XwXVXVXVXSXSXAXpX7XP.K.#XPXPXPXPXPXPXPXPXPXPXPXPXGXFXnXcXcXhXgX%X%XC ].$X).V cXPXPXPXPXPXPXk.) H / / / / ^ W ^ E ! XhX{.6X6X6X=X=X+.].OXOX#XL W E F I hXjXzXzXcXcXcXzXXXD Y R R R E T K oXGXPXPXKXO.4 F K ].|.k oXBX_.oXFXNX;.h g h ;.;.vXPXPXPXPXS ].%.%.+X#Xx .6.;.4.4.z.}.3XMXZXZXaXaXtX7X=Xj.7.q 9 q b b f ;.m 4.7.7.n b b u 0 9 k u u u l b b n l V S ).oX#X1 : 6 K K I U ] [ [ [ { s.X.t 5 6 c o.K.6XqXVXVXKXVXaXaX4XP.J.3XPXPXPXPXPXPXPXPXPXPXPXPXPXPXJXBXzXkXgXXyXyXiXiXpX3X5X3Xz.z.7.; q q d d d d m 4.8.4.b b d u l x b b u 0 b b 9.Z GXJXJXJXS < > a I K U U $.[ m.M.M.B.b.@.X...| Z.=XqXmXmXVXVXaX6XL.J.N.-X].PXPXPXPXPXPXPXPXPXPXPXPXPXPXGXBXcXhX.6.q.7.7.b < K.P.4X^.7X^.P.P.f.t 9 0 l z b 5 I f.n.D.D.V.V.V.n.] U U U [ I GX#Xv x 2 W / / / / / / ` N o.+.Z.N.M.+.GXGXJXJXJXJXJXJX_.# = 3 3 p i G L ~ ~ { { p.s.d.d.B.B.f.NXv b.S.~.~.~.L.L.L.G.G.F.C.V.n.k.f.M z r 0 u 0 u u 0 q l x V C `.JXJXJXJXg.V.d.M.C.V.V.D.H..X;X;Xq.d . ; s d h B h..XwX6XC.A.N ; e s s % & s N z.x.T.R.T.I.l.r.w.w.e.6.5.7.T.R.eXyXyXrXeX3X3X3X;X7.e.z.0 9 q 9 9 7 , b m 5.4.m b b n B 9.B V V 1 b 9.[.$XJXJXJXGXC : 5 a I ..U ] n.n.m.M.s.M.m.] ] ] [ K.K.*X-X[.+.k.P.J.A.k.9XHX#XcXPXPXPXPXPXPXPXPXPXPXPXGXGXFXnXzXhX$XS %X%XS GX|.PXPXPXPXPXPXPXHX0X) -XPXPX&XT Y H / / ^ ^ W T H.+XFX].k $X$X$X$X`.+.F F D F Y F [.zXhX%X#X).S K F Y R R R R R R R R R R R Y o.FXnXFXFXhXgXgX`.;.<.y.t.u.u.u.1.-.e ;.9.PXJX+Xk V ].hXkXoX$XzXcXvX%X.XuXuXuXuXuX;XdXyXpXdXdXdXdXdXdXdXsX;XT.m ;.+XgX u z b n 1 a X.b.b.D.n.n.n.n.@.@.] ! ~ 4 v k c x x 4 ^ / / / ` / / K Z.o.&XZ.A.N.|.JXJXJXJXJXJXJXJX].a 3 6 4 G G J ! ~ [ { { p.d.B.d.d...JXLX.Xj.D.G.~.L.L.G.G.F.C.A.V.Z.k.+.Z n b l l b d l b l b V V n `.JXJXJXJXNXk.V.V.Z.H.*X*X*X3X;XR.m f o ; q d f B g.{.*X*XA.H.9.; e e s 8 . ; y b 7.x.c.T.T.l.I.r.r.w.q.6.q.e.T.R.1X1XeX1X1XeX1X3Xc.q.z.z.4.& , , : # + & q 0 b b b m B 9.0.9.`.). .r }.].JXJXJXJXJXM # t I U X.U b.n.m.m.a.a.a.[ ] [ [ _ $.v H.K.K.P.=X=Xk. XJXJXJXGX].PXPXPXPXPXPXPXPXPXGXGXGXFXBXvXzX S FXJXJXJXZ c =XwXqXqXqXqX8X6X=X=XH.K.P.P.^.P.J.Z.f.k.k..X.X.X.Xc.c.z.5.y r t * : 0 r l l x V x x r 6XqXqXqX=Xc + ; f h h ;.>.4.6.7.5.B r K.P.L.^.7X^.^.K.Z...< x M < X a } b.b.n.b.n.b.] ] ] ~ ~ D D J J 6 v x x 2 W ^ / / / / G o.+.{.&XV.$.+.GXPXPXGXHXPXGXJXGXc t 3 K G G L L ! Q [ [ [ $.d.d.d.U FXPXJXJX#Xh.H.G.G.F.S.C.F.D.Z.Z.f.j.O.Z Z n n b n b n M v V x x ).HXJXJXJXJX9Xk.H.H.*X-X-X>X3X;XT.6.s 7 o ; 7 9 q b A h.*X*X&X.XA ; e s e q $ $ 9 d B z.x.c.l.l.l.1.q.q.6.6.e.e.T.T.1X;X1XR.Q.1X1X1Xx.e.z.z.9.* % + + + , : , 9 u b m 5.9.0.0.`.}.{.k.M A GXJXJXJXJXJXn * a } ..@.b.n.m.m.m.a.[ [ { [ _ { _ K k.H.K.=XP.k.].BXJXJXJXJXJX%XhXPXPXPXPXPXPXPXGXFXFXBXBXvXcXkX#X%.$X%X$X%.kX#XPXPXPXPXPXPXPXPX-XR jXPXPXPXPXFXH.J R ^ ^ T vXOX&XBXkX).$X#X%X%X%X a } ..X.U U U U U ] ! G J R T R 4 z x 5 D R ^ ^ / / / J o.+.+.*XH.N.N.oXGXHXPXHXGXGXJXJX%.4 4 p 4 G G G ! ! _ [ { { d.s.s.X.cXJXJXJXJXJX-Xj.n.d.B.B.B.M.m.b.f.f.h.h.g.A N Z M M M C C C x k %.HXJXJXJXJXJX].&X-X-X-X0X3XeX;XT.R.z.h q & $ & ; y m j.{.OX*X.Xj.9 e q e e $ . ; 9 d 4.e.l.e.1.<.q.>.6.>.5.e.e.e.T.T.R.W.R.R.R.1X;XT.x.}.c.9.9 . % 0 B B Z V q b m B B 9.9.9.9.'. X.Xg.cXHXJXJXJXJXDX1 5 v } X.@.m.N.M.m.] $.[ [ { _ _ _ _ K Z.H.P.H._.nXJXJXJXJXJXJXJXJX].PXPXPXPXPXPXGXFXBXnXnXcXcXhXgX].%.#X%X$X%.JXXXPXPXPXPXPXPXPXPXX3XdX9.u.nXJXGXXXcXcXcXkXzXkX).S %.zXnXBXBXcX].oX > 1 1 k j -XwXwXaXaXwX*X> * 9 q h ;.6.5.j.5.9.B l a J.J.L.^.P.P.J.D.n.@ % # + + O * 5 p I I L K J i # K Y R Y R i x c 3 Y E W ^ / / / G o.O.+._.&X$.$.+.GXPXGXHXHXPXHXGX_.i 4 p 4 G G G L Q _ [ { M.M.s.p.n..4.;.N 9.9.e.z.I.T.T.T.T.T.R.;XT.T.}..X}.0 O * b 5.0.0.`.V m f m 8.5.4.5.9.`. XOX[.].JXJXJXJXJXJX: l | o.f.n.m.M.$.$.$.[ [ _ _ _ _ ' ` K k.H.J.[.HXJXJXJXJXJXJXJXJXGX`.PXPXPXPXPXGXGXFXBXnXcXkXgX%X%X%XS oX$X#X].JX].PXPXPXPXPXPXPXPX-X) jXPXPXPXPXPXPXPXPXvXnXPXPXGX*X XS $X%XgXgXgXgX 5 j k O.wXVXwXVXqXwXk.* ; 0 h 5.5.7.z.j.g.9.n l 5 A.J.L.P.L.J.J.V...X + : + * * # + = y t v M O.h.a F Y F Y i v x 4 E W W W / ` / 3 .O.O.+.{.Z.N.o.kXGXGXPXHXGXPXPX).p p 4 p G G G J ! _ [ { { s.p.s.a. XJXJXJXJXJXJXJXJXh.i.i.' p.s.[ b.b.b.k.k.f.h.+.N c l t c c j 1 %.PXJXJXJXJXGX< >XhX9XyXyXyXeXeX2X1XeX;X>X>Xm . ; s 6.j. X-X9X9X_.7 q q 8 $ . O 9 u f 4.>.6.6.>.;.;.m M 9.j.z.e.l.I.I.I.I.T.T.T.T.T.;X>X>Xz O 7 e 9.9.}.}.}.b f m m m m B 9.x.|.@XoXZ JXJXJXJXJXJXj c .f.b.V.N.M.$.$.[ ] ] [ _ ( ( _ ` | Z.H.+.BXGXJXJXJXJXJXJXGXGXPX%XPXPXPXPXGXFXBXBXnXcXhXgX%X%X%X%X#XS %X%.FXGXnX].PXPXPXPXPXPXPXOXR cXPXPXPXPXPXPXPXPXPXPXPXPXPXOX[.`.%X 1 6 l l 0XwXaXaXaXwX=X .% q B 6.z.z.z.x./.g.0.B , y ..F.J.L.L.J.C.@.r z > : # : 1 1 1 1 x C %.+.).Z .5 6 2 @ t z F ^ ^ ^ / / / J a .O.Z O.+.{.$.$.g.GXPXGXPXGXHXGX].a p 4 4 4 G G G ! Q _ ' { { p.i.p.h.HXJXJXJXJXJXJXJXbXF.' ` ' { [ ] b.b.f.f.f.f. .v i 2 = 2 2 6 : /.GXJXJXJXJXBX0 }.hXlXMXyX1X1XeXiXyXyXyXMXCX>X8 7 ;.6.z..X,XjXxX.X9 8 8 8 $ . O 7 9 d h 4.6.;.h f n V Z 9.x.x.x.r.y.e.l.l.l.x.T.x.;X>X0XM % 7 f 5.z.;X;X+X4.q m m m m 4.7.x..X%XOXZ JXJXJXJXJXJXl c .f.b.N.a.$.$.[ [ [ [ [ _ _ ` ` ) .Z.Z.k.vXJXJXJXJXJXJXJXJXGX+XGXPXPXPXPXGXFXBXnXzXkXhXgXgX < l b n V C C x x x %.6 F R E Y 2 J ^ / / / / ~ 6 v O.A C .+.[.| $.| GXJXJXGXPXPXGX].v 3 4 G p 4 G J ! [ _ ' ' { { i.p...HXJXJXJXJXJXJXJX Xs.' ( ` _ ) ] ] f.f.f.f.f.o.I y = @ @ < : + x FXJXJXJXJXhXb l ;X>X>X;X1X1XrXiXiXiXZXZXLXyX5.8 h 6.q.l..X0XlX0X@Xu 8 8 7 $ o . o o & 7 q h h g y n B 0.0.`.}.}.}.;X}.T.T.c.7.z.9.9.}.,XMXC % q f 7.z.}..X>X9.l e b m m 5.9.}..X#X XZ JXJXJXJXJXGXr c ...n.a.a.[ [ { [ Q Q Q _ _ ` ` { Z.Z.V.V._.JXJXJXJXJXJXJX].).GXPXPXPXPXPXGXFXnXnXzXkXhXgXgXgXgXgX%X%X%.].JXJX].kXBX).#XPXPXPXPXZ.T JXPXPXPXPXJXJXPXPXPXJXGXnXcXgX*X{.gXhXjXOXT ^ ^ W ^ / ^ ^ / W F E T J h.#X%X_.].FXGXo.F G G F _..>.4.x.5.7.z.m x.kXBX4.z.;XR.c.c.;XeXc.$XFXGXHXPXJXGXnXkX).V V S $X%XcXvXcX|..X;XuXuXsXsXdXsXdXdXAX3X+XPXPXXXy.0.|.`.+XkX%.FXJXJXJXGXPXGXGXGXPXPXnXS $XkXnXBXFXFXGXPXPXPXPXPXPXPXPXPXPXPXPXPX].GXGXJXJXGXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXvX .d.d.d.d.S.S.S.F.F.F.F.F.J.J.J.V.b.X.} K M ].JXJXJXJXJXJXJXJXcX: c v a f.Z.K.P.4X7X4X=X4X6X-XmXmXnXBXcX0X>X>X[.g.V l > j x k k k j wXqXqXqX6X5X=XH.H.H.H.K.8XqXaX5X3X;X}.x.x.c.T.c.c.z.v qX8X5X*Xt + @ : : > < r 1 OX-XwXwXqX6X5XH.N b 9.j.}.}.}. X X'.0.0 b b 9.M } N.N.U t '.'.O.C x c x < x n M M V z x x 2 R W W ^ / E E / / / ^ ) * > 6 t C C Z O.%.| #.K HXJXJXPXGXGXPX).c 4 4 3 4 4 G L ! Q _ ' { { ' ' p.] gXJXJXJXJXJXJXBX} { { ' ( E ~ [ ] n.f.U b.X.} a 5 @ @ @ 1 : + x JXJXJXJXJXgXd 9 x.;X;X;XR.1XrXiXZXZXLXIXLXKXz.>.,.w.l.I.T.>XyXlX@Xu q e 7 O o o . $ & y u y m 9.0.`.}.|.oX;X;X;X.X;X;X.Xx.j.9.5.9.+XhXx * q h 7.e.}.;X>X;Xb q b m m 5.9.x.oX#XoXA JXJXJXJXJXFXk c | ..b.a.[ [ _ _ Q Q Q Q _ ` ` _ p.m.N.N.M.X.vXJXJXJXJXJXoX].PXPXPXPXPXPXPXGXFXBXnXcXkXkXhXgXgXhXgX k k k k k .qXqX6X=X=X&XH.k.k.k.Z.4XqXVXVX0X>X}.c.l.x.c.T.c.c.j.qXqXwX*X*X: # : : : > 1 r v -X-X6XqX8X6X=X&Xj.g.}.}.|.;X.X}.h.O.r m B B 0.9.v I l x.`.).).O.C Z O.O.r l M n x x x x D R W ^ / / / / / ^ ^ Q i % 5 K a M C C C +._.I U cXJXJXGXGXGXKX%.6 2 D H 3 G G ! Q Q _ ' _ ' { { { p.h.JXJXJXJXJXJXS Q ' ' ' ( ) [ [ ] ] ] X.] X.I a a 3 = > k : + j JXJXJXJXJXcX0 q 9 x.T.T.T.;XuXZXLXIXPXIXKXCXz.6.>.r.r.x.R.;X3X9X>Xb q d q $ . . o . . o $ 7 d 4.5.z.x..X;X>X>X0X,X>X;X+X;X>X;X}.j.B 9.`.|.# ; s m 6.e.x.;X;X;X7.b e m B 4.9.z..X@X XS JXJXJXJXJXJX1 a v U U [ ) _ _ Q Q Q Q Q _ ( ( _ ' M.M.s.{ { '.JXJXJXJXJX%XPXPXPXPXPXPXPXGXFXFXBXnXnXcXkXhXhXgXhXgXgXgX.-.-.;.:.-.+XPXPXPXPXPXPXFXFXzXx OXv /.fXhX7.e.4.m w.7.z.j.z.x.R.6.U.w.4.m c.R.c.v.c.x.z.x.9. k k l &X5X5X*X&X&XZ.k.h.k.k.Z.qXVXKXmX3X.Xc.z.z.c.}.}.z.H.qXqXqX5X-Xl @ : : * * < 0 0 g.*X5X4X6X6X=X&X*X X|.|.}..X@X.Xv.O.r B b B A B B B 9.9.`.`.`.`.+.).g.h.o.x l x b x x n 2 T W W ^ ^ ^ ^ W E G a x @ t K K x C C O.S | @.U jXPXGXPXGXGXGXx 3 R E D 3 G J ! Q _ _ _ ' { ' p.{ p.o.LXJXJXJXJXJXZ ^ / ( ( ( ( _ ) [ ] ] ] U U K a t 5 = l r > # l JXJXJXJXhXDX1 9 4.T.T.R.R.1XMXZXZXIXLXLXZX0X7.e.<.<.w.l.I.T.;X;X;Xj.d f d % . . o o % m 7.7.z.c.}.;X>X;X0XyXfXyX0X>X0XfX3X>X@X.XN 0 % & 7 s 4.7.x.T.;X1X;Xz.5.f m B 5.9.}.OX@X}.C |._.FXJXJXJXM c a I ] ~ _ _ Q _ Q Q Q Q Q ( _ ' ' s.s.s.{ | FXJXJXJXJXJX#XPXPXPXPXPXJXGXGXFXFXBXnXnXvXzXkXhXhXhXhXgXgXgXgXS GXoXcXzXkXX}.9.z k 0 1 : # : 1 l .*X*X&X[.[.k.h.+.+.o.=XVXSXCX0X;Xv.x.z.c.c.}.9.=X6XqXqXwX*Xg.+ > : : % , 0 9 < {.-X6X6X6X=XH.&X-X X]. XOX@X*Xk.h.t B b b m n b b N 5.j.j.'.}.].[.[.h.k.+.k l x n x x c 3 R W ^ ^ ^ ^ D 6 v v t 6 a } K x C O.O.O.v #...FXGXPXPXFXPXkX@ F Y E 2 G G J T Q _ _ ' { ' { ' { p.h.JXJXJXJXJXJXFXn./ / ^ ( ( W E Q ~ ! P K p 6 3 6 t t t 6 > + C JXJXJXS 9 b z d I.T.R.1X1XeXuXiXZXZXLXiX>X}.>.q.>.<.<.w.e.x.c.R.c.x.m u s & . . X o O B 7.7.z.c..X.X;X>XyXyXyXyXiXMXyX5X3X3X5X*X*Xk.* & 7 q f 4.e.x.R.;X;X;Xc.x.B 4.5.9.j.{..X*Xg.0 l j /.JXJXJX).a p L ! ~ ) _ Q Q Q Q Q Q Q _ _ ' { s.p.{ [ -XPXJXJXJXJXJX%XgXPXPXPXPXGXGXGXFXBXBXnXnXnXcXzXzXkXhXhXhXgXhXgX#X).#XzXkXhX#X].GXGXGXGXGXPXPXPXPXPXFXnXkXgXhXkXgXoX]..,.:.#XvXvXnXBXBXnXnXgXg B 5.7.c.e.I.I.T.hXGXPXPX].X}.j.B k 0 u 0 < % + + 1 1 c v M Z O.O.A Z O.+.wXVXKXxX>X}.x.x.}.;X.Xj.P.8XqXqXqX5X_.: < , : , , , % , r -X5X8X8X4X*X&X&X X[.{.-X*X3X*Xh.r m z u u u d n b B 5.9.z.|. X X.X{.{.h.+.r n V V z l k 4 R W W ^ 2 6 c c c 5 6 I ..| k C O.O.O.K #.o.GXGXPXGXPXPXx 3 4 D E D G F J ! Q _ _ ' ' ' ' ' p.{ [.JXJXJXJXJXJXJX#XJ / ^ ( W ( E R Y L K p 6 5 2 5 6 t a 6 > : |.JXJX].u d d f 6.x.T.1X1XeXuXuXiXiXiXyX>X}.7.g <.,.>.<.w.q.e.l.T.x.x.e.u 9 & . o $ O o X y 7.e.z.x.c.H.H.*X0X0XiXMXZXCXqX6XP.P.J.J.A.A.A.X.- q s h 4.z.T.;X;X1X;Xc.}.c.j.}.}.v..X&X{.h.0 r k k ].JXJXoXt K G ! Q Q Q Q Q Q Q Q Q Q _ ' ' ' p.[ o.9XJXJXJXJXJXJXJXJX`.PXPXPXPXPXJXGXFXBXnXnXnXnXvXcXzXkXhXhXhXhXhXhXhX%.#XkXzXhXS FXGXGXFXGXPXPXPXPXPXPXFXBXnXkXzX#XZ ].%X.1.u.u.E.E.E.u.t.,.-.e e ;.%XJXnXcXV 0 8.X Xj.9.b k u l k 0 , + + 5 k + x 6 > : @ k N .k.VXKXBX9X;Xc.c.}..X;Xg.4X8XtXqXtX8X6Xl < < , , < , d q % O.5X8X6X4X=X*X*X&X{.{.-X-X5X*X .r b u 9 0 u N 5.5.m m 5.9.}. XoXOX{.{.H.+.r M x V l k x N a J R i | c z c x 5 6 } ..} t Z O.O.O.@.#.+.PXGXGXPXGXBX> 3 D F Y T J ! T T Q _ _ ' ' ' p.{ { p.[.JXJXJXJXJXJXJXJXzXO.P ( ( W W Q ! G G 3 2 2 = 5 6 i t 5 = 1 BXJX+Xz b m h h e.x.;X>XeXeXrXuXiXiXyX>XR.z.;.e -.-.>.<.<.<.6.q.e.r.e.e.4., $ $ ; % O . % 6.7.e.z.c..X>X0X=X-X9XlXCXCXqX4XP.F.B.M.d.M.s.M.m.3 y s h 6.l.R.;X1X;X;XT.R..X@X>X-X&X&X&XH.{.z u l l k XXJXBXz p L ! ~ Q Q Q Q Q Q Q _ _ _ ' ' { v '.HXJXJXJXJXJXJXJXJXJX S %.XXXX).FXhX+XXX|.XX%X : : t C O.*XCXCXxX>X;X}.}.;X>Xj.4X8X8XqX8X8X8Xt : < 9 1 % u s q 7 < *X5X8X6X6X=X*X*X*X{.{.*X{.o.r d u 0 9 0 z B j.j.9.5.m 5.j.|.|. X X.Xk.N z V x x k k n Z o.f.K } X.t c v 6 6 a | ..| x O. .o.&X#.#.k.GXPXGXGXKX#Xa 3 D Y Q E Q Q Q Q _ _ _ ' ' ' ' p.{ p..XJXJXJXJXJXJXJXJXJXJXzX+.~ ( W E T J G 2 2 2 2 3 6 i i 5 @ A JXjXB 5.4.4.4.6.x.T.1XeXeXeXuXuXuXyX>X;Xx.6.q q e -.:.:.:.;.>.>.q.w.l.l.q.f $ $ $ o o y 6.l.e.x.;X>XlXxXwX6X*X-XxX0X1X3X!.^.G.d.s.p.s.s.s.m.5 s g m 7.l.R.;XeX;XT.x.c.}..X0X.X&X{.Z.Z.k.u b b b l x %XGX%.G G ! Y ! Q ! Q ! ~ Q Q _ _ ' ] O.vXJXJXJXJXJXJXJXJXJXJXJXJX].PXPXPXPXJXFXFXFXBXBXnXvXcXhXhXhXzXkXzXkXgXhX).PXGXJX].# FXPXPXPXPXPXPXPXPXPXPXPXPXGXFXBXnX).oX$X$X%X$X$X > + z .+.-XCXFXxX0X;X.X+X>X0.^.8XtXtX8X8X6XO.: , < : , 0 q 9 7 % O.=X8XqXqXqXwX=X-X&X+.v 6 r b b l 0 9 0 b 9.c.x.z.5.B m 7.j.}.|.}.{.k.z M n x x z x v .o.k.f.f.| r c c 6 t a ..| K v | O.o.o.&X#.h.PXPXGXGXPX|.a 4 F T E _ E E E Q _ _ _ _ ' ' { ' { ] kXJXJXJXJXJXJXJXJXJXJXGXJX X] W E R H G 2 2 4 i G G i i 2 6 gXnXg.z.7.6.4.7.z.T.R.1X1X2XeXrXuXuX1XR.T.q.s & ; e -.-.-.h g h B .k.o.l.6.6.;.f h h 6.7.e.l.T.;X>XxXFXCXwXqX6X-X0X1X;XeXyX5XL.S.p.p.p.s.p.m.9 s f ;.e.T.R.1X1X;Xx.z.x.}.;X.X-X.Xk.k.f.k.u b n n n z V hXhXa G J ! T ~ ! ! ! ! ~ Q Q _ V.gXJXJXJXJXJXJXJXJXJXJXJXJXJXJX$XkXPXPXJXFXFXFXBXBXnXnXcXzXhXX@XBXBX)./.$X%X%XXX;.-.x.1XuXuXsXuXuX3X.X.X|.s g 4.eX.XzXzXcXzXkXkXkXzXcXvX%.].%X$X%X > = X Z O.O.-XmXmXlX+X;X>X9X@Xk.7XtX8X8X8X4XH.0 : : , u u q 9 7 ; r -XwXaXVXVXaXwX=X{.< k * , r b u u 9 u m 9.}.}.x.z.5.4.B 5.j.'.'.[.+.l n b x z x x v O.f.Z.Z.f.t c c 6 t t a v K a v | +.&X#.o.#.+.GXGXGXGXGX].t 4 F T R E Q E _ W _ _ _ _ ' ' ' { { ] JXJXJXJXJXJXJXJXJXJXJXJXJXJXvXZ.W R Y F I c K D G G G D 4 Z GX).z.z.e.e.x.;XR.;XR.1X2XeXeXrXrX1X;XT.e.h $ $ ; 8 e g g g h 7.x.;XH.Z.Z.6.6.>.6.>.4.>.q.l.x.;X>XlXBXPXmXVXaXqXwX3X;XR.eXyXyX5XG.p.' i.p.p.U 8 g h >.l.T.;X1XyX;Xl.z.z.x.}..X{.[.k.k.f.} b m B B C x x C zX%.K J J T ! | [.{._.L ] [ @.vXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJX+XPXPXGXBXnXvXnXvXcXcXzXhX%X$X%X @ j O.+.+.OXFXMX0X9X0XlXxXh.7XtXtX7X8X4XK.z z k u l d u q 9 7 < -XwXVXSXSXVXaX6X{.1 GXk * 0 z r 5 9 b m z.}.;X;Xz.7.B 4.5.9.j.h.[.+.r b z k k z v Z +.k.k.f.o.z N c 5 6 v ..o.| c | +.+.o.&X#.#.h.PXGXPXPXGX#X6 4 F Y T Q E Q E ( W ( _ _ ' _ ' ' p.f.JXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXo.W Y v oXBXgXS | T H L v bX].x.c.c.c.;X0X>X>X1X1X1X1X1X1X2X2XQ.T.e.;.; o o & 7 e e e h 7.;X1X3X5X3X5Xv.;.-.-.;.:.q.q.l.c.;X>X0XmXKXKXVXVXaXqX;XR.1XeXyXyXyX4XJ.d.B.S.d.y s g ;.>.l.T.R.eXeX;Xe.7.e.e.c.}.v._.k.o.f.v m m B 5.Z C V x S %XC L ! Q v FXGXGXbX| L K kXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXX}.r.1.u.u.y.XX_.7.5.;.6.w.w.r.r.7.;XT.x.m b 4.4.4.b M j.z.;Xx.c.5.oXPXJXPXJXJXFXhX%XnXcX%.$X%.$X%X%X%XhXj.1XrXuXuXsXsXdXdXsXdXdXdXdXdXOXnXvXnXvXcXcXcXnXBXBXFXPX). x z : a 5 5 5 < + A 0.`.g.9XmXxXxXmXnXbX].=X6X7X7X7X4XP.c A x z l l d q 9 7 , XmXKXIXSXSXVXqX*Xz DXX0X0XyX1X1XR.1X1X2XQ.Q.R.I.l.>.7 . O & 7 8 h 6.c.;XyXiXpXaXwX8Xv.-.-.;.;.6.q.z.}.*X5X5XwXwXVXVXqXqXqX;XQ.1XrXrXyX;X3X6X=X=XJ.a q g ;.:.6.l.R.1X1XeXT.e.7.z.7.z.v.h.[.{.&XZ.N m 5.7.9.0.9.Z n r l K ! Q Q | JXJXJXJXGXoX$XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXkXGXPXFXBXkXgXgXgXgXgXgXoXXX].%.#X%X$XXX%XJXJXJXJXJXJXoXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXGX).x +X#X$X$X%X%X t 6 6 6 % n 9.).).g.$XmXmXFXmXmXxXv =X7X7X4X^.P.v _.S M x b u q 0 7 ; n jXVXSXSXSXVXVX*X, Z JXx , b k 9 9 d B 7.x.R.;X;XR.c.x.x.c.}.}..Xv. .k l j j z Z [.{.*XH.o.a _.k y a .@.o.Z.o.c O. .+.+.Z.Z.#.).PXGXPXJXJXGXx 2 D F Y T R R _ ( W W _ _ _ ' ' ' { OXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXJXJXJXJXJX| ~ K 6 5 9.}.XX.X;X>XyXyX1XyX1X>X;X;X1X1XR.R.Y.I.r.w.-.O . O 7 -.6.l.T.1XiXZXCXAXaXqX6X7.g h >.6.9.g..XH.P.L.^.8XqXqXqX6X=XR.Q.1X1XeXR.4.5.j. . .y 8 g h :.6.e.x.R.1X1XR.z.6.7.5.5.9.B j.[.&Xk.Z.7.m 5.9.9.9.0.O.N c t G L Q _ U vXJXJXJXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX%XJXFXcXkXgX k r c c < t a t = z Z 9.0.9.).jXKXKXmXmXbXZ | ^.^.^.^.L.I [.[.O.Z n b b q 9 , : OXxXSXIXSXSXVXqX% < S S , l B N d d m 7.z.;X;X3X1X;X}.}.;X;X.X.X.Xk.c l k j x A [.&X*XH.+.| l c a } f.@.Z.#.o.a .+.o.o.Z.Z.A.%.JXGXPXJXJXGXO.2 D F ! Y T Q E W _ _ _ ' _ ' ' ' p. XHXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXnX..@.o.v C .X>X*X3X0XMXMXyXeX1X;X;X;X1X1X;XR.l.l.w.6.:.h 7 o & s 6.6.q.T.R.1XMXZXIXIXSXqX7X*X4.g 4.4.9.'.H.P.P.G.G.L.L.J.=X7XR.T.T.R.R.l.9 O ; 8 s g -.s g -.:.q.e.x.R.1X1Xc.l.6.4.5.4.N @ 5 g.{.H.f.5.B 4.7.9.9.9.g.N a K G J Q _ { |.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX%.FXBXcXhX t a 5 k B B Z M B g.jXKXCXmXmXxXO. .L.~.~.J.I {..X{.j.9.B b d q 7 , 1 -XmXSXSXVXaXqXr % , : , 9 b B b d h 4.z.c.;X1X3X;X;X;X1X1X>X9X9X5X*Xt k j l Z _..X*XH.k.| c 6 | ..o.o.o.Z.#.t | +.+.k.k.o.&X+.JXGXGXJXGXPXoX> 4 G F Y Y Q E _ W W E ( ' ` ' ' p.].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXjX..V.o.N }.0X=X5XqXVXJXMXeX>X>X>XyXyXyX3XR.T.l.r.l.l.l.l.T.x.x.z.z.l.l.T.R.1XZXIXIXUXSXaXtX7XH.B B 9.S H.P.~.L.G.G.S.S.S.J.Z.e.x.R.R.1Xd $ 7 s s g ;.>.;.g ;.6.q.l.T.;X3X;Xx.4.4.6.5.B y @ @ l [..Xk.B m m 5.9.9.0.j. . .I K L Q { +.BXJXJXJXJXJXJXJXJXJXJXJXHXJXJXJXJXJXJXJXJXJXJXJXJXJX|.XX].XX+XkXzXkXgXgXgXhXhX.0.%.#X+XoX].JXcXw.r.I.w.m 5.7.5.7.x.nXcXkXnXPXPXJXJXPXPXPXPXPXPX#XkXkXhXgXgXzXvXBXGXPXPXPXPXGXPXPXPXPXPXPXPXFX].gXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXg.4X^.^.^.L.L.L.~.G.J.F.A.A.Z.k.f.k.k.k.h.N z z `.JXJXJX].#X%.O.M v v c 6 OXVXIXUXIXSXaXdXaXqXwX-X-XoXoXOXOXOX{.[.O.).0X|.1 z v M 6 a > k Z Z B n b b S mXKXmXmXwX-X3XO.A.F.F.} .X*X.X'.j.B m b u y 9 o S 0XmXVXaX8X8XN % : * * , u m m d f 5.z.x.;X;XeXyX1X1XyXyXyX0X0X0X5Xo.r r l M g.{..X{.Z.o. . .} .f.o.Z.@.o.K k +.o.k.k.Z.Z.k.HXHXHXPXGXGXGXx 4 G F J T T Q Q E E _ ( ( ' ' ' ' '.HXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX Xb.k.g.;X0X=X6XwXKXKXNX0X>XMXZXLXZXyXeX1XR.I.T.T.R.R.R.R.;X>XyX>Xc.T.T.1XiXLXSXPXIXSXaXtX7X5X&X&X{.[.*X^.^.~.~.G.G.F.G.Z.g q.c.;X;XeXc.s h h >.>.6.q.q.f ;.4.q.l.R.;X>X;Xl.4.5.5.7.7.b @ @ c v.{.{.j.m m B 5.5.9.9. . .+...@.$.k.nXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX].zXzXzXkXhXhXgXX5.6.6.6.6.f V hXgXgXB 5.4.5.5.5.|.'.z.5.B m 7.5.`.C JXPXPXGXBX.X;XR.}.R.c.c.;Xc.x.$XPXPXHXC d 7.Z +X#X$X/._.%X7.y.I.U.>.B 7.7.j.c.gXkXBXPXPXPXPXPXPXPXPXPXnX].%XhXkXhXX9.5 v N 6 c > l C Z C B k : < _.mXmXxX-X-X0X5Xh.X.C.U k.-X*Xv.j.A B B m u 0 + + g.OXqXqX7X^.f.; , * + 7 u d h h d 4.z.x.c.;X;XyX>XyXrXiXMXMXmXxXwX*X6 r l M 0.].{.{.k.k.o.} } } } o.o.X...} 5 | +.k.+.Z.Z._.PXPXHXHXGXPXGXO.3 G D J T Q Q _ Q _ Q _ _ _ _ ' ' {.HXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].Z.[.;XpX6X6XwXVXKXMX0XZXLXIXIXZXiXrX1X1XW.Q.R.Q.1X2X1XeXyXiXCXCX0X;XyXZXZXLXIXIXSXaXtX7X4X6X=X*X=X=X7X^.^.^.~.G.G.G.y f 6.x. X>X0X3X6.>.>.7.l.l.x.l.f g 4.7.z.}.;X>X}.z.5.7.l.e.7.7.2 @ 9.}.}.v.v.m f m B B 5.A j.j.k.o.n.H.#XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXvX).nXcXzXkXgX r C A B C 0 * < : mXmXxX-X-X-XwX5X[...] k.-X*X{.h.j.5.m b f d , < % Z *X7X7X^.f.7 ; ; $ < q u d f d m 7.z.x.}.1XeXyXyXuXyXiXMXmXNXbXwXk.< l M 0.'. X.Xk.f.h. . .} } ..o...} } t a o.+.&XZ.&X+.PXHXHXHXPXGXGXoX2 D G J ! ! [ Q Q Q Q _ _ _ _ _ [ .XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX0.}.;XiX5X=X5XmXCXbXCXIXIXIXSXZXuXuX2X2XQ.2XQ.Q.Q.1XrXuXZXIXVXqXqXiXiXZXZXIXIXSXAXaXtX7X7X^.4X7X7X7X7X7X^.^.^.L.G.I e g 4.j.}.OX-X0XR.l.z.z.x.c.R.R.f h 4.7.T.}.;X.Xx.z.z.z.7.z.z.7.y - j.x.c.}.`.B d f m B B A g.h.k.Z.*X{.HXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBX%.BXnXcXkXgX$X).#XGXPXPXGXPXGXJXXX.6.6.6.4.].|.7.B f x.uXsXuXuXuXeX1X1X;Xc.d oXPXPXPXhX@X>X.X3X3XyX3X3X0XuXeXnXPXPXkX/.C 6.9.).oXgXhXhX#X$XhXr..XFX0.m 5.j.kXJXPXPXPXnXXX#Xx S ].%X%X%XgXgXhX > > ].mX-X-X-X-X-X5X*Xk.K +.wX-X-XoX[.9.5.B h f 0 : , @ O.H.4X~.f.& & * % ; 9 q q y d f 7.j.x.}.;X;XyXyXuXyXyXyXMXMXmXxX5Xa l M 9.`.]. Xk.h.o.f. .} ...o...o.} } c 6 c o.Z.H.+.GXGXPXHXGXPXPXFXx 3 D D J J ! ] ! Q Q Q ) _ _ [ +.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBX).;XyXMX=X*X-X-XxXHXIXLXIXLXZXsXiXrX2X2XQ.2XQ.1X1XeXrXsXIXIXVX8X7XpXZXLXSXLXSXSXAXtX8X7X^.P.P.^.^.^.^.7X^.^.^.~.f.8 e s m 9.'.-X0XmX3Xl.x.R.R.R.;X;X7.m 4.7.}.>X>Xj.z.l.7.7.z.7.z.z.a 3 2 v 7.j.x.j.b q b b b B 5.h.h.{.&X-XXdXdXdXdXdXdXdXdXdXsX3XFXPXPXJX: 5.4.4.7.9.OXkXzXcXcXnXe.r.BX+Xb x..XJXPXPXPXPX%.GX`.+X+X#X%X j C M M > 6 6 5 x jX-XOXOX{.*X-X*X*Xh.+.wXwX-X-X#X}.j.5.B m b + , , * 0 .D.b.; % & & % 9 7 9 9 s d 9.z.c..X;X>XyXeXyXeXyXyXyXyX0XbXwX=X5 x A g.`.].[.k.o.k.o.| ..} .} f.o.f...o.o.| +.&X .bXGXHXGXPXGXGXGX|.3 4 G G J ! ! ~ Q Q Q ~ Q ] _.PXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX`.0XyXMXyX5X0X{.9.9XIXIXSXIXAXsXuXuXrXQ.eXrXQ.rXeXeXeXsXSXIXSXVXqX7XqXZXZXLXSXSXVXqX8X4X^.^.L.L.~.~.~.~.^.^.^.^.K.- 7 8 s B j.{.-XwXaXaXR.R.;X1X;X1XyXe.m 5.9.}.>X9.m 4.m m 9.z.z.9.7.a G i i p a t A j.u 0 u l b M A g.[.*X-X'.nXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX%.FXBXcXzXS ].vXGX$XoX].].X0X0XxXxX5Xo.k B 9.g.`.'.h.o. .o.f.} } } } ..f.f.f.f.f.@.o. .O.oXHXHXPXGXGXPXPXGXM 3 4 D L L ! ~ Q ! ~ ~ U _.GXGXHXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX+X3XMXCXiXyX3X.Xg.5 VXSXSXSXZXsXuXuXrXrXQ.eXQ.Q.uXrXuXsXAXIXIXSXAXqX7XqXmXLXIXLXKXdXqX7XL.L.G.G.G.G.G.G.~.^.7X4XqXy O ; 8 f ;.j..X5XaXaXaXyX;X1XeXeXyXyXc.h 5.z.;X|.9 u q u 4.9.j.z.7.5.I J L I I a v N M 0 9 0 k b n B g.[.*X5X5X_.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFXoXFXBXzXXGXPXPXPXPX%.$Xk #X%X%XgXgXhXgXkXkXhXzXcXnXGXPXPXPXPXPXPXPXPXPXcXkXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXg.K.~.L.G.S.S.G.S.G.L.J.L.P.=X=X=X*X5X*X=X=X6X6X5X*X[.M 6 . .a t t l a 6 j @XwXaXqX7X8X8X8X8XqXaXaXVXKXVXVXVXKXCXVXmX-X{.+.6 @ 6 a a t 5 a p t 6 5 > # # > Z _.{.{.{.{. XOX-X*X-X-XOX Xx.5.4.m b d b B M M l $ , ; , ; % $ , 7 ; , 9 9.}.}..X}.;X;X>X1X3XeX3X3X0X0X0XmXwX=Xa n A 0.).`.h.h.+.| } | I } } o.o.f.b.Z.Z.b.X. .v O.cXHXPXGXGXPXGXJXFXz 3 4 G G J J L F L K #XGXHXGXHXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBX_.MXZXyX0X;X.X0.N 3XtXdXAXZXZXiXuXrXeXQ.eXeXrXuXuXiXZXSXUXIXSXAXaXtX8X8XCXLXIXPXKXaXqX^.G.S.F.S.S.F.G.G.L.~.^.pXiXo . ; q d 5.v.*XwXqXqXaXAXuXuXiXiXiXyXR.m 4.9.}.0 O % , d 5.j.x.j.9.m I P ] U o.f.k.{.&X'.x 1 5 u b n 9._.*X0XwX*X$XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].FXcXkXhXzXnXnXnXnXBXnXnXvXcXcXzXvXBXFXGXGXGXGXGXGXPXPXPXJXHXGXvX = K p a K a a a 6 > : : + r O._.]. X X X{. X{.=X=X*X*X}.z.j.5.4.b m B 5.9.j.0., 7 ; ; ; % % ; ; * 9 9..X@X>X>X>X>X>X3X>X3X3X3X-X0X-XjXxX5XH.u Z 9.0.O.+.O. .} } } } .o.| f.f.k.Z.b.b.@.o. .+.O.oXGXGXPXGXJXJXJXJX].C c c v p D a v BXPXGXPXPXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].mXCXMX>X;X}.}.9.N tXpXAXAXIXZXiXiXuXrXuXuXuXuXpXpXAXSXIXUXUXIXSXSXaXtXVXMXZXZXsXpX8X7X^.L.L.G.G.G.G.G.L.L.^.7XAXVX, O ; 8 f j.{.wXaXaXaXAXsXiXiXuXiXiXiX;X4.4.9.`.X + + ; 0 B 9.`.h.5.m f ] f.f.f.k..X-XxX0X9X-X+.r b b A _.OX-XqXqX'.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].FXcXvXFXFXFXBXBXBXBXBXnXvXvXvXnXnXBXFXGXGXPXGXGXPXPXPXPXPXPXHXGXgX#X%X%XhXcXnXFXPXPXGXQ &XnXkXgXx V +X#X#X#X#XoXK L L J J +.#XgXS S #X#X+X#X+X,.,.-.e -.kXBXFXFXFXfX}.e.u.E.t.-.5.5.7.9.%XZ 7.W.W.e.1Xx.e.}.zXkXkXhXgXgXgXhX|..XrXg.vX.XyX+XFX0XyX3X3X3X3X;X;Xv.$XPXPXgXk #X].M c.1XeXrXuXuXsXsXsXdXdXZXdXdX#XGXPXPXPXGXS +X$XkXBXFXGXFXGXPXFXnXFXFXBXFXGXPXPXPXPXPXPXPXPXPXBXcXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXoXa L.G.G.S.G.S.G.L.L.G.L.P.P.=X=X5X5X5X6XqXqXaXqX6X.XN < = a t t t t a a 6 N =X=XP.4X^.7X7X7X7X8XqXqXaXVXwXVXwXaXqX6X=X*Xk.o.v k > p K p a a t 6 5 > : # r B 9._.].|.|.}.'.[.*X4X6X*XOX}.x.z.9.4.m 5.9.9.}.+X9.7 7 ; ; & % * ; ; < 9.+X>X>X9X>X0X3X3X>X3X*X=X-X-X-X-XxX-X*X .n B A A O. .| N I } f.f.f.f. .f.f.f.b.b.@.@.o.O.O.+._.].oXcXGXFXGXFXPXGXPXGXGX+X).kXJXJXJXJXHXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX'.CXMXyX>X;X;X`.0.t tXdXdXSXIXUXSXSXZXsXsXZXdXZXAXAXIXUXUXUXUXUXUXIXSXVXCXrXeXeXyXpX3XH.k.o.f.A.J.J.P.P.=X7X7XdXaXdXN & 7 d m z.@X0XVXSXSXAXiXuXiXsXiXiXiX1X7.4.7.B : + + ; q B 9.x.`.5.m d } f.f.k.k.OX9XmXmXxX-X-Xh.r b Z g.{.-X5XqX*XhXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXPXJXGXGXPX`.BXnXFXPXGXGXFXFXFXFXFXFXFXFXFXBXBXGXGXGXPXPXPXPXPXPXPXPXPXPXJXJXkX#X#XXBXBXPXPXPXoX).#X%XcXGXPXPXPXPXPXPXPXPXGXGXPXPXPXPXPXPXPXPXPXPXPXhXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXHXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXlXO.D.G.G.G.G.G.L.G.G.L.P.=X6X=X5X5XwX6XqXqXwXVXwX5Xv.l < : t t k 6 6 z l t N H.K.P.P.^.^.4X7X7X8X6X6XqXqXqXqX6X6X6X=X*XH.k. .v l 5 K p p a t 6 5 5 > * 0 z m A ).}.}.'.}.h.h.=X7X7X-X-X.Xc.z.9.7.7.j.c.;X>X>X0 9 7 * % % % % * 9 g.+X,X9X9X0X0X>X.X3X*X*X*X*X*X-X*X-X-X-XH.c B B C N c a a a I .} j.f.f.f.f.f.b.b.n.@.@. .| v v c c c c O.h.oXFXPXPXGXJXJXHXPXJXJXJXJXHXPXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXoX-XVXMX0X;X>X|.g.M < 8XdXAXIXUXUXUXIXIXIXSXIXSXSXSXIXUXUXUXUXUXUXUXUXUXSX1XT.4.q * X X o + ; y O..X5X=X6XqXtXaXaXpX4X, q d 5.c.>XbXIXIXSXZXuXiXiXsXZXsXiXR.l.5.5.B : O + % 0 m 9.x.j.5.f u a f.k.h.{.-XmXmXbXmXmXjX-X[.l B O.'.*X=X8XqX].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXJXJXPX K K p p t 6 6 5 # 1 u b m B 0.j.z.x.g.g.k.K.P.4X*X-X}.x.x.x.x.c.R.1X0X>X0 9 7 * & % * + % 1 ).+X$X9X9X0X-X-X*X*X*X*X=XK.*X=X*X*X-X-X*Xk.k x x z l r y t v v } I .o.f.f.f.f.@.b.#.#.o.| v v v | v v a a } | -XGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXA 6XwX3X;X;X+X'.g.b 1 h.AXSXIXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXPXR.w.7 o X o o X * 9 9 m v.0XmXaXpXqXpXpXtXP.t s h 6.c.>XyXCXCXiX3XuXiXZXsXsXiXiX;XT.e.e.9.+ + O % 9 m 9.9.9.4.b q q .k.h.{.9XmXmXmXmXxXwX9X-Xh.t A h.{.=X6X8X{.BXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXFXoX].gXGXGXGXGXPXPXPXPXPXPXPXPXPXGXPXPXGXPXPXPXPXPXPXPXPXPXPXPXcXZ.@.! Q J ].#X$XgXcXcXnXFXGXFXBXL D Q _ _ _ _ ( E Q Q E Q Q Q Q T x gXkXcXzXzXkXhXkXkXkX}.,.:.e -.:.e 6.q.e.w.6.0.XXkXoXe.e.7.6.7.7.hXhXk gXcXhX;X}.cXcXzXhXgX 1 1 u b B 5.9.j.z.`.j.O.h.Z.K.-X0X.X}.x.c.R.R.;X1X>X,Xj 7 , ; % & % ; < l ].OXOX-X0X9X5X*X-X-X=XK.=X=X=XP.=X=X*X3X-X&Xv l k k 6 6 6 a N o.f.j.f.h.o. .o.o.X.@.b.] X.o.o.| v K | K } | K I ..].PXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXO.4X3X3X>X@X*Xk.f.v 5 5 pXSXIXUXUXUXUXUXUXUXUXUXIXIXUXUXIXUXUXUXUXUXLX1XT.T.< o . . o o o o < 0 0 Z }.jXVXaXaXaXaXtX7XZ.d 7.l.e.6.x.}.;X;Xx.R.yXpXdXdXsXpXsX2X;XR.e.x.0 + % * 9 B 7.9.7.m g q q 7 z j.}.-XxXmXwXwXwX-X-X*X&X+.5 j..X-XwXqX8X].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXGX#X].FXPXPXPXPXPXPXPXGXGXPXPXPXPXGXGXPXPXGXPXGXPXPXPXPXPXPXPXPXPXPX{.W W W ^ H ..gXzXcXcXnXnXFXFXnXcXK Y { ' _ _ W ( _ _ E _ E Q Q Q L %XkXcXcXcXcXzX#X}.9.7.8.-.-.-.-.:.:.e q.e.r.y.y.w.7.).7.e.e.8.5.5.#XhX%.S zXcX.X;X#XkXzXzXgX%X#XC S %XBXBXDX+XgXnXnXnXBXGXJXkX8.I.I.Y.I.u.u.I.u.E.Y.E.W.e.cXvX 6 t 6 6 6 6 6 > 5 I C.J.L.L.~.^.^.^.^.4X4X7X4X4X4XP.K.H.k.f. . .a k n ...K p a 6 6 : j 5 b m B B 5.9.j.`.'._.+.&XjXwX-X;X.X;X;X;X1XeX>XA 1 7 , ; & % * & : x ].OX-X-XwX-X-X-X=X*X=X=XP.=X=X=X6X6X=X5X-X*X&Xx k 6 1 1 l N O.h.k.&X&X.XZ.+.o.o...U b.] @...o.o.} | I | I } I U ....{.PXGXPXGXPXPXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX XK.H.*X3X0X0X=XZ.k. .l > N SXSXIXUXUXUXUXUXUXUXUXIXSXSXSXAXAXAXSXUXNX1X1X1XR.B . . . o o o . < y z j..XxXCXVXAXVXaXaX5XB h e.R.I.q & $ O * s x.3XtXtXtXpXpXtXyX1X;XT.x.z.* % : u m 5.7.7.4.d q q 9 7 9.c..X0XwXwXqX5X=X*XH.Z.Z...1 A 3XwXqX6Xk.BXJXJXJXJXJXJXJXJXJXJXJXGXPXGXPXGXGXPXnX%.kXPXPXPXPXPXPXPXPXGXFXFXPXPXPXPXPXPXFXGXGXFXPXPXPXPXPXPXPXPXPXPXJX*X/ / ^ ^ P cXnXBXnXnXnXBXFXFXvX|.U Y ' { _ _ E E Q _ E E Q Q E ! `.zXcXvXnXBXnXoX6.u.u.E.u.E.t.,.-.g -.-.-.q.e.y.e.y.y.y.8.m 8.5.4.4.0.1 |.].|.cXgX>X}.hXkXzXzXgX).x _.%XBXnX|.z.|.nXBXnXnXBXGXPX$X4.w.u.Y.U.Y.Y.Y.E.W.W.y.hXcXcXvX 1 k k j 6 5 > > # t C.G.L.L.~.P.^.^.^.^.4X7X4X4XP.P.K.Z.b.f.} a y j nX+.} K p p a = z k M m m B 5.9.g.`.|.].{.-XwXmX0X0X3X;X;X>X1XyXyXN : 7 , ; * * & % # x [.*X*X5X-X5X5X*X*X&XH.K.=X=X6X6X6X6XwX5X5X-X*X+.k j 1 r x A h.k.{.-X-X-X*Xk.o. ...X.U U U U ......} } } } ..X.X.U ] ..OXGXGXPXGXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXh.K.5XwXwXqX=XP.A.Z.k.v r : -XSXSXUXUXUXUXUXUXUXUXIXSXAXAXdXdXpXaX;X;XyX>X1X;X`.+ O + % O . . y c A '.,XBXKXKXIXSXAXSXlXz.7.z.R.e.; $ . % h T.!.5X!.4X8X8XyXeX;XR.c.z.n ; ; u m 4.7.6.;.f q q q 0 4.x.@XjXwX=X=X=X&Xn.m.[ [ m.f.A g.-X5X6X=X XHXJXJXJXJXJXJXJXJXJXJXPXGXGXHXGXPXzXC GXPXPXPXGXPXFXFXGXGXFXFXFXPXPXPXFXFXFXFXFXFXFXGXGXPXPXPXPXPXPXPXGXBX9X^ / / / / k.BXFXBXBXBXFXGXFXBX%.L H _ ' _ _ _ _ _ E _ Q Q Q Q +.zXcXnXBXBXFXgX:.U.E.E.E.E.E.E.E.E.1.-.e g ;.w.w.r.y.T.u.W.T.r.6.5.4.%XGX|.C 1 kXcX.XyX XhXkXzXC nX+X).zXhXz.T.T.I.}.DXDXBXBXFXJXJX7.4.6.r.I.Y.E.W.W.Y.x.hXcXcXcXvXnX,Xj.z.7.9.x.vXcXkXgXgXhXkXkXzXzXzXcXFXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXHXoXC GXGXPXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX).4X~.L.~.~.~.~.^.^.4X8XqXqXqX6X4XP.K.Z.b.| t 6 < 5 > : k k 5 > > : = = a V.J.G.L.L.L.^.^.^.4X4X^.4X=XP.K.K.Z.f.f.I t l ].JX+Xa c p a > j C V b B m B 5.0.`.}.{.{.OXwXwXwXmXyXyXyXyXyXyXyX0Xx 7 ; ; * ; & % # c [.*X*X-X=X5X0X5X*X&XH.=X=X6XwXmXwXwX6X6X5X5X=X*XN k 6 0 V 0.`.v..X,X9XwX-X*XZ.o.o.X.U U U U X.............X.@.X.U ] ] o.X{.> * , ; @ o . t ._. XjXFXPXUXIXIXIXPXCX;X;X;X1X6.; & $ . y 5.c.!.K.P.4X4X8XyX1X3X;Xz.5.u 7 q h 4.7.4.;.g q q q d 4.z..X0XwX6X=XK.m.[ Q ) [ s.M.b.O.+.=X=XP.+.HXJXJXJXJXJXJXJXJXJXJXGXPXPXPXFX].oXPXPXGXGXGXGXFXFXBXnXnXcXzXzXPXPXPXPXPXPXPXPXPXFXGXPXPXPXPXPXPXPXFXGXBXzX) / / / / Q OXFXFXFXFXFXGXnXnX%.F K G G J ! T Q Q _ _ Q Q ! k.nXvXnXnXFXFXFXe.y.U.E.E.E.E.E.E.E.E.E.E.u.-.e q.e.y.u.u.W.u.W.W.Y.Y.6.vXPXHXFX%.V cXgX;X}.zXcX%XoXPXPXPXBX9.y.T.y.T.e.XhXvXcXzXzXcXnXBXFX+Xz.9.7.c.vXnXcXhXhXkXkXzXcXcXcXnXnXFXGXPXPXPXPXPXPXPXPXPXPXPXPXPXnXx /.GXJXGXGXHXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXj.7X^.L.~.~.~.P.^.4X8X8X8X6X4XP.P.K.A.b.f.I t 5 5 < > # 1 6 5 > : : > = p C.F.G.L.L.L.L.^.^.^.4X^.^.^.P.K.A.Z.b.o.} a l DXJX].t 6 6 a p k Z b m N B A 0.g._._.[.&X-XwXmXVXZXyXyX0XyXMXiX0Xx ; ; , ; ; : ; % c k.&X*X5X5X*X5X*X*X=XA.=X5XwXVXmXKXmXwXwX=X=X-X=Xh.k 0 0 z 9.`..X;X>X0XjX0X=X*Xf.o.o...U U U U X.X.X.o...X.X.@.@.#.] ] [ | hXGXGXGXPXGXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXo.C.J.4X8XqX6XP.P.P.H.H.o.c 6 N VXAXSXIXIXUXUXUXUXIXSXAXaXtX6Xj.c .v.v.3X3XwX0XN * 7 9 9 ; O o O t h.&X0XaXVXKXIXIXUXIXCXyX;X1XyX4.7 ; $ o . . $ m x.D.F.P.J.L.qXuXyXyX1XT.7.B 9 q f ;.4.4.h s s u s f 4.z..X;X0XwX5XZ.$.] Q Q _ { s.M.@.v k.H.P.K. XJXJXJXJXJXJXJXJXJXJXGXGXGXoXoXGXPXGXGXGXGXGXGXFXBXnXvXkXoX_.kXPXPXPXPXPXPXPXPXPXPXFXGXGXGXPXPXFXGXFXBXnXvXX./ / / / W T #XFXFXGXFX#XD +.I D +X+Xo.! L L G K G G | kXBXFXBXnXnXBXFXDX}.u.u.t.U.E.E.E.E.E.E.E.E.E.E.E.y.5.y.y.T.W.W.W.Y.W.Y.e.gXPXPXHXFXBX+X#XgX.XyX+XvX%.FXPXPXPXoXT.I.8.e.T.I.`.FXPXJXJXJXPXvX6.7.e.q.`.zX`.9.XXkXzXkXzXcXnXBXFXJX.X5.5.v.JXFXnXcXhXkXzXcXvXvXvXnXBXFXFXGXGXPXPXPXPXPXPXPXPXPX%XC V hXJXJXJXHXPXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX`.5X7XP.~.^.^.^.7X7X7X7X7X^.P.L.J.D.n.b...K t 5 2 5 = + : 5 > > = = > @ p N.S.F.F.J.L.L.^.^.7X^.4XP.P.P.K.K.A.Z.f.} | h.oX%.K a 6 6 K _.C z b b N B A S +.+.+.{.-XwXVXKXmXdXMXxXyXmXiX0Xz * ; * * ; & * O c k.&X*X5X*X*X=X=X*X*X*X*X5XwXmXBXKXmXwX5X6X=X*X=Xk.0 0 9 l B z.}.;X>X0X0X0X0X5XZ.o.o.....U I U L U X.X.X.X.b.@.#.] #.$.[ [ +.nXGXPXGXPXGXPXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX#Xn.B.L.7X8X8X8X^.P.J.J.K.k. .v k.aXaXVXSXUXUXUXLXCXZXIX9Xx * * f N j.v.H.!.6X5X5X< 9 y u u y ; O o O 6 k.*XwXVXKXIXIXIXSXVXaX0XyXMX6.7 7 & $ o o o d j.D.d.S.S.S.qXZXiXiXyX;Xl.5.u q s h ;.;.g s s s d 4.6.l.}.;X9X0X3Xn.] ! R Q ) { { s.M.@.> o.Z.J.+.GXJXJXJXJXJXJXJXJXJXJXcX/.nXPXPXPXJXGXGXFXFXBXBXvXcXzX].PXPXPXPXPXPXPXPXPXPXPXPXPXPXFXFXGXPXGXFXBXvXzXhX > = @ > > > > = > = t N.C.C.C.F.L.P.^.^.4X^.^.^.=XP.P.K.A.A.V.Z.b.f.o.a 6 p t p %.V u z b b M M Z N Z O.+.*X-XmXVXVXVXCXCXMXmX0X-Xc * ; ; & * * ; % a +.*X*X5X=X=X*X-X*X*X*X-XwXwXmXmXnXmXmX5X6X=X=X-X0X, 0 9 0 B j.}.;X>XyXMXMX0X3X=XZ.o...U U U P ! ! ! X.X.@.@.#.@.] ] [ [ Q [ | cXGXPXGXGXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXj.d.S.L.^.7X7X7X^.^.L.J.J.Z.A.Z.K.6X8XaXVXVXAXVXmX*X[., , , < u v O.f.Z.H.G.L.=Xv.9 u u b f d 9 & O O O X > ].-XmXKXIXSXAXaXqX6XCXCX4.7 7 ; & O o o 9 } m.i.i.p.p.4XSXZXZXZX1XT.z.b q d h h h f g f g g ;.q.l.T..X;X>X{.X.] T R R _ _ { p.s.M.I 5 ..N.N. XJXJXJXJXJXJXJXJXJXhX].PXPXPXPXPXUXPXPXGXGXBXnXzXkX].%XPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXFXBXBXFXFXnXOXoX$X%X%XK ( ` / ( / / R o.#XX.R R Y W F X > = > = > = v n.m.m.n.J.L.P.^.4X^.4X4X^.^.P.J.K.D.D.A.A.A.A.A.b.a 3 1 k 1 k u z n x v v c v .+.[.-XmXVXKXKXIXKXCXBXxXxXc * ; ; ; ; & ; * c .&X5X-X*X-X-X-X-X-X-X-XwXwXmXmXmXmXmXwX5XK.=X-X*Xr , , u B 9.c.;X>XyXyXmXnX0X-X*Xf...X.] ] ! ~ ~ T ! X.@.b.#.#.#.$.$.[ ] ] ! +.%..6.l.x.c.}.}.v.o.U T R Q W _ _ ' s.{ M.4 } m.M.[.JXJXJXJXJXJXJXJXvX].PXPXPXPXPXPXPXPXPXHXGXFXnXzXgX%.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXGXFXnXbXnXcX_.).#X%XgXY F ( W / / ^ ^ W R R R R Q R Y +.#X+XS %X).K G K G L [.GXFXFXBXnXvXcX<.,.:.:.-.hXcXcXvXmXlXc.r.E.E.t.,.BXJXFXcXS gX%XoX#X%X`.FXJXPXJXJXGXFXvXcX}.}.gXyX X`.JXPXPXPXPXXX7.8.4.W.6.4.PXPX = > = = = z N X.X.] n.F.P.^.4X4X4X4X^.4XP.L.K.K.K.K.J.J.J.J.K.A.} = K 5 k l z x M v c c c v O._.*XwXmXKXIXIXIXKXFXmX-Xl * ; * * * ; ; * v k.*X*X*X-X-X-X-XOXOX-X-X-X5XwXwXwXwXxX5X*XK.H.{.O.< 7 ; 0 m 9.x.;XyXyXyXMXBXHXwX-XH.f.b.@.#.] ] Q ~ ! ] X.@.@.#.$.$.[ $.] ] ! ! [ L v #XGXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXf.p.S.G.~.7X7X^.7X7X^.P.P.L.P.P.L.~.4X7XtXtXaXaXVXwXwXh.l , * M h.{.=XP.^.L.K.a u B N B Z 5.5.d 9 * ; $ O % 9.'.+XxXVXaXaXaXaXaXSX.X8 e q 8 7 7 & $ $ a C.d.S.S.4XdXZXIXIXLXyX;Xx.m q s g g g g h h h ;.6.e.e.l.z.x.x.j.j.U P R E E W _ ' { { p.[ K B.M.oXJXJXJXJXJXJXJXHX].PXPXPXPXPXPXPXPXPXPXPXGXFXnXkX 5 2 > = c #XZ o.X.U N.J.^.4X7X6X7X4X4XP.P.K.K.P.K.P.L.P.J.L.A.V...2 5 r l x v C N c a t c Z +.&X-XmXKXIXUXUXKXmXmX-Xl : * * * * ; ; * o.k.&X-X-X-X-X$X#X#X-X*X*X=X=X6X6X5X=X=X=XH.Z.V.O.9 9 9 ; y m 9.j.;X;XyXiXMXCXHXmXxX*X*XZ.A.n.#.[ [ ] ] ! U X.@.@.#.#.[ [ ] ] ] ] Q [ _ U O. = = = = 2 t gX9XA o.f.@.A.P.6X8X8X8X7X^.P.P.K.P.P.P.=XP.P.P.P.J.A.n.} * r z x x v N c t 6 6 v +.[.OXwXmXKXUXUXKXmXmX-Xt : # * % % * % < k.v.*X*X-X-X#X@XOXOX&X&X&X&X=X=X=X=X=XJ.A.N.m.m.] < 9 7 ; r b B 9.}.;XyXuXMXZXFXKXmX0X=X=XK.A.A.n.$.#.[ ] ] ] U @.b.#.#.#.] ] ] ] ] ] [ { { X.{.FXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJX'.' p.S.G.~.7X7X^.tX7XtX8X7X7X^.^.~.^.^.^.7X7X8X7X4XP.H.h.; 9 9 > a I a y < 0 u M j.k.f.h.v.9.b u 0 q , % X N X9XxXxXbXxXjXXMXSXIXZXyXR.m s g h g f s f f m 4.9.j.z.4.9 ; 0 q d q K T R E W / ( / / / ` ` ! o.JXJXJXJXJXJXJXJX$XvXPXPXPXPXPXPXPXPXPXPXPXGXBXvXhX c c c a a a t 6 5 c S _.{.-XmXKXIXUXUXmXmX-X1 * + + + % % X t h.k.{.*X@X>X@XOXoXOX&X&XH.A.N.A.A.F.C.V.M.C.M.[ [ ! 2 7 ; 0 d B 9.x.;X1XyXiXiXCXKXmXmX-X5X*XH.Z.V.n.#.#.] ] P P U #.@.@.U @.U X.] ] ] [ { { { { #.-XKXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXMXU p.d.S.~.^.7XtXtXtXpXqX8X7X4X^.^.^.^.^.7X7X4X^.~.L.A.h.; , 9 , 7 , , ; 9 q u M h.v.Z.k.v.g.b u b d u * $ c h.-XwX0XjX$X`.5.0. XMXCX9Xg.C z 0 q 7 u l q % ; f 5.c.!.3XwXVXVXyX;X5.s f ;.h f f f m B Z j.h.j.5.* 0 u d f b d L R R W ^ / / / / ` ` K OXJXJXJXJXJXJXJXJX`.FXPXPXPXPXPXPXPXPXPXPXPXGXBXcXhX%XgXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXGXnXvXnXo.Y vXnXnXBXnXnXvXnXBXBXcXk.{ { ~ ! ! U L L L ! ! Q _ Q OXNXx.t.u.E.E.E.E.E.u.,.e g -.XXNXcXcXvXBXJXDX5.5.`.gX N h.k..X.X@X+X@X X{.{.{.&XZ.A.A.C.V.C.N.C.C.C.N.s._ R H * * , u B 7.z.;X;XyXyXiXMXCXKXmXxX-X*XH.Z.Z.b.#.@.@.] U P P P ] I K U U U U U L L L ! { { { [ *XHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLX5.a.S.G.G.^.^.^.tXpXaXaXqX8X7X7X^.^.^.^.^.7X4X^.L.L.H. .I * * 9 9 9 7 9 q 0 u v +.&X*Xk.k.j.M x n u 0 , : # > M '.OX}.0.z.c.}.>X0XBXzX Xg.0.g.N z 0 C d , 7 B x.;X3X!.P.4XqXtX3Xc.f h ;.;.m f f b N ..b.o.9.B ; 0 u b b m d v T R W / / / / / ` ' r LXJXJXJXJXJXJXJXJXx GXPXPXPXPXPXPXPXPXPXPXPXJXBXzXgX%XnXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXnX! ! FXFXFXGXGXGXBXBXnXBXcXX.{ { _ ' ' _ _ Q Q _ _ [ Q ! t i y 5 2 > 5 5 t V S _.{.-XmXKXIXUXKXmX-X1 < * % % + + 6 O.h.[..X.XOXOX X X].[.k.f.#.A.C.C.C.B.C.C.C.F.B.s.' ^ E 3 $ * y m 7.z.T.;X3XyXyXiXmXFXmXjX-X-X&XZ.b.b.] b.U ] P L L J L J D G D 4 D D D T R R D { M.{ { k.FXHXHXHXJXJXJXJXJXJXJXJXJXJXJX$XK d.S.G.~.~.^.7X7XpXVXVXaXqX7X7X7X^.^.7X^.^.^.~.L.Z.f.X.U 2 @ 2 - q q u u u n O.{.*X*XZ.v.N 0.x d y , < 1 < 0 k g.}.}.}.}.}.;X,XlXlX#XOX.X&XH.Z.o.%.n q 9 5.v.;X~.G.S.S.J.4X6X0Xj.f m m b u t K T ( ) ] A B * 0 u b m m b v U Q ^ / / / / / i.) oXJXJXJXJXJXJXJXJXBXx FXPXPXPXPXPXPXPXPXPXPXPXGXNXcXhXhXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXBXU o.FXGXPXPXPXHXHXFXnXnXoX{ { ' ' ' { { _ _ _ _ _ Q Q k.%.u.u.E.E.E.E.E.E.E.E.E.E.E.1.-.:.BXnXvX]./.A 6.$XgXhXzXcXvXvXnXvXnXnXvX].FXPXPXPXPXPXPXJXGXJX}.e.gXx.W.T.W.T.T.T.Y.9.DXPXPXPXPXPXvX].oXS +X + : % % % X k O.k.[.{.{..X XOX X{.j.f.X.b.V.C.C.S.F.F.F.J.J.S.s.' _ ( R @ * 0 b 5.z.c.;X1X>XyXMXMXmXbX9X-X-X*X&Xk.f.f.b.X.U L K K D F J H T Y R Y T Y R E E Y ] { { { { +.BXJXHXJXJXJXJXJXJXJXJXJXJXJXBXN U d.d.S.G.L.D. .*X5XVXVXVXtX7X7X7X^.7X7X^.~.G.G.V.n.m.[ ~ T Q H - q e d u b A [.*X*XH.h.Z g. .u m 9 0 z x Z C 9.j.9.9.9.7.9.}.+X9XjX0X5X=XP.J.s.K x u 0 A v.H.K.S.p.i.{ F.4XwX.Xh m b y 3 Y / / ^ ^ R L l < 0 u z b B b n | ~ ( / / / ` ` i.U cXJXJXJXJXJXJXJXJX].oXBXPXPXPXPXPXPXPXPXPXPXPXPXPXDXcXDXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXFXPXPXPXPXPXPXPXPXJXnXvXk.{ { ' { ' ' _ _ _ _ { { [ o.h.7.-.g `.x.t.E.E.E.E.E.E.E.E.E.u.1.].FXvX/._.7.'.gXgXhXzXvXBXBXBXBXnXnXnXcX%.GXPXPXPXPXPXPXPXPXzXe.8.e.W.R.1XW.W.x.+X$X`.).].].XX].].hX%.oX = = * = 5 6 v V C O._.*XwXVXVXVXVXmXOXx l k k : + O : +._.h.k.[.{.].[.k.+. .I ] n.m.C.F.G.J.G.L.L.P.P.P.F.B.p.p.{ = , u M 5.x.c.;XyXyXvXBXxXxX-X-X-X*X&XZ.b.b.f.b.U K G i D J Y T T R R R R R R R R R Q Q Q ( J L { c BXJXJXJXJXJXJXJXJXJXJXJXJXv.G.a.i.i.p.U v N v =X6XqXqXqX8XtX7X7X^.~.G.G.G.b.U m.[ { ) W 4 , 9 9 u u z B O.k.&XH.Z.N `._.A Z B 5.j x Z 9.0.0.9.`.9.j.j.j.|.+X9X*X=XP.P.L.B.M.+.BXM b d j.,X5XF.d.p.^.6XaXVX[.5.A A N p R ^ ^ ^ W R o., 5 r y z N .c N X.n.{ ' ` ` i.` i.j.JXJXJXJXJXJXJXJX%XgXnXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXnXbXbX].I L P ! ! T ! ! L I jXcXcX:.<.:.-.4.vXNXNXFX+Xt.u.E.E.E.E.E.1.`.9.e.y.y.e.l.y.I.T.9.oXnXkX].k ).].C oXcXcX/.].PXPXPXPXPXPXPXPXPXMXW.T.XX(.DXnXvXcXzXzXzXzXhX|.).V X0XmXBXbXxX0X0X*X*X=XH.Z.Z.n.f.f.] U i p D J Y Y R R R R R Y R R R R R E E W ^ Y J ] `.JXJXJXJXJXJXJXJXJXJXJXJXXXV.P i.{ ] ....} X.K.4X7X8XtX7X7X7X^.^.~.~.D.f.p I ] ' ' ( 4 : , , < 0 k b N O.k.k.Z.k.v cX].h.j.A n 6 z C Z 0.9.0.`.`.j.`.].oXOX*X=XK.P.P.L.F.J.].JXS B f 7.;XjXwX=XaXaXAXKXmX.Xv.h.k.h..._ ( ^ ( W ) c 6 < 6 t v .j.t } n.V.d.i.i.i.i.` p.'.DXJXJXJXJXJXJXJX].vX#XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXcXcXvXvX-Xo.] ] $.] ] ] P *X%X#X1.1.,.,.-.0XnXBXFXGXiX.Xr.E.E.E.t.:.e.z.|.B 6.XXhXhX%X|.x.y. : * : : 1 k z N O.h.k.f.b.Z GXBXZ '.`.Z l l C B S 0.0.j.`.'.].{.oX*X*XH.J.J.L.L.P.Z.OXJX).B b 5.c.>XiXIXIXIXIXUXKXxX-X*X*Xb.n.{ / ( W ^ G 6 6 5 < y I ..I p I n.S.S.S.i.i.i.` f.cXLXJXJXJXJXJXJXJX].S PXPXPXPXPXnX|.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXcXcXvXnXnXnX-Xo.$.[ ] P >.t.U.u.,.-.-.,.:.}.nXBXFXFXGXJXcXx.u.1.r.z.$XvXBXA e.oXkXkXzXcX k t z c | a I U ] ] m.B.B.M.B.B.4XqXaXwXqXqX6X4XL.G.B.@.> l n N g. X-XwXwXwX*X*X=XH.K.K.A.D.n.n.n.n.n.U K G K G J T T R R R R Y E R W ^ ^ ^ / / / / / T 5 DXJXJXJXJXJXJXJXJXJXJXJXLXg.K _ i.p.d.s.m.N.S.G.L.L.^.^.^.^.^.^.H.k.f.i p G ~ ) ^ 4 : O + # > 1 k z v .f.f.f.X.O.%XJXZ [. XS M l v A 9.j.`.`.v.'.[.{.&X&XH.A.J.J.L.J.L.N GX].Z M B 7..X0XiXSXSXSXIXIXKXwX5X6XP.V.n.[ p.{ T D t l a a i K X...i K ] M.B.S.S.S.d.i.U #XJXJXJXJXJXJXJXJXJX).`.PXPXFXnXzXkXgXzXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXPXGXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXcXcXvXnXBXFXFXFXk.$.K 1.E.E.E.E.E.E.,.g -.4.nXFXFXFXNXJXJXJX6.w.}.vXnXnXvXS e.8.%XzXzXvXhXy.}.kX ] p.d.B.B.S.S.G.G.L.G.~.~.^.~.~.Z.n.n.n.P ! _ ( ^ D + @ = 5 6 6 l c v .o.....X.U o.N l v &XZ 6 > l O.g.j.`.'.k.v.k.k.Z.A.A.J.F.F.C.J.A.1 x < 6 z A j.;X0XaXKXVXKXSXIXVXwX6X=Xo.5 = = > = 5 k t c I } X.A...4 G ! s.d.d.d.d.N.v j.zXJXJXJXJXJXJXJXJXJXJX|.x PXPXPXBXvXzXkXvXGXPXPXPXPXPXPXPXPXPXPXPXUXPXPXGXFXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXzXcXvXvXvXnXBXFXcXN u.E.E.E.E.E.E.E.E.E.,.e ,XFXGXFXjXBXFXz.r.|.BXNXnXnX#X].].e.e.9.}.|.9.Y.XXhXgXgXgX < t M 0.'.@XmXmXVXSXSXSXaX8X6XH.o.t 5 5 6 6 t 6 t a I | Z.a 4 H H T _ ' ` n.).nXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXGX%.HXPXPXFXFXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBX 2 3 a K ..N.A.A.b.K L ~ T L h.lXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXPXGXoX#XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXkXgXgXgXgXgXkXkXkXcXBXGXGXPXGXGX%X/.#XGXPXPXPXcX&X..k.vXPXPXPXPXHXBXnXGXPXPXR.E.u.t.,.! @.PXmXc.E.E.E.E.E.E.E.E.R.>XY.Y.u.u.}.FXFXFXbX-X`.$XXX$X%XgXzXcXnXvXzXzXzXkXkXzXzXzXzXzXzXzXzX%.PXPXPXPXPXPXPXPXPXPXPXPXPXPX/.cXcXBXkX+XGXPXGXJXhX%XPXPXPXPXPXPXPXPXPXPXPXPX@X>XGXJXC cXvXcX).PXPX|.].FXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLX9Xv.^.7X7X^.7X^.^.P.L.P.J.D.V.V.n.b...I K @ 2 @ @ v GXJXJXJXJXJXJXJX#XX./ / / / / / / / ( ' i.d.S.S.F.S.S.S.F.G.J.J.V.I = = $ 2 v } .f.Z.K.P.=X4X=X=X=XZ.| S O.).).A r o . : : : = L T Q Q ( ^ ^ ^ ^ ^ / / ` d.^.^.P.P.P.L.L.P.^.4XaXaXwXwX-X X X*XH.K.Z.b.b.b.X.n.n.n.n.n.b.f.] b.X.X...U P ! T R E E E E W E T Y Y R ^ / / / / ` I JXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXLXlXj.- f a.p.p.i.d.d.p.p.' ` / ^ ^ ^ T 3 3 G P U ] n.] b.n.U b.] U U P ! ! Q Q W H 6 | p ] M.A..X*Xf.f.U U m.s.d.B.d.d.s.p.i.G p p p K | f.{.*X-XmXqX6X8X7X4X^.P.Z.} I K K G D 4 3 a K ] M.C.J.J.G.f.A `.vXGXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXPXHXFX/.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXzX).S gXhXkXzXcXnXBXFXGXPXPXGXFX%.+X#X]._.GX-X! ! #.~ L XPXPXPXPXPXJXPXPXLXcX}.u.K ] { k.PXPXJX|.r.E.E.E.E.E.E.@X>XU.U.Y.Y.e.FXGXFXcX/.#X#X|.C %.%XkXcXvXcXkXkXkXkXkXkXzXzXzXzXcXzXzXS cXPXGXFXPXPXPXPXPXPXPXPXPXPXcX%.%.oXcXGXPXGXJXJXGX].GXPXPXPXPXPXPXPXPXPXPXPXJXGXJX).oXnXvXnX].vX].cX/.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX,Xv.~.^.7X^.^.^.~.L.L.L.J.F.V.V.N.#.@.K K @ 2 2 @ A JXHXHXJXJXJXJXJXoX! ^ / / / / / / / ` i.i.d.S.S.S.S.F.S.G.G.J.V.Z.a @ O = v o.f.Z.Z.D.P.P.P.=X4XP.A.f.Z S O.%.Z x : # < * 3 J Q ) W W W ^ W ^ ^ ^ ( ' B.P.L.L.L.L.L.L.^.^.8XVXVXVXVXmX0XOX-X*X*X*Xb...} } X.X.n.n.n.X.X.f.@.X.X.X.U P ~ T E E ( ( _ Q J J J G Y R W ^ ^ ^ ` U lXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXoXO.I s.p.i.i.i.` / / E T J Y L L L ] ] n.n.n.n.n.a.n.] ] ] ] ! ~ Q Q E D K A.A...K _ M.3XH.X.K ! [ s.s.s.d.d.p.i.' ~ p K K } o.f.Z.*X3XwXwX6X4X4XP.P.J.A.n.] P J J F F J G K U M.B.V.k.'.zXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXfX$XPXPXPXPXJXPXJXPXJXPXJXPXPXJXPXJXPXJXPXJXBX$X:X%.kXcXcXvXnXFXGXJXJXGXGXGXnXS +X+XOXoXM ! ] hXPX + D Y T R W W W W W ^ W W W [ s.C.S.C.S.S.S.S.S.G.L.8XtXVXSXUXUXUXUXKXKXmX0X0X*X&Xk.a a I f.b.n.n.b.f.X.X.f.X.U ] [ ( ` _ { [ ] L P J G D D F Y W ^ ^ / / ~ HXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHX_.) / ( / ( ( ( ) ) [ [ ] [ [ [ a.[ p.[ { _ _ ) R Q T ! N s.M.N.N.N.N.C.J.F.I ] ) i.i.p.p.p.p.i.` K v J L L U U n.m.V.J.=X6X=XP.L.J.C.C.B.s.{ { { { ' ' _ +.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXJXJXJXJXJXDXJXGXPXGXJXJXJXPXJXJXJXJXJXJXJXPXJXJXJXJXJXJXPXPXPXGXGXPXGXHXHXHXJXJXvXXX].%X$X%XoXU ! O.].%XXBXnXkXkXzXzXzXcXvXnXnXBXBXXXFXJXJXJXJXPXGXGX C gXJXJXJXJXJXJXJXJXJXJX+X .{ ' _ _ ( ' { p.s.d.s.M.s.s.d.s.p.p.{ { { { [ ) [ ' _ ^ ^ / / ^ / / / / / ^ / ^ ^ ^ / / / / / / / p.C.C.C.F.=XJXJXJXHXJXJXJXJXGXJXJXJXJXJXJXJXJXJXmXwXJ.S.J.K.H.Z.Z.X.] ] [ ` ( ^ ^ / G 4 p K vXGXPXGXGXGXPXGXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXBXU ^ / ^ / ^ / / / N.JXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXJXJXJXJXGXHXHXPXJXJXJXJXJXJXJXJXGXGXJXJXJXDX,X/.2.2.3.3./.fXDXJXPXJXJXJXGXFXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXPXPX#X$X$X$X$X$X%XgXgXhXkXzXnXFXJXJXJXJXPXPXJX3.2.2.fXPXJXJXJXPXPXPXPXPXPXPXPXBX$X).kXzXcXvXnXBXGXzXT.Y.e.XXBXnXBXnXBXnXnXzXzXcXvX#Xe.E.e.hXGXGXJXPXGXGXFXBXnXzXzXzXzXcXvXnXBXBXFXnX].JXJXJXJXJXGXPXXr.u.$XFXFXBXBXBXBXcXcXcX_.r.y.}.FXGXJXGXPXJXFXFXBXFXBXcXzXzXcXcXnXnXBXFXBX$X$XGXJXJXJXJXJXnX`.XXS $XkXzXkXkXcXkXcXcXnXFXFXFXnXvXkX].kXkXkXzXzXcX$X|.oXhXhXXX|.k S JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNXj.i.i.i.i.' ` ( ^ ( ^ ^ ^ / W R T H D 3 3 3 3 3 2 2 5 2 2 2 5 2 V FXJXJXJXJXJXJXJXJXJXh.N.s.{ ' _ { p.s.s.s.s.s.s.s.p.{ { _ ( _ _ W ( W W ^ ^ ^ / / / / / / / / / / / ^ ^ ^ ^ / / / / / _ s.C.C.J.6XJXJXHX+XS %.%.].oX+X%XkXvXBXJXJXJXJXJXJXJXLXJXmXJ.M.s.s.a.[ _ _ ( / ^ 4 K K G -XJXJXBX%XoX`.).S V x x k j k 1 1 : # 1 FXJXJXJXJXJXJXJXJXJXJXJXJXJXGXXPXGXPXPXPXJXvXhXgXgX%X#X#X#XS zXDXPX.XE.,XPXPXPXPXBXcXcXvXnXFXFXnXvXnXBXnXvXvXvXcXcXcXcXkXnXvXnXnXnXnXBXzXhXhXgXgX+XJXJXGX).hXkXvXBXFXGXGXPXPXPXGXGXPXGXGXPXFXBXzXXPXPXPXPXGXnXcXvXzXC %.#X HXJX[.] X.b.[ ( ( ( W ^ ^ ^ ( ( _ _ ( ( ( ^ ^ / ^ ^ ^ ^ ^ ^ / / / / / / / / ` { G * # * @ @ = 4 L ! ) ( ^ ^ / / ( ' J.GXPXGX].. X %.JXPXGXA.W E =XPXPXGXGXx Z JXJXJXJ.( ( _ W ( Q Q Q XJXJXJX%. # zXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].X S JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXgX. . 1 JXJXJXJXJXJXJXJXJXJXJXJXJXJXGXx X X V fX/.3.3.(.NXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX`.+ X `.JXJXJXJXJXJXJXJXJXJXJXJXFX1 . 1 %.: . : gXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXC . 1 FXJXJXJXPXPXPXPXPXPXPXGXPXGXGXFXBXFXFXGXGXFX#X%X/. > * @ + X @ i K ] [ J.JXJXJX].. X %.JXJXJXA.E N.GXPXPXGXoX: x JXJXJXwX{ ' ' ' ` ` ` _ ` C.JXGXJX%.. # cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJX].X %.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXkXX . k PXJXJXJXJXJXJXJXJXJXJXJXDXx . . . /.nXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXV . . V GXJXJXJXJXJXJXJXJXJXJXcX: 1 BXGXGXJX|.# . j cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXx . k JXJXJXJXPXPXPXPXPXPXPXPXPXPXPXJXGXGXGXgXV hXGX).GXJXJXJXDX3.2.&.&.2.2.DXJXJXGXFXGXGXFXnXcXgXgX%X > = : > > = * @ + @ * K ! ] =XJXJXJX].. X /.JXJXJXA.$.GXGXGXHXcXx X # gXJXJXJXF.{ p.p.' ' ' _ ( _ A.JXJXJX%.. # cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPX].X %.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXzX. . k JXJXJXJXJXJXJXJXJXJXGXPXXX: . # nXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX`.X 1 JXJXJXJXJXJXJXJXJXJXJXcX: . . 1 BXPXGXGXGX|.# X k vXJXJXJXJXJXJXJXJXJXJXJXJXJXk . x JXJXJXPXPXPXPXPXPXPXPXPXPXPXPXPXGXFX#X).PXPXGX%XJXJXJXJX(.2.&.&.&.2.8.JXJXJXGXGXGXPXGXGXnXkX/.x gX#XS GXPXPXPXPXPXcXl.Y.FXBXcXXX%X%XgXkXcXvXvXvXcXzXkXnXBXvXzXkXkXnXnXkXkXzXzXcXcXvXcXcXkX 5 = = @ @ @ = L ] &XGXGXPX|.. X %.KXGXGX5XGXPXGXGXGXS # X S JXJXJXbX{ { p.p.i.' ' ' ( _ A.PXJXJX%.X # cXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].X ).JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXzX. . j JXJXJXJXJXJXJXJXJXJXGX:XV . C JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXGX$XX # GXJXJXJXJXJXJXJXJXJXJXkX: . . 1 BXGXPXJXJXJX].# X k nXJXJXJXJXJXJXJXJXJXGXPXGXk . x JXJXJXJXPXPXPXUXPXPXPXPXPXPXPXPXGXnX`.PXPXGXJXGXPXJXGXfX2.&.&.&.&.2.:XJXJXJXGXGXJXJXPXPXPXFXV ).kXgX#XcXJXPXPXPXJXcXr.I.GXBXcXgX%XgXkXcXcXvXvXcXzXkXkXFXFXnXvXzXhXkXnXkXkXkXcXcXcXcXcXcXkXgXXJXPXPXPXPXDXzXhXkXkXcXcXcXkXzXkXkXhXhXgXhXvXnXFXPXGXPXGXGXGX$XXXhXkXzXcXnXFXGXoXXJXJXJXJXPXPXPXPXPX].oXXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX|.BXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXFXBXnXgXkXcXcXcXvXBXFXFXnXnXBXvXkXkXcXBXGXPXPXPXPXPXPXPXPXPXoXPXnX].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX2 T / / / / / / / / / / / / / ^ / / / / / / / / W +.JXJXJX].G D D D F H H R R W W W W W ^ ^ W ^ ^ ^ C.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNX3.&.&.(.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXfX/.3.2.&.&.&.&.&.=.2.&.&.&.&.&.&.2.:XJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX).GXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXGXvXkXvXFXFXFX%X].].oXXXX|.%.C FXPXPXPXPXPXPXPXPXPXPXPXPXXXhXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXO.@ H / / / / / / / / / / / / / f.kXJXJXJXJXJXJXJXJXJXJX).Y T R W ^ ^ / / / / / / / / / / T .NXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX3.&.&.&.2./.PXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXfX(.3.2.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2./.NXJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX|.PXPXPXPXPXPXPXPXPXPXPX%X|.hXJXJXJXJXJXJXHXHXJXJXJXJXJXJXPXgX).|.$XoX#X%XkXBXPXPXPXPX|.kXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX].i 4 G J G H H G I P P J K [.GXJXJXJXJXJXJXJXJXJXJXLXkXG R E W ^ / / / / / / / / / / U v.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX(.2.&.&.&.2.(.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXfX/.3.3.2.&.&.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.3.(.NXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX|.PXPXPXPXPXPXPXPXPX%X|.kXJXJXJXJXJXJXJXJXGXPXJXJXJXJXJXJXGXGXGXPXPXGXcXgXoX`.%.%.+X].BXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXk.J / / / / _ I gXBXlXkXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJX| T W ^ ^ / / / / / / / / ^ } 9XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXNX2.&.&.&.&.*./.NXJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXDXMX:X/.3.2.*.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.*.&.2.3.:XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXFX#XXXXX].XXoXoXoXoXvXJXJXJXJXJXJXJXJXJXJXHXGXHXPXJXJXJXJXJXJXJXGXGXGXPXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXxX[.h.h.'.oXFXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGX+.R W ^ ^ / / / / / / / ] [.JXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXPX8.&.&.&.&.&.2.3.:XPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNX:X(.3.2.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.2.2./.fXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXHXPXHXGXJXJXJXJXJXJXJXPXGXPXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX%XF ( ^ / / / / / / { k.vXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNX2.&.&.&.&.&.&.2.8.fXPXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXDXfX(.8.3.3.2.2.=.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.3.:XNXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXJXJXJXJXJXJXGXGXPXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXK W / / / / / / n.@XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXXX&.&.&.&.&.2.2.&.2.3.:XGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXPXNXfX/.3.2.2.2.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.=./.fXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXGXPXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX'.J / / / / / U +XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX/.2.&.&.&.&.&.&.&.&.&.2./.fXDXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXJXJXJXJXPXJXJXJXJXJXJXJXJXDXJXJXJXPXJXJXNX>X(.3.2.&.=.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.2.2.3.fXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDX- T / / ` U @XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX(.=.&.&.&.&.&.&.&.&.&.=.=.3.3.(.>XNXDXJXPXJXJXJXJXJXDXJXJXJXPXJXJXJXLXJXJXDXDXJXJXJXJXJXJXNXNXfX:X(.8.3.2.=.&.=.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.2.8.:XDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLX%XA b.wXlXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX:X2.&.&.&.&.&.&.&.&.&.&.&.&.&.=.2.3.3.8.8.(.:X:XfXfXfXfXfXfXfXfXfXfXfX>XfX:X(././.3.8.3.3.2.2.&.&.&.2.=.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.=.*.3./.fXNXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXPXPXNX3.=.&.&.&.&.&.&.&.&.&.&.&.&.=.=.&.2.2.&.2.2.2.2.2.3.2.2.3.2.2.2.2.2.2.2.2.&.2.2.&.&.&.&.=.=.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.=.2.3.(.lXFXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPX:X3.*.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.=.=.=.2.3.3.:XNXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXPXJX:X3.2.*.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.=.=.2.2.3./.:XfXJXJXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXJXGXfX/.3.2.=.*.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.2.&.&.=.=.2.2.3.3.(.>XNXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXfX/.3.2.2.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.*.2.2.2.3./.(.>XNXJXPXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXNX:X(.8.3.2.2.&.&.&.&.=.&.&.&.&.&.2.&.&.=.&.&.2.&.&.&.&.&.=.&.&.=.=.2.3.3.3.8.:XfXfXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXDXNXfX:X/.8.3.3.3.2.3.3.2.2.3.2.2.3.2.3.2.3.3.3.3././.:X:XfXMXNXDXJXLXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXDXNXNXlXlXfXfXfXfXlXMXNXNXFXGXHXHXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXJXJXJXDXGXJXJXJXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJX", -"JXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXPXGXGXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXHXHXPXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXPXHXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJX+X`.GXJXJXJXJXJXJXJXJXJX GXJXJXJXJXJXkX %.JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXk XXJXcXA kXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX: gXJXJXC /.FXHXHXHX. JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDX . JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX#XS DXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJX: hXJXJXJXGXPXJXJXJXJXx ].JXJXJXJXJX1 #XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX%X `.JXJXJXJXJXJXC cXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXx XXJX]. S JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXHXGXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXPXGX: . gXJXJXC /.JXJXJXLX JXJXJXJXJXJXJXJXJXJXJXHXHXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDX . JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXl $XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJX: gXJXJXJXJXGXJXJXJXJXx ].JXJXJXJXJX1 +XJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXV X nXJXJXJXJXJXDX+ x JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXk ].JXFXoXBXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXHXHXGXKXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXKXPXPX: gXJXJXZ O.JXJXJXJX JXJXJXJXJXJXJXJXJXJXGXPXPXJXJXJXJXJXJXJXJXJXJXJXGXJXJXFX . JXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXvX+XJXJXJXJXJXJXJXJXGXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXDXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXLXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJX1 %X%.x j C kXJXJX).j + : 1 V JXS j X : j Z JXnXk S gXC k k S cXJXJXJXJXzXS k j V +XJXJXJXJXJXJXJXJXJXJXBXX x JXJXJXJXJXJXXX +XJXJXgXk ].].k k ).GXJXGXoXx j k Z $XJXJXJXk |.JX#Xk XXJXJXPX . oXJX%X. # nXJXJXJX`. k GXx `.JXJXJXx ].JXJXJXJXJXj +XGXk ).JXJXJXFX1 ].JXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJXJX", -"JXJXJXJXJXJXJXJX: . ).JXGXPXGXx ).JXJXx ].JXJXJXJXJXj +XJXJXJX.,X>X%X%X&X&X:X%X%X%X%X&XY } =.8.U ;.BX7X7XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.. `.7.@.8.p.8.8.p.y.i.i.8.8.i.p.p.8.| l.Q.T +.y.p.y.i.i.i.@.s.7XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XVXVX#X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X+X] s t s s s s s t t t p x ] X+X+X7XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XVX@X X+X+X+X X#X5 e j f p s t t t t s s s s s s s s s s s s s s s s s t t s p j ` +X+X7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X7 *X:XsXeXwXwXqXuXfXxXnXxXdXqX3X2X0XkXnXzXpXpXpXxXzXpXpXpXzXkXkXzXxXgXeXeX3X2X2X3XqXgXsXsX2X3X4XfXsX0X0X0XpX0XkXbXcXlXlXlXlXkXlXcXbXbXbXbXvXMXvXcXcXcXcXcXlXlXlXkXcXbXbXcXbXcXkXkXxXdXuXpXlXkXuXxXxXfXuXpXzXxXfX4X2XkXbXbXcXcXbXcXbXbXbXkX0XkXkXkXkXlXlXcXcXcXcXlXkXkXkXkXlXlXlX{.{.-XX-X2X{.I | t.E t.t.R Y l.7.^ +X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XVX(.Z.;.T 3.9.0.i.i.i.i.i.i.i.8.8.i.8.i.i.8.*.3.&.7.&.| &.VX-.| +.+.s.SX@X#X#X#X#X#X#X#X#X#X#X#X#X#XBXSXK.L.K.9.0.R.BXAX#XmX@XBX!.;.s.7.@.@.H.7X#X#X#X#X7XVXs.a.a.@.K.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X+X@Xd t t s s s s s s s s s s t t t t t s s i i i p d d d d d d d d d p p i i i s s t t t t t t t t s s s s s s s s s s s s s s s s s s s s s t t t t i f x SX X+X+X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ $XsXqXwX3X3X3XfXxXfXsXwX4X3X0XzXkXnXnXpXkXxXzXkXpXkXlXnXnXkXlXfXeXwXsXsXhXhXgXsXsXwX3X3XlXlXkXzXpXpXpXpXkXcXzXnXnXnXMXMXMXvXlXkXkXcXbXbXbXbXcXbXbXbXvXbXbXbXcXbXcXcXbXbXvXuXeXqX3X3X0XkXkXxXuXzXvXlXxXzX0XuX0XvXvXlXlXlXbXcXvX'.vXvXlXlXlXvXcXlXcXcXcXcXcXbXN.,X,X,Xf.n.b.m.B.m.m.w.w.G I.>X:XdX-X-X:X{.3.5.5.t.t.E E U Z.. #X_.#X#X#X#X#X#X#X#X#X#X#X#X#XBX$Xi.3.U +.8.0.s.i.p.i.i.y.i.y.i.i.i.8.i.8.=.7.3.3.+.3.| A.. ` 0.-.L.VXBX_.% #X#X#X#X#X#X#X#X#X#X#XBXW.9.@.6.p.-.s.p.L.+X+X. +X;.i.8.8.p.i.@.s.@X@X% #X#X+XC.@.G -.y.a.`.VX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X X+Xx s 9 t s s s s s s s s s s s s s s t t t t t t t t t t t t t t t t t t t t t s s s s s s s s s s s s s s s s s s s s s s s s s t t t t s p g v #X+X+X X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XD jXqX4XwX2X2X4XxXfXsXwX3X3XuXnXnXzXuXkXzXnXzXkXkXkXkXkXlXlXcXlXqXeXwXsXgXsXsXfXsXqX3X3XkXlXkXkXlXkXkXpXpXlXlXzXkXlXlXvXMXvXlXlXlXlXcXbXbXcXbXvX'.J.h f.[.vXbXcXcXbXcXbXbXcXeXwX4X3XuXlXkXxXzXzXvXnXnXzXpX4XpXvXvXvXlXlXbXcXvXMXvXlXcXbXcXLXLXbXcXcXbXvX6X,X,X,X,X,Xg.b.b.w.B.m.m.m.w.w.w.Y.L.[ [ [ hX:Xl.+.3.5.5.t.5.U l.(.BX#X#X#X#X#X#X#X#X#X#X#X#XVX#X7X$X8.+.@.7.+.@.i.p.p.p.a.6.6.i.@.8.7.7.i.9.&.7.&.3.| 3.C.+X#X7X7X7X+X@X_.#X#X#X#X#X#X#X#X#X#X#X#XVX`.H.a.8.a.a.s.s.0.9.;.;./ ;.-.a.p.i.9.0.i.8.!.CXVX_.BXP 6.y.s.-.p.y.F.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X@X+X#Xg s t t t s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s t t t t t s p f e SX X+X+X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #XAX_ yXtXwXwXeX2XsXgXsXwX3X3XuXkXxXzX4XeXxXnXzXkXlXkXkXkXkXzXgXxXdXrXwXeXsXsXfXsXsXwX3X2XkXcXkXkXlXkXkXpXpXlXcXvXvXvXcXvXvXnXlXlXcXlXcXbXbXvX'.G.l.v.c.v.=.v.f.'.cXbXbXbXbXbXkXfXuXpXvXvXzXxXuXpXMXMXnXpXpXuXpXcXcXvXlXcXbXcXMXvXlXlXbXbXbXbX<.,.LXbX,X,X,X,X,X,X>X,Xz.n.b.b.m.m.m.w.m.n.m.L.L.C.C.H.[ .Xk.| 7.*.*.3.5.5.O.=.#X@X#X#X#X#X#X#X#X#X#X#XVX` K.L.` K.+.+.+.H.-.y.a.a.6.C._.W.`.S.+.&.*.&.T 7.+.0.(.R.^ +XVX#X#XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#XAXVX+XC.8.s.7.8.9.q.i.9.9.7.*...*.i.p.a.8.0.-.p.7.8.R.+X7X7X-.@.p.0.a.p.p.F.7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmX#X X+XSXg i s 9 t s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s t t t t t s i p f x ` X+X+X X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ D }.mX*X%X%XsXsXwX3X4XkXzXzXpXqX%XdXnXkX0X4XxXpXkXpXiXCX. _ fXpXqXeXsXxXxXsXsX4X3XpXlXvXkXlXpXkXkXpXlXvXMXMX'.MXMXlXcXcXcXbXcXcXcXbXl...j.z.v.f.f.z.d.v.=.'.bXbXbXbXbXvX,X{.vXMXvXkXzX0XpXMXnXnXkXpXuXpXcXcXlXlXcX9XvXvXlXlXbXbXbX9XvX,X,X6X,X,X,X,X,X,X,X,X9Xj.g.g.n.n.m.m.b.b.n.B.C.H.S.S.S.S.[ w.1.t.+.7.&.3.3.5.| Z.CX#X#X#X#X#X#X#X#X#X#XVXL.L.C.H.`.` L.L.P V.8.9.=.3.&.(.. . `.*.3.*.*.&.7.8.` . +XBX#X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX7X7X7XCXm.s.s.8.q.i.9.0.0.a.8.*.E Y u.3.p.a.i.=.i.9.8.8.Y.BX7X9.@.p.i.p.p.y.F.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X X+X].N g i t t t t t s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s s t t t t t t s i p f j a #X X+X+X X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XD D Z =X%XeXsXwX3X3XpXzXxXpX4X%X%X:X:X:X%X:X:X-X%X%X:X=XaX4X4X%XsXfXfXxXsXsXqX3X0XcXkXlXkXkXkXpXpXlXMXMXMXMXMXMXlXlXcX6XbXbXcXbXG.*.z.j.z.d.f.d.G.z.J.J.G.,X,XbXbX,X>X>X>X>XMXMXnXpXpXvXMXnXMXkXpXuXlXcXlXlXlXcXbXcXcXcXbXbXbX9XbXbX,X,X,X,X,X,X,X,X,X,X,Xh.G.v.f.f.z.g.S.m.w.n.B.S.V.B.B.B.B.B.S.g.X.r.r.&.7.*.3.O.;.CX#X#X#X#X#X#X#X#X#XVX_.Y.W F.S.U.. 7XY.5.=.c.u.5...O.:.Y.. 7X=.3.&.*.&.&.q.+XBX_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XBX] m.-.Q n.L.H.q.i.=.i.0.a.i...M.l.l.=.&.8.p.v.T q.n.q.i.=.(.R.0.-.=.0.p.i.y.F.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X+X+X@XN g d i s t 9 t t t t t t s s s s s s s s s s s s s s s s s t t t t t t t t t s i i d f x v ] X+X+X X@XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X< Z &X:XsXsXqXqXuXxXzXuX-X:X-X:X:X:X-X:X:X%X:X:X%X:X:X%X%X%X-XfX0XsXsXsXqX3X4XlXkXlXlXMXnXkXkXlXvXcXvX'.MXMXvXkXcXbXvXbXcXzXJ.d.z.j.z.d.z.G.G.l.{.,X>X>X>X>X>X>X>X>X>X>X>XMXkXkXkXlXvXnXnXkXpXuXvXbXcXcXcXbXcXbXbX[.J.v.G.bXvXvX,X,X,X,X,X,X,X,X,X,XL h.j.c.J.z.G.G.v.G G m.m.S.V.V.B.B.B.m.V.C.S.j.,.r.@.7.| | ' X#X#X#XAX#X#X#X#X#X7XP S.K.L.H.Q ;.*.5.&.=.i.*.3...*.4.{ g.g.*.&.&.7.=.&.&.C.+X7X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX7X7Xb.+.@.y.i.=.b.T =.=.n.p.a.5.d.H.p.i.i.9.0.=.&.9.q.T 9.9.p.B.T 0.s.=.8.8.i.6.Q 7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X@X+X+X X#X` z g f p i i s s t t t t t t t t t t t t t t t t t s s i i d d g j e n ] 7X X+X+X X7X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X< yX%XsXsXwXsXxXxXzXuXwX:X:X-X-X-X:X:X:X:X%X:X:X:X:X%X%X%XsXxX4XxXxXsXqX3X4XkXlXcXlXMXnXkXpXcXvXbXbXvXMXMXvXkXlXbXbXbXcX>Xc.l.G.z.f.f.z.G.f.z.x.j.X.x.,X,X>X>X>X>X>X>X>XpXvXkXkXkXkXMXnXkXpXpXcXcX'.9XbXbXbXvXd.T c.v.c.l.Y z.G.,X,X,X,X,X'.x.j.h.N.h.x.f.v.d.k.X.N.{.dXS.B.C.S.S.S.V.V.B.S.V.C.V.1.$.2.&.-.P @X#X#XBXVX@XVX#X_.#X@X_.H.S.C.H.C.L.V.3.&.7.*.&.7.7.*.*.*.4.} :.5.&.7.7.7.7.8.3.w.+XVX#X#X#X#X#X#X#X#X#X#X#X#X#X_.#XBX. V.3.i.i.0.a.&.b.0.=.8.9.p.p.4.8.o.p.p.p.y.y.i.T =.=.8.9.s.0.7.8.=.7.i.8.8.p.6.F.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX@X X X+X+X X#X8 N v x x x x x x x x x x x x x x x x v n ` _.@X7X+X+X+X X X X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X).< =X:XsXsXwXfXxXuXqX%X%X%X:XSX=X-X%X%X:X:X:X:X:X:X:X:X&XsXgXhXxXfXsXqX3X4XzXlXcXlXnXkXkXcXcXMXMXbXbXMXMXlXlXkXcXbXbXcX[.q.v.d.l.f.d.d.v.J.L x.h.L F F {.>X{.>X>X>X>X>X>X>X>X>X>XnXnXnXkXpXpXcX[.l.z.'.{.h *.d.f.c.v.c.f.z.k.l.'.,X,X,XE ..d.d.x.h.L N.j.k.j.J.A .X-X,XJ.o.V.S.S.S.C.S.S.V.B.S.S.d.r.2.2.u . . BX@XW.Z.L.` @XBX+X. DXB.S.H.S.H.K.C.7.3.7.7.7.7.7.7.&.5.5.5.5.*.&.7.7.&.&.7.T | ;.. VXSX#X#X#X#X#X#X#X#X#XmX#XBX7X$XY.0.0.8.p.i.7.i.0.p.i.i.i.8...z.i.y.p.0.0.p.q.c.q.8.q.9.i.s.0.8.8.8.i.i.p.p.6.s.#XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X@X X X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X X X7X@X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.mXD < =X2XfXqX0XpXfX:X%X:X%X%X=X=X%X:X:X:X:X:X:X:X:X:X&X&XdXxXgXxXxXsXqX4XqXxXkXcXvXnXkXlXcXvXMXvXcXcXvXMXMXlXkXlXbXbXcX0 q.v.d.d.v.J.f.v.x.h.h.N.L N.1.9X,X>X,X,X>X>X>X>X>X>X>X>X>XnXzXkXpXMXj...d.J.l.v.=.d.z.v.v.f.f.l.d.z.E {.,X,X'.E d.l.J.z.h.N.L <.x./.$X+X+Xg J 1.g.w.m.K.H.V.S.S.S.S.S.C.d.I E R 2.g.A.' R.B.B.B.K.VX(.Y.V.Y.S.S.S.S.S.C.K.9.&.7.i.y.8.&.&.&.&.5.t.4.5.*.&.&.*.&.&.T =.U A.+XAXmX#X#X#X#X#X#X#X#X#X7X!.K.S.+.s.s.i.a.9.=.8.i.p.i.i.8.&.u...p.a.p.9.9.0.s.b.9.9.q.i.p.i.i.p.p.p.p.p.0.i.y.W.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X< D *X:X2X2XtXAX*X:X%X:X%X:X%X:X:X:X:X:X:X:X-X:X:X:X:X&XgXzXxXxXsXqX4X4XzXkXvXMXnXkXkXlXcXcXlXcXcXvXMXMXMXlXkXcXbXcX[.T f.z.z.f.l.n.Y $.F L L N.N.h.'.,X,X,X>X>X>X>X-X>X-X-X>X-XkXkXpXpXj.R z.J.d.f.f.f.z.G.J.c.J.z.z.d.z.z.x.6X8Xj.J.l.d.z.d.x.N.N.N.HX. +X#XCXQ.{ { X.n.C.K.m.w.B.V.S.C.C.B.%.2.4...5.} l.S.B.V.S.V.C.Y.q.3.3.T.Y.S.S.V.S.C.K.p.6.i.8.3.4.2.1.2.r.t.t.4.%.4.t.*.&.&.7.8.8.3.O.A.+X#X#X#X#X#X#X#X#XSXBXW.s.-.s.7.8.y.p.p.8.&.&.i.p.y.p.i.7.=.&.=.p.q.i.p.p.a.a.l.=.8.i.i.p.p.a.p.p.p.p.y.6.P.CX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAXD #X*X}.~.D D yX&X:X%X:X:X:X:XrX:X-X-XrX=XtX:X-X:X:XsXxXxXxXsXqX4XpXpXkXvXnXlXkXkXkXlXcXbXbXbXcXMXMXvXlXpXcXbXcX-Xf.l.d.G.d.f.f.{.{.{.x.h.x.N.x.{.,X>X>X>X>X-X-X-X-X-X-X-X-X>XkXkX,X{.k.z.z.J.f.l.z.J.d.G.d.J.J.J.z.z.G.h.>.>.j.z.z.G.G.z.x.N.h.J.@X@X#X#X#X. I.<.v.m.w.b.m.B.B.S.V.b.T.g.z.R r.O.I *.K.K.S.S.S.V.V.V.H.S.Y.H.S.H.H.H.H.K.V.7.8.u.>.#.#.1.#.>.<.#.%.4.4.4.t.*.&.&.&.8.T 7.U T 7X@X#X#X#X#X#X#X#X#XBXK.s.i.&.8.8.p.p.p.i.5...8.i.p.p.s.s.9.9.T 9.8.i.p.p.a.8.t.&.8.i.p.a.a.a.a.a.a.i.@.8.( BXBX_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XD D D _.#X#XD D iX:X:X:X&X:X:X:XrX*X_.D _ Z *X:X%X:XsXxXxXfXuXzXpXuXzXMXvXlXkXkXpXpXcXbXcXbXvXcXvXMXlXkXvXbXbX-Xg.d.v.v.J.z.G.{.-X{.x.x.j.h.9X,X,X>X{.>X>X>X-X-X-X-X-X-X-X{.6X9XN.N.G.z.z.f.f.J.J.z.x.9Xj.z.z.G.j.h.h.<.h.G.j.j.G.k.G.k.1.>.>.I.BXVX#X#X#X. FX>.v.V.b.w.m.V.S.S.m.n.B.Y.C.c.4...z.e.L.C.C.C.V.V.V.S.C.L.H.S.H.H.C.C.C.L.n.3.p.=.4.1.#.>.#.2.1.2.R u.R t.5.3.*.&.=.=.8.7.&.5.( +XSX#X#X#X#X#X#X7XW.B.o.7.8.7.7.i.8.8.5.t.=.q.T 9.p.-.-.y.p.p.i.i.i.p.a.p.3.*.=.8.7.&.7.&.5.8.8.3.&.*.l...c.`.CX+XBXBX@XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAXD }.tX=X:X:X=X*XD _ AX#X_.ZX7 `.iX&X:XeXeX:XsXnXxXxXpXkXzXpXpXpXlXlXcXlXcXMXvXcXvXMXlXkXvXbXbX2XX>X>X-X-X-X-X>X{.F #.#.N.<.G.c.d.d.z.J.z.N.$.h.d.k.x.x.A A.X.$.j.j.k.j.j.x.z.M.>.1.w.VXBX_.#X@X@XVX+Xf...m.T.w.T.V.C.K.C.L.L.C.K.C.B.B.I.H.H.S.C.m.w.V.V.S.C.C.C.C.C.C.C.H.C.K.s.s.8.8.8.3.t.t.%.%.4.4.R t.%.4.t.3.*.*.&.=.T 7.&.| n.7XVX#X#X#X#X#X#X+XQ.0.-.7.&.3.7.i.i.=.r.u.c.q.8.7.9.p.p.a.a.p.p.8.8.9.p.i.7.7.7.i.*.l.*.3.5.5.1.1.5.u.t.l.*.w.$XZ.Y.Z.#X7XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XD _ Z 1X$X< < AX_.#X#X#X#X#XD D 1X=X=X&X&X&XqXnXnXzXzXsXuXpXlXcXcXkXlXMXMXvXcXcXMXvXlXkXcXbX3X-X9.b.m.w.J.x.x.k.x.j.j.h.f.b.v.j.%.j.%.G.{.{.[.>X>X>X>XH x.L j.h.8Xj.v.l.z.z.f.G.N.N.x.l.d.k.k.@X. g.X.k.j.k.x.x.j.G.#.{ c.CX+X$XBX. BXVX. . . /.B.S.B.m.S.L.K.K.Y.S.Y.Y.Y.Y.V.T.m.V.C.C.V.m.V.S.S.C.C.K.K.K.K.K.L.L.T.H.-.5.&.9.b.s.p.&.2.2.2.r.4.4.4.4.5.*.5.3.&.=.&.&.*...DX+X#X#X#X#X#X#XBX' 3.7.8.*.8.0.s.s.q.q.9.9.9.9.7.9.0.p.a.y.i.i.8.7.7.8.p.a.i.6.3.m.[ &.8.8.*.r.4.3.5.5.l.f.*.=.=.=.7.m.#X@X#X#X#X#X#X#X#X#X#X#X#X#XSX#XBXBXBX7XAXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX7 < AX_.#X#X#X#X#X#X#X#X#X7 D 1X*X*X*X*XxXnXnXzXsXfX4XlXlXpXpXuXnXvXMXvXcXvXMXkXkXvXcXMX9.b.b.w.B.w.c.z.j.k.j.h.z.n.b.v.j.k.j.j.j...=.....z.k.Y j.j.j.j.j.h.N.G.l.J.z.d.d.h.N.h.d.d.d...Z.( j.j.k.k.j.x.x.G.h.,.2.3.Q . +X@Xl j.h.l Q.. . S.v.w.V.S.C.C.V.V.S.C.C.C.S.Y.C.S.C.K.Y.V.V.V.S.H.K.C.K.K.L.L.K.b.n.c.m.w.c.V.;.;.;.S.0.O.#.2.4.t.t.4.%.t.3.*.*.*.=.7.&.&.| Y.CX#X#X#X#X#X#X7X` 7.@.8.=.0.0.0.9.T q.9.i.i.0.p.8.*.7.8.8.=.=.=.&.7.*.4.3.&.&.u.b.q.7.8.8.7.u.3.*.*.&.&...&.i.9.0.s.7.C.+X#X#X#X#X#X#X#X#X#X#X#X#X#XVX` R.^ ` VX#X#X#X#X#X#X#X#X#X#X#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X).< D ~.OXuXuXzXuXqX4X0XkXuXuXuX0XpXlXlXcXcXvXMXlXkXcXlXMX=.b.b.b.b.w.T.m.d.j.j.z.c.v.c.v.j.z.G.x.x.z.d.z.z.j.k.k.k.j.j.h.x.x.N.j.l.d.z.d.f.h.N.N.d.f.l.f.l.k.k.G.z.z.j.x.x.#.#.#.>.>.... . j.J ,.,.,.<.z.y x.,.{ Y T.P.K.b.V.V.Y.L.C.H.K.K.K.C.C.L.Y.w.T.V.S.C.K.S.S.g.n.n.v.c.b.w.e.K.K.S.K.L.R.C.&.r.4.R 5.t.4.t.t.5.*.*.*.&.7.7.7.U v.VXVX#X#X#X#X#XVX X9.3.7.+.i.p.8.q.9.i.8.p.a.a.i.5...q.=.s.9.=.q.q.0...>.4.c.T *.5.*.8.8.8.*.t.&.7.*.=.*.7.-.0.8.9.s.6.V.7X#X#X#X#X#X#X#X#X#X#X#X#XBXW.8.@.i.i.Q 7X#X#X#X#X_.VXVX7XBXBX7XBX7XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#X).AXgXuXuX0X0X0X0XkXnXxXxXuX0X0XlXlXcXcXlXMXkXlXlXMXc.z.g.w.n.g.n.n.m.g.j.z.c.c.v.v.v.j.x.k.j.j.k.f.d.x.G.k.z.d.G.j.h.N.j.N.x.d.d.d.z.v.x.$.N.k.v.l.v.d.d.f.l.d.G.z.X.<.>.#.1.#.>.<.g.J.J #.%.%.X.#.,.,.,.2.M.%.n.L.V.I.V.S.S.S.V.Y.L.C.L.C.K.R.K.b.T.B.S.S.V.m.T.n.z.d.T.T.T.b.V.~ K.S.Y.L.K.Y.0.3.4.4.t.t.4.4.u.5.5...5.*.l.*.7.&.U /.CXmX#X#X#X#X_.+X!.+.7.8.8.9.q.i.8.i.p.a.a.p.7.B.L.S.8.i.8.8.B.9.p.8.z.l.7.7.&.7.7.7.7.&.t.t.7.7.&.=.&.T a.p.i.i.0.@.B.BX#X% #X#X#X#X#X#X#X#X#X#X7XC.s.p.y.6.H.CXCX+XBX7X7X@X`.`.^ C.S.H.`.BXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ iXqXqX0X4X4X4XuXzXxXxXuXpXuX0XkXcXcXcXvXvXlXxXI.b.s.f.d.c.g.g.n.n.n.b.f.V.;.v.v.c.G.h.x.z.k.j.j.z.G.G.x.x.z.k.j.k.x.N.N.8XN.d.d.d.d.f.G.$.$.j.v.f.l.l.f.l.l.d.z.*.E h.#.1.1.1.2.r.<.J >.1.X.1.F $.#.>.%.S.K.H.b.b.Y.H.B.S.S.e.B.C.K.S.S.C.K.K.L.T.m.S.T.V.H.m.S.H.c.c.m.T.b.n.K.C.H.S.S.H.C.Y.B.&.u.t.r.%.r.2.O.t.5.5.5.5.E Y Y &.O.w.. . +X+XVX#X#XmX7X`.$X`.q.8.8.i.p.a.p.a.a.8.=.V.9.&.8.i.8.7.0.s.i.a.=...3.&.8.7.8.&.u.4.4.t.&.7.&.8.T 7.8.i.i.i.9.8.9.#XVX#X#X#X#X#X#X#X#X#XSX7X+Xs.0.9.8.6.L.SX;.S.Y.`.mXB.i.9.8.@.+.+.p.VX7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX#X7XBXBX7X#XmX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX_ rXqXrXeXqXsXsXsXxXxXzXzXuXuX0XzXcXbXcXcXcXbXjX[ c.b.I.z.z.g.n.g.g.n.n.c.Y.ZX] e.X.x.k.k.d.z.x.j.G.G.x.N.h.x.j.G.x.N.N.<.<.$.G.d.z.d.f.k.N.N.h.d.v.f.l.l.f.k.j.u.+.&...X.1.2.2.r.4.1.<.$.#.#.$.<.<.$.1.n.K.C.L.K.V.K.K.V.S.K.L.C.C.H.Y.S.S.K.L.K.S.S.V.b.B.B.S.S.Y.n.c.B.w.T.B.m.V.H.H.S.S.S.S.Y.q.5.*.4.2.r.#.M.&.5.5.u.4.u.u.E E E d.Y.!.R./ @XCX+X#X#X7X. ' 0.8.i.p.a.a.a.a.i.*...5.5.7.8.i.a.8.=.0.i.a.8.&.7.7.8.8.&.5.r.4.R %...7.8.q.8.7.&.&.8.8.8.9.T OX+X#X#X#X#X#X#X#X_.mX+X7X/ q.=.9.p.=.=.=.*.&.7.s.c.3.6.y.i.a.o.0.+.C.CX@XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#XmXVXBXW.L.L.^ 7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAXAXhXsXdXeXwXsXqXqXsXxXxXxXuXpXkXeXO vXvXMXvXdXZXS.c.n.T.f.j.J.n.n.n.n.g.c.z.;.. k <.z.z.k.z.k.j.d.v.x.N.N.N.N.h.h.N.N.N.N.$.h.k.d.z.z.f.z.N.x.N.h.k.z.d.l.d.c.A w.| &.3.*.%.1.1.2.r.1.$.#.#.$.#.#.F 2.2.n.S.S.S.Y.K.Y.H.C.K.K.K.K.K.C.C.S.V.S.C.K.K.V.w.b.m.B.S.C.S.c.c.B.m.B.V.V.V.S.S.S.S.C.S.S.m.*.....2.4.1.M.B.5.t.5.t.t.t.E *.&.*.| 5.5.*.c.Y.DX+X+XBX`.s.s.8.8.i.p.a.a.*.4.r.u.E ..E ..&.p.6.=.8.i.p.8.&.7.7.7.3.t.4.r.%.X.#.t.i.q.q.9.8.8.8.i.8.8.9.=.Z.. SX#X#X#X#X#XVX@X. DXm.=.=.=.i.0.&.=.7.=.=.=.9.8.t...p.a.i.[ o.@.7.C.BXVX#X#X#X#X#X#X#X#X#X#X#X#X#X_.VX+XQ 6.a.y.6.` +XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAXD dXqXwXwXeXeXwX4XuXfXzXzXzXpXkXjX. AX1X#XZXCXAXn.c.g.b.f.z.d.g.n.g.v.c.c.d.w.A #.j.k.j.z.z.k.G.f.z.N.N.$.x.g.g.x.,.N.h.N.h.L j.z.z.k.l.l.N.h.x.$.N.h.L h.$.1 . CX*.>.#.#.#.1.>.$.$.#.2.2.X.F 1.1.1.>.z.L.C.S.C.m.S.C.Y.C.C.C.K.L.K.H.V.V.B.V.S.T.T.B.S.b.T.V.Y.S.n.v.v.m.V.V.S.Y.C.Y.S.S.H.C.S.B.V.=.t.Y 2.t.O.<.m.K.r.4.5.3.5.3.*.&.&.7.7.&.+.U U *.V.DX+X`.8.8.8.=.8.7.6.5.2.r.u.........5.3.7.7.q.8.i.p.8.5.4.4.X.2.4.4.r.X.%.2.5.i.i.p.p.p.p.i.8.8.i.p.=.w.VX@X#X#X_.7XBXVX#XQ.b.s.7.=.T 8.9.q.9.=.=.&.l.c.9.*.k...y.6.@.i.p.8.+.Z.. BX+XBX7XVXAXSX#X#X#X#X#X#XAX+XR.a.-.-.a.6.s.SX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ aXgXsXeXeXwXsXqX4X0XpXpXpXkXeX_ #XZX~.@X).@X#Xe.v.c.n.f.z.d.J.c.c.v.v.f.f.v.d.d.z.k.j.k.x.x.k.x.x.L N.f.L.P.D.K.h.<.h.h.j.j.j.z.z.k.z.f.x.N.j.N.N.h.x.N.,.e.CX#XN.J <.$.$.#.$.<.<.#.%.%.2.2.1.#.<.>.B.L.C.C.L.b.c.S.Y.S.S.S.C.Y.V.B.m.V.S.S.K.b.v.B.B.V.V.S.H.g.v.v.d.b.H.S.S.C.K.K.S.S.H.Y.S.B.T.I...R M.4.t.{ 1.~ m.>.4.5.*.*.7.7.7.7.7.7.7.8.7.+.U *.b.m.8.8.8.7.7.*...5.3.&.&.*...*.*.u.*.+.S.s.7.i.p.7.4.1.#.2.%.X.2.2.t.3.t.*.p.9.p.p.0.a.i.8.6.p.i.8.5.( CX#XVX+XDXB.s.9.3.8.p.8.b.i.i.9.p.*...b.0.7.=.=.c.d.j...W -.9.0.8.8.q.Q.(.Q.!.`.#X7XVX_.#X#X#X#X#X7XL.6.a.-.6.-.-.@.L.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ dXzXfXfXxXxXqX0XkXpX0X0XkXeX_ _.#X#X#X+X. +Xe.c.q.n.J.d.J.d.J.f.v.v.J.d.J.v.g.f.l.k.N.x.z.G.h.x.h.g.! F.L.U.! I.,.$.x.k.Y j.z.k.k.z.f.z.N.N.$.N.N.x.N.<.XXDXN.J <.$.#.#.<.<.<.<.#.%.2.#.1.#.{ k.n.C.V.S.K.C.T.m.S.Y.Y.S.m.T.S.V.V.V.V.K.H.Y.K.b.T.V.S.S.Y.T.c.l.z.k.b.C.H.H.K.L.L.C.H.H.S.S.B.b.b.v.U ..R t.t.<.v.! r.4.2.2.5.3.&.7.7.7.8.i.8.8.7.7.7.+.&.i.8.8.=.7.&.*.&.i.9.p.=.*.u.4.r.t.*.s.T 7.i.p.&.2.#.1.R R r.4.&.8.8.7.8.0.p.p.0.a.0.7.6.i.i.i.8.5.b.. +X@X$X0.+.6.7.i.i.8.q.0.i.p.p.a.&.*.i.p.8.8.7.9.v.j.X.Y.-.s.-.8.i.8.&.q.T 7.T T ;.+XVX_.#X#XSXBX`.i.p.p.p.y.[ -.@.o.@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ hXuXfXfXxXfXuXkXzXpXpX0XpXfXD ).#X#XCX' ) I.G.J.g.v.G.d.l.f.z.J.c.f.f.d.d.l.f.v.J.x.G.J.j.h.l.d.x.V.! U.K.L.F.K.d.J.d.x.h.h.j.z.z.d.z.z.j.x.h.N.h.h.x.$.L <.^.<.N.$.,.,.<.,.<.$.#.1.r.2.#.$.c.K.S.B.S.K.S.V.S.S.S.C.H.m.w.V.B.H.C.H.V.S.S.H.K.b.T.S.Y.H.T.c.l.l.n.c.b.K.Y.C.K.K.C.V.w.S.S.B.m.b.b.w.d.O.R 4.5.t.2...*...%.2.r.R t.5.5.t.5.&.y.i.i.q.n.=.7.8.y.i.8.8.7.&.0.s.i.i.8.&...u.u...T &.&.8.p.y.1.>.1.2.u.4.4.-.-.8.8.8.a.0.i.p.a.s.8.O.*.8.8.7.=.&...Y.Y.0.9.9.i.i.p.p.a.8.&.7.p.i.a.p.t.3.7.p.p.8.8.8.&.z.j.3.y.i.p.p.p.p.6.0.-.8.7.l.*.Z.7X#X#X#XAX+XC.p.a.a.p.i.-.a.6.-.#XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ jXuXeXfXzXqXzXnXnXzXpXpXpXuX1X_ #XCX/.z.N.x.G.k.J.G.k.d.f.v.z.G.z.J.f.f.d.d.f.c.z.G.j.x.h.g.K.C.m.S.L.L.K.L.L.U.C.S.C.v.h.h.h.k.z.k.k.j.k.k.k.k.k.G.d.N.^.J ,.<.<.<.$.$.,.<.$.$.#.#.#.X.h.z.V.S.B.V.K.F.S.V.V.S.K.C.H.T.b.V.m.S.C.L.V.H.S.S.K.V.V.B.T.n.v.l.v.B.C.S.S.K.K.C.K.Y.b.T.n.n.m.m.b.b.n.b.c.4.l.n.w.b...{ u.u.E E R 4.r.4.5.4.4.O.5.7.=.v.f.l.7.8.7.p.p.i.i.8.i.p.i.7.*.=.=.=...q.H.&.7.i.p.t.<.>.#.1.2.#.t.-.0.7.7.8.-.-.i.p.s.9.&.5.4.5.7.*.&.=.5.O...&.8.9.i.8.i.p.a.&.*.7.i.i.p.5.2...*.8.i.i.y.6.5.4.l.p.y.p.p.y.p.p.i.6.@.8.c.q.q.q.`.7X_.7X. !.i.9.i.i.i.8.7.i.y.-.VXVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ 1XqXuXnXkXzXnXnXxXxXuX0X0XkXrX. . SXz.j.z.k.k.G.J.k.k.d.f.v.G.x.h.x.k.J.c.v.J.G.j.L N.$.J.U.C.C.K.K.C.K.K.L.U.L.C.B.C.C.d.h.N.x.k.j.x.j.k.k.k.k.k.k.z.z.x.$.<.J ,.,.<.$.<.<.<.<.<.<.^.j.z.k.b.V.B.Y.C.S.H.Y.C.K.K.K.V.V.U.w.n.m.S.K.S.S.S.S.K.C.S.g.d.d.d.l.Y.L.B.S.C.C.C.H.C.K.V.B.S.m.n.n.b.n.n.v.c.g.b.w.m.S.c.:.r.t.u.t.4.4.r.%.O.t.E &.&.l.l.d.v.v.*.7.u.*.i.y.i.8.i.i.&.&.q.9.i.i.&...&.=.8.a.5.J #.#.<.$.<.<.5.i.7.=.&.8.p.0.p.p.0.i.=.9.7.4.5.+.*.&.5.t.l.l.T i.i.8.i.7.3.*.*.7.8.i.8.} l.T 4...&.6.i.3.E ..&.p.p.p.p.p.p.p.p.9.i.c.q.i.p.i.W.CX+XSXA.i.7.i.i.8.=.8.8.i.6.-.7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmX_ yXzXkXxXuXnXnXnXnXxXpX0XkXiXCX) f.z.f.f.k.j.d.J.z.z.f.f.v.G.x.x.h.x.j.J.I.d.N.N.$.N.N.g.L.C.V.B.C.C.C.C.K.L.K.V.V.V.V.b.c.k.x.k.k.x.k.k.k.k.k.z.z.k.z.l.d.k.x.h.h.N.$.#.N.h.N.h.j.j.d.J.d.T.V.m.S.Y.K.C.C.K.K.L.C.T.C.Y.v.v.n.T.V.S.Y.B.S.K.S.c.d.d.l.l.n.K.C.m.m.C.K.H.H.C.K.S.V.S.C.n.c.b.n.c.v.v.c.n.n.g.e.n.r.u.t.t.u.4.4.2.2.5.=.s.p.0.9.t.u.f.v...n.c.O.*.*.*.8.p.i.7.=.s.0.8.8.=.*.&.8.i.a.1.,.$.<.<.$.#.$.u.y.8.8.7.7.i.y.p.p.p.i.=.i.9.5.t.&.3.3.u.t.*.&.*.=.8.i.7.t.r.5.&.7.8.8.7.r.H.q.4.5.3.i.i.=.=.9.9.q.i.8.6.8.6.s.s.p.9.p.9.i.i.6.B.BX' T +.8.i.i.i.i.i.i.9.0.@.H.7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ % xXzXxXuXnXnXnXxXzX0X4XS v.G.d.f.v.J.j.z.f.f.J.d.l.f.f.j.x.h.h.h.x.x.k.j.N.$.j.T.Y.K.H.V.T.g.n.K.C.H.K.K.K.Q K.V.V.H.K.H.j.h.x.j.j.k.j.k.k.k.z.d.d.z.z.d.l.d.z.z.j.x.j.G.d.d.l.v.v.c.c.w.B.m.T.L.L.S.C.K.U.K.l.l.l.d.v.c.n.n.T.m.B.B.S.K.B.l.d.l.f.v.T.H.B.V.m.m.H.C.H.C.C.H.S.S.C.T.v.n.n.c.c.v.l.c.b.n.n.v.e.).w.:.5.5.O.v.w.&.8.W s.7.=.%...9.q.=.v.u.4.*.5...7.i.i.8.i.i.8.8.8.8.7.&.8.p.*.1.2.>.<.<.<.#.#.1.7.p.8.8.7.i.p.i.p.p.i.i.i.i.5.t.*.3.5.t.r.&.7.&.*.7.i.7.4.4.5.5.&.7.8.5...q.R E *.o.-.i.9.i.q.q.q.q.&.*.7.8.s.s.i.a.p.a.9.p.a.=.v.&.7.i.i.p.p.i.p.p.p.i.i.0.SX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ Z xXnXfXxXnXnXnXxXuX4X{.E f.f.J.z.d.z.J.f.v.c.v.J.f.f.k.k.x.N.N.h.x.j.j.h.J.K.P.F.Y.B.V.m.f.w.C.S.H.C.K.K.L.K.C.C.C.C.L.v.<.N.N.$.h.x.h.h.j.k.z.d.d.z.d.z.z.z.k.j.h.x.k.d.V.m.J.v.n.v.I.m.T.S.U.w.T.B.Y.L.c.d.c.n.n.c.v.c.c.B.B.w.V.C.K.T.c.z.d.l.d.d.S.S.S.B.b.m.C.C.H.C.S.S.K.K.l.z.f.v.c.g.n.n.n.b.b.c.n.AX. w.:.3.3.5.=.9.7.3.B.S.3.*.=.9.a.p.i.l.2.r.....*.&.7.8.&.7.i.i.8.8.8.i.&.7.*.#.2.#.<.<.>.#.#.1.F 2.8.y.i.8.8.p.p.i.i.i.9.p.8.%.%.5.5.5...E 5.7.=.*.7.8.&.%.4.5.u.&.7.7.r.u.u.E 5.8.s.0.p.i.=.&.7.8.i.=.c.9.9.9.i.p.a.a.a.p.i.a.&.O.&.8.i.p.a.p.a.p.p.p.y.8.^ +X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XCXFXvXzXnXnXnXnXxXxX0X4XnX*.J.J.J.f.g.f.f.g.n.c.f.f.f.j.j.k.j.h.h.h.j.h.z.L.F.C.S.S.H.H.n.f.B.S.B.S.C.K.K.C.K.K.K.K.K.C.w.g.I.v.f.n.I.g.j.N.x.j.k.z.k.d.z.k.k.k.j.x.j.z.n.C.C.n.n.T.f.l.T.m.Y.P.C.b.b.n.n.n.S.C.C.V.c.v.c.n.S.V.m.S.K.Y.T.J.j.k.z.h.j.S.K.C.C.Y.S.S.C.C.C.C.K.K.n.k.d.l.v.v.c.n.n.b.w.w.q.n.#X. Y.:.3...*.3.7.8.&.&.0.*.=.9.q.8.p.a.&.1.2.*.5.u.*.7.*...*.8.i.i.i.8.i.8.&.3.1.<.<.<.$.$.$.2.#.%.$.<.t.&.i.7.i.p.i.p.p.p.a...%.E E E *.&.=.&.7.7.3.5.8.7.t.%.t...&.7.7.t.%.u.E u.8.8.y.p.i.8.q.*.=.n.9.0.9.i.0.0.p.a.p.p.i.i.i.3.+.=.8.8.7.8.7.&.p.a.p.6.C.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAXCXhXkXxXMXnXnXnXnXfX0X4X[.d.J.g.g.g.c.c.g.c.v.f.v.v.x.h.j.k.G.x.h.h.h.g.L.S.V.S.C.Y.V.c.I.K.K.V.H.C.K.K.K.L.L.L.L.K.B.T.K.S.S.S.V.B.V.V.d.h.N.N.x.x.j.z.z.k.k.x.j.j.f.B.b.g.T.T.V.n.z.I.S.V.S.C.B.w.n.c.K.L.H.S.T.T.B.m.b.w.m.V.H.K.b.z.z.j.X.X.x.c.n.b.S.S.H.S.S.C.K.C.K.K.v.M.d.l.l.v.c.c.c.n.c.n.w.b.d.e.. +Xl.| 3.3.*.&.8.7.5.q.=.*.n.9.8.i.a.8.1.1.u.5.u...5.t.5.*.7.i.p.p.8.8.8.&.7.r.<.#.<.$.$.J #.1.2.X.$.1.2.5.6.i.y.p.p.a.7.4.2.R E E E *.*.&.=.7.7.3.4.*.i.3.2.t.*.&.7.8.t.%.u.u.t.7.8.i.i.8.3.=.=.9.9.9.i.9.0.a.a.8.&.7.7.=.=.=.=.7.7.&.5.4.5.t.4.8.a.6.i.@X+X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XD D gXzXzXMXnXnXnXxXuX0XfX=.v.c.c.v.g.v.v.g.c.v.g.l.x.x.h.x.j.z.G.j.h.c.C.B.S.K.H.V.S.S.C.L.C.H.C.C.K.K.K.K.K.K.K.S.w.B.S.V.V.S.V.T.b.V.n.h.N.h.k.x.h.j.z.x.N.h.N.<.N.m.H.V.V.S.H.J.j.n.B.V.B.m.V.B.T.V.C.C.S.b.B.C.Y.S.T.b.V.H.L.V.x.#.k.j.h.X.d.f.d.z.k.l.v.T.S.S.C.C.C.c.M.d.d.d.l.v.c.q.n.n.c.c.b.b.c.z.e.. B.:.*.&.&.3.5.5.O.b.B.3.9.T 8.7.y.i.M.2.4.u.4.E 4.4...*.&.8.&.8.y.8.8.7.7.4.>.#.#.<.,.,.,.$.#.F %.t.%.%.3.7.8.i.p.*.>.#.2.%.4.4.u.*.&.&.&.&.7.3.u...*.u.r.u.*.5.5.4.r.u...4.5.&.&.8.i.8.5.&.9.i.8.p.p.p.a.a.p.&...*.&.&.&.&.=.T 8.*.%.Y *.*.=.u.U 3.t.A CXVX#XVXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XCXO zXfXnXMXnXnXnXfX0X4X.#.1.<.,.<.#.2.X.2.d.4.u.t.5.3.7.8.1.$.2.2.r.r.4.t.*.&.*.&.*.&.*.......u.%.u.3.5.2.#.r.u...t.5.&.&.8.i.7.*.8.-.i.8.i.9.a.a.p.=.*.*.*.=.=.8.8.=.=.=.q.=.E ..=.8.5.2.t.:.O.OX. +XVX@XBXVXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX_ gXkXxXnXMXnXnXxXxX0X4Xw.T f.J.l.c.v.f.f.c.f.j.N.N.h.x.j.x.x.z.z.v.U.K.V.V.K.K.S.S.S.C.C.C.C.C.K.m.c.n.B.S.H.Y.S.v.l.n.B.S.B.n.v.g.b.z.x.j.z.d.k.k.x.h.j.x.$.$.x.h.k.z.k.h.h.j.T.T.g.n.V.K.L.K.K.C.S.C.S.S.H.V.S.S.S.S.B.B.B.k.j.G.k.h.X.j.z.f.d.d.k.d.v.v.c.n.n.b.S.f.X.k.z.z.d.d.l.v.c.n.c.v.v.n.n.l.M.I.DXt.-.s.5.5.u.*.*.t...8.8.s.i.8.&.3.3.8.&.2.%.2.%.4.*.=.7.p.5.<.O.3.&.3.3.u.#.1.r.4.4.#.<.1.R %.>.1.t.t.t.5.*.8.M.F X.2.2.r.%.4.5.*.*.&.&.5...*.*...*.u.4.3...*.4.2.4.u.5.5...*.&.=.7.8.&.8.p.9.8.8.9.7.*.5.......*.&.=.8.8.8.i.7.7.9.=.*.7.8.5.E *.*.5.*.GX!.b.B./ 7X+XmX_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ O zXzXxXnXnXnXzXxXfX0XsXT c.v.J.f.v.c.f.f.g.b.k.h.x.x.k.d.J.d.d.e.L.Y.S.H.C.Y.H.C.C.C.K.K.H.m.n.g.m.S.K.K.C.C.K.C.n.v.T.V.b.c.c.n.T.l.j.k.Y j.k.z.j.k.z.k.j.j.j.z.d.f.z.k.z.c.K.m.n.T.T.T.T.S.S.B.Y.C.S.S.C.C.H.H.S.S.S.B.B.g.j.j.j.x.h.X.h.l.z.z.d.d.v.v.c.n.T.S.n.x.j.M.k.z.z.d.d.l.v.v.c.v.j.d.n.v.Y w.CXc.t.&.*...5.3.&.t.u.=.8.0.8.8.7.7.7.i.8.2.r.4.4...&.7.y.8.<.$.4.4.5.3.5.2.1.2.4.Y %.4.r.2.%.M.u.3.&.5.4.3.&.u.F F X.X.1.2.r.%.t.3.3.&.&.5.E ..*...5.t.t.5.3.5.2.1.4.5.3.*...5.5.&.=.8.&.7.i.8.8.=.i.5.2.4.u.u.u.....&.8.8.7.7.0.8.i.q.5.7.+.u.5.&.&.&.*.&.v.&.8.=.m.` 7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X).D gXzXfXzXvXnXnXzXxXuX0X.J $.F #.1.t.4.r.4.R R M.5.7.7.*.4.*.i.8.7.8.u.1.X.F X.%.X.#.2.t.4.R u.3.*.&.5.4.5.5...5.u.r.4.u.t.u.r.r.4.5.*.M.M.5.5.7.7.8.&.&.i.7.8.8.=.=.*...*.d.u.4.t.3.y.p.@.=.s.=.i.8.5.5.4.4.*.7.7.7.=.=.=.=.=.6.+.9.VXVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ jXzXfXsXnXnXxXnXxXxX0XqXg.c.g.g.f.z.l.v.v.f.f.d.z.z.z.l.V.U.L.V.H.S.K.S.T.S.K.K.K.C.m.b.B.b.n.m.H.C.K.L.K.K.K.H.S.S.b.c.l.l.c.T.n.d.k.k.b.S.H.C.L.n.x.z.%.m.L.f.n.T.V.V.n.g.b.T.V.V.T.g.v.v.v.z.n.H.T.n.n.B.Y.K.K.C.S.H.S.S.c.x.M.k.v.f.k.j.l.v.T.H.V.S.S.c.M.z.z.j.h.x.x.k.d.l.l.l.l.f.z.k.M.k.f.g.l.{ /._.O.9.=.u.*.t.*.9.0.p.7.8.i.i.8.i.i.y.7.2.2.4.3.&.7.&.O.4.r.#.2.4.4.4.2.2.t.%.%.*.8.=.=.*...7.=.7.7.9.i.1.2.%.h.X.1.#.1.2.2.r.4.4.u.t.2.r.3.*.7.7.*.u.%.4.4.4.4.r.%.t.t.c.v.O.u.&.7.8.7.5.7.8.=.7.8.=.*...=.8.8...t.*.8.8.3.s.-.3.9.i.t.r.r.t.+.7.7.8.=.7.T =.&.i.y.7.P +X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X).D gXzXfXxXkXnXnXnXxXuX0XxX=.l.g.g.f.d.d.l.l.z.d.z.z.j.I.D.L.S.V.K.K.K.C.B.V.K.K.b.c.v.z.c.b.V.L.C.C.K.L.L.K.C.C.S.B.T.m.c.k.v.B.d.z.v.B.Y.V.V.S.S.C.f.X.v.P.I.c.B.K.K.Y.S.T.g.n.T.m.n.I.f.J.c.l.k.n.T.c.c.b.S.V.v.S.C.S.B.T.n.k.v.S.n.v.B.V.n.b.F.S.m.m.H.V.z.v.n.v.j.j.k.j.h.x.x.j.z.z.d.z.k.z.c.l.} } GXCX=.:.4.3.&.*.i.s.p.8.8.8.8.3...7.*...5.r.2.#.2.*.5.4.0.a.p.7.&.&.7.&.r.2.3.R R *.&.&.7.&.*.&.=.&.&.8.7.u.5.3.r.#.#.X.F <.#.1.X.#.>.1.r.R 8.p.6.8.*.5...5.4.r.4.2.2.4...s.&.r.t.*.7.&.3.5.4.8.i.8.8.7.5.9.s.7.9.i.u.u.5.5.5.9.q.&.p.8.1.1.r.r.%.*.@.7.8.i.T q.=.7.i.+.B.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X_ hXzXsXsXnXkXnXxXnXxX0X4Xg.&.v.n.c.J.d.d.d.z.z.z.z.k.I.F.K.S.V.K.L.K.L.S.T.V.V.B.T.B.c.c.B.K.L.H.H.Y.H.Y.S.S.V.V.B.m.T.b.f.l.I.f.d.b.b.n.T.B.m.T.B.n.k.V.V.c.n.V.S.V.B.B.V.n.T.Y.V.I.c.J.d.c.v.z.d.c.n.b.m.H.V.h.l.b.b.b.b.n.v.c.P.V.n.V.B.b.T.C.V.B.m.V.S.n.c.b.T.c.k.j.N.N.N.h.h.j.X.x.k.d.c.l.r.{ d.7X. V.} 5.u.3.8.6.0.K.s.8.i.=.&.&.&.5.t.t.4.$.<.$.2.2.7.[ s.i.i.8.=.&...t.&.p.p.8.&.&.&.=.=.7.7.7.7.&.=.*.u.3.&.*.2.1.1.<.$.%.%.R #.#.4.u.E r.q.9.3.3.3.*.u.u.u.%.1.M.l...5.t.r.u.*.&.*.r.t.r.3.8.6.i.7.3.8.q.7.i.8...O.4.r.t.&.&.=.p.*.#.r.2.$.>.2.5.&.8.i.i.i.q.8.8.@.9.#XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ aXzXfXsXsXzXlXnXxXxXfX0XfXq.q.g.v.f.z.z.d.z.G.d.z.k.B.L.C.C.K.C.K.K.L.L.V.b.m.S.S.B.n.n.V.L.C.C.S.S.S.S.B.B.m.w.m.m.b.T.n.n.T.e.b.b.I.b.n.n.n.n.b.g.e.U.v.Y.V.B.S.m.T.T.T.V.S.S.Y.K.T.z.z.v.c.l.z.d.b.B.B.Y.H.l.d.v.v.g.b.c.c.g.n.b.m.T.m.B.T.m.S.V.B.m.B.B.g.c.n.T.n.X.h.N.N.h.X.k.X.h.d.v.v.c.%.,.g.. . C.:.5.u.4.&.p.8.0.s.i.i.8.8.8.=.7.*.5.5.1.#.4.1.r.i.y.i.i.i.i.8.&...q.8.s.9.8.8.7.=.8.8.i.8.=.8.7.=.=.&.*.*.7.E H #.<.$.1.2.%.1.4.t.E E 2.h.M.2.%.u.r.1.1.%.%.2...z.u...M.r.4...&.5.2.%.t.5.5.&.8.8.7.&.7.8.i.&...=.&.u...&.&.=.i.4.2.4.r.2.2.r.1.r.*.7.i.i.9.9.9.8.7.W.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X).#XgXzXsXsXxXlXkXxXnXxXzX0XxX9.v.g.n.d.z.d.d.d.f.z.c.C.C.K.K.S.S.C.K.L.L.K.T.m.V.S.V.b.T.m.C.L.C.S.S.C.H.S.S.B.m.m.m.m.b.b.V.S.B.Y.C.S.S.B.n.c.n.g.c.P.Y.n.C.S.V.V.I.b.w.T.T.B.V.V.K.L.c.z.z.c.l.M.l.n.B.V.C.Y.k.X.z.z.l.c.J.n.T.b.T.m.m.m.B.V.H.S.S.V.B.m.m.m.b.n.b.B.l.h.h.N.N.h.k.h.j.v.l.l.f.M.z.(.. . V.:.t.t.t.&.p.a.p.i.p.i.i.8.8.7.=.&.*.*.3.%.2.r.4.8.p.i.i.p.i.=.*...v.7.8.8.8.8.8.8.8.i.7.3.8.8.=.&.&.=.&.*.&.q.E <.<.1.1.X.1.2.t.u.E 4.u.4.2.1.#.1.$.#.2.2.#.1.z.Y *.*...t.2.M...5.1.2.u.t.u.*.5.&.&.7.8.8.8.5.q.0.8.=...*.*.T 8.1.1.4.u.5.3...*.E 4.7.i.i.p.9.s.8.8.`.7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX_ jXuXfXsXwXfXkXkXnXxXxXuX0X.2.2.2.2.2.M.%.4.r.u.l.l.u.u.5.u.2.1.t.4.4.u.%.4.&.&.&.*.*.7.7.8.8.7.8.=.8.p.4.#.$.1.&.8.7.&.&.*...*.i.s.-.i.0.9.B.VXVX_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX_ dXzXsXsX4XeX_ nXzXnXzXpX0X.2.X.<.>.4.8.8.7.=.7.&.t.7.p.i.p.i.8.C.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X).D sXzXfXsX4X2XCXgXkXnXuXpX0X.#.4.&.8.&.&.&.*.7.&.3.E 4.E *.&.&.&.8.=.*.5.R u...*...*.E 4.%.4.1.<.2.u.r.#.1.R r.2.<.$.#.2.2.1.u.5.3.u.u.*.*.*.u.2.#.2.2.%.%.F #.2.u.E ..*.5.*.i.7.8.i.E >.%.#.>.#.1.r.7.6.7.7.7.*.7.p.p.p.p.i.b.` 7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ jXzXfXsXqX4X_ O lXxXnXfXpXsXG n.n.n.n.m.B.d.I.K.K.H.V.B.S.V.V.V.n.n.V.S.V.c.b.L.C.C.K.L.C.m.n.n.n.c.v.l.n.n.b.T.w.m.B.B.n.c.c.v.v.d.l.f.c.b.m.m.T.n.v.J.v.n.T.T.e.B.V.V.S.C.H.V.n.n.T.g.g.J.j.j.d.v.C.L.K.C.C.H.S.B.B.m.m.V.V.m.w.b.b.B.m.b.c.v.c.b.m.B.m.c.d.l.c.c.v.f.d.d.l.f.z.k.v.v.f.v.R O.=.Y.+X. W.&.7.8.i.8.8.&.&.....E E E E R 4.t.*.5.4.#.,.<.>.u.&.*...&.&.3.t.4.t.3.=.=.&.*.&.&.&.*.E t.t.3.&.7.*.t.t.......E E ..*.5.r.4.%.1.<.<.#.1.1.2.%.r.r.$.#.#.%.#.1.%.X.u.*.R E ..*.*.5.1.r.2.#.X.2.M.T ..E *.=.t.O.8.8.7.8.5.$.X.1.<.$.1.r.r.*.i.8.7.7.8.6.i.p.i.p.T !.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X).).gXzXsXqX4XeX_ gXkXxXzXpXfXs.c.g.c.g.b.S.S.V.S.V.V.Y.K.L.K.S.Y.Y.B.V.T.l.z.C.P.H.K.S.V.m.b.T.m.B.T.n.l.f.n.c.c.b.V.m.B.V.b.n.c.v.f.d.d.l.c.T.B.w.c.c.g.b.b.I.n.V.C.C.S.H.S.B.B.m.m.b.b.T.T.z.z.l.f.T.K.L.L.K.C.C.K.K.K.C.S.V.B.m.m.m.m.w.b.n.c.n.c.v.c.n.c.v.v.c.c.f.d.l.f.v.l.k.z.v.v.c.z.2.2.l.I.Y.Z.&.+.8.8.8.p...1.r.4.........t.4.5.u.4.u.t.1.$.<.$.u.*...&.=.*.&.&.3.u.t.5.&.&.&.&.*.*.=...2.%.4.*...O.....E E ....E ....4.1.1.R %.#.<.,.$.r.4.r.r.2.h.>.#.%.M.E *.l.=.&.5.4.E *.&.5.2.4.4.#.#.2.l.q.E 5.7.7.r.%.3.7.8.7.2.>.2.X.<.<.2.O.l.4.O.7.8.8.7.8.i.i.i.6.s.mXVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ hXpXfXsXqX0X}.DXlXxXzXuX0XgXT c.c.m.K.K.K.V.S.S.S.C.K.K.L.C.K.L.Y.H.T.z.c.K.C.C.H.m.m.B.B.V.S.S.B.T.d.l.n.n.c.w.B.B.m.m.m.b.b.n.c.c.l.f.c.b.T.g.v.c.v.n.V.b.I.H.H.S.K.C.S.V.m.m.B.m.T.b.T.B.c.J.c.c.g.v.n.n.n.V.I.g.g.g.b.B.V.S.V.B.m.b.b.b.b.v.z.z.c.v.d.c.n.c.c.v.v.v.c.v.k.x.f.c.c.M.#.%.%.t.I I O.3.8.=.&.&.*.4.1.1.%.%.E *...t.*...2.2.u.r.#.X.F 1.4.&.*.E ..&.*.*...E ..E E ..5.t.t.*.&.2.2.t.u.*.1.1.*.3.4...*.R 4.%.2.2.#.1.%.X.1.#.$.<.2.r.1.X.j.#.<.%.d.E t.5.3.*.*.&.5.R R E u.r.4.u.r.1.2.R u...&.&.4.2.R t.5.&.u.1.#.2.X.$.#.t.5.9.E 1.5.6.8.8.6.y.p.p.i.Z.7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XD $XgXfXsXsX0XeXD zXnXxXzXpXeXC l.b.L.F.C.S.T.B.Y.S.S.H.C.L.L.L.g.v.V.B.n.S.L.H.C.V.m.B.S.S.C.S.S.H.T.z.l.c.q.q.b.V.V.B.m.m.b.b.T.n.n.n.v.c.b.n.v.v.c.c.n.S.S.B.Y.S.S.C.C.C.S.B.V.V.B.w.b.b.m.T.c.g.n.b.v.z.v.g.T.v.k.k.M.f.n.n.T.B.B.T.n.n.b.n.z.d.v.n.k.N.k.g.n.c.n.d.k.k.j.x.k.v.v.M.1.%.E %.r.u.U t.&.&.*.&.*.5.t.4.%.H %.X.u.5.5...R X.%.u.2.$.F $.<.<.%.*.&.&.*.*.*.....E E E E 4.%.t.=.E 1.4.4.*.M.<.$.2.E E *.4.$.>.>.#.2.2.%.R 2.1.#.#.#.#.1.x.X.h.$.1.E 4.O.t.5.3.*.&.&.=.*...4.4.$.<.2.t.2.r.u.5.&.5.>.$.X.2.4.t.t.1.1.%.%.1.#.2.4.t.....E ..&.&.@.8.0.p.i.S.CX@X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ hXpXsXsXqX0XtXdXkXxXzXpXqX7XC.w.K.C.K.S.w.B.Y.C.V.S.C.K.U.V.h.d.n.b.S.C.F.K.S.V.B.B.S.H.H.H.K.C.v...l.v.g.n.b.m.S.B.m.w.b.b.n.n.c.v.v.g.T.g.v.c.g.c.c.T.g.n.L.K.C.C.K.K.K.C.S.S.B.m.w.b.b.T.n.n.I.T.T.v.n.b.n.b.b.b.b.b.n.v.v.g.n.n.g.n.T.g.d.v.v.c.z.h.h.x.d.f.d.j.k.X.x.k.z.z.2.2.r.%.R R 1.r.5.4.5.7.=.&.&.&...E R R %.%.r.2.4.M.%.2.%.4.$.J ,.,.<.,.J #.4.%.*.&...u.u.u.R u.t.t.5.&.&...4.4...u.#.$.$.<.$.X.1.$.>.<.#.4.r.%.R 4.1.1.1.1.1.>.#.1.<.$.>.2.r.r.u.5.3.*.7.&.&.&.7.7.*...u.<.J r.u.4.4.5.5.#.,.>.F #.1.r.2.2.2.2.Y M.1.1.2.2.1.4.....3.*.7.9.=.....&.Z.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ 1XxXzXsXsX3X0XxXnXnXzXpXpXeX. S.S.C.K.S.b.b.B.V.V.V.H.K.Y.b.j.k.c.T.S.C.C.H.H.S.V.S.H.Y.S.K.K.c.z.l.l.l.v.c.c.n.m.B.m.m.m.f.k.z.z.k.J.b.T.n.v.n.n.c.c.v.k.c.C.L.U.L.L.K.L.K.C.H.S.B.w.b.n.b.T.g.T.T.f.l.n.n.n.n.b.w.b.n.n.q.v.v.c.c.c.n.b.n.l.d.f.c.d.k.j.h.N.x.j.z.k.j.j.z.k.{ 2.2.1.1.2.%.Y 1.O.5.d.u.&.8.7.&.*.E u.R 4.u.%.F %.R %.%.4.2.<.#.#.1.1.r.2.#.,.J >.%.5.5.t.4.t.t.u.5.....2.4.4.R M.1.#.$.1.<.<.1.$.>.$.$.2.4.4.%.r.r.r.2.1.1.#.$.1.#.>.$.1.r.2.2.u...*.&.8.=.7.&.&.&.&.*.&.t.#.$.r.r.r.%.1.<.1.&.8.&.4.4.4.4.4.r.R H 2.2.r.%.1.1.4.t.5.*.=.=.E E ..5.3.!.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ iXzXfXsXsX4X4XzXnXnXuXpXqX^ H.C.K.L.S.g.c.c.b.V.S.S.K.B.n.g.l.f.b.V.H.C.C.H.H.S.H.C.C.K.K.l.%.z.d.l.v.l.v.c.v.c.T.m.c.z.k.k.z.d.z.k.l.g.n.n.n.g.c.c.c.f.d.T.B.m.T.m.S.S.B.V.V.T.b.n.n.n.T.b.c.c.J.h.k.n.c.q.c.q.n.n.n.q.c.c.c.c.c.n.b.b.c.v.v.c.k.x.k.j.j.x.X.k.z.z.k.j.z.j.2.r.2.1.1.2.R E %.r.*.z.1.R ..*.*...E R 4.4.%.X.M.R R R u.R 4.t.5.t.t.u.t.5.t.2.#.,.,.1.2.%.4.r.2.r.%.F #.1.1.$.X.1.#.#.>.<.,.,.<.$.$.<.1.u.%.2.>.#.2.2.2.2.1.$.<.1.2.#.#.1.1.#.#.%.E U 3.5.*.K &.*.&.&.*.*...3.2.$.#.1.%.X.%.i.[ s.i.i.=...t.r.2.%.X.4.*.*.5.4.4.5.5.5.&.T d.E ..*.&.| S.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmXD D gXuXxXsXsX4XqXnXxXzXpXxXo.C.K.U.L.n.d.l.n.n.B.B.V.C.S.v.T.V.b.T.V.B.C.K.H.C.S.H.C.K.L.S.M.X.z.k.d.d.z.d.c.n.c.v.d.x.h.M.d.l.l.d.k.k.z.J.v.n.n.c.v.c.c.k.d.f.d.c.n.c.v.l.v.f.d.l.v.c.n.b.n.v.z.$.N.x.c.b.n.n.n.n.n.q.g.c.c.n.n.b.b.c.z.z.z.z.h.N.x.k.j.M.X.h.k.z.z.k.k.z.j.2.2.1.#.2.2.%.E r.%.2.2.R 4.2.2.r.*.5.t.4.t.2.X.R R R R u.t.5.5.4.4.4.R E 4.4.t.5.4.$.,.<.2.2.1.2.2.1.#.1.2.1.#.X.1.#.<.$.d.z.#.,.<.<.$.%.u.M.<.<.<.$.<.<.$.$.<.<.$.2.2.#.2.2.1.1.#.1.%.X.4.#.r.7.7.=.&.*.*.....u.#.#.2.%.X.7.y.i.9.8.i.8...t.R 4.O.R *.*.....E E *.&.*.d.Y E ..*.&.7.+.C.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ iXzXfXsXsX3X4XqXnXzXuXS.[ K.L.F.w.M.v.K.B.c.T.m.S.K.S.k.z.T.Y.K.K.S.K.L.L.K.C.C.K.C.B.B.l.M.Y z.Y k.k.z.l.l.k.j.Y z.j.j.d.d.d.d.z.j.k.k.j.d.n.n.c.c.c.z.z.l.f.g.b.n.c.c.c.c.l.l.v.g.n.n.c.g.z.$.N.X.l.g.n.n.c.b.b.c.n.n.n.c.f.l.k.j.x.j.x.h.N.N.X.j.k.x.x.X.x.d.z.j.k.k.%.r.2.#.$.2.r.R Y %.2.2.R E R %.1.2.&.8.7.7.*.t.%.%.Y R u.E *.*.5.R Y E E R 4.%.R 4.5.4.,.<.4.4.2.%.%.2.2.4.r.2.X.%.N.<.,.A CXVX$Xe.$.#.r.4.t.4.<.<.<.<.<.<.$.$.$.#.$.$.#.1.R %.%.R X.1.4.R 4.O.1.t.7.7.=.&.....E 4.#.1.2.2.1.&.i.i.8.8.8.8.*.u.R =.o.i.8.=.*.E u.E ..*.=.d.4.....&.7.7.U L.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX_ jXuXsXsXqX4X4XfXzX;X[ C.K.F.L.l.d.F.! S.n.m.m.S.L.S.v.d.l.B.L.w.z.b.V.H.C.L.K.C.V.B.V.f.k.Y z.z.z.d.d.k.j.j.j.z.k.k.j.d.l.l.d.k.k.M.j.j.k.f.g.c.v.I.J.z.c.c.c.b.b.n.c.c.c.c.c.c.n.T.c.v.n.z.N.X.k.M.j.k.j.x.z.c.n.c.z.k.k.X.j.x.h.X.j.j.k.j.h.h.j.Y h.X.x.x.d.z.j.k.k.%.2.#.1.4.%.%.%.4.4.2.R E R %.1.1.#...8.=.&.&.&.5.%.%.Y E ....E *.R %.R %.r.1.X.4.u.E 5.$.<.t.R 4.R 4.2.4.4.R %.%.F #.J f.. @X@X. AX2.} u.r.4.#.<.$.<.<.<.$.X.$.$.#.$.,.1.4.4.R E R 4.4.R ..E u.5.r.4.5.....*.*...t.1.1.4.r.1.*.y.i.i.8.8.i.&.u.%.&.s.i.7.=.=.&.5.u.&.*.*.&...*...&.+.3.B.VXVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.mX_ ).xXzXsXsX3X4X4XxXH.S.B.K.L.H.v.U.L.n.m.b.m.B.H.L.S.T.n.z.l.H.d.h.X.h.j.f.V.S.m.T.m.m.d.Y z.d.z.d.d.z.z.k.z.k.j.j.j.z.z.l.f.d.k.k.j.k.k.j.z.v.f.c.g.z.v.b.T.B.B.b.n.n.n.n.n.n.c.T.g.d.f.g.z.X.k.x.x.h.$.<.$.X.x.j.h.$.j.k.j.z.k.j.j.j.k.k.j.x.h.j.k.X.h.X.j.z.k.j.k.z.%.1.r.t.t.%.2.r.2.%.%.E E R 1.#.#.1.M.&.7.*.&.*.5.5.u.u.E E u.4.R 4.2.1.2.2.2.2.2.%.E 4.2.#.4.E E E u.4.4.%.R %.1.#.,.h.BX+X#X#X+X' 1.r.4.4.1.<.<.$.#.#.#.1.$.<.$.$.<.1.5.t.E E R 4.R u.E u.R R R R R r.2.2.%.R E u.2.2.2.2.2.%.8.y.y.8.8.8.&.u.4...7.8.=.7.7.=.=.3.&.&.&.=.c.=.*.=.q.C.VX7X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ iXuXfXsXsX3X4XgXo.V.K.B.b.n.B.L.m.c.c.w.m.B.Y.K.C.B.T.l.M.z.j.x.h.<.h.l.l.b.b.b.S.b.j.z.z.z.z.z.d.d.d.l.d.z.k.j.x.X.h.j.d.l.z.k.k.k.j.k.j.k.J.c.f.f.c.b.V.V.c.l.l.z.l.n.b.b.T.n.d.d.v.c.j.h.N.$.h.x.h.h.N.$.$.$.N.N.k.z.k.z.l.d.j.j.k.k.k.j.x.k.Y x.1.h.X.X.X.j.J.z.O.4.t.t.u.%.r.R r.r.R R R %.1.1.%.H %.%.u.5.3.5.5.*.&.5.2.2.r.4.4.%.1.2.X.1.#.#.1.1.%.%.2.2.4.5...E ..R O.4.R X.1.,.1.`.. _.#X#XBXDXM.%.X.2.1.<.$.#.F X.1.$.$.#.$.<.$.4.t.E E u.R E 2.2.4.4.O.2.2.4.E E X.1.1.r.E E *.5.$.#.2.F %.%.5.8.i.8.=.*.u...=.8.7.7.&.=.=.&.&.&.&.&.l.q.P ` VX+XVX_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX_ hXzXsXsXsX4XwXo.S.K.B.w.C.L.V.m.b.V.w.m.S.C.K.K.S.V.f.j.h.j.M.j.$.x.l.d.v.T.m.b.d.X.j.M.k.l.l.k.z.d.d.d.Y z.z.d.d.j.h.x.k.d.z.k.j.j.k.k.k.z.v.c.c.c.n.n.v.d.d.z.z.z.d.v.f.l.l.l.l.v.f.$.N.j.z.v.n.n.n.v.z.N.<.$.$.z.l.z.d.l.d.k.z.d.d.z.j.x.z.j.$.z.A.A S (.A r.4.4.4.4.4.4.%.r.R R 2.4.4.2.%.2.%.Y %.2.1.1.r.%.4.*.&.5.r.%.%.%.X.#.#.F h.N.$.#.1.r.1.#.X.2.X.4.*.....E 4.4.E R %.>.>.`.. #X#X#X#X#X. f.>.#.1.X.2.#.1.1.#.>.#.#.$.>.1.X.R E E E u.E r.2.2.1.1.2.r.r.2.%.E E R R ....u.z.u.4.4.2.%.1.#.#.#.5.8.8.&.E u.*.7.7.7.8.8.*.3.+.*.&.&.*.b.+X+X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ D sXfXsXwX4XwXH.-.m.m.S.K.H.K.S.m.K.b.m.H.C.C.K.H.S.v.h.j.k.z.j.j.c.c.c.c.V.V.k.k.v.l.z.z.d.z.j.x.z.d.z.j.v.b.b.b.n.k.h.h.x.z.k.k.k.j.k.k.z.d.c.v.c.c.v.v.n.c.d.d.d.l.z.j.k.l.l.d.l.d.x.d.c.c.n.b.n.n.b.b.g.d.$.$.l.f.l.l.l.d.d.d.d.d.z.j.j.z.h.$.) . +X+X. CXc.{ 2.%.r.r.r.X.2.r.R %.r.4.X.%.X.%.2.1.2.1.2.r.r.u.&.*.r.%.R R %.1.#.#.#.$.>.1.1.r.%.X.#.1.X.%.u.*.*...*.u.4.E u.>.>.' . #X#X#X#X_.. VXX.>.>.1.Y H 2.1.#.>.$.$.<.$.%.H #.r.&.E E E R 4.R r.1.#.#.$.%.4.%.R E E E 4.1.1.#.#.1.%.r.R R 4.r.>.>.4.*.7.&.5.5.3.3.&.T *.t.5.*.*.&.&.&.=.!.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ jXuXsXsXwX4XA o.B.V.H.S.B.B.C.H.B.m.m.B.T.B.V.B.T.d.j.M.z.l.d.z.c.T.S.S.V.B.v.n.b.m.T.l.j.z.k.j.k.d.k.f.m.b.b.b.m.v.h.x.h.j.z.k.k.k.k.k.k.z.v.n.n.c.n.n.n.g.c.l.l.v.l.z.d.l.l.l.f.c.n.n.v.f.c.n.n.g.c.n.T.m.G.$.l.l.f.l.f.v.f.d.d.d.d.j.k.k.#.f.+X@X#X#X#X@XCXw.{ } 2.2.%.%.1.2.%.4.4.r.X.%.%.X.F X.1.$.2.r.u.*.*.4.4.u.R R %.r.2.#.#.1.2.2.1.2.2.%.%.#.#.%.5.*.*.*.*.5.4.E r.>.( . #XmX#XSXVX. . J.<.2.1.X.Y X.1.1.#.$.<.<.$.%.%.#.>.2.E *.......R r.%.X.%.1.$.#.5.t.R ....%.<.<.<.1.2.2.2.4.u...t.4.%.2.#.<.#.5.&.3.5.........5.*.*.7.&.&.&.*.S +X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmXD _.gXfXsXsX4X3XY.[ [ [ H.V.B.L.P.H.V.V.B.b.w.m.T.b.z.j.d.n.n.d.d.C.K.V.H.C.T.n.c.b.b.m.b.z.z.z.z.l.d.l.n.b.n.n.n.n.v.x.X.h.j.j.k.z.k.k.k.j.k.z.c.b.n.n.b.n.c.c.v.v.v.v.z.f.v.f.v.c.n.n.c.v.v.v.c.n.c.v.g.c.v.c.z.z.v.v.v.c.v.v.l.l.l.d.j.k.j.#.S . #X#X#X#X#XBX. ) %.>.1.R r.1.%.4.r.%.r.%.R 2.2.M.X.$.#.r.r...*.u.t...R u.4.4.%.2.2.r.4.t.r.#.1.r.R %.#.1.%.5.&.&.*.*...4.R <.e.CX#X#X#X_.@X+X/ f.<.2.M.M.H M.1.1.2.$.,.<.#.X.1.$.#.2.$.>.t.E 4.....u.R R X.X.X.#.%.t.E Y %.#.F #.<.$.2.r.2.t.E E E 5.5.5.t.2.#.r.3.&.*.*.*.*.*.3.*.*.&.&.&.7.*.Y.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ iXuXsXsXwX4X3X;XC.A Y.H.S.S.C.H.S.S.H.S.B.B.T.B.f.M.T.T.b.n.T.K.S.V.S.K.T.b.m.n.w.w.B.b.z.z.f.v.v.n.n.q.g.c.c.n.c.k.x.h.X.h.h.z.z.k.j.j.k.j.j.g.m.m.m.w.n.c.c.f.v.l.d.v.l.v.c.n.n.q.c.f.l.f.f.c.c.c.l.d.f.v.v.l.v.c.n.g.c.g.v.l.l.z.j.k.j.j.OX+X#X#X#X#X#X_.@X. (.v.{ { R R %.r.%.4.r.2.%.2.r.4.%.$.r.2.R *...5.....E u.%.4.5.5.5.5.5.5.%.1.1.2.2.1.X.M.2.3.7.*.E *.*.4.{ 1.`.+X#X#X#XVX@XI.<.>.R M.%.R %.1.#.2.#.,.<.#.#.$.>.1.1.$.<.,.#.5.t.4.R R E R H R X.F X.%.%.%.#.$.F F #.<.$.2.2.4.............3.3.3.U u.3.7.&.3.&.=.E R *.*.&.&.&.U A.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XD AXxXuXsXwX4X4XqXxX0XgX[ C.C.S.S.H.H.Y.H.S.V.m.B.v.f.b.n.T.S.V.B.B.B.V.H.w.b.m.b.b.b.m.S.n.l.v.c.n.n.c.c.v.v.c.n.c.k.X.j.k.h.N.h.X.1.h.x.h.x.d.c.g.T.g.c.n.c.c.c.v.k.d.c.v.v.n.c.c.c.l.l.l.v.f.z.k.z.d.z.k.d.d.d.v.g.q.n.n.n.c.d.d.z.k.k.1.e.. #X#X#X#X#X#X#X#X#XCXCX( M.r.t.4.%.4.r.r.2.%.%.2.%.%.%.M.4.....5.*.*.*...R %.5.3.*.*.3.5.5.4.1.X.2.>.1.z.Y t.&.&.U U *...4.>.A . #X#X#X#XBXmX#.>.%.r.2.4.4.1.#.#.#.<.,.$.#.$.#.2.2.1.$.$.$.2.r.4.4.r.%.R R R X.F %.R %.H X.<.,.,.<.$.,.<.2.#.2.5.U R u.....*.....E ..E 5.3.&.=.=.l.E *.&.=.=.3.m.VX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ iXfXsXsXqX3X4XzXgX;XC.S.C.C.C.K.C.C.C.H.S.B.T.v.c.n.V.C.B.B.V.V.V.V.B.S.B.b.w.m.m.m.V.c.v.c.n.n.b.n.c.c.g.v.l.k.h.x.j.j.X.h.#.l e.G.X.z.v.b.g.b.g.k.z.f.v.c.c.f.d.d.d.v.g.g.n.n.c.g.c.z.j.x.h.h.h.d.d.k.z.z.Y f.n.n.b.n.n.g.d.l.k.k.j.k.$X+X_.% #X#X#X#X#X#X#X_.VXCX( { %.2.%.t.r.2.%.%.4.R %.4.Y 2.E E 4...*.*.&.5.%.t.*.*.&.*.*.5.5.u.1.X.%.2.X.H R R } I :.:.O.R { z.@X@XmX#X#X#X).+Xz.>.r.2.R R %.2.1.2.1.1.2.X.%.X.2.2.F 1.1.1.1.r.u.4.4.4.r.%.E %.#.X.E R H %.#.<.<.,.,.<.$.1.2.#.>.2.4.4.4.t.5.E ........E 4.4.&.=.=.l...l.=.=.T ( +X@XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X)._ dXuXsXsX3X4XhX[ [ L.B.c.m.B.S.C.H.m.V.m.T.T.c.v.T.S.S.m.B.S.S.S.V.B.B.B.B.m.m.m.B.m.z.v.g.c.n.n.n.q.b.l.h.h.X.h.h.h.j.k.h.$.Z.CXZ.v.S.b.T.T.n.k.k.d.l.c.c.c.c.c.d.k.z.z.d.v.l.d.d.k.N.N.j.x.x.j.d.d.d.d.d.k.f.b.b.b.b.b.c.l.l.z.k.2.A . #X#X#X#X#X#X#X#X#X#X#X#X@XBXI.#.r.t.3.4.#.%.r.r...E u.4.%.u.4.4...*.*...4.r.5.*.*...3.=.7.l.X.>.X.k.%.%.4.2.%.g.e.V.w.S.g.v.` CX#X#X#X#X#X#X. JX>.%.E %.2.X.%.2.1.1.2.%.2.#.1.#.F X.1.#.X.2.%.E E R 4.1.$.2.%.1.R %.2.R 2.2.t.4.r.2.R %.$.<.#.<.$.1.1.2.t.t.4.E 5.E t.E E E E *.v...3.l.v.*.*.$X+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmX_ jXuXsXsX3X4XC.[ C.L.L.v.v.m.m.V.S.f.l.c.f.l.k.l.m.T.B.V.V.H.C.K.H.S.B.m.m.m.m.m.S.S.x.z.b.b.b.b.g.b.c.x.N.X.j.X.x.x.k.k.h.1.A . ` b.I.n.b.g.d.k.d.d.z.c.g.v.v.v.d.d.k.j.z.d.z.Y x.N.h.x.j.j.x.k.l.d.l.l.d.d.c.m.w.n.n.b.g.f.l.d.M.g.7XBX_.#X@X+X. CX7XSX#X#X#X#XSX. /.#.t.5.5.*.4.2.4.%.u.5...%.R R r.4.5...E *.B.B.*.*.*.U *.=...%.#.2.%.%.%.R 2.{ S CXCX+X+XCX@XBX+X_.#X#X#XSX7XCX#X] d.} %.2.1.2.%.#.1.2.1.#.2.X.%.%.%.%.X.X.F X.E R 4.E 4.2.#.$.4.2.2.1.%.E ..5.4.4.u.5.U t.%.$.,.#.r.%.#.h.R t.E *...4.t.E ..5...l...5.l.=...E n.VX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ $XfXfXsXqXwXC.H.C.L.L.B.v.w.B.S.C.H.d.X.k.z.l.g.B.S.B.B.Y.C.c.m.Y.C.S.B.m.m.m.m.S.f.X.j.k.v.b.b.n.v.j.X.x.x.h.h.X.x.j.j.h.h.g.. R.n.n.T.f.k.z.z.z.d.z.z.f.f.f.c.f.k.k.d.f.d.d.z.M.j.M.j.j.k.z.j.z.l.v.d.z.f.l.n.m.n.g.n.c.c.f.d.k./.. +X. CXVXQ.V.Y.#X+X#XSX#X#X#XBXSXd.O.4.O.&.7.4.r.O...l.u.%.E %.r.4.4...E *.s.-.&.*.&.&.&.#.<.#.X.4.4.r.%.I >.q . @X#X#X#X#X@XVX#X#X#X_.#X. mXm.=.&.&.X.1.2.2.2.X.%.R H %.%.%.%.X.1.1.#.#.>.<.$.%.R 4...R 2.#.<.1.2.1.2.%.E ....t.%.E E u.R t.r.#.4.4.R 2.X.%.4.l.f.Y R E E E ..*.4...=.E U g.A._.@XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX_ dXuXsXwXdXH.H.S.S.T.B.T.n.V.S.H.w.M.d.n.n.B.B.H.K.B.S.C.L.l.d.n.b.B.V.B.B.B.H.T.N.j.k.h.X.k.z.z.j.x.j.x.h.j.h.N.X.j.j.x.#.g.. Y.n.T.n.z.k.d.d.z.z.z.k.k.f.c.g.d.X.z.f.l.l.d.z.k.Y k.k.d.v.l.k.k.l.d.d.z.J.l.c.m.n.c.c.n.v.f.l.z.c.w.V.;.V.=.t.:.} ..` . VXmX#X#X#X. m.} &.9.5.&.5.2.4...&.R Y R %.4.r.2.*.&.*.3.*.&.&.&.9.%.#.%.5.5.H %.{ >.>.g.CX@X#X#X#X% #X#X_.#X#X#X#X+X$XT | 3.7.&.X.2.1.1.1.2.R %.2.1.#.#.#.1.#.#.#.#.#.<.,.$.4.r.%.2.#.$.<.2.4.2.r.%.%.4.E ..R %.r.R E 4.R t.4.4.4.r.1.2.l.O.4.e.( ( w.c.c.v.w.' (.;.A.mXCX7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XD 1XsXqXeXH.o.V.w.b.m.H.V.n.m.B.n.M.w.K.n.B.H.S.V.B.S.C.U.V.z.l.c.v.n.B.S.V.V.m.m.l.j.k.x.X.j.X.Y k.j.j.x.j.k.j.N.h.j.j.h.h.f.A.T.T.T.T.d.z.f.l.z.k.k.z.d.v.c.l.z.k.v.l.d.l.l.d.z.d.d.c.n.c.c.z.j.z.z.z.z.z.f.c.v.v.g.n.g.f.f.5.5.O.u.R :.I O.5.5.5.:...Z.+X@X7XCXCXCXmX..q.S.t...*.t.t.t.r.M.Y 2.r.%.R 2.%.&.=.8.8.&.&.8.&.5.*.*.*.&.5.1.X.z.A +XBX_.#X#X#X#X#X#X#X#X#XSX+X$X*.3.&.7.6.&.2.2.1.1.#.1.1.1.1.1.1.#.#.>.$.>.<.<.,.<.1.%.1.2.#.1.%.1.r.t.t.%.r.r.r.4...&...Y Y Y E Y Y E t.4.4.r.r.z.z.R A.. CX+X7X#XSXSX+X+XBXCX+X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ hX0X;Xo.V.m.w.m.S.Y.K.T.n.m.b.T.~ S.v.m.C.V.V.S.C.L.L.n.l.n.c.c.n.w.B.B.n.c.b.b.k.j.j.j.x.j.Y z.j.j.j.j.j.j.h.h.j.j.X.x.z.d.n.T.V.V.v.z.v.l.z.z.d.l.f.l.J.z.z.l.v.f.l.v.l.d.v.b.b.b.n.g.g.d.j.z.z.z.k.G.z.v.d.d.f.J.z.*.5.U U t.E ..5.3...5.5.*.5.E | C.. SXQ.m.d.c.c.3.4.=.T 5.E u.4.r.%.M.4.2.r...u.2.r.4.u...&...*.*.&.3.3.*.3.3.t.A.. . @X_.#X#X#X#X#X#X#X#X#XmXZX+Xq.| &.&.7.i.d.X.2.1.1.#.#.$.#.1.1.1.>.$.<.<.$.<.,.<.%.E u.1.2.X.1.F 4.U 4.4.%.2.2.X.%.Y X.X.H M.X.M.R E E R t.E 4.4.Y e.DXCXVX#X#XAX@X@X@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ yXuXhXo.K.C.m.B.Y.K.K.C.V.S.K.F.C.b.n.c.Y.C.H.C.C.C.B.m.V.B.b.c.v.c.n.n.v.c.b.b.z.j.k.M.j.j.j.Y j.j.j.k.j.X.x.h.x.k.j.x.d.f.c.n.m.T.n.l.d.f.d.d.l.f.l.z.k.z.d.v.v.v.v.l.d.l.b.b.b.b.n.n.c.l.z.z.d.d.j.j.z.l.l.J.M.I 5...&.l.O.t.t.5.5...5.:.t.&.5.*.l.=.e...O.} 3.q.3.5.*.&.u.4.R E Y r.r.Y E R r.R ..t.r.$.$.>.X.1.h.Y 4.4.3.3.*.3.3.q.#X@XmX#X#X#X#X#X#X#X#X#X7X. . S.:.3.*.+.*.7.M.2.%.r.2.1.$.#.X.1.>.%.u.r.r.#.$.#.2.t.%.1.4.u.R 2.#.2.t.4.4.R R %.H F <.<.#.#.#.#.r.1.%.Y 4.4.u.5.4.Y E ` . #X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ dXgXo.K.C.S.S.H.L.K.T.T.Y.L.K.m.B.m.c.b.C.K.C.S.b.n.m.m.m.m.n.c.c.l.c.b.n.n.n.k.x.z.z.k.x.X.Y j.k.k.k.k.x.k.X.j.k.j.j.z.f.c.n.g.n.b.I.z.z.l.z.J.d.l.k.z.d.d.v.c.c.c.v.l.v.n.n.n.n.n.n.g.v.z.j.z.d.x.k.l.v.d.M.d.S.DX$XmXSXY.O.t.5...U I m.b.O.5.t.v...:.O.5.U =.C.H.9.&.*.5.4.r.%.%.2.R E R R r.r.E E t.%.1.$.1.2.h.u.4.u.5.3.&.3.&.O.e.. #X#X#X#X#X#X_.VXCX+X`.P e.U +.&.&.&.*.3.X.%.2.2.%.2.#.1.X.#.>.4...R 4.#.#.4.u.4.%.R E E 4.2.4.t.4.R E R R R j.#.$.r.4.4.4.t.4.1.X.X.2.5.5.*.E E ..!.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"_.SX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ iXxXH.S.H.K.C.C.L.I.M.d.V.H.B.V.B.B.w.n.m.Y.K.V.b.b.m.m.b.b.n.n.b.v.v.n.v.c.v.j.j.k.z.d.z.j.k.z.j.k.z.Y j.j.k.k.z.k.X.d.v.I.I.c.n.c.T.n.z.d.d.k.G.k.k.d.l.l.v.v.c.v.v.l.v.n.n.c.n.b.b.g.d.k.k.z.j.j.l.v.l.Y A ).. +X7X@X. +Xl.:...5.} m.. Y.} 5.E t.u.5.5.5.5.t.O.=.F.q.5...u.4.%.2.%.E R M.%.4.%.R E u.4.2.#.1.H 1.z.*.5.t.*.&.&.&.3.T VX. @X#XmX_.VXCX+X/ S.=.5.+.7.&.7.*.3.*.3.4.2.%.f.d.Y 2.#.X.F %.M.R u.%.4.5.5.4.u...u.%.r.r.u.5.4.R E R R Y X.#.2.2.2.r.4.t.4.r.1.X.1.1.5.3.&.*...f.DXBX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"mX_.mX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX7XjXS.C.K.K.K.C.m.n.T.n.n.C.K.C.C.H.H.S.V.V.V.m.V.B.B.B.w.b.n.n.w.n.v.c.c.c.j.X.k.d.d.l.l.k.Y k.j.k.z.k.j.k.j.k.d.k.j.d.J.l.g.g.v.f.n.B.v.z.l.d.z.k.k.f.f.f.v.c.c.v.v.c.n.n.n.n.b.n.l.d.j.x.z.G.k.l.f.l.z.z.#X. #X#X#X#X#X+X/ O.{ { n.+X. b.O.5.5.t.5...5.t.5.*.*.t.5.u.4.u.t.%.%.r.E 4.%.X.%.E E 4.R ..t.%.#.#.X.F X.*.*.=.5.5.=.&.7.U w.(.mX. @X+X+X!.n.5.U 3.7.i.i.&.&.*.3.*.&.3.4...l.....X.$.1.1.X.4.4.r.t.*.5.u.....u.%.4.%.t.t.t.5.E E u.%.%.2.r.t.1.#.r.4.4.4.R 2.%.2.1...&.&.*.U w.7XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XCXE.m.C.C.S.V.B.m.S.S.S.B.m.m.S.S.B.T.w.b.w.b.n.m.V.B.B.T.n.n.c.m.v.j.d.l.l.k.x.z.l.l.l.l.z.k.z.k.j.z.d.k.z.k.j.k.j.k.z.l.z.d.c.v.c.v.g.n.f.d.l.l.d.d.l.v.f.l.v.c.c.c.g.b.n.b.b.B.g.Y %.h.j.l.l.d.f.v.f.M.f.VX@X#X#X#X#X#X#XCX' n.e.VX+XBXb.| *...U E ..5.:.U &.*.*.4.%.4.r.2.r.2.4.E r.%.X.%.E R %.2.R E t.2.1.X.%.N.M.8.s.b.&.&.=.5.u.I O.d.Y.BX(.c.4.O.*.*.7.8.i.i.8.3.*.&.&.3.*.&.*.U 5.t.{ $.F #.<.1.X.t.*.5...*...t.4.E u.4.t.E ..*.....%.r.5.5.3.*.5.{ 2.r.4.R R %.%.#.X.*.7.&.*.U A.CX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X% #X#X#X#X#X#XSX#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@XW.C.K.B.S.C.C.K.B.B.B.B.B.m.n.v.f.f.l.v.c.n.m.n.c.c.c.n.b.n.n.b.B.l.j.k.d.l.v.z.z.f.f.l.l.z.Y z.k.k.z.z.k.k.j.k.j.k.z.k.d.z.k.f.n.n.v.v.c.n.v.d.d.d.l.l.f.l.z.f.n.n.c.n.b.b.b.m.w.X.h.j.J.e.( v.l.f.l...%.A CX#X#X#X#X#X#X#X#X+X. CX@X#XCXS.:.&...t.5.5.:.m.B.&.*.*.u.R 4.%.2.2.r.E R 4.%.X.%.E R 4.4.R ..E 4.%.X.H 1.r.&.*.H.H.T 4.4.4.4.O.I } v...} t.*.3.*.7.8.i.i.p.7.3.&.&.&.&.&.*.3.3.=.M.>.%.%.>.1...3.*.&.&.*.u.t.5.5.t.5.5...E 4.E R r.3.&.&.*.5.T g.{ 2.r.r.r.Y Y F t.&.7.7.*.U A.CX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X% #X#X#X#X#X#XSX#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XVX;.B.K.L.L.C.S.S.V.B.V.B.m.m.V.T.l.v.v.g.n.n.n.n.n.c.c.c.g.b.m.b.z.j.l.d.l.v.v.l.z.v.v.v.f.d.j.k.z.z.j.j.k.j.j.z.k.z.G.k.z.z.k.d.T.c.v.v.c.n.T.n.d.d.f.f.f.l.d.z.c.w.n.b.m.m.B.b.w.A A.`.mX+X. I.R z.M.M.S +X).#X#X#X#X#X#X#X#X#X#X#X#X#X@XSXq.| 5...} { b.. A.| t.t.5.k.R E R %.R R 4.u.2.X.r.R 4.R E R R ..R t.%.%.%.2.&.7.3.8.*.r.4.u.t.u.t.4.:.4.t.3.*.3.&.7.8.i.i.i.&.5.*.7.=.7.*.3.&.&.(.g.>.%.%.1.%.*.&.&.=.*.t.5.*.u.t.3.5.5...E R u.4.5.*.&.&.&.| n.. d.<.1.2.z.d.4.4.*.7.=.&.*.U Z.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X_.#XSX#X_.mX#X#X#X#XSX#XSX#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X@XL.B.H.L.C.V.V.S.H.S.B.B.m.m.B.B.v.l.v.c.n.n.b.b.q.c.c.c.n.n.v.h.<.N.f.v.l.v.c.c.f.v.v.v.v.l.k.z.z.z.G.k.j.k.z.z.k.z.z.k.z.z.k.k.v.b.c.v.n.b.B.n.z.d.v.f.v.l.l.z.k.c.b.b.w.w.n.X.I.CX. BX@X#X+X/ z.z.l.) . @X_.#X#X#X#X#X#X#X#X#X#X#X#X#X_.7XVXZ.P (.V.;.. ;.O.5.q.T 5.E E R R R 4.4.4.r.2.X.2.R 4.E E r.r.E ..E 2.F Y X.u.i.&.u.r.4.E t.t.5.5.u.t.u.u.5...*.*.7.i.8.i.&.5.5.*.&.7.&.3.*.+.T $Xk.{ R r.2.u.&.&.&.*.3.&.&...u.*.3.............*.&.&.&.*.*.I q.. SXR r.z.z.R t.t.&.7.&.&.U c.@X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#XmX#X#XmX#X_.#X_.mX_.#X#X#XmX#XSX#XSX#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@XR.S.C.H.K.C.H.C.C.C.S.B.m.T.m.m.v.l.c.v.g.b.m.w.n.g.c.n.c.l.h.<.$.<.z.c.v.c.c.c.g.c.l.v.v.f.l.l.d.d.k.j.z.z.l.z.z.z.k.k.z.z.z.j.d.n.T.T.n.n.c.d.l.d.f.v.v.f.l.z.z.k.d.l.l.z.M.h.j.(.+X#X#X#X#XBX$X(.#X+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X+X+X+X. . W.| &.s.v.=.5...R r.r.4.4.4.2.1.r.%.%.R u.E E %.r.%.E E 4.2.%.X.4.&.u.r.t...t.4.t.5.5.5.5.3...5.3.*.7.7.&.3.3.5.5.3.*.*.3.3.3.3.3.=.DXz.>.R 2.%.5.*.*...3.=.n.=.t.*.&.&.*...*.*.*.*.&.&.&.&.&.U v.(.7X. _...} { O...E &.3.U U ..DX+XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X_.% _.#X_.mX#XmX#X% #XmX_.#XSX#X_.SX_.SX_.SX#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XVX`.K.K.C.H.L.L.C.S.V.I.n.m.T.m.m.f.l.f.v.n.v.z.v.b.n.g.n.n.n.g.v.f.k.k.c.c.c.q.n.n.v.l.v.l.v.f.l.l.d.j.k.d.z.d.k.k.z.k.k.z.z.k.z.v.c.I.T.I.d.l.l.l.l.n.f.l.d.z.l.v.v.l.k.x.X.j.d.1.I.. AX#X#X#X_.BXBX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X7X(.0.7.8.*.t.*.*.5.%.4.4.4.t.2.#.2.%.%.R E R R 4.R R r.4.E u.4.%.M...u.4.E ..t.4.t.*.&.3...5.5.*...3.&.3.5.5.u.E u.t.u.5.| O.t.=.=.5.C.+Xz.{ R r.4...*.5.5.*.c.v.5.t.3.*.*.5.O.O.| t.5.=.7.&.&.| m.BXBX_.#X. ;.O.v.l.%.4.I *.=.=.( . #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#XmX#X#X% _.#X_.#X_.mX_._.#XmX#XSX#XSXmXSXSX_.SX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.VX#XSX`.^ C.B.b.c.v.v.v.n.b.m.m.v.l.v.v.b.d.$.z.g.c.c.c.g.n.b.b.n.n.v.c.g.q.g.n.n.n.c.v.v.v.f.v.f.z.k.d.l.d.l.z.d.d.j.f.l.k.k.d.n.I.T.B.I.d.c.l.d.c.m.v.d.l.d.l.c.c.g.c.k.j.j.z.Y X.!.. #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAXBXm.3.i.8.5.4.*...5.t.4.Y u.r.#.1.r.M.R %.4.E R %.4.4.%.%.E 5.4.%.Y u.5.*...t.4.4.3.8.8.8.7.&.3.5.t.5.t.t.t.t.t.U ..U t.t.c.S.;.`.1XFXCX#XM.2.E 2.4.*.*.u.5.*.l.t.5.5.5.O.O.:...b.v.E U | | | | n.BX+X#X#X#X#X7X#XBX` e.w.e.' #X#X7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X% _.#X_.#X#XmX#XSX#X#XmX#XSX_.SX#XSX#X_.#XmX_.SXSXSX_.SX#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XVX@XBX@Xe.c.n.n.q.n.c.n.n.m.T.f.l.v.v.n.n.f.v.v.c.v.v.c.v.c.v.v.f.v.c.n.n.n.n.b.n.c.v.c.v.v.c.v.z.z.f.d.l.f.d.d.z.z.n.n.z.M.J.n.I.T.V.T.v.v.l.v.n.b.g.l.f.l.f.v.c.c.n.c.z.j.M.z.%.j.' . #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmXVXCXb.3.8.8.*.t.u.....3.5.u.Y X.1.2.%.R X.r.4...E R 4.t.E R R *.R %.j.O.3.*.t.t.5.t.*.*.5.5.5.5.5.t.t.t.t.t.u...5.E 5.t.:...VX. +XBXBX+X+XSXM.{ R %.4.5.u.t.u.*.d.%.Y k.u.) !.A.' BXVX_.' w.q.b.S.% +XSX#X#X#X#XAX7X@XBXCX+X+X+X@XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#XSX#X#X#X#X#XmX#XSX#X_.#X#XSX#X_.mX_.#XSX_.SXSX_.SX_.SX_._._.mXSXSXSXSX% _.SX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@XmXV.n.n.b.b.n.b.b.T.m.f.d.v.v.v.c.w.n.v.v.v.v.v.v.c.l.d.d.d.l.d.g.b.n.n.b.b.g.c.c.c.d.l.v.J.l.v.f.d.d.z.z.z.v.c.n.d.M.v.I.b.T.V.S.c.v.b.n.n.n.m.n.l.v.v.v.c.c.c.c.n.z.j.z.d.2.z.#XBXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX+X!.=.+.7.7...t.r.n.B.t.*.u.h.j.X.X.%.r.2.4.u.E ..5.%.4.*.t.4...u.%.%.l...t.5.5.t.u.4.r.4.t.u.t.t.5.t.E u.5.5...5.r.{ } ..( CX#X#X#X#X#X@XVXz.{ u.r.4.5.u.4.u...l.E %.z.( . CXCX+X#X).7X+X+X7X7XCX@XmX#X#X#X#X#X#X#X#X#X#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#XSX#X_.#X#X_.#XmX#X#XSX#X_.mX_.mX#XSX_.mXSXmXSXmXSXmXSXmX_._.SXmXSXSX$XSXSXSX_.% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X7X(.B.V.S.V.V.I.n.b.b.n.z.l.l.l.v.n.n.n.v.v.v.f.f.l.z.z.l.d.d.l.z.d.w.b.n.n.b.n.q.g.f.d.d.f.c.g.b.g.l.d.z.d.d.l.J.f.f.z.n.T.T.T.V.V.n.n.T.g.g.T.m.m.n.v.v.v.v.v.c.v.c.n.d.H z.z.1.e.. #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@XF.7.3.*.*.&.*.4.4.&.b.5.5.5.r.h.h.X.2.2.2.%.E ......R 4.5.R %...E r.Y g.M.2.4.4.r.r.r.4.t.t.t.t.5.u.U ..5.R O.{ { r.w.Q.SX+X#X#X#X#X#X#XVX7Xl.{ E %.r...5.4.u.E *...1.e.. #X#X#X#X#X#X#X#X#XAXVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#XmX#X_.mX#X_.#XSX#X#XmX#X#X#XSX_.mX_.#X_.SX_.SXSXSXSXSXSXmX_.SX_.SXSXSXSXSXSXSXSX#X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X` V.S.H.V.m.n.v.v.v.c.l.l.v.f.c.b.b.n.c.f.v.f.d.j.h.h.j.d.l.d.d.z.b.m.n.n.c.n.n.c.l.v.c.c.n.n.g.b.b.w.c.f.g.c.d.d.v.v.T.B.I.T.V.K.T.n.c.g.b.T.T.T.B.g.f.v.v.f.v.q.c.n.c.l.k.d.M.z.#X+XmX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XVXmXi.@.i.&...*...u.u.t.4.=...t.4.2.1.1.#.2.2.%.5.E ......R E ..t.....%.j.%.2.2.r.r.r.%.u.t.R ..5.5...5.u.O.{ { } v.V.GXCX. 7X#X#X#X#X#X#X#X@X@Xd.} E 4.r.t.5.4.4.E *.d.2.c . #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X_.mX_.#X#XmX_.mX#XmX#X_.#X_.mX_.mX#X#X_.SXSX#XSX#X_.#XSXSXSXSXSXSXSXSXmX_.SXSXSX$XSXSXSXSXSX_.SX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XVX#XS.m.B.m.b.n.c.v.c.q.f.d.c.v.q.n.n.q.c.c.v.d.M.X.j.j.j.k.d.f.l.d.v.m.m.b.n.n.c.f.v.g.c.c.c.g.n.n.I.S.V.J.c.n.d.z.v.g.n.T.m.T.S.K.V.c.c.n.b.T.b.T.T.v.d.c.c.v.v.c.c.c.v.g.l.k.d.%.I.BX).#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XBXB.+.@.8.*.*.5.u...u...c.*.t.4.t.%.%.4.1.1.r.......*.5.........E ..E %.r.4.4.%.4.r.4.*.E 5...5.t.5.t.O.E n.V./.BX. +X#X#X#X#X#X#X#X#X#X#XVX7Xl.} E R 2.u.*.t.4.E ..l.%.A.. #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X% #X#XmX_.#X#X#X_.#XSX#XSXmX#XmX_._.mX#XmX_.SX_.SXSXSX_.mXmXSX$XSX$XSX$XSXSX$XSXSXSXSX$XSX$XSXSXo SXSXSX% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X;.q.b.m.b.b.q.T q.n.v.v.g.g.g.n.n.c.g.c.l.Y j.j.k.d.l.l.l.l.d.d.k.f.n.n.v.l.d.d.f.c.v.c.g.c.b.T.T.V.L.T.f.I.g.z.c.f.g.V.B.e.V.T.b.n.n.n.w.T.T.T.n.f.d.f.g.c.c.v.v.c.c.v.c.d.z.d.2.A.CX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X_.H.i.@.| 4...u.5.d.5.t...*.3.u.5.7.5.#.#.1.u.*.*.*.....*...5...E t.R r.4.4.E R t.E E 5.t.t.u.5...*.| S.. . CXVX#X#X#X#X#X#X#X#X#X#X#X#X).BXv.} E R r.4.5.t.4.E ....d.' +X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X_.#X_.#X_.#X#XmX_.#XSX#XSX_.#XSXSXSX#XSX#X_.mX_.mX_.mX_.#XSX_._.SXSX_.SXSXSX$X$X$XSXSXSXSXSXSXSX$X] SX$X`.$XSX$XSXSX#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X#XZ.V.w.s.s.s.n.b.n.b.m.m.w.b.v.v.n.c.l.d.d.z.z.d.d.v.c.c.v.z.j.k.z.z.z.k.j.v.n.l.f.c.g.b.n.T.V.S.B.f.v.v.c.I.v.f.k.c.T.n.T.V.V.T.I.n.T.T.m.V.b.v.v.l.d.c.g.g.v.f.f.c.c.c.n.d.l.%.A CX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX@X+X$X!.S.*.r.u.R 5.*.*.*.*.5.u.&.*.X.#.1.1.%.&.*.*.*...E ..t...*.*...4.u.E 5.4.4.4.4.4.5...*.7.7.7.3.B.BXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X+Xn.{ E E %.4.5.5.t.t.E E n.mX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#XmX#XmX_.#XmX#X#X#X_.mX#X_.mX_.mXSX#XSXSX_.#XSX_.SXSXSXmXSXSX_.mX_.SXSXSXSXSX$X$X$XSX$XSX$XSXSX$XSXSXSX$X`.DXSX`.SX#XSX#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX@XBX@X` ;XgXgXxXgXe.G s.G s.9.m.Z.e.l.l.l.v.v.d.z.c.n.g.c.n.d.1.j.l.z.k.k.j.n.Y.T.g.c.n.n.b.I.n.K.T.k.l.v.c.g.c.v.k.J.T.g.T.S.Y.S.T.T.B.V.B.I.c.v.c.c.d.v.c.c.g.v.c.v.c.c.g.v.l.Y V.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X+X+X@Xe.} 4.u...*.3.3.5.*.&.t.N.X.X.>.%.&.*.*.*...t.O.t.t.E v.d.r...t.%.4.4.t.u.5.*.=.7.7.7.7.3.b.7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X. A { E E 4.r.3.*.E t.5.E g.mX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X_.#X_.#XmX_.#X_.mX#XmX_.SX#X_.mX_.SX_.SXmXSXSX#XSX#X_.SX_.#XSXSXSXSXSXSX_.SXSXSXSXSX$X$X$XSXSXSXSXSX$XSXSXSXSX$X$XDXSX$XDXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@XCXrXfXgXuXuXgXy w.I.k ;X. . Y.l.v.v.l.c.c.n.b.n.n.c.c.c.k.j.z.k.j.X.f.T.V.S.n.c.c.n.b.T.g.T.v.j.d.g.g.c.f.g.J.z.T.n.T.Y.C.Y.C.C.V.b.n.n.v.v.n.v.d.v.c.c.c.c.c.v.l.v.c.v.d.d.v.mX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X+X. V.} 4.E ..5.*.&.&.*.u.1.h.1.$.Y T *.*.*.&...E E 5.....4.R E O.4.4.4.5.3.3.*.&.+.3.3.| | ;.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X. Y.} E E R 2.t.3.5.t...4.n.BXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#XmX#X_.#X#X#XmX_.SX#XmX_.SXmX#X_.SX#XSX_.SXSXSXSXSXSXmXSXSX_.#X_.SXSX$XSX$XSXSX$XSXSX$XSXSX$X$X$XSXSX$XSXSXSXSXSXSXSXSX`.$XSX8 SX_.mX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ iXfXhXgXzXuXfXsXqX0XwX$ . Y.l.v.v.v.c.m.w.b.b.n.n.c.w.b.k.x.k.d.d.v.I.T.V.n.g.n.n.b.w.T.g.g.f.v.b.g.c.v.g.l.k.v.T.g.B.S.Y.Y.T.n.n.b.n.c.g.c.v.v.v.q.c.c.g.c.v.v.f.c.c.l.l.d.Q.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X. A :.u...5...u.5.t.r.%.X.$.1.d.&.&.*.*.&.&.=.u.%.R 1.2.E t.%.4.4.2.%.t.5.5.:.| 9.s.s.L.@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX+Xw.} E E E 2.4.3.t.U ..O.w.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#XSX#X#XSX#X_.mX#X#X_.mX#X_.SXSX#XSXSX_.mXSXSXSX$XSX$XSXSX#XSXSXSX_.SXSXSXSX$XSXSXSXSXSX$XSX$XSX`.DX] $XSX$X`.SX$X8 $XSXSX$XDXDXSX8 `.SX% #XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ yXfXhXhXfXzXxXfXuX0X0XrX. P v.T v.c.b.b.b.b.b.b.c.g.V.T.l.z.v.n.v.d.n.B.V.b.v.c.b.I.I.n.n.T.T.g.c.c.v.c.c.z.k.k.g.T.v.T.C.n.v.c.b.b.n.b.n.c.n.n.v.c.g.c.g.v.v.c.v.c.n.v.v.Y A.CX% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmX@X. I.:.5.t.u.t.O.r.%.%.t.1.h.E 5.*.&.*.&.*.&.5.1.1.2.2.%.R R r.%.1.2.r.I 4.n.R.VXBX+XCX#X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X+Xb.} ....E R R 5.5.u.5.O.w.7XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X_.% #X#XSX#X#XmX_.mX_.mX_.SXSXmX_.SX_.#XSX_.mX_.SXSXmXSX$XSX$X$XSXSX_.mXSX_._.SXSX$XSX$XSXSXSXSXSXSX$X$XDX`.DX] $X8 $XSXSX$X] SX$X$X`.DXDX`.DXSX#XSX#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAXAXsXhXtXqXfXuXxXfXuX0XqXCX! v.c.v.v.b.n.b.b.b.b.c.m.S.w.c.c.v.v.c.f.g.Y.Y.S.S.g.v.b.n.n.I.I.T.I.J.l.f.v.J.k.k.j.z.I.b.n.T.n.n.n.n.b.w.w.w.m.I.v.f.v.q.n.n.n.c.v.v.c.n.g.v.Y ;.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.BXBXd.{ :.} } ..S d.r.r.2.1...5...&.&.&.&.&.3.2.2.2.%.%.r.4.r.4.r.1.{ %.( . CX@X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#XVX7Xg.O...5...E %.t.&.5.5.U n.@XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X_.mX#X_.mX#X#X_.mX_.#X#XSX#XSX#XSXSXSXSXSXSX_._._.SXSX#XSX$XSX$X$XSXSXmXSXSXSX_.SXSXSXSX$XSX$XSXSXSXSXSX$XDX`.DX`.DX] $X$XSXSXDXSXSXDX8 $X$XSXDXDXSX_.mXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ dXsXjXqX4XfXxXxXuX0X0XiXZ q.q.c.v.n.b.n.n.b.n.c.V.S.n.c.c.v.v.v.v.l.A.+X@X7X`.I.l.l.g.b.T.T.T.f.l.l.f.v.v.z.x.k.n.T.g.n.n.n.n.b.T.m.B.B.n.J.r e.c.c.n.n.n.c.c.c.v.c.n.n.d.A +X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.CX(.v.l.b.Y.@XDXr.r.%.2.M.*.5.5.*.&.&.&.&.*.r.2.%.r.r.2.r.4.R R 2.%.OX. VX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X+X/ t.E 5.....t.r.3.&.5.E O.m.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X% #X_.mX#X#X_.mX#X#XmX_.mX#X#XSX_.mXSX$XSXSX$XSXmXSXSXSXmXSXSXSXSX`.$XSX`.DXSXSXmX_.SX_.SXSXSXSXSX$XSXSXSXSXSXSX$XSXo DXDXDXDX$XDX$XSX`.$XSXDXDX`.DXDX`.DXDX#XSX_.#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ aXuXtXhX0X4XsXsXuXuX0X4XaX-.T n.v.n.b.b.n.m.n.b.B.I.v.l.l.l.v.v.d.d.d.e.`.. . 7XA.I.c.n.n.T.n.f.f.l.J.n.n.k.x.k.c.I.n.n.b.b.w.m.T.B.g.l.d.l.e.. e.l.n.b.n.c.c.g.c.q.n.n.c.e.@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmX+X+X@XCX. . ) { r.X.2.l.*.U 3.*.&.&.&.&.7.E R R R %.%.r.r.4.4.} z.DX. . 7X_.% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XCXR.O.5...*.*.5.4.5.&.5.5.U / +X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X_.#XSX#X#X#XSX#X#X_.mX_.mX#X#XSX_.mX_._.SX_.#X_.SX$XSXSX$XSX$XSXmXSX$XSX$X$X$X$X$X`.SXmXSXSX$XSXSX$XSX$X$X$X$XSX$XSXSX$X] DXDX`.DXDX8 `.DX`.SXo DXSX] $XDXDXDXDXDXDXDXSXSX#XAX#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XD _.sXfXdXqXwX3X4X3XwXqX0XuX.2.2.%.R 4.%.4.4.u.R r.} { u./ . #X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSXVXBXSX_.b.I 3.*...E *...*.&.*.U =.$X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X_.% #X_.mX#X#XmX#XmX#X#X#XSXSX_.SX_.mXSXSXSX_._.SX$XSXSX$X$X$XSX$XSXSXSX$XSX$XSXSX$XDX`.$XDX`.SXSXSX$XSXSXSX$XSX`.SX`.$X] DX$XSXSXDX$XDXDXDXDXDXDXDXDX] $XDXDX`.DXDXDX@ 5 DXDX@ DXSX_.#XAX#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X).D rXfXsXsXsX3XqXqXqX4XuX0XqXm.G G b.b.w.B.V.B.w.m.m.n.n.c.z.Y k.M.z.d.l.d.X.d.AXCX#X@X@X+X!.e.;.e.z.l.f.g.v.k.h.j.J.g.I.w.w.T.S.g.z.z.d.l.l.l.v.b.n.b.b.b.n.n.c.c.l.l.Z.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X+XP @.8...H 5.*.&.*.5.&.&.7.&.7.&.2.2.2.2.4.E E R u.u.U R E ..:.r.!.. @XSX#X#X#X#X#X#X#X#X#X#X#XSX@XBXZ.0.0.*.U *.&.*.*.&.*.*.*.3.| A CX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X_.mX#X_.mX#X#X_.#X_.#X_.mX_.mX_.#XSX#XSX_.mX_.#XmXSXSXSX_.SXSXSX$XSX$X$X$XSXSXSX$X$X_.SX$XSX$XDXDX$XDXDX$XSXSXSXSXSXSXSXo ] o `.DX8 $XSXDXDX`.DXDXDXDXDXDXDXDXDXDX`.SXDX` DX] DXDX` DXDXDX@ SX#XSX#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ AXsXqXsXsXsXqXtXjXfXeXsX4XqX A c.n.b.b.b.n.g.b.w.b.n.!.+X#X#X#X#X#X#X#X#X7X] m.w.m.B.m.V.m.n.Y.@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X+X;.+.8.i.i.i.6.~ . iXlXzXzXzXzXzXxXzXuX0X0XuXqXfXqXuXpXfXhXSX_ mX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XBXW.8.K 8.K +.V.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#XSX#X#X#X_.mX#X#X_.#X_.mX#XSX#XSXSX#X_.SX$X$XDXDX`.`.SX_.SXSXSX`.$X_.o $XSXSX$XSX_.SXSXSX$XSXSXSX$XSXSXSX$Xo SXSXSX`.DXDXSXSX$XDX`.$X] DXDXFX@ @ FXDXDXDXFX' FX6 / 2 HXHX# (.FXGXFXFX5 DX@ DX` FXFX' 6 GX6 5 DX2 ) 2 ` DX8 FXGX5 DXDXDXDXFX5 (.FX5 5 5 5 FX' FXFX' FXFX@ `.DXGX2 6 6 5 5 5 5 6 4 3 3 HX3 ( 3 1 JXJXHX6 / 6 3 HXGXGXGXGX( 2 GX) 3 4 u 3 6 FX2 ) 4 4 JX2 6 5 DX' GX3 2 ' ' 6 6 N n & dXdXhXhXjXjX+ V N 2 2 / 2 6 FX6 6 m V.B.S.S.H.S.m.b.b.m.m.B.;.2 > n.c.b.b.b.n.g.n.n.n.n.Q.. @X#X#X#X#X#X#X#X#X#X+XZ.0.m.B.B.B.B.n.V.VXVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XBXs.*.i.8.i.i.@.K.. $XkXlXpXzXzXzXzXzXxXzXuX0X0XqXqXfX4X0XpXgXD _ #XmX#X#X#X#X#X#X#X#X#X#X#X#X#X7X` 8.8.8.8.3.m.BXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#XmX#X#X_.mX#XSXSX#X_.#XmX#X_.SXmX#X_.SX$X$XDXDXDX$X$X$XSXSX$XSXDXDXSX$XSXSXSXSXSXSXSXSX$XSXSXSXSX$XSX] $XSX$XDX`.SX$X`.DXDXSXSXDXDXDXDXDX`.` (.DXDXDX(.DXDXDX(.FXGX2 GX/.HXGXFXDXDX(.DXFX' FX6 FX(.6 OX' OXGX/ FXDX` 2 GXDXDX] ] 5 FXDX5 ` 5 (.DXDXFX6 FX5 ' FX5 ' FX6 6 ' FX` FX/ 6 2 3 GX/ 2 ( 2 2 3 4 ) ) u ( 5 / ( 2 2 2 HX3 3 / FXGX3 ) 4 ) ( 3 3 S q u / 6 6 ` DX6 3 HXFXFX5 6 6 5 6 n B b b B B b ~ 6 6 6 6 / 2 2 2 HX2 2 ;.B.B.S.S.H.V.w.b.m.m.B.S.: S n.n.w.w.b.n.n.b.n.c.Y.VXFX$XBX@X#X#X#X#X#X#X#XBX' b.m.m.B.B.V.m.V.AX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XVX7Xn.+.q.8.i.i.6.-.VXCXAXgXlXkXpXzXuXuXzXxXxXzXuX0X4XqXsXuXqXfXgXaX_ AX#X#X#X#X#X#X#X#X#X#X#X#X#XBXP 7.6.8.8.+.9.mX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X_.#X#X_.#X% #X_.#X#X#XSX#XSX_.SX#X_.SX_.mXSXSXSX$XDXDXDXDXDX$X$X$X$X$X`.$X$X`.SXSXSX$XSXSXSXSX$XSXSXSX`.SXSXSXSX$XDX$XSX$XDXDXSX`.SXDXDX`.`.DX$XDXFXDXDXDXDX(.DXDXDXDXDXGXHXHXXX/ GXGX2 / @ FX` DXOXDX# 6 FX# 6 6 6 FXFX' 3 ' 8 DXSX6 GX` DXDXDXFX6 DX` FX' 5 ` 5 FX6 ' FX6 ' 6 5 DX' 2 HX( 6 5 5 5 ' 2 2 M 4 2 / 4 1 KX1 JX4 3 2 2 2 ( HX6 6 2 GX3 2 GX3 HX3 JX2 2 / FXFXS 4 2 6 5 5 DXDX5 ( 4 FX' 6 6 6 6 6 ' 6 6 FXDX6 / 2 2 2 2 GX2 S.B.V.S.H.H.B.w.b.m.m.V.Y.e.n.w.m.b.n.b.b.n.n.e.4 > c S 2 DXAX@X@X#XAX#X#XAX@XC.b.m.B.B.B.B.Y.#XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.BXDX7.7.i.i.8.i.8.0.`.@X_ _ dXkXlXpXzXzXuXzXzXzXzXfXpXuXqXqX0XqXpXfXyX_ D mX#X#X#X#X#X#X#X#X#X#X#X+XC.7.8.8.8.K =.' +X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#XmX#X_.mX#X#XSX#X#XSX#XSX#XmXSX#XSX_.SXSXSX_.$X$XDXDX(.DXDXDXSXSX$XSX$XDX`.SX$X`.$XSXSXSXSXSX$XSX$X$XSXSX$X] $XSX] SX`.`.SX$XDXDXDXSXSXSXDXDX`.SX`.FX6 FXDXFXFXFX5 5 GX( GXHXHXHXHX/.FXFXFX' FXFX' FX' FX` FXFXFX(.FX2 GXDXSXGXu 3 ` 5 DX8 GXHX5 ` 5 5 ' FXDXFX6 6 ' 5 5 FX6 # ' 6 GX6 FX5 ' 6 GX2 2 2 2 5 6 ` 3 1 1 JXJX1 3 6 3 HX3 2 HX/ 6 ( 3 2 6 GX3 3 ) 3 ( 3 4 A u GX' 5 DX8 8 8 3 2 5 6 6 / N 2 2 2 6 ' ' 5 6 ' FX6 6 GX5 @ ;.B.S.S.S.H.C.B.b.w.m.V.V.b.n.b.w.n.g.n.n.n.n.> : > > S c c S 2 SX#X#X@X#X#XBX] m.m.m.m.B.m.Y.SX).#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X+XS.3.8.=.i.8.i.i.6.!.BXmXZX_ aXdXpXkXkXuXuXzXuXuXuXxXzXzXuXuX4XqXfXfXjXD ZXSX#X#X#X#X#X#X#X#X#X#X7XS.K 8.i.8.=.+.Z.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X% _.#XSX#X#XmX_.#XmX_.#XmX_._.SXSXmX_.mX_.mXSXSX#XSXSX$XDXDX1X` DX$XDX$XSXSXDX$XDXDX$XSX$X$XSXSXSXSXSXSXSX`.o $XSXSXSXDXDXSXSX$XDXDX`.SX$XDX` DXSXSXDX5 FXDXDXFXFXFXDXDXFXFX(.6 2 ( HXJX2 GXGXHX/ FX5 DX@ ' @ FXFX' 6 6 OX6 5 FX/ 3 3 ' FX8 FX2 ' 5 DXDXDXFXFX5 5 FXFXFX5 5 FX6 / 6 ' ' 6 FX(.6 2 2 2 6 ' FX' 6 6 2 S 4 4 S JX2 4 y S 4 4 6 GX6 2 HXGX(.6 ( 3 3 HX2 3 ) S u 2 6 6 FX/ ) 4 ( GX6 ' 5 ` 5 3 u ( 6 6 6 / 6 ' 6 6 / 6 6 6 m V.B.S.S.S.H.C.m.b.m.B.S.m.b.b.b.n.c.g.n.c.n.c : 4 M M > S u u 2 ' 6 @ #X@X@X7XY.0.m.B.m.m.S.SXVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X7XDX8.7.8.7.8.8.8.i.@.C.BX#X_.AX_ _ SXtXuXkXpXuXuXzXuXzXfXfXxXfXuXfXsXfXpXhX_ D mX#X#X#X#X#X#X#X#XVX#Xs.8.8.i.8.8.3.S.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#XmX#X#XSX#XmX#X_.#XSX#X#XSX#X_._.SXSX_.mX_.mXSXSX_.SXSX$XDX(.FX(.FXDX$X$X$X`.DXDXDX$XDXDXSXSXSX$XSXSX$X$X] SXSX$X`.`.SXSX$XDXDXDXSX$XDX` DX$XSXDXDXDXDXDX$XDX5 FX`.DXFXDX@ ` @ DXFXHXJXHXHXXXGX6 FXFX/ GXFX6 FXFXOXFXFXFXFXGX6 HX( 6 5 FX2 3 DXDX8 SXSX5 ' 5 FXDX5 FX5 FX5 FX6 ' FX5 ' 6 6 GXFX' 6 FX5 6 2 ( 4 4 6 6 3 3 6 OX3 4 ) KX1 ) 6 2 3 ( 3 GX2 3 3 HX/ GXGX( & 4 ) 2 2 3 S q A HX6 6 5 DXSX6 S 4 GXFX/ 2 / 2 2 2 2 / 6 ' GX# ( V.B.S.S.S.H.S.m.m.m.B.V.b.b.n.n.n.n.n.n.c.c 2 M M 4 M 2 @ ' 6 6 2 3 2 6 ` +X`.w.m.m.B.m.m.`.7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XCXZ.3.i.8.7.7.8.8.i.@.s.#X@X#X#X#X#X_ _ D dXpXkXpXpXfXuXfXzXfXxXxXfXsXsXsXuXhXD _ #X#X#X#X#X#X#X#X7XDX7.7.i.i.8.K +.m.7XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#XSX#X_.mX#X_.#XmX#X#X_.mX#X#XSXmX_.mXSXSX_.SX_.SX$XSX$XSX_.SX$XDXDX(.FX@ ` DX$XSX$X`.o DXDXSXSXDX`.SXSXSXSX$XSXSX$XDX$XSXSX`.DX`.`.SXDXDXDX`.o ] DXFXDXDXDXSX`.DXOXDX`.5 FX' FXDXDXDX` 2 JX4 HXGXHXHXHX3 GXFXFXDX' FXDX# 6 GXGX6 GXFXFXGX/ ( ( DXDXSX5 ( 2 5 5 DX` 6 GX5 5 FX' ' FX5 6 FX' 6 6 5 ' 6 5 (.@ GX3 3 2 5 (.6 ' 2 3 4 4 3 ( 3 u u y S JXS 2 HX2 ( # GX' FX3 ) HX2 FXGX3 3 S 3 2 2 2 FX' 2 4 HX2 2 / 6 5 ' 6 3 ) HX6 6 6 GXFX@ ;.B.B.S.S.H.C.S.m.m.B.S.w.n.b.n.n.n.n.c.w.3 M u c 2 FXFXFX6 6 6 6 / 3 S M DXo S.s.m.m.B.s.R.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X+XC.3.8.8.&.7.8.8.8.i.8.^ BX#X#X#X#X#X#XD _ yXsXqXpXpX0XuXqXfXfXfXxXfXfXsXfXuXdXD _ mX_.#X#X#X#X#XBX^ +.8.i.8.i.8.+.b.VX).#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X% _.mX#X_.#XSX#XSX#X_.mX_.#XmX_.#XSXmXSXSX_.SXSX_.SXSXSX_.SX$XDX1X(.FXFXDX$XDX$XSX`.DX$X`.FXDX$X$X`.SXSXSXSXSX$XSXSX$XDX$XDXSXSXDXDXDXSX$X`.DX(.DXSX$X8 DXFX] DXDX(.' GXFXFXFXFXFXFX2 ( HX3 ) JXJXJXHX' FXGXHX6 FX6 FXFXGXFXFX@ ' 2 GXHXHXFXDXFX3 A 3 DX5 DX5 / 2 ' 5 FX5 5 FXFX' FX6 6 GX6 ' 5 6 GXN GXFX/ 6 FXFX6 2 3 3 M 3 GX' 5 6 u 1 JXJXKXu GX2 ( 2 3 2 GX3 ( 2 ( GXGXGXHX3 3 3 3 ( 3 4 1 4 ' 2 ( 6 FXDXFX2 HX( / 6 ' 6 6 / 6 2 B.B.B.V.S.H.H.V.m.B.V.Y.w.n.b.b.n.c.n.2 = q y 4 5 5 5 6 ' 6 / 2 2 2 2 2 2 6 A w.B.m.B.m.b.W.CX@XVX_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X+Xm.&.0.8.7.&.7.=.8.8.+.L.+X#X#X#X#X#X#X#X#X_ _ ).iXuXpXpX0XuXfXuXfXxXxXfXsXsXuXgX)._ mX#X#X#X#X#XCXZ.3.8.p.i.8.8.7.T $X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X% #X_.#XSX#X_.#X#X_.#XmX#XSX#X#XmX_.#XmX#XSX#XSX_.SXmXSXmX_.SXmXSX$XSXSXSXSX$XDX(.FXFXFXFXDXSXo $XDXDXDXDX$X8 @ `.SX$XSX$XSXSXSX`.8 `.SXSX`.DX`.] $XDX] DXDXDX] DXDXFXDXSXDXDXFX@ ` DXFXGXFXDX(.FXFXFX5 GX2 HXJX4 / HXJXJX2 GX` DXFXFXFX' ' GX/ / GXGX/ 6 2 3 ) ( FX5 DX6 3 GXFX5 ` 8 GX2 ' FX6 FX5 ' 6 ' 6 6 / GX6 FX6 6 FXDXFXHX2 2 GX6 ' 6 6 2 S 4 4 ) ( 4 2 1 A JX4 3 / 2 FX2 4 3 GXFX2 2 HXJXFXGX) 4 4 3 2 ( 2 ' 5 DX6 3 HXFXFX6 FXFX6 ' 6 # Z.m.B.V.H.S.H.H.S.m.m.;.> n.T c.c.b.( SX4 r q 6 6 FX` DXDX6 2 2 / / FX6 ' 6 m.s.B.m.m.B.b.C.@ / SX7X@X#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XBXB.+.i.8.8.7.7.8.8.8.+.B.7XVX#X#X#X#X#X#X#X#XAXD _ ).rXuXpX0XqXuXfXfXfXxXxXfXsXuXgXD _ SX#X#X#X#XBXs.| K i.i.8.8.7.8.` BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X_.#XmX#X#X#XmX#X_.% #X_.#X#X#XSX_.SX_.#XSX_.mX_.SXSX_.SXSXSXmX_.$XSX$X$XSX$XSXSX$XDX(.FXFX' FX` DX$X`.DXDXDXDX` `.SXSXDX`.SXSX$Xo $XSXSX`.DX$Xo SXSXDXDXDXDXSXDXDXDXDXDX] SX(.DXDXDXDXDX5 6 FXDXDX(.FXFX5 `.DX6 JXJX3 JX3 HX2 GX2 ( GXFX6 FXFX2 GXFX/ GXGX/ HXHX2 6 FX' JX3 5 5 DXSX5 GX6 5 5 5 5 ' 5 @ FX6 6 6 6 6 6 6 6 6 GX2 ' ' 6 ' FX6 / ( u 4 3 3 3 4 3 3 4 S 3 JXu 2 6 6 3 HX2 2 2 HX2 4 2 DXGX2 3 4 S 4 3 HX2 6 u A 3 6 6 5 5 DX8 FX/ 4 GXo ;.m.B.S.S.S.H.C.V.m.S.6 3 A Y.S 3 2 ( S S 2 2 HX2 / 5 5 6 ( ( GX2 FX' 5 2 w.w.m.m.B.m.B.m.c 1 c 6 #X@X@X#XAX@XVX#X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XVXVX9.+.=.i.i.8.3.7.=.8.8.7.P +X#X#X#X#X#X#X#X#X#X#X#XD _ SXrXuXpX0XuXuXfXxXxXfXfXsXuXdXD ).#X#X#XBX(.+.&.8.i.i.i.K 7.7.!.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X_.#XSX#X_.#XmX_.#XmX_.mX_.#X#XSX#X_.mX_.#XmXSX#XSX#XSXSXSX$X$XSXSXSXSX$XSXSXSXSX$XDXFXFXFXOXFXDX$X`.DX`.DXDXDX$XDX(.DX$X8 ] SXSXSX$XDX`.8 SXSXSXDXDXDX$X8 `.DXDX`.DXDXDXFXFXDXDXDXDXDXFXFXDX` FXFXGXFXDXFXFXFXHX) 4 KXKX( JXJX4 / GX6 ` FXGXFXGX2 6 GX2 GXGXGX6 GX/ JX( ` DX8 DXu 4 ` 5 5 5 FX2 / ` 5 GX/ ' FXFX6 6 / / 6 / = / 6 DX' 2 3 3 3 2 ' ' 6 2 ( ( 3 S 3 JXKXu KXy u GX3 ( ( 2 2 ) 2 / # ' HXJX) 4 4 JX3 4 A 1 2 / 6 5 DXDX` DX4 S FX5 FXV.s.B.V.S.S.C.H.B.B.Z.# # o @ 2 2 3 2 DX' 3 ( 3 4 ) ' 2 JX6 6 FX5 DX8 2 w.b.B.m.m.m.m.m.V.> u A 1 > DX@X#XDXSX@X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X#Xq.+.=.=.+.H.V.3.7.7.=.+.;.CX#X#X#X#X#X#X#X#X#X#X#X#X#X_ _ #XsXpX0XuXfXfXxXxXzXfXfXuXjX_ #X#X#X+XZ.| 8.8.8.i.8.8.K +.Z.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#XmX#X#X#XmX_.#XmX_.#X#X#XmX#XmX_.mX_.SX#XmX_.SXSX_.SXmX_.SXSXSXSXSX$XSXSXSX$X$XSXSXSX$XDX(.GXGXGXFXFXFXo SX`.DXDXDXFXDX`.DXDX`.SXSXSX`.SX$XSXDXDXDXDX] SX$XDXDXDXo _.DXFXFXDX] SX`.` FXDX8 DX@ FXGXGXFX6 6 GX' # ( JX( JX3 S KXKXHXGXHX2 ( 6 / / 6 FXGXGXDX6 6 / 2 GXHX3 FX5 5 ) q 4 5 FX` DXFX3 2 ` 5 6 FX6 6 DXFX/ 2 6 6 6 6 6 6 6 GX/ m GX6 6 FX6 6 / m 2 3 4 3 ' ' 3 KXr S 6 KX4 2 6 GX2 HX2 6 OX' HX( 4 3 ( 3 ) 4 ) ) ( 2 FXGXu u 4 4 HX' 6 8 Z.m.m.V.S.H.S.C.H.B.;.6 6 6 / 6 6 GX2 3 ) ( HX( 3 4 3 4 4 S S 3 4 3 DX3 b.b.w.B.m.m.m.m.m.S > u c q A 3 4 c 1 2 SX@XBX@X#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAXBXB.3.7.7.7.i.' s.+.7.7.+.s.@XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ D rXpX0X0XuXfXfXxXxXxXfXfXyX_ mX#X+XH.| 7.7.8.8.i.8.=.+.Y.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X_.#XSX#X_.#XmX_.mX#X_.mX_.#X_.SX#XmX_.mX_.SXSX#XmXSX_.#XSX$XSXSXSXSX$X_.SX$XSX$X$X$X`.$X$XDXFXFXGX' GX' DXDXDXDXDX5 DXDXDXFXDXSX$X8 $XSXSXDX@ `.DXDX$XSXDXDXDXDX] SXDX5 DX] DXDX5 FXDXDX`.DX' FX5 DX` ' GXFXFXFXFXFX5 FX2 HX3 KXu HX( JX4 3 GX5 DX` / 6 FXGX/ 2 HX2 / 2 GX2 GX2 S ) ' 6 ` 5 ( 3 / FX5 5 DXm HX5 6 N / 6 ' FX' 6 2 HX/ @ GXGX6 6 ' 2 ( 3 3 3 FXFX6 5 ' 5 N 4 KX4 KXu 3 4 q ( 4 JX/ 2 HX3 3 3 HXGXGX2 JX4 S 4 ( 3 4 1 q q 3 2 GX# DXSX;.s.B.V.S.S.C.C.S.Y.GX2 : / 6 GXGX2 ( 2 FXFXDXFX/ 2 ) c q q A c 1 3 A.b.b.b.m.m.m.m.m.s.V.> > u 1 c u u u c q A 4 / DX@X XBX@X#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X_.@X. K.| &.=.8.3.=.b.+.=.7.7.3./ +XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#XD _ tX0XpX0XuXuXfXxXxXxXxXfX1X_ @XVXT 3.7.=.K 8.i.8.8.+.B.BX#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X_.mX#X#X#XmX_.#X#XSX_.mX#X#XSX#X#X#X_.SX#XSX#X_.SXSXSXSX#XSXSX_.mXSX$XSXSX$X$X$X$X8 SXSXSXSXSX$X`.DXOXGXGXFXGXFX`.$XDXDXDXDXFXDXSXSX(.DXSXSXDX`.SX$XSXSXDXDXDX8 SXSXDXDX` DXDXDX5 FX` DXDXSXDXDXDX` DXDXDXFXGXDXDXFXFXFX6 FXDX(.6 4 4 ) JXHXHX/ XXGX) 3 GX6 / 6 GXGX' FXOX' GXFXGX) 3 GX' 5 ( S 2 FX5 DXDX` GXGX5 FX6 5 6 6 6 ' 6 GX6 6 6 6 6 2 2 6 6 2 2 / 6 5 FX' FX2 2 ) 2 S 1 M 2 ) u 1 4 JX4 3 6 6 6 2 GX2 FX$XOXHX) & 2 2 JX) ) 4 4 HX2 2 6 FX2 3 Y.m.B.V.S.H.C.H.C.3 3 HX6 GX2 2 2 2 2 2 / FX3 4 4 u q q A q 1 > S u w.b.n.m.B.m.w.m.m.b.S.u c 1 u S S S S c c q q q 3 ' 3 FX+X+XVX#XSX#X#X#X#X#X#X#X#X#XSXBXVXY.T &.&.&.=.8.3.5.7.7.&.7.| S.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX_ _.rXpX0X0XuXfXxXfXxXzXsXSX. (.&.+.=.&.=.8.i.K 7.+.m.7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X_.mX_.#X#XSX#X_.mX_.SX#X#XSX#XSX#XSX#X#XSXmX_.mXSX_.SX_.SXSX_.SXSX$XSX$X] SXSX$XSXSX$X$X$XSX$XSXDX(.FXGXGXGXFXDXFX(.`.DXDX` `.DX6 (.`.DXDXDXSXSXSX`.DXDX] $XSX`.DXDXDX`.DXDX(.DXDXDXDXDXFXFXDXDXDXDXDX' 6 ` ` 6 6 / GX5 FX@ ` FX3 4 KXKXKX4 4 HXKX4 FXGX6 DX/ GXFXFX2 GX2 HX2 GX2 2 6 GX2 S 2 DX` 8 DX6 2 / 5 5 5 FX( 3 ' FX6 6 / GX6 6 6 ' GX/ FX6 6 6 6 ' 6 6 2 ) S 4 ( 2 3 / ' 6 3 4 4 3 KXu 3 GX3 3 OX3 2 2 2 2 3 ( # / 2 2 3 4 JX( 3 3 3 2 1 * HXS.m.B.S.S.S.C.H.Y.& ' 6 2 2 2 2 HX( 2 2 4 q q q q q q q q q q * e.b.b.n.q.w.m.s.w.m.w.m.q * c S S u u > S u c 1 A q q q q S @ _.@XAX#X@X@XAX#X#X#X#X#X@X#Xq.| 3.=.&.&.7.7.5.u.&.7.&.=.3.q.7XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ _ iX0XpX0X4XfXsXfXxXzXdXCX] 3.&.7.7.7.7.8.8.7.+.9.% @X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#XmX#X#X#XmX_.#XmX#X_.mXSXSXSX_.#X#XSX_.#XSXSXSX#X_.SX_.mXSXSXSX#XSXSXSXSXSXSX$XSX$X$X$XSXSXSX$X$XDX$XDXDX(.' GX2 GXGXGXDX`.DX@ DXFX' FXDXDX` ` `.$XSX$X8 `.SXDXFXDXDXDX`.SX`.DX@ DXDXDXDXDXFXFXDX`.8 DX6 FX`.DXDX' GX2 GX' GXGXGXFX6 4 S 3 KXKXS 4 KXu 2 / GX( 3 2 6 2 / 6 2 / FX6 GX6 ( HX( KX2 5 DX` M y 1 5 FX5 ` 6 3 HX6 6 6 6 6 6 ' FX/ FX5 6 6 6 FX' 6 / 6 FXGXHX( 2 2 / 6 6 5 5 2 u S 4 S HXHX( y q 3 u ) 3 ( 2 3 3 HX( GXFX2 JX3 4 ( HX) u 1 ) # A.s.B.B.S.H.S.C.;.GX' 6 6 6 GX2 3 3 2 4 A c 1 c A q q A q q q q e.b.b.w.b.n.m.m.w.b.b.b.m.1 u S c > S c u A q q c u c c q y y q GX+X@XFXFX#X+X@XAX_.#XCXY.| *.&.&.&.&.&.&.5.4.*.&.&.*.*.*.DX+X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX_ _.eXpX0XuXfXfXsXfXzXiXW 3.7.&.7.&.7.8.8.8.&.T $X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X_.#XSX#X_.#XmX#XSX#XSX_._.mXSXSXSX#X#XmX_.mX_.#XSXSX#XSXSXSX#XSX$XSX_.SXSX$XSXSX$X$XSXSXo `.$XDX$XDXDX$XSXo FXFXGXGX2 GXFXGXDXDXDX(.@ DXDXFXFXDX] SXDX`.SXSXDXDXDXDXDX] o DX` DXDX8 DXDXDXFX` DXDXDXFX' ` 8 DXFX# ' FX5 5 GX2 # FXFXGXGX6 ' 2 JXS KX1 4 JXHXKXS 3 HXFX5 ` 2 GX' GX6 / HX3 3 3 2 / 2 HXS q 1 6 6 DX5 3 JX2 6 6 ' 5 FX6 ' FX6 6 6 6 / 6 FX` 5 / GXGX2 / 6 GX6 6 6 6 6 2 4 4 ) u S GXFX3 S u JX( u 3 6 6 2 3 ) 3 2 GXHX3 3 S GXOX3 ) ) HX3 2 Y.m.B.B.S.S.S.C.2 # GX5 ' GX2 3 4 4 1 c u u c * A c * c 2 / 2 c n.b.w.b.n.n.b.m.b.w.b.n.q u u c q r r 1 * A * c u u u u 1 c q q ) 3 q q u 2 $X@XBX7X#XT t.*.&.&.*.*.*.&.*.u.4.*.&.*.*.| Y.CX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ D rX0X0X0XuXfXsXfXfXv.+.&.=.7.*.&.K 8.7.7.T DX7X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X% #X#X#XmX#X_.#X#X_.SX#XmX_.SXSXmXSX_.#XSX_.mX_.SX_.SX_.SXSXSX$XSXSX#XSX$XSXSXSXSXSXSX`.DX$XSXSXSX$X$X] DX$XSX$XDXGXGXGX/ GXGXDX`.DXFXDXDXFXFXDXSXDX6 DXSX`.DX`.DX`.SXSXDXDXDXDXSXSXDX` 5 ` DXDXDX5 FX` DXDX8 FXFXDX` FX` FXGXGX' FXFXFX6 6 6 FXFXFX) u 4 S JXHX) JX/ GX) JXHX6 / GX6 3 2 ' 2 ( HX( 3 3 4 3 / 6 5 / u ) 6 GX5 DX5 5 ( 2 6 6 6 6 FX5 FX' 6 ' # 2 2 2 6 GX/ 6 5 GX2 JX4 4 3 6 6 / 3 3 ( 4 3 ) HXKX1 ( q r JXJX3 ( 3 3 3 4 JXGX2 2 3 4 KXu 3 GXY.m.B.V.S.S.B.C.m @ DX5 ( HXJXu u u u c 1 1 c u c 1 2 (.DX$X( b.b.b.w.b.w.m.b.b.b.b.q.b.c u 1 r k q c c c 1 u S c c c u 1 q q y q c c q y q 1 6 CXDXl.*.7.7.7.5.*.*.*.....u.E *.*.&.U T VX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.D _ yXuXpX0X0XqXxXfXf.6.9.&.8.&.&.7.7.7.7.7.(.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X_.#X_.mX_.#X#XmX_.mX#X#X_.#XmX_.SXSX$XSXSX#XSX#XSXmX_.#XSXSXmX_.SXSX_.SX$XSXSX_.SX$XSXSX$X$XSXSX$X`.SXo DXDXSX$X$XSXDX(.FXHXHXHX2 FXGX' `.DXFXFX(.DXFX/ @ DXDXDXDX$XSX8 DXDX` DXSX`.DXDXDX(.DXDXDXDX(.5 DXDXDXDX6 GX5 DX5 (.@ GXGX6 ` FXGXGX' FXFXFXFX' HX4 u u u 1 ) 4 JX1 4 3 2 / 5 FX) # DXHX( 2 2 3 JX( GX2 / 6 3 1 ) ' 5 ` 8 DX6 3 6 6 6 GX6 6 6 ' 5 2 6 6 / / 2 / 6 6 ' FX2 3 ) ( / / 6 ' 6 2 2 / 3 u u 4 2 4 u y r 2 ( KX3 2 FX5 ) HX/ 2 ' XXJX4 4 HXGX+ S.m.B.B.S.S.B.H.6 @ 3 4 4 4 S 4 4 S u c 1 u A q 3 GXGXGX# ) b.b.b.b.b.m.w.b.b.b.b.n.q.e.: u r r q q A 1 S c * 1 u c q y q A c u u c c 1 c q q 4 V.&.7.7.7.i.7.5.*.E E ....u...*.*.*.5.Z.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ ).rX0X0X0XqXsXv.p.G 7.8.&.*.7.7.7.&.&.^ +X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X_.mX_.#X#XSX#XSXSX_.mX#X_.SXSX$X$XSX_.SX_.SXmXSXSXSX_.SXmXSXSX$X$XmXSX$XSX_.SX$XSXSX$X`.DX$XSX$X$X$X`.DXDXDXDXDXDX6 HXHXHXHX2 FXDXDX` FXFXFX6 ' DXDX5 FXDXo $XDXDXDXSX`.6 FXDXDXDX] SXDXDX5 ` DX$X5 FX' FX` DX8 DX' FX` ` DXFXGX2 2 GXGXGX2 2 FX3 A u 1 q KXS u u 4 HXJX2 2 ( ( / 2 3 2 2 3 3 GXGX/ 2 3 3 ) u 3 5 ` 5 ' 4 q ( FX' 5 5 6 ( 3 2 FX6 2 2 6 6 6 6 FX6 2 2 2 / 2 6 6 6 6 6 GX2 2 3 4 4 3 2 6 FX3 1 S JXHXS u 2 3 q 3 3 ) HX# FXHX3 3 2 HX2 S m.B.B.B.V.S.B.H.A.y 1 2 3 M M 3 3 2 3 : y 0 1 ' GX2 2 2 3 w.9.b.b.w.b.f.v.m.b.n.n.q.n.c u r y y y * c * c q q y y y y y * u u c 1 u u u c 1 q &.9.p.i.3.*.&...*.u.R u...E E ..*.*.| w.BXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ _ 1XqX0X4XqXg.@.8.8.8.7.5.&.7.7.7.3.Z.+X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#XSX#XSX#X#X#XmX#XSX_.#X#X#XSXSXSXSX_.SXSX$XSXmXSX#X_.SXSX_.#XSXSX_.SXSXSXSXSX$XSXSXSX`.SXSXSXSX$X] $XSXDXDXDXDX$XDXDX`.SX$X(.FXGXHX3 HXFXGXFXDX5 DXFXFX@ ` GXFX`.8 SX` DXSXSXDXFXDXDXDXDXSXDXDX5 DXDXDXDXDXFXFX5 DXDX` FX6 FXDXDXDX6 / GXGXFX6 2 2 GX6 2 JXGXHX/ 3 S q 1 A ) 3 KXS u 3 3 2 2 FXFX( 2 FX2 2 HX) JX3 4 ) 6 ` 2 4 q y 3 5 6 FX` ' 3 JX2 ' 6 GX/ FX` 5 6 ' 2 ( 2 GX6 2 6 GX6 FXGX( M 3 ( 2 DXFX2 6 FX2 S u u u JXJX3 u r KX4 u HX2 ( ( 3 3 JX4 # Z.m.V.B.B.B.B.V.B.m.A & 3 S 4 3 2 2 3 FX3 0 r 6 6 GX2 HX6 # V.n.b.b.b.b.n.v.n.b.n.n.n.c.b.q 1 c * A A A q q q k k y y q q A c c c u A 1 c q * A 7.0.9.&.O.u.3.3.&.&...u.....u.E ..*.U b.@XVX#X#X#X#XmX#X).AXmX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_ $XuX4X4Xk @.7.=.8.8.3.E 7.7.&.3.Y.+X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#XSX#X_.#X#XSX#XSX#XSX_.mX_.mXSX$X$X$X`.$XSXSXmX_.mXSXSX_.mXSX$XSXSXSX$XSX_.SXo SXSXSX$Xo $XDXDXDX$XSX$X$XSX$XDXDXDXSXDXDXGXGXGX2 HXHXFXDX`.` FX` FX' 6 DXSXDXGXDX] DXDXDXDXDX8 $XDXDX` (.DX] $XDX` FXFXDXDX5 FXGX' 5 DXDXDX6 # 5 5 DX5 GX2 GXFXFXFX@ ' 6 2 GX2 JXq q 4 S oXu S ( GX' FX2 HXGX/ 2 ( 3 3 3 3 HX3 ( 3 3 2 u A 3 6 6 5 5 2 4 ( 2 6 ' 6 5 6 : 2 6 6 6 GX/ 6 6 GX6 / 2 GX/ 6 HX/ 6 / 6 6 6 ' 3 4 4 ) S 1 3 6 3 3 3 u 3 KXu / 6 6 / HXGX3 FX/ B.m.V.m.m.B.B.B.B.B.Y.JX4 S 3 2 A y u q 0 4 FXGXGXFX6 3 4 u w.n.b.b.b.b.b.n.b.b.n.n.c.c.m.* q q q q y q A y k y y r y q c q r y y y q q q u u q.7.+.q.S.*.s.H.&.7.8.*.u.....E 5.5.| c.@XVX#X#X#X#X_.D D _ ).mX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X. iX4XqXg.@.8.8.8.8.7.*.7.&.&.3.B.BX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#XSX#X_.mX#X#XmX_.mX#X#XSX_.#XmX_.SXSX_.SXSXSX$X$X$X$XSXSX_.SXmX_._.SXSXSX_.SXSXSXSX$XSX$XSXSX`.SXSXSX$X$XSXSX`.DX$X`.$XDX`.DXDX`.`.@ FXGXHX3 ( # FXHX6 DXDXDX' FXDX6 / FXDXDX` ` `.] $X8 DX5 DXDXSXSXDX` @ 5 ` DXDX5 FXFX5 ` DXDXFX' FXFXFXFXDX5 /.2 GX` GX2 GX2 6 FXFXFX# 4 u 4 S KX1 JX4 HXS A ) HX/ 3 / GX3 / FXFX( 3 3 GX2 4 KX2 6 ' ' 3 4 3 6 6 # 5 5 ' 3 S 2 FX' 6 2 2 / 6 FX6 / 2 ( 6 ' GX2 2 6 ` 6 / 3 4 ) ( 2 FXGX( 4 3 ) 4 3 JXu u KXHXy r HXHXJXHX2 S.w.m.B.B.m.m.B.B.m.b.Y.4 q q y r k k k y 2 ( 3 GX' / & JXA m.n.n.n.b.n.n.g.c.b.b.n.n.c.n.q q y y q q y q q q q q q q q q c q k k q c A A 1 > m.7.&.=.B.=.i.-.8.7.i.&.R E *...t.t.O.=.#X@X#X#X#X#XZXaXfXdXAX).#X#X#X#X#X#X#X#X#X#X#X#X_.mXBX@X] rXJ.7.&.7.=.=.8.7.7.7.&.&.3.w.7XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X% #X#X_.mX_.#X_.mX_.SX#XmX_._.mX_.mX_.SXSXSXSXo `.DXSXSX_.SX#XSX$XSXmXSXSXSXSX$XSXSXSXSX$X$XSXSX$XSX] $X`.DXDX$XSXSX$XDXDXDXDX@ DX`.`.DXGXHXJXHX3 3 FXDXFXFXFXOX' GXGX` DXFXFXDXDX] DXDXDXDXSXDXFX5 5 FX` DX8 DXFXFXFXDXDXDXDX' GXFX` DX5 DX6 2 ' @ 5 / 2 ( ) GX6 / (.6 6 4 A A q y 1 u KXHXq S OX2 2 FX2 ( 2 / HX3 ( 3 3 M HX2 3 2 / 2 4 1 ) 2 / ' 5 FX6 3 4 / ' 6 6 6 6 6 6 ' 2 GX6 2 6 6 6 6 2 FX$XFXGXHX4 3 2 / DX' 2 2 2 2 4 u S KX4 u S k 0 2 JXu JX= Y.s.w.w.m.m.m.m.m.m.b.b.e.q r r r r k 0 u GXJX( / 3 S O Y.T.m.b.b.q.n.n.n.c.f.v.n.n.q.c.c.b.q q q q q q q q q A c q q A c u S r r q y c u u 3 Y.@.7.&.3.7.=.&.=.8.i.8.5.u...5.t.t.O...`.. @X@XAX#X_ dXkXlXjX_ SX#X#X#X#X#X#X#X#X#X#X#X#X+X` =.7.p.8.3.7.7.=.7.=.7.7.7.+.&.3.b.7XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X% _.% #X_.#XSX#X#XmX#XmX_.#X#XmX#XSXmX_.SX_.SX$XSX$X_.SXSX$X`.`.$XSXSXSXSXSX_.SX$XSX_.SXSX$X$XSX$X`.SX_.$XDX$XSXSX$X`.`.DXDXDX`.DXDX$X$XDXDX$XDXDXDXGX( 3 ) 3 GXHX6 DXFX@ ' GXFX5 GX/ DX8 `.5 ` DX] $X' 6 DX(.5 DXSX`.` FXFX` DXDX` DXFXFX5 DXDXDXFX2 GXDXDX` 2 3 3 2 GX2 ( HX2 ' HXq 1 u S u S 1 KXy 1 3 ) GXu u 4 3 2 ) 2 2 HX2 / # ( 2 ) 3 XXHXu 4 2 2 6 ' ' 3 S HX6 GX6 ' 5 6 HX6 ' 6 6 / / 2 2 6 5 ' 6 / 2 2 2 ( / / 6 6 2 ( 2 3 3 ) S 4 4 ( GX3 1 1 ( JXKXu + ;.b.m.w.b.w.w.m.w.b.w.b.b.e.y y y k 0 k 1 4 S 3 ) q r q b.T.B.B.b.n.q.c.c.c.c.l.v.c.g.c.c.v.w.q q A c c c c q q q q q q q c S q y A r r * c 1 q b.8.8.7.&.7.=.8.8.8.=.&.5.E u.4.E R t.e.3 S / @X+XCXrXpXuXgX_ ).#X#X#X#X#X#X#X#X#X#X#X@X#X=.| *.8.i.+.*.&.&.=.7.7.*.&.&.&.3.n.CXZXmX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X_.#X#X#X_.mX#X#X#XSX#X_.#XmX#XSX_.mX_._.mXSXmXSXmX_.SXSXmXSXSX$XDXDX`.SXSXSX#XSXSX$XSX_.SX$XSXSX$X$XSXSXSX`.DXSXSX$XDXo o DXDXDXDXSXSX`.8 DXDXDXDXDX$XSXDX' GXJX) ( JX6 DX`.FX6 FX(.GXGX5 `.SXGX6 DX8 DXDXDX` @ DX] DX5 @ 5 DXDXDXDX5 FX' FXFXDXDX' / GXFXDXDXFXFXGX6 FX5 FX3 3 / FXDXGXHX2 GX6 S q 1 q y u u JXu q 1 ( GX2 5 FXHXGX6 / HXHX3 4 3 4 ) ) 4 2 6 6 2 1 1 5 FX6 5 5 FX6 3 / 6 GX6 6 FX6 ' 6 ' 2 2 / HX2 / GX2 / # ' FXGX3 ) 3 ( 2 2 2 / FX6 ) S u 4 JX4 JXS m.B.S.b.b.m.b.b.b.b.b.n.9.m.y r r r r y y r r y r r e.T.b.T.g.v.n.c.c.c.c.v.v.l.v.c.c.c.v.n.A * q q c q q y y y r y y r y q q A q q y y q y * * f.@.8.8.7.7.7.7.=.=.=.*...*.u.R E t.v.* q y u / _ iXpXuXkXjX. VX#XAXD _ _ _ D AX).AXCXZ.| 3.*.=.q.&.&.+.*.&.&.=...5.&.&.+.l.jXCX).#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#XSX#XmX_.#XSX#X#X#XmX_.#X#X#XSX_.mX#X_.SXSX_.#XSXmXSXSXSXSXSXSX$Xo DXDX`.SXSXSXSXSXSX`.o SXSXSX$XSXSX$XDX$XSXSXSX`.SXSX`.o $X$X$XDXDX`.DXDXDXDXDXDX`.DXDX(.2 4 3 JX4 HX6 JX6 DXFXFXGXFXDXFX( 2 DXDXDXFX` DX`.8 DX5 (.5 (.DXSXSX5 FXFXFX5 DX5 FXFX6 FX5 DX8 5 (.FXFX' ' FX' ( 3 GXFX' GX2 2 / 3 3 6 / 1 1 1 1 KXHX4 S 4 GX2 S S 3 HX2 m 2 3 ) 3 2 3 JXJX3 6 3 ) u u GX5 5 ` DXDX/ 3 2 6 6 6 5 / 6 6 FX6 2 / 6 2 / 2 6 6 / 2 GXGX/ 3 / FX2 2 2 2 6 6 2 4 u u u 1 JXO KXV.m.V.V.V.H.m.b.b.b.b.b.n.n.m.r r r r r r r k k k r A T.m.B.m.c.c.c.c.v.l.l.c.v.l.v.v.v.c.q.e.c c q q q q q y y q y q q y y y q q q A q y q q q k u.S.7.*.7.&.&.&.&.=.&.*.&...4.R :.v.> c c q y c X xXpXpXhX] _.@XmXyXaXaXaX$XAX#XAX. C.5.*.5.5.l.*.7.&.3.*.*.&.&.u.*.+.&.=.xXrX_ D mX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X_.mX#X#X#XSX#X_.mX#XSX#X#XSX_.#XSXSX_.SXSXSXSX_.SX_.SX$X$X$XSX$XDXDX`.$XSXSX#XSXSXSXSXSXSX$XSXSX$X8 $XSXSXDX`.SXSXDX8 `.SXSX@ DXSX$XDXDXDXDX` @ DXFXFXDXDX' HX( 4 3 4 ) FXDX` 6 FX' GX2 2 FXDX` 6 6 DXDXDXDXDX` 5 DX] FXGXFX` 5 5 DXDXFX5 ' FX` DX5 FXGX2 GX' FX6 FX5 HX( 2 6 6 HX2 GX2 FXFXGXGXGX) 1 1 A KXq u u 1 A y u ( HX( 2 FX2 4 FXDXJX3 2 ( ) ( JXu S 2 FX5 DX6 q q ( 6 ' FX5 6 3 3 ( ' FXGX2 2 GX6 / GX6 GX/ 2 2 2 HX6 / 2 GX2 / / ) 4 4 ) 3 S S ) 4 u V.m.m.B.V.V.V.m.b.b.n.n.n.n.c.w.y r r r y y y r r k I.B.m.V.Y.C.m.c.n.w.c.l.l.v.c.v.v.c.v.v.c.r y q q A q q c q q q q q q c q q q q y y y y y q q d.*.Y O.+.&.*.3.*.7.&.*.&.4.X.} ..u 4 u u u c q c c c u c u S c q q j S 1 c q q A q dXsXfXpXkXkXkXzXtXB.3.9.S.T 9.7.i.7.3.*.*.....E &.7.+. S S * q q q c c c u c S 4 x x u ;XgXxXfXsXzXpXgX9.+.| 9.9.7.p.*.4.5.5.5...Y ..&.K .>.>.f.u y r y q r q y q q A 1 u c 1 S > 3 HX3 c j x 3 ( : hXsXzXd.@.&.&.&.8.=...4...&.*.&.d.E *.+.l wXsXfXdXD D #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X_.mX#XSX#XSX_.SXSX_.mX#XmX#XSX_.mXSXmX_.mXSXSX_.SXSX$X$XSXSXSX$X$XDX$XSXSXDXDXDXDX$X$XSXSXSXSX$X$XSXSXDXSX$X8 `.] SX`.DXDXSXSXDXDX$X8 DXDXFX(.DXSX$X`.DXDXFXFXFXFX' FXGX( KX4 u 4 4 S # ` DXFXGX' 6 2 3 ( FX5 ` ' GXDXDXDXDXFXFX5 FX5 8 SXDXFXFXFX' FX` 5 ' 6 6 6 6 5 DX` 6 GXGXFX/ # ' ' 3 JXHXGX6 GXGX2 2 u HXFXHXu y 1 A KXKXq u S 4 2 ' FX/ 4 3 2 2 JX3 3 2 3 JX( ) 3 2 6 2 4 q 4 6 / FX5 DX' 2 4 3 FX6 6 6 6 6 / # 6 GX/ HXHX3 2 GX2 HX3 2 FX(.( 3 ) u 1 1 A m.m.b.b.n.q.c.v.d.f.c.n.n.v.c.q.b.0 k q y y y r S.H.B.m.w.w.B.m.m.B.V.V.m.m.V.d.k.l.l.z.v.A q c c y y y 0 0 r q y q q q q y q c A y q 2 6 / ( 3 3 ) 4 ) + KXr r g.y e.q u u c r 0 0 k y y q A 1 c c S u u u 4 ( 3 S S HXN N m z 3 q J.3.@.&.7.7.*.5.*.&.7.&.&.=.l.E +. S c 4 2 2 JX3 2 HX2 3 JXM M & q q.+.+.+.&.=.*.3.&.&.&.&.v...K gX4XqXsXfXfXaX_ mX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#XmX#XSX#XSX_._.SXSXmX_.mX_.SX_.#XSXSXSX#X_.SX$X_.mXSX$X$X] SXSX$X$X`.$XSX$X$X`.@ (.(.DXDX$XSXSX$X`.SXSX$XDXDX`.SXDXDXDXSXSX$X5 DX$XDX] DXDXDXFXFXFXDX`.DXDXDXDX` 1XFXFX` @ ' HX) JXu 4 3 u 2 (.GX` FX2 6 ' GX3 HX5 DX8 6 HXDXDX8 `.2 2 FXFXFX' ` SXDX6 ' 6 FX5 DX` FX' / 6 FXFX5 FXFX2 3 / 5 5 6 3 ) S u 2 2 ) GX2 2 2 k r q y q u A 1 HXy r u u HX2 2 FX6 4 3 6 FXHX3 3 S HX/ S 1 ( 5 5 FXm y r u 6 6 5 5 6 6 ( 4 ( 6 6 / 2 2 2 2 2 6 6 2 3 HX3 ( HXu u 1 1 V.T.m.b.n.c.c.l.v.c.c.v.l.v.c.c.f.l.q.c.h 0 0 L k y r H.C.Y.H.S.V.V.B.B.m.m.m.m.T.c.j.M.d.r y r 0 h h 0 k k r y q q c q r y r r k r m FX2 2 2 2 2 / /.HX2 HX2 HX2 GX/ 2 2 2 ( ( 2 M c r r y y y q r r y q A 4 / 6 / 2 HX3 ( 3 ( HX( S S JXKXe.S.S ) GXS q.=.3.3.+.=.f.K xX4X4XqXeXuXgX_ ).#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X% #X#X#X_.mXSX#XSXSXSXSXSXSX#XmX_.SX_.SX_.mXSXSX_.SXSX$X$XSXSX$XSXSXSXSX$X] o DXDXDXDXFXFXDX`.SXSX$XSX_.DXDX`.SXSX`.DX$XSX`.DXDX$XSXSX$XDXDX] DXDXDXDXSX$XDXDXDX@ ` FXFXDX` DXDX' GXHXu u S u 2 FXDXGXHX6 OX' ( HXFXDX`.6 M 5 DXDXDX6 5 ` FX5 DX8 @ ' FXFX' 5 DX5 FXFX6 GXFX' 5 ` 5 / ( ( 6 DXDX' 3 4 M 3 2 ( 4 4 3 HX2 1 q KXS ) 3 u y q q q q u GXHX2 3 S 4 3 HX2 ( 3 JX4 > ) 4 3 3 / 2 3 4 1 q A 6 6 ' FX5 GXJX4 ( 6 6 / 2 / ' 6 GXGX/ HX( 2 ( 2 3 S S u c e.m.V.V.T.c.f.c.l.v.c.c.v.c.n.f.v.l.l.c.k 0 L LX0 y 0 k B.V.V.V.S.S.B.m.w.w.m.m.m.d.j.z.g.r k k 0 0 0 H 0 r y y e.q c q k 0 k k r : FX2 ( 2 / 2 HX( 3 3 3 ( 2 2 6 2 3 3 3 JX3 3 3 3 3 S > 4 > M 4 c 4 3 1 1 HXGX6 GX/ 2 ( HX2 3 3 3 3 4 S 3 # # @ @ 6 # = 4 A q.&.&.*.+.xX0X4XqXsXsXqXiX_ #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.SXmX_.SXSX$XSXSXSX_.mXSX#XSXSXSXSX#XSXSXSXSXSXSX$X$X$X$XSXSX$X`.DXDXSX$XDXDXFX(.DXSXSXSXSX$X`.DX$XSX$XDXSXSXDXDXDXDX$XSX@ DXSX$XDXDXDXDXFX5 FXFXDXDX@ (.DXDXFXFXFXFX6 GX3 KXS u A 4 2 u ) @ FXFX6 2 6 ` 5 ( 4 GXDX5 ` 6 ' DX5 DX8 FX/ 6 5 FX5 DX8 DXFX6 6 6 6 FXDXFXGX( 2 2 ' 5 5 GXGX2 GX5 6 2 3 4 4 2 DXDX6 HX2 3 ( 2 u y 1 u y u u A u q y u ) 2 3 ( 2 ( 3 HX3 2 ( JXHX4 4 3 4 y q 2 GX6 DXm u u ) 6 6 6 6 6 FX2 ) 2 6 GX2 2 HX3 3 ) > 3 4 A B.B.S.C.H.n.n.V.b.l.d.v.v.v.n.l.z.d.z.d.w.1 r 0 0 r 0 0 f.v.d.f.g.w.m.m.b.b.T.S.n.X.z.f.k k k k k 0 h '.0 y y y y q y r k r q A 2 #X6 HXHX6 GX/ 2 2 2 2 3 3 3 GX6 GXGXGX/ 2 HX2 2 2 3 GXGX2 5 5 / GXGXN ' 2 3 ( HX6 6 FXGX2 2 4 u u ) 2 GXGXGX2 6 FXFX1X6 2 2 2 4 w.v.*.xXfXfXqXsXsXfXgXAXD #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmX#X#X_.mXSXSXSX$X$XSXSXSX_.SX_.SX_.mX$XSX_.SX$XSXSXSXSXSX`.DXSXSXSX$XDX$X`.DXDXDXFXFXDX`.SXSXSX$XSXSXDXDXSX$XSX$XDXDX$XDX` DX`.SXDX` DXDXDXDXDXFXDX] DXDXDXDXFXFXFX6 GXFX1X` HXJXKXu 4 u 1 2 FXFX' 2 GXGX/ 3 M ( 5 FX5 HX2 8 DXDXDX6 FXFX6 5 DX] DX' 6 6 ' 6 FX` FX6 GX6 GX6 FXDX6 / ' 3 N GX2 GX6 6 4 u GXGX/ 2 3 3 3 ( 6 / 2 ) 4 S 1 y u KXy A GXGXM 2 FX2 JX2 GXGX4 3 ( 3 HX) S S S 3 / FXN u q S 2 GX6 FXFXFX' ) 4 2 6 2 HX( 2 / 2 2 3 2 3 A V.S.S.B.m.V.C.S.H.m.l.l.v.f.c.c.d.z.z.d.g.q * S > y 0 l k v.v.c.v.v.n.b.w.w.V.T.z.j.z.l r r r k 0 0 0 0 0 r r r y y k 0 h A @ / 6 FX6 / FX' 6 GX6 2 ( 2 / 2 2 / / / 2 GX/ 2 2 2 2 / / / 2 / ' GX2 / 6 2 6 GX3 ) 4 2 ' GX4 q 1 1 q A 3 GXFX2 2 6 / / 6 ' 6 2 HX3 GX= > A 4 O X gXuXqXsXuXaX_ mX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSXSX_.SX$XSXSXSXSXSXmXSX#XmXSXSXSXSX#XSX$XSXSX$XSX$XDX$XSXSX$X$X`.DXSX$X$XDXDXFXFX` 1XDX8 SXSXSX`.$XSXSX$XDXDX`.SXDXDXDXSXSXDXFXDXDX`.DX$XDX` FX(.DX(.@ FX5 DXFXDX` 5 DXFXGX2 JX1 1 1 A ( HXu 2 FXHXGX2 3 2 GXFX6 M 3 FX` 8 ' / DX5 FXDX8 ( M 2 6 FX' FXDX5 6 6 6 6 6 FX5 6 2 3 JX2 GX/ 6 6 FXGX4 4 ( 6 GX) 3 2 3 ( 6 6 HXHX2 2 4 A q A r y 4 u HX3 u u 4 3 / 3 KX( 3 3 3 S HX2 JX3 3 2 4 S A u 2 / 5 5 DX5 5 3 4 GX6 / 2 2 ' GX2 GX6 / GX) S.C.S.m.m.V.C.V.B.S.m.g.v.v.v.b.c.d.l.v.w.q A q y r r y y k v.q.n.q.v.v.n.m.v.k.j.j.h.0 k k k k k k 0 k k k k k r k 0 0 H 4 ' HXGX' ' 5 5 5 GX/ HX4 3 2 GXGX/ 6 2 2 2 2 2 ( ( 2 2 2 6 6 2 ( HX3 M 3 3 M 2 2 ) 4 4 u 4 3 u q q 1 u ) GXGXGX2 FX6 6 6 2 2 2 2 2 6 6 3 2 o ] n V N hXsXfXuXdXD ).#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSXmX_.SX$XSX$X$X`.SX_._.SXSXSXSX_.SX$XSXSXSX$X$XSXSX$X`.DXSXSXSX$X`.DXDX(.DXDXDX6 ' DX$XSX`.8 $XSXSX` DX`.SX$X`.DX`.SX`.DX` DXDXSXSX` DXDXFXFXFX5 DXDX`.DXDXDXFXFXFXFXDXDXFX/.KXJXKX1 u u S 6 ' FX` HX/ GX6 2 ) 3 5 ` DX# 3 FXDXDX8 GX4 6 ' FXFX5 ` SXDXGX' 6 GX6 5 FX/ GX/ HXFX' 6 5 ` ' 3 3 2 6 ' 6 ( 3 ) u 3 / ( JX( 3 ( GX1 k r k r 1 1 u HXq q 4 S 3 3 3 6 GX3 S 2 FX3 HX2 M 3 GX4 q u 6 5 5 DX5 3 A q ) GX6 6 GX' 2 4 2 2 FX6 # M S.S.V.m.B.V.m.B.S.V.m.w.n.c.c.T.n.f.c.v.q u q r k r r r k r v.v.c.c.v.v.c.n.d.X.j.X.j.k k 0 k r 0 0 k k k k k 0 0 0 0 0 k u JX3 GXGX5 DXDX5 FX6 4 u ) ( / 2 6 6 6 6 6 6 GX2 2 m 2 6 ' 6 2 3 3 ) HX2 3 4 M 4 4 3 ( 3 HX/ 2 S 4 /.3 2 6 6 6 FXDX/ 2 / 3 4 M 2 / 2 / HX6 ' 5 6 2 v v v X gXuXiX_ #XVX).#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSXSXSXSX$XSXSX$X$XSXSXSXSX_.mXSXSX$XSX_.SXSXSX$X$XSX$XDX`.SX`.$X$XDXDX@ $XSXDX(.FXFXFXDX$XSXSXSX$XDXDXDXSXSXDXDX8 $XDX@ DXDXSXSXDXFXDX`.DX` DXDXFXFXFXFXFXDXFXFXFX` DXDX5 5 FXGXJXy q S A q 2 FXHX) 6 GXGX2 3 2 ' DXDX/ u 6 DX` DXN GX5 FX' # ` DX' GX' FX' FX5 FXFX6 GX2 6 ' 6 5 5 6 3 3 ) 2 6 FX5 2 4 u 4 ( 3 3 JX3 ) 3 2 1 r q q 1 S A q u 1 u u 1 S 2 6 ' 2 4 4 FXDX3 ( HX4 ( GXKXS HX2 6 5 2 S q r q 3 6 6 ' FX6 ( XX( 3 6 GXGX( S.V.m.b.B.V.S.H.Y.B.n.m.T.b.w.B.v.k.l.k.0 y k 0 k r y y q 1 1 I.v.v.c.c.v.c.v.k.X.X.0 r 0 0 0 r k k r k r k k k k r q 4 JXu A u 3 GX6 / 4 1 S 4 KX3 ) 2 GX/ 6 FX' 6 6 2 2 ' 6 2 GX6 6 6 GX/ 2 2 FXDXFXFXGX2 2 / 2 6 FX6 / / 3 ( 3 ) JX2 6 ` 5 ( 4 4 S u 4 FXDX6 / 6 6 6 / 6 GX/ 2 m v v ;XjX_ #XDX5 SX@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.SXSXSX$X] o `.$X$XSX#XSXSX$XSXSXSXSXSX_.SXSX$XSXSX$XSX`.DXDXSX$X`.DX`.DXDXDXFXFXFXDXDX$XSX`.8 `.`.SX`.DXDX$X`.DXDXDX`.SXDXFXDX] SXDXFXDXDX` 5 FXOX5 DXDXDXDXFXFX' FX' GX/ 2 JX4 1 1 q q 4 1 q 2 FX6 FXHX( 2 FX' 3 S 2 DXFX5 6 ( 5 ` 5 ` DX6 3 6 FX6 FX` DX` FX6 2 / 6 6 FX6 / ( 3 HX3 6 FX` 2 3 ( 2 6 6 GX3 4 4 3 ( ( HX( 3 ( HXGX4 y r 1 oXr A u 4 3 1 q KX( 2 2 3 JX3 ) ) 3 M JX4 S 2 / ) S 1 q y 4 GX6 5 5 6 HXHX4 HX2 / 2 6 A.B.B.m.m.V.H.C.Y.m.n.m.m.T.B.b.k.j.j.h 0 0 0 0 r q ) 2 GXHX3 & u I.f.v.v.f.v.d.L L h k h h 0 k g.r k 0 k k k 0 u ' 2 JXS u 1 A 4 3 ) KX1 y q S ( ( 3 OX6 6 FX5 2 / 3 ) HX3 2 6 6 6 / 2 / 6 6 / GX6 GXFX6 6 GX6 2 HXm 3 3 3 4 S 4 S KX) 2 GX2 3 4 KX4 JX2 FXFXFX' 6 / 6 2 2 6 GX/ 2 2 2 v 2 @ @ 2 3 GXDXSXAX@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmXSX$XSX$XSX$XSX] $XSXSXSXSXSXSXSX$XSX_.mX`.SXSXSX$XDXDXDX$XSX`.`.DXDXDX`.$X`.DX(.# ' FXDXDXDXSXSXSX`.DX$XSXSX$XDXDXDX`.@ ` DXDXSXo FXFXDXDXDXFXFX5 DXDXDXFXFXFXGX' # GXGXFXDXDXHX) JX1 1 u q A / ' 6 ( 2 GXHX3 4 4 ) 6 ' 5 / 4 FXDXDXDX5 GX6 5 6 6 5 8 SX5 ' / 6 6 GX' 5 GXGX2 GX/ / ' DX6 4 HX2 / / / GX( 2 3 ( FXGXHX2 GX3 3 2 3 ) 4 4 u r y KXq u q y u 4 JX2 ) ( 6 3 4 3 OX6 3 ( XX4 FXGX3 A y S / 6 6 5 4 1 S 3 2 2 2 6 FX# C.V.B.B.B.B.V.S.S.m.B.V.B.B.k.X.2.L 0 k r q c 3 GXHXHX/ 2 ( S 3 4 k f.d...f.r 0 9XL 0 h 0 h 0 r r r r r r k r 6 FX3 KX) 4 u JX2 HXJX4 4 4 1 1 3 2 HX2 6 6 DX6 6 GX4 JX3 4 2 DXFX2 2 GX2 2 2 2 2 2 2 2 / 6 6 2 ( 2 ( 2 / 2 / 2 ( 3 3 3 3 3 3 ( 2 2 3 2 / ' 5 FXFXFX2 / 2 2 / 2 N / 2 / / 2 ' 6 6 6 2 M 3 2 FXSX@X@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.SX$XSXSXSX$Xo `.DX$XSX_.SXSXSXSX$XSXSX$XSXSXSX$X`.`.SXSXDXDXo `.$X$XDXDXDXDXFXFXFXFXGXFXDXDX$X$XDXDX`.SXDXFXDXDX$X8 DXDXDX`.SXDXFX5 $XDXDXDX5 (.FXFX' GXFXDXFXFX` 5 FXFXFXFX' GXHX4 u 1 1 q A 3 u 1 6 GX2 GXm M HXGX' 6 3 ) 6 ` 5 8 ( 2 DXFXFX` 8 ' 3 3 GX6 FX' 5 5 ' 6 2 / 6 GX6 5 5 2 3 3 M 2 6 GXGX2 ' 3 4 2 HXGX2 3 ( 2 4 3 $XDX2 3 ( u 1 q q q y q S JX3 3 6 6 3 KX( 6 GXJXHXGX3 / HX) 4 4 4 2 / 6 / A q JXGX/ 2 6 FX' # HXY.;.A.R.;.T.B.B.m.T.B.B.l.1.k.z.k c c ) DX/.3 JX3 2 ( 3 4 4 u A 1 q k I.4 2 2 u k 0 h L L h k 0 H h y r k r ( 2 ( HXHX) S 4 / GX3 4 ) 4 KXu KX( 2 GXGX6 GX3 2 ( 4 ( 3 2 GXFX' 2 6 6 / 2 2 3 3 m 6 / / 6 6 GX6 / 6 (.FXFX6 GX# / 6 FX2 2 / 6 6 / N 6 3 2 FX5 DX` 6 2 6 6 6 N 2 2 2 GX6 6 2 / 6 / / 6 2 3 4 3 / DX_.VXAX#X#XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSXSX$XSXSX`.o ] DX`.$X$X$XSXSXSXSXSXo $XSXSX$XSX$X$XSX$X`.DXDXSXSX`.DXDXDXFXDX$XDXFX6 FXFXDX8 ] $XSXSXDX5 DX`.SX$XDXDXDX$XDX@ (.DX] DXSX` FXDX` FXFXFX5 SXSXDXDX(.FXFXFXGX' FXFXFX6 JXoXu 1 u u q ) ' / FXDX3 / GXGX3 S 4 GX5 ` 8 u S 8 5 DXDX6 u 2 6 6 6 6 5 DXDXFX/ / 6 GX6 DXDX' HX( 4 HX6 FX` 6 6 / 3 ) 3 6 6 ( 4 3 ) S 2 6 / / HX2 2 JX1 y y y r u u oXHXS A S 4 3 HXS 4 3 3 3 4 S 4 / ( ( 3 3 HX3 u q u / 2 6 5 ` FX5 3 KX2 # # # I.l.T.g.d.f.v.k k y 1 2 GXHXHX/ 3 4 ) 6 FXGX3 ) 4 u u JX3 3 # @ / 6 8 c q r r 0 L L 0 '.F y q k y 3 ( JX4 4 4 4 KXJXHXJX2 2 JX4 S 4 ( 2 3 3 3 M u q 1 GXGX2 / ' FXFX5 5 6 6 / M S 3 2 FX6 2 6 / / 6 / GX/ 2 / 6 6 GX6 ' 6 / 6 6 / 2 2 GX5 ( u 4 6 2 2 6 3 3 2 4 u M ( 2 ' FX6 / 2 2 2 2 6 6 6 GXm 3 M 3 FX` 6 5 SXAX@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.$X$XSX$XSX$XDXDXDX`.SXSXSXSX$XSXSXSX$Xo _.SXDX`.SXSX`.$X`.DXDXDX`.`.DXDXDXDXFXFX' # ' FXDXDXSX$XDX`.DX8 $X] DXDXDX`.DX` 5 DX`.SXDX# DXSX8 FXDXDX` FXFX6 GX5 5 FXFX6 FXFX' FX5 FXGX3 1 q q A q u / ' 3 4 2 / GX2 ) 4 2 5 5 DXS u 5 5 ` DX3 2 `.6 6 ' FX5 8 DX6 2 6 GX6 5 DX5 FXGXHX2 ' 5 ' 5 FX6 3 3 3 GX' 6 6 HX4 u u ( 2 4 HX# ) 3 6 y 0 r r l y u u u KXy 1 u u 2 4 JX6 / JX4 3 / GX2 o ( 4 3 4 ) q 1 ( 6 FXDX6 2 u A 4 2 2 ( @ S l.c...%.M.k 1 c q 4 JX4 JX( ( 3 3 3 GXGXOX3 JX( 4 3 2 ( HXGX2 3 6 6 S u u > q k * 5 # 1 y y 1 q u 2 3 6 DXGXS KX( HX( FX6 HX( 3 JX3 3 HXJX4 4 1 y 1 2 2 2 6 5 5 ` 8 ` / 6 GX3 ) 4 GXFX2 2 6 2 2 / 2 2 2 2 2 GXN / 6 / 2 3 ( ( 3 3 ( 3 3 3 4 S 4 A 4 2 ( 3 u u S 3 GXGX# 6 ' 5 ' / 2 2 / GX6 N 2 2 2 ( 2 / / 2 ( 2 FX#X@XAXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX$X] SX$X$X`.`.DXDX$X$XSXSXSX$XSX$X] SX_.$XDXSX$XDXSXDX@ DX$XSXSXDXDXDXDXDXDXDXDXFXGXGXFXDXDXDXDXDX$XSX`.DXDXSX8 `.DXDXDX8 DXFXFXDX`.8 DXFXDX5 FX5 OXGXFXDXDXFXFXFXFX' GX6 GX2 HX3 ) u A q y A 4 q q / 6 GXFX2 3 ( ' 5 / u u 6 FX5 5 2 2 5 FX' 5 DXDXFX6 / ' 6 6 5 ` ' 6 6 2 2 6 6 ' 5 GX/ 2 ) 3 3 FXDX5 GX3 M ) 3 3 4 u ) 3 S 4 ( 1 r y r r q 1 q 1 oXq A 1 u HX( 6 5 3 S 4 3 2 3 3 2 3 S 4 JX4 u 4 ) 6 5 ' 4 A r y 4 GX2 6 5 5 A w.I.X.k.q q y q u 4 4 S 4 ) HX2 ( 3 HX2 3 ( 3 3 2 HX2 / 2 2 6 ' S u 3 3 1 A > 2 2 ) 3 4 KX/.q y KXGX4 q q ) 3 2 3 2 GX2 3 XXOX3 u HXFXHX) 3 S q 1 ( 2 / ' 6 GX` FXGX6 FX2 HX3 3 6 6 2 ' 6 6 2 2 2 2 2 2 2 m 2 DXDX' 6 / 2 3 3 2 3 3 4 ) 3 3 2 HX2 2 2 2 3 3 2 GX6 6 6 5 DX6 2 6 6 2 2 2 / 6 2 2 ' 6 6 FX6 / 2 M > 3 FX#X@XAX#XVX).VXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.o $XSXSX$XDXDXDXDXDX$XSXSXSX$XSX$XSXSXDXDXSXSX$XDXDX`.$X$XDXDX`.DXDX`.DXDXDX1XFXFXFX5 FXFX5 DXSXSX$XDXDXDXSX`.DXDX@ (.DX` FX5 ` o SXDX' FXDXDXDX` 5 FX` FX(.FX(.FXGXGX6 GX( GXFXDXFX3 JXu q 1 q y u / 6 FX2 3 2 HX2 3 S u 3 FX6 5 ( S 5 DX5 DX8 FXm GX' FX5 FXDX8 FX6 2 2 GXGX/ 6 DXGX( 3 HX2 2 ' 5 ' 6 GX3 2 6 6 6 3 u 4 4 ) 3 / GX4 u 2 3 S 1 q y q 1 u S u 3 OXu q u 4 ( HX3 HX( JXKX4 ) 4 ) 4 4 2 6 u 1 u q r 1 GX6 6 FX5 DX# O q k y q q A 1 S 3 3 ) 3 3 2 ( ) 2 / 3 HX3 ( 2 2 6 ` GX2 2 3 S S 3 2 3 3 3 ( 3 S S 4 4 HXoXy q HXh ; h 2 3 3 ) ( HX( 3 3 3 4 4 OX/ 4 3 3 3 ( KXS 4 2 2 ( 2 4 A A u u 4 2 HX3 / 2 6 5 6 6 6 6 2 3 m 6 2 ( 6 ` 5 6 GXDXFX/ 6 GX/ 2 2 2 ( 2 FXFX2 / / 6 / 2 3 3 HX6 FXDX5 / ) 3 ) 4 4 2 6 GX' ' 6 6 ' 6 6 6 / 2 ( M S 3 DX#X#XDXFXDX$X#X@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.#XSX$XSX$X`.$X`.DXDX$X`.`.SXSXSX$XSXDX`.$XSX$XDXSX$X8 $XDXDXDXDX$X$XDXDX@ (.FXDX$XDXFX' GXFXDX] DXDXDXDXSXDXGX(.DX8 `.DXDXDXDXSX8 FXFXDXDX` DXDXDXFX6 / # FX@ ` ' FXDXFXFX` FXFXFXGX( ( KXq y q y 1 ( u A 3 GX3 ( 3 4 4 3 6 6 6 > 1 / DX` DXDX6 6 FX6 6 5 DX] DX6 ( 2 6 / 6 FXFXFX6 2 HX/ GX6 FXDX6 FX1X2 GX6 / FXGX4 ) 3 4 GXDX2 3 / GX3 2 q k q 1 q r q 1 A KXq y S u JX2 3 ( GX2 4 3 3 2 3 4 KX3 4 S 3 ( u r q 2 GX/ 6 ' 3 4 ) ( q y q q 1 u S ) 4 3 2 / 2 3 ( HX2 ( ( 3 ( ( 2 GXGX6 ( 3 4 4 S 4 ( 6 GX2 2 2 3 4 ( 4 KXJXu u u y ,.h / S KX4 3 3 HX2 2 ( 4 3 HX3 3 / 2 JX4 4 S 1 u 3 3 3 4 u q y A 3 FX/ ) HX6 ' FX` 2 / 2 ( 3 3 ( 2 6 FX6 6 6 6 GX6 GX6 6 / GXFX6 / 2 2 2 2 2 3 2 3 3 M ) 4 3 / / HX2 3 S 4 ) 3 6 FX6 5 6 / 6 6 6 N 6 GX6 6 2 2 > 4 ( 6 6 2 m m 2 FXSX@X@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSXSX$X] o SXDX` DXDXDXSXSX$XSXSX$X$XSXSXDX$XSXDXDXo `.DX$XSXDXDXDXDX` DXDXDX$X@ (.6 GXGX/ FXo DX] $XSXDX5 @ DX`.SXDX(.DX] DXFXFXFX` DXDXSXFXGX5 FX6 GX6 FX@ ` @ DXFX@ FX6 ' GX2 FXDXFX( 1 S 1 q q q q 1 ' / HX6 2 M 2 / / 3 S u 3 FX5 5 ' 2 FX5 5 5 DXDX3 2 ( 2 6 6 GXFXDX' / GX2 6 6 6 FX5 ` 6 ) 4 ) 2 2 2 ( 6 6 / 4 1 3 6 HXHX3 3 ) 2 1 y HX2 JXu q 1 y 1 1 k q 4 3 3 ( 2 ' 3 4 ) 2 GX( 4 ) JX3 3 3 4 1 S 1 3 2 2 DX2 y q HXS q y y q A u u q 1 4 / 6 2 HX( 2 HX2 2 2 2 2 2 2 6 GX2 HX3 M ) 3 GX6 / / 6 / 3 3 2 S A u 4 4 S 2 r 0 S JXJXu u 3 6 5 5 FXHX( 3 3 GX# FX3 4 3 JXS 3 HX( HX2 3 u 1 q 4 GX3 2 / 6 6 DXDX' 2 3 3 4 4 3 2 6 6 6 / / 2 2 6 6 / 6 / 2 6 ] FX6 2 ( 2 3 2 2 ( 3 3 3 3 ) M 3 3 ( 3 M 2 3 3 GX6 FXFXFX5 6 6 / 2 2 2 GX6 6 2 2 2 2 N FXDXFX/ 3 > m 6 DX#X@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#XSX`.$X8 `.DXDXDXDXDX$X`.$XSXSXSX$X$XDX$XSXSX`.DX$X$X`.DXDX(.FXDXSX$XDX` DX(.DXDXDX@ ' GX6 FXDXo `.DXDXDXDXSXDXFXFXDXDXDX` FXFX` DXSX5 GX5 `.DXFXFX` DX6 2 6 GX' FXFXGXGXGXFXDXDXFX5 6 HX) y r q q y q ( 6 5 4 4 2 HX/ 3 S 4 2 5 5 FX6 4 2 DX` DXDXS S FX6 GX6 ' 5 5 8 FX2 2 / GX/ 6 5 FX6 GX3 3 4 M 6 6 / 6 2 HX3 S 3 2 6 ( 4 4 3 3 3 / 6 / GXHX3 S q y y q q u ) u 4 3 u 1 S 4 2 HX3 4 JX( 3 3 4 u 3 2 KX3 / 2 2 u q q ) u A q A c 1 c y y HX2 6 GX2 2 2 3 ( 6 GX6 GX/ ( ( ( HX/ ( 2 HX2 / / 2 GX6 2 2 3 3 ) 4 4 u 4 3 2 6 5 3 1 3 HXKX1 ( 3 y [.0 3 HX) JXm FX1XHX3 HX3 3 / GX2 2 3 4 ) ) 1 u 2 2 HX2 6 5 FX3 ( 3 ( HX3 / FX6 6 6 2 / = 2 2 / 6 FX/ 2 2 5 DX' ' GX2 / FXDXGX2 6 GX6 2 2 2 2 / 2 2 6 6 6 2 N 6 FX5 ` 5 5 / 6 6 2 2 / 2 m ( 6 ' 6 ' 6 5 FX6 6 2 3 4 > ( 5 #XVX).VXAXVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSXSX$X$XSX`.DXDXDXDX5 `.SXSXSX`.SX$X] SXSXDXDX] SXDXDXDXFXDX$XDXDXDXDX@ 5 DX$XDX` DXFX2 2 GXFXDXDXDXDXDXSXDXFXDXDXSX`.DXDX` DX8 ` OXFXDXDX8 DXFXFXFXFXFX6 HX' 5 DXFXFXFXFX' 6 GXGX/ 3 3 1 A q q y r y 4 1 y u 6 2 2 GX4 4 ( FX5 6 3 A 3 DX5 ` 5 4 2 5 6 6 6 ' ` DX8 6 2 6 6 / 6 FXFX6 6 / ' FXHX2 ' FX6 FX/ 3 / 2 HX6 6 ( ) 3 ) u u 2 ( 3 HX4 2 3 0 r y y S S S A y 1 3 2 GX6 GX/ 2 GX/ 3 GX6 6 3 2 FX3 2 GX2 2 2 2 2 GX6 ' 6 ( 3 3 4 4 ) 2 ( 3 2 ' 6 6 5 3 S KXu KXu A r k 0 1 ) 4 1 4 2 ( 3 3 ( 3 3 4 ( HX2 HX2 ( 3 3 4 ) 2 3 3 GX6 S q q S GX/ 2 5 FX6 FX6 6 ' 6 6 N 3 GX' 3 / ' ' 6 6 6 6 6 6 FXFXFX6 6 ' DXFX/ 6 / 6 6 GX6 GXN 6 6 / 2 6 FX8 (.6 6 / 4 S > ( 2 6 GX' 6 6 / / N GX6 N 2 2 2 3 M ( 6 DXDX' 5 #X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX`.$XDXDXDXDX(.@ `.$XDXDXSXSX$XSX$XDX`.$XSX`.DXDXDX`.o ` FX` DX`.DXDX` @ FXFXGXFXOX6 2 HX' ` 8 $X8 DXDXDXDXSXDX` DXFXDXDX5 FXFX5 DXSXDX6 6 FXDXDXGX6 ' FXDX5 DXGXFXFX2 / GXHX3 HXFXFXGXu 1 1 y q q y y ) 6 / 5 2 S 3 2 2 m S A 4 5 5 6 ' 2 2 ' FXFX5 5 DX6 2 / GX6 ' 5 5 DX` 6 / GX2 ( 2 GX6 ' 5 6 2 3 3 ( 6 6 2 6 2 HXS 1 u HXJXS 4 KX3 / q h k k k q u A q .Xq q u u S JXM / ` 2 KXHX3 6 OX) 3 3 3 OXOXGX1 y u 1 S 4 > S A y y u JX2 2 6 6 6 ' 2 S 1 3 6 ' 2 FXDX2 2 2 2 / 2 HX2 / 6 6 / 2 GX/ 3 3 ( 2 2 2 GX6 6 6 ' 3 1 1 1 1 1 JX(.6 2 u 4 ( u u GX2 2 ' 6 2 4 JXHXHX( FXGX3 3 3 JX3 2 ) 4 3 4 u u q 4 6 2 2 GX6 FX' 5 DX5 5 ' 3 u 4 ) 2 FX6 GX6 GX6 / 6 6 6 N 6 / 6 6 6 2 6 / 2 2 / 2 2 2 2 ( : HX4 3 3 ( # / 4 S S 4 ( 6 ' 2 6 5 6 6 6 2 2 2 6 FX6 FX6 6 2 3 2 / 2 2 2 6 DXAX@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSXo `.DX`.DXDXFXDXDX5 `.SXSX$X$X`.DXDXSXSXDX$XSXDXDXDX`.DXFXDX`.DXDXDXFXFX' ' DX`.@ OX2 2 6 FXDXDXDXDXDXDXSX6 / FX` DXDXDXDX5 ` DX] FXGX' DXDX5 FXFXFXFX/ 6 GX3 FXFXGXGX6 ' GXGXFX' FX/ ) KXA y y q r r 4 2 4 u 3 2 JX) 3 u u u GX6 6 6 : 4 ' 5 5 5 DXGX> / 6 6 / 6 5 DX` 2 2 6 2 3 2 6 6 GX6 / 3 ( 4 ) 2 ' 6 GX' 3 JX( 2 2 2 3 u S 4 4 4 HXu q q S 4 KX1 A q 1 1 y 1 u ) HX2 & A u 2 3 GX2 4 JX4 ) 3 3 3 A KX4 q u u S q y y r y 3 2 2 6 ' 6 6 3 S 3 4 6 6 / GX2 2 HX( 2 GX6 6 6 2 HX2 2 ( ( GXFX( 2 GX2 GX6 2 2 3 S u 4 m u 1 ) FX@ 2 4 4 3 oXu u ) GXGX/ 2 KX3 GX2 4 ( @ OX2 HX( 3 3 3 2 2 3 ) 3 u q 1 3 2 2 / ' 5 5 8 ] 8 DX3 A 1 S 3 6 ' 6 6 / 6 2 2 6 / 2 2 / 6 6 6 / 6 6 / 2 ( ( 3 M 3 3 ) 4 4 4 ( 3 4 2 2 4 3 2 2 FXFXGX2 ' ' 5 FX6 6 2 2 DXFXN 6 2 2 / 6 FX6 6 6 6 m 3 2 FX#X@XVX#X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSXDXDX`.DXDXDX` (.DX$X`.`.SXSX$XSX$X`.DXSXSX`.DXDXSXDXFX`.DXDXFX(.FXDXDXDX(.@ ` ' GXHXHX2 / 5 DX`.SX$X8 DXFXDXDX] $XDX5 FXDXDXFXFX6 (.o ` `.DXGX' FX6 GX/ GX' 8 `.6 (.FXFX6 # 6 / 2 2 / 3 KX1 q q r q S y r ) GX3 3 2 3 > 3 ( 2 6 2 S u 6 ` 5 DX5 3 2 FX6 6 FX5 DX8 FXS 2 6 ( 2 / 6 ' 6 6 2 2 2 ( GX6 / GX` 2 4 / 6 / 6 FX6 JX4 JX4 ) ( GX2 3 ( ( / 1 y y r y l y u 1 HXKXr y 4 4 4 3 3 3 ( 3 ) 4 KX4 2 3 JX4 A q c u 4 u r k 3 / 6 ' 5 6 GX2 HX) S 2 GX/ 6 GX/ 2 2 2 6 FX6 GXFX2 3 / 2 2 6 GX2 2 2 6 FX6 2 3 4 4 4 ( 6 2 3 3 3 ) ) ) 4 4 KXKXu q 4 1 0 h r GXHX4 1 ( 6 2 3 3 3 3 4 ( GXHXJX3 JX4 ) S 4 ( GX6 6 ' 5 GX3 4 ) S 1 S ) / ' 6 / 6 ' 6 6 / 2 2 m 2 / 6 / 2 6 FXFX6 / 2 2 m 2 2 2 2 2 m HX3 2 2 2 / / / 2 2 DXGX3 2 FX5 8 ' 6 ' : 4 3 M GXFXFX/ 6 6 6 N 6 ' 6 / 2 3 > ( ' `.@XVX#XAX#XAX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX$XDX`.SXDX(.@ ` 5 FXDX$XSXSXDX`.DXDX`.SXSXDX1X$XDX@ DXDXOXFXDXDXDX$X@ FX(.@ FXFXFXFXGXHX2 ' FXDXSX` DXDX` DXDX8 FXFX5 DXDXFX5 FX5 DX8 8 ' GX5 DXDXGXFX5 FX5 FXGX6 ' FX' 6 GX2 / FXFX' 5 GXJXKXq y q A r q 3 / 2 FX6 S ( HX2 ( S u u 2 FX5 5 6 M 6 5 ' 5 DXDX6 2 3 4 6 / 2 6 6 6 FX6 GXGX2 ( 6 6 6 2 5 DX2 JX2 / 2 / 6 / 2 6 4 1 3 2 2 3 3 3 JX2 3 h r JXKXq y 1 A A 1 q y q S ) HX3 ( / 6 3 3 4 ( ' JXHXGXHXu 4 HX) 4 q y 4 / / GXFX' S 1 4 3 HX2 / 6 6 6 ' ' ' / 2 6 2 / FX/ 2 2 2 / 6 2 2 2 / 5 FX6 GX/ ( 3 S 4 3 2 FXFX2 2 2 2 3 3 4 KX) u u u l N.r 6 ) S KX2 HX3 HX3 4 S JXGX( 4 ( ' 2 ( 3 S S S 3 ( 3 3 4 u 1 A y 1 4 3 2 6 6 / 2 ' 5 5 5 ' 3 4 3 2 ' = 3 / ' FX6 6 ' ' 2 GXFXGXGX6 GXGX6 GX6 6 6 6 2 / / 2 3 4 ) 2 6 6 # GXM ) 4 u u 2 ' DX5 ' ' 6 6 6 6 6 GX6 6 ( 3 3 4 > 6 SX#XDX' FX5 #X@XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSXDX`.DXDXDX` FXFXDXSX$XDXDXSXSX`.SX`.(.DXDX$XDXDX5 FXDX$XDX5 (.DXDX@ ` DXDXDX` FX6 HX) 3 GX5 (.5 DXDX`.$XDXFX5 DX`.DXDX` FX5 DXDXFXGXFXDX8 DXFXFX5 FX2 GX2 3 GX' FX' 6 GX6 6 / GXGX6 / 3 KXr r y y r y S 2 3 A 1 2 3 ( 3 4 S u 2 ' FX6 N u 3 DX5 DX8 6 S 1 M 6 GX2 2 ' FX5 DXFX6 m 2 2 / / GXFX6 2 3 3 ) 3 2 # 2 2 GX6 3 u 4 2 2 2 JX4 S 3 2 q 4 AX# ( 4 u u q q 1 r r u ) HXHXHX2 3 ) JX4 GX2 4 ) JX( ( 3 ) 4 u 4 3 u 3 / 2 ' 2 r q 3 2 6 / 6 6 6 FXFX/ 3 ) 4 / GX6 6 2 2 2 2 2 2 2 ' ' 6 / 6 ' 6 GX3 ) 3 ) 2 6 FX6 / 6 6 2 2 m 1 1 ) JX) 3 1 L l 6 JXKX4 4 ) 6 ` DX' 4 / DXHX4 FXDX/ 3 4 HXHX4 4 HXOXGXJX3 ) KXS u 4 ( 2 / 2 2 6 FXDXFX2 / ) 4 4 4 3 m / ' 6 5 ' 6 6 6 GX6 6 / 6 6 6 / / 6 / / / 2 ( 3 2 ( 3 M 4 3 HX3 4 2 4 3 3 3 2 FX' 6 ' 5 ' FX6 6 6 N 6 / 6 2 2 2 2 ( 3 3 6 6 2 m 3 2 5 $XAX@X@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#X$X`.DXDXDXDXDXFXFXDXFX` ] $XSXo `.DXDXDX`.SXDX` $XDXDX`.DX(.@ (.DX] DXDX(.FXFX# / 6 GX/ HXJX2 FXDX8 SX8 DX(.` DX8 DX` DXFXFXDX5 ' 6 FX` DX] FX6 FX' FX5 ' 2 6 6 DXFXFXGXGX' GXHX2 2 3 4 ) JXu A q y r r 1 q k y 2 2 2 GX3 S 4 M FX5 2 4 q S 5 5 ` 5 6 u u 6 FX6 2 6 6 DX` FX6 / HX/ 6 6 2 5 DXGX2 ( JX4 ( 6 FX6 / 2 3 3 / 2 2 2 2 u 4 3 4 u 2 FXHX) JXM GXq l y y 1 r q u u OXu q A u ) u ( HX4 KX) 3 3 ) 4 u 4 2 3 4 ( 2 ( 6 4 q q 4 / 6 GXGXFXDX' 5 3 S 4 3 6 / 6 2 2 GX/ / 2 2 ' FX5 2 2 2 / 2 m 3 GXFX2 / 6 ' 6 6 / m ( ( M S S 4 3 3 3 2 > 1 S 3 HXKX1 4 2 u u u 3 GXGXJX4 6 GXHX( GXFX2 ) 2 GXFXFX2 3 4 S S S ( ( ( 2 2 / 6 5 5 / FX3 S 3 4 2 GXGX6 6 5 5 ' 6 6 6 6 / 2 2 N / 6 ' 6 6 2 2 2 2 3 2 2 2 2 ( 3 3 3 3 3 ( ( 3 3 2 / 2 2 2 5 5 5 5 ' 5 6 / 6 2 2 / 6 N 2 6 6 / 6 FX6 6 / 2 3 3 2 FXDX#X@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmX`.DXDXDXDX` 5 5 FXFXDXSXSX`.8 DXDXDX$XSX(.DXSXDX` DXDXFXDXFXDXDXDX` @ FXFX' GX(.$XDX6 2 3 HXGXDXDXDX5 FX` `.SXGX2 FXFX` DXFXFXFXFX5 ] $X' GX6 5 5 FXFXFXFXFX/ 2 GXGX6 GX2 2 2 HX( JX/ FXFXFX) 4 1 y y y r r u 2 2 / 6 3 4 M 3 2 M 1 A u 6 ' 6 6 ' 4 ) ' 6 6 ' FXDX/ > 3 / 2 2 GXGX6 5 FXFXGX2 2 3 2 6 ' FX' ' 3 ) 3 2 2 (.@ / 3 2 JX1 q 4 ( 4 4 JX4 6 1 '.k y y k r 1 A 1 u A r 1 S 1 JX( 4 4 / 6 3 S S 4 ( HX( GXHXu 4 2 S S y 1 2 GX/ ' 8 5 3 / S KX2 / / 6 GX2 / 6 5 5 5 ` FX6 / 3 2 2 2 GX/ 2 FX6 / 2 2 DXDXFX6 2 3 ) 4 3 2 ( 3 2 FXDX` 5 M 4 JX( u u KXr L L r GX4 u 4 ( 2 2 HX3 2 ( 3 2 2 GXHX3 3 ( 3 4 4 4 ( 2 2 HX2 6 ` 2 ) > u 4 3 3 `.mX6 / FX6 / 6 6 5 5 5 5 ' 6 / 2 6 5 FX' 6 6 GX/ 2 6 GX/ / 2 2 GX/ GX' 6 / / / 2 2 2 2 / GX5 DXDXDX5 2 GXGX4 M 2 / GX/ 6 6 FXFX6 FX6 ' DX' 2 3 > M 2 FXSX@XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#X$XDXDXDX`.DXFX5 FXFXDXFXDX$X$X] `.DXDX@ DXSX$X5 (.DXDXDX`.DXDXFXOXFX` DXDXFXFX` FXGX/ HX( 3 3 ' DXDXDXDX8 DX' GXFX5 DXDXDX` 5 ` DX8 FX6 GX(.DXFXDX5 FX6 GX( ( 2 2 GX6 DX6 GXDXGX' FXGX6 6 2 ) S u y k r r r A 3 3 M S 3 2 S 4 4 u S u 2 6 / 6 2 u 3 6 5 ' 5 5 5 M 4 6 / 2 6 6 6 5 SX5 / 6 2 3 ( 2 6 FX' 6 2 3 ) 4 3 2 DXFX6 6 JXS u 4 3 ( 4 u S 4 4 2 1 k k k r 1 u q q 1 A q KXu u 4 3 ( 6 DX( 1 S S 3 2 3 3 2 HXS 4 4 4 u q u 3 / 5 5 ] S q q y 3 @ / 6 6 6 6 6 FX2 3 3 2 6 FXFX2 / / / GX/ 2 2 2 / 6 GXDXDXDXDXGX3 3 M JX2 6 ( / 5 FX2 ' 6 S u 4 3 KX1 4 1 q 1 4 u ) S 4 / 2 ( 2 6 3 ( ) JX( ( 2 GX6 3 3 2 3 3 2 3 HX/ 6 HX4 A q 4 2 HX2 5 FX6 FX5 6 ' 5 6 ( ( 3 ( 6 5 / 6 6 6 6 6 6 GXFX6 / 6 6 6 6 6 6 6 6 6 FX6 6 6 6 6 6 2 6 5 3 2 ' 6 6 HX3 4 u 1 4 ( 6 GX6 2 6 5 6 6 6 6 6 5 ' 2 2 2 3 M > 3 / DX#X#XAXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#XAX#X#X#XDXDXDXDXFXDXFXFX6 ' 8 SXSXDXDXDXDXDX] SXDXFXDX`.DX@ DXFX# (.DXDX`.DX5 FXFXFXFXFXFXFXGXHXHXGX6 FXDX`.DX5 ` FX5 DX8 FX' FXDXDX5 ' GX6 5 FX` $X` 2 GXFX5 GX/ GX6 FXDX6 FXDXGXFXGX/ GX2 3 3 FXFXHX1 q q y y A q r y 2 2 S 3 3 4 S 4 GX/ 6 6 S A u 2 FXFX5 5 ' 3 6 6 2 / 6 ' ` SX3 4 6 6 2 ( / 6 / 6 DXGX/ @ 4 ) 2 6 6 6 6 2 ) ( 6 2 / ' 3 1 u 4 4 3 / 3 u 1 4 3 HXu y y q .Xq q u S KX3 ( 4 q 1 JX4 / 2 3 ) HX3 ) 4 S KX3 JXS S 6 FXDX: y q y r 4 GX2 2 6 FX' ' 6 4 4 S 4 6 DX5 6 / 2 2 / 2 / 2 2 FXFX6 6 / 2 / 3 3 2 ( 2 6 2 / 6 6 6 / 2 3 u S M u u 4 HX6 6 2 S JX2 S 1 HX6 FXSXDX6 2 ) JX3 GX2 6 2 HX' OX3 ) M 4 ( 3 3 4 S 1 A 3 6 2 2 ' 6 ' 5 FXDX8 FXN 4 4 HXu S 2 6 6 6 ' 6 6 6 6 6 6 6 / 6 / 6 GX6 6 / / 2 / 6 6 ' 6 2 6 ' 6 ( 3 4 ) 6 3 u u u S M GX' GX2 / FX6 5 ' 6 6 ' 6 6 6 6 6 2 / GXm > m 6 / ` #X).VXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#XDXDXDX(.5 DX` ' GX@ DXDX` DX$X`.DX`.SX(.FXDXDXSXDX' FXFXDXDX@ (.@ (.DX5 FXDXDXDXFXFX' HX4 3 2 ' DX` 5 DX5 DX] DX( GXFXDXDX` FXFX6 FXDX8 5 GX/ FXDXDX6 ' FX5 GX) ( 2 2 6 ' 6 2 2 3 2 FXDXFXFX6 ( 4 A k k y q r q 2 / 3 6 2 S M 2 3 2 M u c q ( 6 ' 5 ] 3 q FX6 / 6 ' 5 FX2 y 1 ' / 2 2 6 ' 6 6 FX6 6 GX( 2 / GX/ FXN HX1X6 2 2 GX5 FX4 S ) ) HXGXGX2 HX) JX2 4 y y y y k r q u q q q y y u 4 JX2 ( ) 3 3 ( 3 S 4 4 HX2 4 S 3 N 2 u 1 u u r q 2 2 / 6 FX5 6 2 HX) S 3 5 FX6 / 2 2 / / 6 6 6 6 FX' 2 2 2 FXFX( / / 2 GX6 GX6 6 ' / 2 4 u u 3 2 u 1 ) 2 ( 3 S 4 3 JXKXu u 2 3 r r y S / GXHX2 2 GX2 2 2 HX3 3 S HXGXJXKX4 3 3 u u 2 ( 2 GX6 5 5 DXDXFX6 ' u ) 2 ) 4 / ' 6 6 6 GX6 6 5 5 ' 6 N 2 6 6 6 N / 6 6 2 2 6 / / 2 3 3 > 4 3 ( HX3 HXS 4 FX2 2 6 ' FX6 / 6 5 5 ` ' 6 2 3 3 2 N 6 6 6 6 2 / (.FX/ 6 2 / 6 DX#X@X@X#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X`.@ ` DXDXFX' FXFX6 FXDX`.SXDXDXo ` @ DX`.SXDXFX8 DX` DX` FX6 FXDXDX`.DX5 FXFXFX2 ( 2 GX/ 3 HX6 FXFXDXDXDXDX5 5 ` DX8 DXDX5 FX5 DX5 GX/ 6 5 ` DX5 GX6 ' / 2 ' 3 JXFXFX5 GX2 GX6 FX/ GX2 2 m 4 4 r k k y r l r 4 2 GX4 1 2 3 3 2 4 u S S 2 / 6 5 DXq r 6 5 FX5 DX6 1 q S 6 / 2 2 GX6 FX5 GX6 / 3 3 GX6 6 2 6 ` GX3 ( HX3 ( 2 2 / 6 2 3 1 S OXGX3 ( 4 ) 3 2 y h A JX4 u q 1 A y 1 q k y S JX/.2 3 ( 6 6 4 4 ) ( FX2 ) 3 3 3 u 3 GX2 S y q ) 2 / 6 ' DXM 1 ( 3 JX3 ' 6 6 6 6 6 6 6 ' 6 5 ' / 2 / ( 2 DX' 2 GX2 2 2 / 6 ' FX6 2 3 3 ) 4 4 2 2 2 2 2 3 ) 4 M 4 3 /.KXA KX1 h N.0 4 3 HX4 3 2 / 2 3 JXM 3 4 4 / GXHXJX3 ( ( 3 3 KXS 6 2 ( 6 FX6 ( S S S u u 4 3 2 6 6 FXGX/ 6 / 2 / N 2 ' 5 FX6 6 6 / 6 ' 6 / 2 ( 2 3 3 m 3 4 4 4 3 2 2 3 3 ( ' 6 2 ( HX2 2 ' ` ` DX' 6 2 4 1 u M GX6 2 / / / 5 DX6 6 6 6 m M 3 2 6 DX@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X$XDXDX(.@ DXFXFXFX6 DX$XSXDXDX`.DXDXDXSX`.FX`.SXDX(.DX` FXFXFXDXDXDX(.@ FX' GX3 GXSXDXGX/ 3 ) / 6 DX8 DX` FX5 DXDX`.2 6 FX' DXDX5 6 GX6 FXDXSX5 / GX2 6 DXFXHX2 6 ' DXFXDXFXHX6 / ( 2 HX4 S 4 M M S y y y r r u 1 r y m 2 3 ( 2 u 4 4 S 2 5 ` : q q M 5 5 5 5 6 A 4 5 6 2 2 6 6 FX8 DX2 2 HX( 2 / 6 6 6 6 2 ( M ) 4 JX6 / 3 2 / 2 3 u ) 2 6 2 ) 4 4 ) 3 4 q GX6 2 HX( KXA y 1 u r y 1 JXOXHX3 GX/ q u JX3 6 GXHX3 HXJX3 3 HXGXJXu S ) 4 3 2 6 5 5 q y 2 `.2 2 2 6 6 ' FX' 6 6 3 3 5 GX/ 6 6 2 6 / 2 6 6 / / / 6 ' 5 FX6 / 2 ( 4 S ( 2 2 FXFX6 / 2 2 2 2 3 u u 4 S ) q h r 3 JXHX4 KX2 2 HX2 3 ) 4 S 3 HXHX( 2 6 2 3 4 4 4 KX3 ( 3 2 2 3 4 1 y q u 4 2 ( 2 6 6 6 6 6 6 ' 2 4 4 4 4 3 6 5 5 / 6 5 ' 6 6 2 2 ( 2 FX' ' 6 GX2 m ( 2 2 6 6 2 6 / 3 4 S S 2 5 ` 5 6 2 S S 4 4 4 2 ' ' 6 6 FX6 2 6 ' 6 6 / 6 2 M 4 4 6 #X@X#X#XAX#XAXVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.#XDXDX`.` FXFXFXFX5 ' FXDX8 $X] DXDXDXFXDXSXSXDXFXFX$X$X5 DX` FXGX' @ DXFXFXFX(.FXFX2 3 4 4 ( 3 6 DX5 ` ` DXDX8 3 3 FXFXDXDX(.5 FX' FXDXDX' 2 2 FXDXFX6 / 6 GX6 2 2 FX3 JX6 2 3 2 3 ( 3 JXGX6 ' 2 u A q r r y y k y 2 / 2 ' 3 u 4 JX) 2 / 1 q q S ' ' ' 5 M 1 2 FX6 2 GX6 5 DX6 m 2 / 2 2 GX/ 2 GXFXGX2 3 HXHX) 2 FX' 6 6 GX3 JXGXGX2 6 6 4 4 4 S 4 S ( HX3 HXHX3 2 r r A q y y y S S 1 HX4 y q ) ) 4 ( 3 JX3 4 3 3 3 3 3 4 ) 3 2 ) 3 6 / / 4 q q 3 FX2 / 6 6 ' 5 ' 5 6 1 ) 3 6 5 6 GX/ / 2 2 2 6 ' 6 # ' 6 6 6 6 / GXGX( 3 / FX/ 6 ' 6 6 ' 5 6 2 6 3 A 1 S 3 3 : u u ) 3 ( S 1 3 2 / / HX2 4 ) 2 / 6 HX' FXHX3 JX3 ) 4 S 2 2 JX4 ) ( 4 JXHXS 3 2 3 GXGX2 / 5 5 DX5 ( M ( 4 u S S 2 / 2 ' ' 6 6 6 6 2 2 FXFX6 FXDX` DX' 6 ' 6 6 6 6 6 6 6 3 4 JX3 ( 3 2 6 4 4 GX2 3 3 2 FXFX' 5 FX' 6 6 6 / N ' 5 / 2 2 m > 2 DX#XAXDXFXFXDXAX@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XDX@ DX` FXDXFX6 GXGXDX`.SXDX` DXDXDXDX`.SXDXFX` ] 8 FX5 FX6 GXDXDXDXDXDXFX6 FX' GX/ GX2 HX4 3 6 FXFXDX8 DXDX6 6 FX` DX8 DX5 FXFX` DX' GXGX/ DXDX' DXFX2 6 / 3 HXHXM HX6 5 GX3 GX5 GX( GXGX2 2 3 u q r k r y r l q 3 3 2 4 S 3 3 S 4 3 u u q S 6 6 6 6 3 A 4 5 6 6 6 ` 5 u 1 3 GX2 2 6 ' 6 FXFX6 6 2 HX( 3 2 GX' FXFX' 6 3 3 / 2 / 6 GX2 ) HX3 1 A 4 ) 4 ) ) S / ) 0 0 y r l k 1 u q KXKXy r u 4 u 3 3 3 4 3 / 2 4 ) 3 3 3 2 3 3 2 m # 2 S S q A 2 / 6 6 6 ` DXDX8 4 A 3 HX6 GX6 ' 2 6 6 / 6 6 5 ` 5 ' 6 2 2 ( 3 2 2 3 2 6 2 2 ' 6 6 6 / 2 / 2 3 4 4 4 4 4 ( 2 6 ' N ) 4 oXu 1 ( ` 6 6 2 3 HX2 2 JX2 FXGX2 HXGX/.3 4 3 FXFXGX3 2 2 4 3 4 1 2 3 ( 6 2 2 6 FX` ` FX2 HX3 S 4 u u 3 / 6 6 FX6 6 / 2 2 2 N N 6 6 ' ' GX6 ' 6 6 GX/ 2 2 / 2 ( M 3 / 2 ( HX4 S 6 6 GX2 2 6 6 6 5 FX5 5 ' 6 6 2 2 6 6 2 / / 2 2 3 > / ' 6 6 2 2 GXFXSX).@X@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#XAX#XSXDX(.FXFX` FX6 / FXDXDX` @ DX] $XDXDXSX` FXDXDX$XDXFXFX' FX`.@ (.FXFXFXFXFX6 (.@ ` 5 ' 2 ( S 3 3 6 `.` ' 5 (.FX` DX`.2 / ' FXDX5 6 FX/ 6 5 5 DX8 GX( GX' FX2 m GX6 FXSX2 ( FX6 6 GX/ GX2 JX) 2 2 3 S q y y r r A S y r 4 m 3 4 3 2 M c S 4 > / / / 2 q u ' 5 FX5 ` 2 u m ' 2 ( / 6 ' 5 8 5 / / 2 3 3 2 6 6 FX6 6 2 JX3 4 4 ( 6 6 6 6 M 4 ) S S S 3 S S 4 u 4 GX1 0 r r l r q 1 y u OXq y q q u OX2 3 ( DXFXu 4 GX( 2 GXHX3 HX3 JX(.GX( 4 r y 3 6 GX6 ` 8 5 2 S q 4 3 2 2 / 6 6 FX' FX5 FX6 ' FX6 6 2 2 FXGX2 GX6 / 6 / 2 6 6 GXFX6 2 3 3 3 S u M 2 ( 3 3 6 FX6 6 6 m JXKXu KX4 u k r 4 2 ( 3 u 3 6 2 ( ( ( 3 3 4 3 6 GX2 JXHX/ ( JX4 3 3 HX2 2 2 / 6 ' 5 3 4 3 3 JX3 4 4 3 6 ' 6 6 6 6 6 6 6 6 / 2 2 2 / 6 6 6 6 GX2 2 2 2 m 3 2 / 2 2 2 2 ( 3 2 3 3 ( ( m 2 2 2 2 FXDX5 DX' 6 ' 6 6 N / 6 6 6 6 2 2 6 6 / 6 FX' 6 2 3 M 3 GX5 DX#XVX).#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#X$X5 FX` 5 ' FX' FXGX/ FXDX`.SXDXDX`.FXFXDXDXSXDX' DX` DXDX5 FX' FXFX` DXFXFXFXOXFXGX( 3 4 4 ( 3 HX6 FXFX5 5 DXDXDX/ 6 FX` DX5 FX5 6 6 (.DXDX6 ( / 5 DX` 6 GX' 6 6 2 HXHX3 / 6 6 2 2 3 4 2 FXGX/ 6 2 3 u y k r u y k y 2 ( ) 3 6 6 S u S 3 ( 2 2 4 q 1 6 ' 5 ` FX4 S 5 6 2 2 6 6 DX8 GX> 6 6 2 ( 2 2 / 6 ' / 2 3 3 ) 4 2 / 6 ' / ( 4 u 3 GX2 2 FX) 1 u S 4 4 ( ( 1 q q u 4 4 A A A u XX1 y 4 JXS GXFX( S y 4 2 3 2 / ( 3 ( 3 ) 3 JX3 u 1 u 3 ( 6 5 5 ` A y y q M HX2 6 6 / 6 5 FX/ 3 4 1 4 / 6 ' GX` DX/ / 6 GX6 ' 6 / 6 2 ' DX` FX6 2 M 3 ( 3 2 6 / / ' FX6 6 5 / u u S u 1 ) u u M 3 4 JXu S / HX3 HX3 ( 3 ) 3 GX2 3 3 / GX2 M ) 3 ) ( 2 3 GX2 2 2 2 1 q A 2 ' 2 3 2 5 ' 6 FX' ' FX6 6 6 ' 6 / / 2 6 2 6 5 5 ' 6 6 / / 2 2 6 6 GXGXGX/ 6 GX2 6 / 2 2 2 2 / / 2 6 FXDX] 5 6 2 6 5 ' 2 m 2 N 2 2 6 GX6 6 6 6 6 6 6 ' 6 2 3 M 3 : ' SX@XVX#X#X#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#X$XDX$XFX` DXFX' 6 GX6 DXDXDX` DXDXDXDXDX] DXFX5 SXDXFX5 DXFX# FX6 DXDX(.FX5 GXGXGX3 2 FXFX6 3 4 4 2 6 FX`.8 DX5 5 FX5 DXDXDX5 ` ' GX5 5 6 GX2 GX5 DX` ' GX6 5 HX3 / 3 4 HX/ ' ' 2 GX2 2 / ( ( HX2 2 M u r l k r r r q 3 2 / ` 3 q u 3 ( 3 3 S 1 1 A 2 6 5 5 ` q y FXGX6 ' FX5 ( N u 1 / 6 GX/ 2 6 GX6 6 6 2 HX3 3 2 / GX6 6 FX3 ) HX2 6 2 FXDX3 1 S 4 JX2 HX2 ( ( GXGX2 6 y r u y u y y S A q HXKXy q 1 4 ) 4 2 2 3 ( HX3 ) 4 4 4 ) 2 2 ) 3 / GX6 4 y A q r 1 2 2 GX6 5 ` 6 6 2 u 4 4 3 6 FX6 6 GX2 2 6 / / 6 ' FX5 GX6 6 6 / 2 2 3 HX6 6 / 6 6 / 6 6 5 6 / 2 4 u 4 u u FXDX5 6 3 ) 3 GX+ S 3 2 ( ( 6 6 2 3 3 HX3 JX/ 5 FXGX2 GX2 ) 4 3 3 HX3 ) 3 ) S 1 1 ( 6 2 3 2 6 GX5 ` 5 DX5 ' FX5 6 m m 6 ' 6 6 5 ' 6 ' 6 6 6 6 GX6 6 6 6 6 6 6 6 FX' ' 6 6 6 6 6 6 6 2 2 2 6 / 6 6 GX6 2 S u S 3 2 6 GX6 6 FX' 6 6 6 / 6 FX6 ' FX2 2 ( 3 ( 6 `.#XVX#X#XAXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#X#X`.5 DXDXFX6 FX6 GX6 2 6 DXDXDXDXDXDXDXFXDX] $X5 FXFXFXDX5 FX(.5 # GXFX6 FX/ GX' FX5 ' 2 HXS u ( 2 5 DXFX5 FX6 5 DXDX( 2 5 GX6 ` FX' 6 GXGXFX` ] 5 3 6 2 / ' FX6 2 2 2 5 DX' 6 HX2 / 2 3 3 2 4 S S u q q y r r k y 4 3 u r k u 6 2 4 / S 1 u A 2 ' 5 DX2 r q 6 ' FX5 5 2 A 1 c 2 GX/ 2 2 6 FX5 5 6 GX2 ( 3 2 / 2 / / FX5 2 3 GX/ 2 ( 2 6 2 2 ( 3 3 ) HX2 2 3 GXOX) 6 A '.k q q q A q 1 1 q q q y q 1 ) 1X2 3 3 2 6 ' ( HXJX4 2 / JX3 ( 3 3 6 4 S 4 u y y 2 2 / 6 5 5 6 FX/ ) 2 4 3 5 ' 6 6 / 2 / ' 6 6 6 / ' 5 6 / 6 2 3 GX2 ( 6 6 / / 6 GX6 6 6 GX2 3 4 4 3 2 : 4 HXGXFXGX3 4 M 2 /.u 4 5 6 FX8 8 ' 3 2 2 4 4 2 FX6 2 GXFXGXJX) 3 S JX2 2 3 u 1 A u 2 2 HX2 6 GX5 DX5 DXDX' GX` 2 S 4 4 3 6 GX6 ' 6 6 6 6 6 / / 6 / 6 / 6 ' ' 6 ' 6 6 6 2 GX' GX6 GXN 6 ` 2 S q 4 5 ' 3 u u 4 4 ) 6 DX' 2 GX5 5 2 N 6 2 2 6 6 ' 6 6 / / 6 2 3 3 6 5 6 / DX#X@XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#XAXDXFX` 5 ' FXFX6 GX( 2 DXDXSXDX5 DXDX` (.DXSXDX' FXDXDXFX(.FX6 GX6 OX` SX5 FXGXGXFXGX( 3 JX) 4 4 4 ' DX5 FX5 DX] @ 3 4 GX6 FXDX5 6 6 6 6 FXDX8 GXGXHX/ DXFX6 6 GX' 6 2 2 2 HX( / GX3 3 3 4 4 S u 2 6 6 2 u 1 y k y q y k 0 y 2 / 3 GX6 S u S 2 ' 5 6 A q q m 5 5 5 5 2 u 1 2 ' 6 2 2 / GX5 DXFXGX6 / 3 2 / 6 / 2 FX6 2 ( 4 4 4 4 2 2 2 HX6 6 ) S 4 GX/ HX4 S 4 4 ) GXy k 2 GXHX) 4 S 1 y q q r r A 3 HXJX( 2 GX5 1 u 2 M 3 (.GX) ( HX3 ) ( 3 3 4 u 1 A 4 2 GX6 ' FX6 ( 4 4 3 3 / 5 FX6 GX/ / 6 FXFX` 5 FX6 6 2 GXFX/ / FX/ 2 6 GX6 6 / 6 DXFXGX2 3 ) 4 3 3 2 6 2 M GX6 HX3 4 3 4 JXJX1 u 3 ( y r q 3 HX( ) KX( GX2 ( HX( 3 3 3 S 3 GX2 HX4 4 4 4 3 2 2 2 2 2 ' 5 5 SXSX8 5 5 6 u u 4 3 S 3 FX6 ' 6 6 / 6 / 6 6 N 2 2 2 6 6 6 6 ' 6 6 GX2 2 / 2 2 2 ( 6 6 3 3 3 HX6 2 S S 3 ( 2 6 FXFX6 HX' 5 ` ' 6 ' 5 6 6 6 6 6 N 6 6 6 2 6 ' 6 6 ' N 2 / ' `.@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX$XFXFX' 5 ` GX2 GXFXFXFXFXFXDXDXDXDXDXSXDXGXFXDXDXDXFX6 GXGXFXFX5 (.@ ' FX6 6 6 6 ' FXFXGX3 4 3 / GXGXDXDXDX5 ' 6 ' FX` DXDX5 FX' 5 DXDX6 GX6 2 5 FX' 5 ' GX/ 2 4 S 3 ) & 2 6 ' 2 3 6 / : GXGX2 / 6 HX3 u y k k r y r 0 q 4 3 6 6 S 1 3 3 3 6 u q u c 4 6 ' ' 5 3 y M 5 GX2 2 GX6 ` 5 6 M ( 6 2 m 2 6 GXGXFX' / DX(.3 3 4 / 6 6 6 / 2 3 4 3 2 GX6 2 ) 4 4 4 3 3 3 6 6 2 2 HX/ 3 k r r y A q q 4 u u HX2 M u r 1 2 ) 2 DXOX4 3 3 3 ( 3 ) 3 4 S ) 3 4 M 2 GX6 ' 2 y q ( 2 2 2 6 5 FX' 6 FX' 2 2 6 6 5 FXGX/ 6 / GX6 6 / 6 6 / 2 6 6 5 ` ' GX2 ( 3 M S S M HX/ 6 N 2 ( 2 2 / 2 GXHXu q 1 u r L k 2 JX3 4 u 3 / 2 3 2 2 3 m ) 4 OXGXHX3 HX/ 2 ( ) S S S ( 6 6 GXFX6 4 u 2 2 1 u S 3 3 3 GX' 6 6 6 ' 5 6 6 FX6 6 6 6 2 2 N 6 6 / 6 ' 6 6 2 ( 3 3 3 2 3 M ) m 2 2 2 M 4 ( 2 3 6 6 6 6 / ' 5 ` 5 / 2 6 ' 6 2 / 6 N 2 2 6 / 2 / 6 ' DX5 ' 6 2 3 3 FX#X@XVX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@XSXFX' FXFX6 FX' 6 6 2 2 ' DXDX`.DXDXDXFX' FXDXDXSX' GXFXFXFX5 FXFX' FX5 FXFXFXDXFX6 GX6 / 3 u A 4 3 / DX` 6 6 5 6 6 ` ] 6 ) 6 6 FX` FX6 GX2 / FX6 5 SX6 3 3 2 6 6 2 ( 2 2 FX8 ( HXSX6 / FX2 2 / HXM S 3 HX1 A q r y r 0 q S 1 y q 4 3 3 S 3 1 c 1 q 4 GX= 6 ' : y 4 5 6 6 6 5 DX6 u S 3 6 / 2 2 6 GX6 5 ' 6 6 ' GXHX3 2 GX6 5 FXFXGX) 2 GX6 6 6 ' 2 4 4 3 3 1 q JX3 3 OXGX) 6 k h q r q ;Xr q 4 u 1 KXq y 1 S 3 S ( / 3 3 3 3 2 3 ) 3 ( S 4 3 3 ) 3 2 6 2 6 M y q 3 HX( 2 6 ' FX5 5 8 FXM 3 u ) FX' 6 6 2 2 6 6 ' 6 ' ' 6 6 6 6 FX6 6 6 / 2 3 4 3 2 / 2 6 5 FX6 6 6 6 / 3 4 u 4 3 ) 3 2 y q 3 (.OX4 u 4 6 ( 2 2 2 2 2 JX2 GX( 3 2 GX2 3 4 4 4 KX4 ) 2 2 GX2 u q q q q A 4 3 ' HX2 6 6 6 6 DX5 ' 5 FX' 6 2 / 5 ' ' ' / 2 6 ' FX6 GX6 GX2 2 / 2 ( 2 2 3 3 ( 2 2 FXFX6 2 / 2 3 3 2 5 DX8 2 3 3 3 4 S 3 3 3 2 / FX' GX6 / 6 5 6 6 6 6 2 2 3 M 2 FX$XAX@XAX#X#XVXAXVXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX`.SXFX' DXFX/ GX/ HX6 DXDXDX5 DXDX` @ ` DXSXDXGX` SX` FXFX` 6 GXGX' ` DX` FXFXGXGXGXHX4 4 JX( ( 4 2 6 FX6 5 FX` DXSX2 ) 6 6 5 ` 5 ' 6 2 2 6 DXDXFX2 ( 2 FXDX' 2 6 6 6 2 4 4 HX/ 2 2 GX2 HX2 3 4 3 FXFXFX6 2 4 A y 0 r 1 y l r ) 2 ) S 3 2 S u u S 3 2 / / 2 A 1 FX5 FX5 5 ' 4 q 3 6 GX2 2 6 ' 6 DXDXGX6 / / 2 3 ( / 2 ' FX6 6 2 > 3 ( ( 2 GX6 ' 2 OXGXA q u ) 3 JX4 4 4 2 6 r 0 k r r l y 4 1 q A u q r 1 4 u 3 2 3 ( ) / ` 2 S 4 3 HX2 JX3 HXHXN 3 4 / 4 u q u 2 2 6 ' 6 FX` 5 GX2 M u u ( 5 FX6 6 GX6 GX6 6 FXFX5 5 FX6 / 6 6 / 2 ( 2 2 ( GX' GX6 6 ' 6 6 ' 6 / 2 3 ) S u ) 2 ( ( m 4 ) 2 4 KX4 u 2 ' 6 ' % ' 4 HXGXGX3 ) ' 6 GXGX( 3 3 3 3 3 3 HX( 3 3 3 S u A A 4 GXFX2 3 / 6 2 FX` 5 FX2 2 6 ( 4 3 / FXFX2 2 6 5 5 5 ' 6 6 6 / GX' 6 6 6 6 / 6 2 / FXDX5 6 6 6 2 ( 4 JX2 2 3 GX' 6 4 4 3 3 3 3 6 ` 5 6 FX6 6 5 6 6 6 6 6 6 2 2 2 3 3 3 2 FX$XAX#XDXFXDXSX#X@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XVX@XSXFX' 5 5 6 ' 6 / 6 / GX' ` DXDXDXDXDX] FX' DXSXDX# 5 FXGXFXFXFX5 FXFXFXFX6 / GX3 4 FX$X6 2 3 4 u 2 GX/ 8 DXDX5 ' 6 FX5 ` DXDXFX5 6 GX5 DX' 2 HX2 6 5 5 FXGXGX/ 6 3 S ( 3 u GXFX6 2 3 3 3 ( 6 6 GXFX2 2 / 3 1 0 0 r q r l r M 2 4 3 5 3 u 4 S 3 m 2 2 4 q q m ' ' 5 5 / 4 4 5 6 2 2 / 6 FXDX8 ( m GX/ GX3 3 / 6 6 ' 6 FXGX3 / 4 4 3 2 ' 5 ' 6 2 ) 4 M 3 3 2 4 4 S 4 4 3 ( y r r r q u S u q q u q q u A u S 3 2 2 ' SX& y / 3 4 FXGX( HX2 2 3 S HXGXJXS r y ( 6 5 ' 5 ` 8 6 u A 1 4 2 FX5 6 FX' 6 6 6 6 ' 5 5 DXDX6 2 2 2 2 GX3 / FX2 2 6 6 6 GX6 6 6 / 2 2 ( 3 3 3 4 3 ( 2 HXM HX6 6 2 M 3 HX1 A 3 5 ( y r u 2 GXFXGX3 GXGXGXGX2 HX( 2 2 3 GX2 ( 3 3 ) S u q q 3 2 2 3 3 2 2 6 5 5 DX5 6 ' HX4 4 3 3 2 M 4 6 FX6 ' 5 ' 6 ' 6 / 6 GXGX6 GX6 6 6 N 6 6 / / 6 6 2 / / 3 3 3 KXu / 6 M 4 ' 6 2 2 / FXFX6 ' 5 5 DX5 6 ' / N / / 2 FX6 2 2 m 3 M 2 ' 6 N / 2 / / FXSX@X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#XAXSX5 FXFX6 6 FXGX/ HX3 / 5 `.SXDXFXFXDXFXFXDXDXSX` ' GXFX] 5 FX5 6 GX2 2 FXFX6 / GXFX(.GX6 JXS 1 A 3 3 / ` 5 FX' ' FXFXDXDX3 m FX6 6 FX5 6 2 2 2 GX' 5 8 GX2 6 ( 3 6 5 2 S GX6 6 FX2 / / 2 / 2 2 / HX( 3 4 M 1 q r r y r 0 k 1 3 6 2 q q > 3 3 HX( 4 1 u A 3 6 6 5 5 2 y S DX6 2 6 6 ` 5 / M A 3 6 2 2 2 2 6 ' # 6 6 DXGX6 OX) 2 GX6 ' FX2 3 3 ) 2 6 / 6 6 2 u u 4 ) 4 2 GX) 1 u S S 2 u y 1 q 1 u y q u u KX3 ( 3 3 y y OX3 3 6 2 ( 3 3 3 ) JXGX3 JXKXu u HX' ' 5 FX` FX1 q q u HX6 FXFX6 ' FX5 5 5 6 6 2 2 ' 6 6 FX/ 2 FX6 2 ' ' GX6 / / 6 6 6 GX6 6 2 HX( 3 ) ) 3 2 2 m GX2 6 6 ' 6 5 HXKXu 1 u u q l r 4 2 ) JXHX( / / 2 2 2 3 3 2 ) ( 6 2 2 HX2 3 S 4 4 ) 4 ( 2 ( 2 2 2 ' FX5 6 2 8 6 S S 4 JX4 3 2 6 6 6 ' 6 6 6 6 6 6 ' 6 6 6 N 2 6 / 6 @ ' 6 2 2 2 ( 3 2 2 2 M m / 2 3 3 S 3 6 / 2 2 6 / 2 6 ` 5 5 DX5 6 6 6 2 2 2 2 ' GX2 2 / 6 6 / : / @ ` FX2 m 3 3 6 DXAX@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X` 6 GX' FXGXHX2 / FX@ DX5 ' FXDXDXDX5 DXSXDXGX' DXDX@ ' FXGX2 HXFX5 ] DX@ ' GX/ GXGX) 4 4 S 4 4 1 2 ` ' 5 FX5 5 DXDXS 1 / / 6 ` 5 ' 6 GX6 / ' DXDX2 ( 3 3 6 FX6 ' 6 2 2 ' 6 6 2 3 HX( ( 3 3 2 ( 4 3 GXFX6 M 1 q q y 0 0 q u y 0 r ) 2 3 ( / 4 1 S u 3 ' 6 FXDXS r u 5 ' ' ' DX2 1 A y u 2 / GXGX2 GX6 FX5 6 GX2 2 2 3 2 GXN GX6 6 GX) ( 2 ' 6 2 ' DX( 4 3 ) 4 2 / HXHX6 FXHXJX2 3 l y 1 y r l y KXu A A KXq r q 4 3 ( HX( 2 ( JX( 3 3 4 ) ) u S 2 HX4 2 FX5 : 2 4 y A A u 2 GXN 6 / 6 5 5 5 6 / ( S S 3 / 5 FX6 6 DXFX6 6 6 6 6 6 / / 6 / 6 ' 6 6 / 2 3 3 2 6 2 2 6 DXFXGX' 5 ' 5 3 u 4 ( 3 1 ) 6 2 M ( 4 u 4 4 GX2 3 2 / ( 3 4 4 3 OXGXJX2 ( HX2 2 3 3 3 3 ) 2 2 2 2 GX6 6 1 q ) ) 4 3 ( ( 2 ' 5 ' / FX6 N 6 6 6 2 6 5 5 ' 6 6 6 6 2 5 ] @ DXDX6 2 2 2 2 6 / 2 2 2 ( m 2 2 m 2 2 ( 2 2 2 2 2 / DX] DX' 3 3 6 N 2 2 N 2 N 6 6 2 GX6 6 6 5 ' FXFX6 / 2 m 3 3 / DXSX#X@XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X).#XDX' ' FX6 / 6 2 6 / 2 ( GX5 DXDXDX5 DXSXFX/ FX5 DXDXFX6 GX2 GXDXFX6 6 FX' GX6 / ( GXFX1X5 GX3 4 u 3 6 ( 6 DXDX5 5 2 3 6 6 5 ` DXDX5 6 / 6 5 DX6 2 3 3 ' ` 6 6 FX/ HX6 / 4 3 ) S 2 GX2 3 ( HX4 4 6 DX$XFXGX6 3 ) q y 0 k y r k k u / ( 3 2 2 4 u u 3 6 ' 5 2 y q A 2 ' FX5 5 > A u 4 2 6 GX2 2 6 FX' DX` 6 / 2 2 3 ( GXGX6 6 GX' FX3 4 3 / 2 ) m 2 HXGX' 2 3 3 3 HX/ HXGX/ 3 ( ' y '.y u 1 A 1 q 1 1 q 1 1 y r q M 3 JX2 ( 3 2 2 FXGXS 4 M 4 3 GX3 S 2 / 3 S 3 4 S S 1 q 3 2 GX6 6 5 FX5 ' 6 FX4 S 3 2 FX' ' 6 6 6 / / 6 / 6 5 FX6 ' 6 / GX6 / 2 GXGX3 2 ' GX2 ' ' FX6 6 5 FX6 m 4 4 > 3 u 4 FXDXHX4 3 3 3 3 4 3 2 2 6 2 2 ( 3 ( 4 2 ( 3 ( ( / GX/ 3 ) 3 3 4 2 / 3 4 ( ( ( u q 1 S 2 GX/ 3 6 5 FX6 5 5 6 FX' ' 6 6 2 3 ( 2 2 5 5 6 6 5 FX' 5 FX6 / / GX6 6 FX@ ` FX6 6 / 6 GX/ 2 / 2 / 6 6 6 2 2 ' 8 / 2 2 GX6 3 4 3 2 N 2 / GX' / 6 FX6 6 6 6 6 6 / / 2 m : 3 m 2 FXSXVX@X#XAX@XAXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XmX#X#XSXmXDX/ FX5 / / / 2 ( 2 ` DXSXDXFXDX` 6 FX5 (.DX8 6 GX` FX' 5 FX6 / 2 GX6 FXFXGXFX' 6 GX6 3 ) S y u 4 4 ' ` FX6 6 FX' FXFXDX5 3 / 6 / ' DX' 2 / 3 2 5 FX6 ` ' 3 2 2 3 3 m S 4 HXFX/ 6 2 3 2 / 6 6 GXGX/ 3 3 3 4 A y r k r r 0 l 1 2 2 6 / u u > HX3 2 6 u u u y M 6 ' 5 5 2 u 4 6 6 / / 2 / 6 5 8 ] 2 HX6 / 2 2 2 / 2 6 ' FX2 3 3 ) JX4 4 6 GX2 GX6 6 2 ) S ) 2 6 ( 3 3 3 3 3 2 y y DXFXFX3 S 3 1 y y XXu k y u 4 ( HX3 2 ' 8 S q 3 ) 4 2 ' FX3 HX6 JX4 HXHX3 ( u y q M 2 2 / 6 / FX5 2 4 4 S 3 4 2 ' 6 6 6 ' ' 6 6 / 2 ' 5 ` 5 5 GX2 6 2 m 2 ' 6 2 6 6 2 6 ' 6 6 6 ' FXN 3 ) 4 4 3 m 4 u 3 FXGX3 S S 3 3 u A S 6 FX5 8 8 FX3 JXGX( 3 3 2 6 6 # 2 2 HX) 3 2 3 JX( 3 3 4 S 1 u 4 HX2 2 2 2 ' 6 FXDX5 DX5 6 5 5 ( 4 u ) 3 2 6 6 6 ' ' 6 6 6 6 ' 6 6 6 6 6 ' FX` FX' 6 ' @ 5 6 6 6 6 6 6 6 6 ` 3 q 3 ' 6 6 M u 4 S S ) 3 GX6 6 6 2 FX6 ' 6 / 6 N / = 6 6 6 / 2 2 2 3 2 / DXSXDX5 $X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#XAX@X#XFX' DX5 6 2 GX6 2 6 FX' 6 FXDXDX5 DX(.DX] FXGXFXSX5 GX(.DX' HX2 HXGXDX` 6 ' FX/ 2 / 3 S S 4 ) 2 4 S 2 ' 6 6 6 6 5 5 8 2 1 2 GX6 5 5 6 GXGXHX2 ' FX` 5 GXM 3 HX6 FX2 3 2 ( 2 / / 2 GXSXHX3 GX2 3 3 ( 3 S 4 u 1 1 y r r r 0 0 A S A r y S 3 3 u 3 ) A 4 S y 3 5 6 6 5 3 q u / 6 FX6 GX6 5 5 HXS u 2 6 GX/ 2 6 6 / / FXDX6 2 ( 3 3 4 2 5 ` 6 2 ' 2 JX4 3 6 ' 2 3 4 4 3 ) 4 4 4 / FXGX2 3 HX/ y r A q 1 y r q JX) u KX/ 2 ( r q / 3 4 2 GX( 3 HX2 ) 3 ( 3 3 3 A q S KX4 2 6 2 6 5 DX4 q S 2 GX4 3 6 ' 6 6 6 ' 5 5 5 5 ' 6 6 ' 6 3 ' $XGX2 6 6 6 ' 6 ' 6 GX6 ' FX' 6 2 2 3 4 M 3 2 6 2 2 ) 3 HX4 S 4 3 3 JXKX1 1 3 4 y y A 4 2 /.3 ) 3 / GXGX2 3 ( 3 3 3 4 S 2 # 2 3 4 u 1 u 2 GX3 ( 2 / 2 6 DX5 DXDX5 5 DX' 1 u S 4 ) 3 ( ' FX6 6 6 / 6 GX6 6 ' 6 / 6 6 6 6 6 6 6 ' 6 GX6 6 6 GX6 ' / 2 6 / 4 S 6 6 4 u 3 HX3 3 2 / FXFX' 6 FX` DXDX5 2 m 2 2 2 GX6 FX6 2 ' `.5 2 3 / FX6 m 6 ] #XVX).#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#XFX' FXFX/ / 6 / 2 ( 4 3 6 5 DXDXDXFXFXDX' GX5 DXDX6 GXFXGXFXFXGX' FX6 GX2 6 / 2 HX3 3 2 FX5 2 ) S 1 u / 2 6 5 DXDX` N 2 6 6 5 DXDX' 6 ' 6 2 ' 5 ' 2 3 JX2 FXDX` 2 2 / GX' 2 4 3 6 HXu 3 / 3 4 3 3 4 S u u 3 / 2 S q q q r y q r k r S m 3 4 ) 3 S c u S ) 2 2 GX6 m q q GX5 6 ' FX5 DXS q A 3 6 2 2 2 / ' FX5 5 6 FX/ 2 HX( 3 / 6 GX5 FX' ' 3 4 3 GX6 GX2 / 3 S ) 3 4 1 S GX2 3 3 GX3 / 3 h k q q y k y 1 u 1 KXHX1 y 1 3 3 4 S 2 HX3 JX2 3 3 3 3 ) 3 S S 3 JX4 > ( 2 / FXm 6 1 q 3 / HX3 2 6 6 ' 6 6 5 5 6 / 6 6 2 6 5 6 GX5 5 6 GX6 6 GX6 5 6 / 6 6 5 5 ' 6 2 2 3 3 ) 4 ( 6 / 6 / GX2 3 3 3 M 3 OXXX1 y q q k L r 3 JX4 ) 4 4 / 2 2 ( HX3 3 3 ) S JXHX/ ( ) 4 3 ) 3 / 2 2 3 2 2 6 5 5 DX8 5 2 2 5 m u ) 4 ) 3 ) 2 6 FX' 6 / 6 6 2 / 6 N / 6 / 6 ' 6 ' 6 6 6 6 6 / / / 6 6 2 2 2 3 4 GXGX( 3 1 S / / 2 2 GX6 6 6 6 6 DX8 5 3 3 ' 6 2 N / 2 N 2 6 / 6 FX/ 2 6 5 ' 6 6 2 2 2 / DXVX@XAXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX` 6 6 / 6 ' 6 ( 4 3 GX` DXDX5 6 FXDXFXFXFXDXSXDXGX/ 6 DX` 6 FX6 GX2 HXHX/ DX2 ) FXGXGX' HX3 & A y u 3 ( 5 ` 5 FX' 6 GX' FXDXFX/ 5 5 2 / FXFX/ 2 ( ( 2 5 5 5 6 HX2 FX6 4 4 3 3 u S 3 2 / ( 2 ) 3 2 3 3 6 2 2 6 4 u A y 0 r q r k y 4 3 4 4 GX6 S u 4 4 ) 2 6 m 3 A y 3 5 FX5 5 5 2 u u 6 6 GX2 2 6 6 6 5 DXFX' 6 6 2 2 3 ( / 6 FX5 6 6 2 4 ) 3 ( 3 / $X5 6 6 ) 4 u A 4 2 4 S JXHX3 HX' y 0 y y r k k q 1 A 1 u oXq y y 4 S u 2 ( ) 3 3 ( / 3 4 3 3 S 2 2 3 FXGXM 2 ' 2 4 3 S 1 3 2 ( 2 6 GX5 5 5 ` 5 GX( 3 JXS / ` ' ' ' 6 GX6 6 6 / 6 6 ' 6 5 ` ' ' ' 6 6 GX' 2 ( 4 S S 3 2 6 DXFX2 / 6 6 3 ( 3 S u u q q A k q ( ) ) 4 4 4 2 2 3 2 6 ( 3 ( M 4 ( 2 2 3 ) HXGX2 ( 4 S ) 4 3 6 ' 2 / 6 3 u q q q q 4 S JX2 ( 2 6 6 5 ' 6 6 ' 6 6 / 2 m 3 2 6 6 6 ' 5 5 / 6 ' 6 2 2 2 : ( m m HXm 3 M 3 3 M 4 3 2 2 2 2 / 2 2 2 HX5 ` ` / 3 M 2 6 3 3 : N 5 6 6 6 6 2 2 6 5 ' 6 6 6 6 / : 3 M 2 DX#X@X#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#XAX).#XDXGX6 6 ' 6 ( 2 / 2 6 6 6 6 GXFXDX` DX5 FX] DX/ GX5 (.5 # GXGX2 ( GXDX6 8 6 GXFX2 2 GX2 4 S u u u u S S 2 5 6 6 6 / ' ` DX4 A 3 6 HX2 5 6 6 2 2 2 2 6 5 `.5 3 2 / 3 M 3 3 3 ) : 2 6 8 GX1 2 FX' GXHX2 HX2 2 JX4 u y k 0 k r k 0 r 4 m 2 ' 4 1 S u 3 ) 2 3 4 u A y u 5 6 5 ` GX4 A 3 5 GX2 2 6 6 FX8 SXm 4 6 6 6 / 2 2 2 2 6 5 ' 2 2 M 3 3 ) 4 3 ' o DX' GX> u S 4 2 2 JXS S 4 4 M S 2 4 r r r k y 1 u A q 1 S q 1 u u u 3 OX2 2 3 / 8 # 4 4 ( 3 HX6 HX2 DXFX( 2 2 ) 3 ) 4 A S 2 2 / 6 5 ' 5 5 8 DXM 4 3 S u 2 6 FX6 6 6 6 / ' ' 6 6 6 6 5 DXDX6 6 6 6 ' 6 / 2 3 3 2 2 m 6 ' 5 ' / 6 FXN 3 / 3 1 S 2 3 3 4 S 1 u 3 HXHXS S 4 3 / 8 ` 5 ` 2 4 OXFXHX2 ( 2 GX/ 2 3 4 4 4 4 S 3 2 2 HX4 u u u u r q ) 4 2 2 3 2 6 6 6 6 ` 5 FX6 5 6 M 4 3 : / 6 5 DX6 2 GXFX5 ' ' 6 / 2 2 ( 2 2 2 6 2 m 3 GX6 / 2 2 2 2 2 / 2 4 HX6 6 FX2 3 2 ) 4 4 u S 4 ( 6 FXFX' 6 FX5 / 6 6 6 GX6 6 / 2 m M M 2 DX@X@X#X#X#X#XAXVXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAXDXFX$XDX2 GX` 2 ( 2 3 3 3 6 5 FXDXDXFX5 DXFX2 6 FXFXDX5 / 2 HX2 ' FX' # ' 6 GX/ 2 / 2 3 2 GX' 6 ) S 1 u 6 ' 2 6 5 @ DX5 2 u S / / ' DXFX6 2 / 2 2 ' DXSX/ ) 3 4 2 FX` FX3 2 6 / 6 ` S q FX6 / 2 2 2 ( HX3 4 4 3 3 3 A y y y r 0 r u ) 1 k r u M 3 3 JX3 4 1 1 q u 6 6 FXDX6 A r > ` 6 2 ' FX5 FX2 2 u 1 ( 6 / 2 2 2 GX6 GXFX6 6 2 HXHX3 3 2 / 6 5 5 6 3 3 M 3 / 2 6 ' 2 S S 4 4 4 ( 2 3 u 1 u 4 u 3 S y y y q XXXXq u 3 4 ) GX2 2 6 A y 3 HX( 2 GX2 2 2 ( 2 3 S JXHX3 1 u 3 3 ( 6 FX' 5 FX] 6 u A S u u HX/ 6 6 6 6 / 6 5 5 5 ` 5 5 5 ' 4 : 2 2 GX6 2 2 2 m 2 FX@ ( 6 6 ' 5 6 6 ' 6 2 m : 4 u S S ) 3 3 3 4 u 4 4 ( + ) 1 S 2 3 u y u 3 HXFXGX( 3 2 6 6 / ( JX3 ( 3 3 u 4 2 6 GX3 ( 2 4 1 1 KXS 3 HX( ( 6 ' GX/ 5 8 ` / 2 / 2 3 3 3 3 2 ' 2 m 3 2 6 5 FX6 6 6 FX6 / 6 6 6 / 6 / / 5 DX` DXFX' 6 6 / 6 / 4 3 ( ) 3 / N 3 4 3 ( 3 ) ) 2 FX5 FX' 6 5 ' 6 6 6 6 6 6 6 N 2 2 2 2 3 3 / SX#X#X@X#XSX`.$X#X@XVXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X).@XFXGX5 6 GX( ( 6 ( ( FXDXDX5 6 FXDX' 6 ' FXDX] 5 HX/ FXFX6 FX6 / GXGX6 6 ' # / GXFX/ 2 2 2 4 S 1 y S ) M 5 DX5 ' 6 2 GXGX6 5 5 ` FX' 2 / FX5 8 5 3 3 3 ( 6 5 ` 6 HX6 GX6 2 S 4 ( 3 2 HX2 GX( 2 3 4 4 / 6 6 GX6 2 ) KXq k 0 k q y k l q 3 2 ( 3 GX3 u u 1 4 6 6 5 ` 2 y r 1 ` 6 ' FX5 DXS q A c 4 2 GX2 2 2 / FXFX5 ' 6 GX2 2 3 3 2 2 6 6 FX' / / GX2 GX6 6 FXDXGX1 u 4 ) 3 2 ' (.GX2 3 ) 4 2 2 y q q y 1 y r q 4 u u KXS q q y 1 3 ( JX2 2 2 ( ( 3 2 3 4 3 3 ) u 3 ( ) : 6 6 ' 5 DX5 A r q u 4 3 / 6 / 6 ' 6 6 ' FX6 6 ' 5 DX8 3 4 2 2 ( 6 GXHXFX5 2 GX5 6 6 / 6 6 6 6 FX6 2 ( 3 3 3 M 2 3 2 6 2 3 2 6 N 2 3 GXoXu KXS k h L L r / HX3 ) 4 2 6 GX2 HX2 2 HX( 4 S / 6 GX/ ( 2 2 u A 4 JX3 ( : HX( GX6 2 6 DXDX5 6 6 6 ( GX2 ) JX4 3 3 3 / ' ' 6 FX5 6 6 6 / 2 GX6 6 6 6 6 FX5 FXFX5 6 6 ` FX/ m 6 6 3 3 3 3 2 FX/ 3 GX2 2 2 6 FX' ' 5 5 ` 5 5 5 ' 6 6 6 6 6 6 6 GXN / 2 2 M : 2 6 5 FX6 2 2 6 FXDX_.VX@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#X#X#XFX6 FX5 2 ( GX2 3 2 / 2 / 6 5 DX5 5 (.5 DXDXGX/ DXSXFXGX6 FX/ ( / GX6 FX5 6 GXGX2 HX2 ( 4 1 1 q u > u ) 5 ` 6 2 6 6 / 6 5 DXGX> 6 2 6 5 ` ` 2 ( 3 3 2 GX` 5 5 / ( / 6 3 4 2 2 S S 3 2 6 FX2 3 ( ( GX/ 2 HX2 / 2 M 4 q 0 k y r k k r S 2 2 3 6 5 4 u S ( / 6 ' 5 u q q q GX5 FX5 5 6 S A 1 M 6 GX/ GX2 / 6 FX` DX6 / 6 / 2 2 / / GX6 6 ' 5 FX2 ( 3 / / 3 2 6 4 3 2 2 3 HX3 / @ GXm HX3 ) 2 / 1 l y y r r y A u 1 A y KX1 r q S 4 S 3 2 2 2 3 4 ( ( ) 3 3 4 S S 3 ( 3 ) 2 6 5 ' 6 S q 1 y q 2 3 / 6 6 ' 5 5 5 ` / 4 ) 3 4 2 2 2 ] SX6 2 6 GX/ 6 GX6 6 GX6 6 6 GX6 6 FX5 FXFXGX( 3 ) 4 3 2 / 2 ( 2 5 DXFX5 6 ) u KXS S 4 l L '.q 2 3 u S 4 ) 2 / 2 ( 2 ( 2 2 3 3 GX/ 2 2 HX2 4 4 3 4 ) 2 3 2 / 3 2 2 6 DX' 6 3 HX5 ` 4 4 ) 3 3 3 3 6 FXFX6 6 5 5 6 6 6 6 / 6 / N / N 6 6 6 / 6 6 / 2 2 6 2 2 2 2 2 m 2 2 2 3 HXJX3 6 2 ( 3 / 6 6 6 GX5 DX5 DXFX5 5 5 5 ' 6 N 6 N 6 6 6 N 2 HX2 / 2 2 6 6 6 2 3 M 3 6 DX#X@XVX#XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#X_.DX/ / 2 / / 2 m 4 S 3 / FXDXDXDXFX6 DXFXGX# ` DXDX6 6 6 HX/ # GXFX' 2 ' ' 6 / ( ( 2 u u HX6 / 3 4 u u : 6 ( / ' 6 FX` DXm A 3 2 GXFXDX5 2 2 2 2 HX2 6 5 ] GX> 2 3 3 / FXFXm 4 2 / ' 8 3 GXGX3 6 / 3 ( ( 3 3 4 4 4 q y r k r r 0 0 A 4 3 / u 1 u S 2 3 3 / > q c A q 3 6 ' 5 ' 2 1 1 M 5 GX/ 2 2 6 ' FXDX8 6 2 GX/ / 2 2 / 6 ' FX6 ' 6 2 3 4 4 3 4 3 2 ( 2 / 6 ' 2 S u 4 HXGX2 3 3 ( 3 > 2 r 0 A KX) 3 3 1 S S y 1 A r r q 4 4 HX( / 3 4 2 2 6 3 4 ) 4 4 3 HX3 4 4 3 6 2 3 ( 4 S S q y 4 ( 6 ' 6 6 5 ` 5 6 2 4 3 3 4 u 4 ' DXDX' 6 6 2 / 6 6 / 6 6 / ' 5 6 6 ' ' 6 FX' 2 2 3 3 GX2 2 6 6 / 6 ' ' 6 6 5 m u S > 3 4 q r S 2 2 4 KXJX4 u ) GX3 ( 3 2 GX2 2 ( 3 3 ( 3 2 3 HX6 GX2 ( 3 3 3 3 2 2 ( 6 / 2 / u q 1 ) u 4 / 2 ( / FX5 ' 6 6 5 FX5 ' 5 ' ' 6 6 6 / 2 2 / N / 6 ' 5 FX6 6 2 2 2 3 m 2 / 2 2 2 m 3 m HX3 3 m ( 3 3 m 2 m / 5 8 DX' 6 / 5 5 2 4 ) 2 2 2 / 2 / FX6 6 6 ' 6 ' ' 6 6 6 6 ( M 3 2 FX`.@X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX@XDXGX2 2 6 ' HX4 ( 2 2 FXDXDX5 6 GXFX5 FXFXFXDX8 (.GX2 2 5 ` ' # 6 HXGXHX3 / GX2 S 4 2 6 @ HXm 4 u q y S 2 4 6 DX5 5 5 2 4 / 6 FX5 DX8 6 / 6 / 2 6 FX8 FX( 2 M JX6 ' 6 / GXFX6 / 2 S 3 DX3 JXGX2 HX3 3 ( 3 u u 1 1 4 4 u y r q k l A 1 y l y S 4 2 S 4 HXu 1 u q A 2 6 6 / 6 2 q q 3 ' 6 6 2 / FX5 8 / ( S 4 6 6 6 2 2 / 6 6 ' 6 6 ( ( GX3 OXGX3 / 6 6 6 / 6 2 4 u 3 ( 2 ' HX4 3 3 4 ) 3 3 u GX5 6 2 2 2 u r A y y q y y u 4 ) 4 3 3 2 ' FX' 4 3 HX4 3 / GX3 4 ( 2 2 4 3 HX3 HX4 q q 1 3 2 6 6 FX' 5 6 2 3 3 3 ) 3 4 3 5 5 5 6 6 / 2 6 ' 6 6 6 / 6 5 ` 5 6 6 6 6 2 / 2 ( 2 DXDX2 6 6 / 6 6 6 ' 6 6 2 2 u S S 1 1 S 2 6 3 3 3 HX/ 4 1 u 2 6 6 DX] SX5 2 ( 3 3 JX2 / ( 6 GX( 3 HXGX2 4 4 3 3 ( 2 ) ( 3 1 y y q 3 6 6 3 ( 6 ` FX6 6 ` 5 5 ' 6 6 5 DX5 6 5 5 6 6 6 2 / 6 ' 5 ` DXFXFXDX6 2 GX6 2 / / 2 2 2 / 6 6 5 DXFX/ 2 2 2 2 2 2 ' 5 6 2 2 6 ( ) u u 3 2 2 2 2 6 FX' 6 6 6 6 6 6 6 6 6 6 2 2 2 : 3 3 6 SX#X@XAX#X#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@XSXFX` ' 6 ( 2 6 3 2 2 2 3 2 ( 6 FXFX5 5 6 (.8 5 3 2 GX5 DX6 2 GX2 3 4 ( # 5 DX4 GXGXHX2 ( 2 u A q y r S S S / ' 6 6 2 2 6 6 FX` DXDX6 : / 2 6 FX5 FX2 ( 2 3 / FX6 GX6 2 DX2 3 M u ( JXu 2 2 3 3 4 3 3 S S S u HX6 2 6 4 q y q k l r r k l A M 3 S S 3 3 u c u 4 ( 3 2 2 6 m u q 4 2 ' ' 6 ' 5 5 S y * S 2 / 6 GX2 / 6 GX6 FX6 6 2 / 2 HX/ 2 2 / 6 FX' 6 6 ( 3 HX2 6 / 6 6 3 M 4 4 3 HXM 2 2 ( HXHX3 ( 3 0 l q y y y y y S KXA A 3 6 3 S A 4 3 ( M 3 2 ( 4 ) 3 3 4 3 ( 3 JXJX1 q q 4 ) 3 2 / 2 2 ' 6 4 A S u 3 HXHX2 ' 5 6 6 6 2 / 6 6 6 5 FX5 5 ' 5 6 2 / 2 m 2 DXFX3 6 FX2 / GX6 6 6 6 ' 6 / 6 3 4 4 3 3 1 u M 3 2 3 ) S M 3 KXu u KX2 ) q q A u 4 3 HX2 ) 2 HX2 GX/ 3 3 2 2 3 4 3 M 4 JX4 ) 3 S u q q 4 3 6 2 3 3 6 5 6 6 5 5 5 DX/ 2 6 n 3 4 3 4 2 5 5 5 2 / 6 6 6 FX' 6 ' ' / 6 6 / 6 6 6 6 6 6 2 / FXDX` 6 / / 2 6 6 6 2 : ( 6 2 2 S 1 u u 1 u 3 2 ( FX' 6 / 6 FX6 6 6 6 N / 6 6 N / 2 2 2 m M 3 / 5 SX#X#XSX_.#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X#X6 2 6 6 m m 3 4 ) 3 m 6 FX5 ` FXFX@ DX' HXGXFX5 DXFX2 / ( HX3 FX5 ' ' FX6 / 2 HX2 2 4 S 3 > 4 S S q A 2 6 N / N GX' 5 DXFX6 u 1 GX/ 6 FX5 6 / 2 3 ( 6 6 ' 5 ` GX3 3 m 3 JX3 4 ) ( 3 ' 6 3 ) 3 GX6 2 ( / 2 ( / 2 4 S 1 r 0 r y y k r 1 3 3 S 3 FX4 u S 3 3 4 2 2 m 3 u q u 2 ' FX5 5 5 3 q q M 6 6 / / / 6 5 5 5 DX' 6 6 / 6 2 2 3 2 6 6 ' ' FX5 FX2 3 2 6 6 2 6 6 / 3 4 ( 3 M JXHX3 2 ( GX( 4 2 2 l k q y r r y 1 4 q q 4 ) q y 1 4 3 ( 3 3 / 3 ) 3 2 3 ) 2 ( 3 ) 4 4 ( 3 ( 3 3 / 2 2 6 6 6 4 q S 3 GX/ 2 2 6 ' ' FXFX6 5 ' 2 2 ' 5 5 FX5 FX2 6 6 2 2 FXFX6 / 6 / 2 6 6 6 6 6 5 FX6 2 ( 3 4 M ( 6 N 3 4 ( ( 3 ) 4 3 3 JXKXKX1 KXq h 0 '.h q ) 2 GXHX( 3 2 GX/ 2 2 2 2 ( 3 3 ) ( # ( ( 3 4 4 u 3 S u ( 2 ( 2 / 6 6 5 DX` DX5 6 5 ` 3 1 ( 3 u ) / / 2 6 6 ' FX5 6 6 6 6 6 6 6 6 6 2 / 6 6 6 6 6 6 6 6 6 6 6 6 2 GX6 6 5 2 ) 2 6 ( S 4 4 3 3 ) 3 2 FXDXFX6 2 FX` 5 ' 6 6 2 2 6 6 6 ' 6 6 N / 6 2 : 3 2 ' 6 / 6 6 `.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#XAX#X#X#XSX_.#X#X$XSXSX#XSXSX_.#X#X#XSX_._._.mXSX_._.mX_.#XSXSXmX_._.#X_.SX$XSXmX_.SXSX_.$XSX#X).@X#X#XSX$XDX$X_.mXSXmX_.mX_.#XSX$XDX$XSXSXmX#X_._.mXSXSXSX#X_.mX#X#XSX$XSX$XSXSX$XSX#XSXSX#X#X_.$XSXVX#XSX_.SXmXSXmXSX_.#XSX1XDX] $X8 DXFXDXSXSXSX#X#XSX$XSXSXSXSX_.SXSX$XSX] $XSX_.mX_.#XSXSXSXSX#X#X_.mXSXmX#X_.#X#X_.SX#X_.SX_.SXSXSX_.#X_.#XmX#X#X_.SXSXSX_.$XSXSX_.#X#XSX_.SX$XmX#XSX$XSX#XSXSX$X@XSXFXSX$X$X$XDXDX$X$X$X$X$XDX`.$X$XSXSXSXSXSXSXSXmX_.SXSXSXSXSXSXSX_.#X#XSX$X_.mXSX#X_.SX_.$XDX$X_.SXSXSXSX$XSX#XmX_.#X#XSXSXSXSX$X_._.#X#XSX_.#XSX#X_.SX_.#XSXSX_.SX_._._._.mX_.SXSXSXSXSXSX#X_.mXSXSXSXSXSXSXSXSX$XSXSXSX$X`.SXDXSX5 ' `.SXSXmX_.o $XSXSXSX$XSX_.SXSXSX_.$X#X#X#XSXSXSX$X$X_.o DXSXSXSX_.SXSX_.#X_.#XmX_.#X#X#XDX$X#XSXSXSXSX$XSX#X_.mX#XSX_._._.SX_.mX#X_.mXSX#X_.SX_.SX_.SXSX$XSX_.mX_._.SXSXSX_.#XSXSX_.SXSX#X_.#X_.SXSX_.#XSXSX_.SX#XSX#X_.#X_.SX_.SX_.#XSX_.SX#X#X_.#X_.SXSX$XSXSXSX$XSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#X#XAX).#XVX#XVX#X#X#X#X#X#X#X#X#X#XAX#X#X#X#X#XAX).#XVXAX#X#X#XAX#X#X#XVX#XVXAX@XAX#X#XAXVX).@X@XVX#X#X#X#X#X#X#X#X#XAX@X@X#X).#X#X#X#X#X).#XAX#X#XAX#X#X#X).VX).@X).VXAX#XVXAX#X#XAX@XAX#X#XVXAXAX).#X#XAXVX).VX@X@X@X@X@X@X@X@X).#X@XAX#X).VX).@X#X).#XVXAX).@X@XVXAX#X#X#X#X#X).@X@XAX#X#X#XAX#XAX#X#X#X#X#X#X#X#X#XAX#XVX#X#X#X#XAX#X#X#XAX#XVX#XVX#X#X#X#XAX@X#XAX@XAX#X).VX#XAX#X).VX#X@X+X@X@X@X@X@X@X@X@X@XVX).@X@X@XVX).#X).#X).#X@XAX#X).@X#X).#X).#XVXAXAX).VXAX#XAX#X#X#XAX@X@X@XAXAX#X).AX#X#X#X#X#X#X#X#XAX#X#XVX#X#X#X#X#X#X#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X).#X).@X#X#X#X#X).).#X).#X@X).@X).#X@XVX).@X@X@X@X+X+XVX).#XAXAX).VX).#XVX).#XVX#X).#XVX).AX#XAX).#X).@XVXAX@X@X#X).VX#XAX#X#X#X#X#X#X#X#X#X#X@X).AX).#X@XAXAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X).#X#X@XVXAXAXAXVX).#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX#X#XVX).#X#X#X#X#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X_.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#XSX+X. @XSX#X#X#X#X#X#X#X#XAX#X#X#X#XAX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX+X. AXmXSX@X. ZXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX@X. CXSXVX. . SX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSX. . SX#X#XAX#X#XSX. . AXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XSXCX. @XSX#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X_.ZX. BXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X% #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#XZXu [.XXZX#X#X#X#X#X#XSX. . #XmX_.CX. AXSX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XZXGX[.HX+XZX_.[.1 ZX#X#X#X#X#X#X#X#X#X#X#X#X#X#XZXOX[.;XZXOXh y ZX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XAX+X[.l ZX#X+X. @X#XZXy [.DXZX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X@X1 [.FXZX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XZXoXh oXZX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X", +"#X#X#X#X#X. [. 1 . #X#X#X#X#XAX+Xl '.$XZX+X Date: Thu, 8 Jan 2026 16:00:16 +0000 Subject: [PATCH 173/189] revert back to torch==2.7.1+cu128, but make this the default environment --- environment.yml | 4 ++-- environment_blackwell.yml | 36 ------------------------------------ 2 files changed, 2 insertions(+), 38 deletions(-) delete mode 100644 environment_blackwell.yml diff --git a/environment.yml b/environment.yml index aba0683c9..f59638173 100644 --- a/environment.yml +++ b/environment.yml @@ -6,8 +6,8 @@ dependencies: - python=3.10 - setuptools=59.5.0 - pip: - - torch==2.9.0 - - torchvision==0.24 + - --extra-index-url https://download.pytorch.org/whl/cu128 + - torch==2.7.1+cu128 - tqdm==4.65.0 - mrcfile==1.4.3 - starfile>=0.5.6 diff --git a/environment_blackwell.yml b/environment_blackwell.yml deleted file mode 100644 index b6ad83d51..000000000 --- a/environment_blackwell.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: relion-5.0 -channels: - - conda-forge -dependencies: - - pip - - python=3.10 - - setuptools=59.5.0 - - pip: - - --extra-index-url https://download.pytorch.org/whl/cu128 - - torch==2.7.1+cu128 - - torchvision==0.22.1+cu128 - - tqdm==4.65.0 - - mrcfile==1.4.3 - - starfile>=0.5.6 - - loguru==0.7.0 - - scikit-learn==1.3.0 - - umap-learn==0.5.3 - - matplotlib==3.7.2 - - morphosamplers==0.0.13 - - pydantic==1.10.18 - - napari[all]==0.4.18 - - tsnecuda==3.0.1 - - PyQt5==5.15.9 - - typer==0.9.0 - - click<8.2.0 - - biopython==1.81 - - fastcluster==1.2.6 - - seaborn==0.12.2 - - dill==0.3.7 - - numpy==1.26.1 - - scipy==1.11.2 - - git+https://github.com/3dem/relion-classranker - - git+https://github.com/3dem/relion-blush - - git+https://github.com/3dem/DynaMight - - git+https://github.com/3dem/topaz - - git+https://github.com/3dem/model-angelo From ba3b86be9622e9c5a2300e7d0f45b693e588c8ed Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 8 Jan 2026 16:00:43 +0000 Subject: [PATCH 174/189] this is the old environment; also removed torchvision from topaz dependency --- environment_pascal.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 environment_pascal.yml diff --git a/environment_pascal.yml b/environment_pascal.yml new file mode 100644 index 000000000..c61f03684 --- /dev/null +++ b/environment_pascal.yml @@ -0,0 +1,37 @@ +name: relion-5.1 +channels: + - conda-forge +dependencies: + - pip + - python=3.10 + - setuptools=59.5.0 + - pip: + - torch==2.0.1 + - tqdm==4.65.0 + - mrcfile==1.4.3 + - starfile>=0.5.6 + - loguru==0.7.0 + - scikit-learn==1.3.0 + - umap-learn==0.5.3 + - matplotlib==3.7.2 + - morphosamplers==0.0.13 + - pydantic==1.10.18 + - napari[all]==0.4.18 + - tsnecuda==3.0.1 + - PyQt5==5.15.9 + - typer==0.9.0 + - click<8.2.0 + - biopython==1.81 + - fastcluster==1.2.6 + - seaborn==0.12.2 + - dill==0.3.7 + - numpy==1.26.1 + - scipy==1.11.2 + - skan==0.12.2 + - opencv-python==4.10.0.84 + - git+https://github.com/3dem/relion-classranker + - git+https://github.com/3dem/relion-blush + - git+https://github.com/3dem/DynaMight + - git+https://github.com/3dem/topaz + - git+https://github.com/3dem/model-angelo + - ".[vis]" From 68512155e10249d78ec45b78d6bd71745d2af4d1 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 3 Feb 2026 13:15:19 +0000 Subject: [PATCH 175/189] new text for background dynein structure --- src/gui_mainwindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui_mainwindow.cpp b/src/gui_mainwindow.cpp index 155b8db69..b6d1d574d 100644 --- a/src/gui_mainwindow.cpp +++ b/src/gui_mainwindow.cpp @@ -2569,9 +2569,9 @@ Please also cite relevant papers when you used external programs or their algori * Postscript plots are made using CPlot2D from http://www.amzsaki.com\n\n\ \ About the start up screen:\n\n\ -The map shown is the cryo-EM map of a fungal pheromone receptor STE2 dimer\n\ -without G proteins. Application of Blush algorithm improved the map\n\ -from the left to the middle panel and allowed automatic chain tracing by ModelAngelo (right panel).\n\ +The image shown is a cryo-EM model of the dynein-dynactin complex on a microtubule resolved by \ +the Carter lab (MRC-LMB) using signal subtraction and local refinements in RELION: \ +Chaaban & Carter, 2022 (PMID: 36071160), Singh et al., 2024 (PMID: 38547289).\ ") ShowHelpText *help = new ShowHelpText(HELPTEXT); From 9d94289ad221c1b6d2ecf4ed3f24ed5dbb58e735 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 3 Feb 2026 13:15:44 +0000 Subject: [PATCH 176/189] repair bug fourfilter --- src/apps/image_handler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/image_handler.cpp b/src/apps/image_handler.cpp index 21106a409..af3d2bed5 100644 --- a/src/apps/image_handler.cpp +++ b/src/apps/image_handler.cpp @@ -932,7 +932,7 @@ class image_handler_parameters avg_ampl = Iop(); } - if (fn_mult != "" || fn_div != "" || fn_add != "" || fn_subtract != "" || fn_fsc != "" || fn_adjust_power != "" ||fn_fourfilter != "") + if (fn_mult != "" || fn_div != "" || fn_add != "" || fn_subtract != "" || fn_fsc != "" || fn_adjust_power != "") if (XSIZE(Iop()) != xdim || YSIZE(Iop()) != ydim || ZSIZE(Iop()) != zdim) REPORT_ERROR("Error: operate-image is not of the correct size"); From b07a33d2012fa8be59b7ab443a14924e77ccb609 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 3 Feb 2026 13:16:23 +0000 Subject: [PATCH 177/189] remove skip_direction2 from helical orientations --- src/healpix_sampling.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/healpix_sampling.cpp b/src/healpix_sampling.cpp index 5c9e425e7..cede5d133 100644 --- a/src/healpix_sampling.cpp +++ b/src/healpix_sampling.cpp @@ -1156,13 +1156,6 @@ void HealpixSampling::selectOrientationsWithNonZeroPriorProbabilityFor3DHelicalR pointer_dir_nonzeroprior.clear(); directions_prior.clear(); - // If prior_tilt is not 90, then these are tilted images: DON'T perform direction2 angular searches - bool skip_direction2 = false; - if ((prior_tilt > 0. && prior_tilt < 90.) || (prior_tilt > 90. && prior_tilt < 180.) ) - { - skip_direction2 = true; - } - if (is_3D) { // If tilt prior is less than 20 or larger than 160 degrees, print a warning message @@ -1204,7 +1197,7 @@ void HealpixSampling::selectOrientationsWithNonZeroPriorProbabilityFor3DHelicalR } } - if (!do_auto_refine_local_searches && !skip_direction2) + if (!do_auto_refine_local_searches) { // Assume tilt = (0, +180) // TODO: Check if "(tilt_angles[idir] > 0.01) && (tilt_angles[idir] < 179.99)" is needed @@ -1326,7 +1319,7 @@ void HealpixSampling::selectOrientationsWithNonZeroPriorProbabilityFor3DHelicalR } } - if (!do_auto_refine_local_searches && !skip_direction2) + if (!do_auto_refine_local_searches) { // Assume tilt = (0, +180) // TODO: Check if "(tilt_angles[idir] > 0.01) && (tilt_angles[idir] < 179.99)" is needed From 0332f23c391eaa7f519b44041256d40848bfe063 Mon Sep 17 00:00:00 2001 From: scheres Date: Tue, 3 Feb 2026 17:21:08 +0000 Subject: [PATCH 178/189] allow tilted images for helices to set tilt prior --- src/helix.cpp | 38 +++++++++++++++++++++++++++++--------- src/helix.h | 20 ++++++++++++++------ src/preprocessing.cpp | 20 +++++++++++--------- src/preprocessing.h | 12 ++++++++---- 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/src/helix.cpp b/src/helix.cpp index b1c355e48..f7a7af143 100644 --- a/src/helix.cpp +++ b/src/helix.cpp @@ -1821,7 +1821,8 @@ void convertHelicalTubeCoordsToMetaDataTable( RFLOAT box_size_pix, bool bimodal_angular_priors, bool cut_into_segments, - RFLOAT tilt_prior) + RFLOAT tilt_angle, + RFLOAT tilt_axis) { int nr_segments, MDobj_id; RFLOAT psi_deg, psi_rad, x1, y1, x2, y2, dx, dy, xp, yp, step_pix, half_box_size_pix, len_pix, psi_prior_flip_ratio, pitch; @@ -1940,7 +1941,8 @@ void convertHelicalTubeCoordsToMetaDataTable( if (!cut_into_segments) { - MD_out.addObject(); + RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); + MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, ((x1 + x2) * 0.5)); MD_out.setValue(EMDL_IMAGE_COORD_Y, ((y1 + y2) * 0.5)); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, (tube_id + 1)); @@ -1985,6 +1987,7 @@ void convertHelicalTubeCoordsToMetaDataTable( } #endif + RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, xp); MD_out.setValue(EMDL_IMAGE_COORD_Y, yp); @@ -2277,6 +2280,12 @@ void removeBadPsiHelicalSegmentsFromDataStar( return; } +RFLOAT calculateTiltPriorFromPsi(RFLOAT psi, RFLOAT tilt_angle, RFLOAT tilt_axis) +{ + if (fabs(tilt_angle) < 0.01) return 90.; + else return ACOSD(SIND(tilt_angle) * SIND(psi - tilt_axis)); +} + void convertHelicalSegmentCoordsToStarFile_Multiple( FileName& suffix_coords, FileName& suffix_out, @@ -2286,7 +2295,8 @@ void convertHelicalSegmentCoordsToStarFile_Multiple( RFLOAT Ydim, RFLOAT boxsize, bool bimodal_angular_priors, - RFLOAT tilt_prior) + RFLOAT tilt_angle, + RFLOAT tilt_axis) { int total_segments, nr_segments, total_tubes, nr_tubes; FileName fns_coords; @@ -2304,9 +2314,9 @@ void convertHelicalSegmentCoordsToStarFile_Multiple( FileName fn_out; fn_out = fn_coords_list[ii].beforeFirstOf(suffix_coords) + suffix_out; if (format_tag == XIMDISP_COORDS_FORMAT) - convertXimdispHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors, tilt_prior); + convertXimdispHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors, tilt_angle, tilt_axis); else if (format_tag == EMAN2_FORMAT) - convertEmanHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors, tilt_prior); + convertEmanHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors, tilt_angle, tilt_axis); else REPORT_ERROR("helix.cpp::convertHelicalCoordsToStarFile_Multiple(): BUG Invalid format tag!"); total_segments += nr_segments; @@ -2399,7 +2409,8 @@ void convertXimdispHelicalSegmentCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors, - RFLOAT tilt_prior) + RFLOAT tilt_angle, + RFLOAT tilt_axis) { int nr_segments_on_edges, nr_segments, nr_tubes; RFLOAT x, y, x_old, y_old, psi_deg_old, psi_deg, half_box_size_pix, len_pix, psi_prior_flip_ratio; @@ -2475,6 +2486,7 @@ void convertXimdispHelicalSegmentCoordsToMetaDataTable( } #endif + RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); nr_segments++; MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, x); @@ -2507,7 +2519,8 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( RFLOAT box_size_pix, bool bimodal_angular_priors, bool cut_into_segments, - RFLOAT tilt_prior) + RFLOAT tilt_angle, + RFLOAT tilt_axis) { int nr_segments, nr_tubes; RFLOAT xp, yp, dx, dy, x1, y1, x2, y2, psi_deg, psi_rad, half_box_size_pix, len_pix, psi_prior_flip_ratio; @@ -2585,6 +2598,7 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( if (!cut_into_segments) { + RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, ((x1 + x2) * 0.5)); MD_out.setValue(EMDL_IMAGE_COORD_Y, ((y1 + y2) * 0.5)); @@ -2625,6 +2639,7 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( } #endif + RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, xp); MD_out.setValue(EMDL_IMAGE_COORD_Y, yp); @@ -2657,7 +2672,8 @@ void convertEmanHelicalSegmentCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors, - RFLOAT tilt_prior) + RFLOAT tilt_angle, + RFLOAT tilt_axis) { int nr_segments_on_edges, nr_segments, nr_tubes; RFLOAT x, y, x_old, y_old, psi_deg, half_box_size_pix, len_pix, width, psi_prior_flip_ratio; @@ -2744,6 +2760,7 @@ void convertEmanHelicalSegmentCoordsToMetaDataTable( } #endif + RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); nr_segments++; MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, x); @@ -2777,7 +2794,8 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( RFLOAT box_size_pix, bool bimodal_angular_priors, bool cut_into_segments, - RFLOAT tilt_prior) + RFLOAT tilt_angle, + RFLOAT tilt_axis) { int nr_segments, nr_tubes; RFLOAT xp, yp, dx, dy, x1, y1, x2, y2, psi_deg, psi_rad, half_box_size_pix, len_pix, psi_prior_flip_ratio; @@ -2872,6 +2890,7 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( if (!cut_into_segments) { + RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, ((x1 + x2) * 0.5)); MD_out.setValue(EMDL_IMAGE_COORD_Y, ((y1 + y2) * 0.5)); @@ -2912,6 +2931,7 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( } #endif + RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, xp); MD_out.setValue(EMDL_IMAGE_COORD_Y, yp); diff --git a/src/helix.h b/src/helix.h index 68e3b9ae2..769656c55 100644 --- a/src/helix.h +++ b/src/helix.h @@ -226,7 +226,8 @@ void convertHelicalTubeCoordsToMetaDataTable( RFLOAT box_size_pix, bool bimodal_angular_priors = true, bool cut_into_segments = true, - RFLOAT tilt_prior = 90.); + RFLOAT tilt_angle = 0., + RFLOAT tilt_axis = 90.); void combineParticlePriorsWithKaiLocalCTF( FileName& fn_priors, @@ -254,6 +255,8 @@ void removeBadPsiHelicalSegmentsFromDataStar( FileName& fn_out, RFLOAT max_dev_deg = 15.); +RFLOAT calculateTiltPriorFromPsi(RFLOAT psi, RFLOAT tilt_angle, RFLOAT tilt_axis); + void convertHelicalSegmentCoordsToStarFile_Multiple( FileName& suffix_coords, FileName& suffix_out, @@ -263,7 +266,8 @@ void convertHelicalSegmentCoordsToStarFile_Multiple( RFLOAT Ydim, RFLOAT boxsize, bool bimodal_angular_priors = true, - RFLOAT tilt_prior = 90.); + RFLOAT tilt_angle = 0., + RFLOAT tilt_axis = 90.); void convertHelicalSegmentCoordsToMetaDataTable( FileName& fn_in, @@ -286,7 +290,8 @@ void convertXimdispHelicalSegmentCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors = true, - RFLOAT tilt_prior = 90.); + RFLOAT tilt_angle = 0., + RFLOAT tilt_axis = 90.); void convertXimdispHelicalTubeCoordsToMetaDataTable( FileName& fn_in, @@ -301,7 +306,8 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( RFLOAT box_size_pix, bool bimodal_angular_priors = true, bool cut_into_segments = true, - RFLOAT tilt_prior = 90.); + RFLOAT tilt_angle = 0., + RFLOAT tilt_axis = 90.); void convertEmanHelicalSegmentCoordsToMetaDataTable( FileName& fn_in, @@ -313,7 +319,8 @@ void convertEmanHelicalSegmentCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors = true, - RFLOAT tilt_prior = 90.); + RFLOAT tilt_angle = 0., + RFLOAT tilt_axis = 90.); void convertEmanHelicalTubeCoordsToMetaDataTable( FileName& fn_in, @@ -328,7 +335,8 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( RFLOAT box_size_pix, bool bimodal_angular_priors = true, bool cut_into_segments = true, - RFLOAT tilt_prior = 90.); + RFLOAT tilt_angle = 0., + RFLOAT tilt_axis = 90.); void makeHelicalReference2D( MultidimArray& out, diff --git a/src/preprocessing.cpp b/src/preprocessing.cpp index f39b5ae55..152c313ff 100644 --- a/src/preprocessing.cpp +++ b/src/preprocessing.cpp @@ -100,7 +100,8 @@ void Preprocessing::read(int argc, char **argv, int rank) helical_rise = textToFloat(parser.getOption("--helical_rise", "Helical rise (in Angstroms)", "0.")); helical_bimodal_angular_priors = parser.checkOption("--helical_bimodal_angular_priors", "Add bimodal angular priors for helical segments"); helical_cut_into_segments = parser.checkOption("--helical_cut_into_segments", "Cut helical tubes into segments"); - helical_tilt_prior = textToFloat(parser.getOption("--helical_tilt_prior", "Set the tilt prior of helices to this value (in degrees)", "90.")); + helical_tilt_angle = textToFloat(parser.getOption("--helical_tilt_angle", "Tilt angle to set the tilt prior of helices (in degrees)", "9.")); + helical_tilt_axis = textToFloat(parser.getOption("--helical_tilt_axis", "Tilt axis direction (inplane angle in deg; 0 = X-axis)", "90.")); // Initialise verb for non-parallel execution verb = 1; } @@ -678,7 +679,7 @@ void Preprocessing::addOneHelicalSegment(MetaDataTable &MD, RFLOAT xcoord, RFLOA void Preprocessing::convertHelicalLineCoordsToMetaDataTable( FileName& fn_in, MetaDataTable& MD_out, int& total_segments, int& total_tubes, int nr_asu, RFLOAT rise_A, RFLOAT pixel_size_A, RFLOAT Xdim, RFLOAT Ydim, RFLOAT box_size_pix, - bool bimodal_angular_priors, bool cut_into_segments, RFLOAT tilt_prior) + bool bimodal_angular_priors, bool cut_into_segments, RFLOAT tilt_angle, RFLOAT tilt_axis) { // Check parameters and open files @@ -741,7 +742,7 @@ void Preprocessing::convertHelicalLineCoordsToMetaDataTable( total_segments++; //std::cerr << "is_first= "< Date: Fri, 6 Feb 2026 15:55:09 +0000 Subject: [PATCH 179/189] default tilt angle should be zero --- src/preprocessing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preprocessing.cpp b/src/preprocessing.cpp index 152c313ff..687254a4c 100644 --- a/src/preprocessing.cpp +++ b/src/preprocessing.cpp @@ -100,7 +100,7 @@ void Preprocessing::read(int argc, char **argv, int rank) helical_rise = textToFloat(parser.getOption("--helical_rise", "Helical rise (in Angstroms)", "0.")); helical_bimodal_angular_priors = parser.checkOption("--helical_bimodal_angular_priors", "Add bimodal angular priors for helical segments"); helical_cut_into_segments = parser.checkOption("--helical_cut_into_segments", "Cut helical tubes into segments"); - helical_tilt_angle = textToFloat(parser.getOption("--helical_tilt_angle", "Tilt angle to set the tilt prior of helices (in degrees)", "9.")); + helical_tilt_angle = textToFloat(parser.getOption("--helical_tilt_angle", "Tilt angle to set the tilt prior of helices (in degrees)", "0.")); helical_tilt_axis = textToFloat(parser.getOption("--helical_tilt_axis", "Tilt axis direction (inplane angle in deg; 0 = X-axis)", "90.")); // Initialise verb for non-parallel execution verb = 1; From 9db40ec147cd8ed670f5b2b0b810514590276bd4 Mon Sep 17 00:00:00 2001 From: scheres Date: Thu, 12 Feb 2026 16:45:54 +0000 Subject: [PATCH 180/189] add option to search tilt in CTFFINF4.1; not exposed on GUI as doesn't allow power spectra precalculation, nor float16 --- src/ctffind_runner.cpp | 9 ++++++--- src/ctffind_runner.h | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ctffind_runner.cpp b/src/ctffind_runner.cpp index 801e0938f..7a77fc7ba 100644 --- a/src/ctffind_runner.cpp +++ b/src/ctffind_runner.cpp @@ -78,7 +78,7 @@ void CtffindRunner::read(int argc, char **argv, int rank) phase_step = textToFloat(parser.getOption("--phase_step", "Step in phase shift (in degrees)", "10.")); nr_threads = textToInteger(parser.getOption("--j", "Number of threads (for CTFIND4 only)", "1")); do_fast_search = parser.checkOption("--fast_search", "Disable \"Slower, more exhaustive search\" in CTFFIND4.1 (faster but less accurate)"); - + do_tilt_search = parser.checkOption("--tilt_search", "Run CTFFIND-4.1.15+ tilt search algorithm"); // Initialise verb for non-parallel execution verb = 1; @@ -784,8 +784,11 @@ void CtffindRunner::executeCtffind4(long int imic) } else fh << "no" << std::endl; - // Set determine sample tilt? (as of ctffind-4.1.15) - fh << "no" << std::endl; + // Set determine sample tilt? (as of ctffind-4.1.15) + if (do_tilt_search) + fh << "yes" << std::endl; + else + fh << "no" << std::endl; // Set expert options? fh << "no" << std::endl; diff --git a/src/ctffind_runner.h b/src/ctffind_runner.h index ba8799822..69c0d6bce 100644 --- a/src/ctffind_runner.h +++ b/src/ctffind_runner.h @@ -154,6 +154,9 @@ class CtffindRunner // Disable "Slower, more exhaustive search?" in CTFFIND 4.1.5- bool do_fast_search; + // As of CTFFINF-4.1.15 one can search for the tilt angle and axis too. This will be written to the logfile, but for now relion doesn't do anything with it. + bool do_tilt_search; + // Which GPU devices to use? std::string gpu_ids; std::vector < std::vector < std::string > > allThreadIDs; From a74afdea4594e83b2fe15a046028eddad7ad6c18 Mon Sep 17 00:00:00 2001 From: scheres Date: Wed, 18 Feb 2026 10:21:14 +0000 Subject: [PATCH 181/189] removed incorrect handling of tilted images for filaments --- src/gui_jobwindow.cpp | 3 --- src/helix.cpp | 58 ++++++++++++++----------------------------- src/helix.h | 24 +++++------------- src/pipeline_jobs.cpp | 2 -- src/preprocessing.cpp | 21 +++++++--------- src/preprocessing.h | 12 ++------- 6 files changed, 36 insertions(+), 84 deletions(-) diff --git a/src/gui_jobwindow.cpp b/src/gui_jobwindow.cpp index d4a91650d..ec0992c14 100644 --- a/src/gui_jobwindow.cpp +++ b/src/gui_jobwindow.cpp @@ -1095,9 +1095,6 @@ Pixels values higher than this many times the image stddev will be replaced with place("helical_nr_asu", TOGGLE_DEACTIVATE); place("helical_rise", TOGGLE_DEACTIVATE); - //current_y += STEPY/2; - //place("helical_tilt_prior", TOGGLE_DEACTIVATE); - group5->end(); guientries["do_extract_helix"].cb_menu_i(); diff --git a/src/helix.cpp b/src/helix.cpp index f7a7af143..21f875e5d 100644 --- a/src/helix.cpp +++ b/src/helix.cpp @@ -1792,11 +1792,11 @@ void extractHelicalSegmentsFromTubes_Multiple( FileName fn_out; fn_out = fn_in_list[ii].beforeFirstOf(suffix_in) + suffix_out; if (format_tag == RELION_STAR_FORMAT) - convertHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments, tilt_prior); + convertHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments); else if (format_tag == XIMDISP_COORDS_FORMAT) - convertXimdispHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments, tilt_prior); + convertXimdispHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments); else if (format_tag == EMAN2_FORMAT) - convertEmanHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments, tilt_prior); + convertEmanHelicalTubeCoordsToMetaDataTable(fn_in_list[ii], MD_out, nr_segments, nr_tubes, nr_asu, rise_A, pixel_size_A, Xdim, Ydim, box_size_pix, bimodal_angular_priors, cut_into_segments); else REPORT_ERROR("helix.cpp::extractHelicalSegmentsFromTubes_Multiple(): BUG Invalid format tag!"); total_segments += nr_segments; @@ -1820,9 +1820,7 @@ void convertHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors, - bool cut_into_segments, - RFLOAT tilt_angle, - RFLOAT tilt_axis) + bool cut_into_segments) { int nr_segments, MDobj_id; RFLOAT psi_deg, psi_rad, x1, y1, x2, y2, dx, dy, xp, yp, step_pix, half_box_size_pix, len_pix, psi_prior_flip_ratio, pitch; @@ -1941,12 +1939,11 @@ void convertHelicalTubeCoordsToMetaDataTable( if (!cut_into_segments) { - RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, ((x1 + x2) * 0.5)); MD_out.setValue(EMDL_IMAGE_COORD_Y, ((y1 + y2) * 0.5)); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, (tube_id + 1)); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, 0.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -1987,12 +1984,11 @@ void convertHelicalTubeCoordsToMetaDataTable( } #endif - RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, xp); MD_out.setValue(EMDL_IMAGE_COORD_Y, yp); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, (tube_id + 1)); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, pixel_size_A * len_pix); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2294,9 +2290,7 @@ void convertHelicalSegmentCoordsToStarFile_Multiple( RFLOAT Xdim, RFLOAT Ydim, RFLOAT boxsize, - bool bimodal_angular_priors, - RFLOAT tilt_angle, - RFLOAT tilt_axis) + bool bimodal_angular_priors) { int total_segments, nr_segments, total_tubes, nr_tubes; FileName fns_coords; @@ -2314,9 +2308,9 @@ void convertHelicalSegmentCoordsToStarFile_Multiple( FileName fn_out; fn_out = fn_coords_list[ii].beforeFirstOf(suffix_coords) + suffix_out; if (format_tag == XIMDISP_COORDS_FORMAT) - convertXimdispHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors, tilt_angle, tilt_axis); + convertXimdispHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors); else if (format_tag == EMAN2_FORMAT) - convertEmanHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors, tilt_angle, tilt_axis); + convertEmanHelicalSegmentCoordsToMetaDataTable(fn_coords_list[ii], MD_out, nr_segments, nr_tubes, pixel_size_A, Xdim, Ydim, boxsize, bimodal_angular_priors); else REPORT_ERROR("helix.cpp::convertHelicalCoordsToStarFile_Multiple(): BUG Invalid format tag!"); total_segments += nr_segments; @@ -2408,9 +2402,7 @@ void convertXimdispHelicalSegmentCoordsToMetaDataTable( RFLOAT Xdim, RFLOAT Ydim, RFLOAT box_size_pix, - bool bimodal_angular_priors, - RFLOAT tilt_angle, - RFLOAT tilt_axis) + bool bimodal_angular_priors) { int nr_segments_on_edges, nr_segments, nr_tubes; RFLOAT x, y, x_old, y_old, psi_deg_old, psi_deg, half_box_size_pix, len_pix, psi_prior_flip_ratio; @@ -2486,13 +2478,12 @@ void convertXimdispHelicalSegmentCoordsToMetaDataTable( } #endif - RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); nr_segments++; MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, x); MD_out.setValue(EMDL_IMAGE_COORD_Y, y); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, pixel_size_A * len_pix); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2518,9 +2509,7 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors, - bool cut_into_segments, - RFLOAT tilt_angle, - RFLOAT tilt_axis) + bool cut_into_segments) { int nr_segments, nr_tubes; RFLOAT xp, yp, dx, dy, x1, y1, x2, y2, psi_deg, psi_rad, half_box_size_pix, len_pix, psi_prior_flip_ratio; @@ -2598,12 +2587,11 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( if (!cut_into_segments) { - RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, ((x1 + x2) * 0.5)); MD_out.setValue(EMDL_IMAGE_COORD_Y, ((y1 + y2) * 0.5)); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, 0.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2639,12 +2627,11 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( } #endif - RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, xp); MD_out.setValue(EMDL_IMAGE_COORD_Y, yp); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, pixel_size_A * len_pix); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2671,9 +2658,7 @@ void convertEmanHelicalSegmentCoordsToMetaDataTable( RFLOAT Xdim, RFLOAT Ydim, RFLOAT box_size_pix, - bool bimodal_angular_priors, - RFLOAT tilt_angle, - RFLOAT tilt_axis) + bool bimodal_angular_priors) { int nr_segments_on_edges, nr_segments, nr_tubes; RFLOAT x, y, x_old, y_old, psi_deg, half_box_size_pix, len_pix, width, psi_prior_flip_ratio; @@ -2760,13 +2745,12 @@ void convertEmanHelicalSegmentCoordsToMetaDataTable( } #endif - RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); nr_segments++; MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, x); MD_out.setValue(EMDL_IMAGE_COORD_Y, y); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, pixel_size_A * len_pix); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2793,9 +2777,7 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors, - bool cut_into_segments, - RFLOAT tilt_angle, - RFLOAT tilt_axis) + bool cut_into_segments) { int nr_segments, nr_tubes; RFLOAT xp, yp, dx, dy, x1, y1, x2, y2, psi_deg, psi_rad, half_box_size_pix, len_pix, psi_prior_flip_ratio; @@ -2890,12 +2872,11 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( if (!cut_into_segments) { - RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, ((x1 + x2) * 0.5)); MD_out.setValue(EMDL_IMAGE_COORD_Y, ((y1 + y2) * 0.5)); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, 0.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); @@ -2931,12 +2912,11 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( } #endif - RFLOAT tilt_prior = calculateTiltPriorFromPsi(-psi_deg, tilt_angle, tilt_axis); MD_out.addObject(); MD_out.setValue(EMDL_IMAGE_COORD_X, xp); MD_out.setValue(EMDL_IMAGE_COORD_Y, yp); MD_out.setValue(EMDL_PARTICLE_HELICAL_TUBE_ID, nr_tubes); - MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, tilt_prior); + MD_out.setValue(EMDL_ORIENT_TILT_PRIOR, 90.); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR, -psi_deg); MD_out.setValue(EMDL_PARTICLE_HELICAL_TRACK_LENGTH_ANGSTROM, pixel_size_A * len_pix); MD_out.setValue(EMDL_ORIENT_PSI_PRIOR_FLIP_RATIO, psi_prior_flip_ratio); diff --git a/src/helix.h b/src/helix.h index 769656c55..e537cabff 100644 --- a/src/helix.h +++ b/src/helix.h @@ -225,9 +225,7 @@ void convertHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors = true, - bool cut_into_segments = true, - RFLOAT tilt_angle = 0., - RFLOAT tilt_axis = 90.); + bool cut_into_segments = true); void combineParticlePriorsWithKaiLocalCTF( FileName& fn_priors, @@ -265,9 +263,7 @@ void convertHelicalSegmentCoordsToStarFile_Multiple( RFLOAT Xdim, RFLOAT Ydim, RFLOAT boxsize, - bool bimodal_angular_priors = true, - RFLOAT tilt_angle = 0., - RFLOAT tilt_axis = 90.); + bool bimodal_angular_priors = true); void convertHelicalSegmentCoordsToMetaDataTable( FileName& fn_in, @@ -289,9 +285,7 @@ void convertXimdispHelicalSegmentCoordsToMetaDataTable( RFLOAT Xdim, RFLOAT Ydim, RFLOAT box_size_pix, - bool bimodal_angular_priors = true, - RFLOAT tilt_angle = 0., - RFLOAT tilt_axis = 90.); + bool bimodal_angular_priors = true); void convertXimdispHelicalTubeCoordsToMetaDataTable( FileName& fn_in, @@ -305,9 +299,7 @@ void convertXimdispHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors = true, - bool cut_into_segments = true, - RFLOAT tilt_angle = 0., - RFLOAT tilt_axis = 90.); + bool cut_into_segments = true); void convertEmanHelicalSegmentCoordsToMetaDataTable( FileName& fn_in, @@ -318,9 +310,7 @@ void convertEmanHelicalSegmentCoordsToMetaDataTable( RFLOAT Xdim, RFLOAT Ydim, RFLOAT box_size_pix, - bool bimodal_angular_priors = true, - RFLOAT tilt_angle = 0., - RFLOAT tilt_axis = 90.); + bool bimodal_angular_priors = true); void convertEmanHelicalTubeCoordsToMetaDataTable( FileName& fn_in, @@ -334,9 +324,7 @@ void convertEmanHelicalTubeCoordsToMetaDataTable( RFLOAT Ydim, RFLOAT box_size_pix, bool bimodal_angular_priors = true, - bool cut_into_segments = true, - RFLOAT tilt_angle = 0., - RFLOAT tilt_axis = 90.); + bool cut_into_segments = true); void makeHelicalReference2D( MultidimArray& out, diff --git a/src/pipeline_jobs.cpp b/src/pipeline_jobs.cpp index a50e71f2b..0c39068b0 100644 --- a/src/pipeline_jobs.cpp +++ b/src/pipeline_jobs.cpp @@ -2564,7 +2564,6 @@ Set to No if the 3D helix looks the same when rotated upside down."); joboptions["helical_nr_asu"] = JobOption("Number of unique asymmetrical units:", 1, 1, 100, 1, "Number of unique helical asymmetrical units in each segment box. This integer should not be less than 1. The inter-box distance (pixels) = helical rise (Angstroms) * number of asymmetrical units / pixel size (Angstroms). \ The optimal inter-box distance might also depend on the box size, the helical rise and the flexibility of the structure. In general, an inter-box distance of ~10% * the box size seems appropriate."); joboptions["helical_rise"] = JobOption("Helical rise (A):", 1, 0, 100, 0.01, "Helical rise in Angstroms. (Please click '?' next to the option above for details about how the inter-box distance is calculated.)"); - //joboptions["helical_tilt_prior"] = JobOption("Tilt prior (deg):", 90, 0, 180, 5, "Use this option to set a prior value for the tilt angle. This will typically be 90 degrees, but this value will need to be changed if you tilted your images during data collection. If you tilted the stage 20 degrees, then you could try 70 and 110 degrees as a tilt prior. If you know the handedness of your camera/stage, then you can just use either 70 or 110."); } @@ -2720,7 +2719,6 @@ bool RelionJob::getCommandsExtractJob(std::string &outputname, std::vector Date: Wed, 18 Mar 2026 17:11:40 +0900 Subject: [PATCH 182/189] Remove Jasenko's experimental programs. --- src/apps/CMakeLists.txt | 27 - src/jaz/scripts/BP_test.cpp | 374 ---------- src/jaz/scripts/CMakeLists.txt | 0 src/jaz/scripts/CTFPQ_test.cpp | 479 ------------- src/jaz/scripts/add_subset.cpp | 33 - src/jaz/scripts/add_tomogram_name.cpp | 87 --- src/jaz/scripts/align_2d_classes.cpp | 379 ---------- src/jaz/scripts/analyse_dual_contrast.cpp | 822 ---------------------- src/jaz/scripts/blobs_from_points.cpp | 405 ----------- src/jaz/scripts/box_PCA.cpp | 119 ---- src/jaz/scripts/conversion_test.cpp | 115 --- src/jaz/scripts/cuda_test.cpp | 23 - src/jaz/scripts/defilament.cpp | 469 ------------ src/jaz/scripts/deramp.cpp | 61 -- src/jaz/scripts/dose_convert.cpp | 90 --- src/jaz/scripts/enumerate_micrographs.cpp | 77 -- src/jaz/scripts/extract_blob_points.cpp | 343 --------- src/jaz/scripts/find_blobs_2d.cpp | 428 ----------- src/jaz/scripts/fit_class_blobs.cpp | 313 -------- src/jaz/scripts/img_test.cpp | 33 - src/jaz/scripts/imod_test.cpp | 130 ---- src/jaz/scripts/make_split_tilts_star.cpp | 57 -- src/jaz/scripts/measure_noise.cpp | 240 ------- src/jaz/scripts/measure_scale.cpp | 352 --------- src/jaz/scripts/membrane_erase_2D.cpp | 165 ----- src/jaz/scripts/membrane_segment.cpp | 274 -------- src/jaz/scripts/plot_2d_classes.cpp | 203 ------ src/jaz/scripts/plot_DC_analysis.cpp | 413 ----------- src/jaz/scripts/plot_dose_weight.cpp | 95 --- src/jaz/scripts/plot_particles.cpp | 171 ----- src/jaz/scripts/plot_pdb.cpp | 81 --- src/jaz/scripts/plot_tomo_defoci.cpp | 75 -- src/jaz/scripts/plot_topaz_points.cpp | 167 ----- src/jaz/scripts/predict_tomo_view.cpp | 178 ----- src/jaz/scripts/print_coords.cpp | 98 --- src/jaz/scripts/process_image.cpp | 14 - src/jaz/scripts/ramp.cpp | 30 - src/jaz/scripts/rotate_subtomograms.cpp | 74 -- src/jaz/scripts/sample_blob_points.cpp | 361 ---------- src/jaz/scripts/scale_test.cpp | 108 --- src/jaz/scripts/scratch.cpp | 50 -- src/jaz/scripts/set_defocus_slope.cpp | 83 --- src/jaz/scripts/shift_plot.cpp | 139 ---- src/jaz/scripts/sort_2d_classes.cpp | 98 --- src/jaz/scripts/spa_backproject.cpp | 40 -- src/jaz/scripts/split_tilts.cpp | 230 ------ src/jaz/scripts/suppress_cone.cpp | 79 --- src/jaz/scripts/test_extraction.cpp | 211 ------ src/jaz/scripts/test_logger.cpp | 76 -- src/jaz/scripts/test_splines.cpp | 106 --- src/jaz/scripts/update_tomogram_set.cpp | 103 --- 51 files changed, 9178 deletions(-) delete mode 100644 src/jaz/scripts/BP_test.cpp delete mode 100755 src/jaz/scripts/CMakeLists.txt delete mode 100644 src/jaz/scripts/CTFPQ_test.cpp delete mode 100644 src/jaz/scripts/add_subset.cpp delete mode 100644 src/jaz/scripts/add_tomogram_name.cpp delete mode 100644 src/jaz/scripts/align_2d_classes.cpp delete mode 100644 src/jaz/scripts/analyse_dual_contrast.cpp delete mode 100644 src/jaz/scripts/blobs_from_points.cpp delete mode 100644 src/jaz/scripts/box_PCA.cpp delete mode 100644 src/jaz/scripts/conversion_test.cpp delete mode 100644 src/jaz/scripts/cuda_test.cpp delete mode 100644 src/jaz/scripts/defilament.cpp delete mode 100644 src/jaz/scripts/deramp.cpp delete mode 100644 src/jaz/scripts/dose_convert.cpp delete mode 100644 src/jaz/scripts/enumerate_micrographs.cpp delete mode 100644 src/jaz/scripts/extract_blob_points.cpp delete mode 100644 src/jaz/scripts/find_blobs_2d.cpp delete mode 100644 src/jaz/scripts/fit_class_blobs.cpp delete mode 100644 src/jaz/scripts/img_test.cpp delete mode 100644 src/jaz/scripts/imod_test.cpp delete mode 100644 src/jaz/scripts/make_split_tilts_star.cpp delete mode 100644 src/jaz/scripts/measure_noise.cpp delete mode 100644 src/jaz/scripts/measure_scale.cpp delete mode 100644 src/jaz/scripts/membrane_erase_2D.cpp delete mode 100644 src/jaz/scripts/membrane_segment.cpp delete mode 100644 src/jaz/scripts/plot_2d_classes.cpp delete mode 100644 src/jaz/scripts/plot_DC_analysis.cpp delete mode 100644 src/jaz/scripts/plot_dose_weight.cpp delete mode 100644 src/jaz/scripts/plot_particles.cpp delete mode 100644 src/jaz/scripts/plot_pdb.cpp delete mode 100644 src/jaz/scripts/plot_tomo_defoci.cpp delete mode 100644 src/jaz/scripts/plot_topaz_points.cpp delete mode 100644 src/jaz/scripts/predict_tomo_view.cpp delete mode 100644 src/jaz/scripts/print_coords.cpp delete mode 100644 src/jaz/scripts/process_image.cpp delete mode 100644 src/jaz/scripts/ramp.cpp delete mode 100644 src/jaz/scripts/rotate_subtomograms.cpp delete mode 100644 src/jaz/scripts/sample_blob_points.cpp delete mode 100644 src/jaz/scripts/scale_test.cpp delete mode 100644 src/jaz/scripts/scratch.cpp delete mode 100644 src/jaz/scripts/set_defocus_slope.cpp delete mode 100644 src/jaz/scripts/shift_plot.cpp delete mode 100644 src/jaz/scripts/sort_2d_classes.cpp delete mode 100644 src/jaz/scripts/spa_backproject.cpp delete mode 100644 src/jaz/scripts/split_tilts.cpp delete mode 100644 src/jaz/scripts/suppress_cone.cpp delete mode 100644 src/jaz/scripts/test_extraction.cpp delete mode 100644 src/jaz/scripts/test_logger.cpp delete mode 100644 src/jaz/scripts/test_splines.cpp delete mode 100644 src/jaz/scripts/update_tomogram_set.cpp diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index 90202ef54..488aedf6c 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -115,10 +115,6 @@ file(GLOB RELION_JAZ_TOMO_TARGETS "${CMAKE_SOURCE_DIR}/src/jaz/tomography/apps/* file(GLOB RELION_MPI_TARGETS RELATIVE "${CMAKE_SOURCE_DIR}/src/apps" "${CMAKE_SOURCE_DIR}/src/apps/*_mpi.cpp") list(APPEND RELION_MPI_TARGETS "postprocess.cpp;flex_analyse.cpp;autopick.cpp") -if(RELION_TEST) - file(GLOB RELION_JAZ_EXPERIMENTAL_TARGETS "${CMAKE_SOURCE_DIR}/src/jaz/scripts/*.cpp") -endif(RELION_TEST) - set(GUI_TARGETS maingui display manualpick) #--Remove apps using X11 if no GUI-- @@ -488,29 +484,6 @@ foreach (_target ${RELION_JAZ_TOMO_TARGETS}) install (TARGETS ${_target} RUNTIME DESTINATION bin) endforeach() -foreach (_target ${RELION_JAZ_EXPERIMENTAL_TARGETS}) - GET_FILENAME_COMPONENT(_target "relion_exp_${_target}" NAME_WE) - add_executable(${_target} ${CMAKE_SOURCE_DIR}/src/jaz/scripts/${_target}.cpp) - add_dependencies(${_target} relion_lib) - set_target_properties(${_target} PROPERTIES PREFIX "relion_exp_") - target_link_libraries(${_target} relion_lib ${EXTRA_LIBS} ${MPI_LIBRARIES} ${CMAKE_DL_LIBS}) - set_target_properties(${_target} PROPERTIES CXX_EXTENSIONS OFF ) - - if(TIFF_FOUND) - target_link_libraries(${_target} ${TIFF_LIBRARIES}) - endif() - - if(SYCL) - target_link_libraries(${_target} sycl OpenCL relion_lib) - endif(SYCL) - - if (ALTCPU) - target_link_libraries(${_target} ${TBB_LIBRARIES}) - endif(ALTCPU) - - install (TARGETS ${_target} RUNTIME DESTINATION bin) -endforeach() - if (NOT SYCL) target_link_libraries(relion_lib PUBLIC ${OpenMP_omp_LIBRARY}) endif() diff --git a/src/jaz/scripts/BP_test.cpp b/src/jaz/scripts/BP_test.cpp deleted file mode 100644 index 10cd86d2c..000000000 --- a/src/jaz/scripts/BP_test.cpp +++ /dev/null @@ -1,374 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace gravis; - -#define INPUT_PRECISION float -#define OUTPUT_PRECISION float - -int main(int argc, char *argv[]) -{ - const int s = 180; - const int sh = s/2 + 1; - const int num_observations = 100000; - const int num_shells = 5; - const int num_spheres = 2 * num_shells - 1; - const int num_threads = 6; - const double outer_radius = s / 2; - - const std::string tag = "100k_backward_noxg"; - - const double SNR = 1.0; - const bool forward = false; - const bool explicit_gridding = false; - const bool legacy_backprojector = false; - const bool wrap_voxels = false; - - std::vector sphere_radius(num_spheres); - std::vector sphere_scale(num_spheres); - - for (int i = 0; i < num_spheres; i++) - { - const double r = (i + 1.0) * outer_radius / num_spheres; - sphere_radius[i] = r; - sphere_scale[i] = 1 - 2 * (i % 2); - } - - BufferedImage observation(s,s); - - for (int y = 0; y < s; y++) - for (int x = 0; x < s; x++) - { - double sum = 0.0; - - const double xx = x < s/2? x : x - s; - const double yy = y < s/2? y : y - s; - const double r2 = xx*xx + yy*yy; - - for (int i = 0; i < num_spheres; i++) - { - const double r2s = sphere_radius[i] * sphere_radius[i]; - const double d2 = r2s - r2; - - if (d2 > 0.0) - { - const double l = 2.0 * sqrt(d2); - sum += sphere_scale[i] * l; - } - } - - observation(x,y) = sum; - } - - observation.write("observation_"+tag+".mrc"); - - BufferedImage> observation_FS; - FFT::FourierTransform(observation, observation_FS, FFT::Both); - - BufferedImage ctf(sh,s); - ctf.fill(1); - - BufferedImage> data(sh,s,s); - BufferedImage weight(sh,s,s), spreading_function(sh,s,s); - - data.fill(tComplex(0,0)); - weight.fill(0); - spreading_function.fill(0); - - BackProjector backprojector; - Image obervation_FS_legacy; - Image ctf_legacy; - - if (legacy_backprojector) - { - observation_FS.copyTo(obervation_FS_legacy); - ctf.copyTo(ctf_legacy); - - backprojector = BackProjector(s, 3, "C1", TRILINEAR, 1, 10, 0, 1.9, 15, 2, true); - backprojector.initZeros(-1); - } - - Log::beginProgress("Backprojecting observations", num_observations); - - for (int i = 0; i < num_observations; i++) - { - Log::updateProgress(i); - - const double phi = 2.0 * PI * rand() / (double)RAND_MAX; - const double psi = 2.0 * PI * rand() / (double)RAND_MAX; - const double tilt = (PI/2.0) * sin(PI * rand() / (double)RAND_MAX - PI/2.0); - - d4Matrix proj = EulerDynamo::anglesToMatrix4(phi, tilt, psi); - - if (legacy_backprojector) - { - Matrix2D A(3,3); - - for (int r = 0; r < 3; r++) - for (int c = 0; c < 3; c++) - { - A(r,c) = proj(r,c); - } - - backprojector.set2DFourierTransform(obervation_FS_legacy(), A, &ctf_legacy()); - } - else - { - if (forward) - { - if (wrap_voxels) - { - FourierBackprojection::backprojectSlice_forward_wrap( - observation_FS, ctf, proj, - data, weight); - } - else - { - ClippedPointInsertion clippedInsertion; - - FourierBackprojection::backprojectSlice_forward( - clippedInsertion, observation_FS, ctf, proj, - data, weight); - } - } - else - { - FourierBackprojection::backprojectSlice_backward( - observation_FS, ctf, proj, - data, weight, num_threads); - - if (explicit_gridding) - { - FourierBackprojection::backprojectSpreadingFunction( - proj, spreading_function); - } - } - } - } - - Log::endProgress(); - - - BufferedImage data_div_RS(s,s,s); - - - - if (legacy_backprojector) - { - Image vol_xmipp; - vol_xmipp().initZeros(s, s, s); - vol_xmipp().setXmippOrigin(); - - MultidimArray tau2; - - backprojector.reconstruct(vol_xmipp(), 10, false, tau2); - - data_div_RS.copyDataAndSizeFrom(vol_xmipp); - } - else - { - data.writeVtk("data_"+tag+".vtk"); - - BufferedImage data_RS(s,s,s); - - if (forward || !explicit_gridding) - { - std::cout << "Reconstruction::griddingCorrect3D_sinc2" << std::endl; - Reconstruction::griddingCorrect3D_sinc2( - data, data_RS, - true, 1); - } - else - { - Reconstruction::griddingCorrect3D( - data, spreading_function, // in - data_RS, // out - true, num_threads); - } - - Reconstruction::ctfCorrect3D_Wiener( - data_RS, weight, // in - data_div_RS, // out - 1.0 / SNR, num_threads); - } - - Reconstruction::taper(data_div_RS, 10, true, num_threads); - - data_div_RS.write("reconstruction_"+tag+".mrc"); - - - const int num_predictions = 10; - - BufferedImage predictions(s,s,num_predictions); - - for (int i = 0; i < num_predictions; i++) - { - BufferedImage> data_div_FS; - FFT::FourierTransform(data_div_RS, data_div_FS); - - Centering::shiftInSitu(data_div_FS); - - const double phi = 2.0 * PI * rand() / (double)RAND_MAX; - const double psi = 2.0 * PI * rand() / (double)RAND_MAX; - const double tilt = (PI/2.0) * sin(PI * rand() / (double)RAND_MAX - PI/2.0); - - d4Matrix proj = EulerDynamo::anglesToMatrix4(phi, tilt, psi); - - - BufferedImage prediction(sh,s), psf(sh,s); - - ForwardProjection::forwardProject_withPSF(data_div_FS, {proj}, prediction, psf, 1); - - BufferedImage predictionReal(s,s); - - Reconstruction::correctStack(prediction, psf, predictionReal, true, 1); - - predictions.getSliceRef(i).copyFrom(predictionReal); - } - - predictions.write("predictions_"+tag+".mrc"); - - std::vector radial_mean(sh, 0.0); - std::vector radial_count(sh, 0.0); - - for (long int z = 0; z < s; z++) - for (long int y = 0; y < s; y++) - for (long int x = 0; x < s; x++) - { - const double xx = x - s/2; - const double yy = y - s/2; - const double zz = z - s/2; - - const double r = sqrt(xx*xx + yy*yy + zz*zz); - const int ri = (int) (r + 0.5); - - if (ri < sh) - { - radial_mean[ri] += data_div_RS(x,y,z); - radial_count[ri] += 1; - } - } - - for (int r = 0; r < sh; r++) - { - if (radial_count[r] > 0.0) - { - radial_mean[r] /= radial_count[r]; - } - } - - std::vector radial_variance(sh, 0.0); - - for (long int z = 0; z < s; z++) - for (long int y = 0; y < s; y++) - for (long int x = 0; x < s; x++) - { - const double xx = x - s/2; - const double yy = y - s/2; - const double zz = z - s/2; - - const double r = sqrt(xx*xx + yy*yy + zz*zz); - const int ri = (int) (r + 0.5); - - if (ri < sh) - { - const double d = data_div_RS(x,y,z) - radial_mean[r]; - - radial_variance[ri] += d * d; - } - } - - for (int r = 0; r < sh; r++) - { - if (radial_count[r] > 1.0) - { - radial_variance[r] /= radial_count[r] - 1.0; - } - } - - double plot_mean = 0.0; - - for (int r = 0; r < sh; r++) - { - plot_mean += radial_mean[r]; - } - - plot_mean /= sh; - - double plot_power = 0.0; - - for (int r = 0; r < sh; r++) - { - const double d = radial_mean[r] - plot_mean; - plot_power += d * d; - } - - const double plot_scale = 1.0 / sqrt(plot_power/sh); - - - std::vector curves(3); - - for (int i = 0; i < 3; i++) - { - CDataSet& curve = curves[i]; - - curve.SetDrawMarker(false); - curve.SetDrawLine(true); - curve.SetLineWidth(0.5); - - } - - curves[0].SetDatasetColor(0,0,0); - curves[1].SetDatasetColor(0.5,0.5,0.5); - curves[2].SetDatasetColor(0.5,0.5,0.5); - - for (int r = 0; r < sh; r++) - { - const double mu = plot_scale * (radial_mean[r] - plot_mean); - const double sd = plot_scale * sqrt(radial_variance[r]); - - curves[0].AddDataPoint(CDataPoint(r, mu)); - curves[1].AddDataPoint(CDataPoint(r, mu - sd)); - curves[2].AddDataPoint(CDataPoint(r, mu + sd)); - } - - CPlot2D plot(tag); - - for (int i = 2; i >= 0; i--) - { - plot.AddDataSet(curves[i]); - } - - std::string title = tag; - - for (int i = 0; i < title.length(); i++) - { - if (title[i] == '_') - { - title[i] = ' '; - } - } - - plot.SetTitle(title); - plot.SetXAxisTitle("r"); - plot.SetYAxisTitle("mean"); - - plot.SetViewArea(0,-2,sh,3); - - plot.OutputPostScriptPlot("reconstruction_"+tag+".eps"); - - return 0; -} diff --git a/src/jaz/scripts/CMakeLists.txt b/src/jaz/scripts/CMakeLists.txt deleted file mode 100755 index e69de29bb..000000000 diff --git a/src/jaz/scripts/CTFPQ_test.cpp b/src/jaz/scripts/CTFPQ_test.cpp deleted file mode 100644 index 2fd136ff0..000000000 --- a/src/jaz/scripts/CTFPQ_test.cpp +++ /dev/null @@ -1,479 +0,0 @@ -#include -#include -#include -#include -#include -#include - - - - -void applyCTFPandCTFQ_original( - MultidimArray &Fin, CTF &ctf, FourierTransformer &transformer, - MultidimArray &outP, MultidimArray &outQ, bool skip_mask, - int newbox, int nr_sectors, bool is_reverse, - double angpix, double mask_diameter, double width_mask_edge) -{ - outP.resize(Fin); - outQ.resize(Fin); - float angle_step = 180./nr_sectors; - - for (float angle = 0.; angle < 180.; angle +=angle_step) - { - MultidimArray CTFP(Fin), Fapp(Fin); - MultidimArray Iapp(YSIZE(Fin), YSIZE(Fin)); - - // Two passes: one for CTFP, one for CTFQ - for (int ipass = 0; ipass < 2; ipass++) - { - bool is_my_positive = (ipass == 1) ? is_reverse : !is_reverse; - - // Get CTFP and multiply the Fapp with it - ctf.getCTFPImage(CTFP, YSIZE(Fin), YSIZE(Fin), angpix, is_my_positive, angle); - - Fapp = Fin * CTFP; // element-wise complex multiplication! - - if (!skip_mask) - { - // inverse transform and mask out the particle.... - CenterFFTbySign(Fapp); - transformer.inverseFourierTransform(Fapp, Iapp); - - softMaskOutsideMap(Iapp, ROUND(mask_diameter/(angpix*2.)), (RFLOAT)width_mask_edge); - - // Re-box to a smaller size if necessary.... - if (newbox > 0 && newbox < YSIZE(Fin)) - { - Iapp.setXmippOrigin(); - Iapp.window(FIRST_XMIPP_INDEX(newbox), FIRST_XMIPP_INDEX(newbox), - LAST_XMIPP_INDEX(newbox), LAST_XMIPP_INDEX(newbox)); - } - - // Back into Fourier-space - transformer.FourierTransform(Iapp, Fapp, false); // false means: leave Fapp in the transformer - CenterFFTbySign(Fapp); - } - - // First time round: resize the output arrays - if (ipass == 0 && fabs(angle) < XMIPP_EQUAL_ACCURACY) - { - outP.resize(Fapp); - outQ.resize(Fapp); - } - - // Now set back the right parts into outP (first pass) or outQ (second pass) - float anglemin = angle + 90. - (0.5*angle_step); - float anglemax = angle + 90. + (0.5*angle_step); - - // angles larger than 180 - bool is_sector_reverse = false; - - if (anglemin >= 180.) - { - anglemin -= 180.; - anglemax -= 180.; - is_sector_reverse = true; - } - - MultidimArray *myCTFPorQ, *myCTFPorQb; - - if (is_sector_reverse) - { - myCTFPorQ = (ipass == 0) ? &outQ : &outP; - myCTFPorQb = (ipass == 0) ? &outP : &outQ; - } - else - { - myCTFPorQ = (ipass == 0) ? &outP : &outQ; - myCTFPorQb = (ipass == 0) ? &outQ : &outP; - } - - // Deal with sectors with the Y-axis in the middle of the sector... - bool do_wrap_max = false; - if (anglemin < 180. && anglemax > 180.) - { - anglemax -= 180.; - do_wrap_max = true; - } - - // use radians instead of degrees - anglemin = DEG2RAD(anglemin); - anglemax = DEG2RAD(anglemax); - FOR_ALL_ELEMENTS_IN_FFTW_TRANSFORM2D(CTFP) - { - RFLOAT x = (RFLOAT)jp; - RFLOAT y = (RFLOAT)ip; - RFLOAT myangle = (x*x+y*y > 0) ? acos(y/sqrt(x*x+y*y)) : 0; // dot-product with Y-axis: (0,1) - - // Only take the relevant sector now... - if (do_wrap_max) - { - if (myangle >= anglemin) - { - DIRECT_A2D_ELEM(*myCTFPorQ, i, j) = DIRECT_A2D_ELEM(Fapp, i, j); - } - else if (myangle < anglemax) - { - DIRECT_A2D_ELEM(*myCTFPorQb, i, j) = DIRECT_A2D_ELEM(Fapp, i, j); - } - } - else - { - if (myangle >= anglemin && myangle < anglemax) - { - DIRECT_A2D_ELEM(*myCTFPorQ, i, j) = DIRECT_A2D_ELEM(Fapp, i, j); - } - } - } - } - } -} - - - -void applyCTFPandCTFQ_logged( - MultidimArray &Fin, CTF &ctf, FourierTransformer &transformer, - MultidimArray &outP, MultidimArray &outQ, bool skip_mask, - int newbox, int nr_sectors, bool is_reverse, - double angpix, double mask_diameter, double width_mask_edge) -{ - outP.resize(Fin); - outQ.resize(Fin); - float angle_step = 180./nr_sectors; - - outP.initZeros(); - outQ.initZeros(); - - RawImage(outP).writeVtk("initial_outP.vtk"); - RawImage(outQ).writeVtk("initial_outQ.vtk"); - - for (float angle = 0.; angle < 180.; angle +=angle_step) - { - std::cout << "angle = " << angle << std::endl; - - - MultidimArray CTFP(Fin), Fapp(Fin); - MultidimArray Iapp(YSIZE(Fin), YSIZE(Fin)); - - // Two passes: one for CTFP, one for CTFQ - for (int ipass = 0; ipass < 2; ipass++) - { - std::cout << "ipass = " << ipass << std::endl; - - bool is_my_positive = (ipass == 1) ? is_reverse : !is_reverse; - - // Get CTFP and multiply the Fapp with it - ctf.getCTFPImage(CTFP, YSIZE(Fin), YSIZE(Fin), angpix, is_my_positive, angle); - - Fapp = Fin * CTFP; // element-wise complex multiplication! - - RawImage(CTFP).writeVtk(ZIO::itoa(angle)+"_"+ZIO::itoa(ipass)+"_CTFP.vtk"); - RawImage(Fapp).writeVtk(ZIO::itoa(angle)+"_"+ZIO::itoa(ipass)+"_Fapp_0.vtk"); - - if (!skip_mask) - { - // inverse transform and mask out the particle.... - CenterFFTbySign(Fapp); - transformer.inverseFourierTransform(Fapp, Iapp); - - softMaskOutsideMap(Iapp, ROUND(mask_diameter/(angpix*2.)), (RFLOAT)width_mask_edge); - - // Re-box to a smaller size if necessary.... - if (newbox > 0 && newbox < YSIZE(Fin)) - { - Iapp.setXmippOrigin(); - Iapp.window(FIRST_XMIPP_INDEX(newbox), FIRST_XMIPP_INDEX(newbox), - LAST_XMIPP_INDEX(newbox), LAST_XMIPP_INDEX(newbox)); - } - - // Back into Fourier-space - transformer.FourierTransform(Iapp, Fapp, false); // false means: leave Fapp in the transformer - CenterFFTbySign(Fapp); - } - - RawImage(Fapp).writeVtk(ZIO::itoa(angle)+"_"+ZIO::itoa(ipass)+"_Fapp_1.vtk"); - - // Now set back the right parts into outP (first pass) or outQ (second pass) - float anglemin = angle + 90. - (0.5*angle_step); - float anglemax = angle + 90. + (0.5*angle_step); - - // angles larger than 180 - bool is_sector_reverse = false; - - if (anglemin >= 180.) - { - anglemin -= 180.; - anglemax -= 180.; - is_sector_reverse = true; - } - - std::cout << "\tanglemin = " << anglemin << std::endl; - std::cout << "\tanglemax = " << anglemax << std::endl; - std::cout << "\tis_sector_reverse = " << is_sector_reverse << std::endl; - - MultidimArray *myCTFPorQ, *myCTFPorQb; - - if (is_sector_reverse) - { - myCTFPorQ = (ipass == 0) ? &outQ : &outP; - myCTFPorQb = (ipass == 0) ? &outP : &outQ; - } - else - { - myCTFPorQ = (ipass == 0) ? &outP : &outQ; - myCTFPorQb = (ipass == 0) ? &outQ : &outP; - } - - // Deal with sectors with the Y-axis in the middle of the sector... - bool do_wrap_max = false; - if (anglemin < 180. && anglemax > 180.) - { - anglemax -= 180.; - do_wrap_max = true; - } - - std::cout << "\tdo_wrap_max = " << do_wrap_max << std::endl; - - // use radians instead of degrees - anglemin = DEG2RAD(anglemin); - anglemax = DEG2RAD(anglemax); - FOR_ALL_ELEMENTS_IN_FFTW_TRANSFORM2D(CTFP) - { - RFLOAT x = (RFLOAT)jp; - RFLOAT y = (RFLOAT)ip; - RFLOAT myangle = (x*x+y*y > 0) ? acos(y/sqrt(x*x+y*y)) : 0; // dot-product with Y-axis: (0,1) - - // Only take the relevant sector now... - if (do_wrap_max) - { - if (myangle >= anglemin) - { - DIRECT_A2D_ELEM(*myCTFPorQ, i, j) = DIRECT_A2D_ELEM(Fapp, i, j); - } - else if (myangle < anglemax) - { - DIRECT_A2D_ELEM(*myCTFPorQb, i, j) = DIRECT_A2D_ELEM(Fapp, i, j); - } - } - else - { - if (myangle >= anglemin && myangle < anglemax) - { - DIRECT_A2D_ELEM(*myCTFPorQ, i, j) = DIRECT_A2D_ELEM(Fapp, i, j); - } - } - } - - RawImage(outP).writeVtk(ZIO::itoa(angle)+"_"+ZIO::itoa(ipass)+"_outP.vtk"); - RawImage(outQ).writeVtk(ZIO::itoa(angle)+"_"+ZIO::itoa(ipass)+"_outQ.vtk"); - } - } -} - -void applyCTFPandCTFQ_refac( - MultidimArray &Fin, CTF &ctf, FourierTransformer &transformer, - MultidimArray &outP, MultidimArray &outQ, bool skip_mask, - int newbox, int nr_sectors, bool is_reverse, - double angpix, double mask_diameter, double width_mask_edge) -{ - outP.resize(Fin); - outQ.resize(Fin); - float angle_step = 180./nr_sectors; - - outP.initZeros(); - outQ.initZeros(); - - RawImage(outP).writeVtk("initial_outP.vtk"); - RawImage(outQ).writeVtk("initial_outQ.vtk"); - - for (float angle = 0.; angle < 180.; angle +=angle_step) - { - std::cout << "angle = " << angle << std::endl; - - - MultidimArray CTFP(Fin), Fapp(Fin); - MultidimArray Iapp(YSIZE(Fin), YSIZE(Fin)); - - // Two passes: one for CTFP, one for CTFQ - for (int ipass = 0; ipass < 2; ipass++) - { - std::cout << "ipass = " << ipass << std::endl; - - bool is_my_positive = (ipass == 1) ? is_reverse : !is_reverse; - - // Get CTFP and multiply the Fapp with it - ctf.getCTFPImage(CTFP, YSIZE(Fin), YSIZE(Fin), angpix, is_my_positive, angle); - - Fapp = Fin * CTFP; // element-wise complex multiplication! - - RawImage(CTFP).writeVtk(ZIO::itoa(angle)+"_"+ZIO::itoa(ipass)+"_CTFP.vtk"); - RawImage(Fapp).writeVtk(ZIO::itoa(angle)+"_"+ZIO::itoa(ipass)+"_Fapp_0.vtk"); - - if (!skip_mask) - { - // inverse transform and mask out the particle.... - CenterFFTbySign(Fapp); - transformer.inverseFourierTransform(Fapp, Iapp); - - softMaskOutsideMap(Iapp, ROUND(mask_diameter/(angpix*2.)), (RFLOAT)width_mask_edge); - - // Re-box to a smaller size if necessary.... - if (newbox > 0 && newbox < YSIZE(Fin)) - { - Iapp.setXmippOrigin(); - Iapp.window(FIRST_XMIPP_INDEX(newbox), FIRST_XMIPP_INDEX(newbox), - LAST_XMIPP_INDEX(newbox), LAST_XMIPP_INDEX(newbox)); - } - - // Back into Fourier-space - transformer.FourierTransform(Iapp, Fapp, false); // false means: leave Fapp in the transformer - CenterFFTbySign(Fapp); - } - - RawImage(Fapp).writeVtk(ZIO::itoa(angle)+"_"+ZIO::itoa(ipass)+"_Fapp_1.vtk"); - - // Now set back the right parts into outP (first pass) or outQ (second pass) - float anglemin = angle + 90. - (0.5*angle_step); - float anglemax = angle + 90. + (0.5*angle_step); - - // angles larger than 180 - bool is_sector_reverse = false; - - if (anglemin >= 180.) - { - anglemin -= 180.; - anglemax -= 180.; - is_sector_reverse = true; - } - - std::cout << "\tanglemin = " << anglemin << std::endl; - std::cout << "\tanglemax = " << anglemax << std::endl; - std::cout << "\tis_sector_reverse = " << is_sector_reverse << std::endl; - - MultidimArray *myCTFPorQ, *myCTFPorQb; - - if (is_sector_reverse) - { - myCTFPorQ = (ipass == 0) ? &outQ : &outP; - myCTFPorQb = (ipass == 0) ? &outP : &outQ; - } - else - { - myCTFPorQ = (ipass == 0) ? &outP : &outQ; - myCTFPorQb = (ipass == 0) ? &outQ : &outP; - } - - // Deal with sectors with the Y-axis in the middle of the sector... - bool do_wrap_max = false; - if (anglemin < 180. && anglemax > 180.) - { - anglemax -= 180.; - do_wrap_max = true; - } - - std::cout << "\tdo_wrap_max = " << do_wrap_max << std::endl; - - // use radians instead of degrees - anglemin = DEG2RAD(anglemin); - anglemax = DEG2RAD(anglemax); - FOR_ALL_ELEMENTS_IN_FFTW_TRANSFORM2D(CTFP) - { - RFLOAT x = (RFLOAT)jp; - RFLOAT y = (RFLOAT)ip; - RFLOAT myangle = (x*x+y*y > 0) ? acos(y/sqrt(x*x+y*y)) : 0; // dot-product with Y-axis: (0,1) - - // Only take the relevant sector now... - if (do_wrap_max) - { - if (myangle >= anglemin) - { - DIRECT_A2D_ELEM(*myCTFPorQ, i, j) = DIRECT_A2D_ELEM(Fapp, i, j); - } - else if (myangle < anglemax) - { - DIRECT_A2D_ELEM(*myCTFPorQb, i, j) = DIRECT_A2D_ELEM(Fapp, i, j); - } - } - else - { - if (myangle >= anglemin && myangle < anglemax) - { - DIRECT_A2D_ELEM(*myCTFPorQ, i, j) = DIRECT_A2D_ELEM(Fapp, i, j); - } - } - } - - RawImage(outP).writeVtk(ZIO::itoa(angle)+"_"+ZIO::itoa(ipass)+"_outP.vtk"); - RawImage(outQ).writeVtk(ZIO::itoa(angle)+"_"+ZIO::itoa(ipass)+"_outQ.vtk"); - } - } -} - - -int main() -{ - const int s = 180; - const int sh = s/2 + 1; - - const bool skip_mask = false; - const int newbox = s; - const int nr_sectors = 2; - const bool is_reverse = false; - const double angpix = 1; - const double mask_diameter = s/2.0; - const double width_mask_edge = 3.0; - - CTF ctf; - ctf.setValues(8000, 12000, 30, 300, 2.7, 0.1, 0, 1, 0); - - - MultidimArray obs_xmipp(s,s,sh), obsP_xmipp, obsQ_xmipp; - - FourierTransformer transformer; - - BufferedImage obs = ImageNoise::generateSmoothFourierNoise(s, s/4.0); - - obs.writeVtk("obs.vtk"); - obs.copyTo(obs_xmipp); - - applyCTFPandCTFQ_logged( - obs_xmipp, ctf, transformer, obsP_xmipp, obsQ_xmipp, skip_mask, - newbox, nr_sectors, is_reverse, angpix, mask_diameter, width_mask_edge); - - RawImage obsP(obsP_xmipp); - RawImage obsQ(obsQ_xmipp); - - obs.writeVtk("obs.vtk"); - obsP.writeVtk("obsP.vtk"); - obsQ.writeVtk("obsQ.vtk"); - - (obsP + obsQ).writeVtk("obsPplusQ.vtk"); - - BufferedImage flatCTF = obs; - - RFLOAT bs = (RFLOAT)s * angpix; - - for (int y = 0; y < s; y++) - for (int x = 0; x < sh; x++) - { - const double xx = x / bs; - const double yy = (y < s/2? y : y - s) / bs; - - flatCTF(x,y) *= ctf.getCTF(xx,yy); - } - - flatCTF.writeVtk("obsCTF.vtk"); - - - MultidimArray Fctf(s,sh); - - ctf.applyWeightEwaldSphereCurvature_noAniso( - Fctf, s, s, angpix, mask_diameter); - - RawImage(Fctf).write("maskedCTF.mrc"); - - - - - return 0; -} diff --git a/src/jaz/scripts/add_subset.cpp b/src/jaz/scripts/add_subset.cpp deleted file mode 100644 index b4f471e3f..000000000 --- a/src/jaz/scripts/add_subset.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include -#include -#include -#include -#include - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - if (argc < 3) - { - std::cerr << "usage: relion_exp_add_subset " << std::endl; - return -1; - } - - ParticleSet ps(argv[1], ""); - - std::cout << ps.partTable.numberOfObjects() << std::endl; - - ps.partTable.addLabel(EMDL_PARTICLE_RANDOM_SUBSET); - - for (int p = 0; p < ps.partTable.numberOfObjects(); p++) - { - ps.partTable.setValue(EMDL_PARTICLE_RANDOM_SUBSET, 1 + (rand()%2), p); - } - - ps.write(argv[2]); - - return 0; - -} diff --git a/src/jaz/scripts/add_tomogram_name.cpp b/src/jaz/scripts/add_tomogram_name.cpp deleted file mode 100644 index 0477c79e5..000000000 --- a/src/jaz/scripts/add_tomogram_name.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include -#include -#include -#include - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - IOParser parser; - - std::string tableFn, inputFn, outputFn; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - inputFn = parser.getOption("--i", "Input particle set"); - tableFn = parser.getOption("--m", "Table mapping micrograph names (first column) to tomogram names (second column)"); - outputFn = parser.getOption("--o", "Output particle set"); - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - std::map mic2tomo; - - std::ifstream ifs(tableFn); - - if (!ifs) - { - REPORT_ERROR_STR("Unable to read "+tableFn); - } - - std::string line; - - while (std::getline(ifs, line)) - { - std::stringstream sts(line); - std::string mic, tomo; - - sts >> mic; - sts >> tomo; - - std::cout << mic << " -> " << tomo << std::endl; - - mic2tomo[mic] = tomo; - } - - MetaDataTable opticsTable, particleTable; - - opticsTable.read(inputFn, "optics"); - particleTable.read(inputFn, "particles"); - - particleTable.addLabel(EMDL_TOMO_NAME); - particleTable.addLabel(EMDL_TOMO_TILT_SERIES_NAME); - - for (int p = 0; p < particleTable.numberOfObjects(); p++) - { - std::string mic = particleTable.getString(EMDL_MICROGRAPH_NAME, p); - - if (mic2tomo.find(mic) == mic2tomo.end()) - { - REPORT_ERROR_STR("Micrograph name '" << mic << "' not found in " - << tableFn); - } - - std::string tomo = mic2tomo[mic]; - - particleTable.setValue(EMDL_TOMO_NAME, tomo, p); - particleTable.setValue(EMDL_TOMO_TILT_SERIES_NAME, mic, p); - } - - std::ofstream ofs(outputFn); - - opticsTable.write(ofs); - particleTable.write(ofs); - - return 0; -} diff --git a/src/jaz/scripts/align_2d_classes.cpp b/src/jaz/scripts/align_2d_classes.cpp deleted file mode 100644 index a03a1a31e..000000000 --- a/src/jaz/scripts/align_2d_classes.cpp +++ /dev/null @@ -1,379 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace gravis; - - -BufferedImage rotate(const RawImage& img, double phi); -BufferedImage flip_x(const RawImage& img); -BufferedImage correlate(const RawImage& img0, const RawImage& img1); -float max_correlation(const RawImage& img0, const RawImage& img1); - -int main(int argc, char *argv[]) -{ - std::string particlesFn, class_averages_filename, outDir; - double margin; - int num_threads; - bool flip_orientation, reorder, align_horizontally_only; - - - IOParser parser; - - try - { - IOParser parser; - - parser.setCommandLine(argc, argv); - - int general_section = parser.addSection("General options"); - - particlesFn = parser.getOption("--i", "Input file (e.g. run_it023_data.star)"); - class_averages_filename = parser.getOption("--ca", "Class averages stack"); - flip_orientation = parser.checkOption("--flip", "Rotate the initial alignment by 180°"); - align_horizontally_only = parser.checkOption("--horizontal_only", "Only align the biggest class horizontally, then exit"); - reorder = parser.checkOption("--sort", "Sort classes by descending cardinality"); - margin = textToDouble(parser.getOption("--m", "Margin around the particle [Px]", "20")); - - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - outDir = parser.getOption("--o", "Output directory"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - - outDir = ZIO::makeOutputDir(outDir); - - ObservationModel obs_model; - MetaDataTable particles_table; - ObservationModel::loadSafely(particlesFn, obs_model, particles_table); - - int max_class = -1; - - for (long int p = 0; p < particles_table.numberOfObjects(); p++) - { - const int class_id = particles_table.getIntMinusOne(EMDL_PARTICLE_CLASS, p); - - if (class_id > max_class) max_class = class_id; - } - - const int class_count = max_class + 1; - - if (class_count == 1) - { - Log::warn("1 class found"); - } - else - { - Log::print(ZIO::itoa(class_count)+" classes found"); - } - - - std::vector particle_count(class_count, 0); - - for (long int p = 0; p < particles_table.numberOfObjects(); p++) - { - const int class_id = particles_table.getIntMinusOne( - EMDL_PARTICLE_CLASS, p); - - particle_count[class_id]++; - } - - std::vector> particle_by_class(class_count); - - for (long int c = 0; c < class_count; c++) - { - particle_by_class[c].reserve(particle_count[c]); - } - - for (long int p = 0; p < particles_table.numberOfObjects(); p++) - { - const int class_id = particles_table.getIntMinusOne( - EMDL_PARTICLE_CLASS, p); - - particle_by_class[class_id].push_back(p); - } - - std::vector class_by_size = IndexSort::sortIndices(particle_count); - const int biggest_class = class_by_size[class_count-1]; - - - const int angle_samples_coarse = 36; - const int angle_samples_fine = 100; - - - BufferedImage class_averages; - class_averages.read(class_averages_filename); - - - std::vector angle_by_class(class_count, 0.0); - - BufferedImage template_slice; - - - // align biggest class to maximise horizontal symmetry - { - - - double best_phi_coarse = 0; - double best_phi_CC_coarse = 0; - - RawImage slice0 = class_averages.getSliceRef(biggest_class); - - - for (int ai = 0; ai < angle_samples_coarse; ai++) - { - const double phi = ai * 2.0 * PI / angle_samples_coarse; - - BufferedImage slice = rotate(slice0, phi); - BufferedImage flipped_slice = flip_x(slice); - - const double max_CC = max_correlation(slice, flipped_slice); - - if (max_CC > best_phi_CC_coarse) - { - best_phi_CC_coarse = max_CC; - best_phi_coarse = phi; - } - } - - double best_phi_fine = best_phi_coarse; - double best_phi_CC_fine = best_phi_CC_coarse; - - for (int ai = 0; ai < angle_samples_fine; ai++) - { - const double range = 2.0 * PI / angle_samples_coarse; - const double phi = best_phi_coarse + (2 * ai / (double) angle_samples_fine - 1) * range; - - BufferedImage slice = rotate(slice0, phi); - BufferedImage flipped_slice = flip_x(slice); - - const double max_CC = max_correlation(slice, flipped_slice); - - if (max_CC > best_phi_CC_fine) - { - best_phi_CC_fine = max_CC; - best_phi_fine = phi; - } - } - - if (flip_orientation) - { - best_phi_fine += PI; - - if (best_phi_fine > 2 * PI) - { - best_phi_fine -= 2 * PI; - } - } - - angle_by_class[biggest_class] = best_phi_fine; - - template_slice = rotate(slice0, best_phi_fine); - - slice0.write(outDir + "class_"+ZIO::itoa(biggest_class)+"_original.mrc"); - template_slice.write(outDir + "class_"+ZIO::itoa(biggest_class)+"_rotated.mrc"); - } - - - - if (align_horizontally_only) return 0; - - - BufferedImage class_averages_rotated = class_averages; - class_averages_rotated.getSliceRef(biggest_class).copyFrom(template_slice); - - - // align every other class against the biggest one - - for (int ci = 1; ci < class_count; ci++) - { - const int class_id = class_by_size[class_count - ci - 1]; - - Log::print("Aligning class " + ZIO::itoa(class_id)); - - - double best_phi_coarse = 0; - double best_phi_CC_coarse = 0; - - RawImage slice0 = class_averages.getSliceRef(class_id); - - for (int ai = 0; ai < angle_samples_coarse; ai++) - { - const double phi = ai * 2.0 * PI / angle_samples_coarse; - - BufferedImage slice = rotate(slice0, phi); - const double max_CC = max_correlation(slice, template_slice); - - if (max_CC > best_phi_CC_coarse) - { - best_phi_CC_coarse = max_CC; - best_phi_coarse = phi; - } - } - - double best_phi_fine = best_phi_coarse; - double best_phi_CC_fine = best_phi_CC_coarse; - - for (int ai = 0; ai < angle_samples_fine; ai++) - { - const double range = 2.0 * PI / angle_samples_coarse; - const double phi = best_phi_coarse + (2 * ai / (double) angle_samples_fine - 1) * range; - - BufferedImage slice = rotate(slice0, phi); - const double max_CC = max_correlation(slice, template_slice); - - if (max_CC > best_phi_CC_fine) - { - best_phi_CC_fine = max_CC; - best_phi_fine = phi; - } - } - - angle_by_class[class_id] = best_phi_fine; - - BufferedImage slice = rotate(slice0, best_phi_fine); - class_averages_rotated.getSliceRef(class_id).copyFrom(slice); - } - - class_averages_rotated.write(outDir+"class_averages_rotated.mrc"); - - - - for (long int p = 0; p < particles_table.numberOfObjects(); p++) - { - const int class_id = particles_table.getIntMinusOne(EMDL_PARTICLE_CLASS, p); - const double phi0 = particles_table.getDouble(EMDL_ORIENT_PSI, p); - - double phi1 = phi0 + RAD2DEG(angle_by_class[class_id]); - - if (phi1 > 360) phi1 -= 360; - - particles_table.setValue(EMDL_ORIENT_PSI, phi1, p); - } - - if (reorder) - { - std::vector new_class_id(class_count); - - for (int i = 0; i < class_count; i++) - { - new_class_id[class_by_size[i]] = class_count - i - 1; - } - - for (long int p = 0; p < particles_table.numberOfObjects(); p++) - { - const int class_id = particles_table.getIntMinusOne(EMDL_PARTICLE_CLASS, p); - - particles_table.setValue(EMDL_PARTICLE_CLASS, new_class_id[class_id] + 1, p); - } - } - - - - ObservationModel::saveNew( - particles_table, - obs_model.opticsMdt, - obs_model.generalMdt, - outDir+"rotated_particles.star"); - - - return 0; -} - - - -BufferedImage rotate(const RawImage& img, double phi) -{ - const int s = img.xdim; - const int m = s / 2; - - const d2Matrix A( - cos(phi), sin(phi), - -sin(phi), cos(phi) ); - - BufferedImage out(s,s); - - for (int y = 0; y < s; y++) - for (int x = 0; x < s; x++) - { - const d2Vector r0(x - m, y - m); - const d2Vector r1 = A * r0; - - out(x,y) = Interpolation::linearXY_clip(img, m + r1.x, m + r1.y); - } - - return out; -} - -BufferedImage flip_x(const RawImage& img) -{ - const int s = img.xdim; - - BufferedImage out(s,s); - - for (int y = 0; y < s; y++) - for (int x = 0; x < s; x++) - { - out(x,y) = img(s-x-1, y); - } - - return out; -} - -BufferedImage correlate(const RawImage& img0, const RawImage& img1) -{ - const int s = img0.xdim; - const int sh = s/2 + 1; - - BufferedImage img0_FS, img1_FS; - - FFT::FourierTransform(img0, img0_FS, FFT::Both); - FFT::FourierTransform(img1, img1_FS, FFT::Both); - - BufferedImage product(sh,s); - - for (int y = 0; y < s; y++) - for (int x = 0; x < sh; x++) - { - product(x,y) = img0_FS(x,y) * img1_FS(x,y).conj(); - } - - BufferedImage CC; - - FFT::inverseFourierTransform(product, CC, FFT::Both); - - return CC; -} - -float max_correlation(const RawImage& img0, const RawImage& img1) -{ - BufferedImage CC = correlate(img0, img1); - return Interpolation::discreteMaxXYV(CC)[2]; -} diff --git a/src/jaz/scripts/analyse_dual_contrast.cpp b/src/jaz/scripts/analyse_dual_contrast.cpp deleted file mode 100644 index cfdf3f059..000000000 --- a/src/jaz/scripts/analyse_dual_contrast.cpp +++ /dev/null @@ -1,822 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace gravis; - -void compareAtomsByResidue( - const std::map>>& atoms_by_name_by_residue, - double coord_offset_pixels, - double pixel_size, - const RawImage& phase_map_RS, - const RawImage& amp_map_RS, - std::string out_path, - std::string tag, - bool images_are_premultiplied); - -void compareResidues( - const std::map>& atoms_by_residue, - double coord_offset_pixels, - double pixel_size, - const RawImage& phase_map_RS, - const RawImage& amp_map_RS, - std::string filename_out, - bool images_are_premultiplied); - -double computeShellAverage( - const RawImage& map_RS, - double r0, double r1) -{ - const int s = map_RS.xdim; - - double sum = 0.0; - double count = 0.0; - - for (int z = 0; z < s; z++) - for (int y = 0; y < s; y++) - for (int x = 0; x < s; x++) - { - const d3Vector r( - x - s/2, - y - s/2, - z - s/2); - - const double rl = r.length(); - - if (rl > r0 && rl < r1) - { - sum += map_RS(x,y,z); - count += 1.0; - } - } - - return sum / count; -} - -d2Vector computeUniformScaleAndIntercept( - const RawImage& phase_map_RS, - const RawImage& amp_map_RS) -{ - const int s = phase_map_RS.xdim; - - d2Matrix A(0,0,0,0); - d2Vector b(0,0); - - for (int z = 0; z < s; z++) - for (int y = 0; y < s; y++) - for (int x = 0; x < s; x++) - { - const d3Vector r( - x - s/2, - y - s/2, - z - s/2); - - if (r.length() >= s/2 - 10) continue; - - const double vp = phase_map_RS(x,y,z); - const double va = amp_map_RS(x,y,z); - - A(0,0) += vp * vp; - A(0,1) += vp * 1; - A(1,0) += 1 * vp; - A(1,1) += 1 * 1; - - b[0] += vp * va; - b[1] += va; - } - - d2Matrix Ainv = A; - Ainv.invert(); - - const d2Vector solution = Ainv * b; - const double optimal_scale = solution[0]; - const double optimal_offset = solution[1]; - - std::cout << "optimal scale = " << optimal_scale << std::endl; - std::cout << "optimal offset = " << optimal_offset << std::endl; - - return d2Vector(optimal_scale, optimal_offset); -} - -double computeUniformScale( - const RawImage& phase_map_FS, - const RawImage& amp_map_FS) -{ - const int sh = phase_map_FS.xdim; - const int s = phase_map_FS.ydim; - - double num = 0.0, denom = 0.0; - - for (int z = 0; z < s; z++) - for (int y = 0; y < s; y++) - for (int x = 0; x < sh; x++) - { - const d3Vector r( - x - s/2, - y - s/2, - z - s/2); - - if (r.length() >= s/4) continue; - - const dComplex zp = phase_map_FS(x,y,z); - const dComplex za = amp_map_FS(x,y,z); - - num += za.imag * zp.imag + za.real * zp.real; - denom += zp.imag * zp.imag + zp.real * zp.real; - } - - return num / denom; -} - -BufferedImage normalisePhaseByShell( - const RawImage& phase_map_FS, - const RawImage& amp_map_FS) -{ - const int sh = phase_map_FS.xdim; - const int s = phase_map_FS.ydim; - - std::vector num(sh,0.0), denom(sh,0.0); - - for (int z = 0; z < s; z++) - for (int y = 0; y < s; y++) - for (int x = 0; x < sh; x++) - { - const dComplex zp = phase_map_FS(x,y,z); - const dComplex za = amp_map_FS(x,y,z); - - const double rd = RadialAvg::get1DIndex(x,y,z,s,s,s); - const int r = ((int)(rd + 0.5) >= sh)? sh - 1 : (int)(rd + 0.5); - - num[r] += za.imag * zp.imag + za.real * zp.real; - denom[r] += zp.imag * zp.imag + zp.real * zp.real; - } - - std::vector ratio(sh); - - for (int r = 0; r < sh; r++) - { - if (denom[r] > 0.0) - { - ratio[r] = num[r] /= denom[r]; - } - else - { - ratio[r] = 0.0; - } - } - - BufferedImage normalised_phase(sh,s,s); - - for (int z = 0; z < s; z++) - for (int y = 0; y < s; y++) - for (int x = 0; x < sh; x++) - { - const double rd = RadialAvg::get1DIndex(x,y,z,s,s,s); - const int r = ((int)(rd + 0.5) >= sh)? sh - 1 : (int)(rd + 0.5); - - normalised_phase(x,y,z) = ratio[r] * phase_map_FS(x,y,z); - } - - return normalised_phase; -} - -std::vector extractCoords( - const std::vector& atom_positions, - const RawImage phase_map, - const RawImage amp_map, - double coord_offset_pixels, - double pixel_size, - d2Matrix transform) -{ - const int ac = atom_positions.size(); - - std::vector out(ac); - - for (int a = 0; a < ac; a++) - { - - const d3Vector pos = atom_positions[a] / pixel_size + coord_offset_pixels * d3Vector(1,1,1); - - const double vp = Interpolation::linearXYZ_clip(phase_map, pos.x, pos.y, pos.z); - const double va = Interpolation::linearXYZ_clip(amp_map, pos.x, pos.y, pos.z); - - out[a] = transform * d2Vector(vp,va); - } - - return out; -} - -void plotCloud( - CPlot2D& plot2D, - const std::vector& coords, - int marker_size, dRGB colour, bool filled) -{ - CDataSet data_set; - - data_set.SetDrawMarker(true); - data_set.SetDrawLine(false); - data_set.SetMarkerSize(marker_size); - data_set.SetDatasetColor(colour.r, colour.g, colour.b); - data_set.SetDrawMarkerFilled(filled); - - const int cc = coords.size(); - - for (int c = 0; c < cc; c++) - { - const d2Vector d = coords[c]; - data_set.AddDataPoint(CDataPoint(d.x, d.y)); - } - - plot2D.AddDataSet(data_set); -} - -void plotEllipse( - CPlot2D& plot2D, - const Ellipse& ellipse, int samples, - double line_thickness, - int marker_size, dRGB colour, bool filled) -{ - CDataSet perimeter; - - perimeter.SetDrawMarker(false); - perimeter.SetDrawLine(true); - perimeter.SetDatasetColor(colour.r, colour.g, colour.b); - perimeter.SetLineWidth(line_thickness); - - for (int i = 0; i < samples+1; i++) - { - const double phi = 2.0 * PI * i / (double) samples; - const double sp = sin(phi); - const double cp = cos(phi); - - const d2Vector c = - ellipse.mean + 2.0 * ( - cp * ellipse.axis0 - + sp * ellipse.axis1); - - perimeter.AddDataPoint(CDataPoint(c.x, c.y)); - } - - plot2D.AddDataSet(perimeter); - - - CDataSet centre; - - centre.SetDrawMarker(true); - centre.SetDrawLine(false); - centre.SetMarkerSize(marker_size); - centre.SetDatasetColor(colour.r, colour.g, colour.b); - centre.SetDrawMarkerFilled(filled); - centre.AddDataPoint(CDataPoint(ellipse.mean.x, ellipse.mean.y)); - - plot2D.AddDataSet(centre); - - - CDataSet centre_outline; - - centre_outline.SetDrawMarker(true); - centre_outline.SetDrawLine(false); - centre_outline.SetMarkerSize(marker_size + 1); - centre_outline.SetDatasetColor(0, 0, 0); - centre_outline.SetDrawMarkerFilled(false); - centre_outline.AddDataPoint(CDataPoint(ellipse.mean.x, ellipse.mean.y)); - - plot2D.AddDataSet(centre_outline); -} - -std::map> mergeElementClouds( - const std::map>& name_to_cloud, - const std::map>& element_to_names) -{ - std::map> element_to_cloud; - - for (std::map>::const_iterator it0 = element_to_names.begin(); - it0 != element_to_names.end(); it0++) - { - const std::string element = it0->first; - - for (std::set::const_iterator it1 = it0->second.begin(); - it1 != it0->second.end(); it1++) - { - const std::vector& coords0 = name_to_cloud.find(*it1)->second; - std::vector& coords = element_to_cloud[element]; - - coords.insert(coords.begin(), coords0.begin(), coords0.end()); - } - } - - return element_to_cloud; -} - -Ellipse fitEllipse(const std::vector& cloud) -{ - const int ac = cloud.size(); - - d2Vector mean(0,0); - - for (int a = 0; a < ac; a++) - { - mean += cloud[a]; - } - - mean /= ac; - - Tensor2x2 cov(0,0,0); - - for (int a = 0; a < ac; a++) - { - const d2Vector d = cloud[a] - mean; - - cov.xx += d.x * d.x; - cov.xy += d.x * d.y; - cov.yy += d.y * d.y; - } - - cov /= ac; - - return cov.getEllipse(mean); -} - -double fitSlope(const std::vector& cloud) -{ - const int ac = cloud.size(); - - double num = 0.0; - double denom = 0.0; - - for (int a = 0; a < ac; a++) - { - num += cloud[a].x * cloud[a].y; - denom += cloud[a].x * cloud[a].x; - } - - return num / denom; -} - -void plotAllAtoms( - std::map> element_to_names, - std::map> coords_by_name, - std::map element_colours, - double ellipse_line_width, - bool plot_window_set, - d2Vector plot_start, - d2Vector plot_end, - const std::string& out_path, - const std::string& tag) -{ - std::ofstream element_angles_file(out_path+"element_angles_" + tag + ".txt"); - element_angles_file << "element slope angle [degrees]\n"; - - CPlot2D all_atoms_plot(""); - - std::map> all_element_coords; - - for (std::map>::iterator it = - element_to_names.begin(); it != element_to_names.end(); it++) - { - std::string element = it->first; - std::set& names = it->second; - - std::vector& all_coords = all_element_coords[element]; - - for (std::set::iterator it2 = names.begin(); - it2 != names.end(); it2++) - { - const std::string atom_name = *it2; - const std::vector& coords = coords_by_name[atom_name]; - all_coords.insert(all_coords.end(), coords.begin(), coords.end()); - } - - dRGB full_colour = element_colours[element]; - const double fade = 0.67; - dRGB faded_colour = (1 - fade) * full_colour + fade * dRGB(1,1,1); - - plotCloud(all_atoms_plot, all_coords, 1, faded_colour, true); - - const double slope = fitSlope(all_coords); - const double angle_deg = RAD2DEG(atan(slope)); - - element_angles_file << element << ": " << slope << ' ' << angle_deg << '\n'; - } - - for (std::map>::iterator it = - all_element_coords.begin(); it != all_element_coords.end(); it++) - { - std::string element = it->first; - std::vector& all_coords = it->second; - - Ellipse ellipse = fitEllipse(all_coords); - - dRGB colour = element_colours[element]; - - CDataSet line; - - const double m = fitSlope(all_coords); - const d2Vector optimal_ratio_spot = ellipse.mean.length() * d2Vector(1,m) / sqrt(1 + m*m); - - - line.SetDrawMarker(false); - line.SetDrawLine(true); - line.SetDatasetColor(colour.r, colour.g, colour.b); - line.AddDataPoint(CDataPoint(0, 0)); - line.AddDataPoint(CDataPoint(optimal_ratio_spot.x, optimal_ratio_spot.y)); - line.SetLineWidth(1.5); - - all_atoms_plot.AddDataSet(line); - } - - for (std::map>::iterator it = - all_element_coords.begin(); it != all_element_coords.end(); it++) - { - std::string element = it->first; - std::vector& all_coords = it->second; - - plotEllipse( - all_atoms_plot, fitEllipse(all_coords), 100, - ellipse_line_width, 12, element_colours[element], true); - } - - all_atoms_plot.SetTitle(tag); - all_atoms_plot.SetXAxisTitle("phase"); - all_atoms_plot.SetYAxisTitle("amplitude"); - - if (plot_window_set) - { - all_atoms_plot.SetViewArea(plot_start.x, plot_start.y, plot_end.x, plot_end.y); - } - - all_atoms_plot.OutputPostScriptPlot(out_path+"all_atoms_" + tag + ".eps"); -} - - - -int main(int argc, char *argv[]) -{ - IOParser parser; - - std::string in_phase, in_amplitude, in_model, out_path, tag; - double filter_freq, high_pass_frequency, sigma_scale, psModel; - d2Vector plot_start, plot_end; - bool plot_window_set; - int boxModel, boxOut, number_of_threads; - bool normalise_by_shell, normalise_uniformly, write_filtered_maps, write_ratios, subtract_solvent; - - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - in_phase = parser.getOption("--phase", "Phase map"); - in_amplitude = parser.getOption("--amp", "Amplitude map"); - in_model = parser.getOption("--pdb", "Atomic model"); - filter_freq = textToDouble(parser.getOption("--res", "Resolution [A]", "3.0")); - high_pass_frequency = textToDouble(parser.getOption("--hp", "High-pass frequency [px]", "-1.0")); - sigma_scale = textToDouble(parser.getOption("--sc", "Region width for scale adaptation [px]", "7.0")); - - boxModel = textToInteger(parser.getOption("--box_model", "Box size of the map corresponding to the PDB file")); - psModel = textToDouble(parser.getOption("--angpix_model", "Pixel size of the map corresponding to the PDB file")); - boxOut = textToInteger(parser.getOption("--box_out", "Box size of the map to be compared")); - - subtract_solvent = parser.checkOption("--zp", "Zero solvent phase"); - normalise_uniformly = parser.checkOption("--nu", "Normalise phase map uniformly"); - normalise_by_shell = parser.checkOption("--ns", "Normalise phase map per shell"); - - write_filtered_maps = parser.checkOption("--write_maps", "Write out filtered maps and their difference"); - write_ratios = parser.checkOption("--write_ratios", "Write out ratio maps"); - number_of_threads = textToInteger(parser.getOption("--j", "Number of threads", "6")); - - std::string plot_window = parser.getOption("--window", "Area to plot (format: ,,,)", ""); - - plot_window_set = plot_window != ""; - - if (plot_window_set) - { - for (int i = 0; i < plot_window.length(); i++) - { - if (plot_window[i] == ',') plot_window[i] = ' '; - } - - std::stringstream sts(plot_window); - - sts >> plot_start.x; - sts >> plot_start.y; - sts >> plot_end.x; - sts >> plot_end.y; - - std::cout << "plotting from " << plot_start << " to " << plot_end << std::endl; - } - - out_path = parser.getOption("--o", "Output path"); - tag = parser.getOption("--tag", "Output tag", ""); - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - if (out_path[out_path.length()-1] != '/') - { - out_path = out_path + "/"; - } - - std::string command = "mkdir -p " + out_path; - int res = system(command.c_str()); - - Image dummy; - dummy.read(in_phase, false); - const double pixel_size = dummy.samplingRateX(); - - std::cout << "pixel size: " << pixel_size << std::endl; - - - BufferedImage phase_map_RS(in_phase), amp_map_RS(in_amplitude); - - - - BufferedImage phase_map_FS, amp_map_FS; - - FFT::FourierTransform(phase_map_RS, phase_map_FS); - FFT::FourierTransform(amp_map_RS, amp_map_FS); - - const int s = amp_map_RS.xdim; - const int sh = amp_map_FS.xdim; - - - const double resolution_pixels = filter_freq / pixel_size; - - phase_map_FS = ImageFilter::lowpass3D(phase_map_FS, resolution_pixels, 5); - amp_map_FS = ImageFilter::lowpass3D(amp_map_FS, resolution_pixels, 5); - - if (high_pass_frequency > 0) - { - phase_map_FS = ImageFilter::highpassGauss3D(phase_map_FS, high_pass_frequency); - amp_map_FS = ImageFilter::highpassGauss3D(amp_map_FS, high_pass_frequency); - } - - FFT::inverseFourierTransform(phase_map_FS, phase_map_RS); - FFT::inverseFourierTransform(amp_map_FS, amp_map_RS); - - if (subtract_solvent) - { - const double avg = computeShellAverage(phase_map_RS, s/4, s/2); - phase_map_RS -= avg; - } - - Reconstruction::taper(phase_map_RS, 10, false, number_of_threads); - Reconstruction::taper(amp_map_RS, 10, false, number_of_threads); - - FFT::FourierTransform(phase_map_RS, phase_map_FS); - FFT::FourierTransform(amp_map_RS, amp_map_FS); - - const double optimal_ratio = computeUniformScale(phase_map_FS, amp_map_FS); - - /*const d2Vector scale_and_intercept = computeUniformScaleAndIntercept(phase_map_RS, amp_map_RS); - const double optimal_ratio = scale_and_intercept[0]; - const double optimal_intercept = scale_and_intercept[1];*/ - - std::cout << "amplitude contrast: " << (100.0 * optimal_ratio) << '%' << std::endl; - - if (normalise_uniformly) - { - phase_map_FS *= optimal_ratio; - } - else if (normalise_by_shell) - { - phase_map_FS = normalisePhaseByShell(phase_map_FS, amp_map_FS); - } - - - FFT::inverseFourierTransform(phase_map_FS, phase_map_RS); - FFT::inverseFourierTransform(amp_map_FS, amp_map_RS); - - if (write_filtered_maps) - { - BufferedImage filtered_difference(s,s,s); - - for (int z = 0; z < s; z++) - for (int y = 0; y < s; y++) - for (int x = 0; x < s; x++) - { - filtered_difference(x,y,z) = amp_map_RS(x,y,z) - optimal_ratio * phase_map_RS(x,y,z); - } - - - phase_map_RS.write(out_path + "phase_" + tag + ".mrc", pixel_size); - amp_map_RS.write(out_path + "amplitude_" + tag + ".mrc", pixel_size); - filtered_difference.write(out_path + "difference_" + tag + ".mrc", pixel_size); - } - - - { - const int bins = 100; - BufferedImage histogram(bins, bins); - histogram.fill(0.0); - - const double phase_sd = sqrt(Normalization::computeVariance(phase_map_RS, 0.0)); - const double amp_sd = sqrt(Normalization::computeVariance(amp_map_RS, 0.0)); - - for (int z = 0; z < s; z++) - for (int y = 0; y < s; y++) - for (int x = 0; x < s; x++) - { - const double vp = phase_map_RS(x,y,z) / phase_sd; - const double va = amp_map_RS(x,y,z) / amp_sd; - - const int ip = bins * (vp + 2.0) / 4.0 + 0.5; - const int ia = bins * (va + 2.0) / 4.0 + 0.5; - - if (ip >= 0 && ip < bins && ia >= 0 && ia < bins) - { - histogram(ip,ia) += 1.0; - } - } - - histogram.write(out_path + "joint_histogram_" + tag + ".mrc"); - - BufferedImage normalised_histogram(bins, bins); - - for (int x = 0; x < bins; x++) - { - double max_value = 0.0; - - for (int y = 0; y < bins; y++) - { - if (histogram(x,y) > max_value) - { - max_value = histogram(x,y); - } - } - - for (int y = 0; y < bins; y++) - { - normalised_histogram(x,y) = histogram(x,y) / max_value; - } - } - - normalised_histogram.write(out_path + "joint_histogram_normalised_" + tag + ".mrc"); - - BufferedImage cumulative_histogram(bins, bins); - - for (int x = 0; x < bins; x++) - { - double sum = 0.0; - - for (int y = 0; y < bins; y++) - { - sum += histogram(x,y); - } - - double sum_y = 0.0; - - for (int y = 0; y < bins; y++) - { - cumulative_histogram(x,y) = sum_y / sum; - - sum_y += histogram(x,y); - } - } - - cumulative_histogram.write(out_path + "cumulative_joint_histogram_" + tag + ".mrc"); - } - - const double coord_offset_pixels = - ( (boxOut/2)*pixel_size - (boxModel/2)*psModel ) / pixel_size; - - - Assembly assembly; - assembly.readPDB(in_model); - - std::set heavy_external; - heavy_external.insert("P"); - heavy_external.insert("CL"); - heavy_external.insert("FE"); - heavy_external.insert("ZN"); - heavy_external.insert("NA"); - heavy_external.insert("MG"); - - std::map> atoms_by_name = - PdbHelper::groupAtomsByName(assembly, heavy_external); - - std::map> coords_by_name; - - const d2Matrix scale(1e8, 0, 0, 1e8); - - for (std::map>::iterator it = - atoms_by_name.begin(); it != atoms_by_name.end(); it++) - { - coords_by_name[it->first] = extractCoords( - it->second, phase_map_RS, amp_map_RS, - coord_offset_pixels, pixel_size, scale); - - std::cout << it->first << ": " << coords_by_name[it->first].size() << std::endl; - } - - std::map> element_to_names; - - for (std::map>::iterator it = - coords_by_name.begin(); it != coords_by_name.end(); it++) - { - std::string atom_name = it->first; - std::string element = PdbHelper::getElement(atom_name); - - element_to_names[element].insert(atom_name); - } - - - std::map element_colours; - element_colours["C"] = dRGB(0.1,0.1,0.1); - element_colours["N"] = dRGB(48,80,248)/255.0; - element_colours["O"] = dRGB(1.0,0.0,0.0); - element_colours["P"] = dRGB(0.5,0.125,1); - element_colours["S"] = dRGB(0.9,0.9,0.1); - element_colours["CL"] = dRGB(0.1,0.9,0.1); - element_colours["FE"] = dRGB(255,165,0)/255.0; - element_colours["ZN"] = dRGB(165,42,42)/255.0; - element_colours["NA"] = dRGB(171,92,242)/255.0; - element_colours["MG"] = dRGB(138,255,0)/255.0; - - - const double ellipse_line_width = 0.5; - - { - std::ofstream element_angles_file(out_path+"main_chain_element_angles_" + tag + ".txt"); - element_angles_file << "element angle [degrees]\n"; - - std::vector C_coords = coords_by_name[" C "]; - std::vector CA_coords = coords_by_name[" CA "]; - std::vector O_coords = coords_by_name[" O "]; - std::vector N_coords = coords_by_name[" N "]; - - - CPlot2D main_chain_plot(""); - - std::vector all_C_coords = C_coords; - all_C_coords.insert(all_C_coords.end(), CA_coords.begin(), CA_coords.end()); - - - element_angles_file << "C: " << RAD2DEG(atan(fitSlope(all_C_coords))) << '\n'; - element_angles_file << "N: " << RAD2DEG(atan(fitSlope(N_coords))) << '\n'; - element_angles_file << "O: " << RAD2DEG(atan(fitSlope(O_coords))) << '\n'; - - - plotCloud(main_chain_plot, all_C_coords, 1, element_colours["C"], true); - plotCloud(main_chain_plot, O_coords, 1, element_colours["O"], true); - plotCloud(main_chain_plot, N_coords, 1, element_colours["N"], true); - - - plotEllipse(main_chain_plot, fitEllipse(all_C_coords), 100, - ellipse_line_width, 12, element_colours["C"], true); - - plotEllipse(main_chain_plot, fitEllipse(C_coords), 100, - 0.5*ellipse_line_width, 7, element_colours["C"], true); - - plotEllipse(main_chain_plot, fitEllipse(CA_coords), 100, - 0.5*ellipse_line_width, 7, element_colours["C"], true); - - plotEllipse(main_chain_plot, fitEllipse(O_coords), 100, - ellipse_line_width, 12, element_colours["O"], true); - - plotEllipse(main_chain_plot, fitEllipse(N_coords), 100, - ellipse_line_width, 12, element_colours["N"], true); - - main_chain_plot.SetXAxisTitle("phase"); - main_chain_plot.SetYAxisTitle("amplitude"); - - if (plot_window_set) - { - main_chain_plot.SetViewArea(plot_start.x, plot_start.y, plot_end.x, plot_end.y); - } - - main_chain_plot.OutputPostScriptPlot(out_path+"main_chain_atoms_" + tag + ".eps"); - } - - - plotAllAtoms( - element_to_names, coords_by_name, element_colours, - ellipse_line_width, plot_window_set, plot_start, plot_end, - out_path, tag); - - - //plot2D.SetTitle("amplitude over phase"); - //plot2D.SetDrawLegend(true); - -} diff --git a/src/jaz/scripts/blobs_from_points.cpp b/src/jaz/scripts/blobs_from_points.cpp deleted file mode 100644 index cb20e5576..000000000 --- a/src/jaz/scripts/blobs_from_points.cpp +++ /dev/null @@ -1,405 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - std::string points_file_name, image_directory, outDir; - double binning, score_threshold, particle_spacing, min_radius_bin1, max_radius_bin1, tolerance_bin1, threshold, tethering, - aspect_cost, contrast_cost, acceptance_threshold; - int radius_steps, max_iterations, max_frequencies, num_threads; - bool diag; - - - IOParser parser; - - try - { - IOParser parser; - - parser.setCommandLine(argc, argv); - - int general_section = parser.addSection("General options"); - - points_file_name = parser.getOption("--i", "Topaz membrane detections"); - image_directory = parser.getOption("--id", "Directory containing the micrographs"); - score_threshold = textToDouble(parser.getOption("--pt", "Score threshold for input particles", "-4")); - particle_spacing = textToDouble(parser.getOption("--sp", "Input particle spacing (bin-1 pixels)", "25")); - binning = textToDouble(parser.getOption("--bin", "Binning level", "32")); - min_radius_bin1 = textToDouble(parser.getOption("--r0", "Minimal blob radius (bin-1 pixels)", "300")); - max_radius_bin1 = textToDouble(parser.getOption("--r1", "Maximal blob radius (bin-1 pixels)", "400")); - radius_steps = textToInteger(parser.getOption("--rs", "Radius steps", "5")); - tolerance_bin1 = textToDouble(parser.getOption("--rt", "Radius tolerance (bin-1 pixels)", "50")); - threshold = textToDouble(parser.getOption("--dt", "Blob centre detection threshold (#particles)", "15")); - - max_iterations = textToInteger(parser.getOption("--it", "Max. number of iterations", "1000")); - max_frequencies = textToInteger(parser.getOption("--frq", "Max. number of blob frequencies", "6")); - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - - tethering = textToDouble(parser.getOption("--tth", - "Blob tethering to its initial position", - "0.0")); - - aspect_cost = textToDouble(parser.getOption("--ac", - "Cost of deviating from a circular shape", - "0.02")); - - contrast_cost = textToDouble(parser.getOption("--cc", - "Cost of contrast mismatch (dark pixels outside or bright pixels inside)", - "2.0")); - - acceptance_threshold = textToDouble(parser.getOption("--at", - "Acceptance threshold for final blobs", - "0.5")); - - diag = parser.checkOption("--diag", "Write out diagnostic information"); - - outDir = parser.getOption("--o", "Output directory"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - outDir = ZIO::makeOutputDir(outDir); - - if (image_directory.length() > 0) - { - if (image_directory[image_directory.length()-1] != '/') - { - image_directory = image_directory + "/"; - } - } - - - TopazParticleMap particles_by_image = TopazHelper::read(points_file_name, score_threshold); - - const std::string first_image_name = particles_by_image.begin()->first; - - t3Vector full_image_size = ImageFileHelper::getSize(image_directory + first_image_name + ".mrc"); - - const i2Vector binned_image_size( - full_image_size.x / binning, - full_image_size.y / binning); - - const double avg_radius_bin1 = (min_radius_bin1 + max_radius_bin1) / 2.0; - const double binned_avg_radius = avg_radius_bin1 / binning; - const double binned_tolerance = tolerance_bin1 / binning; - - - const int micrograph_count = particles_by_image.size(); - - std::vector all_image_names(micrograph_count); - std::vector> all_particles(micrograph_count); - - { - int index = 0; - - for (TopazParticleMap::iterator it = particles_by_image.begin(); - it != particles_by_image.end(); it++) - { - all_image_names[index] = it->first; - all_particles[index] = it->second; - - index++; - } - } - - { - std::ofstream names_list(outDir+"micrographs.txt"); - - for (int m = 0; m < micrograph_count; m++) - { - names_list << all_image_names[m] << '\n'; - } - } - - - BufferedImage diagnostic(binned_image_size.x, binned_image_size.y, micrograph_count); - - int res; - res = system(("mkdir -p "+outDir+"Frames").c_str()); - res = system(("mkdir -p "+outDir+"diag").c_str()); - - - Log::beginProgress("Finding blobs", micrograph_count / num_threads); - - #pragma omp parallel for num_threads(num_threads) - for (int m = 0; m < micrograph_count; m++) - { - const int th = omp_get_thread_num(); - - if (th == 0) - { - Log::updateProgress(m); - } - - const std::string image_name = all_image_names[m]; - const std::vector particles = all_particles[m]; - - BufferedImage centre_quality(binned_image_size.x, binned_image_size.y); - centre_quality.fill(0.f); - - BufferedImage blob_radius(binned_image_size.x, binned_image_size.y); - blob_radius.fill(0.f); - - BufferedImage micrograph; - micrograph.read(image_directory + image_name + ".mrc"); - micrograph = Resampling::FourierCrop_fullStack(micrograph, binning, 1, true); - micrograph = Normalization::byNormalDist(micrograph); - - BufferedImage lowpass0 = ImageFilter::Gauss2D( - micrograph, 0, 0.125 * binned_avg_radius, true); - - BufferedImage lowpass1 = ImageFilter::Gauss2D( - micrograph, 0, 1.500 * binned_avg_radius, true); - - BufferedImage dog = lowpass1 - lowpass0; - - if (diag) - { - dog.write(outDir+"diag/"+image_name+"_DoG.mrc"); - micrograph.write(outDir+"diag/"+image_name+"_micrograph.mrc"); - } - - - for (int y = 0; y < binned_image_size.y; y++) - for (int x = 0; x < binned_image_size.x; x++) - { - const d2Vector point_pos(x,y); - - double max_score = 0.0; - double best_radius = 0.0; - - for (int rr = 0; rr < radius_steps; rr++) - { - const double t = rr / (double)(radius_steps - 1); - const double rad_bin1 = (1 - t) * min_radius_bin1 + t * max_radius_bin1; - const double rad = rad_bin1 / binning; - - double score = 0.0; - - for (int p = 0; p < particles.size(); p++) - { - const TopazHelper::Particle& particle = particles[p]; - - const d2Vector binned_pos( - particle.coordinates.x / binning, - particle.coordinates.y / binning); - - const double d = (binned_pos - point_pos).length(); - const double dr = (d - rad) / binned_tolerance; - - score += exp(-dr*dr/2); - } - - if (score > max_score) - { - best_radius = rad; - max_score = score; - } - - } - - centre_quality(x,y) = (float) max_score; - blob_radius(x,y) = (float) best_radius; - } - - if (diag) - { - centre_quality.write(outDir + "centre_quality.mrc"); - } - - BufferedImage box_maxima = LocalExtrema::boxMaxima(centre_quality, (int)(0.72 * binned_avg_radius)); - std::vector detections = LocalExtrema::discretePoints2D(centre_quality, box_maxima, (float)threshold); - - - - std::vector all_particle_positions(particles.size()); - - for (int i = 0; i < particles.size(); i++) - { - i2Vector coords = particles[i].coordinates; - all_particle_positions[i] = d2Vector(coords.x, coords.y) / binning; - } - - - if (diag) - { - BufferedImage detections_image(binned_image_size.x, binned_image_size.y); - detections_image.fill(0.f); - Drawing::drawCrosses(detections, 1.f, 10, detections_image); - detections_image.write(outDir + "topaz_blob_detections.mrc"); - detections_image.fill(0.f); - Drawing::drawCrosses(all_particle_positions, 1.f, 3, detections_image); - detections_image.write(outDir + "topaz_input.mrc"); - } - - - BufferedImage density_map(binned_image_size.x, binned_image_size.y); - - for (int y = 0; y < binned_image_size.y; y++) - for (int x = 0; x < binned_image_size.x; x++) - { - density_map(x,y) = 0.f; - - const double sigma = 2 * particle_spacing / binning; - - for (int p = 0; p < all_particle_positions.size(); p++) - { - const double d = (all_particle_positions[p] - d2Vector(x,y)).length() / sigma; - const double rho = exp(-d*d / 2); - - if (density_map(x,y) < rho) - { - density_map(x,y) = rho; - } - } - } - - if (diag) - { - density_map.write(outDir + "density_map.mrc"); - } - - - BufferedImage final_plots_sum(binned_image_size.x, binned_image_size.y); - final_plots_sum.fill(0.f); - - BufferedImage initial_plots_sum; - - if (diag) - { - initial_plots_sum.resize(binned_image_size.x, binned_image_size.y); - initial_plots_sum.fill(0.f); - } - - std::vector all_costs(detections.size()); - std::vector> all_optimal_parameters(detections.size()); - std::vector is_accepted(detections.size()); - - for (int detection_id = 0; detection_id < detections.size(); detection_id++) - { - const d2Vector d = detections[detection_id]; - - const double radius = blob_radius((int)std::round(d.x), (int)std::round(d.y)); - - AreaPointBlobFit point_blob_fit( - density_map, dog, d, radius, binned_tolerance, tethering, aspect_cost, contrast_cost); - - /*PointBlobFit2D point_blob_fit( - all_particle_positions, d, radius, binned_tolerance, tethering);*/ - - std::vector initial_parameters(3+2*max_frequencies, 0.0); - initial_parameters[0] = radius; - initial_parameters[1] = d.x; - initial_parameters[2] = d.y; - - if (diag) - { - BufferedImage final_plot = point_blob_fit.visualise( - initial_parameters, binned_image_size.x, binned_image_size.y); - - initial_plots_sum += final_plot; - } - - - std::vector optimal_parameters = NelderMead::optimize( - initial_parameters, point_blob_fit, 0.5, 0.0001, max_iterations, - 1.0, 2.0, 0.5, 0.5, false); - - const double cost = point_blob_fit.f(optimal_parameters,0); - - - is_accepted[detection_id] = cost < acceptance_threshold; - - if (!(cost == cost)) - { - std::cout << "nan: " << d << ", " << radius << "\n"; - std::cout << "optimal_parameters.size() = " << optimal_parameters.size() << "\n"; - - for (int i = 0; i < optimal_parameters.size(); i++) - { - std::cout << optimal_parameters[i] << " "; - } - std::cout << "\n"; - } - - if (is_accepted[detection_id]) - { - BufferedImage final_plot = point_blob_fit.visualise( - optimal_parameters, binned_image_size.x, binned_image_size.y); - - final_plots_sum += final_plot; - } - - all_costs[detection_id] = cost; - all_optimal_parameters[detection_id] = optimal_parameters; - } - - - BufferedImage all_particle_dots(binned_image_size.x, binned_image_size.y); - Drawing::drawCrosses(all_particle_positions, 0.1f, 3, all_particle_dots); - final_plots_sum += all_particle_dots; - - - std::vector sorted_blobs = IndexSort::sortIndices(all_costs); - - - std::ofstream blob_file(outDir+"Frames/"+image_name+".blobs"); - - for (int i = 0; i < sorted_blobs.size(); i++) - { - if (is_accepted[sorted_blobs[i]]) - { - const std::vector parameters = all_optimal_parameters[sorted_blobs[i]]; - - for (int j = 0; j < parameters.size(); j++) - { - blob_file << binning * parameters[j] << " "; - } - - blob_file << '\n'; - - Drawing::drawCross( - d2Vector(parameters[1], parameters[2]), - (float)all_costs[sorted_blobs[i]], 5, final_plots_sum); - } - } - - diagnostic.getSliceRef(m).copyFrom(final_plots_sum); - - if (diag) - { - initial_plots_sum.write(outDir+"diag/"+image_name+"_initial.mrc"); - final_plots_sum.write(outDir+"diag/"+image_name+"_final.mrc"); - } - } - - Log::endProgress(); - - diagnostic.write(outDir + "diagnostic.mrc"); - - return 0; -} diff --git a/src/jaz/scripts/box_PCA.cpp b/src/jaz/scripts/box_PCA.cpp deleted file mode 100644 index acf51b5c7..000000000 --- a/src/jaz/scripts/box_PCA.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include -#include -#include -#include -#include - -using namespace Eigen; -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::string all_input_filenames, output_directory; - int number_of_dimensions_to_output; - bool subtract_mean; - - - IOParser parser; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - all_input_filenames = parser.getOption( - "--i", "Comma-separated list of input images (no space after comma)"); - - number_of_dimensions_to_output = textToInteger(parser.getOption( - "--N", "Number of dimensions to write out (negative means all)", "-1")); - - subtract_mean = parser.checkOption("--s", "Subtract the mean value from each image"); - - output_directory = parser.getOption( - "--o", "Output directory"); - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - output_directory = ZIO::makeOutputDir(output_directory); - - std::vector input_filenames = ZIO::split(all_input_filenames, ","); - - const int ic = input_filenames.size(); - std::vector> images(ic); - - for (int i = 0; i < ic; i++) - { - std::cout << i << ": " << input_filenames[i] << std::endl; - images[i].read(input_filenames[i]); - } - - const int w = images[0].xdim; - const int h = images[0].ydim; - const int d = images[0].zdim; - - for (int i = 1; i < ic; i++) - { - if (!images[i].hasEqualSize(images[0])) - { - REPORT_ERROR_STR( - "Images '" << input_filenames[0] << "' and '" << input_filenames[i] << "' are " - << "of different size: " << images[0].getSizeString() - << " vs. " << images[i].getSizeString() ); - } - } - - - std::vector mean(ic); - - for (int i = 0; i < ic; i++) - { - mean[i] = subtract_mean? Normalization::computeMean(images[i]) : 0.0; - } - - MatrixXd A0(ic,ic); - - std::vector values(ic); - - for (int z = 0; z < d; z++) - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - { - for (int i = 0; i < ic; i++) - { - values[i] = images[i](x,y,z); - } - - for (int i = 0; i < ic; i++) - for (int j = 0; j < ic; j++) - { - A0(i,j) += (values[i] - mean[i]) * (values[j] - mean[j]); - } - } - - JacobiSVD svd(A0, ComputeFullV); - - const int N = number_of_dimensions_to_output > 0? number_of_dimensions_to_output : ic; - - for (int i = 0; i < N; i++) - { - BufferedImage out = ((float)svd.matrixV()(0,i)) * images[0]; - - for (int j = 1; j < ic; j++) - { - out += ((float)svd.matrixV()(j,i)) * images[j]; - } - - out.write(output_directory+ZIO::itoa(i)+".mrc"); - } - - return 0; -} diff --git a/src/jaz/scripts/conversion_test.cpp b/src/jaz/scripts/conversion_test.cpp deleted file mode 100644 index f63f3c51b..000000000 --- a/src/jaz/scripts/conversion_test.cpp +++ /dev/null @@ -1,115 +0,0 @@ - -#include - -#include -#include -#include -#include - - -template -class Test -{ - public: - - Test(); - - template - Test(const Test& z); - - T a, b; -}; - -template -Test::Test() -{} - -template template -Test::Test(const Test& z) -: a(z.a), b(z.b) -{} - - - - - -template inline -T linearXY_clip(const RawImage& img, double x, double y, int z) -{ - int x0 = FLOOR(x); - int y0 = FLOOR(y); - - int x1 = x0 + 1; - int y1 = y0 + 1; - - const double xf = x - x0; - const double yf = y - y0; - - if (x0 < 0) x0 = 0; - if (x0 >= img.xdim) x0 = img.xdim - 1; - if (x1 < 0) x1 = 0; - if (x1 >= img.xdim) x1 = img.xdim - 1; - if (y0 < 0) y0 = 0; - if (y0 >= img.ydim) y0 = img.ydim - 1; - if (y1 < 0) y1 = 0; - if (y1 >= img.ydim) y1 = img.ydim - 1; - - const T vx0 = (1 - xf) * img(x0,y0,z) + xf * img(x1,y0,z); - const T vx1 = (1 - xf) * img(x0,y1,z) + xf * img(x1,y1,z); - - std::cout << vx0 << ", " << vx1 << std::endl; - - return (1 - yf) * vx0 + yf * vx1; -} - - - - - - -int main() -{ - const double x = 1.7, y = 2.3; - const int z = 0; - - BufferedImage> img(5,5); - img.fill(tComplex(2,3)); - - /*int x0 = FLOOR(x); - int y0 = FLOOR(y); - - int x1 = x0 + 1; - int y1 = y0 + 1; - - const double xf = x - x0; - const double yf = y - y0; - - if (x0 < 0) x0 = 0; - if (x0 >= img.xdim) x0 = img.xdim - 1; - if (x1 < 0) x1 = 0; - if (x1 >= img.xdim) x1 = img.xdim - 1; - if (y0 < 0) y0 = 0; - if (y0 >= img.ydim) y0 = img.ydim - 1; - if (y1 < 0) y1 = 0; - if (y1 >= img.ydim) y1 = img.ydim - 1; - - const tComplex vx0 = (1 - xf) * img(x0,y0,z) + xf * img(x1,y0,z); - const tComplex vx1 = (1 - xf) * img(x0,y1,z) + xf * img(x1,y1,z); - - std::cout << (1 - yf) * vx0 + yf * vx1;*/ - - std::cout << linearXY_clip(img.getRef(), x, y, z) << std::endl; - - return 0; - - Test ti; - ti.a = 5; - ti.b = 7; - - Test td = ti; - - std::cout << td.a << ", " << td.b << std::endl; - - return 0; -} - diff --git a/src/jaz/scripts/cuda_test.cpp b/src/jaz/scripts/cuda_test.cpp deleted file mode 100644 index aeda93b52..000000000 --- a/src/jaz/scripts/cuda_test.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - - -#ifdef CUDA -#include -#endif - - -int main(int argc, char *argv[]) -{ - #ifdef CUDA - - CudaTest00 test; - test.run(); - - #else - - std::cerr << "Not compiled with CUDA." << std::endl; - - #endif - - return 0; -} diff --git a/src/jaz/scripts/defilament.cpp b/src/jaz/scripts/defilament.cpp deleted file mode 100644 index d0201cdfc..000000000 --- a/src/jaz/scripts/defilament.cpp +++ /dev/null @@ -1,469 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace gravis; - -/* - TODO: - - - consider all frames again - - write non-circular Fourier model - - write explicit-position fit (outside of FilamentFit?) - - write TV segmentation with varying surface cost but constant (axis-aligned) anisotropy - (n.x expensive, n.y and n.z cheap) - - - outline: - - variant A: - - - iterate similarity fit until convergence (better average, more constant-error regions) - - perform TV segmentation - - fit deformation to model - - offset model parameters - - iterate similarity fit again - - - variant B: - - - find segmentation cost using a synthetic template - - perform TV segmentation - - use to intialize similarity fit - -*/ - -void read_splines( - std::string filename, - std::vector>& points, - std::vector>& radii, - double spline_binning) -{ - std::string nextSplineKey = "(0)); - radii.push_back(std::vector(0)); - - splineIndex++; - } - else if (ZIO::beginsWith(line, nextPointKey)) - { - for (int i = 0; i < line.length(); i++) - { - if (line[i] == '"') line[i] = ' '; - } - - int id; - double x, y, z, r; - - std::sscanf(line.c_str(), formatStr.c_str(), &id, &x, &y, &z, &r); - - points[splineIndex].push_back(spline_binning * d3Vector(x,y,z)); - radii[splineIndex].push_back(r); - } - } -} - -BufferedImage compute_mask( - const BufferedImage& binnedStack, - double edge_bin, - double sigma, - double thresh, - double grow_thresh, - int iters, - double current_binning, - int num_threads - ) -{ - const double edge_bin_rel = edge_bin / current_binning; - - - const int w = binnedStack.xdim; - const int h = binnedStack.ydim; - const int fc = binnedStack.zdim; - - BufferedImage out(w,h,fc); - - BufferedImage binnedStackSmooth = ImageFilter::GaussStack(binnedStack, edge_bin_rel, true); - - BufferedImage edgeStack = Resampling::downsampleFiltStack_2D_full( - binnedStackSmooth, edge_bin_rel, num_threads); - - BufferedImage edgeStackHP = ImageFilter::highpassStackGaussPadded(edgeStack, sigma); - - edgeStackHP = Normalization::byNormalDistByFrame(edgeStackHP); - - edgeStackHP = Resampling::upsampleCubic_Stack_full( - edgeStackHP, edge_bin_rel, w, h); - - for (int f = 0; f < fc; f++) - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - { - out(x,y,f) = edgeStackHP(x,y,f) > thresh? 1.0 : 0.0; - } - - for (int i = 0; i < iters; i++) - { - out = ImageFilter::GaussStack(out, edge_bin_rel, true); - - for (int f = 0; f < fc; f++) - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - { - out(x,y,f) = out(x,y,f) > grow_thresh? 1.0 : 0.0; - } - } - - return out; -} - -void precondition(BufferedImage& binnedStack) -{ - const float mean = Normalization::computeMean(binnedStack); - const float var = Normalization::computeVariance(binnedStack, mean); - const float stdDev = sqrt(var); - - for (size_t i = 0; i < binnedStack.getSize(); i++) - { - binnedStack[i] = (binnedStack[i] - mean) / stdDev; - } -} - -int main(int argc, char *argv[]) -{ - std::string splineFn = "spline_fit_coord/clicker_23.cmm"; - const double spline_binning = 6.0; - - std::string stackFn = "TS_23/TS_23_aligned_bin8.mrc"; - const double initial_binning = 8.0; - const double max_binning = 8.0; - - std::string projFn = "proj/23.proj"; - - std::string cacheDir = "cache/"; - const bool cached_filament_coordinates = true; - - const double radius_bin_1 = 500.0; - const int num_iters = 100; - - const bool use_Fourier_cropping = false; - const bool hi_pass = false; - const bool low_pass = false; - - - std::string outDir = "1st_max8_DS/"; - - - - int dummy = std::system(("mkdir -p "+outDir).c_str()); - - const int num_freqs = 20; - const int num_threads = 6; - - - - std::cout << "reading projection matrices from: " << projFn << "..." << std::endl; - - int w3D, h3D, d3D; - std::vector proj0 = ProjectionIO::read(projFn, w3D, h3D, d3D); - - for (int f = 0; f < proj0.size(); f++) - { - proj0[f] /= initial_binning; - } - - - - std::cout << "reading filament splines from: " << splineFn << "..." << std::endl; - - std::vector> splines; - std::vector> radii; - - read_splines(splineFn, splines, radii, spline_binning); - - const int filament_count = splines.size(); - - std::vector filaments(filament_count); - - for (int fil = 0; fil < filament_count; fil++) - { - filaments[fil] = Filament(splines[fil], radius_bin_1); - } - - - - std::cout << "reading image stack from: " << stackFn << "..." << std::endl; - - BufferedImage origStack_unfilt; - origStack_unfilt.read(stackFn); - - BufferedImage origStack = origStack_unfilt; - - if (hi_pass) - { - std::cout << " filtering..." << std::endl; - - origStack = ImageFilter::highpassStackGaussPadded(origStack, radius_bin_1 / initial_binning); - } - - std::cout << " preconditioning..." << std::endl; - - precondition(origStack); - - origStack.write(outDir+"initial_stack.mrc"); - - - /*{ - const double rho = 7.0; - Image> J = StructureTensor::compute2D(origStack, rho, 0.0, 5.0); - Image evals = StructureTensor::computeEigenvalues2D(J); - std::vector> evalsSplit = StructureTensor::split(evals); - - - Image diff = evalsSplit[0] - evalsSplit[1]; - - evalsSplit[0].write("eval_0.mrc"); - evalsSplit[1].write("eval_1.mrc"); - diff.write("evals_diff.mrc"); - - return 0; - }*/ - - - std::cout << " computing outlier mask..." << std::endl; - - BufferedImage marker_mask_full_size; - - - { - const double edge_bin = 16.0; - - const double sigma = 20.0; - const double thresh = -3.0; - const double grow_thresh = 0.8; - const int iters = 3; - - - marker_mask_full_size = compute_mask( - origStack, edge_bin, sigma, thresh, grow_thresh, iters, initial_binning, num_threads); - - marker_mask_full_size.write(outDir+"outlier_mask.mrc"); - } - - std::cout << "\ninitiating multiscale fit..." << std::endl; - - - const int scaleCount = (int) round(log(max_binning/initial_binning)/log(2.0)) + 1; - - std::vector - abs_binning_levels(scaleCount), - rel_binning_levels(scaleCount); - - { - double temp_binning = max_binning; - - for (int scale = 0; scale < scaleCount; scale++) - { - abs_binning_levels[scale] = temp_binning; - rel_binning_levels[scale] = temp_binning / initial_binning; - - temp_binning /= 2.0; - } - } - - - std::vector> current_params(filament_count); - - for (int fil = 0; fil < filament_count; fil++) - { - current_params[fil] = std::vector(8 * num_freqs, 0.0); - } - - for (int scale = 0; scale < scaleCount; scale++) - { - std::cout << " scale #" << (scale + 1) << ", " << abs_binning_levels[scale] << std::endl; - - double current_binning = abs_binning_levels[scale]; - double current_rel_binning = rel_binning_levels[scale]; - - BufferedImage binnedStack, binnedStack_unfilt, marker_mask; - - if (use_Fourier_cropping) - { - - binnedStack = Resampling::FourierCrop_fullStack( - origStack, current_rel_binning, num_threads, true); - - binnedStack_unfilt = Resampling::FourierCrop_fullStack( - origStack_unfilt, current_rel_binning, num_threads, true); - - marker_mask = Resampling::FourierCrop_fullStack( - marker_mask_full_size, current_rel_binning, num_threads, true); - - } - else - { - binnedStack = Resampling::downsampleFiltStack_2D_full( - origStack, current_rel_binning, num_threads); - - binnedStack_unfilt = Resampling::downsampleFiltStack_2D_full( - origStack_unfilt, current_rel_binning, num_threads); - - marker_mask = Resampling::downsampleFiltStack_2D_full( - marker_mask_full_size, current_rel_binning, num_threads); - } - - if (low_pass) - { - binnedStack = ImageFilter::GaussStack(binnedStack, 0.5, false); - } - - std::string scaleTag = "bin"+ZIO::itoa(abs_binning_levels[scale]); - - binnedStack.write(outDir + scaleTag + "_filtered_stack.mrc"); - binnedStack_unfilt.write(outDir + scaleTag + "_unfiltered_stack.mrc"); - - - const int w = binnedStack.xdim; - const int h = binnedStack.ydim; - - marker_mask.write(outDir + scaleTag + "_mask.mrc"); - - - std::vector proj = proj0; - - for (int f = 0; f < proj.size(); f++) - { - proj[f] /= current_rel_binning; - } - - std::vector models; - std::vector mappings(filament_count); - std::vector initialFits; - models.reserve(filament_count); - - std::cout << " rasterising filament coordinates..." << std::endl; - - BufferedImage allErased = binnedStack; - - for (int fil = 0; fil < filament_count; fil++) - { - filaments[fil] = Filament(splines[fil], radius_bin_1); - - if (!cached_filament_coordinates) - { - mappings[fil] = filaments[fil].rasteriseCoordinates( - w, h, proj, current_binning, num_threads); - - mappings[fil].write( - cacheDir+scaleTag+"_filament_mapping_"+ZIO::itoa(fil)); - } - else - { - mappings[fil] = FilamentMapping::read( - cacheDir+scaleTag+"_filament_mapping_"+ZIO::itoa(fil)); - } - - models.push_back(CircularFourierFilamentModel(filaments[fil].arcLen, proj)); - - initialFits.push_back(FilamentFit(filaments[fil], mappings[fil], proj, binnedStack, - marker_mask, &models[fil], current_binning, num_threads)); - - allErased = initialFits[fil].visualise(current_params[fil], true, &allErased); - } - - std::cout << " fitting filaments..." << std::endl; - - - BufferedImage allFitsOut = binnedStack_unfilt; - BufferedImage allErasedOut = binnedStack_unfilt; - - - for (int fil = 0; fil < filament_count; fil++) - { - std::cout << " " << (fil+1) << " / " << filament_count << std::endl; - - BufferedImage othersErased = initialFits[fil].visualise( - current_params[fil], true, &allErased, -1); - - FilamentFit subtractedFit(filaments[fil], mappings[fil], proj, othersErased, - marker_mask, &models[fil], current_binning, num_threads); - - - std::string tag = scaleTag + "_fil_" + ZIO::itoa(fil); - - - - /*current_params[fil] = LBFGS::optimize( - current_params[fil], subtractedFit, true, num_iters, 1e-7); - - ZIO::writeDat(current_params[fil], outDir+"params_"+tag+".dat");*/ - - - current_params[fil] = ZIO::readDat(outDir+"params_"+tag+".dat"); - - BufferedImage costByOffset = subtractedFit.computeCostByOffset( - current_params[fil], -10, 10, 42, 1.0); - - costByOffset.write(outDir+tag+"_costByOffset.mrc"); - - - - { - FilamentFit visFit(filaments[fil], mappings[fil], proj, binnedStack_unfilt, - marker_mask, &models[fil], current_binning, num_threads); - - BufferedImage predSub = visFit.visualise(current_params[fil], true, &allErasedOut); - predSub.write(outDir+tag+"_sub.mrc"); - - allFitsOut = visFit.visualise(current_params[fil], false, &allFitsOut); - allFitsOut.write(outDir+tag+"_fit.mrc"); - - allErasedOut = predSub; - } - } - } - - return 0; -} diff --git a/src/jaz/scripts/deramp.cpp b/src/jaz/scripts/deramp.cpp deleted file mode 100644 index c48e34896..000000000 --- a/src/jaz/scripts/deramp.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include -#include -#include -#include -#include - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::vector indices = {"R2_3115", "R2_3160", "R2_3180", "R2_3197"}; - - for (int ind = 0; ind < indices.size(); ind++) - { - std::string index = indices[ind]; - - std::string fn = index+"_square_DW_4.1_CTFcorrected_FlippedXZY_WeightFiltered_XYZ_bin8.rec:mrc"; - std::string fn_out = index+"_square_DW_4.1_CTFcorrected_FlippedXZY_WeightFiltered_XYZ_bin8_filtered.rec:mrc"; - std::string fn_test = index+"_square_DW_4.1_CTFcorrected_FlippedXZY_WeightFiltered_XYZ_bin8_unfiltered.rec:mrc"; - - std::cout << " " << fn << "\n-> " << fn_out << std::endl; - const double r_crit = 50; - - BufferedImage img; - img.read(fn); - - img.write(fn_test); - - BufferedImage imgFS; - - FFT::FourierTransform(img, imgFS, FFT::Both); - - const int w = img.xdim; - const int h = img.ydim; - const int d = img.zdim; - - const int wh = w/2 + 1; - - for (int z = 0; z < d; z++) - for (int y = 0; y < h; y++) - for (int x = 0; x < wh; x++) - { - const double xx = x; - const double yy = y < h/2? y : y - h; - const double zz = z < d/2? z : z - d; - - const double r = sqrt(xx*xx + yy*yy + zz*zz); - - const double amp = r_crit / (r_crit + r); - - imgFS(x,y,z) *= amp; - } - - FFT::inverseFourierTransform(imgFS, img, FFT::Both); - - img.write(fn_out); - } - - return 0; -} diff --git a/src/jaz/scripts/dose_convert.cpp b/src/jaz/scripts/dose_convert.cpp deleted file mode 100644 index 69bebf13f..000000000 --- a/src/jaz/scripts/dose_convert.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include -#include -#include - -int main(int argc, char *argv[]) -{ - IOParser parser; - - std::string orderFn, tltFn, outFn; - double fractionalDose; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - orderFn = parser.getOption("--ol", "Frame-order list (*.csv)", "order_list.csv"); - tltFn = parser.getOption("--t", "Tilt angles list (*.tlt)"); - fractionalDose = textToDouble(parser.getOption("--fd", "Fractional dose")); - outFn = parser.getOption("--o", "Output file"); - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - std::vector> order = ZIO::readFixedDoublesTable(orderFn, 2, ','); - std::vector tilts = ZIO::readDoubles(tltFn); - - int fc = tilts.size(); - // - - MetaDataTable mdt; - mdt.addLabel(EMDL_MICROGRAPH_FRAME_NUMBER); - mdt.addLabel(EMDL_MICROGRAPH_PRE_EXPOSURE); - - for (int i = 0; i < fc; i++) - { - double minDist = 1000000.0; - int bestJ = -1; - - for (int j = 0; j < order.size(); j++) - { - double d = order[j][1] - tilts[i]; - double dd = d*d; - - if (dd < minDist) - { - bestJ = j; - minDist = dd; - } - } - - mdt.addObject(); - mdt.setValue(EMDL_MICROGRAPH_FRAME_NUMBER, i+1, i); - mdt.setValue(EMDL_MICROGRAPH_PRE_EXPOSURE, bestJ * fractionalDose, i); - } - - mdt.write(outFn); - - return 0; - - /*std::ofstream out(outFn); - - for (int i = 0; i < fc; i++) - { - double minDist = 1000000.0; - int bestJ = -1; - - for (int j = 0; j < order.size(); j++) - { - double d = order[j][1] - tilts[i]; - double dd = d*d; - - if (dd < minDist) - { - bestJ = j; - minDist = dd; - } - } - - out << (bestJ * fractionalDose) << '\n'; - } - - return 0;*/ -} diff --git a/src/jaz/scripts/enumerate_micrographs.cpp b/src/jaz/scripts/enumerate_micrographs.cpp deleted file mode 100644 index f27a152b8..000000000 --- a/src/jaz/scripts/enumerate_micrographs.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::string particlesFn, outDir; - - IOParser parser; - - try - { - IOParser parser; - - parser.setCommandLine(argc, argv); - - int general_section = parser.addSection("General options"); - - particlesFn = parser.getOption("--i", "Input file (e.g. run_it023_data.star)"); - outDir = parser.getOption("--o", "Output directory"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - outDir = ZIO::makeOutputDir(outDir); - - ObservationModel obs_model; - MetaDataTable particles_table; - ObservationModel::loadSafely(particlesFn, obs_model, particles_table); - - - std::vector particles_by_micrograph = StackHelper::splitByMicrographName(particles_table); - - const int micrograph_count = particles_by_micrograph.size(); - - - MetaDataTable output_table; - - for (int m = 0; m < micrograph_count; m++) - { - output_table.addObject(); - - output_table.setValue( - EMDL_MICROGRAPH_ID, - m, - m); - - output_table.setValue( - EMDL_MICROGRAPH_NAME, - particles_by_micrograph[m].getString(EMDL_MICROGRAPH_NAME, 0), - m); - } - - output_table.write(outDir + "micrographs.star"); - - return 0; -} diff --git a/src/jaz/scripts/extract_blob_points.cpp b/src/jaz/scripts/extract_blob_points.cpp deleted file mode 100644 index fdd3fd0c0..000000000 --- a/src/jaz/scripts/extract_blob_points.cpp +++ /dev/null @@ -1,343 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::string micrographs_list_filename, micrographs_dir, blobs_dir, outPath; - double min_radius, max_radius, min_distance, perimeter_margin; - int num_threads; - bool diag, estimate_radius; - - IOParser parser; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - micrographs_list_filename = parser.getOption("--ml", "Micrograph lists filename"); - micrographs_dir = parser.getOption("--md", "Micrographs directory"); - blobs_dir = parser.getOption("--bd", "Initial blobs directory"); - min_distance = textToDouble(parser.getOption("--d", "Min. particle distance [bin-1 pixels]", "128")); - perimeter_margin = textToDouble(parser.getOption("--pm", "Distance of perimeter points from the outline [bin-1 pixels]", "64")); - estimate_radius = parser.checkOption("--est_rad", "Estimate the radius of the blob"); - min_radius = textToDouble(parser.getOption("--r0", "Min. radius [bin-1 pixels]", "300")); - max_radius = textToDouble(parser.getOption("--r1", "Max. radius [bin-1 pixels]", "800")); - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - - diag = parser.checkOption("--diag", "Write out diagnostic information"); - - outPath = parser.getOption("--o", "Output filename pattern"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - parser.writeUsage(std::cout); - exit(1); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - - outPath = ZIO::makeOutputDir(outPath); - - const std::string perimeter_dir = "perimeter/Frames/"; - const std::string full_dir = "full/Frames/"; - const std::string outer_half_dir = "outer_half/Frames/"; - - ZIO::makeOutputDir(outPath + perimeter_dir); - ZIO::makeOutputDir(outPath + full_dir); - ZIO::makeOutputDir(outPath + outer_half_dir); - - micrographs_dir = ZIO::ensureEndingSlash(micrographs_dir); - blobs_dir = ZIO::ensureEndingSlash(blobs_dir); - - std::vector all_micrograph_names; - all_micrograph_names.reserve(1000); - - std::ifstream file(micrographs_list_filename); - - if (!file) - { - REPORT_ERROR("Unable to read " + micrographs_list_filename); - } - - std::string line; - - while (std::getline(file, line)) - { - std::stringstream sts; - sts << line; - - std::string name; - sts >> name; - - all_micrograph_names.push_back(name); - } - - const int micrograph_count = all_micrograph_names.size(); - - std::vector failed_micrographs; - - #pragma omp parallel for num_threads(num_threads) - for (int m = 0; m < micrograph_count; m++) - { - const int thread_id = omp_get_thread_num(); - const bool verbose = thread_id == 0; - - if (verbose) - { - Log::beginSection( - "Micrograph "+ZIO::itoa(m+1)+"/" - +ZIO::itoa((int)std::ceil(micrograph_count / (double)num_threads))+" on this thread (out of " - +ZIO::itoa(micrograph_count)+" on all threads)"); - } - - try - { - const std::string micrograph_name = all_micrograph_names[m]; - const std::string micrograph_path = micrographs_dir + micrograph_name + ".mrc"; - const std::string blob_path = blobs_dir + micrograph_name + ".blobs"; - std::vector> blob_shapes = ZIO::readDoublesTable(blob_path); - - BufferedImage micrograph; - micrograph.read(micrograph_path); - - - const double pixel_size = ImageFileHelper::getSamplingRate(micrograph_path); - - const int w = micrograph.xdim; - const int h = micrograph.ydim; - - - BufferedImage weight(micrograph); - weight.fill(1.f); - - const int blob_count = blob_shapes.size(); - - BufferedImage plot(micrograph); - - std::vector blobs(blob_count); - - if (verbose) - { - Log::beginProgress("Processing Blobs", blob_count); - } - - for (int b = 0; b < blob_count; b++) - { - if (verbose) - { - Log::updateProgress(b); - } - - if (blob_shapes[b].size()%2 == 0) - { - for (int i = 0; i < blob_shapes[b].size(); i++) - { - std::cout << i << ": " << blob_shapes[b][i] << "\n"; - } - - REPORT_ERROR("The blobs in "+blob_path+" do not have a radius"); - } - - DelineatedBlob2D blob(blob_shapes[b]); - - if (estimate_radius) - { - std::pair,std::vector> radAvgAndWgh - = blob.radialAverageAndWeight( - micrograph, weight, max_radius); - - std::vector radAvg = radAvgAndWgh.first; - std::vector radWgh = radAvgAndWgh.second; - - const int bin_count = radAvg.size(); - - int best_t = 0; - double best_separation = 0.0; - - int delta_t = 20; - - for (int t = min_radius; t <= max_radius - delta_t && t < bin_count; t++) - { - double avg0 = 0.0; - double wgh0 = 0.0; - - int t0 = t - delta_t; - if (t0 < 0) t0 = 0; - - for (int i = t0; i < t; i++) - { - avg0 += radWgh[i] * radAvg[i]; - wgh0 += radWgh[i]; - } - - double avg1 = 0.0; - double wgh1 = 0.0; - - int t1 = t + delta_t + 1; - if (t0 > bin_count) t1 = bin_count; - - for (int i = t; i < t1; i++) - { - avg1 += radWgh[i] * radAvg[i]; - wgh1 += radWgh[i]; - } - - avg0 /= wgh0; - avg1 /= wgh1; - - const double separation = avg1 - avg0; - - if (separation > best_separation) - { - best_separation = separation; - best_t = t; - } - } - - blob.radius = best_t; - } - - blobs[b] = blob; - } - - if (verbose) - { - Log::endProgress(); - } - - const int EDGE = 0; - const int OUTER_HALF = 1; - const int FULL_BLOB = 2; - - std::vector> picks(3); - - - for (int b = 0; b < blob_count; b++) - { - DelineatedBlob2D blob = blobs[b]; - - - const double d = min_distance; - const double margin = d; - const double dd = 0.5 * sqrt(3.0) * d; - const int rc = (h - 2*margin) / dd + 1; - const std::vector cc = {(int)((w - 2*margin) / d), (int)((w - 2*margin - d/2) / d)}; - - for (int r = 0; r < rc; r++) - for (int c = 0; c < cc[r%2]; c++) - { - gravis::d2Vector p( - margin + (r%2)*d/2 + c*d, - margin + r*dd); - - double rr = blob.getRelativeSignedDistance(p); - - if (rr < 0) - { - picks[FULL_BLOB].push_back(p); - - if (diag) Drawing::drawCross(p, 100.f, 3, plot); - - if (rr > -0.5) - { - picks[OUTER_HALF].push_back(p); - - if (diag) Drawing::drawCross(p, 100.f, 6, plot); - } - } - } - - const double l = blob.perimeter(); - const int samples = std::floor(l / min_distance); - - DelineatedBlob2D smaller_blob(blob); - smaller_blob.radius -= perimeter_margin; - - for (int i = 0; i < samples; i++) - { - const double phi = 2 * PI * i / (double) samples; - const d2Vector p = smaller_blob.getOutlinePoint(phi); - - if (p.x > margin && p.x < w - margin - && p.y > margin && p.y < h - margin) - { - picks[EDGE].push_back(p); - - if (diag) Drawing::drawCross(p, 100.f, 10, plot); - } - } - } - - if (diag) plot.write(outPath + "diag_"+ZIO::itoa(m)+".mrc", pixel_size); - - std::vector tables(3); - - for (int i = 0; i < 3; i++) - { - const int pc = picks[i].size(); - MetaDataTable& table = tables[i]; - - table.setName("images"); - - table.addLabel(EMDL_IMAGE_COORD_X); - table.addLabel(EMDL_IMAGE_COORD_Y); - table.addLabel(EMDL_PARTICLE_AUTOPICK_FOM); - table.addLabel(EMDL_PARTICLE_CLASS); - table.addLabel(EMDL_ORIENT_PSI); - - for (int p = 0; p < pc; p++) - { - table.addObject(); - - table.setValue(EMDL_IMAGE_COORD_X, picks[i][p].x, p); - table.setValue(EMDL_IMAGE_COORD_Y, picks[i][p].y, p); - table.setValue(EMDL_PARTICLE_AUTOPICK_FOM, 1.0, p); - table.setValue(EMDL_PARTICLE_CLASS, 0, p); - table.setValue(EMDL_ORIENT_PSI, 0.0, p); - } - } - - tables[EDGE].write(outPath + perimeter_dir + micrograph_name + "_blob_pick.star"); - tables[FULL_BLOB].write(outPath + full_dir + micrograph_name + "_blob_pick.star"); - tables[OUTER_HALF].write(outPath + outer_half_dir + micrograph_name + "_blob_pick.star"); - } - catch (...) - { - #pragma omp critical - { - failed_micrographs.push_back(all_micrograph_names[m]); - } - } - - if (verbose) - { - Log::endSection(); - } - } - - - if (failed_micrographs.size() > 0) - { - ZIO::writeToFile(failed_micrographs, outPath + "failed_micrographs.txt"); - } - - return 0; -} diff --git a/src/jaz/scripts/find_blobs_2d.cpp b/src/jaz/scripts/find_blobs_2d.cpp deleted file mode 100644 index 2f2f51f24..000000000 --- a/src/jaz/scripts/find_blobs_2d.cpp +++ /dev/null @@ -1,428 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::string particlesFn, class_averages_filename, outDir; - int num_threads, max_class, min_MG, max_MG; - double radius, binning, additional_binning; - bool flip_y, diag; - - - IOParser parser; - - try - { - IOParser parser; - - parser.setCommandLine(argc, argv); - - int general_section = parser.addSection("General options"); - - particlesFn = parser.getOption("--i", "Input file (e.g. run_it023_data.star)"); - class_averages_filename = parser.getOption("--ca", "Class averages stack"); - radius = textToDouble(parser.getOption("--r", "Average blob radius (in bin-1 pixels)", "300")); - flip_y = parser.checkOption("--flip_y", "Centre of blob lies in positive y direction"); - - - max_class = textToInteger(parser.getOption("--max_class", "Last 2D class to consider", "25")); - min_MG = textToInteger(parser.getOption("--min_MG", "First micrograph index", "0")); - max_MG = textToInteger(parser.getOption("--max_MG", "Last micrograph index", "-1")); - - binning = textToDouble(parser.getOption("--bin", "Binning level to work at", "8")); - additional_binning = textToDouble(parser.getOption("--bin2", "Additional binning for the log file", "4")); - - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - diag = parser.checkOption("--diag", "Write out diagnostic information"); - outDir = parser.getOption("--o", "Output directory"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - outDir = ZIO::makeOutputDir(outDir); - - ObservationModel obs_model; - MetaDataTable particles_table; - ObservationModel::loadSafely(particlesFn, obs_model, particles_table); - - - std::vector particles_by_micrograph = StackHelper::splitByMicrographName(particles_table); - - const int micrograph_count = particles_by_micrograph.size(); - - if (max_MG < 0) - { - max_MG = micrograph_count - 1; - } - - i2Vector full_size, binned_size, log_size; - { - - const std::string micrograph_fn = particles_by_micrograph[0].getString(EMDL_MICROGRAPH_NAME, 0); - - BufferedImage micrograph; - micrograph.read(micrograph_fn); - - full_size.x = micrograph.xdim; - full_size.y = micrograph.ydim; - - BufferedImage micrograph_binned = Resampling::FourierCrop_fullStack( - micrograph, binning, num_threads, true); - - binned_size.x = micrograph_binned.xdim; - binned_size.y = micrograph_binned.ydim; - - BufferedImage micrograph_binned_2 = Resampling::FourierCrop_fullStack( - micrograph_binned, additional_binning, num_threads, true); - - log_size.x = micrograph_binned_2.xdim; - log_size.y = micrograph_binned_2.ydim; - - } - - const int w_log = log_size.x; - const int h_log = log_size.y; - - const int w_binned = binned_size.x; - const int h_binned = binned_size.y; - - BufferedImage log_image(w_log, h_log, max_MG + 1); - log_image.fill(0.f); - - - - Log::beginProgress("Finding blobs", (max_MG - min_MG + 1)/num_threads); - - std::vector> blob_coordinates(max_MG + 1); - - #pragma omp parallel for num_threads(num_threads) - for (int m = min_MG; m <= max_MG; m++) - { - const int thread_id = omp_get_thread_num(); - - if (thread_id == 0) - { - Log::updateProgress(m); - } - - MetaDataTable& particles = particles_by_micrograph[m]; - - const std::string micrograph_fn = particles.getString(EMDL_MICROGRAPH_NAME, 0); - - BufferedImage micrograph; - micrograph.read(micrograph_fn); - - BufferedImage micrograph_binned = Resampling::FourierCrop_fullStack( - micrograph, binning, 1, true); - - if (diag) - { - micrograph_binned.write(outDir + "mg_" + ZIO::itoa(m) + "_micrograph_binned.mrc"); - } - - BufferedImage accumulation_image(w_binned, h_binned); - - accumulation_image.fill(0.f); - - - const int particle_count = particles.numberOfObjects(); - - for (int p = 0; p < particle_count; p++) - { - const int class_id = particles.getIntMinusOne(EMDL_PARTICLE_CLASS, p); - - if (class_id <= max_class) - { - const d2Vector image_coord( - particles.getDouble(EMDL_IMAGE_COORD_X, p), - particles.getDouble(EMDL_IMAGE_COORD_Y, p)); - - const double phi = DEG2RAD(particles.getDouble(EMDL_ORIENT_PSI, p)); - - const d2Vector predicted_centre = flip_y? - (image_coord + radius * d2Vector(sin(phi), cos(phi))) / binning : - (image_coord - radius * d2Vector(sin(phi), cos(phi))) / binning; - - const int xi = (int)(predicted_centre.x + 0.5); - const int yi = (int)(predicted_centre.y + 0.5); - - if (xi >= 0 && xi < w_binned && yi >= 0 && yi < h_binned) - { - accumulation_image(xi, yi) += 1; - } - } - } - - BufferedImage blurred_accumulation_image = ImageFilter::Gauss2D( - accumulation_image, 0, 0.5 * radius / binning, true); - - if (diag) - { - blurred_accumulation_image.write(outDir + "mg_" + ZIO::itoa(m) + "_likelihood_by_particles.mrc"); - } - - BufferedImage blurred_micrograph_0 = ImageFilter::Gauss2D( - micrograph_binned, 0, 1.00 * radius / binning, true); - - BufferedImage blurred_micrograph_1 = ImageFilter::Gauss2D( - micrograph_binned, 0, 0.25 * radius / binning, true); - - BufferedImage dog_map = blurred_micrograph_0 - blurred_micrograph_1; - - if (diag) - { - dog_map.write(outDir + "mg_" + ZIO::itoa(m) + "_likelihood_by_appearance.mrc"); - } - - BufferedImage detections(w_binned, h_binned); - - for (int y = 0; y < h_binned; y++) - for (int x = 0; x < w_binned; x++) - { - const float dog = dog_map(x,y); - const float centre = blurred_accumulation_image(x,y); - - if (dog > 0.f && centre > 0.f) - { - detections(x,y) = dog * centre; - } - } - - if (diag) - { - detections.write(outDir + "mg_" + ZIO::itoa(m) + "_likelihood_total.mrc"); - } - - BufferedImage box_maxima = LocalExtrema::boxMaxima(detections, radius / binning); - - const float mean_DOG = Normalization::computeMean(detections); - const float threshold = mean_DOG; - - BufferedImage sparse_detections_image(w_binned, h_binned); - std::vector sparse_detections; - sparse_detections.reserve(100); - - for (int y = 0; y < h_binned; y++) - for (int x = 0; x < w_binned; x++) - { - const float bm = box_maxima(x,y); - const float dt = detections(x,y); - - if (bm == dt && dt > threshold) - { - sparse_detections_image(x,y) = dt; - sparse_detections.push_back(binning * d2Vector(x,y)); - } - else - { - sparse_detections_image(x,y) = 0.f; - } - } - - blob_coordinates[m] = sparse_detections; - - BufferedImage micrograph_binned_2 = Resampling::FourierCrop_fullStack( - micrograph_binned, additional_binning, 1, true); - - const float mean = Normalization::computeMean(micrograph_binned_2); - const float variance = Normalization::computeVariance(micrograph_binned_2, mean); - const float dot_value = mean + 7.f * sqrt(variance); - - RawImage log_slice = log_image.getSliceRef(m); - log_slice.copyFrom(micrograph_binned_2); - - for (int i = 0; i < sparse_detections.size(); i++) - { - const d2Vector d = sparse_detections[i] / (binning * additional_binning); - const int dx = (int)(d.x + 0.5); - const int dy = (int)(d.y + 0.5); - - if (dx >= 0 && dx < w_log && dy >= 0 && dy < h_log) - { - log_slice(dx,dy) = dot_value; - } - } - } - - Log::endProgress(); - - log_image.write(outDir+"diagnostic.mrc"); - - { - const int mc = blob_coordinates.size(); - - std::ofstream ofs(outDir+"blobs.star"); - - for (int m = 0; m < mc; m++) - { - MetaDataTable mdt; - mdt.setName(ZIO::itoa(m)); - - const int bc = blob_coordinates[m].size(); - - for (int b = 0; b < bc; b++) - { - const d2Vector p = blob_coordinates[m][b]; - - mdt.addObject(); - - mdt.setValue(EMDL_IMAGE_COORD_X, p.x); - mdt.setValue(EMDL_IMAGE_COORD_Y, p.y); - } - - mdt.write(ofs); - } - } - - return 0; - - - /* - - { - const double bin = 32; - const double radius_bin1 = 350; - const double radius_binned = radius_bin1 / bin; - const double threshold = 0.1; - - BufferedImage micrograph_binned = Resampling::FourierCrop_fullStack( - micrograph_filtered, bin, num_threads, true); - - micrograph_binned.write(outPath+"DEBUG_micrograph_binned.mrc"); - - BufferedImage symm = evaluateRotationalSymmetry( - micrograph_binned, radius_binned, 1.5*radius_binned, 0.25*radius_binned); - - float mean = Normalization::computeMean(symm); - - symm -= mean; - - symm.write(outPath+"DEBUG_symmetry.mrc"); - - - BufferedImage lowpass0 = ImageFilter::Gauss2D( - micrograph_binned, 0, 0.5 * radius_binned, true); - - BufferedImage lowpass1 = ImageFilter::Gauss2D( - micrograph_binned, 0, 1.500 * radius_binned, true); - - BufferedImage dog = lowpass1 - lowpass0; - - dog.write(outPath+"DEBUG_dog.mrc"); - - double var_dog = Normalization::computeVariance(dog, 0.f); - double var_symm = Normalization::computeVariance(symm, 0.f); - - dog /= sqrt(var_dog); - symm /= sqrt(var_symm); - - dog = ImageFilter::thresholdAbove(dog, 0.f); - symm = ImageFilter::thresholdAbove(symm, 0.f); - - BufferedImage product = dog * symm; - - product.write(outPath+"DEBUG_product.mrc"); - - BufferedImage box_maxima = LocalExtrema::boxMaxima( - product, (int)(0.72 * radius_binned)); - - box_maxima.write(outPath+"DEBUG_product_maxima.mrc"); - - std::vector peaks = LocalExtrema::discretePoints2D( - product, box_maxima, (float)threshold); - - const float mean_mg = Normalization::computeMean(micrograph_binned); - const float var_mg = Normalization::computeVariance(micrograph_binned, mean_mg); - const float drawing_value = mean_mg + 5 * sqrt(var_mg); - - Drawing::drawCrosses(peaks, drawing_value, 5, micrograph_binned); - - micrograph_binned.write(outPath+"DEBUG_detections.mrc"); - - std::exit(0); - } - - */ -} - -/* -BufferedImage DeleteBlobs2DProgram::evaluateRotationalSymmetry( - const RawImage &image, double radius, double max_radius, double sigma) -{ - const int w = image.xdim; - const int h = image.ydim; - - BufferedImage out(w,h); - - BufferedImage weight(w,h); - weight.fill(1.f); - - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - { - std::vector params{(double)x, (double)y}; - Blob2D blob(params, max_radius); - - std::vector radAvg = blob.radialAverage(image, weight); - - double sum = 0, sum_wgh = 0; - - for (int i = 0; i < radAvg.size(); i++) - { - sum += radAvg[i] * i; - sum_wgh += i; - } - - const double avg = sum / sum_wgh; - - double power = 0; - - for (int i = 0; i < radAvg.size(); i++) - { - const double d = radAvg[i] - avg; - const double dr = i - radius; - const double g = exp(-dr/(2*sigma*sigma)); - - power += g * d * d * i; - } - - out(x,y) = power; - } - - return out; -}*/ diff --git a/src/jaz/scripts/fit_class_blobs.cpp b/src/jaz/scripts/fit_class_blobs.cpp deleted file mode 100644 index 692968ba1..000000000 --- a/src/jaz/scripts/fit_class_blobs.cpp +++ /dev/null @@ -1,313 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::string particlesFn, class_averages_filename, classes_filename, outDir; - int num_threads, num_frequencies, min_MG, max_MG, max_iterations; - double radius, edge_padding; - bool diag, flip_y; - - - IOParser parser; - - try - { - IOParser parser; - - parser.setCommandLine(argc, argv); - - int general_section = parser.addSection("General options"); - - particlesFn = parser.getOption("--i", "Input STAR file output by align_2d_classes"); - class_averages_filename = parser.getOption("--ca", "Class averages stack"); - classes_filename = parser.getOption("--classes", "File with a list of 2D classes to consider"); - radius = textToDouble(parser.getOption("--r", "Average blob radius (in bin-1 pixels)", "600")); - flip_y = parser.checkOption("--flip_y", "Centre of blob lies in positive y direction"); - edge_padding = textToDouble(parser.getOption("--pad", "Edge padding (in bin-1 pixels)", "20")); - num_frequencies = textToInteger(parser.getOption("--f", "Number of 1D frequencies used to model the blob", "10")); - - min_MG = textToInteger(parser.getOption("--min_MG", "First micrograph index", "0")); - max_MG = textToInteger(parser.getOption("--max_MG", "Last micrograph index", "-1")); - - max_iterations = textToInteger(parser.getOption("--it", "Maximal number of iterations", "1000")); - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - diag = parser.checkOption("--diag", "Write out diagnostic information"); - outDir = parser.getOption("--o", "Output directory"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - outDir = ZIO::makeOutputDir(outDir); - - ObservationModel obs_model; - MetaDataTable particles_table; - ObservationModel::loadSafely(particlesFn, obs_model, particles_table); - - BufferedImage class_averages; - class_averages.read(class_averages_filename); - - - const int box_size = class_averages.xdim; - const int num_classes = class_averages.zdim; - - std::vector classes_to_consider = ZIO::readInts(classes_filename); - const int relevant_class_count = classes_to_consider.size(); - - std::vector class_to_subset(num_classes, -1); - - for (int i = 0; i < relevant_class_count; i++) - { - class_to_subset[classes_to_consider[i]] = i; - } - - - const double pixel_size = obs_model.getPixelSize(0); - - - BufferedImage blob_fits(box_size, box_size, relevant_class_count); - std::vector> all_optimal_parameters(relevant_class_count); - - - - Log::beginProgress("Fitting blobs", relevant_class_count/num_threads); - - #pragma omp parallel for num_threads(num_threads) - for (int cc = 0; cc < relevant_class_count; cc++) - { - const int thread_id = omp_get_thread_num(); - - if (thread_id == 0) - { - Log::updateProgress(cc); - } - - const int class_id = classes_to_consider[cc]; - - RawImage slice = class_averages.getSliceRef(class_id); - const d2Vector initial_centre(box_size/2, box_size/2 + (flip_y? radius : -radius)); - - BlobFit2D blob_fit( - slice, initial_centre, radius/2, 0, 0, num_threads); - - for (int y = 0; y < blob_fit.weight.ydim; y++) - for (int x = 0; x < blob_fit.weight.xdim; x++) - { - const double dx = x - box_size/2; - const double dy = y - box_size/2; - const double r = sqrt(dx*dx + dy*dy); - - if (r < box_size/2 - edge_padding) - { - blob_fit.weight(x,y) = 1; - } - else - { - blob_fit.weight(x,y) = 0; - } - } - - - std::vector initial_parameters(num_frequencies + 2, 0.0); - initial_parameters[0] = initial_centre.x; - initial_parameters[1] = initial_centre.y; - - std::vector optimal_parameters = NelderMead::optimize( - initial_parameters, blob_fit, 2, 0.001, max_iterations, 1.0, 2.0, 0.5, 0.5, false); - - all_optimal_parameters[cc] = optimal_parameters; - - - Blob2D blob(optimal_parameters, radius + 2*box_size); - - std::vector radial_average = blob.radialAverage(slice, blob_fit.weight); - BufferedImage projection = blob.radialAverageProjection(slice, radial_average); - - blob_fits.copySliceFrom(cc, projection); - - // DEBUG: - /*{ - const double c = box_size/2; - std::vector coeffs0 = {c, 0.0, 20.0, 0.0, 20.0, 0.0}; - Blob2D b0(coeffs0, box_size); - - std::vector radial_average(box_size+1); - - for (int i = 0; i < box_size; i++) - { - radial_average[i] = sin(i); - } - - BufferedImage projection; - - projection = b0.radialAverageProjection(slice, radial_average); - - projection.write("DEBUG_projection_0.mrc", pixel_size); - - std::vector coeffs1 = Blob2D::rotate(coeffs0, DEG2RAD(30), d2Vector(c,c)); - Blob2D b1(coeffs1, box_size); - - projection = b1.radialAverageProjection(slice, radial_average); - - projection.write("DEBUG_projection_1.mrc", pixel_size); - - std::exit(0); - }*/ - } - - Log::endProgress(); - - blob_fits.write(outDir+"blob_fits_by_class.mrc", pixel_size); - - - - ZIO::makeOutputDir(outDir+"Frames"); - - MetaDataTable output_particles; - - std::vector particles_by_micrograph = StackHelper::splitByMicrographName(particles_table); - const int micrograph_count = particles_by_micrograph.size(); - - Log::beginProgress("Deleting Blobs", micrograph_count); - - for (int m = 0; m < micrograph_count; m++) - { - Log::updateProgress(m); - - const MetaDataTable& particles = particles_by_micrograph[m]; - const int particle_count = particles.numberOfObjects(); - - - std::vector compressed_particle_indices(particle_count, -1); - - int current_index = 0; - - for (int p = 0; p < particle_count; p++) - { - const int global_class_id = particles.getIntMinusOne(EMDL_PARTICLE_CLASS, p); - const int selected_class_id = class_to_subset[global_class_id]; - - if (selected_class_id >= 0) - { - compressed_particle_indices[p] = current_index; - current_index++; - } - } - - const int relevant_particle_count = current_index; - - if (relevant_particle_count > 0) - { - BufferedImage all_particle_images(box_size, box_size, relevant_particle_count); - BufferedImage all_blob_images(box_size, box_size, relevant_particle_count); - - #pragma omp parallel for num_threads(num_threads) - for (int p = 0; p < particle_count; p++) - { - const int global_class_id = particles.getIntMinusOne(EMDL_PARTICLE_CLASS, p); - const int selected_class_id = class_to_subset[global_class_id]; - - if (selected_class_id >= 0) - { - const double dx_A = particles.getDouble(EMDL_ORIENT_ORIGIN_X_ANGSTROM, p); - const double dy_A = particles.getDouble(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, p); - - std::vector raw_blob_parameters = all_optimal_parameters[selected_class_id]; - raw_blob_parameters[0] += dx_A / pixel_size; - raw_blob_parameters[1] += dy_A / pixel_size; - - const d2Vector raw_blob_centre(raw_blob_parameters[0], raw_blob_parameters[1]); - const d2Vector image_centre((double)(box_size/2), (double)(box_size/2)); - const double max_dist = (image_centre - raw_blob_centre).length() + box_size / sqrt(2.0) + 1; - - const double phi = DEG2RAD(particles.getDouble(EMDL_ORIENT_PSI, p)); - - const double centre = box_size / 2; - const d2Vector axis(centre,centre); - - std::vector rotated_blob_parameters = Blob2D::rotate(raw_blob_parameters, phi, axis); - Blob2D blob(rotated_blob_parameters, max_dist); - - BufferedImage weight(box_size,box_size); - weight.fill(1.f); - - - std::string img_fn = particles.getString(EMDL_IMAGE_NAME, p); - BufferedImage particle_image; - particle_image.read(img_fn); - - std::vector rad_average = blob.radialAverage(particle_image, weight); - BufferedImage projection = blob.radialAverageProjection(particle_image, rad_average); - - particle_image -= projection; - - const int index = compressed_particle_indices[p]; - all_particle_images.copySliceFrom(index, particle_image); - all_blob_images.copySliceFrom(index, projection); - } - } - - std::string img_fn = particles.getString(EMDL_IMAGE_NAME, 0); - img_fn = outDir + "Frames/" + img_fn.substr(img_fn.find_last_of('/')+1); - - all_particle_images.write(img_fn); - all_blob_images.write(img_fn.substr(0,img_fn.find_last_of('.'))+"_blobs.mrcs"); - - for (int p = 0; p < particle_count; p++) - { - const int index = compressed_particle_indices[p]; - - if (index >= 0) - { - output_particles.addObject(); - output_particles.setObject(particles.getObject(p)); - output_particles.setValue(EMDL_IMAGE_NAME, ZIO::itoa(index+1) + "@" + img_fn); - } - } - } - } - - Log::endProgress(); - - ObservationModel::saveNew(output_particles, obs_model.opticsMdt, obs_model.generalMdt, outDir + "particles.star"); - - return 0; -} diff --git a/src/jaz/scripts/img_test.cpp b/src/jaz/scripts/img_test.cpp deleted file mode 100644 index 7739eb5c6..000000000 --- a/src/jaz/scripts/img_test.cpp +++ /dev/null @@ -1,33 +0,0 @@ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace gravis; -using namespace au::edu::anu::qm::ro; - -template -void test(const RawImage& img) -{ - std::cout << img.ydim << "\n"; -} - -int main(int argc, char *argv[]) -{ - BufferedImage imgVec0(64,64,64); - RawImage img0(64,64,64,imgVec0.data); - std::cout << img0.ydim << "\n"; - test(img0); - return 0; -} diff --git a/src/jaz/scripts/imod_test.cpp b/src/jaz/scripts/imod_test.cpp deleted file mode 100644 index 26d6f8b09..000000000 --- a/src/jaz/scripts/imod_test.cpp +++ /dev/null @@ -1,130 +0,0 @@ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - if (false) - { - Image stack; - stack.read("imod_good/b001ts001.ali:mrc"); - - BufferedImage stack2(stack.data.xdim, stack.data.ydim, stack.data.zdim); - - for (int z = 0; z < stack.data.zdim; z++) - for (int y = 0; y < stack.data.ydim; y++) - for (int x = 0; x < stack.data.xdim; x++) - { - stack2(x,y,z) = stack(z,y,x); - } - - stack2.write("imod_good/b001ts001_nohead.ali:mrc"); - - for (int z = 0; z < stack.data.zdim; z++) - for (int y = 0; y < stack.data.ydim; y++) - for (int x = 0; x < stack.data.xdim; x++) - { - stack(z,y,x) = (x == y)? 1.0 : 0.0; - } - - stack.write("diagonal.ali:mrc"); - - for (int z = 0; z < stack.data.zdim; z++) - for (int y = 0; y < stack.data.ydim; y++) - for (int x = 0; x < stack.data.xdim; x++) - { - stack2(x,y,z) = (x == y)? 1.0 : 0.0; - } - - stack2.write("diagonal_nohead.ali:mrc"); - - return 0; - } - - /*if (false) - { - std::string stack_fn = "gridStack.mrc"; - std::string tlt_fn = "test_off2.tlt"; - std::string xf_fn = "testC.xf"; - - TomoStack ts0 = TomoStack(stack_fn, -1, tlt_fn, xf_fn, "", 1.0, 1.0); - - const int w = ts0.images[0].xdim; - const int h = ts0.images[0].ydim; - const int d = 81; // field THICKNESS in .com file - - BufferedImage dest(w,d,h); - BufferedImage maskDest(w,d,h); - - dest.fill(0.0); - maskDest.fill(0.0); - - d3Vector origin(0.0, -d/2.0 + 1.0, 0.0); - d3Vector spacing(1.0); - - const int n_threads = 6; - - RealSpaceBackprojection::backprojectRaw( - ts0, dest, maskDest, origin, spacing, n_threads, - RealSpaceBackprojection::Linear, - 2, 2, 1.0); - - dest.writeVtk("gridStack_dyn_testC_off2.vtk", d3Vector(0.0, d/2, 0.0), d3Vector(1.0)); - }*/ - - //if (false) - { - const int w = 240; - const int h = 160; - const int s = 240; - const int fc = 5; - - BufferedImage testImg(w,h,fc); - testImg.fill(0.f); - - for (int i = 0; i < s; i++) - { - for (int d = 1; d <= 3; d++) - { - const double e = s / (2 << d); - const double intensity = 1.0 / d; - const int qMax = (int) FLOOR(s / e); - - for (int q = 0; q <= qMax; q++) - { - const int r = ((int)(q * e + 0.5) + s/2) % s; - - for (int f = 0; f < fc; f++) - for (int t = 0; t < s; t++) - { - if (r < w && t < h) testImg(r,t,f) += intensity; - if (r < h && t < w) testImg(t,r,f) += intensity; - } - } - } - } - - testImg.write("gridStack.mrc"); - testImg.write("gridStack.vtk"); - - return 1; - } - - - - -} diff --git a/src/jaz/scripts/make_split_tilts_star.cpp b/src/jaz/scripts/make_split_tilts_star.cpp deleted file mode 100644 index 6906f4c2f..000000000 --- a/src/jaz/scripts/make_split_tilts_star.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include -#include -#include -#include - - -int main(int argc, char *argv[]) -{ - std::map> movies_by_tomo; - - for (int i = 1; i < argc; i++) - { - std::string arg = std::string(argv[i]); - std::string arg_no_path = arg.substr(arg.find_last_of('/')+1); - - int tomo_index = -1; - int tilt_index = -1; - - if (!sscanf(arg_no_path.c_str(), "TS_%d_%d_%*s", &tomo_index, &tilt_index)) - { - std::cerr << "failed to extract indices from " << argv[i] << std::endl; - } - else - { - std::string tomo_name = arg_no_path.substr(0, arg_no_path.find_first_of('_', 3)); - - movies_by_tomo[tomo_name][tilt_index] = arg; - } - } - - std::ofstream out_stream("tilt_frames.star"); - - for (auto it = movies_by_tomo.begin(); it != movies_by_tomo.end(); it++) - { - MetaDataTable table; - - std::cout << it->first << std::endl; - - table.setName(it->first); - - for (auto itt = it->second.begin(); itt != it->second.end(); itt++) - { - std::cout << " " << itt->first << " " << itt->second << std::endl; - - table.addObject(); - table.setValue(EMDL_TOMO_TILT_MOVIE_INDEX, itt->first); - table.setValue(EMDL_TOMO_TILT_MOVIE_FILE_NAME, itt->second); - } - - table.write(out_stream); - } - - out_stream.flush(); - - return 0; -} diff --git a/src/jaz/scripts/measure_noise.cpp b/src/jaz/scripts/measure_noise.cpp deleted file mode 100644 index 0a40a1d5b..000000000 --- a/src/jaz/scripts/measure_noise.cpp +++ /dev/null @@ -1,240 +0,0 @@ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - std::string outDir; - int num_threads, tomoIndex, boxSize; - - OptimisationSet optimisationSet; - - //ReferenceMap reference; - TomoReferenceMap referenceMap; - - IOParser parser; - - try - { - parser.setCommandLine(argc, argv); - - optimisationSet.read( - parser, - true, // optimisation set - true, true, // particles - true, true, // tomograms - true, false, // trajectories - false, false, // manifolds - true, true); // reference - - int gen_section = parser.addSection("General refinement options"); - - boxSize = textToInteger(parser.getOption("--b", "Box size")); - - referenceMap.read(optimisationSet); - - tomoIndex = textToInteger(parser.getOption("--ti", "Tomogram index", "0")); - boxSize = textToInteger(parser.getOption("--b", "Box size")); - - num_threads = textToInteger(parser.getOption("--j", "Number of threads", "8")); - outDir = parser.getOption("--o", "Output path"); - - parser.checkForErrors(); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - parser.writeUsage(std::cout); - std::exit(-1); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - return RELION_EXIT_FAILURE; - } - - outDir = ZIO::prepareTomoOutputDirectory(outDir, argc, argv); - - - TomogramSet tomogramSet(optimisationSet.tomograms); - - ParticleSet dataSet(optimisationSet.particles); - std::vector> particles = dataSet.splitByTomogram(tomogramSet); - - AberrationsCache aberrationsCache(dataSet.optTable, boxSize, dataSet.getOriginalPixelSize(0)); - - referenceMap.load(boxSize); - - - Tomogram tomogram = tomogramSet.loadTomogram(tomoIndex, true); - tomogram.validateParticleOptics(particles[tomoIndex], dataSet); - - BufferedImage doseWeights = tomogram.computeDoseWeight(boxSize, 1.0); - - const int s = referenceMap.getBoxSize(); - const int sh = s/2 + 1; - const int fc = tomogram.frameCount; - - std::vector> sum_delta2_f(fc, std::vector(sh, 0.0)); - std::vector> sum_obsPwr_f(fc, std::vector(sh, 0.0)); - std::vector> sum_weight_f(fc, std::vector(sh, 0.0)); - - - const int pc = particles[tomoIndex].size(); - - Log::beginProgress("Measuring noise", pc); - - for (int p = 0; p < pc; p++) - { - Log::updateProgress(p); - - const ParticleIndex part_id(p); - - const std::vector traj = dataSet.getTrajectoryInPixels(part_id, fc, tomogram.optics.pixelSize); - - #pragma omp parallel for num_threads(num_threads) - for (int f = 0; f < fc; f++) - { - d4Matrix projCut; - - BufferedImage> observation(sh,s); - - TomoExtraction::extractFrameAt3D_Fourier( - tomogram.stack, f, s, 1.0, tomogram, traj[f], - observation, projCut, 1, true); - - CTF ctf = tomogram.getCtf(f, dataSet.getPosition(part_id)); - RawImage doseSlice = doseWeights.getSliceRef(f); - - BufferedImage prediction = Prediction::predictModulated( - part_id, dataSet, tomogram.projectionMatrices[f], s, - ctf, tomogram.optics.pixelSize, aberrationsCache, - referenceMap.image_FS, - Prediction::OwnHalf, - Prediction::AmplitudeModulated, - &doseSlice, - Prediction::CtfScaled); - - for (int y = 0; y < sh; y++) - for (int x = 0; x < s; x++) - { - const double xx = x; - const double yy = y < s/2? y : y - s; - const double r = sqrt(xx*xx + yy*yy); - - const fComplex delta = prediction(x,y) - (-observation(x,y)); - - const int ri = (int) r; - - if (ri < sh) - { - sum_delta2_f[f][ri] += delta.norm(); - sum_obsPwr_f[f][ri] += observation(x,y).norm(); - sum_weight_f[f][ri] += 1.0; - } - } - } - } - - Log::endProgress(); - - - std::vector sum_delta2(sh, 0.0); - std::vector sum_obsPwr(sh, 0.0); - std::vector sum_weight(sh, 0.0); - - for (int f = 0; f < fc; f++) - { - std::vector var_delta(sh, 0.0), var_obs(sh, 0.0); - - for (int ri = 0; ri < sh; ri++) - { - if (sum_weight_f[f][ri] > 0.0) - { - var_delta[ri] = sum_delta2_f[f][ri] / sum_weight_f[f][ri]; - var_obs[ri] = sum_obsPwr_f[f][ri] / sum_weight_f[f][ri]; - } - - sum_delta2[ri] += sum_delta2_f[f][ri]; - sum_obsPwr[ri] += sum_obsPwr_f[f][ri]; - sum_weight[ri] += sum_weight_f[f][ri]; - } - - std::ofstream out(outDir + "noise_frame_" + ZIO::itoa(f) + ".dat"); - - for (int ri = 0; ri < sh; ri++) - { - out << ri << ' ' << var_delta[ri] << '\n'; - } - - out << '\n'; - - for (int ri = 0; ri < sh; ri++) - { - out << ri << ' ' << var_obs[ri] << '\n'; - } - - - } - - std::vector var_delta(sh, 0.0), var_obs(sh, 0.0); - - for (int ri = 0; ri < sh; ri++) - { - if (sum_weight[ri] > 0.0) - { - var_delta[ri] = sum_delta2[ri] / sum_weight[ri]; - var_obs[ri] = sum_obsPwr[ri] / sum_weight[ri]; - } - } - - std::ofstream out(outDir + "noise_all_frames.dat"); - - for (int ri = 0; ri < sh; ri++) - { - out << ri << ' ' << var_delta[ri] << '\n'; - } - - out << '\n'; - - for (int ri = 0; ri < sh; ri++) - { - out << ri << ' ' << var_obs[ri] << '\n'; - } - - return RELION_EXIT_SUCCESS; -} diff --git a/src/jaz/scripts/measure_scale.cpp b/src/jaz/scripts/measure_scale.cpp deleted file mode 100644 index cc1e2f8a1..000000000 --- a/src/jaz/scripts/measure_scale.cpp +++ /dev/null @@ -1,352 +0,0 @@ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace gravis; - - -BufferedImage computeFrequencyWeights( - const Tomogram& tomogram, TomoReferenceMap& referenceMap, - bool whiten, int num_threads) -{ - const int s = referenceMap.image_FS[0].ydim; - const int sh = s / 2 + 1; - const int fc = tomogram.frameCount; - - BufferedImage frqWghts(sh,s,fc); - - if (whiten) - { - #pragma omp parallel for num_threads(num_threads) - for (int f = 0; f < fc; f++) - { - BufferedImage powSpec = PowerSpectrum::periodogramAverage2D( - tomogram.stack, s, s, 2.0, f, false); - - std::vector powSpec1D = RadialAvg::fftwHalf_2D_lin(powSpec); - - std::vector frqWghts1D(powSpec1D.size()); - - for (int i = 0; i < powSpec1D.size(); i++) - { - frqWghts1D[i] = (float)(1.0 / powSpec1D[i]); - } - - RawImage fw = frqWghts.getSliceRef(f); - RadialAvg::toFftwHalf_2D_lin(frqWghts1D, sh, s, fw); - } - - - BufferedImage staticSigma2(sh,s); - staticSigma2.fill(0.f); - - for (int f = 0; f < fc; f++) - { - for (int y = 0; y < s; y++) - for (int x = 0; x < sh; x++) - { - const double val = frqWghts(x,y,f); - - if (val > 0.f) - { - staticSigma2(x,y) += 1.f / val; - } - } - } - - for (int f = 0; f < fc; f++) - { - for (int y = 0; y < s; y++) - for (int x = 0; x < sh; x++) - { - frqWghts(x,y,f) = fc / staticSigma2(x,y); - } - } - } - else - { - frqWghts.fill(1.0); - } - - for (int f = 0; f < fc; f++) - { - referenceMap.contributeWeight(frqWghts.getSliceRef(f)); - } - - BufferedImage doseWeights = tomogram.computeDoseWeight(s, 1.0); - frqWghts *= doseWeights; - - return frqWghts; -} - - - -int main(int argc, char *argv[]) -{ - std::string outDir; - int num_threads, boxSize; - - OptimisationSet optimisationSet; - - TomoReferenceMap referenceMap; - - IOParser parser; - - try - { - parser.setCommandLine(argc, argv); - - optimisationSet.read( - parser, - true, // optimisation set - true, true, // particles - true, true, // tomograms - true, false, // trajectories - false, false, // manifolds - true, true); // reference - - int gen_section = parser.addSection("General refinement options"); - - boxSize = textToInteger(parser.getOption("--b", "Box size")); - - referenceMap.read(optimisationSet); - - boxSize = textToInteger(parser.getOption("--b", "Box size")); - - num_threads = textToInteger(parser.getOption("--j", "Number of threads", "8")); - outDir = parser.getOption("--o", "Output path"); - - parser.checkForErrors(); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - parser.writeUsage(std::cout); - std::exit(-1); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - return RELION_EXIT_FAILURE; - } - - outDir = ZIO::prepareTomoOutputDirectory(outDir, argc, argv); - - - TomogramSet tomogramSet(optimisationSet.tomograms); - - ParticleSet dataSet(optimisationSet.particles); - std::vector> particles = dataSet.splitByTomogram(tomogramSet); - - AberrationsCache aberrationsCache(dataSet.optTable, boxSize, dataSet.getOriginalPixelSize(0)); - - referenceMap.load(boxSize); - - - const int f0 = 10; - const int f1 = 30; - - for (int tomoIndex = 0; tomoIndex < tomogramSet.size(); tomoIndex++) - { - Tomogram tomogram = tomogramSet.loadTomogram(tomoIndex, true); - tomogram.validateParticleOptics(particles[tomoIndex], dataSet); - - std::string outDirTomo = outDir + tomogram.name + "/"; - ZIO::ensureParentDir(outDirTomo); - - const int s = referenceMap.getBoxSize(); - const int sh = s/2 + 1; - const int fc = tomogram.frameCount; - - std::vector> sum_prdObs_f(fc, std::vector(sh, 0.0)); - std::vector> sum_prdSqr_f(fc, std::vector(sh, 0.0)); - - - BufferedImage doseWeights = tomogram.computeDoseWeight(s, 1.0); - - BufferedImage frqWeight = computeFrequencyWeights( - tomogram, referenceMap, true, num_threads); - - - const int pc = particles[tomoIndex].size(); - - Log::beginProgress("Measuring scale", pc); - - for (int p = 0; p < pc; p++) - { - Log::updateProgress(p); - - const ParticleIndex part_id = particles[tomoIndex][p]; - - const std::vector traj = dataSet.getTrajectoryInPixels(part_id, fc, tomogram.optics.pixelSize); - - #pragma omp parallel for num_threads(num_threads) - for (int f = f0; f <= f1; f++) - { - d4Matrix projCut; - - BufferedImage> observation(sh,s); - - TomoExtraction::extractFrameAt3D_Fourier( - tomogram.stack, f, s, 1.0, tomogram, traj[f], - observation, projCut, 1, true); - - CTF ctf = tomogram.getCtf(f, dataSet.getPosition(part_id)); - RawImage doseSlice = doseWeights.getSliceRef(f); - - BufferedImage prediction = Prediction::predictModulated( - part_id, dataSet, tomogram.projectionMatrices[f], s, - ctf, tomogram.optics.pixelSize, aberrationsCache, - referenceMap.image_FS, - Prediction::OwnHalf, - Prediction::AmplitudeModulated, - &doseSlice, - Prediction::CtfUnscaled); - - - const double cos_f = tomogram.projectionMatrices[f](2,2); - const double sin2_f = 1.0 - cos_f * cos_f; - const double tan2_f = sin2_f / (cos_f * cos_f); - - for (int y = 0; y < sh; y++) - for (int x = 0; x < s; x++) - { - const double xx = x; - const double yy = y < s/2? y : y - s; - const double r = sqrt(xx*xx + yy*yy); - - const fComplex obs = -observation(x,y); - const fComplex prd = prediction(x,y); - - const int ri = (int) r; - - if (ri < sh) - { - sum_prdObs_f[f][ri] += frqWeight(x,y) * (prd.real * obs.real + prd.imag * obs.imag); - sum_prdSqr_f[f][ri] += frqWeight(x,y) * (prd.real * prd.real + prd.imag * prd.imag); - } - } - } - } - - Log::endProgress(); - - - std::vector sum_prdObs_byFreq(sh, 0.0); - std::vector sum_prdSqr_byFreq(sh, 0.0); - - std::vector sum_prdObs_byFrame(fc, 0.0); - std::vector sum_prdSqr_byFrame(fc, 0.0); - std::vector ratio_byFrame(fc, 0.0); - - double sum_prdObs_tomo = 0.0; - double sum_prdSqr_tomo = 0.0; - - const double eps = 0.01; - - for (int f = 0; f < fc; f++) - { - std::vector ratio(sh, 0.0); - - for (int ri = 0; ri < sh; ri++) - { - if (sum_prdSqr_f[f][ri] > eps) - { - ratio[ri] = sum_prdObs_f[f][ri] / sum_prdSqr_f[f][ri]; - } - - sum_prdObs_byFreq[ri] += sum_prdObs_f[f][ri]; - sum_prdSqr_byFreq[ri] += sum_prdSqr_f[f][ri]; - - sum_prdObs_byFrame[f] += sum_prdObs_f[f][ri]; - sum_prdSqr_byFrame[f] += sum_prdSqr_f[f][ri]; - - sum_prdObs_tomo += sum_prdObs_f[f][ri]; - sum_prdSqr_tomo += sum_prdSqr_f[f][ri]; - } - - std::ofstream out(outDirTomo + "scale_over_freq__frame_" + ZIO::itoa(f) + ".dat"); - - for (int ri = 0; ri < sh; ri++) - { - if (ratio[ri] > 0.0) - { - out << ri << ' ' << ratio[ri] << '\n'; - } - } - - if (sum_prdSqr_byFrame[f] > eps) - { - ratio_byFrame[f] = sum_prdObs_byFrame[f] / sum_prdSqr_byFrame[f]; - } - } - - std::cout << tomogram.name << ": " << (sum_prdObs_tomo / sum_prdSqr_tomo) << '\n'; - - std::vector ratio_byFreq(sh, 0.0); - std::ofstream out(outDirTomo + "scale_over_freq__all_frames.dat"); - - for (int ri = 0; ri < sh; ri++) - { - if (sum_prdSqr_byFreq[ri] > eps) - { - ratio_byFreq[ri] = sum_prdObs_byFreq[ri] / sum_prdSqr_byFreq[ri]; - out << ri << ' ' << ratio_byFreq[ri] << '\n'; - } - } - - - std::ofstream out2(outDirTomo + "scale_over_frame.dat"); - std::ofstream out3(outDirTomo + "thickness_by_frame.dat"); - - for (int f = 0; f < fc; f++) - { - if (ratio_byFrame[f] > 0.0) - { - out2 << f << ' ' << ratio_byFrame[f] << '\n'; - } - - const double cos_f = tomogram.projectionMatrices[f](2,2); - const double sin2_f = 1.0 - cos_f * cos_f; - const double tan2_f = sin2_f / (cos_f * cos_f); - - out3 << f << ' ' << 1.0 / sqrt(1 + tan2_f) << '\n'; - } - } - - return RELION_EXIT_SUCCESS; -} diff --git a/src/jaz/scripts/membrane_erase_2D.cpp b/src/jaz/scripts/membrane_erase_2D.cpp deleted file mode 100644 index 8f9686dc8..000000000 --- a/src/jaz/scripts/membrane_erase_2D.cpp +++ /dev/null @@ -1,165 +0,0 @@ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - IOParser parser; - - std::string outTag, tomoListFn, catFnN, catFnS, catFnC; - - int num_threads; - double diameter, binning, thickness; - bool diag; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - catFnN = parser.getOption("--co", "Catalogue file, outer membrane particles"); - catFnS = parser.getOption("--ci", "Catalogue file, inner membrane particles"); - catFnC = parser.getOption("--cc", "Catalogue file, substack particles"); - - tomoListFn = parser.getOption("--t", "Tomogram list", "tomolist.txt"); - - diameter = textToDouble(parser.getOption("--d", "Disc diameter in binned pixels", "64")); - thickness = textToDouble(parser.getOption("--th", "Membrane thickness in binned pixels", "3")); - - - binning = textToDouble(parser.getOption("--bin", "Binning factor", "1")); - diag = parser.checkOption("--diag", "Write out diagnostic information"); - - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - outTag = parser.getOption("--o", "Output filename pattern"); - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - Catalogue catN(catFnN), catS(catFnS), catC(catFnC); - TomoList tomoList(tomoListFn); - - const int tc = catN.particles.size(); - - if (catN.particles.size() != catS.particles.size()) - { - REPORT_ERROR_STR("Unequal numbers of particles in " - << catFnN << " and " << catFnS << "."); - } - - int dummy = std::system(("mkdir -p " + outTag).c_str()); - - std::string diagTag = outTag + "/diag/"; - - if (diag) - { - int dummy = std::system(("mkdir -p " + diagTag).c_str()); - } - - for (int tt = 0; tt < tc; tt++) - { - DynamoParticle& partC = catC.particles[tt]; - - DynamoParticle& partN = catN.particles[tt]; - DynamoParticle& partS = catS.particles[tt]; - - const int t = partC.tomo; - - if (!tomoList.isKnown(t)) - { - REPORT_ERROR_STR("Bad tomogram: " << t << "."); - } - - std::string stackFn = tomoList.getTiltSeriesFilename(t); - std::string projFn = tomoList.getProjectionsFilename(t); - - BufferedImage stack; - stack.read(stackFn); - - const int w = stack.xdim; - const int h = stack.ydim; - const int fc = stack.zdim; - - int w0, h0, d0; - std::vector projTomo = ProjectionIO::read(projFn, w0, h0, d0); - - QuadricDiscFit mdf( - stack, projTomo, - partN.getPosition(), - partS.getPosition(), - binning, diameter, thickness, num_threads); - - std::vector x0 = mdf.getInitial(); - - std::vector x1 = NelderMead::optimize(x0, mdf, 1e-5, 1e-6, 10000); - - std::cout << "final: " << mdf.f(x1, 0) << std::endl; - - BufferedImage phaselines = mdf.writePhaselines(x1, w, h, projTomo); - - BufferedImage stackAvg(w,h,fc); - - for (int f = 0; f < fc; f++) - { - std::vector avg = PhaseLineAverage::averageNN( - stack.getSliceRef(f), phaselines.getSliceRef(f), w); - - BufferedImage expa = PhaseLineAverage::expandLIN( - stack.getSliceRef(f), phaselines.getSliceRef(f), avg); - - stackAvg.copySliceFrom(f, expa); - } - - stack -= stackAvg; - - if (diag) - { - std::string tag = diagTag + partC.getFormattedTag(); - - mdf.localTomo.write(tag + "_3D_tomo.mrc"); - - BufferedImage vis0 = mdf.visualize(x0); - BufferedImage vis1 = mdf.visualize(x1); - - vis0.write(tag + "_3D_initial.mrc"); - vis1.write(tag + "_3D_final.mrc"); - - phaselines.write(tag + "_phase_lines.mrc"); - - BufferedImage vis2D = mdf.visualize2D(x1, stack, projTomo); - vis2D.write(tag + "_fit.mrc"); - - stackAvg.write(tag + "_average.mrc"); - } - - stack.write(outTag + "/" + partC.getFormattedTag() + ".mrc"); - } - - return 0; -} - diff --git a/src/jaz/scripts/membrane_segment.cpp b/src/jaz/scripts/membrane_segment.cpp deleted file mode 100644 index 3cdb5e84d..000000000 --- a/src/jaz/scripts/membrane_segment.cpp +++ /dev/null @@ -1,274 +0,0 @@ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::string tomoTag = "214"; - - std::string pointsFn = "coords_"+tomoTag+"_bin1.txt";//"coords_145_bin8.txt"; - std::vector> coords = ZIO::readFixedDoublesTable(pointsFn, 6); - - const double binning = 16.0; - const int pc = coords.size(); - - const bool debug = true; - const double rho = 1.0; // 5 - const double sigma0 = 0.0; // 0 - const int num_threads = 6; - - const double sigma_diff = 1; - const double lambda_edge = 0.5; - const double thresh_edge = 0.1; - const double sigma_plate = 0.0002; - - const int iters_across = 30; - const int iters_along = 500; - - - { - - BufferedImage membrane; - membrane.read("debug/membrane.mrc"); - - BufferedImage softmaxDist = MembraneSegmentation::softmaxMembraneDist(membrane, 5); - softmaxDist.write("debug/softmaxDist.mrc"); - - BufferedImage centers; - centers.read("debug/maxCent_FS.mrc"); - - Tapering::taper(centers, 30, 6, false); - - centers /= softmaxDist; - - centers.write("debug/maxCent_by_softmaxDist.mrc"); - - return 0; - } - - - - - - - - - BufferedImage tomo; - tomo.read("bin16/R1_"+tomoTag+".mrc"); - - Tapering::taper(tomo, 20, num_threads, true); - - TomoList tomoList("local_tomolist.txt"); - std::string projFn = tomoList.getProjectionsFilename(145); - - - int w0, h0, d0; - std::vector projTomo = ProjectionIO::read(projFn, w0, h0, d0); - - - std::vector south(pc), north(pc); - std::vector thickness(pc); - - for (int p = 0; p < pc ; p++) - { - south[p] = d3Vector(coords[p][0], coords[p][1], coords[p][2]) / binning; - north[p] = d3Vector(coords[p][3], coords[p][4], coords[p][5]) / binning; - thickness[p] = (north[p] - south[p]).length(); - } - - - const int w = tomo.xdim; - const int h = tomo.ydim; - const int d = tomo.zdim; - - BufferedImage data(w,h,d), regional(w,h,d), weight(w,h,d); - BufferedImage regional_memb(w,h,d), weight_memb(w,h,d); - data.fill(0.f); - weight.fill(0.f); - regional.fill(0.f); - weight_memb.fill(0.f); - regional_memb.fill(0.f); - - for (int p = 0; p < pc ; p++) - { - d3Vector nd = north[p]; - - const int r = 3; - - for (int dx = -r; dx <= r; dx++) - for (int dy = -r; dy <= r; dy++) - for (int dz = -r; dz <= r; dz++) - { - regional_memb( - (int)(nd.x + 0.5) + dx, - (int)(nd.y + 0.5) + dy, - (int)(nd.z + 0.5) + dz) = 1.f; - - weight_memb( - (int)(nd.x + 0.5) + dx, - (int)(nd.y + 0.5) + dy, - (int)(nd.z + 0.5) + dz) = 1.f; - } - } - - for (int z = 0; z < d; z++) - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - { - const d3Vector r(x,y,z); - - for (int p = 0; p < pc ; p++) - { - const d3Vector n = north[p]; - const d3Vector s = south[p]; - const d3Vector t = 2.0 * n - s; - - const double dp = 0.9 * thickness[p]; - - const double ds = (s - r).length(); - const double dt = (t - r).length(); - - const double ra = (r - n).dot(n - s)/dp; - - if (dt < dp || ds < dp) - { - weight(x,y,z) += 1.f; - data(x,y,z) -= ra; - - const double thr = 1; - - if (ra > thr) - { - regional(x,y,z) = 1.f; - } - else if (ra < -thr) - { - regional(x,y,z) = -1.f; - } - } - - double bounary_conf = 0.2; - double boundary = Tapering::getTaperWeight3D(x,y,z, w,h,d, 5); - - regional(x,y,z) += bounary_conf * (1.0 - boundary); - weight(x,y,z) += bounary_conf * (1.0 - boundary); - - double conf_reg = 0.001; - double reg_thresh = -4.0; - - regional(x,y,z) += conf_reg * (tomo(x,y,z) - reg_thresh); - weight(x,y,z) += conf_reg; - } - - - } - - if (debug) - { - weight.write("debug/weight.mrc"); - regional.write("debug/regional.mrc"); - weight_memb.write("debug/weight_memb.mrc"); - regional_memb.write("debug/regional_memb.mrc"); - data.write("debug/data.mrc"); - } - - BufferedImage> J = StructureTensor::compute3D(tomo, rho, sigma0, 20.0); - J = StructureTensor::forwardAverage(J); - - /*std::vector> evals0 = StructureTensor::split( - StructureTensor::computeEigenvalues3D(J)); - - evals0[0].write("debug/Jlin_eval0.mrc"); - (evals0[0] - evals0[1]).write("debug/Jlin_eval0by1.mrc"); - - Image> J_nl = StructureTensor::computeNonLinear( - tomo, rho, sigma0, 20.0, - 20, 20, true, sigma_diff, 0.05f, num_threads); - - - std::vector> evals1 = StructureTensor::split( - StructureTensor::computeEigenvalues3D(J_nl)); - - evals1[0].write("debug/Jnonlin_eval0.mrc"); - (evals1[0] - evals1[1]).write("debug/Jnonlin_eval0by1.mrc");*/ - - std::cout << "finding membranes..." << std::endl; - - BufferedImage membrane = MembraneSegmentation::determineMembraniness( - tomo, J, sigma_diff, lambda_edge, thresh_edge, - num_threads, iters_across, iters_along); - - membrane.write("debug/membrane.mrc"); - - - BufferedImage softmaxDist = MembraneSegmentation::softmaxMembraneDist(membrane, 10); - softmaxDist.write("debug/softmaxDist.mrc"); - - - BufferedImage ptSymmsMembrane = Cells::findPointSymmetries(membrane, 20, tomo.xdim/24); - ptSymmsMembrane.write("debug/membrane_ptSymms.mrc"); - - BufferedImage> surfKernel = Skeletonization::getKernel( - J, Skeletonization::Surface, 0.1f); - BufferedImage surf = Skeletonization::apply(membrane, surfKernel, num_threads, 1); - surf.write("debug/membrane_thinned.mrc"); - - BufferedImage ptSymmsMembraneThin = Cells::findPointSymmetries(surf, 20, tomo.xdim/24); - ptSymmsMembraneThin.write("debug/thin_membrane_ptSymms.mrc"); - - - //std::vector surfPts = Skeletonization::discretize(surf, 1.f); - - //std::cout << surfPts.size() << " surface points found." << std::endl; - - BufferedImage maxCent(w,h,d), maxRad(w,h,d); - - Cells::findCenters(surf, 10, 100.f, 1.f, maxCent, maxRad, num_threads); - - - maxCent.write("debug/maxCent_FS.mrc"); - maxRad.write("debug/maxRad_FS.mrc"); - - //Image ptSymms = Cells::findPointSymmetries(tomo); - - - - //NewVtkHelper::writeR3(eig0, "debug/eigenvector0.vtk"); - - /*Image diffused = Diffusion::diffuse(tomo, D, 0.05f, 500, num_threads); - diffused.write("debug/diffused.mrc"); - - Image diffusedNoise = Diffusion::diffuse(noise, D, 0.05f, 500, num_threads); - diffusedNoise.write("debug/diffused_noise.mrc");*/ - - /*Image segTV = Segmentation::anisotropicTV(regional, D, 100, 0.1f, 0.1f); - segTV.write("debug/seg-TV_100.mrc");*/ - -} - diff --git a/src/jaz/scripts/plot_2d_classes.cpp b/src/jaz/scripts/plot_2d_classes.cpp deleted file mode 100644 index b12e5a1a6..000000000 --- a/src/jaz/scripts/plot_2d_classes.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::string particlesFn, class_averages_filename, classes_filename, micrograph_dir, outDir; - int num_threads, num_MG, num_best_classes, pad; - bool flip_contrast, diag; - - - IOParser parser; - - try - { - IOParser parser; - - parser.setCommandLine(argc, argv); - - int general_section = parser.addSection("General options"); - - particlesFn = parser.getOption("--i", "Input STAR file output by align_2d_classes"); - class_averages_filename = parser.getOption("--ca", "Class averages stack"); - micrograph_dir = parser.getOption("--mgdir", "Micrographs directory", ""); - classes_filename = parser.getOption("--classes", "File with a list of 2D classes to consider", ""); - num_best_classes = textToInteger(parser.getOption("--bc", "Number of best 2D classes to consider otherwise", "20")); - num_MG = textToInteger(parser.getOption("--mgs", "Number of micrographs to use", "12")); - pad = textToInteger(parser.getOption("--pad", "Image padding (pixels)", "20")); - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - flip_contrast = parser.checkOption("--keep_contrast", "Do not flip the contrast"); - diag = parser.checkOption("--diag", "Write out diagnostic information"); - outDir = parser.getOption("--o", "Output directory"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - outDir = ZIO::makeOutputDir(outDir); - - ObservationModel obs_model; - MetaDataTable particles_table; - ObservationModel::loadSafely(particlesFn, obs_model, particles_table); - - BufferedImage class_averages; - class_averages.read(class_averages_filename); - - - const int box_size = class_averages.xdim; - const int midbox = box_size/2; - const double radius = midbox - pad; - const int num_classes = class_averages.zdim; - - std::vector classes_to_consider; - - if (classes_filename != "") - { - classes_to_consider = ZIO::readInts(classes_filename); - } - else - { - const int class_count = ClassHelper::countClasses(particles_table); - - if (class_count < num_best_classes) - { - REPORT_ERROR_STR("Cannot consider " << num_best_classes - << " 2D classes, only " << class_count << " present"); - } - - std::vector particle_count = ClassHelper::getClassSizes(particles_table, class_count); - std::vector order = ClassHelper::sortByAscendingFrequency(particle_count); - - classes_to_consider = std::vector(num_best_classes); - - for (int i = 0; i < num_best_classes; i++) - { - classes_to_consider[i] = order[i]; - } - } - - const int relevant_class_count = classes_to_consider.size(); - - std::vector class_to_subset(num_classes, -1); - - for (int i = 0; i < relevant_class_count; i++) - { - class_to_subset[classes_to_consider[i]] = i; - } - - - const double particle_pixel_size = obs_model.getPixelSize(0); - - - std::vector particles_by_micrograph = StackHelper::splitByMicrographName(particles_table); - const int micrograph_count = std::min(num_MG, (int)particles_by_micrograph.size()); - - - t3Vector mg_size = ImageFileHelper::getSize( - particles_by_micrograph[0].getString(EMDL_MICROGRAPH_NAME, 0)); - - BufferedImage output(mg_size.x, mg_size.y, 2 * micrograph_count); - - const float scale = flip_contrast? -1.f : 1.f; - - - Log::beginProgress("Plotting 2D class averages", micrograph_count); - - for (int m = 0; m < micrograph_count; m++) - { - Log::updateProgress(m); - - if (particles_by_micrograph[m].numberOfObjects() == 0) continue; - - MetaDataTable& particles = particles_by_micrograph[m]; - - std::string micrograph_filename = particles.getString(EMDL_MICROGRAPH_NAME, 0); - - BufferedImage micrograph; - micrograph.read(micrograph_filename); - - const float average_value = Normalization::computeMean(micrograph); - - output.getSliceRef(2*m).copyFrom(micrograph); - - const int w = micrograph.xdim; - const int h = micrograph.ydim; - - const double micrograph_pixel_size = ImageFileHelper::getSamplingRate(micrograph_filename); - const double particle_scale = particle_pixel_size / micrograph_pixel_size; - - const int pc = particles.numberOfObjects(); - - for (int p = 0; p < pc; p++) - { - const int global_class_id = particles.getIntMinusOne(EMDL_PARTICLE_CLASS, p); - const int selected_class_id = class_to_subset[global_class_id]; - - if (selected_class_id >= 0) - { - const double dx_A = particles.getDouble(EMDL_ORIENT_ORIGIN_X_ANGSTROM, p); - const double dy_A = particles.getDouble(EMDL_ORIENT_ORIGIN_Y_ANGSTROM, p); - const double phi = DEG2RAD(particles.getDouble(EMDL_ORIENT_PSI, p)); - - const double xc = particles.getDouble(EMDL_IMAGE_COORD_X, p) - dx_A / micrograph_pixel_size; - const double yc = particles.getDouble(EMDL_IMAGE_COORD_Y, p) - dy_A / micrograph_pixel_size; - - const int x0 = std::max(std::ceil(xc - particle_scale * box_size), 0.0); - const int y0 = std::max(std::ceil(yc - particle_scale * box_size), 0.0); - const int x1 = std::min(std::ceil(xc + particle_scale * box_size), (double) w); - const int y1 = std::min(std::ceil(yc + particle_scale * box_size), (double) h); - - for (int y = y0; y < y1; y++) - for (int x = x0; x < x1; x++) - { - const double xm = x - xc; - const double ym = y - yc; - - const double xp = (cos(phi) * xm - sin(phi) * ym) / particle_scale; - const double yp = (sin(phi) * xm + cos(phi) * ym) / particle_scale; - - if (xp * xp + yp * yp < radius * radius) - { - micrograph(x,y) = average_value + scale * Interpolation::linearXY_clip( - class_averages.getSliceRef(global_class_id), - midbox + xp, - midbox + yp); - } - } - } - } - - output.getSliceRef(2*m+1).copyFrom(micrograph); - } - - Log::endProgress(); - - output.write(outDir+"class_average_plots.mrc"); - - return 0; -} diff --git a/src/jaz/scripts/plot_DC_analysis.cpp b/src/jaz/scripts/plot_DC_analysis.cpp deleted file mode 100644 index 465cd8c74..000000000 --- a/src/jaz/scripts/plot_DC_analysis.cpp +++ /dev/null @@ -1,413 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace gravis; - -#define LINE_BUFFER_LEN 1024 - -bool isAtomInteresting( - std::string atom_name, - bool main_chain_only, - const std::set& desired_elements) -{ - const std::string element = atom_name.substr(0,1); - const std::string position_code = atom_name.substr(1); - - if (main_chain_only - && element != "S" - && atom_name != "CA" - && position_code.length() > 0) - { - return false; - } - - if (desired_elements.size() != 0 && desired_elements.find(element) == desired_elements.end()) - { - return false; - } - - return true; -} - -int main(int argc, char *argv[]) -{ - std::string fn_data = "processed_SNR1_sig1_res2_norot/amp_over_phase_sharp_all.dat"; - std::string fn_legend = "processed_SNR1_sig1_res2_norot/amp_over_phase_sharp_all_legend.txt"; - std::string fn_out = "DC_all_CNOS_rot5.eps"; - - const bool main_chain_only = false; - const bool rotate = true; - const double angle = DEG2RAD(-5); - - std::set desired_elements; - desired_elements.insert("C"); - desired_elements.insert("O"); - desired_elements.insert("N"); - desired_elements.insert("S"); - - std::ifstream ifs_data(fn_data); - std::ifstream ifs_legend(fn_legend); - - const int line_buffer_length = 1024; - char* data_line = new char[line_buffer_length]; - - - std::map> coordinates, averages, inner_ellipses, outer_ellipses; - std::vector atom_names; - - while (ifs_legend) - { - std::string atom_name, atom_count_string; - ifs_legend >> atom_name; - ifs_legend >> atom_count_string; - - atom_names.push_back(atom_name); - - for (int figure = 0; figure < 4; figure++) - { - while (true) - { - ifs_data.getline(data_line, line_buffer_length); - - std::string data_string(data_line); - - if (data_string.length() == 0) - { - break; - } - else - { - std::stringstream sts(data_string); - - d2Vector c; - sts >> c.x; - sts >> c.y; - - c *= 1e8; - - if (rotate) - { - const d2Matrix A - ( cos(angle), -sin(angle), - sin(angle), cos(angle) ); - - c = A * c; - } - - switch (figure) - { - case 0: - coordinates[atom_name].push_back(c); - break; - case 1: - averages[atom_name].push_back(c); - break; - case 2: - inner_ellipses[atom_name].push_back(c); - break; - case 3: - outer_ellipses[atom_name].push_back(c); - break; - } - } - } - } - } - - std::map element_colours; - element_colours["C"] = dRGB(0.2,0.2,0.2); - element_colours["N"] = dRGB(0.0,0.3,1.0); - element_colours["O"] = dRGB(1.0,0.2,0.0); - element_colours["S"] = dRGB(0.8,0.8,0.0); - - CPlot2D plot2D(""); - - //plot2D.SetTitle("amplitude over phase"); - plot2D.SetDrawLegend(true); - - const int anc = atom_names.size(); - - std::map element_means; - std::map element_atom_count; - - for (std::set::iterator it = desired_elements.begin(); - it != desired_elements.end(); it++) - { - std::string element = *it; - element_means[element] = d2Vector(0,0); - element_atom_count[element] = 0; - } - - for (int an = 0; an < anc; an++) - { - const std::string atom_name = atom_names[an]; - - if (atom_name == "") continue; - - const std::string element = atom_name.substr(0,1); - - if (!isAtomInteresting(atom_name, main_chain_only, desired_elements)) - { - continue; - } - - const int ac = coordinates[atom_name].size(); - - for (int a = 0; a < ac; a++) - { - element_means[element] += coordinates[atom_name][a]; - } - - element_atom_count[element] += ac; - } - - std::map> element_covariance_matrices; - - for (std::set::iterator it = desired_elements.begin(); - it != desired_elements.end(); it++) - { - std::string element = *it; - element_means[element] /= element_atom_count[element]; - element_covariance_matrices[element] = Tensor2x2(0,0,0); - - std::cout << element << ": mean = " << element_means[element] << std::endl; - } - - for (int an = 0; an < anc; an++) - { - const std::string atom_name = atom_names[an]; - - if (atom_name == "") continue; - - const std::string element = atom_name.substr(0,1); - - if (!isAtomInteresting(atom_name, main_chain_only, desired_elements)) - { - continue; - } - - const int ac = coordinates[atom_name].size(); - - for (int a = 0; a < ac; a++) - { - const d2Vector d = coordinates[atom_name][a] - element_means[element]; - - element_covariance_matrices[element].xx += d.x * d.x; - element_covariance_matrices[element].xy += d.x * d.y; - element_covariance_matrices[element].yy += d.y * d.y; - } - } - - for (std::set::iterator it = desired_elements.begin(); - it != desired_elements.end(); it++) - { - std::string element = *it; - - element_covariance_matrices[element] /= element_atom_count[element]; - - std::cout << element << ": S = " << element_covariance_matrices[element] << std::endl; - - const dRGB colour = element_colours[element]; - - CDataSet ellipse_points; - ellipse_points.SetDrawMarker(false); - ellipse_points.SetDatasetColor(colour.r, colour.g, colour.b); - ellipse_points.SetDrawLine(true); - ellipse_points.SetLineWidth(0.5); - - const d2Vector mu = element_means[element]; - - const int ellipse_samples = 100; - - Tensor2x2 C = element_covariance_matrices[element]; - d2Vector evals, eig0, eig1; - C.diagonalize(evals, eig0, eig1); - - eig0 *= sqrt(evals[0]); - eig1 *= sqrt(evals[1]); - - std::cout << " e0 = " << eig0 << std::endl; - std::cout << " e1 = " << eig1 << std::endl; - - for (int i = 0; i < ellipse_samples+1; i++) - { - const double phi = 2.0 * PI * i / (double) ellipse_samples; - const double sp = sin(phi); - const double cp = cos(phi); - - const d2Vector c = mu + 2.0 * (cp * eig0 + sp * eig1); - - ellipse_points.AddDataPoint(CDataPoint(c.x, c.y)); - } - - plot2D.AddDataSet(ellipse_points); - - - CDataSet average_point; - average_point.SetDrawMarker(true); - average_point.SetMarkerSize(16); - average_point.SetDatasetColor(colour.r, colour.g, colour.b); - average_point.SetDrawLine(false); - average_point.SetDrawMarkerFilled(true); - - average_point.AddDataPoint(CDataPoint(mu.x, mu.y)); - average_point.SetDatasetTitle(element); - - plot2D.AddDataSet(average_point); - - - CDataSet line_to_origin_points; - line_to_origin_points.SetDrawMarker(false); - line_to_origin_points.SetDatasetColor(colour.r, colour.g, colour.b); - line_to_origin_points.SetDrawLine(true); - - line_to_origin_points.AddDataPoint(CDataPoint(mu.x, mu.y)); - line_to_origin_points.AddDataPoint(CDataPoint(0,0)); - - plot2D.AddDataSet(line_to_origin_points); - } - - std::map plot_symbol; - - plot_symbol["A"] = "o"; - plot_symbol["B"] = "x"; - plot_symbol["G"] = "+"; - plot_symbol["D"] = "diamond"; - plot_symbol["E"] = "square"; - plot_symbol["Z"] = "triangle"; - plot_symbol["H"] = "triangle"; - - for (int an = 0; an < anc; an++) - { - const std::string atom_name = atom_names[an]; - - if (atom_name == "") continue; - - const std::string element = atom_name.substr(0,1); - - if (!isAtomInteresting(atom_name, main_chain_only, desired_elements)) - { - continue; - } - - std::cout << atom_name << ": " << element << std::endl; - - const dRGB colour = element_colours[element]; - const bool is_hollow = main_chain_only && (atom_name == "CA" || atom_name == "SD"); - - const std::vector& coords = coordinates[atom_name]; - const std::vector& average = averages[atom_name]; - const std::vector& inner_ellipse = inner_ellipses[atom_name]; - const std::vector& outer_ellipse = outer_ellipses[atom_name]; - - const int ac = coords.size(); - const int ec = inner_ellipse.size(); - - CDataSet cloud_points; - cloud_points.SetDrawMarker(true); - cloud_points.SetMarkerSize(1); - cloud_points.SetDatasetColor(colour.r, colour.g, colour.b); - cloud_points.SetDrawLine(false); - cloud_points.SetDrawMarkerFilled(!is_hollow); - - for (int a = 0; a < ac; a++) - { - const d2Vector c = coords[a]; - cloud_points.AddDataPoint(CDataPoint(c.x, c.y)); - } - - - CDataSet average_point; - average_point.SetDrawMarker(true); - average_point.SetMarkerSize(8); - - if (atom_name.length() > 1) - { - average_point.SetMarkerSymbol(plot_symbol[atom_name.substr(1,1)]); - } - - average_point.SetDatasetColor(colour.r, colour.g, colour.b); - average_point.SetDrawLine(false); - - if (main_chain_only) - { - average_point.SetDatasetTitle(atom_name); - } - - average_point.SetDrawMarkerFilled(!is_hollow); - - { - const d2Vector c = average[0]; - average_point.AddDataPoint(CDataPoint(c.x, c.y)); - } - - - CDataSet line_to_origin_points; - line_to_origin_points.SetDrawMarker(false); - line_to_origin_points.SetDatasetColor(colour.r, colour.g, colour.b); - line_to_origin_points.SetDrawLine(true); - - if (main_chain_only) - { - const d2Vector c = average[0]; - line_to_origin_points.AddDataPoint(CDataPoint(c.x, c.y)); - line_to_origin_points.AddDataPoint(CDataPoint(0,0)); - } - - CDataSet inner_ellipse_points; - inner_ellipse_points.SetDrawMarker(false); - inner_ellipse_points.SetDatasetColor(colour.r, colour.g, colour.b); - inner_ellipse_points.SetDrawLine(true); - inner_ellipse_points.SetLineWidth(0.5); - - CDataSet outer_ellipse_points; - outer_ellipse_points.SetDrawMarker(false); - outer_ellipse_points.SetDatasetColor(colour.r, colour.g, colour.b); - outer_ellipse_points.SetDrawLine(true); - outer_ellipse_points.SetLineWidth(0.5); - - - for (int e = 0; e < ec; e++) - { - const d2Vector ci = inner_ellipse[e]; - const d2Vector co = outer_ellipse[e]; - - inner_ellipse_points.AddDataPoint(CDataPoint(ci.x, ci.y)); - outer_ellipse_points.AddDataPoint(CDataPoint(co.x, co.y)); - } - - if (main_chain_only) - { - //plot2D.AddDataSet(outer_ellipse_points); - plot2D.AddDataSet(line_to_origin_points); - } - - plot2D.AddDataSet(cloud_points); - plot2D.AddDataSet(average_point); - } - - - if (rotate) - { - //plot2D.SetXAxisTitle("amplitude + "+ZIO::itoa(AC)+" * phase"); - //plot2D.SetYAxisTitle("amplitude - "+ZIO::itoa(AC)+" * phase"); - } - else - { - plot2D.SetXAxisTitle("phase"); - plot2D.SetYAxisTitle("amplitude"); - } - - plot2D.OutputPostScriptPlot(fn_out); - - return 0; -} diff --git a/src/jaz/scripts/plot_dose_weight.cpp b/src/jaz/scripts/plot_dose_weight.cpp deleted file mode 100644 index 970267d41..000000000 --- a/src/jaz/scripts/plot_dose_weight.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - - - -int main(int argc, char *argv[]) -{ - IOParser parser; - parser.setCommandLine(argc, argv); - - std::string tomoSetFn, optFn, outDir; - int boxSize; - bool normalise; - - try - { - int gen_section = parser.addSection("General options"); - - tomoSetFn = parser.getOption("--t", "Tomogram set", "tomograms.star"); - optFn = parser.getOption("--ctf", "Consider a CTF using parameters from the supplied file"); - boxSize = textToInteger(parser.getOption("--b", "Box size")); - normalise = parser.checkOption("--nrm", "Scale each column to make its sum 1"); - outDir = parser.getOption("--o", "Output directory"); - - if (parser.checkForErrors()) - { - parser.writeUsage(std::cout); - std::exit(-1); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - TomogramSet tomogramSet(tomoSetFn); - const int tc = tomogramSet.size(); - - if (outDir[outDir.length()-1] != '/') - { - outDir = outDir + "/"; - } - - int res = system(("mkdir -p "+outDir).c_str()); - - for (int t = 0; t < tc; t++) - { - Tomogram tomogram = tomogramSet.loadTomogram(t, false); - const int fc = tomogram.frameCount; - const int s = boxSize; - const int sh = s/2 + 1; - - BufferedImage doseWeights = tomogram.computeDoseWeight(s, 1.0); - - BufferedImage out(sh, fc); - - for (int f = 0; f < fc; f++) - for (int x = 0; x < sh; x++) - { - out(x,f) = doseWeights(x,0,f); - } - - if (normalise) - { - for (int x = 0; x < sh; x++) - { - double sum(0.0); - - for (int f = 0; f < fc; f++) - { - sum += out(x,f); - } - - if (sum != 0.0) - { - for (int f = 0; f < fc; f++) - { - out(x,f) /= sum; - } - } - } - } - - out.write(outDir + ZIO::itoa(t) + ".mrc"); - } - - return 0; -} diff --git a/src/jaz/scripts/plot_particles.cpp b/src/jaz/scripts/plot_particles.cpp deleted file mode 100644 index 9f16b49a0..000000000 --- a/src/jaz/scripts/plot_particles.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -using namespace gravis; - - -void addPoint(const d3Vector& pos, const d3Matrix& A, double rad, double out_bin, Mesh& mesh) -{ - const d3Vector n(A(0,2), A(1,2), A(2,2)); - - MeshBuilder::addCone(pos / out_bin, (pos + 2 * rad * n) / out_bin, rad, 12, mesh); -} - -int main(int argc, char *argv[]) -{ - std::string inFn, refFn, tomoSetFn, motFn, outDir; - int t0, t1, f; - double bin, rad, relScale; - bool allFrames; - - - IOParser parser; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - inFn = parser.getOption("--i", "Input particle set"); - refFn = parser.getOption("--ref", "Other particles file to serve as reference to zoom against", ""); - tomoSetFn = parser.getOption("--t", "Tomogram set", "tomograms.star"); - - t0 = textToInteger(parser.getOption("--t0", "First tomogram", "0")); - t1 = textToInteger(parser.getOption("--t1", "Last tomogram", "-1")); - - motFn = parser.getOption("--mot", "Particle trajectories", ""); - - f = textToInteger(parser.getOption("--f", "Frame", "0")); - allFrames = parser.checkOption("--all_frames", "Output meshes for all frames"); - - bin = textToDouble(parser.getOption("--bin", "Divide coordinates by this", "1.0")); - rad = textToDouble(parser.getOption("--rad", "Size of output octahedron", "1.0")); - relScale = textToDouble(parser.getOption("--rel_scale", "Scale the distance from reference pt. by this", "8.0")); - - outDir = parser.getOption("--o", "Output directory"); - - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - if ((f != 0 || allFrames) && (motFn == "")) - { - std::cerr << "A motion file (--mot) is required to select a frame (--f) or output all frames (--all_frames)." << std::endl; - return -1; - } - - if (f < 0) - { - std::cerr << "The frame index (--f) must not be negative." << std::endl; - return -1; - } - - - TomogramSet tomogramSet(tomoSetFn); - - ParticleSet dataSet(inFn, motFn); - std::vector> particles = dataSet.splitByTomogram(tomogramSet); - - - const int tc = particles.size(); - - if (t1 < 0) t1 = tc - 1; - - outDir = ZIO::makeOutputDir(outDir); - - - for (int t = t0; t <= t1; t++) - { - const int pc = particles[t].size(); - - if (pc == 0) continue; - - Tomogram tomogram = tomogramSet.loadTomogram(t, false); - - std::vector frameSeq = IndexSort::sortIndices(tomogram.cumulativeDose); - - const int fc = tomogram.frameCount; - dataSet.checkTrajectoryLengths(particles[t][0], pc, fc, "backproject"); - - - Mesh mesh; - std::vector meshes(fc); - - for (int p = 0; p < pc; p++) - { - const ParticleIndex part_id = particles[t][p]; - - const d3Matrix A = dataSet.getMatrix3x3(part_id); - - - if (f == 0 && !allFrames) - { - const d3Vector pos = dataSet.getPosition(part_id); - - addPoint(pos, A, rad, bin, mesh); - } - else - { - const std::vector traj = dataSet.getTrajectoryInPixels( - part_id, 0, tomogram.optics.pixelSize); - - if (allFrames) - { - const int fc = traj.size(); - - for (int f = 0; f < fc; f++) - { - const d3Vector pos = traj[frameSeq[f]]; - - addPoint(pos, A, rad, bin, meshes[f]); - } - } - else - { - if (f >= traj.size()) - { - if (motFn != "") - { - REPORT_ERROR_STR( - "Bad frame index: only " << traj.size() << " frames available for particle " - << part_id.value << " in " << motFn); - } - else - { - // has been handled - } - } - - const d3Vector pos = traj[frameSeq[f]]; - - addPoint(pos, A, rad, bin, mesh); - } - } - } - - if (allFrames) - { - for (int f = 0; f < fc; f++) - { - meshes[f].writePly(outDir + ZIO::itoa(t) + "_frame_" + ZIO::itoa(f) + ".ply"); - } - } - else - { - mesh.writePly(outDir + ZIO::itoa(t) + ".ply"); - } - } - - return 0; -} diff --git a/src/jaz/scripts/plot_pdb.cpp b/src/jaz/scripts/plot_pdb.cpp deleted file mode 100644 index bff603bc8..000000000 --- a/src/jaz/scripts/plot_pdb.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include -#include -#include -#include - - -using namespace gravis; - - -void addPoint(d3Vector pos, d3Vector ref, double scale, double rad, double out_bin, Mesh& mesh) -{ - const d3Vector pos2 = ref + scale * (pos - ref); - MeshBuilder::addOctahedron(pos2 / out_bin, rad, mesh); -} - -int main(int argc, char *argv[]) -{ - std::string inFn, outDir; - double psModel, psOut; - int boxModel, boxOut; - - IOParser parser; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - inFn = parser.getOption("--i", "Input PDB file"); - outDir = parser.getOption("--o", "Output directory"); - boxModel = textToInteger(parser.getOption("--box_model", "Box size of the map corresponding to the PDB file")); - psModel = textToDouble(parser.getOption("--angpix_model", "Pixel size of the map corresponding to the PDB file")); - boxOut = textToInteger(parser.getOption("--box_out", "Box size of the map to be compared")); - psOut = textToDouble(parser.getOption("--angpix_out", "Pixel size of the map to be compared")); - - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - outDir = ZIO::makeOutputDir(outDir); - - const double coordOffset = (boxOut/2)*psOut - (boxModel/2)*psModel; - - Assembly assembly; - assembly.readPDB(inFn); - - std::map> atoms = PdbHelper::groupAtomsByElement(assembly); - - std::cout << atoms.size() << " elements found" << std::endl; - std::cout << " " << assembly.molecules.size() << " molecules" << std::endl; - std::cout << " " << assembly.looseAtoms.size() << " lose atoms" << std::endl; - - for (std::map>::iterator it = atoms.begin(); - it != atoms.end(); it++) - { - Mesh mesh; - - const std::string element = it->first; - const std::vector& positions = it->second; - - std::cout << element << " (" << positions.size() << ")" << std::endl; - - const int pc = positions.size(); - - for (int p = 0; p < pc; p++) - { - const d3Vector pos = positions[p] + coordOffset * d3Vector(1,1,1); - MeshBuilder::addOctahedron(pos, 0.5, mesh); - } - - mesh.writePly(outDir+element+".ply"); - } - - return 0; -} diff --git a/src/jaz/scripts/plot_tomo_defoci.cpp b/src/jaz/scripts/plot_tomo_defoci.cpp deleted file mode 100644 index 02b9013be..000000000 --- a/src/jaz/scripts/plot_tomo_defoci.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include -#include -#include - - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::string inFn, outDir; - - - IOParser parser; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - inFn = parser.getOption("--t", "Input tomogram set"); - outDir = parser.getOption("--o", "Output directory"); - - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - TomogramSet tomogramSet(inFn); - - outDir = ZIO::makeOutputDir(outDir); - - std::ofstream file(outDir + "defoci.dat"); - - const int tc = tomogramSet.size(); - - for (int dim = 0; dim < 2; dim++) - { - double x_coord = 0; - - for (int t = 0; t < tc; t++) - { - Tomogram tomogram = tomogramSet.loadTomogram(t, false); - - const int fc = tomogram.frameCount; - - for (int f = 0; f < fc; f++) - { - if (dim == 0) - { - file << x_coord << ' ' << tomogram.centralCTFs[f].DeltafU << '\n'; - } - else - { - file << x_coord << ' ' << tomogram.centralCTFs[f].DeltafV << '\n'; - } - - x_coord += 1.0 / (double) (fc + 1); - } - - x_coord += 1.0 / (double) (fc + 1); - } - - file << '\n'; - } - - return 0; -} - diff --git a/src/jaz/scripts/plot_topaz_points.cpp b/src/jaz/scripts/plot_topaz_points.cpp deleted file mode 100644 index 9d167ca35..000000000 --- a/src/jaz/scripts/plot_topaz_points.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - std::string points_file_name, image_directory, outDir; - double binning; - int num_threads; - - - IOParser parser; - - try - { - IOParser parser; - - parser.setCommandLine(argc, argv); - - int general_section = parser.addSection("General options"); - - points_file_name = parser.getOption("--i", "Topaz membrane detections"); - image_directory = parser.getOption("--id", "Directory containing the micrographs"); - binning = textToDouble(parser.getOption("--bin", "Binning level", "32")); - - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - - outDir = parser.getOption("--o", "Output directory"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - outDir = ZIO::makeOutputDir(outDir); - - if (image_directory.length() > 0) - { - if (image_directory[image_directory.length()-1] != '/') - { - image_directory = image_directory + "/"; - } - } - - - TopazParticleMap particles_by_image = TopazHelper::read(points_file_name, -1000); - - const std::string first_image_name = particles_by_image.begin()->first; - - - BufferedImage micrograph; - micrograph.read(image_directory + first_image_name + ".mrc"); - micrograph = Resampling::FourierCrop_fullStack(micrograph, binning, 1, true); - - /*const i2Vector binned_image_size( - full_image_size.x / binning, - full_image_size.y / binning);*/ - - const i2Vector binned_image_size(micrograph.xdim, micrograph.ydim); - - const int micrograph_count = particles_by_image.size(); - - std::vector all_image_names(micrograph_count); - std::vector> all_particles(micrograph_count); - - { - int index = 0; - - for (TopazParticleMap::iterator it = particles_by_image.begin(); - it != particles_by_image.end(); it++) - { - all_image_names[index] = it->first; - all_particles[index] = it->second; - - index++; - } - } - - { - std::ofstream names_list(outDir+"micrographs.txt"); - - for (int m = 0; m < micrograph_count; m++) - { - names_list << m << " " << all_image_names[m] << '\n'; - } - } - - - BufferedImage all_particle_images(binned_image_size.x, binned_image_size.y, micrograph_count); - BufferedImage all_micrographs(binned_image_size.x, binned_image_size.y, micrograph_count); - - - int res = system(("mkdir -p "+outDir+"Frames").c_str()); - - - Log::beginProgress("Plotting micrographs and particles", micrograph_count / num_threads); - - #pragma omp parallel for num_threads(num_threads) - for (int m = 0; m < micrograph_count; m++) - { - const int th = omp_get_thread_num(); - - if (th == 0) - { - Log::updateProgress(m); - } - - const std::string image_name = all_image_names[m]; - const std::vector particles = all_particles[m]; - - BufferedImage centre_quality(binned_image_size.x, binned_image_size.y); - centre_quality.fill(0.f); - - BufferedImage blob_radius(binned_image_size.x, binned_image_size.y); - blob_radius.fill(0.f); - - BufferedImage micrograph; - micrograph.read(image_directory + image_name + ".mrc"); - micrograph = Resampling::FourierCrop_fullStack(micrograph, binning, 1, true); - micrograph = Normalization::byNormalDist(micrograph); - - all_micrographs.getSliceRef(m).copyFrom(micrograph); - - - BufferedImage particles_image(binned_image_size.x, binned_image_size.y); - particles_image.fill(0.f); - - - for (int i = 0; i < particles.size(); i++) - { - TopazHelper::Particle p = particles[i]; - i2Vector c = p.coordinates; - - Drawing::drawCross(d2Vector(c.x, c.y) / binning, (float)p.score, 2, particles_image); - } - - all_particle_images.getSliceRef(m).copyFrom(particles_image); - } - - Log::endProgress(); - - all_particle_images.write(outDir + "particles.mrc"); - all_micrographs.write(outDir + "micrographs.mrc"); - - return 0; -} diff --git a/src/jaz/scripts/predict_tomo_view.cpp b/src/jaz/scripts/predict_tomo_view.cpp deleted file mode 100644 index ef633a641..000000000 --- a/src/jaz/scripts/predict_tomo_view.cpp +++ /dev/null @@ -1,178 +0,0 @@ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - std::string particleFn, tomoSetFn, outPath; - double minFreqPx, lowPass; - bool oppositeHalf, predictCTF, applyDose, writeObs; - int threads, tomoIndex, particleIndex, boxSize; - - //ReferenceMap reference; - TomoReferenceMap referenceMap; - - IOParser parser; - - try - { - parser.setCommandLine(argc, argv); - - parser.addSection("General options"); - - particleFn = parser.getOption("--i", "Input particle set"); - tomoSetFn = parser.getOption("--t", "Tomogram set", "tomograms.star"); - tomoIndex = textToInteger(parser.getOption("--ti", "Tomogram index", "0")); - particleIndex = textToInteger(parser.getOption("--pi", "Particle index", "0")); - boxSize = textToInteger(parser.getOption("--b", "Box size")); - - referenceMap.read(parser); - - minFreqPx = textToDouble(parser.getOption("--min_freq", "Min. image frequency [px]", "0")); - - predictCTF = parser.checkOption("--predict_CTF", "Modulate prediction by CTF"); - oppositeHalf = parser.checkOption("--opposite_half", "Make prediction from the opposite subset"); - applyDose = !parser.checkOption("--no_dose", "Do not apply dose weighting"); - writeObs = parser.checkOption("--write_obs", "Write out the observed images as well"); - - lowPass = textToDouble(parser.getOption("--lowpass", "Blur by a Gaussian with this sigma [px]", "-1")); - - threads = textToInteger(parser.getOption("--j", "Number of threads", "1")); - outPath = parser.getOption("--o", "Output path"); - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - return RELION_EXIT_FAILURE; - } - - if (outPath[outPath.length()-1] != '/') - { - outPath = outPath + "/"; - } - - std::string command = "mkdir -p " + outPath; - int res = system(command.c_str()); - - - TomogramSet tomogramSet(tomoSetFn); - - ParticleSet dataSet(particleFn); - std::vector> particles = dataSet.splitByTomogram(tomogramSet); - - AberrationsCache aberrationsCache(dataSet.optTable, boxSize, dataSet.getOriginalPixelSize(0)); - - referenceMap.load(boxSize); - - const int s = referenceMap.getBoxSize(); - const int sh = s/2 + 1; - - - { - const int pc = particles[tomoIndex].size(); - - if (pc <= particleIndex) - { - REPORT_ERROR_STR("Only " << pc << " particles found in tomogram " << tomoIndex); - } - - const ParticleIndex part_id(particleIndex); - - Tomogram tomogram = tomogramSet.loadTomogram(tomoIndex, true); - tomogram.validateParticleOptics({part_id}, dataSet); - - BufferedImage doseWeights = tomogram.computeDoseWeight(s, 1.0); - - const int fc = tomogram.frameCount; - - if (writeObs) - { - BufferedImage obsStack(s,s,fc); - BufferedImage sliceRS(s,s); - BufferedImage> sliceFS(sh,s); - - const std::vector traj = dataSet.getTrajectoryInPixels(part_id, fc, tomogram.optics.pixelSize); - - for (int f = 0; f < fc; f++) - { - d4Matrix projCut; - - TomoExtraction::extractFrameAt3D_Fourier( - tomogram.stack, f, s, 1.0, tomogram, traj[f], - sliceFS, projCut, 1, true); - - Centering::shiftInSitu(sliceFS); - FFT::inverseFourierTransform(sliceFS, sliceRS); - - obsStack.getSliceRef(f).copyFrom(sliceRS); - } - - obsStack.write( - outPath + "tomo_" + ZIO::itoa(tomoIndex) + "_part_" + ZIO::itoa(particleIndex) + "_obs.mrc", - tomogram.optics.pixelSize); - } - - BufferedImage predStack(s,s,fc); - - for (int f = 0; f < fc; f++) - { - CTF ctf = tomogram.getCtf(f, dataSet.getPosition(part_id)); - RawImage doseSlice = doseWeights.getSliceRef(f); - - BufferedImage prediction = Prediction::predictModulated( - part_id, dataSet, tomogram.projectionMatrices[f], s, - ctf, tomogram.optics.pixelSize, aberrationsCache, - referenceMap.image_FS, - oppositeHalf? Prediction::OppositeHalf : Prediction::OwnHalf, - predictCTF? Prediction::AmplitudeModulated : Prediction::Unmodulated, - applyDose? &doseSlice : 0, - Prediction::CtfScaled); - - Centering::shiftInSitu(prediction); - - BufferedImage predReal(s,s); - FFT::inverseFourierTransform(prediction, predReal); - - predStack.getSliceRef(f).copyFrom(predReal); - } - - predStack.write( - outPath + "tomo_" + ZIO::itoa(tomoIndex) - + "_part_" + ZIO::itoa(particleIndex) + "_pred.mrc", - tomogram.optics.pixelSize); - } - - return RELION_EXIT_SUCCESS; -} diff --git a/src/jaz/scripts/print_coords.cpp b/src/jaz/scripts/print_coords.cpp deleted file mode 100644 index 3cbdde6b7..000000000 --- a/src/jaz/scripts/print_coords.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - IOParser parser; - - std::string particlesFn, tomoSetFn, outFn; - int particlesToOutput; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - particlesFn = parser.getOption("--i", "Input particle set"); - tomoSetFn = parser.getOption("--t", "Tomogram set", "tomograms.star"); - particlesToOutput = textToInteger(parser.getOption("--n", "Number of particles to output", "10")); - outFn = parser.getOption("--o", "Output filename pattern"); - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - TomogramSet tomogramSet(tomoSetFn); - - ParticleSet dataSet(particlesFn, ""); - std::vector> particles = dataSet.splitByTomogram(tomogramSet); - - const int tc = particles.size(); - - std::ofstream output(outFn); - - int tpc = 0; - - for (int t = 0; t < tc; t++) - { - const int pc = particles[t].size(); - - if (pc == 0) continue; - - Tomogram tomogram = tomogramSet.loadTomogram(t, false); - output << "ts_" << t << ": \n"; - - std::vector projTomo = tomogram.projectionMatrices; - const int fc = projTomo.size(); - - for (int p = 0; p < pc; p++) - { - output << "\n\tparticle " << p << ": \n\n"; - - const ParticleIndex part_id = particles[t][p]; - - const d3Vector pos = dataSet.getPosition(part_id); - - const gravis::d4Vector pw(pos.x, pos.y, pos.z, 1.0); - - for (int f = 0; f < fc; f++) - { - const d4Vector q = projTomo[f] * pw; - d2Vector r = gravis::d2Vector(q.x, q.y); - - output << "\t\t" << f << ": \t" - << std::setw(8) << r.x << " \t" - << std::setw(8) << r.y << '\n'; - } - - tpc++; - - if (tpc >= particlesToOutput) return 0; - } - } - - return 0; -} diff --git a/src/jaz/scripts/process_image.cpp b/src/jaz/scripts/process_image.cpp deleted file mode 100644 index fc7fd0b4b..000000000 --- a/src/jaz/scripts/process_image.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include -#include -#include - -#include - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - return 0; -} diff --git a/src/jaz/scripts/ramp.cpp b/src/jaz/scripts/ramp.cpp deleted file mode 100644 index 49f0b75c9..000000000 --- a/src/jaz/scripts/ramp.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include -#include -#include -#include -#include - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - if (argc < 3) - { - std::cerr << "usage: ramp \n"; - return 1; - } - - std::string fnIn = argv[1]; - std::string fnOut = argv[2]; - - BufferedImage img; - img.read(fnIn); - - BufferedImage out = ImageFilter::ramp(img); - - out.write(fnOut); - - return 0; -} diff --git a/src/jaz/scripts/rotate_subtomograms.cpp b/src/jaz/scripts/rotate_subtomograms.cpp deleted file mode 100644 index 4ad2f5d1e..000000000 --- a/src/jaz/scripts/rotate_subtomograms.cpp +++ /dev/null @@ -1,74 +0,0 @@ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - IOParser parser; - - std::string particles_filename, output_filename; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - particles_filename = parser.getOption("--i", "Catalogue .tbl or .star file"); - output_filename = parser.getOption("--o", "Output filename pattern"); - - Log::readParams(parser); - - parser.checkForErrors(); - - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - - - - ParticleSet data_set(particles_filename, ""); - ParticleSet data_set_copy = data_set; - - const int pc = data_set.getTotalParticleNumber(); - - for (int p = 0; p < pc; p++) - { - double rot_particle = data_set.partTable.getDouble(EMDL_ORIENT_ROT, p); - double tilt_particle = data_set.partTable.getDouble(EMDL_ORIENT_TILT, p); - double psi_particle = data_set.partTable.getDouble(EMDL_ORIENT_PSI, p); - - data_set_copy.partTable.setValue(EMDL_TOMO_SUBTOMOGRAM_ROT, rot_particle, p); - data_set_copy.partTable.setValue(EMDL_TOMO_SUBTOMOGRAM_TILT, tilt_particle, p); - data_set_copy.partTable.setValue(EMDL_TOMO_SUBTOMOGRAM_PSI, psi_particle, p); - - data_set_copy.partTable.setValue(EMDL_ORIENT_ROT, 0.0, p); - data_set_copy.partTable.setValue(EMDL_ORIENT_TILT, 0.0, p); - data_set_copy.partTable.setValue(EMDL_ORIENT_PSI, 0.0, p); - } - - data_set_copy.write(output_filename); -} diff --git a/src/jaz/scripts/sample_blob_points.cpp b/src/jaz/scripts/sample_blob_points.cpp deleted file mode 100644 index 2b1cf58c3..000000000 --- a/src/jaz/scripts/sample_blob_points.cpp +++ /dev/null @@ -1,361 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::string micrographs_list_filename, micrographs_dir, blobs_dir, outPath; - double min_radius, max_radius, min_distance, perimeter_margin; - int num_threads; - bool diag, estimate_radius; - - IOParser parser; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - micrographs_list_filename = parser.getOption("--ml", "Micrograph lists filename"); - micrographs_dir = parser.getOption("--md", "Micrographs directory"); - blobs_dir = parser.getOption("--bd", "Initial blobs directory"); - min_distance = textToDouble(parser.getOption("--d", "Min. particle distance [bin-1 pixels]", "128")); - perimeter_margin = textToDouble(parser.getOption("--pm", "Distance of perimeter points from the outline [bin-1 pixels]", "64")); - estimate_radius = parser.checkOption("--est_rad", "Estimate the radius of the blob"); - min_radius = textToDouble(parser.getOption("--r0", "Min. radius [bin-1 pixels]", "300")); - max_radius = textToDouble(parser.getOption("--r1", "Max. radius [bin-1 pixels]", "800")); - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - - diag = parser.checkOption("--diag", "Write out diagnostic information"); - - outPath = parser.getOption("--o", "Output filename pattern"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - parser.writeUsage(std::cout); - exit(1); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - - outPath = ZIO::makeOutputDir(outPath); - - const std::string perimeter_dir = "perimeter/Frames/"; - const std::string full_dir = "full/Frames/"; - const std::string outer_half_dir = "outer_half/Frames/"; - - ZIO::makeOutputDir(outPath + perimeter_dir); - ZIO::makeOutputDir(outPath + full_dir); - ZIO::makeOutputDir(outPath + outer_half_dir); - - micrographs_dir = ZIO::ensureEndingSlash(micrographs_dir); - blobs_dir = ZIO::ensureEndingSlash(blobs_dir); - - std::vector all_micrograph_names; - all_micrograph_names.reserve(1000); - - std::ifstream file(micrographs_list_filename); - - if (!file) - { - REPORT_ERROR("Unable to read " + micrographs_list_filename); - } - - std::string line; - - while (std::getline(file, line)) - { - std::stringstream sts; - sts << line; - - std::string name; - sts >> name; - - all_micrograph_names.push_back(name); - } - - const int micrograph_count = all_micrograph_names.size(); - - std::vector failed_micrographs; - - #pragma omp parallel for num_threads(num_threads) - for (int m = 0; m < micrograph_count; m++) - { - const int thread_id = omp_get_thread_num(); - const bool verbose = thread_id == 0; - - if (verbose) - { - Log::beginSection( - "Micrograph "+ZIO::itoa(m+1)+"/" - +ZIO::itoa((int)std::ceil(micrograph_count / (double)num_threads))+" on this thread (out of " - +ZIO::itoa(micrograph_count)+" on all threads)"); - } - - try - { - const std::string micrograph_name = all_micrograph_names[m]; - const std::string micrograph_path = micrographs_dir + micrograph_name + ".mrc"; - const std::string blob_path = blobs_dir + micrograph_name + ".blobs"; - std::vector> blob_shapes = ZIO::readDoublesTable(blob_path); - - BufferedImage micrograph; - micrograph.read(micrograph_path); - - - const double pixel_size = ImageFileHelper::getSamplingRate(micrograph_path); - - const int w = micrograph.xdim; - const int h = micrograph.ydim; - - - BufferedImage weight(micrograph); - weight.fill(1.f); - - const int blob_count = blob_shapes.size(); - - BufferedImage plot(micrograph); - - std::vector blobs(blob_count); - - if (verbose) - { - Log::beginProgress("Processing Blobs", blob_count); - } - - for (int b = 0; b < blob_count; b++) - { - if (verbose) - { - Log::updateProgress(b); - } - - if (blob_shapes[b].size()%2 == 0) - { - for (int i = 0; i < blob_shapes[b].size(); i++) - { - std::cout << i << ": " << blob_shapes[b][i] << "\n"; - } - - REPORT_ERROR("The blobs in "+blob_path+" do not have a radius"); - } - - DelineatedBlob2D blob(blob_shapes[b]); - - if (estimate_radius) - { - std::pair,std::vector> radAvgAndWgh - = blob.radialAverageAndWeight( - micrograph, weight, max_radius); - - std::vector radAvg = radAvgAndWgh.first; - std::vector radWgh = radAvgAndWgh.second; - - const int bin_count = radAvg.size(); - - int best_t = 0; - double best_separation = 0.0; - - int delta_t = 20; - - for (int t = min_radius; t <= max_radius - delta_t && t < bin_count; t++) - { - double avg0 = 0.0; - double wgh0 = 0.0; - - int t0 = t - delta_t; - if (t0 < 0) t0 = 0; - - for (int i = t0; i < t; i++) - { - avg0 += radWgh[i] * radAvg[i]; - wgh0 += radWgh[i]; - } - - double avg1 = 0.0; - double wgh1 = 0.0; - - int t1 = t + delta_t + 1; - if (t0 > bin_count) t1 = bin_count; - - for (int i = t; i < t1; i++) - { - avg1 += radWgh[i] * radAvg[i]; - wgh1 += radWgh[i]; - } - - avg0 /= wgh0; - avg1 /= wgh1; - - const double separation = avg1 - avg0; - - if (separation > best_separation) - { - best_separation = separation; - best_t = t; - } - } - - blob.radius = best_t; - } - - blobs[b] = blob; - } - - if (verbose) - { - Log::endProgress(); - } - - const int EDGE = 0; - const int OUTER_HALF = 1; - const int FULL_BLOB = 2; - - std::vector> picks(3); - std::vector normals; - - - for (int b = 0; b < blob_count; b++) - { - DelineatedBlob2D blob = blobs[b]; - - - const double d = min_distance; - const double margin = d; - const double dd = 0.5 * sqrt(3.0) * d; - const int rc = (h - 2*margin) / dd + 1; - const std::vector cc = {(int)((w - 2*margin) / d), (int)((w - 2*margin - d/2) / d)}; - - for (int r = 0; r < rc; r++) - for (int c = 0; c < cc[r%2]; c++) - { - gravis::d2Vector p( - margin + (r%2)*d/2 + c*d, - margin + r*dd); - - double rr = blob.getRelativeSignedDistance(p); - - if (rr < 0) - { - picks[FULL_BLOB].push_back(p); - - if (diag) Drawing::drawCross(p, 100.f, 3, plot); - - if (rr > -0.5) - { - picks[OUTER_HALF].push_back(p); - - if (diag) Drawing::drawCross(p, 100.f, 6, plot); - } - } - } - - const double l = blob.perimeter(); - const int samples = std::floor(l / min_distance); - - DelineatedBlob2D smaller_blob(blob); - smaller_blob.radius -= perimeter_margin; - - for (int i = 0; i < samples; i++) - { - const double phi = 2 * PI * i / (double) samples; - const d2Vector p = smaller_blob.getOutlinePoint(phi); - - if (p.x > margin && p.x < w - margin - && p.y > margin && p.y < h - margin) - { - picks[EDGE].push_back(p); - - d2Vector n = smaller_blob.estimateNormal(phi, 2 * PI / (double) samples); - normals.push_back(n); - - if (diag) Drawing::drawCross(p, 100.f, 10, plot); - } - } - } - - if (diag) plot.write(outPath + "diag_"+ZIO::itoa(m)+".mrc", pixel_size); - - std::vector tables(3); - - for (int i = 0; i < 3; i++) - { - const int pc = picks[i].size(); - MetaDataTable& table = tables[i]; - - table.setName("images"); - - table.addLabel(EMDL_IMAGE_COORD_X); - table.addLabel(EMDL_IMAGE_COORD_Y); - table.addLabel(EMDL_PARTICLE_AUTOPICK_FOM); - table.addLabel(EMDL_PARTICLE_CLASS); - table.addLabel(EMDL_ORIENT_PSI); - table.addLabel(EMDL_ORIENT_PSI_PRIOR); - - for (int p = 0; p < pc; p++) - { - table.addObject(); - - table.setValue(EMDL_IMAGE_COORD_X, picks[i][p].x, p); - table.setValue(EMDL_IMAGE_COORD_Y, picks[i][p].y, p); - table.setValue(EMDL_PARTICLE_AUTOPICK_FOM, 1.0, p); - table.setValue(EMDL_PARTICLE_CLASS, 0, p); - - if (i == EDGE) - { - const d2Vector n = normals[p]; - double phi_deg = 180 - RAD2DEG(atan2(n.x, -n.y)); - - table.setValue(EMDL_ORIENT_PSI, phi_deg, p); - table.setValue(EMDL_ORIENT_PSI_PRIOR, phi_deg, p); - } - else - { - table.setValue(EMDL_ORIENT_PSI, 0.0, p); - table.setValue(EMDL_ORIENT_PSI_PRIOR, 0.0, p); - } - } - } - - tables[EDGE].write(outPath + perimeter_dir + micrograph_name + "_blob_pick.star"); - tables[FULL_BLOB].write(outPath + full_dir + micrograph_name + "_blob_pick.star"); - tables[OUTER_HALF].write(outPath + outer_half_dir + micrograph_name + "_blob_pick.star"); - } - catch (...) - { - #pragma omp critical - { - failed_micrographs.push_back(all_micrograph_names[m]); - } - } - - if (verbose) - { - Log::endSection(); - } - } - - - if (failed_micrographs.size() > 0) - { - ZIO::writeToFile(failed_micrographs, outPath + "failed_micrographs.txt"); - } - - return 0; -} diff --git a/src/jaz/scripts/scale_test.cpp b/src/jaz/scripts/scale_test.cpp deleted file mode 100644 index 864f0c6d3..000000000 --- a/src/jaz/scripts/scale_test.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include -#include -#include -#include - -using namespace gravis; - - -int main(int argc, char** argv) -{ - const int box = 512; - - BufferedImage box2D(box, box); - - box2D.fill(1.f); - - const double dot_rad = box/16; - const double dot_edge = 3; - - for (int y = 0; y < box; y++) - for (int x = 0; x < box; x++) - { - const double xx = x < box/2? x : x - box; - const double yy = y < box/2? y : y - box; - const double r = sqrt(xx*xx + yy*yy); - - if (r < dot_rad) - { - box2D(x,y) = 1.f; - } - else if (r < dot_rad + dot_edge) - { - box2D(x,y) = 1.0 - (r - dot_rad) / dot_edge; - } - else - { - box2D(x,y) = 0.f; - } - } - - box2D.write("DEBUG_box2D_initial.mrc"); - - - BufferedImage box2D_FS(box/2 + 1, box); - FFT::FourierTransform(box2D, box2D_FS, FFT::Both); - - - - std::vector crops {512, 384, 256, 192, 128, 64}; - - for (int ic = 0; ic < crops.size(); ic++) - { - const int crop = crops[ic]; - - BufferedImage box3D(crop, crop, crop); - - BufferedImage box3D_FS(crop/2 + 1, crop, crop); - - box3D_FS.fill(fComplex(0.f, 0.f)); - - BufferedImage box3D_div(crop, crop, crop); - - BufferedImage ctf(box/2 + 1, box, box); - ctf.fill(1.f); - - { - BufferedImage - ctfImgFS(crop/2 + 1, crop, crop), - multiImageFS(crop/2 + 1, crop, crop); - - ctfImgFS.fill(0.f); - multiImageFS.fill(0.f); - - d4Matrix P; - P.loadIdentity(); - - P *= crop / (double) box; - - FourierBackprojection::backprojectSlice_forward_with_multiplicity( - box2D_FS, - ctf, - P, - box3D_FS, - ctfImgFS, - multiImageFS); - - box3D_FS *= (float) sqrt(box / (double) crop); - - BufferedImage div3D_FS(crop/2 + 1, crop, crop); - - for (size_t i = 0; i < div3D_FS.getSize(); i++) - { - div3D_FS[i] = box3D_FS[i] / (ctfImgFS[i] + 0.001); - } - - FFT::inverseFourierTransform(div3D_FS, box3D_div, FFT::Both); - } - - box3D_div.write("DEBUG_box3D_div_box_"+ZIO::itoa(box)+"_crop_"+ZIO::itoa(crop)+".mrc"); - - FFT::inverseFourierTransform(box3D_FS, box3D, FFT::Both); - - box3D.write("DEBUG_box3D_data_box_"+ZIO::itoa(box)+"_crop_"+ZIO::itoa(crop)+".mrc"); - } - - - return 0; -} diff --git a/src/jaz/scripts/scratch.cpp b/src/jaz/scripts/scratch.cpp deleted file mode 100644 index 851c2e0f4..000000000 --- a/src/jaz/scripts/scratch.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include -#include -#include -#include -#include - - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - const int s = 32; - BufferedImage test(s,s); - - for (int y = 0; y < s; y++) - for (int x = 0; x < s; x++) - { - test(x,y) = rand() / (double)RAND_MAX; - } - - const int s2 = 512; - BufferedImage test2(s2,s2,5); - - const double scale = s / (double)s2; - const double eps = 1e-6; - - for (int y = 0; y < s2; y++) - for (int x = 0; x < s2; x++) - { - test2(x,y,0) = Interpolation::cubicXY_clip(test, scale*x, scale*y, 0); - - d2Vector g = Interpolation::cubicXYGrad_clip(test, scale*x, scale*y, 0); - - const double vxp = Interpolation::cubicXY_clip(test, scale*x + eps, scale*y, 0); - const double vxn = Interpolation::cubicXY_clip(test, scale*x - eps, scale*y, 0); - const double vyp = Interpolation::cubicXY_clip(test, scale*x, scale*y + eps, 0); - const double vyn = Interpolation::cubicXY_clip(test, scale*x, scale*y - eps, 0); - - test2(x,y,1) = g.x; - test2(x,y,2) = (vxp - vxn) / (2.0 * eps); - - test2(x,y,3) = g.y; - test2(x,y,4) = (vyp - vyn) / (2.0 * eps); - - } - - test2.write("test2.mrc"); - - return 0; -} diff --git a/src/jaz/scripts/set_defocus_slope.cpp b/src/jaz/scripts/set_defocus_slope.cpp deleted file mode 100644 index 4ada8339c..000000000 --- a/src/jaz/scripts/set_defocus_slope.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace gravis; - - -void run(int argc, char *argv[]); - -int main(int argc, char *argv[]) -{ - try - { - run(argc, argv); - } - catch (RelionError e) - { - return RELION_EXIT_FAILURE; - } - - return RELION_EXIT_SUCCESS; -} - -void run(int argc, char *argv[]) -{ - OptimisationSet optimisationSet; - - IOParser parser; - - parser.setCommandLine(argc, argv); - - optimisationSet.read( - parser, - true, // optimisation set - false, false, // particles - true, true, // tomograms - true, false, // trajectories - false, false, // manifolds - false, false); // reference - - const int gen_section = parser.addSection("General options"); - const double slope = textToDouble(parser.getOption("--ds", "Defocus slope", "1.0")); - std::string outDir = parser.getOption("--o", "Output directory"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); - } - - outDir = ZIO::prepareTomoOutputDirectory(outDir, argc, argv); - - - TomogramSet tomogram_set(optimisationSet.tomograms); - - const int tc = tomogram_set.size(); - - for (int t = 0; t < tc; t++) - { - tomogram_set.globalTable.setValue(EMDL_TOMO_DEFOCUS_SLOPE, slope, t); - } - - tomogram_set.write(outDir + "tomograms.star"); - optimisationSet.tomograms = outDir + "tomograms.star"; - - optimisationSet.write(outDir + "optimisation_set.star"); -} - diff --git a/src/jaz/scripts/shift_plot.cpp b/src/jaz/scripts/shift_plot.cpp deleted file mode 100644 index 83821ef3d..000000000 --- a/src/jaz/scripts/shift_plot.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - std::string partFn0, partFn1, tomoSetFn0, tomoSetFn1, outDir; - double scale; - - IOParser parser; - parser.setCommandLine(argc, argv); - - try - { - int defocus_section = parser.addSection("General options"); - - partFn0 = parser.getOption("--i0", "Initial-state particles"); - tomoSetFn0 = parser.getOption("--t0", "Initial tomogram set"); - - partFn1 = parser.getOption("--i1", "Final-state particles"); - tomoSetFn1 = parser.getOption("--t1", "Final tomogram set"); - - scale = textToDouble(parser.getOption("--scale", "Scale of output 3D arrows", "1")); - - outDir = parser.getOption("--o", "Output directory"); - - if (parser.checkForErrors()) std::exit(-1); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - if (outDir[outDir.length()-1] != '/') - { - outDir = outDir + "/"; - } - - int res = system(("mkdir -p "+outDir).c_str()); - - - ParticleSet dataSet0(partFn0, ""); - ParticleSet dataSet1(partFn1, ""); - - TomogramSet tomogramSet0(tomoSetFn0); - TomogramSet tomogramSet1(tomoSetFn1); - - std::vector> particles0 = dataSet0.splitByTomogram(tomogramSet0); - std::vector> particles1 = dataSet1.splitByTomogram(tomogramSet1); - - const int tc0 = particles0.size(); - const int tc1 = particles1.size(); - - if (tc0 != tc1) - { - REPORT_ERROR_STR("Unequal tomogram numbers: " << tc0 << " and " << tc1); - } - - const int tc = tc0; - - for (int t = 0; t < tc; t++) - { - const int pc0 = particles0[t].size(); - const int pc1 = particles1[t].size(); - - if (pc0 != pc1) - { - std::cerr << "Warning: unequal particle numbers in tomogram " << t << ": " - << pc0 << " and " << pc1 << std::endl; - std::cerr << "skipping tomogram" << std::endl; - - continue; - } - - const int pc = pc0; - - if (pc == 0) continue; - - const std::string tag = outDir+"tomo_"+ZIO::itoa(t); - - Mesh out; - - double var1D = 0.0; - d3Vector var3D(0.0, 0.0, 0.0); - - - for (int pp = 0; pp < pc; pp++) - { - const ParticleIndex p = particles0[t][pp]; - - d3Vector p0 = dataSet0.getPosition(p); - d3Vector p1 = dataSet1.getPosition(p); - d3Vector m = (p0 + p1) / 2.0; - - p0 = m + scale * (p0 - m); - p1 = m + scale * (p1 - m); - - double rad = 0.1 * (p0 - p1).length(); - MeshBuilder::addCone(p0, p1, rad, 5, out); - - const d3Vector d = p1 - p0; - - var1D += d.norm2(); - - var3D.x += d.x * d.x; - var3D.y += d.y * d.y; - var3D.z += d.z * d.z; - } - - var1D /= pc - 1; - var3D /= pc - 1; - - out.writePly(tag+"_mesh.ply"); - - std::cout << "std. dev.: " << sqrt(var1D) << '\n'; - std::cout << " x: " << sqrt(var3D.x) << '\n'; - std::cout << " y: " << sqrt(var3D.y) << '\n'; - std::cout << " z: " << sqrt(var3D.z) << std::endl; - } - - return 0; -} diff --git a/src/jaz/scripts/sort_2d_classes.cpp b/src/jaz/scripts/sort_2d_classes.cpp deleted file mode 100644 index 3516554aa..000000000 --- a/src/jaz/scripts/sort_2d_classes.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::string particles_filename, class_averages_filename, outDir; - int num_threads; - - IOParser parser; - - try - { - IOParser parser; - - parser.setCommandLine(argc, argv); - - int general_section = parser.addSection("General options"); - - particles_filename = parser.getOption("--i", "Input file (e.g. run_it023_data.star)"); - class_averages_filename = parser.getOption("--ca", "Class averages stack"); - - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - outDir = parser.getOption("--o", "Output directory"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - outDir = ZIO::makeOutputDir(outDir); - - ObservationModel obs_model; - MetaDataTable particles_table; - ObservationModel::loadSafely(particles_filename, obs_model, particles_table); - - const int class_count = ClassHelper::countClasses(particles_table); - std::vector particle_count = ClassHelper::getClassSizes(particles_table, class_count); - std::vector order = ClassHelper::sortByAscendingFrequency(particle_count); - - BufferedImage class_averages; - class_averages.read(class_averages_filename); - - std::cout << class_averages.getSizeString() << std::endl; - - const int s = class_averages.xdim; - - BufferedImage output_stack(s, s, class_count); - - std::ofstream class_size_file(outDir+"class_sizes.txt"); - - int total = 0; - - for (int i = 0; i < class_count; i++) - { - const int j = order[i]; - - output_stack.getSliceRef(i).copyFrom(class_averages.getSliceRef(j)); - class_size_file << i << " (old " << j << ") " << particle_count[j] << '\n'; - - total += particle_count[j]; - } - - class_size_file << "\ntotal: " << total << '\n'; - - output_stack.write(outDir+"sorted_classes.mrc"); - - return 0; -} - diff --git a/src/jaz/scripts/spa_backproject.cpp b/src/jaz/scripts/spa_backproject.cpp deleted file mode 100644 index cc62eb30a..000000000 --- a/src/jaz/scripts/spa_backproject.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/*************************************************************************** - * - * Author: "Jasenko Zivanov" - * MRC Laboratory of Molecular Biology - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * This complete copyright notice must be included in any revised version of the - * source code. Additional authorship citations may be added, but existing - * author citations must be preserved. - ***************************************************************************/ - -#include - -int main(int argc, char *argv[]) -{ - SpaBackproject program; - - try - { - program.read(argc, argv); - program.run(); - - } - catch (RelionError XE) - { - std::cerr << XE; - return RELION_EXIT_FAILURE; - } - - return RELION_EXIT_SUCCESS; -} diff --git a/src/jaz/scripts/split_tilts.cpp b/src/jaz/scripts/split_tilts.cpp deleted file mode 100644 index 3a5e9308b..000000000 --- a/src/jaz/scripts/split_tilts.cpp +++ /dev/null @@ -1,230 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - IOParser parser; - - double data_scale; - int num_threads; - bool write_stack; - - std::string in_file, gain_file, out_path; - OptimisationSet optimisation_set; - - try - { - parser.setCommandLine(argc, argv); - - optimisation_set.read( - parser, - true, // optimisation set - false, false, // particles - true, true, // tomograms - false, false, // trajectories - false, false, // manifolds - false, false); // reference - - in_file = parser.getOption("--ti", "STAR file mapping chronological tilt indices to their movie files"); - gain_file = parser.getOption("--g", "Gain reference file", ""); - data_scale = textToDouble(parser.getOption("--ds", "Scale gain-reference multiplied pixels by this before truncating them to 16 bits", "1000")); - write_stack = !parser.checkOption("--no_stack", "Do not write out an image stack"); - out_path = parser.getOption("--o", "Output filename"); - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - out_path = ZIO::prepareTomoOutputDirectory(out_path, argc, argv); - ZIO::makeDir(out_path+"TiltSeries"); - - TomogramSet tomogram_set = TomogramSet(optimisation_set.tomograms, true); - - std::vector tables = MetaDataTable::readAll(in_file, 100); - - const int tc = tables.size(); - const int tc2 = tomogram_set.size(); - - if (tc != tc2) - { - REPORT_ERROR_STR("Tomogram count mismatch: " << tc << " vs. " << tc2); - } - - - BufferedImage gain; - bool has_gain = gain_file != ""; - - if (has_gain) - { - gain.read(gain_file); - } - - TomogramSet new_tomogram_set = tomogram_set; - - Log::beginProgress("Processing tomograms", tc); - - for (int tt = 0; tt < tc; tt++) - { - Log::updateProgress(tt); - - MetaDataTable& table = tables[tt]; - const std::string tomo_name = table.getName(); - - const int t = tomogram_set.getTomogramIndexSafely(tomo_name); - const Tomogram tomogram = tomogram_set.loadTomogram(t, false); - - const int fc = tomogram.frameCount; - const int fc2 = table.numberOfObjects(); - - if (fc2 != fc) - { - REPORT_ERROR_STR( - "Tilt number mismatch in " << table.getName() - << ": " << fc2 << " vs. " << fc); - } - - - int new_frame_count = 0; - i2Vector movie_size(-1,-1); - std::vector frames_by_tilt(fc); - - double pixel_size = 1; - - for (int ft = 0; ft < fc; ft++) - { - const int f = tomogram.frameSequence[ft]; - - const std::string movie_file = table.getString(EMDL_TOMO_TILT_MOVIE_FILE_NAME, ft); - t3Vector size = ImageFileHelper::getSize(movie_file); - - if (ft == 0) - { - pixel_size = ImageFileHelper::getSamplingRate(movie_file); - } - - new_frame_count += size.z; - - if (movie_size.x < 0) - { - movie_size.x = size.x; - movie_size.y = size.y; - } - else if (movie_size.x != size.x || movie_size.y != size.y) - { - REPORT_ERROR_STR( - "Movie size mismatch in " << table.getName() - << ": " << movie_size.x << 'x' << movie_size.y - << " vs. " << size.x << 'x' << size.y); - } - - frames_by_tilt[f] = size.z; - } - - if (has_gain && (gain.xdim != movie_size.x || gain.ydim != movie_size.y)) - { - REPORT_ERROR_STR( - "Gain-reference size mismatch in " << table.getName() - << ": " << movie_size.x << 'x' << movie_size.y - << " vs. " << gain.xdim << 'x' << gain.ydim); - } - - std::vector tilt_to_frame(fc); - int current_index = 0; - - for (int f = 0; f < fc; f++) - { - tilt_to_frame[f] = current_index; - current_index += frames_by_tilt[f]; - } - - - - BufferedImage new_stack(movie_size.x, movie_size.y, new_frame_count); - const std::string out_file = out_path + "TiltSeries/" + tomo_name + ".mrc"; - - if (write_stack) - { - new_stack.fill(0.f); - - #pragma omp parallel for num_threads(num_threads) - for (int ft = 0; ft < fc; ft++) - { - const int f = tomogram.frameSequence[ft]; - - BufferedImage movie; - const std::string movie_file = table.getString(EMDL_TOMO_TILT_MOVIE_FILE_NAME, ft); - movie.read(movie_file); - - for (int ff = 0; ff < movie.zdim; ff++) - { - if (has_gain) - { - for (size_t y = 0; y < movie.ydim; y++) - for (size_t x = 0; x < movie.xdim; x++) - { - new_stack(x, y, tilt_to_frame[f] + ff) = data_scale * (gain(x,y) * movie(x,y,ff)); - } - } - else - { - for (size_t y = 0; y < movie.ydim; y++) - for (size_t x = 0; x < movie.xdim; x++) - { - new_stack(x, y, tilt_to_frame[f] + ff) = movie(x,y,ff); - } - } - } - } - - - new_stack.writeNoVTK(out_file, pixel_size); - } - - new_tomogram_set.globalTable.setValue(EMDL_TOMO_TILT_SERIES_NAME, out_file, t); - new_tomogram_set.globalTable.setValue(EMDL_TOMO_FRAME_COUNT, new_frame_count, t); - - const double fdose0 = tomogram_set.globalTable.getDouble(EMDL_TOMO_IMPORT_FRACT_DOSE, t); - new_tomogram_set.globalTable.setValue(EMDL_TOMO_IMPORT_FRACT_DOSE, fdose0 / frames_by_tilt[0], t); - - - MetaDataTable& new_table = new_tomogram_set.tomogramTables[t]; - MetaDataTable& old_table = tomogram_set.tomogramTables[t]; - - new_table.clear(); - new_table.setName(old_table.getName()); - - for (int f = 0; f < fc; f++) - { - const double dose0 = old_table.getDouble(EMDL_MICROGRAPH_PRE_EXPOSURE, f); - const double ddose = tomogram.fractionalDose / frames_by_tilt[f]; - - for (int ff = 0; ff < frames_by_tilt[f]; ff++) - { - new_table.addObject(old_table.getObject(f)); - new_table.setValue(EMDL_MICROGRAPH_PRE_EXPOSURE, dose0 + ff * ddose); - } - } - } - - Log::endProgress(); - - optimisation_set.tomograms = out_path + "tomograms.star"; - new_tomogram_set.write(optimisation_set.tomograms); - optimisation_set.write(out_path + "optimisation_set.star"); - - return 0; -} diff --git a/src/jaz/scripts/suppress_cone.cpp b/src/jaz/scripts/suppress_cone.cpp deleted file mode 100644 index c71b0fb90..000000000 --- a/src/jaz/scripts/suppress_cone.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include -#include -#include -#include -#include - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - IOParser parser; - - int n_threads; - - double slope, d0; - std::string imgFn, outFn; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - imgFn = parser.getOption("--i", "map"); - const double alpha = 0.5 * textToDouble(parser.getOption("--angle", "opening angle [°]", "2")); - slope = sin(DEG2RAD(alpha)); - d0 = textToDouble(parser.getOption("--d0", "d0", "1")); - n_threads = textToInteger(parser.getOption("--j", "Number of threads", "5")); - outFn = parser.getOption("--o", "Output filename"); - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - std::cout << "slope = " << slope << std::endl; - - BufferedImage img; - img.read(imgFn); - - const int s = img.xdim; - const int sh = s/2 + 1; - - BufferedImage imgFS; - - FFT::FourierTransform(img, imgFS); - imgFS.write("debug_0.vtk"); - - - BufferedImage mask(sh,s,s); - - for (int z = 0; z < s; z++) - for (int y = 0; y < s; y++) - for (int x = 0; x < sh; x++) - { - const double xx = x; - const double yy = y < s/2? y : y - s; - const double zz = z < s/2? z : z - s; - - const double rho = sqrt(xx*xx + yy*yy); - const double t = rho / (std::abs(zz) * slope + d0); - - const double m = 1.0 - exp(-0.5*t*t); - - imgFS(x,y,z) *= m; - - mask(x,y,z) = m; - } - - imgFS.write("debug_1.vtk"); - FFT::inverseFourierTransform(imgFS, img); - - mask.write("debug_mask.mrc"); - img.write(outFn); -} diff --git a/src/jaz/scripts/test_extraction.cpp b/src/jaz/scripts/test_extraction.cpp deleted file mode 100644 index a2694d758..000000000 --- a/src/jaz/scripts/test_extraction.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace gravis; - - -int main(int argc, char *argv[]) -{ - std::string particlesFn, outDir; - double margin, SNR; - int num_threads; - - - IOParser parser; - - try - { - IOParser parser; - - parser.setCommandLine(argc, argv); - - int general_section = parser.addSection("General options"); - - particlesFn = parser.getOption("--i", "Input file (e.g. run_it023_data.star)", ""); - SNR = textToDouble(parser.getOption("--SNR", "Assumed signal-to-noise ratio", "0.1")); - margin = textToDouble(parser.getOption("--m", "Margin around the particle [Px]", "20")); - num_threads = textToInteger(parser.getOption("--j", "Number of OMP threads", "6")); - outDir = parser.getOption("--o", "Output directory"); - - Log::readParams(parser); - - if (parser.checkForErrors()) - { - REPORT_ERROR("Errors encountered on the command line (see above), exiting..."); - } - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - outDir = ZIO::makeOutputDir(outDir); - - ObservationModel obs_model; - MetaDataTable particles_table; - ObservationModel::loadSafely(particlesFn, obs_model, particles_table); - - int max_class = -1; - - for (long int p = 0; p < particles_table.numberOfObjects(); p++) - { - const int class_id = particles_table.getIntMinusOne(EMDL_PARTICLE_CLASS, p); - - if (class_id > max_class) max_class = class_id; - } - - const int class_count = max_class + 1; - - if (class_count == 1) - { - Log::print("1 class found"); - } - else - { - Log::print(ZIO::itoa(class_count)+" classes found"); - } - - - std::vector class_size(class_count, 0); - - for (long int p = 0; p < particles_table.numberOfObjects(); p++) - { - const int class_id = particles_table.getIntMinusOne( - EMDL_PARTICLE_CLASS, p); - - class_size[class_id]++; - } - - - - const int box_size = obs_model.getBoxSize(0); - const double pixel_size = obs_model.getPixelSize(0); - - - - BufferedImage data(box_size / 2 + 1, box_size, num_threads * class_count); - data.fill(dComplex(0.0, 0.0)); - - BufferedImage weight(box_size / 2 + 1, box_size, num_threads * class_count); - weight.fill(0.0); - - - std::vector particles_by_micrograph = StackHelper::splitByMicrographName(particles_table); - - const int micrograph_count = particles_by_micrograph.size(); - - for (int micrograph_id = 0; micrograph_id < micrograph_count; micrograph_id++) - { - Log::print("Micrograph " + ZIO::itoa(micrograph_id+1)); - - const MetaDataTable& particles_in_micrograph = particles_by_micrograph[micrograph_id]; - const int particle_count = particles_in_micrograph.numberOfObjects(); - - - BufferedImage micrograph; - float mean_value = 0, std_dev = 1; - double micrograph_pixel_size = pixel_size; - double extraction_scale = 1; - - - const std::string micrograph_filename = particles_in_micrograph.getString(EMDL_MICROGRAPH_NAME, 0); - micrograph_pixel_size = ImageFileHelper::getSamplingRate(micrograph_filename); - extraction_scale = pixel_size / micrograph_pixel_size; - extraction_scale = std::round(1000 * extraction_scale)/1000.0; - - - micrograph.read(micrograph_filename); - - mean_value = Normalization::computeMean(micrograph); - std_dev = sqrt(Normalization::computeVariance(micrograph, mean_value)); - - const i2Vector micrograph_size(micrograph.xdim, micrograph.ydim); - - - BufferedImage - out_loaded(box_size, box_size, particle_count), - out_extracted(box_size, box_size, particle_count), - out_centres = micrograph; - - for (long int p = 0; p < particle_count; p++) - { - BufferedImage particle_image_RS; - - std::string img_fn = particles_in_micrograph.getString(EMDL_IMAGE_NAME, p); - particle_image_RS.read(img_fn); - - out_loaded.copySliceFrom(p, particle_image_RS); - - const int extraction_box_size = std::round(extraction_scale * box_size); - - const d2Vector global_position_0( - particles_in_micrograph.getDouble(EMDL_IMAGE_COORD_X, p), - particles_in_micrograph.getDouble(EMDL_IMAGE_COORD_Y, p)); - - const d2Vector global_position = global_position_0; - - i2Vector integral_position(std::round(global_position.x), std::round(global_position.y)); - - BufferedImage extraction_buffer(extraction_box_size, extraction_box_size); - - const int x0 = integral_position.x - extraction_box_size / 2; - const int y0 = integral_position.y - extraction_box_size / 2; - - for (int y = 0; y < extraction_box_size; y++) - for (int x = 0; x < extraction_box_size; x++) - { - int xx = x0 + x; - int yy = y0 + y; - - if (xx < 0) xx = 0; - else if (xx >= micrograph_size.x) xx = micrograph_size.x - 1; - - if (yy < 0) yy = 0; - else if (yy >= micrograph_size.y) yy = micrograph_size.y - 1; - - extraction_buffer(x,y) = -micrograph(xx, yy); - - out_centres(xx,yy) = mean_value + 6 * std_dev; - } - - const double mean_value_p = Normalization::computeMean(extraction_buffer); - extraction_buffer -= mean_value_p; - - extraction_buffer /= std_dev; - - if (std::abs(micrograph_pixel_size - pixel_size) > 0.001) - { - particle_image_RS = Resampling::FourierCrop_fullStack( - extraction_buffer, extraction_scale, num_threads, true); - } - else - { - particle_image_RS = extraction_buffer; - } - - out_extracted.copySliceFrom(p, particle_image_RS); - } - - out_extracted.write(outDir+"micrograph_"+ZIO::itoa(micrograph_id)+"_extracted.mrc"); - out_loaded.write(outDir+"micrograph_"+ZIO::itoa(micrograph_id)+"_loaded.mrc"); - out_centres.write(outDir+"micrograph_"+ZIO::itoa(micrograph_id)+"_centres.mrc"); - } - - return 0; -} diff --git a/src/jaz/scripts/test_logger.cpp b/src/jaz/scripts/test_logger.cpp deleted file mode 100644 index 6bd8d9dce..000000000 --- a/src/jaz/scripts/test_logger.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include -#include - - -int main(int argc, char *argv[]) -{ - - IOParser parser; - parser.setCommandLine(argc, argv); - - Log::readParams(parser); - - const int N = 70; - - Log::beginSection("initialising"); - - Log::beginSection("component X"); - - Log::print("loading X"); - usleep(920000u); - - Log::beginProgress("transmogrifying", N); - - for (int i = 0; i < N; i++) - { - Log::updateProgress(i); - - usleep(50000u); - } - - Log::endProgress(); - - Log::print("component X ready"); - - Log::endSection(); - - Log::endSection(); - - Log::beginSection("processing"); - - Log::beginSection("component X"); - - Log::beginSection("preparing"); - usleep(230000u); - - Log::print("step 1"); - usleep(400000u); - Log::print("step 2"); - usleep(270000u); - - Log::endSection(); - - Log::endSection(); - - Log::endSection(); - - Log::beginProgress("performing global optimisation", N); - - for (int i = 0; i < N; i++) - { - Log::updateProgress(i); - - usleep(70000u); - } - - Log::endProgress(); - - Log::print("writing"); - - usleep(370000u); - - Log::print("all done"); - - return 0; -} diff --git a/src/jaz/scripts/test_splines.cpp b/src/jaz/scripts/test_splines.cpp deleted file mode 100644 index 112c5ed9b..000000000 --- a/src/jaz/scripts/test_splines.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include -#include -#include - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - const int w = 5; - const int h = 4; - - BufferedImage data(w,h,2); - - for (int z = 0; z < 2; z++) - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - for (int i = 0; i < 4; i++) - { - data(x,y,z)[i] = 1.0 - 2.0 * (rand() / (double) RAND_MAX); - } - - - const int ww = 1024; - const int hh = 768; - - Deformation2D::Parameters parameters; - parameters.grid_width = w; - parameters.grid_height = h; - - Spline2DDeformationModel model(parameters, i2Vector(ww, hh)); - - /*BufferedImage test0(ww,hh), test1(ww,hh); - - for (int y = 0; y < hh; y++) - for (int x = 0; x < ww; x++) - { - d2Vector def, def_x, def_y; - - model.computeShiftAndGradient( - d2Vector(x,y), (double*)(data.data), def, def_x, def_y); - - test0(x,y) = def_y.x; - } - - test0.write("spline_test_gradXy-a.mrc"); - - for (int y = 1; y < hh-1; y++) - for (int x = 0; x < ww; x++) - { - d2Vector def1, def1_x, def1_y; - d2Vector def0, def0_x, def0_y; - - model.computeShiftAndGradient( - d2Vector(x,y-1), (double*)(data.data), def0, def0_x, def0_y); - - model.computeShiftAndGradient( - d2Vector(x,y+1), (double*)(data.data), def1, def1_x, def1_y); - - test1(x,y) = (def1.x - def0.x) / 2.0; - } - - test1.write("spline_test_gradXy-n.mrc");*/ - - - BufferedImage data0 = data; - - const double eps = 1e-3; - const int dim = 0; - - for (int dy = 0; dy < 2; dy++) - for (int dx = 0; dx < 2; dx++) - for (int i = 0; i < 4; i++) - { - data = data0; - - d2Vector def1, def1_x, def1_y; - d2Vector def0, def0_x, def0_y; - - const d2Vector pl(1.7 * model.gridSpacing.x, 1.2 * model.gridSpacing.y); - - model.computeShiftAndGradient( - pl, (double*)(data.data), def0, def0_x, def0_y); - - data(1+dx, 1+dy, dim)[i] += eps; - - model.computeShiftAndGradient( - pl, (double*)(data.data), def1, def1_x, def1_y); - - const d2Vector d = def1 - def0; - const d2Vector g0(1,2); - - const double m = g0.dot(d) / eps; - - - BufferedImage grad(w,h,2); - grad.fill(d4Vector(0.0,0.0,0.0,0.0)); - - model.updateDataTermGradient(pl, g0, 0, (double*)grad.data); - - std::cout << std::setprecision(12) << std::scientific - << grad(1+dx, 1+dy, dim)[i] << " \t vs \t " << m << " \t (" - << (grad(1+dx, 1+dy, dim)[i] - m) << ')' << std::endl; - } - - return 0; -} diff --git a/src/jaz/scripts/update_tomogram_set.cpp b/src/jaz/scripts/update_tomogram_set.cpp deleted file mode 100644 index bc60045f0..000000000 --- a/src/jaz/scripts/update_tomogram_set.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace gravis; - -int main(int argc, char *argv[]) -{ - std::string tomoSetFn, outFn; - - IOParser parser; - - try - { - parser.setCommandLine(argc, argv); - int gen_section = parser.addSection("General options"); - - tomoSetFn = parser.getOption("--t", "Tomogram set"); - outFn = parser.getOption("--o", "Output file name"); - - parser.checkForErrors(); - } - catch (RelionError XE) - { - parser.writeUsage(std::cout); - std::cerr << XE; - exit(1); - } - - std::ifstream ifs(tomoSetFn); - - if (!ifs) - { - REPORT_ERROR_STR("update_tomogram_set: Unable to read " << tomoSetFn); - } - - - MetaDataTable globalTable; - std::vector tomogramTables; - - globalTable.readStar(ifs, "global"); - - const int tc = globalTable.numberOfObjects(); - - tomogramTables.resize(tc); - - std::vector allTables = MetaDataTable::readAll(ifs, tc+1); - - bool namesAreOld = false; - - for (int t = 0; t < tc; t++) - { - const std::string expectedOldName = "tomo_" + ZIO::itoa(t); - const std::string expectedNewName = globalTable.getString(EMDL_TOMO_NAME, t); - const std::string name = allTables[t+1].getName(); - - if (name == expectedOldName) - { - namesAreOld = true; - } - else if (name != expectedNewName) - { - REPORT_ERROR_STR("TomogramSet::TomogramSet: file is corrupted " << tomoSetFn); - } - - tomogramTables[t] = allTables[t+1]; - tomogramTables[t].setName(expectedNewName); - } - - if (!namesAreOld) - { - Log::warn("Tomogram set " + tomoSetFn + " was already up to date."); - } - - globalTable.setName("global"); - - if (outFn.find_last_of('/') != std::string::npos) - { - std::string path = outFn.substr(0, outFn.find_last_of('/')); - mktree(path); - } - - std::ofstream ofs(outFn); - - if (!ofs) - { - REPORT_ERROR("update_tomogram_set: unable to write to " + outFn); - } - - globalTable.write(ofs); - - for (int t = 0; t < tc; t++) - { - tomogramTables[t].write(ofs); - } - - return 0; -} From 13cc231e415e57831a40173c4d1b624f9bb3731f Mon Sep 17 00:00:00 2001 From: Bogdan Toader Date: Wed, 25 Mar 2026 09:24:41 +0000 Subject: [PATCH 183/189] Pin napari-threedee and psygnal to working versions from relion-5.0 environment --- environment.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/environment.yml b/environment.yml index f59638173..9fbbc3c52 100644 --- a/environment.yml +++ b/environment.yml @@ -18,6 +18,8 @@ dependencies: - morphosamplers==0.0.13 - pydantic==1.10.18 - napari[all]==0.4.18 + - napari-threedee==0.0.28 + - psygnal==0.11.1 - tsnecuda==3.0.1 - PyQt5==5.15.9 - typer==0.9.0 From ed374e853b27a1dbb02df16ad7f1579004e3e416 Mon Sep 17 00:00:00 2001 From: Takanori Nakane Date: Wed, 1 Apr 2026 13:32:10 +0900 Subject: [PATCH 184/189] Fix amyloid tracer model download during `cmake` (Issue #1338). --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 675e75f47..d24d503be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -579,6 +579,12 @@ if (FETCH_WEIGHTS) "NOTE: You can skip this step by setting -DFETCH_WEIGHTS=OFF" ) + # `relion_trace_amyloids.py` is necessary for weight download during `cmake`. + # The `copy_scripts` target below is executed only during `make`. + configure_file("${CMAKE_SOURCE_DIR}/scripts/trace_amyloids.py" + "${CMAKE_BINARY_DIR}/bin/relion_trace_amyloids.py" + COPYONLY) + execute_process( COMMAND bash ${CMAKE_BINARY_DIR}/bin/relion_python_fetch_weights WORKING_DIRECTORY ${CMAKE_BINARY_DIR} From fe136118df1e4f1f4b238b648a0c73481d5d84b7 Mon Sep 17 00:00:00 2001 From: Amin <67607590+amineuron@users.noreply.github.com> Date: Sun, 31 May 2026 02:26:24 +0200 Subject: [PATCH 185/189] Treat ".gain" gain references as TIFF on the generic image path (#1346) The special code path for EER movies is unaffected. --- src/image.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/image.h b/src/image.h index 667e72aba..ca2a698ef 100644 --- a/src/image.h +++ b/src/image.h @@ -302,6 +302,16 @@ class fImageHandler fileName = fileName.addExtension(ext_name); } + // A ".gain" reference is a TIFF. RELION already assumes this in + // EERRenderer::loadEERGain (which appends ":tif" for .gain); make the + // generic image path assume the same, so a TIFF gain with a .gain + // extension also reads for non-EER (e.g. TIFF) movies and in Bayesian + // Polishing, instead of falling through to the SPIDER reader. + // See https://github.com/3dem/relion/pull/1346 and the CCPEM thread: + // https://www.jiscmail.ac.uk/cgi-bin/wa-jisc.exe?A2=ind2605&L=CCPEM&O=D&P=34021 + if (ext_name == "gain") + ext_name = "tif"; + isTiff = ext_name.contains("tif"); // Open image file From 260c6d2b5c5836ec0d37e93d441fe8dd952a4dd5 Mon Sep 17 00:00:00 2001 From: Takanori Nakane Date: Tue, 4 Aug 2026 20:11:49 +0900 Subject: [PATCH 186/189] --maskedge: clarify the default (as pointed out in #1349). --- src/ml_optimiser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ml_optimiser.cpp b/src/ml_optimiser.cpp index 1179c6374..f5d6ef67c 100644 --- a/src/ml_optimiser.cpp +++ b/src/ml_optimiser.cpp @@ -1000,7 +1000,7 @@ void MlOptimiser::parseInitial(int argc, char **argv) random_seed = textToInteger(parser.getOption("--random_seed", "Number for the random seed generator", "-1")); max_coarse_size = textToInteger(parser.getOption("--coarse_size", "Maximum image size for the first pass of the adaptive sampling approach", "-1")); adaptive_fraction = textToFloat(parser.getOption("--adaptive_fraction", "Fraction of the weights to be considered in the first pass of adaptive oversampling ", "0.999")); - width_mask_edge = textToInteger(parser.getOption("--maskedge", "Width of the soft edge of the spherical mask (in pixels)", "5")); + width_mask_edge = textToInteger(parser.getOption("--maskedge", "Width of the soft edge of the spherical mask (in pixels). The default is 10 for helical refinement, otherwise 5.", "5")); // If we're doing helical, and maskedge is not given, use a default maskedge of 10 if (helical_tube_outer_diameter > 0. && !checkParameter(argc, argv, "--maskedge")) width_mask_edge = 10.; fix_sigma_noise = parser.checkOption("--fix_sigma_noise", "Fix the experimental noise spectra?"); From ad64784ec5cbf3a20cb46ccb0c0cbdc17f5c593d Mon Sep 17 00:00:00 2001 From: scheres Date: Fri, 21 Aug 2026 15:30:24 +0100 Subject: [PATCH 187/189] Claude optimisation to reduce memory footprint and speed up --- src/amyloid_finder.cpp | 631 ++++++++++++++++++++++++++++++++--------- 1 file changed, 490 insertions(+), 141 deletions(-) diff --git a/src/amyloid_finder.cpp b/src/amyloid_finder.cpp index 1f1b238ba..5e0a9ef86 100644 --- a/src/amyloid_finder.cpp +++ b/src/amyloid_finder.cpp @@ -20,6 +20,19 @@ #include "src/amyloid_finder.h" //#define DEBUG_BOUNDS +namespace +{ +// Largest prime factor of n, used to pick FFT-friendly box sizes below +int maxPrimeFactor(int n) +{ + int mx = 1; + while (n % 2 == 0) { mx = 2; n /= 2; } + for (int p = 3; (long int)p * p <= (long int)n; p += 2) + while (n % p == 0) { mx = XMIPP_MAX(mx, p); n /= p; } + return XMIPP_MAX(mx, n); +} +} + void AmyloidFinder::read(int argc, char **argv, int rank) { parser.setCommandLine(argc, argv); @@ -201,6 +214,18 @@ void AmyloidFinder::initialise(bool is_leader) down_ysize = FLOOR( (ori_ysize * angpix) / down_angpix ); if (ilengthmax %2 != 0) ilengthmax++; nr_psi = ROUND(180./psi_step); + // The second half of the psi range is not searched explicitly: it is obtained by + // rotating the first half over 90 degrees (a transpose of the downscaled image), + // which is only exact when getPsiAngle(ipsi + nr_psi/2) - getPsiAngle(ipsi) is + // exactly 90, i.e. when nr_psi is even. With an odd number, the top half of the psi + // range would silently get score maps belonging to the wrong angle. + if (nr_psi < 2) nr_psi = 2; + if (nr_psi % 2 != 0) + { + nr_psi++; + if (verb > 0) + std::cout << " - Rounding the number of psi angles up to " << nr_psi << " to make it even" << std::endl; + } psi_step = 180./nr_psi; // Calculate Fourier shells for amyloid signal @@ -213,6 +238,32 @@ void AmyloidFinder::initialise(bool is_leader) large_box = sqrt(2.)*XMIPP_MAX(ori_xsize, ori_ysize); large_box += ROUND(XMIPP_MAX(search_filament_width, search_filament_length) / angpix); if (large_box%2 != 0) large_box++; + + // Grow the padded box until both transforms get an FFT-friendly size. FFTW is much + // slower for sizes with a large prime factor, because it falls back to a Rader or + // Bluestein convolution. Measured with RELION's own FFTW on the 4096^2 test data at + // 0.955 A: the box coming out of the lines above was 6054 = 2*3*1009, and one + // forward transform took 0.52 s, against 0.21 s for 6174 = 2*3^2*7^3; the cropped + // box improves from 2754 = 2*3^4*17 (0.058 s) to 2808 = 2^3*3^3*13 (0.034 s). Two + // codelets serving the prime 1009 were 19% of the run time of the whole program. + // + // NOTE: this changes the results slightly, because the padding size sets the + // downscaled sampling: on the test data the FOM map moved by 0.009 RMS on a map + // with a standard deviation of 0.099 (worst pixel 0.055). Delete this loop to keep + // the old box sizes exactly. + const int box_start = large_box; + for (int trial = box_start; trial <= box_start + 1024; trial += 2) + { + if (maxPrimeFactor(trial) > 7) continue; + + int trial_crop = trial * angpix/down_angpix; + if (trial_crop%2 != 0) trial_crop++; + if (maxPrimeFactor(trial_crop) > 13) continue; + + large_box = trial; + break; + } + // Also calculate size of cropped box: crop_box = large_box * angpix/down_angpix; if (crop_box%2 != 0) crop_box++; @@ -227,6 +278,9 @@ void AmyloidFinder::initialise(bool is_leader) std::cout << " + Size of image to sample (in downscaled pixels): " << down_xsize << " x " << down_ysize << std::endl; std::cout << " + Fourier shells for the amyloid signal (in downscaled pixels): " << imin_signal << " - " << imax_signal << std::endl; std::cout << " + Fourier shells for the non-signal control (in downscaled pixels): " << imin_nonsignal << " - " << imax_nonsignal << std::endl; + std::cout << " + Padded box for the rotations: " << large_box << " (largest prime factor " + << maxPrimeFactor(large_box) << "), cropped to " << crop_box + << " (largest prime factor " << maxPrimeFactor(crop_box) << ")" << std::endl; std::cout << " ========================== " << std::endl; } } @@ -305,190 +359,485 @@ MultidimArray AmyloidFinder::growNonSignalMask(MultidimArray &in return Mresult; } -void AmyloidFinder::getScoreForOneMicrograph(MultidimArray &image, MultidimArray &Mscore, - MultidimArray &Mangle, RFLOAT &skew, RFLOAT &kurt, bool myverb) +// Single/double precision names for the FFTW calls used below +#ifdef RELION_SINGLE_PRECISION +#define AMY_FFTW(name) fftwf_##name +#define AMY_FFTW_COMPLEX fftwf_complex +#define AMY_FFTW_PLAN fftwf_plan +#else +#define AMY_FFTW(name) fftw_##name +#define AMY_FFTW_COMPLEX fftw_complex +#define AMY_FFTW_PLAN fftw_plan +#endif + +namespace { +// Bilinear rotation, parallelised over the rows of the output. +// +// This is a faithful copy of applyGeometry() for the 2D, wrap, outside = 0 case, i.e. of +// what rotate(V1, V2, ang, 'Z', true) does, with the outer loop shared over threads. Each +// output row is independent, and the accumulation of xp/yp along a row is kept exactly as +// in the original, so the result is bit-identical to applyGeometry() -- just not serial. +// applyGeometry() itself is left alone: it is used all over RELION, often from inside +// parallel regions already. +void rotateBilinearOmp(const MultidimArray &V1, MultidimArray &V2, + RFLOAT ang, int nr_threads) +{ + Matrix2D A; + rotation2DMatrix(ang, A, true); + // applyGeometry() is called with IS_NOT_INV, so it inverts the matrix itself + const Matrix2D Aref = A.inv(); + + const int cen_y = (int)(YSIZE(V2) / 2); + const int cen_x = (int)(XSIZE(V2) / 2); + const int cen_yp = (int)(YSIZE(V1) / 2); + const int cen_xp = (int)(XSIZE(V1) / 2); + const RFLOAT minxp = -cen_xp; + const RFLOAT minyp = -cen_yp; + const RFLOAT maxxp = XSIZE(V1) - cen_xp - 1; + const RFLOAT maxyp = YSIZE(V1) - cen_yp - 1; + const int Xdim = XSIZE(V1); + const int Ydim = YSIZE(V1); + + const RFLOAT a00 = MAT_ELEM(Aref, 0, 0), a01 = MAT_ELEM(Aref, 0, 1), a02 = MAT_ELEM(Aref, 0, 2); + const RFLOAT a10 = MAT_ELEM(Aref, 1, 0), a11 = MAT_ELEM(Aref, 1, 1), a12 = MAT_ELEM(Aref, 1, 2); + + const long int ysize = YSIZE(V2), xsize = XSIZE(V2); - MultidimArray Mbig(large_box, large_box); - Mbig.setXmippOrigin(); - for (long int i=STARTINGY(Mbig); i<=FINISHINGY(Mbig); i++) +#pragma omp parallel for num_threads(nr_threads) + for (long int i = 0; i < ysize; i++) { - long int ip = i; - //if (i < STARTINGY(image)) ip += YSIZE(image); - //else if (i > FINISHINGY(image)) ip -= YSIZE(image); - if (i < STARTINGY(image)) ip = 2*STARTINGY(image) - i; - else if (i > FINISHINGY(image)) ip = 2*FINISHINGY(image) - i; + // position of the beginning of this row in the output image + const RFLOAT x = -cen_x; + const RFLOAT y = i - cen_y; + RFLOAT xp = x * a00 + y * a01 + a02; + RFLOAT yp = x * a10 + y * a11 + a12; - for (long int j=STARTINGX(Mbig); j<=FINISHINGX(Mbig); j++) + for (long int j = 0; j < xsize; j++) { - long int jp = j; - if (j < STARTINGX(image)) jp = 2*STARTINGX(image) - j; - else if (j > FINISHINGX(image)) jp = 2*FINISHINGX(image) - j; + // outside the image, apply a periodic extension (wrap == true) + if (xp < minxp - XMIPP_EQUAL_ACCURACY || xp > maxxp + XMIPP_EQUAL_ACCURACY) + xp = realWRAP(xp, minxp - 0.5, maxxp + 0.5); + if (yp < minyp - XMIPP_EQUAL_ACCURACY || yp > maxyp + XMIPP_EQUAL_ACCURACY) + yp = realWRAP(yp, minyp - 0.5, maxyp + 0.5); + + // integer position of the top left corner of the interpolation square, and the + // weights for the m1+1, n1+1 corner + RFLOAT wx = xp + cen_xp; + int m1 = (int) wx; + wx = wx - m1; + int m2 = m1 + 1; + + RFLOAT wy = yp + cen_yp; + int n1 = (int) wy; + wy = wy - n1; + int n2 = n1 + 1; + + // m2 and n2 can be out by one + if (m2 >= Xdim) m2 = 0; + if (n2 >= Ydim) n2 = 0; + + RFLOAT tmp = (1 - wy) * (1 - wx) * DIRECT_A2D_ELEM(V1, n1, m1); + + if (m2 < Xdim) + tmp += (1 - wy) * wx * DIRECT_A2D_ELEM(V1, n1, m2); + + if (n2 < Ydim) + { + tmp += wy * (1 - wx) * DIRECT_A2D_ELEM(V1, n2, m1); - A2D_ELEM(Mbig, i, j) = A2D_ELEM(image, ip, jp); + if (m2 < Xdim) + tmp += wy * wx * DIRECT_A2D_ELEM(V1, n2, m2); + } + + DIRECT_A2D_ELEM(V2, i, j) = tmp; + + // compute new point inside input image + xp += a00; + yp += a10; } } +} - // Rotate the large image, and store downscaled images by cropping their Fourier Transform - std::vector > rotated_imgs(nr_psi), rotated_scores_perline(nr_psi), rotated_scores(nr_psi); - std::vector > rotated_nonscores_perline(nr_psi), rotated_nonscores(nr_psi); - std::vector transformer(nr_threads); - // TODO: in principle, only need to rotate to 90 degrees, as I can use both the X and the Y direction for the 1D FFTs! - if (myverb) - { - std::cout << " - Rotating the input image ..." << std::endl; - init_progress_bar(nr_psi); - } +// Forward and inverse 2D transforms, parallelised by splitting them the way FFTW does +// internally: a transform along x for every row, then one along y for every column. Both +// were checked against fftw_plan_dft_r2c_2d() / fftw_plan_dft_c2r_2d() on this data and +// give bit-identical output, at 17x (forward, 6174^2) and 6x (inverse, 2808^2) the speed on +// 24 threads. Executing a plan from several threads at once is allowed by FFTW; creating one +// is not, hence the critical sections, which share their name with the one in fftw.cpp. +void forwardFT2Domp(MultidimArray &in, MultidimArray &out, int nr_threads) +{ + const int N = XSIZE(in); + const int Nh = N / 2 + 1; -#pragma omp parallel for num_threads(nr_threads) - for (int ipsi = 0; ipsi < nr_psi/2; ipsi++) + if (YSIZE(in) != N) + REPORT_ERROR("forwardFT2Domp ERROR: only square images are supported"); + + out.reshape(N, Nh); + + RFLOAT *rdata = MULTIDIM_ARRAY(in); + AMY_FFTW_COMPLEX *cdata = (AMY_FFTW_COMPLEX*) MULTIDIM_ARRAY(out); + int n[1] = { N }; + +#pragma omp parallel num_threads(nr_threads) { const int tid = omp_get_thread_num(); - RFLOAT psi = getPsiAngle(ipsi); + const int nthr = omp_get_num_threads(); - // Rotate the images in their original size to prevent interpolation artefacts near the signal frequencies - MultidimArray Mrot; - Mrot.setXmippOrigin(); - Mrot.initZeros(large_box, large_box); - rotate(Mbig, Mrot, psi, 'Z', true); - - //Image Ir; - //Ir()=Mrot; - //Ir.write("Ir_psi"+ integerToString(ipsi)+".spi"); - //std::cerr << " written: " << "Ir_psi"<< integerToString(ipsi)<<".spi" << std::endl; - - // Re-scale image so that Nyquist is at down_angpix - MultidimArray FT, FT2; - transformer[tid].FourierTransform(Mrot, FT, false); - windowFourierTransform(FT, FT2, crop_box); - Mrot.resize(crop_box, crop_box); - transformer[tid].inverseFourierTransform(FT2, Mrot); - Mrot.setXmippOrigin(); - rotated_imgs[ipsi] = Mrot; + // real-to-complex along x, for a chunk of rows + const long int r0 = (long int)N * tid / nthr; + const long int r1 = (long int)N * (tid + 1) / nthr; + if (r1 > r0) + { + AMY_FFTW_PLAN p = NULL; + #pragma omp critical(FourierTransformer_fftw_plan) + p = AMY_FFTW(plan_many_dft_r2c)(1, n, (int)(r1 - r0), + rdata + r0 * N, NULL, 1, N, + cdata + r0 * Nh, NULL, 1, Nh, FFTW_ESTIMATE); + if (p == NULL) REPORT_ERROR("forwardFT2Domp ERROR: FFTW plan cannot be created"); + AMY_FFTW(execute)(p); + #pragma omp critical(FourierTransformer_fftw_plan) + AMY_FFTW(destroy_plan)(p); + } - } - MultidimArray Mzero(crop_box, crop_box); - Mzero.setXmippOrigin(); - for (int ipsi = nr_psi/2; ipsi < nr_psi; ipsi++) - { - rotated_imgs[ipsi] = Mzero; - // stay away from boundary to prevent many if-statements below. Images are cropped in larger box anyway, so boundaries should be zero - for (long int i=STARTINGY(Mzero)+1; i<=FINISHINGY(Mzero)-1; i++) +#pragma omp barrier + + // complex transform along y, for a chunk of columns + const long int c0 = (long int)Nh * tid / nthr; + const long int c1 = (long int)Nh * (tid + 1) / nthr; + if (c1 > c0) { - for (long int j=STARTINGX(Mzero)+1; j<=FINISHINGX(Mzero)-1; j++) - { - A2D_ELEM(rotated_imgs[ipsi], i, j) = A2D_ELEM(rotated_imgs[ipsi-nr_psi/2], -j, i); - } + AMY_FFTW_PLAN p = NULL; + #pragma omp critical(FourierTransformer_fftw_plan) + p = AMY_FFTW(plan_many_dft)(1, n, (int)(c1 - c0), + cdata + c0, NULL, Nh, 1, + cdata + c0, NULL, Nh, 1, FFTW_FORWARD, FFTW_ESTIMATE); + if (p == NULL) REPORT_ERROR("forwardFT2Domp ERROR: FFTW plan cannot be created"); + AMY_FFTW(execute)(p); + #pragma omp critical(FourierTransformer_fftw_plan) + AMY_FFTW(destroy_plan)(p); } } +} - if (myverb) progress_bar(nr_psi); +// As above, in the other direction. Like any FFTW complex-to-real transform this destroys +// its input. +void inverseFT2Domp(MultidimArray &in, MultidimArray &out, int nr_threads) +{ + const int N = XSIZE(out); + const int Nh = N / 2 + 1; - // Just prepare the rotated_scores vector too - for (int ipsi = 0; ipsi < nr_psi; ipsi++) + if (YSIZE(out) != N || XSIZE(in) != Nh || YSIZE(in) != N) + REPORT_ERROR("inverseFT2Domp ERROR: unexpected array sizes"); + + AMY_FFTW_COMPLEX *cdata = (AMY_FFTW_COMPLEX*) MULTIDIM_ARRAY(in); + RFLOAT *rdata = MULTIDIM_ARRAY(out); + int n[1] = { N }; + +#pragma omp parallel num_threads(nr_threads) { - rotated_scores_perline[ipsi].initZeros(crop_box, crop_box); - rotated_scores_perline[ipsi].setXmippOrigin(); - rotated_nonscores_perline[ipsi].initZeros(crop_box, crop_box); - rotated_nonscores_perline[ipsi].setXmippOrigin(); - rotated_scores[ipsi].initZeros(crop_box/shift_step, crop_box/shift_step); - rotated_scores[ipsi].setXmippOrigin(); - rotated_nonscores[ipsi].initZeros(crop_box/shift_step, crop_box/shift_step); - rotated_nonscores[ipsi].setXmippOrigin(); + const int tid = omp_get_thread_num(); + const int nthr = omp_get_num_threads(); + + const long int c0 = (long int)Nh * tid / nthr; + const long int c1 = (long int)Nh * (tid + 1) / nthr; + if (c1 > c0) + { + AMY_FFTW_PLAN p = NULL; + #pragma omp critical(FourierTransformer_fftw_plan) + p = AMY_FFTW(plan_many_dft)(1, n, (int)(c1 - c0), + cdata + c0, NULL, Nh, 1, + cdata + c0, NULL, Nh, 1, FFTW_BACKWARD, FFTW_ESTIMATE); + if (p == NULL) REPORT_ERROR("inverseFT2Domp ERROR: FFTW plan cannot be created"); + AMY_FFTW(execute)(p); + #pragma omp critical(FourierTransformer_fftw_plan) + AMY_FFTW(destroy_plan)(p); + } + +#pragma omp barrier + + const long int r0 = (long int)N * tid / nthr; + const long int r1 = (long int)N * (tid + 1) / nthr; + if (r1 > r0) + { + AMY_FFTW_PLAN p = NULL; + #pragma omp critical(FourierTransformer_fftw_plan) + p = AMY_FFTW(plan_many_dft_c2r)(1, n, (int)(r1 - r0), + cdata + r0 * Nh, NULL, 1, Nh, + rdata + r0 * N, NULL, 1, N, FFTW_ESTIMATE); + if (p == NULL) REPORT_ERROR("inverseFT2Domp ERROR: FFTW plan cannot be created"); + AMY_FFTW(execute)(p); + #pragma omp critical(FourierTransformer_fftw_plan) + AMY_FFTW(destroy_plan)(p); + } } +} - // Prepare all the transformers for the 1D lines - MultidimArray oneline_tmp(ilengthmax); - for (int i = 0; i < nr_threads; i++) - transformer[i].setReal(oneline_tmp); +// Rotate the mirror-padded image over psi and downscale it by cropping its Fourier +// transform. +// +// This is the memory-heavy step: a large_box^2 real image plus its complex transform is +// about 1.3 GB for a K3 micrograph with the default settings. How many of these exist at +// the same time sets the peak memory of the program, and that is what --fom_psi_batch +// controls. +void makeRotatedImage(AmyloidFinder &finder, + const MultidimArray &Mbig, + int ipsi, + MultidimArray &Mrot) +{ + const RFLOAT psi = finder.getPsiAngle(ipsi); + + // Rotate the images in their original size to prevent interpolation artefacts near the + // signal frequencies. No initZeros: with the periodic extension every output pixel is + // written, so zeroing a large_box^2 array first is a pointless memset. + Mrot.reshape(finder.large_box, finder.large_box); + rotateBilinearOmp(Mbig, Mrot, psi, finder.nr_threads); + + // Re-scale image so that Nyquist is at down_angpix. + // + // Note the order: crop first, normalise afterwards. FourierTransformer::Transform() + // normalises the whole transform, which for a 6174^2 box is 19 M complex divisions of + // which windowFourierTransform() keeps only a fifth. Normalising just the survivors is + // the same arithmetic on every coefficient we keep, so the result is unchanged. + MultidimArray FT, FT2; + forwardFT2Domp(Mrot, FT, finder.nr_threads); + windowFourierTransform(FT, FT2, finder.crop_box); + FT.clear(); + + const unsigned long int fft_size = (unsigned long int)finder.large_box + * (unsigned long int)finder.large_box; + FOR_ALL_DIRECT_ELEMENTS_IN_MULTIDIMARRAY(FT2) + DIRECT_MULTIDIM_ELEM(FT2, n) /= fft_size; + + Mrot.reshape(finder.crop_box, finder.crop_box); + inverseFT2Domp(FT2, Mrot, finder.nr_threads); + Mrot.setXmippOrigin(); +} - // Now loop over all positions to calculate 1D FFTs - if (myverb) +// The psi angles in the second half of the range are the first half rotated over 90 +// degrees, which for the downscaled image is a transpose. +void transposeRotatedImage(MultidimArray &Mrot, int crop_box, + MultidimArray &Mrot90) +{ + Mrot90.initZeros(crop_box, crop_box); + Mrot90.setXmippOrigin(); + // stay away from boundary to prevent many if-statements below. Images are cropped in + // larger box anyway, so boundaries should be zero + for (long int i = STARTINGY(Mrot90) + 1; i <= FINISHINGY(Mrot90) - 1; i++) { - std::cout << " - Searching over all coordinates ..." << std::endl; - init_progress_bar(nr_psi); + for (long int j = STARTINGX(Mrot90) + 1; j <= FINISHINGX(Mrot90) - 1; j++) + { + A2D_ELEM(Mrot90, i, j) = A2D_ELEM(Mrot, -j, i); + } } +} - int my_skip_side_length = ilengthmax/2; - for (int ipsi = 0; ipsi < nr_psi; ipsi++) +// Accumulate the 1D power spectra of the signal and non-signal Fourier shells along every +// sampled line, then sum those over the width of the search box. +// +// Cheap on memory (the two crop_box^2 per-line maps are passed in and reused for every psi) +// and compute-bound, so this runs for one psi at a time with all threads on the rows. +void accumulatePsiScore(AmyloidFinder &finder, + MultidimArray &Mrot, + MultidimArray &scores_perline, + MultidimArray &nonscores_perline, + MultidimArray &out_scores, + MultidimArray &out_nonscores) +{ + scores_perline.initZeros(finder.crop_box, finder.crop_box); + scores_perline.setXmippOrigin(); + nonscores_perline.initZeros(finder.crop_box, finder.crop_box); + nonscores_perline.setXmippOrigin(); + + out_scores.initZeros(finder.crop_box / finder.shift_step, finder.crop_box / finder.shift_step); + out_scores.setXmippOrigin(); + out_nonscores.initZeros(finder.crop_box / finder.shift_step, finder.crop_box / finder.shift_step); + out_nonscores.setXmippOrigin(); + + const int my_skip_side_length = finder.ilengthmax / 2; +#pragma omp parallel num_threads(finder.nr_threads) { -#pragma omp parallel for num_threads(nr_threads) - for (int ypos = my_skip_side_length; ypos < YSIZE(rotated_imgs[ipsi]) - my_skip_side_length; ypos += 1) + // Keep these out of the loops, and use our own plan on them rather than + // FourierTransformer. Two reasons: setReal() rebuilds its plans whenever the data + // pointer changes, which with a fresh array per row meant re-planning ~150,000 times + // per micrograph, all serialised on one critical section; and Transform() normalises + // all ilengthmax/2+1 coefficients when only the signal and non-signal shells are ever + // read -- 61 complex divisions per line where 10 are needed. Profiling showed that + // second point alone to be 8% of the run time of the whole program. + MultidimArray oneline(finder.ilengthmax); + MultidimArray FTline(finder.ilengthmax / 2 + 1); + +#ifdef RELION_SINGLE_PRECISION + fftwf_plan line_plan = NULL; + #pragma omp critical(FourierTransformer_fftw_plan) + line_plan = fftwf_plan_dft_r2c_1d(finder.ilengthmax, MULTIDIM_ARRAY(oneline), + (fftwf_complex*) MULTIDIM_ARRAY(FTline), FFTW_ESTIMATE); +#else + fftw_plan line_plan = NULL; + #pragma omp critical(FourierTransformer_fftw_plan) + line_plan = fftw_plan_dft_r2c_1d(finder.ilengthmax, MULTIDIM_ARRAY(oneline), + (fftw_complex*) MULTIDIM_ARRAY(FTline), FFTW_ESTIMATE); +#endif + if (line_plan == NULL) REPORT_ERROR("accumulatePsiScore ERROR: FFTW plan cannot be created"); + + // same normalisation statement as FourierTransformer::Transform(), so the + // coefficients we use come out bit-for-bit as before + const unsigned long int line_size = (unsigned long int)finder.ilengthmax; + +#pragma omp for + for (int ypos = my_skip_side_length; ypos < YSIZE(Mrot) - my_skip_side_length; ypos += 1) { - int cen_ypos = ypos - YSIZE(rotated_imgs[ipsi])/2; - const int tid = omp_get_thread_num(); - MultidimArray oneline(ilengthmax); - MultidimArray FTline(ilengthmax/2 + 1); + const int cen_ypos = ypos - YSIZE(Mrot) / 2; - for (int xpos = my_skip_side_length; xpos < XSIZE(rotated_imgs[ipsi]) - my_skip_side_length; xpos += 1) + // Only columns with cen_xpos % shift_step == 0 are ever read by the summation + // over the filament width below, so start at the first such column and step + // through them: the 1D transform of every other column is dead work, and its + // entry in the per-line maps stays zero exactly as it did before. + int first_xpos = my_skip_side_length; { - int cen_xpos = xpos - XSIZE(rotated_imgs[ipsi])/2; + const int cen_first = first_xpos - XSIZE(Mrot) / 2; + const int rem = ((cen_first % finder.shift_step) + finder.shift_step) % finder.shift_step; + if (rem != 0) first_xpos += finder.shift_step - rem; + } + + for (int xpos = first_xpos; xpos < XSIZE(Mrot) - my_skip_side_length; xpos += finder.shift_step) + { + const int cen_xpos = xpos - XSIZE(Mrot) / 2; // Grab the line from the rotated image, in X and in Y directions - for (int iline = 0; iline < ilengthmax; iline++) - DIRECT_A1D_ELEM(oneline, iline) = A2D_ELEM(rotated_imgs[ipsi], cen_ypos, cen_xpos+iline-ilengthmax/2); + for (int iline = 0; iline < finder.ilengthmax; iline++) + DIRECT_A1D_ELEM(oneline, iline) = A2D_ELEM(Mrot, cen_ypos, cen_xpos + iline - finder.ilengthmax / 2); - transformer[tid].FourierTransform(oneline, FTline, false); +#ifdef RELION_SINGLE_PRECISION + fftwf_execute(line_plan); +#else + fftw_execute(line_plan); +#endif - for (int isig = imin_signal; isig <= imax_signal; isig++) + for (int isig = finder.imin_signal; isig <= finder.imax_signal; isig++) { - A2D_ELEM(rotated_scores_perline[ipsi], cen_ypos, cen_xpos) += norm(DIRECT_A1D_ELEM(FTline, isig)); + Complex val = DIRECT_A1D_ELEM(FTline, isig); + val /= line_size; + A2D_ELEM(scores_perline, cen_ypos, cen_xpos) += norm(val); } - for (int isig = imin_nonsignal; isig <= imax_nonsignal; isig++) + + for (int isig = finder.imin_nonsignal; isig <= finder.imax_nonsignal; isig++) { - A2D_ELEM(rotated_nonscores_perline[ipsi], cen_ypos, cen_xpos) += norm(DIRECT_A1D_ELEM(FTline, isig)); + Complex val = DIRECT_A1D_ELEM(FTline, isig); + val /= line_size; + A2D_ELEM(nonscores_perline, cen_ypos, cen_xpos) += norm(val); } + } + } - } // end loop ypos - } // end for xpos +#ifdef RELION_SINGLE_PRECISION + #pragma omp critical(FourierTransformer_fftw_plan) + fftwf_destroy_plan(line_plan); +#else + #pragma omp critical(FourierTransformer_fftw_plan) + fftw_destroy_plan(line_plan); +#endif + } - /* - Image It0; - It0()= rotated_scores_perline[ipsi]; - FileName fnt0="It0_scores_psi"+ integerToString(ipsi)+".spi"; - It0.write(fnt0); - std::cerr <<" written: "< &image, MultidimArray &Mscore, + MultidimArray &Mangle, RFLOAT &skew, RFLOAT &kurt, bool myverb) +{ + + if (nr_psi % 2 != 0) + REPORT_ERROR("getScoreForOneMicrograph ERROR: the number of psi angles must be even; see initialise()."); - // Now that we have signal per individual line for each coordinate, sum over the width of the search box - // The below is split in two halves, becauses otherwise cen_pos=0 may be sampled twice!!! - int my_skip_side_width = iwidthmax/2; - #pragma omp parallel for num_threads(nr_threads) - for (int ypos = 0; ypos < YSIZE(rotated_imgs[ipsi])/2 - my_skip_side_width; ypos += shift_step) + + MultidimArray Mbig(large_box, large_box); + Mbig.setXmippOrigin(); + for (long int i=STARTINGY(Mbig); i<=FINISHINGY(Mbig); i++) + { + long int ip = i; + //if (i < STARTINGY(image)) ip += YSIZE(image); + //else if (i > FINISHINGY(image)) ip -= YSIZE(image); + if (i < STARTINGY(image)) ip = 2*STARTINGY(image) - i; + else if (i > FINISHINGY(image)) ip = 2*FINISHINGY(image) - i; + + for (long int j=STARTINGX(Mbig); j<=FINISHINGX(Mbig); j++) { - for (int ipassy = 0; ipassy < 2; ipassy++) - { - int cen_ypos = (ipassy == 0) ? ypos : -ypos; - if (ypos == 0 && ipassy == 1) continue; - - for (int xpos = 0; xpos < XSIZE(rotated_imgs[ipsi])/2 - my_skip_side_width; xpos += shift_step) - { - for (int ipass = 0; ipass < 2; ipass++) - { - int cen_xpos = (ipass == 0) ? xpos : -xpos; - if (xpos == 0 && ipass == 1) continue; - for (int iwidth = 0; iwidth < iwidthmax; iwidth++) - { - // Grab the line from the rotated image, in X and in Y directions - A2D_ELEM(rotated_scores[ipsi], cen_ypos/shift_step, cen_xpos/shift_step) += - A2D_ELEM(rotated_scores_perline[ipsi], cen_ypos+iwidth-iwidthmax/2, cen_xpos); - A2D_ELEM(rotated_nonscores[ipsi], cen_ypos/shift_step, cen_xpos/shift_step) += - A2D_ELEM(rotated_nonscores_perline[ipsi], cen_ypos+iwidth-iwidthmax/2, cen_xpos); - } // end loop iwidth - } // end loop ipass - } // end loop xpos - } // end for ipassy - } // end for ypos - - if (myverb) progress_bar(ipsi); - - } // end for ipsi - if (myverb) progress_bar(nr_psi); + long int jp = j; + if (j < STARTINGX(image)) jp = 2*STARTINGX(image) - j; + else if (j > FINISHINGX(image)) jp = 2*FINISHINGX(image) - j; + A2D_ELEM(Mbig, i, j) = A2D_ELEM(image, ip, jp); + } + } + + const int half_nr_psi = nr_psi / 2; + + // All per-psi score maps are kept, so the gather over psi further down is unchanged. + // They are small: two (crop_box/shift_step)^2 arrays per psi. + std::vector > rotated_scores(nr_psi), rotated_nonscores(nr_psi); + + // Scratch, reused for every psi rather than kept per psi + MultidimArray scores_perline, nonscores_perline; + MultidimArray Mrot, Mrot90; + + if (myverb) + { + std::cout << " - Rotating and searching over all coordinates ..." << std::endl; + init_progress_bar(nr_psi); + } + + // One psi angle at a time. The rotation and both 2D transforms are internally + // parallelised over all --j threads, so there is nothing to gain from running several + // psi angles at once, and this way only one large_box^2 image and its transform exist + // at any moment. + for (int ipsi = 0; ipsi < half_nr_psi; ipsi++) + { + makeRotatedImage(*this, Mbig, ipsi, Mrot); + + accumulatePsiScore(*this, Mrot, scores_perline, nonscores_perline, + rotated_scores[ipsi], rotated_nonscores[ipsi]); + + // the second half of the psi range is this image rotated over 90 degrees + transposeRotatedImage(Mrot, crop_box, Mrot90); + Mrot.clear(); + + accumulatePsiScore(*this, Mrot90, scores_perline, nonscores_perline, + rotated_scores[ipsi + half_nr_psi], rotated_nonscores[ipsi + half_nr_psi]); + Mrot90.clear(); + + if (myverb) progress_bar(2 * (ipsi + 1)); + } + if (myverb) progress_bar(nr_psi); // Now loop over all positions and find the best Zscore and the best ipsi // Note that each translation in the original image has a different coordinate in the rotated_score images! From 7e5af22de7e9c97ba5378a2597da3aaabb98e45f Mon Sep 17 00:00:00 2001 From: James Fuller Date: Mon, 24 Aug 2026 12:01:43 -0500 Subject: [PATCH 188/189] mpi broadcast full per body rot+trans accuracy vectors --- src/ml_optimiser_mpi.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ml_optimiser_mpi.cpp b/src/ml_optimiser_mpi.cpp index 656ddb786..c18054244 100644 --- a/src/ml_optimiser_mpi.cpp +++ b/src/ml_optimiser_mpi.cpp @@ -3661,6 +3661,9 @@ void MlOptimiserMpi::calculateExpectedAngularErrors(long int my_first_part_id, l // The reconstructing follower Bcast acc_rottilt, acc_psi, acc_trans to all other nodes! node->relion_MPI_Bcast(&acc_rot, 1, MY_MPI_DOUBLE, first_follower, MPI_COMM_WORLD); node->relion_MPI_Bcast(&acc_trans, 1, MY_MPI_DOUBLE, first_follower, MPI_COMM_WORLD); + // Also Bcast full per-body/class vectors, e.g. for synching body fixing logic during multibody refinement + node->relion_MPI_Bcast(&mymodel.acc_rot[0], mymodel.acc_rot.size(), MY_MPI_DOUBLE, first_follower, MPI_COMM_WORLD); + node->relion_MPI_Bcast(&mymodel.acc_trans[0], mymodel.acc_trans.size(), MY_MPI_DOUBLE, first_follower, MPI_COMM_WORLD); } void MlOptimiserMpi::updateAngularSamplingGrad(long int my_first_part_id, long int my_last_part_id, bool myverb) From 95017d25fa34b7aba7e904d7fdf522631da629a4 Mon Sep 17 00:00:00 2001 From: Srijan Keshri Date: Wed, 16 Sep 2026 11:26:02 +0530 Subject: [PATCH 189/189] Reuse exactly-sized free blocks in the custom GPU allocator (issue #1225) (#1369) _getFirstSuitedFree() skipped every free block whose size was equal to the requested size, so an exact fit was never returned. Three consequences: - the "curAlloc->size == size" branch in alloc() is unreachable, even though it is written to handle precisely that case; - a request the pool can satisfy exactly still fails, and alloc() aborts with ERRGPUCAOOM after exhausting its retry loop; - a freed block is never reused by a request of the same size, so the pool fragments under the common free-then-reallocate pattern. Requests are rounded up to alignmentSize before the search, which makes exact fits common rather than rare. The HIP port carries an identical copy of the function, so both are changed. Reported by @fengxiaoshuai. --- src/acc/cuda/custom_allocator.cuh | 2 +- src/acc/hip/custom_allocator.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/acc/cuda/custom_allocator.cuh b/src/acc/cuda/custom_allocator.cuh index c8f49dff6..eb890d320 100644 --- a/src/acc/cuda/custom_allocator.cuh +++ b/src/acc/cuda/custom_allocator.cuh @@ -122,7 +122,7 @@ private: { Alloc *a = first; //If not the last and too small or not free go to next allocation region - while (a != NULL && ( a->size <= size || ! a->free ) ) + while (a != NULL && ( a->size < size || ! a->free ) ) a = a->next; return a; diff --git a/src/acc/hip/custom_allocator.h b/src/acc/hip/custom_allocator.h index 7cc954407..36f7fc2c4 100644 --- a/src/acc/hip/custom_allocator.h +++ b/src/acc/hip/custom_allocator.h @@ -124,7 +124,7 @@ class HipCustomAllocator { Alloc *a = first; //If not the last and too small or not free go to next allocation region - while (a != NULL && ( a->size <= size || ! a->free ) ) + while (a != NULL && ( a->size < size || ! a->free ) ) a = a->next; return a;