pax_global_header00006660000000000000000000000064132337040220014506gustar00rootroot0000000000000052 comment=187c48cbcb65cb09449e17a8e55af3442af1cdf1 view-0.1.00/000077500000000000000000000000001323370402200125365ustar00rootroot00000000000000view-0.1.00/.gitignore000066400000000000000000000000621323370402200145240ustar00rootroot00000000000000view cfl2png viewer.inc tags *.swp Makefile.local view-0.1.00/LICENSE000066400000000000000000000027161323370402200135510ustar00rootroot00000000000000Copyright (c) 2015. Martin Uecker. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. view-0.1.00/Makefile000066400000000000000000000023641323370402200142030ustar00rootroot00000000000000 CUDA?=0 CUDA_BASE ?= /usr/local/cuda BUILDTYPE = Linux UNAME = $(shell uname -s) ifeq ($(UNAME),Darwin) BUILDTYPE = MacOSX endif ifeq ($(TOOLBOX_PATH),) TOOLBOX_INC=/usr/include/bart/ TOOLBOX_LIB=/usr/lib/bart/ else TOOLBOX_INC=$(TOOLBOX_PATH)/src/ TOOLBOX_LIB=$(TOOLBOX_PATH)/lib/ endif CC ?= gcc CFLAGS ?= -Wall -O2 CFLAGS += -std=c11 -fopenmp ifeq ($(CUDA),1) CUDA_L := -L$(CUDA_BASE)/lib64 -lcufft -lcudart -lcublas else CUDA_L := endif ifeq ($(BUILDTYPE), MacOSX) EXPDYN = -Wl,-export_dynamic else EXPDYN = -export-dynamic endif -include Makefile.local all: view cfl2png src/viewer.inc: src/viewer.ui @echo "STRINGIFY(`cat src/viewer.ui`)" > src/viewer.inc view: src/main.c src/view.[ch] src/draw.[ch] src/viewer.inc $(CC) $(CFLAGS) $(EXPDYN) -o view -I$(TOOLBOX_INC) `pkg-config --cflags gtk+-3.0` src/main.c src/view.c src/draw.c `pkg-config --libs gtk+-3.0` $(TOOLBOX_LIB)/libmisc.a $(TOOLBOX_LIB)/libnum.a -lm -lpng cfl2png: src/cfl2png.c src/view.[ch] src/draw.[ch] src/viewer.inc $(CC) $(CFLAGS) $(EXPDYN) -o cfl2png -I$(TOOLBOX_INC) src/cfl2png.c src/draw.c $(TOOLBOX_LIB)/libmisc.a $(TOOLBOX_LIB)/libnum.a -lm -lpng install: install view $(DESTDIR)/usr/lib/bart/commands/ clean: rm -f view cfl2png viewer.inc view-0.1.00/README000066400000000000000000000007451323370402200134240ustar00rootroot00000000000000 Small image viewer for multi-dimensional files. Compiles on Linux and Mac OS X. It needs some libraries from the Berkeley Advanced Reconstruction Toolbox (BART). https://mrirecon.github.io/bart/ Requires BART up to commit e4df99aa75e0344931ba2e76d9ae052b02da498f Mac OS X: sudo port install pkgconfig sudo port install gtk3 sudo port install adwaita-icon-theme Linux: sudo apt-get install libgtk-3-dev Compile with make after setting the TOOLBOX_PATH environment variable. view-0.1.00/README.md000066400000000000000000000010141323370402200140110ustar00rootroot00000000000000 Small image viewer for multi-dimensional files. Compiles on Linux and Mac OS X. It needs some libraries from the Berkeley Advanced Reconstruction Toolbox (BART). https://mrirecon.github.io/bart/ ![viewer](viewer.png) Installation: Requires BART up to commit e4df99aa75e0344931ba2e76d9ae052b02da498f Mac OS X: sudo port install pkgconfig sudo port install gtk3 sudo port install adwaita-icon-theme Linux: sudo apt-get install libgtk-3-dev Compile with make after setting the TOOLBOX_PATH environment variable. view-0.1.00/genctags000077500000000000000000000001251323370402200142550ustar00rootroot00000000000000#!/bin/bash ctags --langmap=c++:+.cu --extra=+f `find . -regex '.*\.[ch]u*' -print` view-0.1.00/src/000077500000000000000000000000001323370402200133255ustar00rootroot00000000000000view-0.1.00/src/cfl2png.c000066400000000000000000000140061323370402200150250ustar00rootroot00000000000000 #include #include #include #include #include #undef MAX #undef MIN #include "num/multind.h" #include "num/init.h" #include "misc/misc.h" #include "misc/debug.h" #include "misc/mmio.h" #include "misc/opts.h" #include "misc/png.h" #include "draw.h" #include "view.h" #ifndef CFL_SIZE #define CFL_SIZE sizeof(complex float) #endif void export_images(const char* output_prefix, int xdim, int ydim, float windowing[2], float zoom, enum mode_t mode, enum flip_t flip, enum interp_t interpolation, const long dims[DIMS], const complex float* idata); static const char usage_str[] = " "; static const char help_str[] = "Export images to png."; int main(int argc, char* argv[]) { int xdim = 0; int ydim = 0; float windowing[2] = {0.f, 1.f}; enum mode_t mode = MAGN; float zoom =2.f; enum flip_t flip = OO; enum interp_t interpolation = NLINEAR; struct opt_s modeopt[] = { OPT_SELECT('M', enum mode_t, &mode, MAGN, "magnitude gray (default) "), OPT_SELECT('V', enum mode_t, &mode, MAGN_VIRIDS, "magnitude viridis"), OPT_SELECT('C', enum mode_t, &mode, CMPL, "complex"), OPT_SELECT('G', enum mode_t, &mode, CMPL_MYGBM, "complex MYGBM"), OPT_SELECT('P', enum mode_t, &mode, PHSE, "phase"), OPT_SELECT('Y', enum mode_t, &mode, PHSE_MYGBM, "phase MYGBM"), OPT_SELECT('R', enum mode_t, &mode, REAL, "real"), OPT_SELECT('F', enum mode_t, &mode, FLOW, "flow"), }; struct opt_s flipopt[] = { OPT_SELECT('O', enum flip_t, &flip, OO, "flip OO"), OPT_SELECT('X', enum flip_t, &flip, XO, "flip XO"), OPT_SELECT('Y', enum flip_t, &flip, OY, "flip OY"), OPT_SELECT('Z', enum flip_t, &flip, XY, "flip XY"), }; struct opt_s interpopt[] = { OPT_SELECT('L', enum interp_t, &interpolation, NLINEAR, "n-linear interpolation"), OPT_SELECT('N', enum interp_t, &interpolation, NEAREST, "nearest neighbor interpolation"), }; const struct opt_s opts[] = { OPT_INT('x', &xdim, "xdim", "output xdim (default: 0) "), OPT_INT('y', &ydim, "ydim", "output ydim (default: 0). If both are zero, first two non-singleton dims are used."), OPT_FLOAT('l', &windowing[0], "l", "lower windowing value"), OPT_FLOAT('u', &windowing[1], "u", "upper windowing value"), OPT_FLOAT('z', &zoom, "z", "zoom factor (default: 2) "), OPT_SUBOPT('C', "cmap", "colormap. -Ch for help.", ARRAY_SIZE(modeopt), modeopt), OPT_SUBOPT('F', "flip", "flip. -Fh for help.", ARRAY_SIZE(flipopt), flipopt), OPT_SUBOPT('I', "interp", "interp. -Ih for help.", ARRAY_SIZE(interpopt), interpopt), OPT_INT('d', &debug_level, "level", "Debug level"), }; cmdline(&argc, argv, 2, 1000, usage_str, help_str, ARRAY_SIZE(opts), opts); assert( (windowing[0] >= 0.f) && (windowing[1] <= 1.f) && (windowing[0] < windowing[1])); const char* infile = argv[1]; const char* output_prefix = argv[2]; assert((0 <= xdim) && (xdim < DIMS)); assert((0 <= ydim) && (ydim < DIMS)); /* * If the filename ends in ".hdr", ".cfl" or just "." (from * tab-completion), just replace the "." with a \0-character. */ char* dot = strrchr(infile, '.'); if ( (NULL != dot) && ( !strcmp(dot, ".cfl") || !strcmp(dot, ".hdr") || !strcmp(dot, "."))) *dot = '\0'; long dims[DIMS]; complex float* idata = load_cfl(infile, DIMS, dims); char* ext = rindex(output_prefix, '.'); if (NULL != ext) { if (0 != strcmp(ext, ".png")) error("Unknown file extension."); else *ext = '\0'; } export_images(output_prefix, xdim, ydim, windowing, zoom, mode, flip, interpolation, dims, idata); unmap_cfl(DIMS, dims, idata); return 0; } /** * Convert flat index to pos * */ static void unravel_index(unsigned int D, long pos[D], const long dims[D], long index) { for (unsigned int d = 0; d < D; ++d) { if (1 == dims[d]) continue; pos[d] = index % dims[d]; index /= dims[d]; } } void export_images(const char* output_prefix, int xdim, int ydim, float windowing[2], float zoom, enum mode_t mode, enum flip_t flip, enum interp_t interpolation, const long dims[DIMS], const complex float* idata) { if (xdim == ydim) { long sq_dims[2] = { 0 }; int l = 0; for (int i = 0; (i < DIMS) && (l < 2); i++) if (1 != dims[i]) sq_dims[l++] = i; assert(2 == l); xdim = sq_dims[0]; ydim = sq_dims[1]; } double max = 0.; for (long j = 0; j < md_calc_size(DIMS, dims); j++) if (max < cabsf(idata[j])) max = cabsf(idata[j]); if (0. == max) max = 1.; int rgbw = dims[xdim] * zoom; int rgbh = dims[ydim] * zoom; int rgbstr = 4 * rgbw; unsigned char* rgb = xmalloc(rgbh * rgbstr); complex float* buf = xmalloc(rgbh * rgbw * sizeof(complex float)); // loop over all dims other than xdim and ydim long loopdims[DIMS]; unsigned long loopflags = (MD_BIT(xdim)|MD_BIT(ydim)); md_select_dims(DIMS, ~loopflags, loopdims, dims); debug_printf(DP_DEBUG3, "flags: %lu\nloopdims: ", loopflags); debug_print_dims(DP_DEBUG3, DIMS, loopdims); long pos[DIMS] = { [0 ... DIMS - 1] = 0 }; long strs[DIMS]; md_calc_strides(DIMS, strs, dims, sizeof(complex float)); for (unsigned long d = 0l; d < md_calc_size(DIMS, loopdims); ++d){ unravel_index(DIMS, pos, loopdims, d); debug_printf(DP_DEBUG3, "\ti: %lu\n\t", d); debug_print_dims(DP_DEBUG3, DIMS, pos); // Prepare output filename unsigned int bufsize = 255; char name[bufsize]; char* cur = name; const char* end = name + bufsize; cur += snprintf(cur, end - cur, "%s", output_prefix); for (unsigned int i = 0; i < DIMS; i++) { if (1 != loopdims[i]) cur += snprintf(cur, end - cur, "_%s_%04ld", get_spec(i), pos[i]); } cur += snprintf(cur, end - cur, ".png"); debug_printf(DP_DEBUG2, "\t%s\n", name); update_buf(xdim, ydim, DIMS, dims, strs, pos, flip, interpolation, zoom, zoom, rgbw, rgbh, idata, buf); draw(rgbw, rgbh, rgbstr, (unsigned char(*)[rgbw][rgbstr / 4][4])rgb, mode, 1. / max, windowing[0], windowing[1], 0, rgbw, buf); if (0 != png_write_bgr32(name, rgbw, rgbh, 0, rgb)) error("Error: writing image file.\n"); } // free(source); free(buf); free(rgb); } view-0.1.00/src/colormaps.inc000066400000000000000000000420101323370402200160140ustar00rootroot00000000000000// cyclic_mygbm is a phase colormap from the the // "colorcet" project (https://github.com/bokeh/colorcet) // using a transition from magenta through yellow, green, and blue back to // magenta. More information can be found in: // Peter Kovesi. Good Colour Maps: How to Design Them. // arXiv:1509.03700 [cs.GR] 2015 // cyclic_mygbm by Peter Kovesi (http://www.peterkovesi.com) is licensed under the // Creative Commons Attribution 4.0 International Public License (CC-BY) // (https://creativecommons.org/licenses/by/4.0/) // Direct link to the colormap is // https://github.com/bokeh/colorcet/blob/master/assets/CETperceptual_csv_0_1/cyclic_mygbm_30-95_c78_n256_s25.csv static const double cyclic_mygbm[256][3] = { {0.18062, 0.13244, 0.91856}, {0.19173, 0.12446, 0.92396}, {0.20389, 0.11765, 0.92889}, {0.21681, 0.11214, 0.93335}, {0.23028, 0.10794, 0.93739}, {0.24417, 0.10525, 0.94101}, {0.25828, 0.10403, 0.94425}, {0.27246, 0.10417, 0.94716}, {0.28659, 0.10560, 0.94977}, {0.30059, 0.10807, 0.95212}, {0.31440, 0.11153, 0.95426}, {0.32796, 0.11565, 0.95622}, {0.34126, 0.12031, 0.95803}, {0.35426, 0.12544, 0.95973}, {0.36698, 0.13084, 0.96134}, {0.37942, 0.13637, 0.96288}, {0.39160, 0.14210, 0.96436}, {0.40352, 0.14786, 0.96581}, {0.41520, 0.15363, 0.96722}, {0.42669, 0.15941, 0.9686}, {0.43799, 0.16515, 0.96996}, {0.44913, 0.17088, 0.97129}, {0.46013, 0.17650, 0.97261}, {0.47106, 0.18204, 0.97389}, {0.48191, 0.18751, 0.97514}, {0.49273, 0.19280, 0.97635}, {0.50357, 0.19798, 0.97752}, {0.51446, 0.20302, 0.97863}, {0.52541, 0.20790, 0.97969}, {0.53647, 0.21259, 0.98067}, {0.54767, 0.21709, 0.98157}, {0.55903, 0.22137, 0.98238}, {0.57057, 0.22542, 0.9831}, {0.58230, 0.22925, 0.98372}, {0.59423, 0.23284, 0.98424}, {0.60636, 0.23623, 0.98465}, {0.61868, 0.23935, 0.98497}, {0.63116, 0.24227, 0.98518}, {0.64379, 0.24496, 0.9853}, {0.65656, 0.24750, 0.98533}, {0.66943, 0.24982, 0.98527}, {0.68237, 0.25203, 0.98515}, {0.69537, 0.25408, 0.98496}, {0.70839, 0.25601, 0.98471}, {0.72139, 0.25786, 0.98441}, {0.73437, 0.25968, 0.98405}, {0.74728, 0.26143, 0.98365}, {0.76012, 0.26319, 0.98321}, {0.77286, 0.26494, 0.98271}, {0.78548, 0.26676, 0.98216}, {0.79794, 0.26867, 0.98153}, {0.81023, 0.27069, 0.98082}, {0.82233, 0.27285, 0.98001}, {0.83418, 0.27525, 0.97905}, {0.84576, 0.27791, 0.97793}, {0.85702, 0.28089, 0.97661}, {0.86794, 0.28430, 0.97504}, {0.87845, 0.28819, 0.97317}, {0.88852, 0.29263, 0.97097}, {0.89810, 0.29768, 0.96837}, {0.90717, 0.30340, 0.96533}, {0.91567, 0.30984, 0.96182}, {0.92361, 0.31700, 0.9578}, {0.93095, 0.32489, 0.95323}, {0.93769, 0.33352, 0.94809}, {0.94383, 0.34283, 0.94239}, {0.94939, 0.35275, 0.93613}, {0.95439, 0.36323, 0.92931}, {0.95886, 0.37422, 0.92198}, {0.96283, 0.38558, 0.91416}, {0.96634, 0.39727, 0.90588}, {0.96944, 0.40921, 0.89721}, {0.97216, 0.42130, 0.88817}, {0.97454, 0.43350, 0.87883}, {0.97663, 0.44573, 0.86923}, {0.97845, 0.45797, 0.8594}, {0.98004, 0.47017, 0.84939}, {0.98142, 0.48228, 0.83922}, {0.98262, 0.49430, 0.82894}, {0.98364, 0.50620, 0.81854}, {0.98453, 0.51799, 0.80808}, {0.98527, 0.52964, 0.79753}, {0.98588, 0.54116, 0.78694}, {0.98637, 0.55255, 0.77629}, {0.98676, 0.56382, 0.76559}, {0.98704, 0.57494, 0.75486}, {0.98722, 0.58595, 0.74407}, {0.98733, 0.59683, 0.73326}, {0.98736, 0.60758, 0.72241}, {0.98732, 0.61821, 0.71153}, {0.98723, 0.62871, 0.7006}, {0.98710, 0.63909, 0.68965}, {0.98694, 0.64936, 0.67864}, {0.98677, 0.65949, 0.66762}, {0.98660, 0.66950, 0.65654}, {0.98644, 0.67938, 0.64543}, {0.98630, 0.68916, 0.63428}, {0.98621, 0.69879, 0.62309}, {0.98616, 0.70831, 0.61184}, {0.98617, 0.71772, 0.60055}, {0.98624, 0.72700, 0.5892}, {0.98636, 0.73618, 0.57779}, {0.98653, 0.74526, 0.56632}, {0.98675, 0.75426, 0.55476}, {0.98702, 0.76315, 0.54311}, {0.98731, 0.77198, 0.53134}, {0.98761, 0.78074, 0.51946}, {0.98792, 0.78944, 0.50744}, {0.98820, 0.79807, 0.49526}, {0.98845, 0.80666, 0.48291}, {0.98864, 0.81518, 0.47039}, {0.98875, 0.82365, 0.45765}, {0.98875, 0.83206, 0.4447}, {0.98863, 0.84039, 0.43151}, {0.98834, 0.84863, 0.41804}, {0.98786, 0.85677, 0.40433}, {0.98715, 0.86476, 0.39033}, {0.98616, 0.87259, 0.37607}, {0.98485, 0.88020, 0.36152}, {0.98316, 0.88754, 0.3467}, {0.98105, 0.89458, 0.33163}, {0.97845, 0.90123, 0.31637}, {0.97534, 0.90744, 0.30092}, {0.97166, 0.91315, 0.28537}, {0.96737, 0.91831, 0.26983}, {0.96244, 0.92285, 0.25432}, {0.95686, 0.92672, 0.23895}, {0.95064, 0.92990, 0.22388}, {0.94377, 0.93236, 0.2092}, {0.93628, 0.93410, 0.19506}, {0.92820, 0.93512, 0.18153}, {0.91959, 0.93544, 0.16884}, {0.91047, 0.93510, 0.15697}, {0.90091, 0.93414, 0.1461}, {0.89097, 0.93263, 0.13623}, {0.88070, 0.93061, 0.12747}, {0.87014, 0.92815, 0.11977}, {0.85937, 0.92531, 0.11315}, {0.84840, 0.92216, 0.10742}, {0.83729, 0.91874, 0.10257}, {0.82605, 0.91511, 0.098502}, {0.81472, 0.91132, 0.095091}, {0.80332, 0.90739, 0.092162}, {0.79187, 0.90336, 0.089659}, {0.78036, 0.89925, 0.087518}, {0.76881, 0.89508, 0.08551}, {0.75723, 0.89086, 0.083837}, {0.74562, 0.88662, 0.082243}, {0.73398, 0.88235, 0.080673}, {0.72230, 0.87807, 0.079194}, {0.71060, 0.87376, 0.077792}, {0.69886, 0.86946, 0.076415}, {0.68710, 0.86514, 0.075063}, {0.67529, 0.86082, 0.073757}, {0.66346, 0.85649, 0.072319}, {0.65156, 0.85215, 0.071005}, {0.63963, 0.84780, 0.069678}, {0.62764, 0.84345, 0.068313}, {0.61560, 0.83909, 0.066946}, {0.60351, 0.83473, 0.065602}, {0.59134, 0.83035, 0.064284}, {0.57911, 0.82597, 0.063016}, {0.56681, 0.82158, 0.061599}, {0.55443, 0.81718, 0.060374}, {0.54195, 0.81278, 0.059088}, {0.52940, 0.80837, 0.057695}, {0.51672, 0.80395, 0.056522}, {0.50395, 0.79952, 0.055189}, {0.49106, 0.79508, 0.053903}, {0.47801, 0.79064, 0.052644}, {0.46484, 0.78619, 0.051424}, {0.45151, 0.78173, 0.050257}, {0.43803, 0.77726, 0.04922}, {0.42437, 0.77277, 0.04812}, {0.41056, 0.76827, 0.047322}, {0.39656, 0.76375, 0.04674}, {0.38239, 0.75922, 0.046427}, {0.36808, 0.75466, 0.046596}, {0.35361, 0.75006, 0.047299}, {0.33906, 0.74543, 0.04874}, {0.32443, 0.74075, 0.050897}, {0.30984, 0.73602, 0.054069}, {0.29532, 0.73122, 0.058336}, {0.28105, 0.72634, 0.063783}, {0.26717, 0.72137, 0.070322}, {0.25387, 0.71631, 0.077992}, {0.24134, 0.71112, 0.08687}, {0.22981, 0.70580, 0.096608}, {0.21961, 0.70035, 0.10741}, {0.21092, 0.69475, 0.11899}, {0.20400, 0.68901, 0.13129}, {0.19894, 0.68310, 0.14422}, {0.19593, 0.67703, 0.15768}, {0.19486, 0.67081, 0.17161}, {0.19560, 0.66443, 0.18594}, {0.19795, 0.65791, 0.2005}, {0.20163, 0.65125, 0.21533}, {0.20639, 0.64446, 0.2303}, {0.21183, 0.63756, 0.24538}, {0.21771, 0.63057, 0.26052}, {0.22381, 0.62348, 0.27565}, {0.22992, 0.61632, 0.29071}, {0.23593, 0.60910, 0.30574}, {0.24162, 0.60182, 0.32064}, {0.24693, 0.59450, 0.33543}, {0.25184, 0.58716, 0.35008}, {0.25622, 0.57980, 0.36459}, {0.26011, 0.57242, 0.37897}, {0.26346, 0.56501, 0.3932}, {0.26624, 0.55762, 0.4073}, {0.26846, 0.55021, 0.42127}, {0.27013, 0.54280, 0.43513}, {0.27122, 0.53539, 0.44886}, {0.27173, 0.52798, 0.46247}, {0.27170, 0.52057, 0.476}, {0.27112, 0.51317, 0.48942}, {0.26997, 0.50576, 0.50275}, {0.26823, 0.49837, 0.516}, {0.26598, 0.49096, 0.52919}, {0.26316, 0.48352, 0.54228}, {0.25982, 0.47610, 0.55529}, {0.25594, 0.46864, 0.56822}, {0.25162, 0.46117, 0.58105}, {0.24680, 0.45365, 0.5938}, {0.24161, 0.44609, 0.60643}, {0.23605, 0.43849, 0.61894}, {0.23017, 0.43081, 0.63131}, {0.22410, 0.42303, 0.64353}, {0.21793, 0.41517, 0.6556}, {0.21170, 0.40718, 0.66749}, {0.20550, 0.39906, 0.67919}, {0.19945, 0.39079, 0.6907}, {0.19367, 0.38234, 0.70201}, {0.18818, 0.37372, 0.71312}, {0.18300, 0.36488, 0.72404}, {0.17829, 0.35585, 0.73477}, {0.17392, 0.34657, 0.7453}, {0.16999, 0.33707, 0.75566}, {0.16639, 0.32732, 0.76586}, {0.16312, 0.31735, 0.7759}, {0.16005, 0.30712, 0.78581}, {0.15724, 0.29667, 0.79557}, {0.15457, 0.28595, 0.80522}, {0.15202, 0.27505, 0.81474}, {0.14966, 0.26395, 0.82414}, {0.14744, 0.25264, 0.8334}, {0.14554, 0.24118, 0.84252}, {0.14402, 0.22960, 0.85145}, {0.14312, 0.21800, 0.86019}, {0.14305, 0.20639, 0.86869}, {0.14404, 0.19481, 0.87691}, {0.14630, 0.18336, 0.88484}, {0.15007, 0.17219, 0.89241}, {0.15537, 0.16140, 0.8996}, {0.16230, 0.15103, 0.90637}, {0.17075, 0.14136, 0.9127}, }; // viridis is the new default perceptually uniform colormap of the // "matplotlib" project (http://matplotlib.org/) // Direct link to the colormap is // https://github.com/BIDS/colormap/blob/master/colormaps.py // Viridis (and the other colormaps available at the link) are created by // Nathaniel J. Smith, Stefan van der Walt, and (in the case of viridis) Eric Firing. // The file (https://github.com/BIDS/colormap/blob/master/colormaps.py) // and the colormaps in it are released under the CC0 license / // public domain dedication. The authors would appreciate credit if you use or // redistribute these colormaps, but do not impose any legal restrictions. // See for a copy // of the CC0 license. static const double viridis[256][3] = { {0.267004, 0.004874, 0.329415}, {0.268510, 0.009605, 0.335427}, {0.269944, 0.014625, 0.341379}, {0.271305, 0.019942, 0.347269}, {0.272594, 0.025563, 0.353093}, {0.273809, 0.031497, 0.358853}, {0.274952, 0.037752, 0.364543}, {0.276022, 0.044167, 0.370164}, {0.277018, 0.050344, 0.375715}, {0.277941, 0.056324, 0.381191}, {0.278791, 0.062145, 0.386592}, {0.279566, 0.067836, 0.391917}, {0.280267, 0.073417, 0.397163}, {0.280894, 0.078907, 0.402329}, {0.281446, 0.084320, 0.407414}, {0.281924, 0.089666, 0.412415}, {0.282327, 0.094955, 0.417331}, {0.282656, 0.100196, 0.422160}, {0.282910, 0.105393, 0.426902}, {0.283091, 0.110553, 0.431554}, {0.283197, 0.115680, 0.436115}, {0.283229, 0.120777, 0.440584}, {0.283187, 0.125848, 0.444960}, {0.283072, 0.130895, 0.449241}, {0.282884, 0.135920, 0.453427}, {0.282623, 0.140926, 0.457517}, {0.282290, 0.145912, 0.461510}, {0.281887, 0.150881, 0.465405}, {0.281412, 0.155834, 0.469201}, {0.280868, 0.160771, 0.472899}, {0.280255, 0.165693, 0.476498}, {0.279574, 0.170599, 0.479997}, {0.278826, 0.175490, 0.483397}, {0.278012, 0.180367, 0.486697}, {0.277134, 0.185228, 0.489898}, {0.276194, 0.190074, 0.493001}, {0.275191, 0.194905, 0.496005}, {0.274128, 0.199721, 0.498911}, {0.273006, 0.204520, 0.501721}, {0.271828, 0.209303, 0.504434}, {0.270595, 0.214069, 0.507052}, {0.269308, 0.218818, 0.509577}, {0.267968, 0.223549, 0.512008}, {0.266580, 0.228262, 0.514349}, {0.265145, 0.232956, 0.516599}, {0.263663, 0.237631, 0.518762}, {0.262138, 0.242286, 0.520837}, {0.260571, 0.246922, 0.522828}, {0.258965, 0.251537, 0.524736}, {0.257322, 0.256130, 0.526563}, {0.255645, 0.260703, 0.528312}, {0.253935, 0.265254, 0.529983}, {0.252194, 0.269783, 0.531579}, {0.250425, 0.274290, 0.533103}, {0.248629, 0.278775, 0.534556}, {0.246811, 0.283237, 0.535941}, {0.244972, 0.287675, 0.537260}, {0.243113, 0.292092, 0.538516}, {0.241237, 0.296485, 0.539709}, {0.239346, 0.300855, 0.540844}, {0.237441, 0.305202, 0.541921}, {0.235526, 0.309527, 0.542944}, {0.233603, 0.313828, 0.543914}, {0.231674, 0.318106, 0.544834}, {0.229739, 0.322361, 0.545706}, {0.227802, 0.326594, 0.546532}, {0.225863, 0.330805, 0.547314}, {0.223925, 0.334994, 0.548053}, {0.221989, 0.339161, 0.548752}, {0.220057, 0.343307, 0.549413}, {0.218130, 0.347432, 0.550038}, {0.216210, 0.351535, 0.550627}, {0.214298, 0.355619, 0.551184}, {0.212395, 0.359683, 0.551710}, {0.210503, 0.363727, 0.552206}, {0.208623, 0.367752, 0.552675}, {0.206756, 0.371758, 0.553117}, {0.204903, 0.375746, 0.553533}, {0.203063, 0.379716, 0.553925}, {0.201239, 0.383670, 0.554294}, {0.199430, 0.387607, 0.554642}, {0.197636, 0.391528, 0.554969}, {0.195860, 0.395433, 0.555276}, {0.194100, 0.399323, 0.555565}, {0.192357, 0.403199, 0.555836}, {0.190631, 0.407061, 0.556089}, {0.188923, 0.410910, 0.556326}, {0.187231, 0.414746, 0.556547}, {0.185556, 0.418570, 0.556753}, {0.183898, 0.422383, 0.556944}, {0.182256, 0.426184, 0.557120}, {0.180629, 0.429975, 0.557282}, {0.179019, 0.433756, 0.557430}, {0.177423, 0.437527, 0.557565}, {0.175841, 0.441290, 0.557685}, {0.174274, 0.445044, 0.557792}, {0.172719, 0.448791, 0.557885}, {0.171176, 0.452530, 0.557965}, {0.169646, 0.456262, 0.558030}, {0.168126, 0.459988, 0.558082}, {0.166617, 0.463708, 0.558119}, {0.165117, 0.467423, 0.558141}, {0.163625, 0.471133, 0.558148}, {0.162142, 0.474838, 0.558140}, {0.160665, 0.478540, 0.558115}, {0.159194, 0.482237, 0.558073}, {0.157729, 0.485932, 0.558013}, {0.156270, 0.489624, 0.557936}, {0.154815, 0.493313, 0.557840}, {0.153364, 0.497000, 0.557724}, {0.151918, 0.500685, 0.557587}, {0.150476, 0.504369, 0.557430}, {0.149039, 0.508051, 0.557250}, {0.147607, 0.511733, 0.557049}, {0.146180, 0.515413, 0.556823}, {0.144759, 0.519093, 0.556572}, {0.143343, 0.522773, 0.556295}, {0.141935, 0.526453, 0.555991}, {0.140536, 0.530132, 0.555659}, {0.139147, 0.533812, 0.555298}, {0.137770, 0.537492, 0.554906}, {0.136408, 0.541173, 0.554483}, {0.135066, 0.544853, 0.554029}, {0.133743, 0.548535, 0.553541}, {0.132444, 0.552216, 0.553018}, {0.131172, 0.555899, 0.552459}, {0.129933, 0.559582, 0.551864}, {0.128729, 0.563265, 0.551229}, {0.127568, 0.566949, 0.550556}, {0.126453, 0.570633, 0.549841}, {0.125394, 0.574318, 0.549086}, {0.124395, 0.578002, 0.548287}, {0.123463, 0.581687, 0.547445}, {0.122606, 0.585371, 0.546557}, {0.121831, 0.589055, 0.545623}, {0.121148, 0.592739, 0.544641}, {0.120565, 0.596422, 0.543611}, {0.120092, 0.600104, 0.542530}, {0.119738, 0.603785, 0.541400}, {0.119512, 0.607464, 0.540218}, {0.119423, 0.611141, 0.538982}, {0.119483, 0.614817, 0.537692}, {0.119699, 0.618490, 0.536347}, {0.120081, 0.622161, 0.534946}, {0.120638, 0.625828, 0.533488}, {0.121380, 0.629492, 0.531973}, {0.122312, 0.633153, 0.530398}, {0.123444, 0.636809, 0.528763}, {0.124780, 0.640461, 0.527068}, {0.126326, 0.644107, 0.525311}, {0.128087, 0.647749, 0.523491}, {0.130067, 0.651384, 0.521608}, {0.132268, 0.655014, 0.519661}, {0.134692, 0.658636, 0.517649}, {0.137339, 0.662252, 0.515571}, {0.140210, 0.665859, 0.513427}, {0.143303, 0.669459, 0.511215}, {0.146616, 0.673050, 0.508936}, {0.150148, 0.676631, 0.506589}, {0.153894, 0.680203, 0.504172}, {0.157851, 0.683765, 0.501686}, {0.162016, 0.687316, 0.499129}, {0.166383, 0.690856, 0.496502}, {0.170948, 0.694384, 0.493803}, {0.175707, 0.697900, 0.491033}, {0.180653, 0.701402, 0.488189}, {0.185783, 0.704891, 0.485273}, {0.191090, 0.708366, 0.482284}, {0.196571, 0.711827, 0.479221}, {0.202219, 0.715272, 0.476084}, {0.208030, 0.718701, 0.472873}, {0.214000, 0.722114, 0.469588}, {0.220124, 0.725509, 0.466226}, {0.226397, 0.728888, 0.462789}, {0.232815, 0.732247, 0.459277}, {0.239374, 0.735588, 0.455688}, {0.246070, 0.738910, 0.452024}, {0.252899, 0.742211, 0.448284}, {0.259857, 0.745492, 0.444467}, {0.266941, 0.748751, 0.440573}, {0.274149, 0.751988, 0.436601}, {0.281477, 0.755203, 0.432552}, {0.288921, 0.758394, 0.428426}, {0.296479, 0.761561, 0.424223}, {0.304148, 0.764704, 0.419943}, {0.311925, 0.767822, 0.415586}, {0.319809, 0.770914, 0.411152}, {0.327796, 0.773980, 0.406640}, {0.335885, 0.777018, 0.402049}, {0.344074, 0.780029, 0.397381}, {0.352360, 0.783011, 0.392636}, {0.360741, 0.785964, 0.387814}, {0.369214, 0.788888, 0.382914}, {0.377779, 0.791781, 0.377939}, {0.386433, 0.794644, 0.372886}, {0.395174, 0.797475, 0.367757}, {0.404001, 0.800275, 0.362552}, {0.412913, 0.803041, 0.357269}, {0.421908, 0.805774, 0.351910}, {0.430983, 0.808473, 0.346476}, {0.440137, 0.811138, 0.340967}, {0.449368, 0.813768, 0.335384}, {0.458674, 0.816363, 0.329727}, {0.468053, 0.818921, 0.323998}, {0.477504, 0.821444, 0.318195}, {0.487026, 0.823929, 0.312321}, {0.496615, 0.826376, 0.306377}, {0.506271, 0.828786, 0.300362}, {0.515992, 0.831158, 0.294279}, {0.525776, 0.833491, 0.288127}, {0.535621, 0.835785, 0.281908}, {0.545524, 0.838039, 0.275626}, {0.555484, 0.840254, 0.269281}, {0.565498, 0.842430, 0.262877}, {0.575563, 0.844566, 0.256415}, {0.585678, 0.846661, 0.249897}, {0.595839, 0.848717, 0.243329}, {0.606045, 0.850733, 0.236712}, {0.616293, 0.852709, 0.230052}, {0.626579, 0.854645, 0.223353}, {0.636902, 0.856542, 0.216620}, {0.647257, 0.858400, 0.209861}, {0.657642, 0.860219, 0.203082}, {0.668054, 0.861999, 0.196293}, {0.678489, 0.863742, 0.189503}, {0.688944, 0.865448, 0.182725}, {0.699415, 0.867117, 0.175971}, {0.709898, 0.868751, 0.169257}, {0.720391, 0.870350, 0.162603}, {0.730889, 0.871916, 0.156029}, {0.741388, 0.873449, 0.149561}, {0.751884, 0.874951, 0.143228}, {0.762373, 0.876424, 0.137064}, {0.772852, 0.877868, 0.131109}, {0.783315, 0.879285, 0.125405}, {0.793760, 0.880678, 0.120005}, {0.804182, 0.882046, 0.114965}, {0.814576, 0.883393, 0.110347}, {0.824940, 0.884720, 0.106217}, {0.835270, 0.886029, 0.102646}, {0.845561, 0.887322, 0.099702}, {0.855810, 0.888601, 0.097452}, {0.866013, 0.889868, 0.095953}, {0.876168, 0.891125, 0.095250}, {0.886271, 0.892374, 0.095374}, {0.896320, 0.893616, 0.096335}, {0.906311, 0.894855, 0.098125}, {0.916242, 0.896091, 0.100717}, {0.926106, 0.897330, 0.104071}, {0.935904, 0.898570, 0.108131}, {0.945636, 0.899815, 0.112838}, {0.955300, 0.901065, 0.118128}, {0.964894, 0.902323, 0.123941}, {0.974417, 0.903590, 0.130215}, {0.983868, 0.904867, 0.136897}, {0.993248, 0.906157, 0.143936}, }; view-0.1.00/src/draw.c000066400000000000000000000235741323370402200144410ustar00rootroot00000000000000/* Copyright 2015-2016. Martin Uecker. * All rights reserved. Use of this source code is governed by * a BSD-style license which can be found in the LICENSE file. * * Author: * 2015-2016 Martin Uecker */ #include #include #include #include "misc/misc.h" #include "draw.h" #include "colormaps.inc" // multind.h #define MD_BIT(x) (1u << (x)) #define MD_IS_SET(x, y) ((x) & MD_BIT(y)) #define MD_CLEAR(x, y) ((x) & ~MD_BIT(y)) #define MD_SET(x, y) ((x) | MD_BIT(y)) static double clamp(double a, double b, double x) { return (x < a) ? a : ((x > b) ? b : x); } static double window(double a, double b, double x) { return clamp(0., 1., (x - a) / (b - a)); } static void trans_magnitude(double rgb[3], double a, double b, complex double value) { double magn = window(a, b, cabs(value)); rgb[0] *= magn; rgb[1] *= magn; rgb[2] *= magn; } static void trans_magnitude_viridis(double rgb[3], double a, double b, complex double value) { double magn = window(a, b, cabs(value)); // since window returns nonsense (NaN) if a == b, and since casting nonsense to int // and using it as an array subscript is bound to lead to segfaults, // we catch that case here if ( isfinite(magn) ) { int subscript = magn*255.; rgb[0] *= viridis[subscript][0]; rgb[1] *= viridis[subscript][1]; rgb[2] *= viridis[subscript][2]; } } static void trans_real(double rgb[3], double a, double b, complex double value) { rgb[0] *= window(a, b, +creal(value)); rgb[1] *= window(a, b, -creal(value)); rgb[2] *= 0.; } static void trans_phase(double rgb[3], double a, double b, complex double value) { UNUSED(a); UNUSED(b); rgb[0] *= (1. + sin(carg(value) + 0. * 2. * M_PI / 3.)) / 2.; rgb[1] *= (1. + sin(carg(value) + 1. * 2. * M_PI / 3.)) / 2.; rgb[2] *= (1. + sin(carg(value) + 2. * 2. * M_PI / 3.)) / 2.; } static void trans_phase_MYGBM(double rgb[3], double a, double b, complex double value) { UNUSED(a); UNUSED(b); double arg = carg(value); if (isfinite(arg)) { int subscript = (arg+M_PI)/2./M_PI*255.; assert( (0 <= subscript) && (subscript <=255) ); rgb[0] *= cyclic_mygbm[subscript][0]; rgb[1] *= cyclic_mygbm[subscript][1]; rgb[2] *= cyclic_mygbm[subscript][2]; } } static void trans_complex(double rgb[3], double a, double b, complex double value) { trans_magnitude(rgb, a, b, value); trans_phase(rgb, a, b, value); } static void trans_complex_MYGBM(double rgb[3], double a, double b, complex double value) { trans_magnitude(rgb, a, b, value); trans_phase_MYGBM(rgb, a, b, value); } static void trans_flow(double rgb[3], double a, double b, complex double value) { trans_magnitude(rgb, a, b, value); double pha = clamp(-1., 1., carg(value) / M_PI); rgb[0] *= (1. + pha) / 2.; rgb[1] *= (1. - fabs(pha)) / 2.; rgb[2] *= (1. - pha) / 2.; } // we could move in the multiplication with the factor // as an extra argument which could save time static complex float int_nlinear(int N, const float x[N], const long strs[N], const complex float* in) { return (0 == N) ? in[0] : ( (1. - x[N - 1]) * int_nlinear(N - 1, x, strs, in + 0) + x[N - 1] * int_nlinear(N - 1, x, strs, in + strs[N - 1])); } static complex float int_nearest(int N, const float x[N], const long strs[N], const complex float* in) { size_t offs = 0; for (int i = 0; i < N; ++i) offs += round(x[i]) * strs[i]; return *(in + offs); } complex float sample(int N, const float pos[N], const long dims[N], const long strs[N], enum interp_t interpolation, const complex float* in) { float rem[N]; int div[N]; int D = 0; long strs2[N]; // 0 1. [0 1] dims 2 for (int i = 0; i < N; i++) { div[i] = 0; rem[i] = 0.; if (dims[i] > 1) { float xrem = 0.; // values outside of valid range set to the edge values if (pos[i] < 0.) { div[i] = 0.; } else if (pos[i] > (dims[i] - 1)) { div[i] = dims[i] - 1; } else { div[i] = truncf(pos[i]); xrem = pos[i] - truncf(pos[i]); } if (xrem != 0.) { strs2[D] = strs[i] / sizeof(complex float); rem[D++] = xrem; } if ((div[i] < 0) || (div[i] >= dims[i]) || ((div[i] >= dims[i] - 1) && (xrem > 0.))) return 0.; } } long off0 = 0; for (int i = 0; i < N; i++) off0 += div[i] * strs[i]; switch (interpolation) { case NLINEAR: return int_nlinear(D, rem, strs2, (const complex float*)(((char*)in) + off0)); case NEAREST: return int_nearest(D, rem, strs2, (const complex float*)(((char*)in) + off0)); default: assert(0); } } // The idea is the following: // samples sit in the middle of their pixels, so for even zoom factors, // the original values are between adjacent pixels, with pixels outside // of the valid range (negative pos2 and pos2 greater than dim-1) set to the // corresponding values. Therefore we need to start the pos2 array at negative // positions. extern void resample(int X, int Y, long str, complex float* buf, int N, const double pos[N], const double dx[N], const double dy[N], const long dims[N], const long strs[N], enum interp_t interpolation, const complex float* in) { for (int x = 0; x < X; x++) { for (int y = 0; y < Y; y++) { float pos2[N]; for (int i = 0; i < N; i++) { // start is only != 0 if dx or dy are != 0. // Further, for negative dx/dy, it needs the same sign. // ....0.......1.... d (|d| - 1.) / 2. // |---*---|---*---| 1.00 -> -0.000 // |-*-|-*-|-*-|-*-| 0.50 -> -0.250 // |*|*|*|*|*|*|*|*| 0.25 -> -0.375 double start = - (dx[i] != 0.) * copysign((fabs(dx[i]) - 1.) / 2., dx[i]) - (dy[i] != 0.) * copysign((fabs(dy[i]) - 1.) / 2., dy[i]); pos2[i] = pos[i] + start + x * dx[i] + y * dy[i]; } buf[str * y + x] = sample(N, pos2, dims, strs, interpolation, in); } } } extern void draw(int X, int Y, int rgbstr, unsigned char (*rgbbuf)[Y][rgbstr / 4][4], enum mode_t mode, float scale, float winlow, float winhigh, float phrot, long str, const complex float* buf) { for (int x = 0; x < X; x++) { for (int y = 0; y < Y; y++) { complex float val = scale * buf[str * y + x]; val *= cexpf(1.i * phrot); double rgb[3] = { 1., 1., 1. }; switch (mode) { case MAGN: trans_magnitude(rgb, winlow, winhigh, val); break; case MAGN_VIRIDS: trans_magnitude_viridis(rgb, winlow, winhigh, val); break; case PHSE: trans_phase(rgb, winlow, winhigh, val); break; case PHSE_MYGBM: trans_phase_MYGBM(rgb, winlow, winhigh, val); break; case CMPL: trans_complex(rgb, winlow, winhigh, val); break; case CMPL_MYGBM: trans_complex_MYGBM(rgb, winlow, winhigh, val); break; case REAL: trans_real(rgb, winlow, winhigh, val); break; case FLOW: trans_flow(rgb, winlow, winhigh, val); break; default: assert(0); } (*rgbbuf)[y][x][0] = 255. * rgb[2]; (*rgbbuf)[y][x][1] = 255. * rgb[1]; (*rgbbuf)[y][x][2] = 255. * rgb[0]; (*rgbbuf)[y][x][3] = 255.; } } } void update_buf(long xdim, long ydim, int N, const long dims[N], const long strs[N], const long pos[N], enum flip_t flip, enum interp_t interpolation, double xzoom, double yzoom, long rgbw, long rgbh, const complex float* data, complex float* buf) { double dpos[N]; for (int i = 0; i < N; i++) dpos[i] = pos[i]; dpos[xdim] = 0.; dpos[ydim] = 0.; double dx[N]; for (int i = 0; i < N; i++) dx[i] = 0.; double dy[N]; for (int i = 0; i < N; i++) dy[i] = 0.; dx[xdim] = 1.; dy[ydim] = 1.; if ((XY == flip) || (XO == flip)) { dpos[xdim] = dims[xdim] - 1; dx[xdim] *= -1.; } if ((XY == flip) || (OY == flip)) { dpos[ydim] = dims[ydim] - 1; dy[ydim] *= -1.; } dx[xdim] = dx[xdim] / xzoom; dy[ydim] = dy[ydim] / yzoom; resample(rgbw, rgbh, rgbw, buf, N, dpos, dx, dy, dims, strs, interpolation, data); } // Get dimension specifier for filename extern char* get_spec(int i) { switch(i) { case 0: return "x"; break; case 1: return "y"; break; case 2: return "z"; break; case 3: return "coil"; break; case 4: return "map"; break; case 5: return "n"; break; case 6: return "o"; break; case 7: return "p"; break; case 8: return "q"; break; case 9: return "slice"; break; case 10: return "frame"; break; case 11: return "s"; break; case 12: return "t"; break; case 13: return "u"; break; case 14: return "v"; break; case 15: return "w"; break; default: error("Invalid dimension!"); } return ""; } const char color_white[3] = { 255, 255, 255 }; const char color_blue[3] = { 255, 0, 0 }; const char color_red[3] = { 0, 0, 255 }; extern void draw_line(int X, int Y, int rgbstr, unsigned char (*rgbbuf)[Y][rgbstr / 4][4], float x0, float y0, float x1, float y1, const char (*color)[3]) { float stepx = x1 - x0; float stepy = y1 - y0; float max = 1.44 * sqrtf(powf(stepx, 2.) + powf(stepy, 2.)); stepx /= max; stepy /= max; for (unsigned int i = 0; i < max; i++) { int xi = (int)roundf(x0 + i * stepx); int yi = (int)roundf(y0 + i * stepy); if ((0 <= xi) && (xi < X) && (0 <= yi) && (yi < Y)) { (*rgbbuf)[yi][xi][0] = (*color)[0]; (*rgbbuf)[yi][xi][1] = (*color)[1]; (*rgbbuf)[yi][xi][2] = (*color)[2]; } } } extern void draw_grid(int X, int Y, int rgbstr, unsigned char (*rgbbuf)[Y][rgbstr / 4][4], const float (*coord)[4][2], int divs, const char (*color)[3]) { for (int i = 0; i <= divs; i++) { for (int j = 0; j <= divs; j++) { float x0 = (*coord)[0][0] + i * ((*coord)[1][0] - (*coord)[0][0]) / divs; float y0 = (*coord)[0][1] + i * ((*coord)[1][1] - (*coord)[0][1]) / divs; float x1 = (*coord)[2][0] + i * ((*coord)[3][0] - (*coord)[2][0]) / divs; float y1 = (*coord)[2][1] + i * ((*coord)[3][1] - (*coord)[2][1]) / divs; draw_line(X, Y, rgbstr, rgbbuf, x0, y0, x1, y1, color); x0 = (*coord)[0][0] + i * ((*coord)[2][0] - (*coord)[0][0]) / divs; y0 = (*coord)[0][1] + i * ((*coord)[2][1] - (*coord)[0][1]) / divs; x1 = (*coord)[1][0] + i * ((*coord)[3][0] - (*coord)[1][0]) / divs; y1 = (*coord)[1][1] + i * ((*coord)[3][1] - (*coord)[1][1]) / divs; draw_line(X, Y, rgbstr, rgbbuf, x0, y0, x1, y1, color); } } } view-0.1.00/src/draw.h000066400000000000000000000026641323370402200144430ustar00rootroot00000000000000 #include enum mode_t { MAGN, MAGN_VIRIDS, CMPL, CMPL_MYGBM, PHSE, PHSE_MYGBM, REAL, FLOW }; enum flip_t { OO, XO, OY, XY }; enum interp_t { NLINEAR, NEAREST }; extern complex float sample(int N, const float pos[N], const long dims[N], const long strs[N], enum interp_t interpolation, const complex float* in); extern void resample(int X, int Y, long str, complex float* buf, int N, const double pos[N], const double dx[N], const double dy[N], const long dims[N], const long strs[N], enum interp_t interpolation, const complex float* in); extern void draw(int X, int Y, int rgbstr, unsigned char (*rgbbuf)[Y][rgbstr / 4][4], enum mode_t mode, float scale, float winlow, float winhigh, float phrot, long str, const complex float* buf); extern void update_buf(long xdim, long ydim, int N, const long dims[N], const long strs[N], const long pos[N], enum flip_t flip, enum interp_t interpolation, double xzoom, double yzoom, long rgbw, long rgbh, const complex float* data, complex float* buf); extern char* get_spec(int i); extern void draw_line(int X, int Y, int rgbstr, unsigned char (*rgbbuf)[Y][rgbstr / 4][4], float x0, float y0, float x1, float y1, const char (*color)[3]); extern void draw_grid(int X, int Y, int rgbstr, unsigned char (*rgbbuf)[Y][rgbstr / 4][4], const float (*coord)[4][2], int divs, const char (*color)[3]); extern const char color_white[3]; extern const char color_red[3]; extern const char color_blue[3]; view-0.1.00/src/main.c000066400000000000000000000031341323370402200144160ustar00rootroot00000000000000/* Copyright 2015-2016. Martin Uecker. * All rights reserved. Use of this source code is governed by * a BSD-style license which can be found in the LICENSE file. * * Author: * 2015-2016 Martin Uecker */ #include #include #include #undef MAX #undef MIN #include "misc/misc.h" #include "misc/mmio.h" #include "misc/opts.h" #include "view.h" static const char usage_str[] = " ..."; static const char help_str[] = "View images."; int main(int argc, char* argv[]) { gtk_init(&argc, &argv); const struct opt_s opts[] = { }; cmdline(&argc, argv, 1, 100, usage_str, help_str, ARRAY_SIZE(opts), opts); struct view_s* v = NULL; for (int i = 1; i < argc; i++) { long dims[DIMS]; /* * If the filename ends in ".hdr", ".cfl" or just "." (from * tab-completion), just replace the "." with a \0-character. * * Ignoring '.hdr' and '.cfl' is useful since now this viewer * can be set as the default program to open these files from * a file manager. */ char* dot = strrchr(argv[i], '.'); if ((NULL != dot) && ( !strcmp(dot, ".cfl") || !strcmp(dot, ".hdr") || !strcmp(dot, "."))) *dot = '\0'; complex float* x = load_cfl(argv[i], DIMS, dims); // FIXME: we never delete them struct view_s* v2 = window_new(argv[i], NULL, dims, x); // If multiple files are passed on the commandline, add them to window // list. This enables sync of windowing and so on... if (NULL != v) { window_connect_sync(v, v2); } else { v = v2; } } gtk_main(); return 0; } view-0.1.00/src/view.c000066400000000000000000000474251323370402200144570ustar00rootroot00000000000000/* Copyright 2015-2016. Martin Uecker. * All rights reserved. Use of this source code is governed by * a BSD-style license which can be found in the LICENSE file. * * Author: * 2015-2016 Martin Uecker */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #undef MAX #undef MIN #include "num/multind.h" #include "misc/misc.h" #include "misc/png.h" #include "misc/debug.h" #include "draw.h" #include "view.h" #ifndef DIMS #define DIMS 16 #endif #define STRINGIFY(x) # x const char* viewer_gui = #include "viewer.inc" ; struct view_s { struct view_s* next; struct view_s* prev; bool sync; bool cross_hair; const char* name; // geometry long* pos; //[DIMS]; int xdim; int ydim; double xzoom; double yzoom; enum flip_t flip; bool transpose; // representation enum mode_t mode; double winhigh; double winlow; double phrot; double max; enum interp_t interpolation; complex float* buf; cairo_surface_t* source; // rgb buffer int rgbh; int rgbw; int rgbstr; unsigned char* rgb; bool invalid; bool rgb_invalid; // data long dims[DIMS]; long strs[DIMS]; const complex float* data; // widgets GtkComboBox* gtk_mode; GtkComboBox* gtk_flip; GtkComboBox* gtk_interp; GtkWidget* gtk_drawingarea; GtkWidget* gtk_viewport; GtkAdjustment* gtk_winlow; GtkAdjustment* gtk_winhigh; GtkAdjustment* gtk_zoom; GtkAdjustment* gtk_aniso; GtkEntry* gtk_entry; GtkToggleToolButton* gtk_transpose; GtkToggleToolButton* gtk_fit; GtkAdjustment* gtk_posall[DIMS]; GtkCheckButton* gtk_checkall[DIMS]; GtkWidget *dialog; // Save dialog GtkFileChooser *chooser; // Save dialog GtkWindow *window; // windowing int lastx; int lasty; }; static void add_text(cairo_surface_t* surface, int x, int y, int size, const char* text) { cairo_t* cr = cairo_create(surface); cairo_set_source_rgb(cr, 1., 1., 1.); PangoLayout* layout = pango_cairo_create_layout(cr); pango_layout_set_text(layout, text, -1); PangoFontDescription* desc = pango_font_description_new(); pango_font_description_set_family(desc, "sans"); pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD); pango_font_description_set_absolute_size(desc, size * PANGO_SCALE); pango_layout_set_font_description(layout, desc); pango_font_description_free(desc); int w = 0; int h = 0; pango_layout_get_pixel_size(layout, &w, &h); cairo_move_to(cr, (x >= 0) ? x : -(x + (double)w), (y >= 0) ? y : -(y + (double)h)); pango_cairo_show_layout(cr, layout); g_object_unref(layout); cairo_destroy(cr); } extern gboolean update_view(struct view_s* v) { if (NULL != v->source) cairo_surface_destroy(v->source); v->rgbw = v->dims[v->xdim] * v->xzoom; v->rgbh = v->dims[v->ydim] * v->yzoom; v->rgbstr = 4 * v->rgbw; v->rgb = realloc(v->rgb, v->rgbh * v->rgbstr); v->buf = realloc(v->buf, v->rgbh * v->rgbw * sizeof(complex float)); v->source = cairo_image_surface_create_for_data(v->rgb, CAIRO_FORMAT_RGB24, v->rgbw, v->rgbh, v->rgbstr); // trigger redraw gtk_widget_set_size_request(v->gtk_drawingarea, v->rgbw, v->rgbh); gtk_widget_queue_draw(v->gtk_drawingarea); return FALSE; } extern gboolean fit_callback(GtkWidget *widget, gpointer data) { UNUSED(widget); struct view_s* v = data; gboolean flag = gtk_toggle_tool_button_get_active(v->gtk_fit); if (!flag) return FALSE; double aniso = gtk_adjustment_get_value(v->gtk_aniso); GtkAllocation alloc; gtk_widget_get_allocation(v->gtk_viewport, &alloc); double xz = (double)(alloc.width - 5) / (double)v->dims[v->xdim]; double yz = (double)(alloc.height - 5) / (double)v->dims[v->ydim]; if (yz > xz / aniso) yz = xz / aniso; // aniso gtk_adjustment_set_value(v->gtk_zoom, yz); return FALSE; } extern gboolean configure_callback(GtkWidget *widget, GdkEvent* event, gpointer data) { UNUSED(event); return fit_callback(widget, data); } extern void view_setpos(struct view_s* v, unsigned int flags, const long pos[DIMS]) { for (unsigned int i = 0; i < DIMS; i++) { if (MD_IS_SET(flags, i)) { gtk_adjustment_set_value(v->gtk_posall[i], pos[i]); for (struct view_s* v2 = v->next; v2 != v; v2 = v2->next) if (v->sync && v2->sync) gtk_adjustment_set_value(v2->gtk_posall[i], pos[i]); } } } extern void view_refresh(struct view_s* v) { v->invalid = true; long size = md_calc_size(DIMS, v->dims); double max = 0.; for (long j = 0; j < size; j++) if (max < cabsf(v->data[j])) max = cabsf(v->data[j]); if (0. == max) max = 1.; v->max = max; update_view(v); } extern gboolean refresh_callback(GtkWidget *widget, gpointer data) { UNUSED(widget); view_refresh(data); return FALSE; } extern gboolean geom_callback(GtkWidget *widget, gpointer data) { UNUSED(widget); struct view_s* v = data; for (int j = 0; j < DIMS; j++) { v->pos[j] = gtk_adjustment_get_value(v->gtk_posall[j]); bool check = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(v->gtk_checkall[j])); if (!check) continue; if (1 == v->dims[j]) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(v->gtk_checkall[j]), FALSE); else if ((j != v->xdim) && (j != v->ydim)) { for (int i = 0; i < DIMS; i++) { if (v->xdim == (DIMS + j - i) % DIMS) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(v->gtk_checkall[v->xdim]), FALSE); v->xdim = j; break; } if (v->ydim == (DIMS + j - i) % DIMS) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(v->gtk_checkall[v->ydim]), FALSE); v->ydim = j; break; } } } } gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(v->gtk_checkall[v->xdim]), TRUE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(v->gtk_checkall[v->ydim]), TRUE); double zoom = gtk_adjustment_get_value(v->gtk_zoom); double aniso = gtk_adjustment_get_value(v->gtk_aniso); v->xzoom = zoom * aniso; v->yzoom = zoom; v->flip = gtk_combo_box_get_active(v->gtk_flip); v->interpolation = gtk_combo_box_get_active(v->gtk_interp); v->transpose = gtk_toggle_tool_button_get_active(v->gtk_transpose); if (v->transpose) { if (v->xdim < v->ydim) { int swp = v->xdim; v->xdim = v->ydim; v->ydim = swp; } } else { if (v->xdim > v->ydim) { int swp = v->xdim; v->xdim = v->ydim; v->ydim = swp; } } v->lastx = -1; v->lasty = -1; v->invalid = true; update_view(v); return FALSE; } extern gboolean window_callback(GtkWidget *widget, gpointer data) { UNUSED(widget); struct view_s* v = data; v->mode = gtk_combo_box_get_active(v->gtk_mode); v->winlow = gtk_adjustment_get_value(v->gtk_winlow); v->winhigh = gtk_adjustment_get_value(v->gtk_winhigh); for (struct view_s* v2 = v->next; v2 != v; v2 = v2->next) { if (v->sync && v2->sync) { gtk_adjustment_set_value(v2->gtk_winlow, v->winlow); gtk_adjustment_set_value(v2->gtk_winhigh, v->winhigh); gtk_combo_box_set_active(v2->gtk_mode, v->mode); } } v->rgb_invalid = true; update_view(v); return FALSE; } static void update_buf_view(struct view_s* v) { update_buf(v->xdim, v->ydim, DIMS, v->dims, v->strs, v->pos, v->flip, v->interpolation, v->xzoom, v->yzoom, v->rgbw, v->rgbh, v->data, v->buf); } extern gboolean save_callback(GtkWidget *widget, gpointer data) { UNUSED(widget); struct view_s* v = data; // Prepare output filename unsigned int bufsize = 255; char name[bufsize]; char* cur = name; const char* end = name + bufsize; cur += snprintf(cur, end - cur, "%s", v->name); char dir[bufsize]; strncpy(dir, v->name, bufsize); for ( int i = 0; i < DIMS; i++) { if ( v->dims[i] != 1 && i != v->xdim && i != v->ydim ){ cur += snprintf(cur, end - cur, "_%s_%04ld", get_spec(i), v->pos[i]); } } cur += snprintf(cur, end - cur, ".png"); v->dialog = gtk_file_chooser_dialog_new ("Save File", v->window, GTK_FILE_CHOOSER_ACTION_SAVE, "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL); v->chooser = GTK_FILE_CHOOSER (v->dialog); gtk_file_chooser_set_current_name (v->chooser, basename(name)); // Outputfolder = Inputfolder gtk_file_chooser_set_current_folder (v->chooser, dirname(dir)); gtk_file_chooser_set_do_overwrite_confirmation (v->chooser, TRUE); gint res = gtk_dialog_run (GTK_DIALOG (v->dialog)); if (res == GTK_RESPONSE_ACCEPT) { // export single image char *filename = gtk_file_chooser_get_filename (v->chooser); if (CAIRO_STATUS_SUCCESS != cairo_surface_write_to_png(v->source, filename)) gtk_entry_set_text(v->gtk_entry, "Error: writing image file.\n"); gtk_entry_set_text(v->gtk_entry, "Saved!"); g_free (filename); } gtk_widget_destroy (v->dialog); return FALSE; } extern gboolean save_movie_callback(GtkWidget *widget, gpointer data) { UNUSED(widget); struct view_s* v = data; unsigned int bufsize = 255; char dir[bufsize]; strncpy(dir, v->name, bufsize); int frame_dim = 10; v->dialog = gtk_file_chooser_dialog_new("Export movie to folder", v->window, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, "Cancel", GTK_RESPONSE_CANCEL, "Export", GTK_RESPONSE_ACCEPT, NULL); v->chooser = GTK_FILE_CHOOSER(v->dialog); // Outputfolder = Inputfolder gtk_file_chooser_set_current_folder(v->chooser, dirname(dir)); gint res = gtk_dialog_run (GTK_DIALOG (v->dialog)); if (res == GTK_RESPONSE_ACCEPT) { char *chosen_dir = gtk_file_chooser_get_filename(v->chooser); char output_name[bufsize]; for (unsigned int f = 0; f < v->dims[frame_dim]; f++) { v->pos[frame_dim] = f; update_buf_view(v); draw(v->rgbw, v->rgbh, v->rgbstr, (unsigned char(*)[v->rgbw][v->rgbstr / 4][4])v->rgb, v->mode, 1. / v->max, v->winlow, v->winhigh, v->phrot, v->rgbw, v->buf); char suff[16]; snprintf(suff, 16, "/mov-%04d.png", f); strncpy(output_name, chosen_dir, bufsize - 16); strncat(output_name, suff, 16); if (CAIRO_STATUS_SUCCESS != cairo_surface_write_to_png(v->source, output_name)) gtk_entry_set_text(v->gtk_entry, "Error: writing image file.\n"); } g_free(chosen_dir); gtk_entry_set_text(v->gtk_entry, "Movie exported."); } gtk_widget_destroy (v->dialog); return FALSE; } struct xy_s { float x; float y; }; static struct xy_s pos2screen(const struct view_s* v, /*const+*/float (*pos)[DIMS]) { float x = (*pos)[v->xdim]; float y = (*pos)[v->ydim]; if ((XY == v->flip) || (XO == v->flip)) x = v->dims[v->xdim] - 1 - x; if ((XY == v->flip) || (OY == v->flip)) y = v->dims[v->ydim] - 1 - y; x *= v->xzoom; y *= v->yzoom; return (struct xy_s){ x, y }; } static void screen2pos(const struct view_s* v, float (*pos)[DIMS], struct xy_s xy) { for (unsigned int i = 0; i < DIMS; i++) (*pos)[i] = v->pos[i]; float x = xy.x / v->xzoom; float y = xy.y / v->yzoom; if ((XY == v->flip) || (XO == v->flip)) x = v->dims[v->xdim] - 1 - x; if ((XY == v->flip) || (OY == v->flip)) y = v->dims[v->ydim] - 1 - y; (*pos)[v->xdim] = x; (*pos)[v->ydim] = y; } extern gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) { UNUSED(widget); struct view_s* v = data; if (v->invalid) { update_buf_view(v); v->invalid = false; v->rgb_invalid = true; } if (v->rgb_invalid) { draw(v->rgbw, v->rgbh, v->rgbstr, (unsigned char(*)[v->rgbw][v->rgbstr / 4][4])v->rgb, v->mode, 1. / v->max, v->winlow, v->winhigh, v->phrot, v->rgbw, v->buf); v->rgb_invalid = false; } // add_text(v->source, 3, 3, 10, v->name); if (v->cross_hair) { float posi[DIMS]; for (unsigned int i = 0; i < DIMS; i++) posi[i] = v->pos[i]; struct xy_s xy = pos2screen(v, &posi); draw_line(v->rgbw, v->rgbh, v->rgbstr, (unsigned char (*)[v->rgbw][v->rgbstr / 4][4])v->rgb, 0, (int)xy.y, v->rgbw - 1, (int)xy.y, (v->xdim > v->ydim) ? &color_red : &color_blue); draw_line(v->rgbw, v->rgbh, v->rgbstr, (unsigned char (*)[v->rgbw][v->rgbstr / 4][4])v->rgb, (int)xy.x, 0, (int)xy.x, v->rgbh - 1, (v->xdim < v->ydim) ? &color_red : &color_blue); // float coords[4][2] = { { 0, 0 }, { 100, 0 }, { 0, 100 }, { 100, 100 } }; // draw_grid(v->rgbw, v->rgbh, v->rgbstr, (unsigned char (*)[v->rgbw][v->rgbstr / 4][4])v->rgb, &coords, 4, &color_white); } cairo_set_source_surface(cr, v->source, 0, 0); cairo_paint(cr); return FALSE; } struct view_s* create_view(const char* name, const long pos[DIMS], const long dims[DIMS], const complex float* data) { long sq_dims[2] = { 0 }; int l = 0; for (int i = 0; (i < DIMS) && (l < 2); i++) if (1 != dims[i]) sq_dims[l++] = i; assert(2 == l); struct view_s* v = xmalloc(sizeof(struct view_s)); v->next = v->prev = v; v->sync = true; v->cross_hair = false; v->name = name; v->max = 1.; v->pos = xmalloc(DIMS * sizeof(long)); for (int i = 0; i < DIMS; i++) v->pos[i] = (NULL != pos) ? pos[i] : 0; v->xdim = sq_dims[0]; v->ydim = sq_dims[1]; v->xzoom = 2.; v->yzoom = 2.; v->source = NULL; v->rgb = NULL; v->buf = NULL; md_copy_dims(DIMS, v->dims, dims); md_calc_strides(DIMS, v->strs, dims, sizeof(complex float)); v->data = data; v->winlow = 0.; v->winhigh = 1.; v->phrot = 0.; v->lastx = -1; v->lasty = -1; v->invalid = true; return v; } static void delete_view(struct view_s* v) { v->next->prev = v->prev; v->prev->next = v->next; free(v->buf); free(v->rgb); #if 0 free(v->pos); //free(v); #endif } extern gboolean toggle_sync(GtkToggleButton* button, gpointer data) { UNUSED(button); struct view_s* v = data; v->sync = !v->sync; return FALSE; } static void update_status_bar(struct view_s* v, /*const*/ float (*pos)[DIMS]) { int x2 = (*pos)[v->xdim]; int y2 = (*pos)[v->ydim]; (*pos)[v->xdim] = x2; (*pos)[v->ydim] = y2; complex float val = sample(DIMS, *pos, v->dims, v->strs, v->interpolation, v->data); // FIXME: make sure this matches exactly the pixel char buf[100]; snprintf(buf, 100, "Pos: %03d %03d Magn: %.3e Val: %+.3e%+.3ei Arg: %+.2f", x2, y2, cabsf(val), crealf(val), cimagf(val), cargf(val)); gtk_entry_set_text(v->gtk_entry, buf); } extern void set_position(struct view_s* v, unsigned int dim, unsigned int p) { v->pos[dim] = p; gtk_adjustment_set_value(v->gtk_posall[dim], p); for (struct view_s* v2 = v->next; v2 != v; v2 = v2->next) if (v->sync && v2->sync) gtk_adjustment_set_value(v2->gtk_posall[dim], p); } extern gboolean button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer data) { UNUSED(widget); struct view_s* v = data; struct xy_s xy = { event->x, event->y }; float pos[DIMS]; screen2pos(v, &pos, xy); if (event->button == GDK_BUTTON_PRIMARY) { v->cross_hair = false; v->rgb_invalid = true; update_view(v); } if (event->button == GDK_BUTTON_SECONDARY) { v->cross_hair = true; set_position(v, v->xdim, pos[v->xdim]); set_position(v, v->ydim, pos[v->ydim]); update_status_bar(v, &pos); for (struct view_s* v2 = v->next; v2 != v; v2 = v2->next) if (v->sync && v2->sync) update_status_bar(v2, &pos); } return FALSE; } extern gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) { UNUSED(widget); struct view_s* v = data; int y = event->y; int x = event->x; if (event->state & GDK_BUTTON1_MASK) { if (-1 != v->lastx) { double low = gtk_adjustment_get_value(v->gtk_winlow); double high = gtk_adjustment_get_value(v->gtk_winhigh); low -= (x - v->lastx) / 200.; high -= (y - v->lasty) / 200.; if (high > low) { gtk_adjustment_set_value(v->gtk_winlow, low); gtk_adjustment_set_value(v->gtk_winhigh, high); for (struct view_s* v2 = v->next; v2 != v; v2 = v2->next) { if (v->sync && v2->sync) { gtk_adjustment_set_value(v2->gtk_winlow, low); gtk_adjustment_set_value(v2->gtk_winhigh, high); } } } } v->lastx = x; v->lasty = y; } else { v->lastx = -1; v->lasty = -1; } return FALSE; } extern gboolean show_hide(GtkWidget *widget, GtkCheckButton* button) { gboolean flag = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(button)); (flag ? gtk_widget_show : gtk_widget_hide)(widget); return FALSE; } static int nr_windows = 0; extern gboolean window_close(GtkWidget *widget, GdkEvent* event, gpointer data) { UNUSED(widget); UNUSED(event); struct view_s* v = data; delete_view(v); if (0 == --nr_windows) gtk_main_quit(); return FALSE; } extern struct view_s* window_new(const char* name, const long pos[DIMS], const long dims[DIMS], const complex float* x) { struct view_s* v = create_view(name, pos, dims, x); GtkBuilder* builder = gtk_builder_new(); // gtk_builder_add_from_file(builder, "viewer.ui", NULL); gtk_builder_add_from_string(builder, viewer_gui, -1, NULL); v->gtk_drawingarea = GTK_WIDGET(gtk_builder_get_object(builder, "drawingarea1")); v->gtk_viewport = GTK_WIDGET(gtk_builder_get_object(builder, "scrolledwindow1")); v->gtk_winlow = GTK_ADJUSTMENT(gtk_builder_get_object(builder, "winlow")); v->gtk_winhigh = GTK_ADJUSTMENT(gtk_builder_get_object(builder, "winhigh")); v->gtk_entry = GTK_ENTRY(gtk_builder_get_object(builder, "entry")); PangoFontDescription* desc = pango_font_description_new(); pango_font_description_set_family(desc, "mono"); pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD); pango_font_description_set_absolute_size(desc, 10 * PANGO_SCALE); gtk_widget_override_font(GTK_WIDGET(v->gtk_entry), desc); pango_font_description_free(desc); v->gtk_zoom = GTK_ADJUSTMENT(gtk_builder_get_object(builder, "zoom")); v->gtk_aniso = GTK_ADJUSTMENT(gtk_builder_get_object(builder, "aniso")); v->gtk_mode = GTK_COMBO_BOX(gtk_builder_get_object(builder, "mode")); gtk_combo_box_set_active(v->gtk_mode, 0); v->gtk_flip = GTK_COMBO_BOX(gtk_builder_get_object(builder, "flip")); gtk_combo_box_set_active(v->gtk_flip, 0); v->gtk_interp = GTK_COMBO_BOX(gtk_builder_get_object(builder, "interp")); gtk_combo_box_set_active(v->gtk_interp, 0); v->gtk_transpose = GTK_TOGGLE_TOOL_BUTTON(gtk_builder_get_object(builder, "transpose")); v->gtk_fit = GTK_TOGGLE_TOOL_BUTTON(gtk_builder_get_object(builder, "fit")); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(v->gtk_fit), TRUE); for (int j = 0; j < DIMS; j++) { char pname[10]; snprintf(pname, 10, "pos%02d", j); v->gtk_posall[j] = GTK_ADJUSTMENT(gtk_builder_get_object(builder, pname)); gtk_adjustment_set_upper(v->gtk_posall[j], v->dims[j] - 1); gtk_adjustment_set_value(v->gtk_posall[j], v->pos[j]); snprintf(pname, 10, "check%02d", j); v->gtk_checkall[j] = GTK_CHECK_BUTTON(gtk_builder_get_object(builder, pname)); } gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(v->gtk_checkall[v->xdim]), TRUE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(v->gtk_checkall[v->ydim]), TRUE); #if 0 gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(builder, "toolbar1"))); gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(builder, "toolbar2"))); gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(builder, "entry"))); #endif gtk_builder_connect_signals(builder, v); GtkWindow* window = GTK_WINDOW(gtk_builder_get_object(builder, "window1")); g_object_unref(G_OBJECT(builder)); v->window = window; gtk_window_set_title(window, name); gtk_widget_show(GTK_WIDGET(window)); nr_windows++; // fit_callback(NULL, v); refresh_callback(NULL, v); geom_callback(NULL, v); window_callback(NULL, v); return v; } void window_connect_sync(struct view_s* v, struct view_s* v2) { // add to linked list for sync v2->next = v->next; v->next->prev = v2; v2->prev = v; v->next = v2; window_callback(NULL, v); } struct view_s* view_clone(struct view_s* v, const long pos[DIMS]) { struct view_s* v2 = window_new(v->name, pos, v->dims, v->data); window_connect_sync(v, v2); return v2; } extern gboolean window_clone(GtkWidget *widget, gpointer data) { UNUSED(widget); struct view_s* v = data; view_clone(v, v->pos); return FALSE; } view-0.1.00/src/view.h000066400000000000000000000007011323370402200144460ustar00rootroot00000000000000 #ifndef DIMS #define DIMS 16 #endif struct view_s; extern struct view_s* window_new(const char* name, const long pos[DIMS], const long dims[DIMS], const complex float* x); extern void window_connect_sync(struct view_s* a, struct view_s* b); extern void view_refresh(struct view_s* v); extern void view_setpos(struct view_s* v, unsigned int flags, const long pos[DIMS]); extern struct view_s* view_clone(struct view_s* v, const long pos[DIMS]); view-0.1.00/src/viewer.ui000066400000000000000000001603531323370402200151750ustar00rootroot00000000000000 10 1 0.10000000000000001 1 MAGNITUDE (gray) MAGNITUDE (viridis) COMPLEX COMPLEX (MYGBM) PHASE PHASE (MYGBM) REAL FLOW 00 X0 0Y XY NLIN NEAREST 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 1 0.001 0.050000000000000003 1 0.001 0.050000000000000003 10 2 0.10000000000000001 1 False 600 600 True False vertical True False icons True False True True True True False gtk-new False True True False True False liststore1 0 0 False True True False fit True gtk-zoom-fit False True True False True False start liststore2 0 0 False True True False True False start liststore3 0 0 False True True False transpose True gtk-page-setup False True True False True True True True False gtk-save False True True False True True True True False media-record False True True False Refresh True gtk-refresh False True True False sync True gtk-connect False True False True 0 200 True False 100 True False start 100 True True True start winlow 1 3 3 right False True 100 True False start 100 True True True start winhigh 1 3 3 right False True 100 True False start True True zoom 10 1 right False True 100 True False start True True aniso 10 1 right False True True False False True True False True True True False False True True False showpos True gtk-preferences False True False True 1 True False True True in 100 100 True False True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK True True 0 True end in 150 200 True False end True False True True False 0 True 0 0 1 1 True True 0 pos00 1 0 1 1 True True 0 pos01 1 1 1 1 True True 0 pos02 1 2 1 1 True True 0 pos03 1 3 1 1 True True 0 pos04 1 4 1 1 True True 0 pos05 1 5 1 1 True True 0 pos06 1 6 1 1 True True 0 pos07 1 7 1 1 True True 0 pos08 1 8 1 1 True True 0 pos09 1 9 1 1 True True 0 pos10 1 10 1 1 True True 0 pos11 1 11 1 1 True True 0 pos12 1 12 1 1 True True 0 pos13 1 13 1 1 True True 0 pos14 1 14 1 1 True True 0 pos15 1 15 1 1 True True False 0 True 0 1 1 1 True True False 0 True 0 2 1 1 True True False 0 True 0 3 1 1 True True False 0 True 0 4 1 1 True True False 0 True 0 5 1 1 True True False 0 True 0 6 1 1 True True False 0 True 0 7 1 1 True True False 0 True 0 8 1 1 True True False 0 True 0 9 1 1 True True False 0 True 0 10 1 1 True True False 0 True 0 11 1 1 True True False 0 True 0 12 1 1 True True False 0 True 0 13 1 1 True True False 0 True 0 14 1 1 True True False 0 True 0 15 1 1 False True end 1 True True 2 True True False False True 3 view-0.1.00/viewer.png000066400000000000000000003452151323370402200145570ustar00rootroot00000000000000PNG  IHDRZrnsBIT|d IDATxw|{UW*m6`liސ/@Hޗzu0{Ƹ.[_t;$ْ,>ۙyfgg)@ M߁c3\n/e=֗L cIINf5 . |FVU2$Ij<@ AX =58;>OTU\Y@ g$I) *X4@ AB\ ).wfă:JT hUO@ 34MA%iy:!R }]$"Kx t @h "0t$ I.@^dYV">ǭpԙNYQ )-.@sU"md%"IeN|f2rZcdӹ4 e:6q1fj^*ZDƖAבMV3E74$dG:U/>I bA4n:lڲ%Y1yo}(N7y6u`Ƣ-.$xt]nlT:<(&_Y4CbYӧOpzF-m]#7-. GӴ S  .^Y"c,-EaVz(Lo{m[ȥ{g?'w-ŗŽ?P=w8FΙO6~/Hr_.5ª5_F7&@Q=.㬼W0 .L[Rzb_)} 5/so>.䲫o@r\B=1 Q-hu #)@C? )L=BʕKK^C=t)O1) |#y}{s7c';yBWIQ1㸱vI@)MbXB~}x%̽v!5nLfk)-²sǭ ӻ' ]}#$}l(945TMMRLXMkfN̪p-;o#+3O>w?;UW]Q#٭޽CS=Nxwi Kp1~~ϯHOcǟUȖ4+\t(lv۶WnMvܲ: @rJUl-z]T e/wzگTA!Ǎaæ-,+,fkG@5`\t|/nD6Y˫`ɯW\MS8GW]e|*}\pȲ;1$Z; y,`˶(dfϻ7M֣[gYIdr,8΀ eFR\\ϾdC/'yOd][1~E1M;;y%~g9+7gc\Hzj*GE~Q%v"IJiT.`# c+xꭵhhԪ.Itx} ח ';erz#.W#hztSp4dhL| xz0HIIfάvtw 4S.HYY95̝3 㒩QVb- ʹNJJ2Ӧ\JS$'sf2{|:asww,̚eW/@ƅih8yRFUW\qcXH6d0yV$L²OWbXr>_]8ŷ,vŪu^Gtro@Ç f;jlPSHNL7US9v8k֮k̊->D1%lIHѰѧ{2RS=s7m艓ք&eS _zUD7KhU}ÆföڙQ,Qӧx8!Ǝͺo6p藼3 RRU^@ ɒמ~o'ꅭnhA}?HJ.ҫgwbO=O<'|L.?U62n̅CyLeu c/ŷ.HΒW͖b_3x6m`K&Mje.0'ѧ(HN9&YY$'%2}, #.0v.7*>SjБ'УkE6d0|?YI+؆eۭLu}.Cd]?/VXc0&5ZfKIizpWsocʥ(oO<%p_%R۫;NKK"Ybٲmݺߋd"'; еv%db蚗q/`8N.~9nرm'_hxvY h [z^^'3jaرO6bbT1|t1LifSpY ͌SXq啡~l9Ɓ'~C_5Y &^r !QPX_B6Y8~"?(M-ջW~.?Ci1T|1Ě/,EQ|S-YY{n**$6miz8zTrUTӳikwfæ<̺QQBl5y:CQ$dk|1^#OkK[N:5y]}-,}mndY͋q=kե* ]k_JH4tOm;БccQ%;%Muc+5,fƼQ\R8^\Rs1[#3ܝU%~Z&;=~ܜl;}棷_%;= ՠEd1}#Z^OT⒲=_3j cسw#Oaz@NI;gӦ\ow薻p8ޱIA%}AnN6"]:.PVVAn]rKMTVVMj*:i ]oRvtvD$ Սk̝7tM eSa7Yxu$3x_y ?$͝i<˘>GvdeWst&MYذi++y%IbPŰ1K=誫vDp/dF1FC؆r4DC>TM ǙgkWs\?, quŏqHnВeԡ'JCl:n 2__:weVz̚_SQYI~y?y\ FEV E=پ}';2g |y?-' SN\l1m꥘LWYGiiݺv=OYyS\颠̌tp ArQ%]I>x{)))e̩A5 X|";,k`Yم%+׬qc/"%%Mvw ۓUlB8Y|O|l9Oߊ{3;NM͢Ol޲u_o``#71 Ӄg!rOXהѽ[W}U6v{F"EVZHlyВDa9Յ;\~€5{LrmAC˫|b v[t`8].av׷;DA!f{JU.|'?P{?knzz.U,ZczwOwPRRgӖĻ_Çڒgyik8 7_ c/k?+.p$Xxaeسw|3 #):ɔV:?.|*f͘Z{&eM6|Yzѣ?"lyR!/7Ə =|(:~]r?QbkUq|zy2x@~zbt]g'{Xڕ-YQG$j-ZcEV"sNg:z^bl6:i*cz`SI$YQdB@uhrϗQ,00Ŧ I2ǁq m6 ͋"9)SHf;VF1@ Q(ߖ 43L5$ŐLQq15:rܬt.et ={* ,mz?,_lbx]L:=qP047E%nL,8zN %Yͳv/<=I67x$)aeaL+ ]CuTBB|Njj f$vT@p")i(,ݻd7lhIɤ( r`Gї$>ik}7hʉjȜѾkѬ tkӔF4W_sU2yDN)7V݁l;Q۞(CTrZu ]漳ҥa8›r悔im@;4jh -a赫ڶ/_rJtx2)++c|+LĈinZ$K$7yA{wZlٽwotBSQi Z$K$7yAGZA3"&$hV@ aغs-@ Aw53@ @ A ! -@ "׮a矠}omPWT' ۣ{^t]Cˡev 7fG0LhGjwOiCG#ձIo~5MkM9ZeHeoΧؑ*t;RY;RY"A:@ h!2 5hU:rJGnwlc헎"w^YΝ3rRSSY"AGAh ]((vjDL&lV;ɉȲ2uvבvu̚q={lp t]ZZi>`dee!-#XGvƍ%5AR1rM=jэO pFAQ}z瑑Df4iJaQ!!3=E--ӳgOּ5>6p!3p00ta̘ >|>fNCFF-D|fq`ȔTְ)U`7Kd%\2'3F:bv(Z|Dkƍw}Ʃ*{E>ƣC qxcf#d ȔW/oi IFe>HJRNtvבv>a): +<(X00t0 tujjjZL4 ǟ|ĢonٚI95f^lF}ؗ؄fOЊ>Z%%%37*0v ɃQL2&en U[Z"eگ8y''+33p( W͛rΡGذiLO7aqv|-5)ˈƟef8/WйS' ċe AdbbC=C? ؟ݻ+IV%=--uKCe8!^{ %?҉2x9ɿp*//ym۷oOÆc[HO`]&o]TU>b ֵKxaQ+Wa| \.7Q#ϙMNHH`ؐ}`pX "g߰wۻ~}#~GjG.O?ZԨ,9֢2$t맍aA7Kis\4cƞ7US(q$yi &q\A*DWװ}ǷTWpˢo7d2qCyX78Nn0?g;I闗GII)/.y~~{O [xXpu?3.?}zdeZlݶ̠PdOʕsҫGy~=bct-^yY Uٴu+\<~?v,fs ^qq 兤x/.y>gO3N=?vNo-TՋ,x^08,#fNZV/x9}Fnu Æ7!9LonZgtGkk[eҰ^qswп[N༚=hLq>Ѫu,w> `$Z\M,3ѬZ7\wrƍ͆M[Bҫaƍq-,}M\??GŗPRZnCTdY;nǵdYfЀ|cfϜARbb,+!, @|a/1.X,.IR]/F@QL?]?9Ͽ;o%oԈ Cs8j:_9z~`Ԉ B.&%%4JHINFe***٬ʡGܩ srsrΧ555A|\\Hޥe1p@kvw п1w bo뗀ϸ 6_CZJH-Zpl=,~>S8܏k[L-hٵawi`϶w͂훿'#+N݊2lz /rb<у >9:;.Oâ{.W 2GҳS .酮R}:x^xF,:\?jWՒ$ gK&Md-TTFT4r_s fg3nE,}*ZDHĐih+2Ooq"̜6JJ΢9f)))raZb O=ѣf3g X+׬e/>.NMs7q͋8tv귁HxސO8at6-^b"zmG\|Ӥ_maWPZ|G*ٵmUٿԾص.jjJM;0 $rһ[Gѷ[gd$d$F+ow#0k2.edA+h3NG5& I=HuuuCI5їdRh,%iVݚTLBj?.-M+;[dԎ|V[S\ﯾw Ƞ:`ӖrUWjN# +x}"3md~seрhƪk`ذ)Æ\6{&Yte嚵A+ $$L7LNJbƴg,vVVkٶ!>r%]t!-55`AIYY8NJKKIN;~xG(Ne6>]$c:K_D~~ NbB< Ɗիٰi3ϙ0JJȌQSSfITWB54McgϘSoG0q4-0u N{9Z|W_œ'#ĐѧocOxֵkiE5GnZ?~#owTNV OymD%dgHJIc:$_FRn[dddS?ոyZ?͞,TVVRSSC||@FUFYmasc钗HY_%JЎڕU\q?p^={r$5%3f`Y4#GՓ͟ea(pV|l f={v7[crpV5TN".% 1۷o7+*&).JtMVCNA$FWKMyaOiCct9'xuPGk]y4rW_rIL*pcܲp?.***CyaTU<=S^~zppq-7$ RVV2GqS]]t/@cѬXS& > VMd2sP]}_GyE99x<, EE+a֌i!76ЫWOu2gJEřmђ8]NjbX8u*/Be濰Z| X,\w x<?b 3jh^"mw#G`gPXXL|9EEN}TUp:΢4m)[ogӧ4ֶõ‚vizm~}bMf ;uS]{&!!ϣ?fo>=dÇӯ_?{ɓ'3-yoaJ#xÜN''NWqNӁ(q8us)//o2^IIxY**ʩ(b6pԄ+^n^x#.w߸蔓MAa!o!,\h_S^^,m6 ;rkV{=d#ܵ:"PSSClly}zW0ՂвZ-i_>yjjCH۝u]vC( V(2ζOm 4=At3SƤG̀P$;j5 $%d1|毡ޕ 4=0|`6#1imIq갥iqCkk}dz{A}m8BF:zow0vXĀ:{Fva۶o##[sMD;i+}3z0 ؾ0}mwa(`p`nz~ fanֿw)cY,5gKJJ"11lоy}uyyNVvv!z5x F9QQ ^Ғ }1&8γv}?_Y&#b2d`~m.F"!!1s+++ٴy#_|9\}5(J6 l֘4V}|?uP |?fKē;sE]F 晇 kG74Wgyk}7tݠ_^?=z͈J}>Z.0 PUY7b#|cmg|W_⚫cٚ}c#GdS\ :+x%lIX,$$3dzhd$ E1-ݎlj֕u䲝/tܕ%_4tܬ4W+// Y(%hyYuq;\6,˵XAki.:re;_xD<'z`N]ETPnj`6[u"FDsGIGnwl `۱Z:~iqHG.@V#M6u"FAБ]G.m X%e6K;vheoMk0;b66BhN)m`=td:Z;_'lJ~q2٬-Sr).. ;͎T6A~LvolJaa!@jwOiCG#ձIo~YY[þ=aЭ[wU6AnIoכRSS:V}J۠:2Oz#2xէε@ +En]+@  -@ BZ@ -0@ Zah @ @ h!%@B@.((( 33\!h4U? ĉ f )) IRpah @ hS]]INNN{Eԡ@ 6Miiq}d"==Ғ@Цzmc2@ A ! -@ BZ@ -MxαcG8r%%85ĒF.]ܹ+,츶_Bu[ &QZ'NgӦ$&&ӷo&]Jbb?~={g۶-9N.k[}.z &bC0 vƑ#>}6:uFUUt]yѿ@ʱcG쳏)**dȐaa7Bm E\oqD>a4Kz$Cѱ& -]ٸq=ӧ024 I/׻)TU^/Æih?-!IŸ\*? ^ر#$$$*ҏdeecGM𖦨>\@bB+Wa8.8\n7F~tErHLLDalܴ^V^-[q:BgGoΝX JJJ ;g_|yr/׻)6ļ{y&G,(,M~$I 9\Mh9r}PU3QU>}8|v=sdك 66u_C~y(u뮹oX.z dQ>Ǝ;%9ooQ#C]!C~ƐBgGo߾ :!{| I'ZΗF^19rXDlM hkȲLFF ͒$F4JK3fYgͺukغBK( ]HKK\]9)N ֵ+֬׽Y`}E g& >lxٳg/l6<oEQLLgwSoW]Vf9s$I| ϝ;7n3l=CpbX:C͎dgU:x8quL. ]׈ 3͘fjjg_UUv^=?7/næI6ԇB?@jLs8lڼsf7GGS;f4׬Ql%??Ge _uiN}4ř7-喛녽‹KIO{~MB5q(P6E1y*&|rv;6)o,]#n2DQa1Ed:<~kD\\,4>ΎH_MM . $MShΧi{ )񤤔p0,˭7e۩(7-('*HQ:u>s 'i|]Yj 55fϚQoSSrۼ<5 &ך𓚒BjQu֕~F(/+}zsMdE!9ɷSxGGSDzQ#ХSRXե9)gDh߸ 曌:^˕W^,4ihRPpԩ|rrrmX,Q\RBfϙ]HK?tI|0X++*3s::VSp6s0WhVHNJ0tn7o}O-{I"9)I,y<<));ÄDeUYZcu4G{5~Cw뭷 mGN=z Q$rrv_^ص{70ؾ[222HH 1;~7G0 ֬SNq餋1 ˅Ii*l(FapȑemĒH.=;:e2&c`~BgI$/X_68݇^iigwp#A4\.^orF$I̛7[\wu>i4Mhuԅ;wp!z,Gk:NNφwÇ Wɤc3 PPPĉ0Vλ4dY֛6~6tE6+Z-RS>eJDBgG$ׯ.4c2zL7|ޑ|.זp㏮fE՟ 35@ C\8cw4ihl6ʎۈ%'SpM9IΉ'عsC ;k?DQM@UUCS 4(faV>i*l(F$1q8nbcb#nBgG$ׯnګp:(ɝ; #L h )~ߌ__xh"ZөSضmK`j0t &_>}^.'Os6tjMLLL]:wGn?t$E=JDGǙN_$y4?"E$=!fIĢE9вZ0ذa="--xl6[`鴪\.())wѥKW`ZEW)U }/ZS_z7M{j?AcG4WgJěǓ7vچ[xL&ߊ"URYYEAA>NAM\\\ ZG"@p~ /Ь/>UQG.]HJJ$I"&&vAzzI=!׶h]:>=ztGix*a( &d |ݞQolZIKK#!!ΨMf"׶h]$I K3@$VWB5iM"@ @ A ! -@ mٌZQUsQAZ@ 8))iIcKUU Im_ rrr8yz=Zf dg @ Zs- T$\rrr[ICL @ @ h!%h IDAT@Bn|@ pgΜs.D$IgVrIe aXVEAUU4MC4v; \qp =:|nk|>|8]v%994dY0 ?u]0 dYP!2ʲ,H8_@|IP%? Ei8TQKc6˅vxzrݸn\.W\,h$I ;?zMu뢿KhMaΓe9x)_]Qy󩛷O+~,_κ1Mq ÇaPTTDQQ!n\#FFF&iiil۶gbd5êQ˲@o$;`#Sgϙlߘ umy5#8NC;j)Ÿ.ƌf;DEaav鹔rkDŽuPIlYVRRR";;;|<Ⱦ}8riii$%%j u)=>Z- è#}O$T : ZmZh4MqѨ1\㹤 Rq䳄$* ?J$-1J A!Br<}a)GGp q5 R),..ٳ8sL_ˮT*XZZ֐/~f6׵|,)|t:h6昑dY.d tc}/ "H=e6Yg*#Hii<ɲ)RMsr?I\.dFrHQGr nx2c=gbqqolV^G>G2D$1*Pޔn~+z]EU_-yT%%zA"@>G.̌!Zn- TdHO+*_:Ûb1m39 doIImܐۯASo0nq5ܹsZRi<tapDQSbvCL|Ǐ{KKK_X ǎC65CJfYsDlMhTo6g*,\C7D"dYdY #+\ǖ k;gmkTU=zO5eN| T_DFdi (puj\r=}T*7yF GDH$f1;;pu;wsssr&!F=hKVz!]sP͠n}f7C2DKrpttڄ sb/Uy*DdthN^gp@,p^/["oz|Iȉz9۷o"*JO߉D"ug359]wyJ>,{A7aHP(YD$A2D.he,//޽{8w1??og1s6`뙬-7yH}a$I^)f P !YJ|dfJ;Cۺ\Dt;yj* oj5j55ew۶LDIR'ɜiS$a6jD_s>cӏŋx饗o~|k_WC[S10"|>'OxZ-c !h|ֳu)/f˓J3,ze Uo#aDF@R1+"q* }0DeH ,b1( 6ʱђkbek4 ZޥO~o_~Ӿիx뭷gΜW^yU## |N©SI%g޺Y׍.Vl.VŦ؍ z29rl{OdZ-|7i^{Q*G8<$5吊 A24$!c~~Νٳg^>V\F"7lOrl-}.ɢEtqd98TIR LKz $׭yy_Mp'A^}e.^cqqϕfQ(pΝC#ZS*3tNpi<䓸|2.\ײ77750ͣ vҿYOζ=3>*ތꦗp$YYBy>5ԙӠp{P]GI'oagX[Y r;tqԊr?/g?g??B|o3_}?y?i,zCTj_AqƯnsVXhh=ˆD"X\\SO=˗ݲe#ɘZ@r fCT80A^~WF<6$%uR!yץj$CeTi2!o|_"\Mu)Ag)5MBp6E\.3l/+~ݛ~q ܽd>W^< ^ xGr 4;;kZ8qeLuP(+R4)Z6Y^]V|rmFh4F>;v5h w鐞e.\fl\vII"}VeS~OQV߳+۶ Zm?>b~_7WZ_/ 8{,>\r駟&Ga*UB+UTU4z1z?SN vYNI9IHr%*pCR95dވ94f6ZH"k,hT.Pql-\)Ɍ<[׍{l)$ɥ<Vбn#[|^ÂܶZ![7+/"~m x73'|w8T85e$UD>lj'&ZVz\.t:mr6ۈY/ BN_Q8ۧ9}t1UMФ?^}4$!)Iu#kq:LM Ize{k;;;Lr℉AI~Iª}YGI]G_}ۨS$^{5ܸqOF*{gnÇ#ZS 8IvٳgbRX,fHU3R=hB,9C3ɔ$`rFδq]/ ,1 æCcɉzKA=I%+__:t'En=\N׻ bhVZ DZwood.PTpőJ֔Āh>ӧqֳܹrVe ezA!C^ȳ#(uB.k|sIO3; < #l2^r!'TT&J> om#C2+QNA͓EpmDv솉T*q}fݯuOT*N: pX,)gX RɼA0V$1!Cb6HK)YA \1aȐ7!0,Ѕ8mג ȆҐNu־ӊ^M!:m=NײJVgTf+ EKacq,>/Q| e7Z-ܾ} gz>epDk M,95Mqɓ'Zǽ{jyjȓmʔm*|rzƁa  [/C N"s1aob5ɲ]ڠ .a5%z}r@h^={3/\x _ƍk=.L8} .^)S͑ɓ=϶$p],//# # f6J,: b^rr$Z:d+sy.pؐMz$wIX !C|ZDdHN>ײEbi iJe72ߗkF.O=sH$d2L&3s5њ2gff9X:FP@{.޽#afftZid1Bϛ0,N22,ADK2%a9Ѳ2cD~-cu™4B!dYdّa|֔Arp.JhZwww\pdpx_&C:ĠW3yn9ʦ2dC+Afwp8,H%ItPg> H$i!tQ~۞y}u_틼AJWrp8J85DEf BJ޽%eyR)㽐%(LJm3 =ț j !u[`7a<1JoaP<y&vvvæBt#)z-ɖ^\\\hpQ)e2 ܻwF133T*em!>n~P)Z2"387mڕJl}Eaxt:ƔM ?EO>q$Ҵon- i0*853AiFx'O#Hܥ%ܻw+++( rH&"@z=l$ٌ^.)G& { 8lI%4S$K}Wu3NޝltmیieapDkJA  t7Ppmm @<G*&ul52K/M2ަ~-, Hti'Z$B-W>*%ke{Rx&Åvk';I>Fq $I,,,9G ҄*T:rObB`rH,ɖ=ZI_|lVZ\eI+PzY\ NN4 +֬E%*p͓)^W 'Uj':AzGr`IR駟~u|gw* No#ZSh4j X O<zꩁ_ \|333P9hS{Aj%*% .@Eީb1d08}t:x<8;կuSzQA%YAt$D,D"GHA-/%=TRѶ)e{T1KmTUQtx#ZS9c&αcp,..;wݻj8}4ɤI maCx[؏/WDمJ%0bϖ( o0h6fl/ISju_ggRɌtWY^pp5њ2b1#s0ř3gpفIǑ bA9A>-YgdoBfA!ѪVT*VKt&otl:W 1 zEL^$HpyJ#\fRLR)AÉ#ZGG>eX Ǐ@VWWuy_YTd*bG*ZVd,>"H`ggjLd ˻ o·{D" D"ܺΜO{5;T*xgs CB$IȎΎg=aNcf ¡yKH%YR`&` fJO@3ar@B_S2 l—7PbBZE2Ap=ܽ{+++G8AЧ 0 gB5,^ǑსjB=wRuhSY"K"խ!@m Uٌp1THr( T,Sx˗'0^**Y*XhM0l|)R)L߿5looi(iC2kPlhlCMLfh,W(h߽on ɈtX"t˖](?k[4Aɹ̈́g޺j>xo\_o^x|oy晁8 hM(l:[j!Lرc%VannDb`K5Hhe+dHd5h2b!=\甐ɀTD"68H_$Tu'}X02?D$a!ghqdEL= BR4GZ=:;F֟%kƍ8}4R{=v;>њPfgTj40???ݻ5B!S2&4XPx6<-FBXȌbff~Gy ̰d, 'WZv$Jg \iߕ^VF# EV+?C _qR~L&bii J/^ߡ785Ѓ/ih5 ܽ{<@:*=.ЄKl$L6n۾ٶ %3h95dܷAx{٨p9  pFo%$YK,iE$K6$)وM T*q)3&f__ΎfeRwԩdfhM0G7eAOܹE6a 0!(KoCd5fth Rtd 'IA+4AلuczIϟ(x~jiZn߾ųܟ285%`H9;wneݽ{6+8XBAfx}zu|d3-9⤬L{8#~2Åli%Z-uA$۩'f~M~_,0j\~˿z]Jq\]#Z =20??DT*̙3huSf@,RJ/5dxGѹxzfh ^6re#I.cY8%M~ɲQzr~8wH$d2L&3s5њ`pׅ@6岩̾f, N=؍pIfIXҁ:!XTxڡ+tˌRg ͦ)ź*Grdt#j _cȨ B!dYdّa| H HWH41~zT^ ޤ/znQo$݈#Z YnlWn4(JVV8v!Z iULR:aj5T՜q8/!`9R<`^ ۲;dm~4l-ItҠZ[z'u}ʼnkt2QoCnܸܹG&lQ"T*t:=RJjӧO#LY4e ?6f],E45Rܠb=6YWkb"Ò3n0ItK/`0!m{M&ha}} ~ 8L$p#Z٫ ZƆ:>  ز5I2aAǩ oTKIB)†ӃNBt:n- V:fs4]/zv{r}95@VN<L8y@\__GX4DK6*5A6Y2ۉ=ӄh4W}%-dDŐJddL![*3ndbc jGز?X[[Cʕߞ8 NXxd<ܼy kkk5G&opsss8qP,QVFu@ fi\, !kL;"RbbѨQ)F;jB!b1fe2dYa #~0$Tsz*yP-/_$q'd9 .]Ƈ¼l̛p^7T?XYYUd2$I-{IhVAp\iXcE3Q)Z6Ac~Wޡ7Rʤثc*=u!I;zݑ F2yњPhMFRAX{y2߿REٙ*: $gr}Hx`byY?ٰWq apDkBI ;j5loocuu+++Ø)Z:4daӭ)rҋo4h fiaգ$:$ZCoxT@p:Aگ#Vmapp5њ`Ȗ6^A7,RZTY6Oa.,* rBfXn0ɐXsVXWCBV3C@Vs$HEN88FV+LjS-9y뵙hM hPj_B}ܵZ4Vh1f~׃h1Dϋt|DKC,9kfп2N Ifo/Q:ISMvhɉ'Yɀ#Zp8lvdcǎ}gnllFҕJFÁN7aBeI-0Zt:Ce CwL_'Bhla?NZz~.#Zi g~7_;;}-\.c{{R vTʴWy TGHl75R0HCnMM7uV zݔzFV/ͯFI6_;wnV뭭X*?K.yF G&9s[}/T*;n&ȕ_K% ߲v0ɘP{?]mtвlxFx GlݼyZWr\O)JxC:Q ox \pafҚhџe .!IF?AuRnP#bxб$8B׋}Q,Yǒ֯ٺ}C$AR;H_Wg9]wyJ>,{A7aHp`',LB'NhYr4fofǒ;32Jbk+B3資Z xctH$z5/| /⥗^›o:[[myNњ0L@l6Y,, ۉr  x (+ibX :8U 4Ґ$#a+RgkԪ U믿W⭷k!yW+chM4vVPlˣdW6k&mԎ'i\h!X8q+{lyIcDvOjyGTG}4]rNњ`t:qr9 dپAoV\F-iY-2^Vr&C լP(d7IͦY&ץ3n%hv wZYYA^➟;w u d2\.7ua3LJ;С4*u,uuYQ{+ Ft dKz)T"Ѩ11K~ pO!T*5QzB]8~۠_tњ@dtzlC`W݋hPa7[zdxFl]xEry~c{Ⴟ9  -?KpI51P(677}ollhM0Z8@aC`W*LRTY>6B%_U-.aD"F#[xKyPI/_Dl!ģ°CBgϞ'|+W>Sx˗'0AJam3Owiggt333{YFbbV lDšcشS-ZүBǨ}Y YjCzstaY|\w%Ƭg ^M{°/&6x <3x'W)Zwvvn133فԾ*hגjָc!Xlg"0ߟשPs^T*{C_CpDk@ֱc^*&T*X,fB㸍 2֣^AD;JP(7hT*T˖d V+(C(&X/^L&bii J/^t  C45%hL|~ o:)D"T*T*DQC%ad!pM4,랡Cv:xgTz`7$+ޫ#% 4ӊQ1TB"@*2W`umxgJ%ÅA {5O"x(2$?H[xjbnnsssG46M}:^rb!ME0JO ;ԁ]%kffƜ_$ZzB*$#H<$ɣy5$I#Zc =Cw:qd2 $ZF×$y[YN۾nIf%NgT2 Ѩ4J}8!}F IDATy ܱH b&{ۀ1IˢC0> tqߑ$,ݞu؃>sHٜ( lϊ*VWWumyuh٣fU:'rǹspyԩSh4RUlnn* gUE5͢jSbV|]m "a>tn쯤?)X[[Cʕߞ8 NXxd<ܼy kkk5G&tŌ׋pggǔtX__pT!Hs7W7c P{e XDcz#cݦZr- ϚNqݿq>htkE%߳T蒥2&ŏe}\ʼn#YΗ.]Ƈ¼aJ4)m/(J&677Q*L64jC<` 63II%IzGN:"~r|?͚q\.խ^#RZb{{ZZ䨛4O"zeSub -m 㲐&[䖌dM0ɔ[@{؎'LA *((hi4$KTXEZq0tX47L&\.h4jTF1EIc* ]Z" .H8, 3-ghM*r|85!`&yE.C.f,˨T*hZVj$ɇyƾg5п*628fffH$P,m8KOh86{I*r 'lu*[6E ˊlE9т#ZIQ z%Z;;;VѲ AIҳNxnRnk,\J%ZR7fqzg֡asAC+z,6>AMHig(![F-85HRt:f~ml'E5K*Z.?LT~qa2dL=cYs\@H_MgT*o,1ѢS x :q@gE/ǒFaJHHcL]BQ TXWYS)(J(֖lYZҾMlK2&$ͭsOG[)fUL?RNiWTקv! }%`z"HZ#dM4f2zn 4;8hM$b{t$Z~V<fBhɁ-0ސg!Z }ДnC/ъF>91ڗJ%_+i>[ln4| {mj]B24!F&2u-|[H$KH$|O3ΜYy]׮];#Jg}=ܠ0$85>p8d2D"ѓ7 %ZRGu*eZ$$b3:02ZY4W*/<%Ѣb X4yɓ'q ,,,  akk [[[n,2t4ђ79ċv]A~VC7J$ȨP'"vNDP#|җ_~/^K/_wln\8PC21{rsBRA^7,iץ~lmZ<9ەPa"(C*[,GAz!C0$NP(|H&677=>RJ$Y:S?'$g:FfQ,}%lo*n:^z pxW\e1#Zc kZƟ%n`b-SIH")ӳ&b/0Ln^rAc5C$]f|놥yJP(L\4!ju&_򗇺~D[JeISh&Z4@J KQfi%HWپQ !HR&lVIjMA^H\T c*+.ֱcLBB4yf %:e+++X\seY ܹsgvh)d=dLGyGVC(2,f62Dt|$ i>2F**ҧDJ#6^R`{{b\L\D(TX,~idHEKb:\(Knb1_B(B"0Nt:l6kobh4FN*Z685fDfN{2W*"DK,It, z)ך Dt%8y͘.,fgg} "8yƒ R :w!N#ɘ& /UMit,>ALi4&*X$z|޷ x0RͦSѢe۝znn20(F9>w V,-?Jxcٷ$L`q8ɠ*Ő0??o1mtxϥULmP!!͚> y
  • 6Nr+-h6X__G\ꚶ*jjR1u3fR4!xo^@>ogyO>;# oTzBADtDS<Mzo~dǔV&DLHx =_{5ܸqOF*{。ס?857E겷!V& \gJ%0! 汲$Il-61T8r!/w2hG⪽qR \,6 7cp $ᮃ&}Zzݼ&mBf03 Qa8'!6<*pDJrB`MlP֩K6%" GLP z>21!Hh4Gx*^ "T=mJhYgB{K,I3tw:czgia`PEaDyPFepb'AAvMn#͚1tp)7ܻ0J3xqGf)1jggbTo4F(hZWG+Hh9K%TuhN*kMIH˰Cd٬ H\>`5q(!2wС̓υI)CJuSO TVCR"JI0}WHB4 8&iȱRo\O?y;Dϊo$1%| hQ q FnDk{{[[[l5 cƭVt:n[u|]-Yʕ̪$Zz$Z0k6Fb] VΧO?t&.h6\雕+prP! frt:T]766P,Q.M!A2ejۦFIB,H0pDkLFh8g,VMߗ%3Vf R |EM6&r9r9;Q$@PL6mr wOFVd ߒ?t5A {yg=TB6%RI;~}6"Lm`8xggl6k-88 hjE#%MD+JK; lh+-Q&Zl#l-IIlqU)T(fܶáa/6$QlRqΚPUI u!o*:ԢףIܾn^$3#OMA% hbsDWȒT,8k% _Cpjrrl: uXǖ=Yj&ktC(k?%SY£ۺvh4h,*ȉ>Ah 6*V1IE+bvv( R86j67772UdAzXH& m.I-@d #$Z$[-BjಬkȔ$ph4d29MFX,z8?_1كYCD7+Hm"t!'=y, -G[~htPi4&vصzңÆe2CgffP(ߒh2;$[>+I/0oًb;X[[Cʕߞ8 NXxd<ܼy kkk5GD{$ɢҒfJc ;0V-ÐUo|MV̮>/D,C*2I;GMxd2(Jfe/ |T$X꣍0[lD}<2t;j0NR\l4,C4'3ke0sՂycW=Z-L38-d:9Fѯ1aTnq}\ʼn#Y^.]?y1V2M!gm}X<B*Z V,HXtQćaz> 5c45k333h6Mb}6ehW}%Ѳ~7gYCf^3e$LoZA,7YG|- eDNgnVTy(QVh4Lqh4jRR/!E`s<7 Ul6nQT* ?(؎D;jR4L{A^w$kL|:Gz !:Dxܤ1=CB&ٗiثEF$dJ {7R"g9GEV47QʃhG+ ݖMos:ZIv)[gn IRF)ln Ba7#Qۑr[IhKLI7u&@@duT*}IQ&%-o: NjZF)˦֐\^FR޸yj_49ҨH$033_(F6 7:d2t:m2 cTi|HL+b\,c呤m$Y KUOrLmA,YFD'YCkKP#٢*?ۆ'I9ĉ&Ⱦ@սAkhx= ٖ0#`A1 ItدFa!Ie>ي>lR)SsP(d޼dR*tf2$ZreXK[M"eH[&I:bkBsI\R4~ Z,n;& x&aQ IDAT<^i5R#`jWRm}װ*doIe~8CV5DeBJX~5䍧ͭQZ Twl!)qx'cT zJM4 T}j|ZY[KZvܤGJeK::Dͻn2&2/,2Y87s㍉7JFT?PG2ϰYRZ''P NrwP* ׉,u>:L3ț M5ժ1ebf\v3+-Ft ys%CP"Z4˛ VR Ot:EeI.hʇT%φ o|QIctfBJ%fqYx܄l'#I䇱\>~+3o6 C FE3HÎJL*؛k*BP0sNd "ܓc!}QCaͨeX@"QڌƜJCEŶ ڰ.3lYiv~ty*wzÀ$HT:AR [[[DDM:=IA$C7*W2,s}6.A]Lzu?ђuUUj-`IITdF$a{MCyIoXඓxmoo%=u$E(`ٻ2/IB+I@e5FvׯΝۨz wRi<tapDqXftST*TUb1iwiiG2)$do&'C2 HAB + 6!CBaT*Kq1Fzwl6MђJT $ O6 xY4Uໆ&r\J$b>׏ߗw! 7o^GVի\.wJyapDk Tlݶ,Mj"! lEL3?'=Lhq Sj9U*0TtZY[L3Y敲yH$r=,;n;3dYrRJ~,6>]ށ7/ @cXSQy^#B2*id&&'uI@wHj /x:DgcȈ۷o"HٓH_Wg9]wyJ>,{A7aHpDkHAlHSV,@VkgfiLRAtu "ѲY-լa+Zң&A13ǜj,")EoNb ,n||͇dhp I/n$=GIl56Kqd:lTt\7['1÷^dM3r>@YwTok)(["9t&ʏ?_җ/ŋx饗 7խ[ Q'ՐLΰ /*K on6A`oGJz9r/QSdL-YH%$V] uV(ϳ5&4Kߍ 1Yf$_r{uy>ѫB@z:w>ٔ7+R$dCaT@簠UnkPiXM  qY oDz*z-3gꫯW^qh"d])̀?+ 7( %!+%6w p333dh^VQ*P. aWM2q[I%#2yРI$~T/2\BC4_:+UB!ђK`< hu~24yÂL*Z_I֬dCat}^NZfdIEVr@wnqVaii 'N'︂pvv^bvjUb3Y" 9}5ON&&'v$zlDv=Ɗ̓:wg6R)eϴyO*yl?I0{AI2D |t/|vtR+ɚ9ibzG /⹘͛cv Zi;JA$onnhd9h8vR|wSOb[ZZɓ'U:u +++}6@0VB3=CEŀb7*L$/ݦ.FU%OtD+|}v}GdD.jIw=1[L@ \ɫqipUxy5~Dg{&JTQflQV_jX`<0T~l u"_ nC @-օnMDK-=Ԭqϔ+ VVV7xkkkX[[É'eQ#ݬ7/LI\F.ϜYN$$$gwRy2B*YJ6V@{n7`0'^z)gSunla]ܥ)aӭ<_Xu1E(^ҨwꊥV>,s='!!aqqE|x}6}$=Ru(VaG@4 1f3#Z@Z#J<ɅHD{*QNqbii)sd:u*#Yw)*)⬜ -%[@W֑h t+gkuRbDKgVˣ@4dEGWR4qN9 Z*PqJeT{N89+UC:^|O~%W=| h Wtfqu}\b| 'gTh li8=OxWeU,.Kլ,(Zf_o@ziܫP3H]3$VfNM$K7D;lnnfijh4üqWo4$j6OɝZuQ"W(n٣o_`C::IW勀^G_W\sPV?cn Z(+<x 666Rc|襌ey;tpdU  6fgg.yoƃ, %7-wFJR4ܝR^Uפi,..b3C+!צi1mr-_h(^qX4.!i! U{q*T¤}6+R®Q_${Zʥ"^~Z **I:NF:3*3ƕLϗ{'YA/\>k6~.\x_ ZwOUѢjAc<_{=|x>} lnnf*HXD.0>z$dZ xt%8|M~_]TV (g,G@] ݆>WFx;pw>+ܪoJ(""8lZIw/\d y;w.=?냂hE 3HDk{{;#Z7oă-4̭ƀ(]:O'BWNQtH2ՅZ-#dmlldD{ sSQu,1kh$B,S='RDɖ#O.SbU[V)|PVém~RRe7/`ou]LOOi{-͍ -`pbDz\33IUkaa?7y'Zm̵+0iD@A* )48?#AY0w_ߕ.U!RdChJwc8c'ZYY*N83nɕ,l638%icu @4݄7&`qK`9QQähvRCv*w]>gݛ3 "FMr1qGم#&ܼ*XT9Pa)Zx:v1@hЅC"JM`4W^'#i`yNT'FU9 Lvp1{;-;*VVVҭnd جxc߰_I/uQ2E@݃Sqq.K(݇f$tqQe[(uO%Ǒh/i6BlS>nJxJ-Mzq"^DkLUd !dUjY$ZuA8UT"s;mb0h-E3^E ʴ$Z9mZ"rqhy?k.Eḧ\-W#(ɌT;/p3zs{1Q]=9EMZNZu\Ȉ7P*Vv:,l@:.pUWJ?֟c}'(0.DkL@.hD_WThb4(UF̥R)SI>55 Êk1gZmg4hJlAN>#*ERgw*FEςa޽ޯQ_REnT`diZw}upI3%\RF+z  u@>ctt wpvո1u^=WɕӰK,uMOOg13Vagg'K[ 8s NRW:ާRvU  : 5Ah&)߶a) @Ron?er< #7~j#EUT%`煅=mb@;L(LB'0ݱ 0}|]J+HUYr4n&]@аiT}yvꤩE<7!kzwE#NJ9NFqHC1]T᫧@1,(ZOիwQT:v+W>n޼ ZEdu% 7cd81C4Uh^ǍL75^)A 7.ji#EFuQe -")SRQG\Zcͯʪ@I;@_4kwBP}{J}}AЮ~{WF^/{Ω&s\*?~>c8(+X[[ɓ:*P%AElNcgTowJݏ42n!bdYV$g$Jh* 3u RnUҭ8ģGpܹ澵 9IggVN&? 2BnReOiGƈ$܇yI m•NUG#e_L_SI[ }:;^KalR IDATho3(0ID@g4*B?WhrI)ઌPלdkff&KR5<|?FȈ .լuT*9s'N*ʀҢn0g:u骉+az4Ξ#uڿ9zNJi&$WrGurR*WӍ_sTSSߕb?j^9 NSQ#U]48\ נ0/S ΆPHb`ye䃫 Ur#lll`ss3KZE Ȗ*UN"Ø2rnДheu,//ɓ8u?~gϞhdZ-lnnfyHjZZi46#.uqѵKw(sԯޏV~l S–Gb-޶<^fhI%<f:?R5vʍ𰋼I\W~VW,,,tNO\FAchf *Z$Z\VU}6B-=njj*(ɓ'<\677sݸ4@<.uqԻ`MwC}?xj 7`QdIƺ?S}'( s#q]q0dRLҜYUмXQ{F+|~.@h+7QṩRgX:(I~{6}dZz}ϧũ:L$ "b f}N#5$o)? Z-͕E>ȮF_~K.?)~_﻾kڵ+P|jjj1Ѭ}?zHDPhF#[mDF&>npCF|"^KQ:6}ߩ,=JFDx"™W>:{6~| I}"]'ExC' UR/J*_ {FG91mM:e(k?ď~h4<&xI@ g^ ~!;}/R:p6 +r!{yFuλyߏB"%+Th܋mSTkE$cR~jsҪ9" XiqeX^^l,%ԝ'oK43xf)TlÌg#c@ܿ oXaYPQ0????+F]*"ڶT{S}0R_/UvTrX9T:y]_*#=.L+Ť}1ENa U5^5O_r Ν;j5u :HbO?".Ν;3gΠVӧ(JG0x)U+Oy#F @lq{{;#Zt...fj} $+: :,'D)]/E|/ïw?H$xT9~vQIV^/#zv!"!ym6}s !y}5*T]f7x6C5SUy>TI-lT*;wlҥK.:$l.12Q']*n‡~{0??F1@WlGN%4 4Ml/Z'ܞ`TU幄"d!:&%c]Ŋ2ʏݨ$+kQgt=w7 #5JJcIcIJla 7KמVw.x,ffF3;;;{.}_ٳ>@7VCJ:$,voŋ133'NɓX]]n޼G`?=΅a΂ ˼M{3z,j}}f3b?|FdqDj@ORD]yU<~L #M6C^}>#ra+JۑG\Jɖ>GD0O:I& .V5N~.\xl1 uH $\.`ss333Z]]͖z_ǍYqfk{{;Rll-2$nCMd;f6tHˋATF3P)7cTfEu#Q{G"u4oAC}htܳҡIuxHppd+L -|p'#]Xܹt^D08#yN>3gɓX\\dys>вt #t:gnVkORM7???@2u)ڞ}PrNb?#R9FU\yG8vr]AK=,hHd+7yn ?՟U݊y{H)TZ6o3|4==JZ64 u yQ# gK!?w:)R3,ՆBOR#rK#"y؈( Ϫ=NJ%u\FAR)O]i:H0 owauu5A`2J&`z2z=Z-b+"= H1VL-' .޼%)FIi*VI>WbiR '(渐(0 DARn;s|w;̙38}4>}Fm z`65|;R)ՁsU.CE%K vDhXGuWI ;+sLU$#'6DлB7.,GDRh2Y'ދRli;u_fNqƟ /H SW,QR c Z[;fWgJPoӸu677\E3KXP$A%\ڣhED "v*t]\JU%JFqKlr_R)}L92UM^w}ΘdҷRJMQۣ9vG;nPb1|Q߽^/SI0;; $_|n5 u(PPCFQ(݈j?W_}wbww7˩O?4S*^C >`Pq̛:R)[Pթ 'u\Av1\1$h/Dwi{c Rj[1T X%J|ԥkN&t:ق{ ,3j+lQa^IfggGRoJlѽOu[y vJ尫R%nP.W<)!#Ni4ׯkGkeeKKKhh48qDF<`:2tQII`P<`16K3gʜ-\=WrH9ɮEu\JN^tq-dOh]R"Rl:EgMjΟ>v;#YZ۟Mx( > $:>5U8PS޷$%o<98} ^/Jz)-\)p :tͰ3 LRnܸWX[[ VVV` 2:swqQ$J(+1Ә`Fnwh1>Or%JRja-[D8+Zl@ޝE"刼(Xwɟovv6U確-*ZXIV>$=N~Sma;Rc~155!h)tr|c耺I;@؂C?WL&$G x>菙*Rx\`mm 'OD >ߩ!<_~%1;;eF^Ç6ˠwŅ+M/\KW꼟<'Fd)HiE.s/uQm$Yr^Rc[%R>sx|m۱z>N^CիѣGgњ F8 $!*hZꫯpE?Ξ=i:u 7n(]5w5re sss.E^q@իR" lMB{\\{cG <|]| 5aDW^L~4P[[[x߿jY={'OÇl6,թ)b ":4 oZ$͓]m,ݕ@刪kFJ=D-ed4 uSuW0en*X9=eFJ\DJ#IEDR|.C 0*A+OQ'UTGϢ3-WӶQQG)H1FR+ք y9[ܻwKKKŹso?&rTE.&W~si7SSS S&n3HITb]@-*uK]a{ƂĖ01LI B5VU3$u-Fvլ@F!yD+"Ys/u=z\VD U0QCI@4ב4Mܺu KKK8uΜ9m2}]j5j=)͙,"&7Ip8mll ff0 }Y0U8UH5F*(5C.`}hԚH2ij4=FfX=ߩ2+SHSDRD/֨ gX S YE(<;eJjA & u0ꀥDn߾%,,,VauukkksVWWq cv_OBfFZER 2Ϟ=ˮǴPVz5 /0nh5;;eWE ,y "Liُ2sX_.QTR.g*$+y ZcqR~G bD;8҉+}&hjvwwh42Aɓ'ux b~~OƗ_~wٳ{:'Z1J)Z̵mt]z=<}tO;4Ik UTeݑUEK򔚥7U鵵'IĩbVyws*UHq:wJmsו3!RTYI^*޳^7[$Ry>n(p(@ʥt=<~V 8}4Ο?{h Dr"4&V3 ˟uOh;oc=%AT; C p[l=o_?yy}{,Y!Ҵ=J-=ֿKUQ%)Ϯ>ydQVMWtq߆<.5'z>ܼyHTx뭷qc]I ZLF4P1EZ F?Vh}R IDAT]#pc+ $ĩ(YZSF+_1 ɊTGA.V[.RN*x^GdUۘh~HʻVȖos7wBS`>0J'055h'z`/:?W~VW,,,tNO\FA y@|%@333snܸӧOcvvϟ*kܹsg &gN2"AWJtPc<TCWKOI~&_H%Ɠ9 p}I&^\Y?$eܜRˏ S{O:",?zuCoY?OEnqkl6G:gzz/_Z| ~ߡl>~l 'kj?3.+ q]\v < .^7|o۷ٳ,gߓ#t`/.6ЅXղEjED+. ut /_ )Xy1I~"2} u%gD"XzwOoZ^\:??r'CcJEU䄽 ZaR?ͩmFMwMJjZ+hF_~K.?)~_﻾kڵ+P ~lf0o߾ XZZ™3gpO7055ymJ 2vvv2wK(Byޠ˺*И,'YN_kJ/B0@4=L7y}x5z\UǨRRkݼZ/))"7A0 cxpWO81벜r3/Ko~8<~g?YYP eP*n[n᫯Çj .O>7h4GT\AF}VLUM)5^ѬF.RcXi{nCQJz\$"0BDTHXE.ZT!ud"h&h$ۿ?Lgzuww~!~e?Dq4KP&H}fhZfff-|׸xbF.^uܼyַUb055elg6y#oUx,}9cMwD~F%Y){-nf⏶2 d[=L(K!c~矧T.-CO]['|唪 2x= ;rJAW'iJZI/>:.\xUױ7oh33W0Jz{x>N>|;޽{xU$HI$H\ 1/O~Fe'6l,?7DNϥ;}Z*ĩZGjk"mSdST}"up哐a2cB,HwV~ѳ/%F7[^^=I%Zy*`J[mdMkDg!Erc+iyqg=40^ )˪8Gj=iL/~+WܹsVX[[_P#a?L1%5z666O޹sǓ'OPq)?j'Of4~? nQڠ =q[_ɘH,u;7bB1RJx)usÑRrF,O?HqSb ~#*kf-Dk38HbJnJq4M\tPZ њ,԰vQ[oaii ΝV!OowysssBPSU WuOIyDKˌa?jEˉԧx]`7~d}J z鱑{uWE|[i|mB],ైH@DnC g#Z8ITUܻwoq6-޽ju~;{)AǍDkP5 0hZ(˘ӧOqTU8u/ŕ+WW_azzٌje 6_uWfQiwQaSX\^j 9NIJZFD>U漏9l;SH\ @H p("T p*YФTBUAgF yq…쳿jp}P1BAp~~NF.VVVo} O >C\v NB\F\޳(NϣQB_eh{#+YyDωȝS)/\W5FDJQ7IGq)Q߻$oX>s毱2ѽd|Dy#Rt>5|AIW//(|uywѤdxo/+W>yjuΝǥK- ZcĸHiR2t033kݾ}߱E;whZӟ۷ogp}`ՆdxZwjpTW7z̯×.wڢfP\ E2 ؛AeP#y8=3+2Zq1Iƕ*+{TIυՓ|o3| 4* jژkW`(֘p1t0;;%2/'Ow;wgΜAGZW_}[n֭[#+cn?Gjpf)WMʖƨQQ\.] >"9^ow)#O"wl_bD"kJm֯pCI;o^7￈Ըh'X\*PQ'vG`1??ܸqx뭷Pױl2*?~mжqdyQ:(QiE7-d2K\k>󈖟W"W5\^VD'9Tk,Ev 5c !zV#23i: U 4 5c8*&rz>s;{.fffPozr<سou+W3pЌy1#(h1w+U6dD'2jH1NpmPJr +ZR"]] 5:zR׮+"0>Q*9z+ZrxNrO3walNMGnJ눃Rh O<$@(-GeLMMe)a48(hَ :6h Axׯ/*Ξ=]T*,//ױM<{l@YU R Wb_gjD\љpd#pWky_S(Y;lw@rw/KDSA_7 ^??tU{AH!> 믗Iu͟qs9l"pu>}W~˗ER=\kkkyhϚϛ7obmm o>|J%,,,`aaXXX@^<I_ ]C88Yt݁'p"r=^ȖZͶuT*FD XqOt~ylK0 :ܒQ,7y}튩*QE ;>h;hhWWW#|3U@\N\-ָqjZBR,666p5\t oƙ3gPTMf~~J% W9y#LEIV:N͚Hƈ Ac?Evs;9X^索IyqtQ%Z]ѽQץD{lRd^hz (Yg(E7WzG+0 D~?jt:ꫯp ܾ}JNʈrj%ZnS ":v9H.<(E"%щz='I L'WNvD \acYZ׋HEJ^S㶴>JRhE*gYﹹ,!cM%  o ORd_J6{8:m\ =z}VcleZZE­[gFqZsssVY\[^(stStgCe)#8j'7 1SJC/ Rcʢ"Duu ϾJTI+t?QP+[uc=[.3833=NV+{u:=  RUU[Wوa<8{x~HVRwG>+_9EBV<k|jX^^ƅ  ۾.-%%ͅ4my3tyQ۩ ?W&Nw2G)R޷ȞQ_J^#Eɯ*T}SKϟ X ~g\V͒&&=* \e/Ɋ>&x}Q1UgscѣGx!߿Yf+ќ76%[n%sU5(.#Ͳ)5kT8)rx_9]OWbKl"Ϣ]`yT\>|vrFh`ssɚxoF!Mju=sJ=ZVhI9lp"dݹs'KZ hA W4]qty$3/pJ)7GJݠQWX9\Vm꣨ 1rUs077w:lnnbccO>fpBcD޹J]>y5{@q ZcqQsWw5 ܻwofffP>AQ%:F('`J\Ė:fEx}Saa-'9JU)2=6;{˿=Aև=Ѥ >Bç~7juo6._~w̵+0iDk 8.٩Ixjݻw/#Y$I PE.]TrUR;.DKJ˨#*5$YQ'J0S׏7*#_|\a;Sd,&k}}Ϟ=FA ;n\uswk`LW~VW,,,tNO\FAAgϞ޽{T*XXX\z$DQ FskxK&)Z.5J)eE)<`lke|'TkyNRɢDf|g6ommaccOȮF_~K.?)~_﻾kڵ+P&a~?0d\j.JRh)HҘTQVj04E4*E8gTqQXUxRn7&z/YHRĒ[ MNy?Yʀ'Tknn.[a S5csˤv2uB'd"o1IUeچ_xpy?~"`)&#8@~幢pJw6Ӝ9NڼRȌ)h<[,.HJORRu!"Z<*q IDAT*EʖU*=*ֳgϲqU'Q&Js=w)GKǘ<$7OE1]|я~}FѤ/Bњ 4x(,9<c~Xx U6|q"G&]x!saZ=VFzߘxCA&:m~Lp&QWA@s`o@=( U9,n"ՇUp֮$)W^DTT\) RRVU#IIgϞٳgh6cۿIhXɓ'w ux|TT*annjZ E‰%W|yK+IFJ+j\Ez8h؞݁x3ԊCu#VE4Vn7WsjZ R/{qY v$vvvjl6 4,AA A}.hs.Ƚ}cIOǩTldT 5Ae\b?77Ebnn 8}4={ullld):NHH.t:]+RBAVՙ>_sUKS@D*~]sr/ 2*HpbO#Y.ևXF#[axJ'ڳS?mYt`FIi$?RN#$nG/ElM?O_Wo~cvQ (-Ƨ(Zfffn3bvn%Wt!=9"E1a$mJšKaD)ehtEuH+CZhlZz)&F4 Ri`AM j̙KQ #yAսϹ+DZJd}86~VRV:"Y$_r Ν;j5u њ C~vV V J% f4JbѢO3{ u_uA BbW8<cRȿ"5jsaz@Tu*Gf>2B*YT/x=g#ɢjp ɢ~]!{?I(A{AJd+$#j4a~~>[};&%L?bԦIBW=uJs&.]t.{Q7*YVBZ\6vw;,wyQQW+E5H%˽ǤV*1EtpvGdKX$Uw=qu$BJ*=Rjj|2U7~677l6d)"*˹=R5U?Wa@%%(;y.q}yc*>33Z3ezN8qVw.x잔8)ݻV}gitXA dHöh4P0??ZN NY'ݏ+"rBVOkIQb\Ȗ7L1[<e#YKTȉ&} :a-sMaw;Rպ;☊[? U*PQ'~Gњ񳳳B^TnZ6Dq`u:=q[#Xo}wY ~}8J?kisQ\2W;R]iv 0v I ZHNp o|L$lSWꂍmd4& W)~poOR¤*6I0Zh  5f@w\T ˠ捍 j̕vB00TQ8qDC3ZKP#+5Q"#;T0?'Um&BPEs3TѕT;;;hvhZo40]|ۅ`Gw~G'6@:eEJ>'dl_ɕ1"p:janf4/PRͳ$ mGJIH@I{EA'[=ύơWTҾmZfo]I}=  U`R;J,V\FVV{UՌ u:aPpӢ rkk juOREFT/H#g4Esc5DH,%Yn>Qّɻ:vnJȾSXޅzjŦNTy(Jyȗ?U{+D PkTBMHJ@lz]UbupshMifߥXV3AZq*^ rq=xA ^IVD|O'An$x+,Ѭ~uJ@w 14U;E̙a rr=_ʦ*=&M#{O"NK{_}[ͯh Xz~j3vq\ ._~JS`h[rS 5vƐ Y1133) LPL2zՅ"0]? Sc糽FJXdpy'Zj@Ou1T4'h;Iڨja}"`6bjjj`MNʗWK% @bnn;;;Y<⸉*{|m+)h:\+)Ss?9jX]]ǏGTrkkk8yr Z(QUJRFlDn^c~~sssY`"j6ARɝ9@L]~)RV=60S3zoj,A]|N%ER-5=u*l,1MP$D(RU2IYtxָ5+"-F棅" }BK?Uf!vBq`Ezǫ^h$*9:H݄LK[zu:2%3dK}3ϒU ~>v#Oℜh 'ZN:#qdy cb$wUuuh{<&4X1J)@8rXx"JBd!%HyBC"x!0"HDY r&篧g꿪{huWs{nTݷ=ug0"1p?@Y0W`Nj? =`Am{X'3?ڃrbmƐT4ykuyM{t6E677ի~n߾e.\|dڵWmss3\ˀQD ,3 ;kZpnDUByvVVV<ڞpgHQy h.,|S`phŔ2eeigfmߴyMS,H+o1}Nܾ}Ӯ^S21+vʳ+2uHdF efS";VZ zrBy3[ %v 1lnO> m,b)cD (h2?)c6/ew(Ȃx}T֐lLcuO db)+ ߺ h-XfyOڄj^֤P(X $X"(\.`VVVij~ߚͦ?s2/RW:+RūYliLKwRά#; ĐB NMccXIcfjt~@vױG5s3 eDZ06<汻 ĂB 1FU3mIg3ytZ0 G=DNT,C 䈃T;΄"0By 1Xq-؉)ҘҙXyxlUl"I )f4/DBYy8֕ ="P`SF؅ei9%ԑ8-;Je?X$vD',jv-П{Gb RGY&ARy}-Z?k^Ҝɒh-Es໅Qvwwy6GhY a|u XhaKO;3(xR񀀮YS\uVef}<Ɗb=h-+|iQP6 tئXh1 SX-NݙyPO<&$&^#lz=l,<:h|(K3LSŒ`G+ 6K~``V H6 `r݋z=+_uk,sξv5v`Fcgͦ};߶k^[p2Y<4@Kf&G+|ov`^J`fuAqVRzggg:ښj؜ 3mu\= e0Y+fƁY+51I%>im;PJcPbiT3p`@X ] ZT>hOkiu@hiS ݻGձ:yhƘE[oi=V(nLP|}Şxy/}O|-v& t6Y&M|qJpζ~1VPHmk4a%v%MY`tu'mR>>yfIx M7ưh]1HRsuq AA939샆6=,5P+2Ot\p/X_x[ bG'o2!:,; cT.KݮaRifSُ~#Ї>dԧg^z^~eg?{bgsٛoӿ G^hf𨂟6Cr;OVt:':V| +JLp<v tԔ0+J^`1}*خi,8RMby ϑQEaLp(ehikcuWMlft? omOn0>ִP.(70x<"47|yp;ep2o^~e{_=|>OgO$Llrщ,D]*e; vV+]jYݶnv'rOvxHU=1[E{ʊ6.hWX,V\_ @~GY;NOer)R0p^|@[c+,0A~?׼~Yo\`/VUrMw-Sޞ}ߵ~'?I{?}^h. %b)VT&+JణlB˜6~-v?W^ ~Bd>yh3Yժ%C% ,Lp&yu M0cJF+sf:yQeCڎM(Q=6 ~x) H< PYT!z@mqZ}Gy nl2?!0-113j6xTGy+V6uSIft1&67S8]%QTsw ϛC%bA=-irTRA T1cȂBs;>@y2V eӱVs(f>0Օo-w&<ʉ-R>i\no'i dŀYֆ1` طve'֟|@-," U4tϬAYӔB\GON1 {uv` ;߳LY03[&'CК,2yR6x593j‡BMhv b3GJ?mUp@hwUl&di@VDxJic̰x LY.3BL^l>TNۄ>99|ޞcjjJ%YEPi"ۛC={a}K_ n^xޙ.V&xZ4 Z+ޣJWb:ckZA DVB171+ \#8`i{x<,~&.|9bLG!~FMYh;@l%h1p)J]{[+Mot|1 /vww-p8nv=3ScRT wE2t>9{ҥKVTŋo~sf2d@!%0-+zn끝0bE!C%bvvv)T*%#|i̊2,{ooP,*wr?n;={7'3!GYx3nZ 1}ҔlZOӱvm8 B]zaQ@Q_\%f Se, 4s|wY_3*MCǜq+ Юz?/ P/btJ`#8>?&BY($'Yu=a+M%Ddm|y8ߐuQ!y[Vr00ds,lL4!n|ͦ/R)U:1`\oOig!B[21HcefӶ޽{j%Fv}t:/+[Sa\~_ &v?(P.ϏMs/i~s eqX63rZ-wݽ{׶lZMJ\q >U7VxϹ`zFԔf T汧鲁V&dS,h-}4Y՚`p3&VZ)&T1ajd3Ƃ4^X<8Y(`>Í2{>L^+ng;/<ԏA:н^n߾m7n@ݻL Eߧ€+^5zqa/:NX@;c?#cJ3$ZX9Ż_*i:"(B6}@A.0;X]!/&P~6#*.&-79b:kj:bAzJ1Ӛ ooZeI_Q2.C1(+u0Xղfin Mk6dNo }vDpTػB#ﭖ?b~[\4FcT4;-R*̙.V*1'9E*I%moo[Z QZ@4sN.wp4FǦG rݻ&k63`5`phVV, 6K@H^GMl5 *lbmPV%<0@<Т *\MdˀLX"(p=#w]1k۶e;;;\m@YdC01r%BN(#y(* pY@Ch؈pNj0;A ]y2&u;ڵWʕg\wq2CzkvE~,ZNxĠUf NPF=B>+\Z0FI߷r&aL2Dj^np1 Lc?jRcsZ1 fBdQ{4y+L' Na,bS)s2B{׳fi[[[vΝdhz;f6&pQ03qZ҉х Sg5{f&ړ/:urv+Rl/^sg@ai!샂]S 0Łuj6nnI#jY0s" ?34 +qf1917`)K2Dgg*4dy)\sgցd&S71&kÏ=9ܼynܸaw ;y!17MbxlJgQ'6=ߟ. 6h4ЪT*Hܾ&"xpMۆ7b~U64ht:@a&ANMB!,|&Bby)f*˅wvv޽{XfyVVVnP 0?1C$ J~LfUi@@b`_S 4^Y8/'f􀡚~>M^`o6„c1:hdnךfp qmoo'vNOe\oaZ7 ܊4??lT;X5D3b[?F+?i~Ϯ^Su-p#[^g׮jZN&|>`cH5/hF#{ݽ{ךfD73P \rlgϞ[nޞ59X+`4KT:1k檘;isT{ +'N Qoi:NX_im &)c@҂@͛vMIyY,Px׫@6)hu v=bwL1F⾴çѮ/tqhâo{vo:ɾκrY{khK 9^Atݙ|yU|8p<m;;;~` rlj5~d/ٹzbʩڑ'j_Mẁe{=3jZ\#3xm8(tԛMS;>0i} sl{{;qbx1ӔƦze,:xR,iSV L3s@˞ձ_=Ӿ2~,myoxg K\I֝hi=!8 yp7o$x1ðngΜ T*v`f :? %6+فcu.7KzZR,L3IzW` 1vT9ɒC3Lc uU E'A6tj `fvܹ/&qZZjL+Y@B0l߫RX,/)+2^چi#U0鍝9h'4m'3_1/iu6Qw;cBYq㏠a*W')Ν;!~Vղ^7]*ds&~x5of"DerDWpe3p8 G9sΝ;p:777֭[nB*Jb/LiS:(QլV%F]yfn4Ӕ u^fi;ǠVeb剉*04d ŊWH eͬ 8`I cwܱh1JQ۳-YðtmL qY|V#cc<0Ӧ,&,fu~)L[N$Ђx+eYdy"03ذ84k5$&E,ʠ¾QRXVz>q:${٤?PlgVkZjZ U@_ムhc7+ЊZVpJgo412ȇ<(ұg{l0zl =F͛UnÀ, Fi*,5(y xfgY\ػb h<\r"+ !pfIk~ߚͦmoox  mpuU9`yPѶQ:FՄU2U?ieEL-\݈1v\G[ːk^nm?h4fzlwmv.eˉZf+sy>)^hv 9s`~;S(^[  p>#CqOZDjjZ-]4e6cxҟez=3ZΓyӔZLr\.7 VE9"nN uV*6`cYE$es{z]cP[x X9^i`!o=[PǞMB`Gk_=zW_m?n'[L$'hyeIRQ19Ppo0īJbzݻg߷\n!"i>{ߟB!ԧ>e;Gà[_~ٞyhffO<}3OYd$'hejy 8B IU*t:u̒l0V~dn-9jPM 7(JuZW?Mp rX=eLoga@K=V('nEf~/1Yv/bu}|^Ɔ]~}yg2d@+3;8̂:o%zN&cgX@Ah4,֖ k6V.CAԬjPVݹYARYMGSA,-M@,Upc7+3Lb'=&i"p8Lʖ>^c 4MMge>,P?.+~z Aw J{b {uUIs;Y|GXyc($m_YSK[{˔e}sۥKRŋd>ɀV&f6q>Lve{g˿lЮ_nׯ_3gݻw'̍la&=3K4jr6 lkk˪jpj8y G2Ax 8M(gN+&|'`hq QA .\LjƚB4 +JQK2B5R|.=Uhy:382#gؿ*M:[X`aj| LGhCf`4rپomv۞y晥H3.dBa4[Ѱ .˗g ;ݻgZnݺpDvR)2TXf@RX V#m\}2Z NbSy2@aT+bcf9>tj<'L˨)=nj) rff,ƤZ16QA { " gV6KۏIZ4-Jn޼a=x&v TsL&q9j he'R[sVV5+GB!85x+ϗC6?x`c9<*ydaRMAV @ L*EW(M{5tlM3%@5bex*-il2i1 8C.Xt0Y5'7~YdoozM|ɅLO2I|oo/qRXV @y†۝{Ԭ= kZ~Sj1ӃSUGt1bƊLK&OF_rěB[LU1Aeb2Z /kgLF|M+8MYڱ8E,. ("@i_ّEeOigV*Ut {g3yx$Za b1-#n,Wcp4oK ˂ZUѰKY:C3fn7 0*6T*YOY^%wym4S4Elw=,(+iZgS,i4Χf_0ZmdْLlrAɗp4g6U(i@uBVfpݙURl^>b̒W/jc7ycbY ư(xruyX^/N=M1@~J^]PP%y1 ݏ08`+`(JPŏgeC<fqgg<<(9-;Әx@DS*jN-)|t&V2IBb &Xtwyvz)ZLrSPJt:{Pacph4J(34T굋Tſy x@YB!x(@~-2,5<업k`sPn/#~NaY_ l<=epXŀMk; +/lO6ݩIKchF }_,7Z8q~c& qċDPZPۇ2 4oezvګͷ+B<+=hdaF#Dvp8zpjv$z_8Nx>h9i̗ V(J~X~<kKV|ؠ1(HOwԔ@c4rX^E̼h}8|1b zr͟g.ɾ_+۷µz%6xٳg… vy[__~3~?0ZfZ1 ě\# |5:{&X9=sRcJɫ;+Ì׵,h;/ 1L4?Ep{n^vj3xGSc_|>`|Nbф90PV=/=J-<`ꕗ-vZX~S,r%[L,9j5k۶f?]xΟ?okkkV55aF&f&AYbF' |lA&/-JmdEq? jyl?k}=奣mq:^ڳ-خ?烊3<7UT*!Ȼ)yf͛!üfIW_7R1Z&vki𴀬L>yhV666vY[__jvz=k6cfG,yJp,^0Gv~wk-ϱl< +6&0rU͇:;`y(x1&I1F ʊJvh۶bN BqX6Ȃ]~WlĘc{cbL0$eC IxTU諫nΝ YJ%(vl6ZXr5aʘw  D&9]We4b +6{1vco@ ld<_5Y?hΗ8sqoX aC\nb!vz`ǁtX1u sZ~~ir9J`J63Kl+}s^ljMYtz)ݣsI&--ЊM'ENRJ2rp FoP@մx&(b62#V7xoy[=k=lN{1'΢p\y3S>fz?+^> [1-6 Q(@ 5U,H '<ކV.çRP(ZnO_>iRX\r;~L C~wtNɣ+-b9fēT3 [˗̙3g1payţR'aX=Ѕ{x( sJ9{iceeH0r^;s:jF-i1bO1AUOIpgZ>[Ugeeźnl*t1?E #Zٓ6fC b (*YoBgP(ؙ3gٳaZ--(p->MrPhwVf$FsJOD9 -0]=Ę,1e>j!9ܯYM-i ƱBvbŨ1 C\?]QH \2G;Wmmm5_"Qe [ Ĝ&@_!Y/oYr7.?3ml/BFګv[vfzRڻ]KɲZ,⤀Uv j5k4fjՊĮbhF#JSҺ# ,H™a_, ęmŔ+| RPLi^9Tu6T,ć ^%ƈLu:\B5oLVVm}}5PF;&Ulł*70;M3znZ-N< eC@?5ːk^nm?h4fzlwmv.eC ڣ T< ^[R L{Y}@=(6M7>ϧF,xa.Ą1M^{+(QS m13[iZZG70c:f,fsl:j&|>Duo4agΜZf>\~ߚͦ}<>{:OKG q6C܆@1ÀQė!o=[PIP|}ŞxL|{߳ׯ[Ѱߴ Ν;g=گڡd)Aϒ紕y& ^UլRcO  F;6|4Kk䉕+O]']#E?<c@q:1}( h{q]c1m|gTA thAЯ6M̱/B<&ON\.gZjUΞ=kgϞ lllعs󶱱az=,p9s}1+?=6b)>̼{y"9ߍ]+xlRifSWs玽=ﱕ?#?>{ꩧlkkˮ^j0W]E~>:JFKWA7I[KR8LQbZ5q~ Nb!OG7Q3`I p IDAT?Vnx,ofUHඊ)8ߧSnZޞِQӉ T=h;='p? :cs炏d4xpx'8x<`Fm~uH2y~y,i20y/})+WOO7ߴ . _ s-Rֲ8^E']F#+aSV C" jƢ'Z}wR `0bȇ\L8)9rZ&EocZi]<P{]ۘ㵡N,T\>0 2fI'#jho/JG*}  2t|_ A,x=w֟Y U}Xʰli6vܹ?,3K.#т9J9l`nјZ2\x>V,Y1J>Q`v(@+*WI?= d鳚>AցrĔV98@- Rmo~>V棖vw8e$-Z1YNr~8EL+Gr!gHZW>*~{{}vo֞+y [V=F =too/4jZaX,<bb- ŀRXy2*KSәK/m+4ϫr32-lмM+J "P+̇5c+`xr`f1 ,T`O/42fXdO|'d2-'zn|;-ۿ~>gww^xp_R5 K/^?lzQt(IOvy-S!׀`kkk#OtBԉ.`T{ G +.bQp9rYɟ?,PlDLY^!i?p<"1hdk$93"wb1Fk<dpY``JJpQ۝:iCf 먻gq#, {~,p>1={l1V,RLZ**kR`P]P/"PW*3ZBJ%wWbRFeQX nkv;8b$@+N۱& (xzhzۀe`Цc+*C5?r&O1^纂d6 gk*0@O̼`,Rzu'Of';F#vvfr=Sd8 *Jjs|8tLЮ W3O&|LonK`NQs$/˒eYޞݽ{{1FK/oaofK/4Y$Su2:e@eމe^fkQ+3X2ii %+iN)R0l1؊ V\/~v@4/[Z\1>ZvWX9-ak `~}}ݪժUUݵJbJz dee%oT*!o(cxwww$޸wیc {o~k˔Ehd7muu՚ͦ_]t~~}{߳o}[6lssr\a>KV|m?kE sӀbʛ̖v+8RoG\n`ë`Yh*-CLƍs;- lrJ_Z6+OZceaq,8ͰAF`a}8YCm?1F]Ύz ɡ8Y6sBl2>ɟY=ɰߢey=sv޶__o~󛶲bVgJRL/КS)ä+e QcfeT>0% CwWǤp͒;)viӭWwe/i@+--x5 M[@x,)يB͟ǀ?WiyAb&CCr9+JV.lH`FjZj 0`"3?jYlZu@$q7[7 bqy͕hY%~3ummm;;sL^1K=>MLӆWcuu… v̙pf C-3N&bJR}}8-56xc둦28Bд$ƎnZWcw 5-WX/JFS\n/̖ف2%'Yx|s}tp??^ld,˶|jZmJ?\.v"=0p~}$̙ޢ b4  hǐ]}b2R͛7w=OvwwƍVTŋv3X!105*.KxZ]]RQ']^@c"b cCX1}d1Oo uO3wx`+hդl&oMO}1gHGՄ?P. Qg)m[|Z2ΫVTl4;y퍝`0؄y3t1ec6Z\ONtޞ֛vbf2d@SDWY˥㜴jure-VŕJź@A;M@ t25؊>~>-mə4aF g38_eDG(cǿ9L O.eNpٙB2k xevN@O<Ƞnoo/D.6񪿝?1J]hq)Ŗw2~_u +]=<<#gQ\Xe2-0!lllXј`T7Z'Ibi**={lZTjF#y"F] \x4RPEt`*C9=63zƘ?N&Uze{x^ a%TX>MO<|ʖͳ@C͸1R`8`0lj` Zpݵrl acKktC컅tq:F h-@tE>M> :ybR.J?oV _ X X1?-~>6szh/hlyuՅ}[(l&L .%h#|b3zP-V^k_rxm前ꟄQ ʠt~L372  >`FX-r_0qB:xc. 7x~eI677ի~n߾e.!Y^g׮jZ D24F0iW*pvap9v: 4`E4#V@HZi#ҀyLO\^] 0{1Xx|(3ܧm̌Y;eh14 p)Q4̅R) / bdyv0 7nK.'mJ ~gJ.p_i۷oիq@V&Wjb+#.sLA)ҶS0P ϰ5c-T?K|`9=FRr\cƱ(@YG9gVXpF/sUC{̧GS5X>40+'A~N˕o](ԗ ΠJ${:q(@2-)ӟc. =0#=H,?~~{e2{fFA6p?2xVGͬM `5-?&2yFHCY do"oLZr1 f~v%vr ebs#61R =.Z(-dlɀ0 xCft5 Nz ӑT$-)ӴIWRcxEMfx`"m%i4!=? l,G x}կA~]4 p1pa!'fdyǚqZ#S{e9UZ^+iq9>302m1aX!UpA^ ihL2Yd@kA1sBbY\c[xca!3Ьe@+c h@VU&"p@ 3&e4WL1@l~{,䰲<:u!@;›Y"$V^ݮSQV~Z-޶^gRVWW۶mV+-5&!,c617PV1(fLE2 UU2FZ8LcfaeY-}Q%ƿ5 11`z!}U|i^ڶ b&֦}t,4(P\D x=́w ~YhXŷ,YicFDA9tJVrlr98c Mfٴ|>vΝ F )u$m҅32ԱLgXp4kjׯengg*]Oٕ+.t,[2 ɼ/!@Z-5&L6r\%ޚq|$S1+4fK=3c ({3:\WV. 2S+wJ&X?y`W˯? ǀ}` m3;Lʻo߾m[[[l6C:X>LbbUfÊ0Cy,;5ŦYYкv5v`Fcgͦ};߶k^[p2Yd@k9,;uIc4Jr\PX)6;`Ϻc'\ pl @n<'MEC |NQȊ0yaDSz>oкI[}Y[oi=V(&B>׾{3o}˾o[׳__^zɍIe,QSW_}7TQ-~S:famRZQV\3 +G5qz^ZJ6)4eU@!?(@3K뙅1 s"Z IDAT: Swl!,`6d7%ݮZp! 2lssݻg΁Kw+97[\Nry t' /t0B;t0V*`>VK vX,5c+J3̾/__ٟٟƆ}߰>L3|ceffGӎ:hIX,Z^u؈ޏMuէ-\JR%3A18J{=N˘cb0ph^Ҁ+1/3c3! <^~bP`CDA-LMu,ʀihajX,V>%VղĂc޷Hvծzvsgg18cl1P+Xe",'@dAr#Y{+l$$DB( /%JL< !gݯGwW1ݫާz=TS>o}++ Hu`y ʑ|^CuYeٰ!7JmgHˣ*^Swr5zO{~={077]_8:7bG0::q&Q&Ӌja;$뒲Oa ň^&iƟǂ.ce{*# a\1eOo({:]+ar\P1ibia&>$Z$xx8,~s X^?,K-RBoZ}djdDWV z=|jZJr3t*|_d2fݾZڮt0tn~\krrj5_T88p;BӬxKE׍5*Fu-6uSfbߓ܏V1% \|H@!rgt&1c#nH1&3jt:A] 1c_ڕu#$XBM  tj@DVux=L ,OkcD- *B;Ύ{8(fXc!56b4hS-{LŘ ҡ aϛܩbς׎Al{cJBnS5/| G"Jr\ 1I^K}Tضga֪vIE l:* r?r5$Q-Ҙ+(Ubuo>S;pI{C ;/b~Иf^{ Yuۿ[߯``cc>(/'C R uK*֑%=Bde@65JZLN,yo1g[bfaɖd((Njh.d{>WM A[T9#Ֆi>-;{KjSNa48}⻺跭7ߌ??ƻ.iٟ&뮾^۱;Dsp\?^nU<3c8¤+ѢK#D;.C;BY'^Gst:גN)DA)7ʺbFܪp9-1tyUt^KnݪVi(ѢcL^tJ4֍Eb 6UÔ(h}ږD6۞m]6lX(R?yppp/4a'hG˧DKUiu'jX>q%UV]}v.mJXPa###$UgTCW{ ( T*?Cr-}wD\VEKΙT3GPVWŎ{hh(1uXEvvįPmz_Zϫ[ 515"\Fz:DޡLKl{l`#Z>UT766:JXKUlP#O%ö"$UcO'JNb꠭Ws͑|#,_^Y8 *a,Ma'~:پ27;%-K, w-0/F*-1C3ISukY$^#VJTС.osIY"o?VgWfJ%a||fARU+&3žTxY&]5AIJ9@-_%Y'Oހ_~ z},jꫯzVN1^Ƙb;ML:60'f)v4l;c48>f,ѸΖ3߭bʉ$%fd屄e ]^GU[K -ɢ"Ju5a||G111fMĔBB#ں]ژ ]0=F}t#[LYUN@ e lLVZB\ HLS*MN t&.ĶVeH`˥FJcXWh;$bѥøyΝ;څ\'pԕchbtS,Γ.2&:"vGk;lvDIZ+T 刑,-KLqֵ;VW#1$62]ߒ9uCcHx$U1FV-!v>Fֱcp@fQTPThqƤ?I"nV7n۟nI"Ѻ#`5%ZkZpRz]1X$Zc_¾bQYާ+QEB|vh1vorr|>j5&j\122n7xsϳ9. }.cDB b vd3qX&ͻCTҥ/utSVKTN ԋ(b(bPF7Yڍ(pc:'~Pk98@ippb1?~ǎt R:Í`cc#t06Оuk/MRI< xOjZ!݃-k\hպ3պS1d|ZPԅLz( VXYYARA\(677144ZEd20@mc?DB;nDqEK@ըcddSSS8z(&'': 6\ L-VqQ*VH*7xYE޴\$ZIJƱh![ټB6_%v1dy!K)Z2賳j!J%ĉűc055$ HbI9ڧ*>URXr* ݈',&dS2J,%$~5VbLu2)Z|ZbfiGmۏUtL||f12ئR`'''q1bffE6 Ya̺mJ*maǶm[/ fŞ-xRDKݞ7>6p#ld;ϊR۠*W:QqDU`*l!hVR`ee%tHaVViWcDBa:cckT->G똟zgGf5a5B8 4i+q1;~кTd]=J\cU4%5]F N=^>;N#]8vFsbff.'ZTKnJ$YOT.C r2LR(vhMJ-5KU%ȱI~9SurrG022|>|>Fènw$TnGoG  m:cnss3-Q{m-ڍ%]ڦIemORbx2{KRA">s;Iz~ss\А,bspױԜh`QX;x;"Qڑ8 X*V KKKX^^"2j+,;?k%!`GFh``koˏF+V^OgcƋWkZ7֣-YĠNUHFfJkU(=pUCUASY sgff¹Ǒ绒,}~ZoV5HIS5ubuV!YZ*Y,ZlZGR[/q}I)c,yMjj;6: G}.czz-[F`~~>lsuaE;=4R٢lwʧ3AԎgMD禪 V5E낱d+[EEc]e5rBDZ DKueŌ5>th𴶃t:Q?~Ǐ,9a *X M]*0==G bDE8;h;gHUb+)>Җ3Ll{%1Us];b TVkl`eCkt2;ҔJHuS2.ǡ#Y+/4͉!@h,)ZL4oɊu7s1VCgjqlt:Fq6on`$vItKe똓 =؞;K]2,k.CPPm9<<ܡRYN%Z6`M,*]vݱ3?GF`¡.ʛf 199bD @06ù5lk$=hZI'V^뽂cla]VdYtafkC۳N$`l]>:gH*Ѳ cºA ͦCl6[DjdoM@PPeAU#qyIxND˖F+H\ VIT:[eAIIZV6Q1-d %/z.XSSSAuyXuadd$p_T*all GHH}m-Xc,%l>1nߗD+I T,'Vg}j?@72gY7uuy~3;Do)`~@hW4,v;LKBp 'Z{!*v[,c!t=؝Tv^SGږLuI؁ !fq#yUFZ xiYiDMn9=w:2[éb 2 9bCU”L/n_]]ER 9X?>>#GT*Db6( k17%Jb˒^cy2 IDATJېbs3՟3;Rlw[t./percܫ8:NjNB.[a uYm #l4W]us k8b2Ŏ,N'Y2;XilViJFm7B)\L Nmݰwlw'ObVVXZZBTHp266I7-Z!@و K|ԍe1&(dbL&ӡ:(Y\`kl>XN.MSS^y13辱$s!ލK0ލ/cϪ#=N4T*aRsze6VR[q(Q,+[w-N褶SX߻F1WJ>G;(J#YTvpHjLٲ:Sa6By;WKKK]WҪJ@|XX,&puMuĈ;G%g,.UlPUNv(:ktuT*T*j)2րp))fhh8q;IvLט3wMjnCڈ@bj!Iɕ] RϭdNzuDncsvY{O`}jL%to+AlbX{3F3`K.J%LMMd\jleeʼnn;X \QiP#4āDo[k`v{n`` ,Js)A{PΙD?"^*DO(~mm-jVWWQBbz {?IPT^U*1( U5rfҩƙ3mUSh2{Smcl{,iȺ>yr,iS,WEԺ܍|jy*YzL&zf~9 ajj ǎ 햹FFFPw&Ξ= ^E^\.nwqϥs7hp\ tdl#=**4݈pli|P4I"aJAKRz k$ջUbeo6`u ]-kͽł>1 %R;|gZV5cLlwr‚U,RMo:!A *aINsQ#VBRVw VG%Z:HK"tV, mۖR6܏h@˗qQ,--U)J?*y1221j}.cD`ELLLtt6@5ֈS5#3J%IȲZmdfuŎֺ$P`-Z n?j\4 IƗ0$˖-f0ѴeF$f|U1J BeH_Yiۢ ۍƫ$%UfcueJ [ֵnK5+rhzEI {=.e*nosiZ(h4XXX2VWW1118gE^}|ct:՞Ixޏ~qɞu|_*xGU s[:l1ʒ,UbnRmJ|uiɖ}nMhUDKc |\BJ"l=}t^26nc}ORtҌ~ʪkޯ$zÙ]+j}{{N?/~.?|W|h]gv{Vǃ$B :NLeI;Fui9ΖFK]2I$eRF5F5P1)S|TRKrkVaIu:SBe˧ϔhi/Kųvd),u s\{Q[nE5!19ܗV]jP.'krTkv`?1UΒ>;'Ju,wa>I`OhZxgCmGQV /W W3J"DCdBE 6md^KX16IHD*4f T%@n, ޷{$b;^Uau_}NJ8Z}x>.yNŌܪ*Z}jg Z2}[,GlXgU E;Jf`Ljhڶ; g=jZ=ٳ.@ Yfɉ'jXXX@>i/˗l6qVB JR+uح I?"4`}1:r(A-3%ZvV yccF+++X^^;`988Fνfhz': 7܀_|w_FӇ'i?-*C*ݐJmb'5pj44%Fl,h4APj:5.d҈U~7t.5I1Pコf0!iQG>b0F4y$laC\dŢ&lڑfbc077H *h$B [R ud(*o֍v9hq`a[7H>Y=wSkipVg8>cxg裏btto{~mGpuȐdv;r:CRm1LʟdLh4::Ok%,.{$&6M")jv.D;#V&MY%mORt1" ŔFZjL.M i^{ X]]2L vfɹ87G^٠wksd@m%X(iScəUS5K]J0\=Aթؠ1i<&t@d~c?SO=savv\3337:v'Z)=&Sc'F|>$FIVWWcjXeBIGdĔـpӭxmM"mhï5}* ݞD(kFϪz%,V*]a=r@^q:"x XZZ2f._*P,;]TvlYj I`l*ݞ^_]iIFn֒ؾv@a $t{LղK!4D^[Qg2] Ols]ox u_+Er+JR;~l6xױSN8D+6w(a6GU(ٲmolj*VUwII Ւ0 ^l')aI_އ<^?6+IJ{I7{5h:㵭SϯdƺcRA\Rp \.\.w(^󘛛o%ZT}=TKqX6i:xκggԫ`RsJجc{}tfѲ qlTN-q{8sXYZEfg߰_D+ҥ8vxAx"rw]H^Ǎ%=Gy>XR r!sd0P92Q!5 ѥ,KhǠ׌[b糮N[WVIucqX^I5ոfY`tt4[.]0$+g*jO,$+VΫkJ!SM0m͙6n#U͙ǘ "Fc16ݶT?V͍ bVtu}dUXmk7g~'o/z5[^}0%/ň>s'? &4+?U-Vu'F|Jl|U/D˺i}c#Z^{~Pg$ SJ;+ IhOFԎ|5 Ȟ~PظK-U!**jH5 ?$e;1:u;O,EWjpYLّ"^^[[CFV q\z#Ul{T7HmJ4~'nP~*apq֯^CU[*P+qy,.v#Zzm뼮%nVQ[.R rZ-4[`}p%h]'hQp%K6F) / $*)Au PxKrqR>apET'7v%zx&.[D9r_vj5${Z]]\geeoԄ\L˗177P$wIcqSZØj$dt'lllEel6DK'ukޟ%1-uԸi-"ъkߒ8 K*'ipN#Ȝƀ?~炬!aIAvM`Sl+[mвj<,1$ˎIR).h-62]'-`'ڻۅՕC7p)h]'݌RI=6پ9-n XmBMf;Uu!rAt(Qkcv{k|B踖Ul0v$K:k)kF=pL=@bb.CxZ(+S5p|V .nbF r* p$t#[N4nWsñ -wLųm=FbXVՊcHdڷ^ ɠh:2,'Z jB"h4[n".QRÒ=vhRPEXcACD#YZ>9B؉*ѽ Q7uƂc&N詛*U:1Jc]5l$Zkkka;-B2Ͳkk4aC9\.P&*N +: u3ވy|t@ruhɾ%6E]֛yHXR;厑2>a0Xe2-µQ?wql`ޢѨܹ ֎?Q- rV!VF:Qŵv9D54T;UVR U܇DKSaYy}ǦvK"Ie]uewܮϰSU/(N f]ϚJRd J,F'ˁh-..l6;( t6KRն*q7e>S;$ R ]b$VIb#D<`IlۻmV޾ImuW(2PlhMMMaaa?\PEL333r+D @770cWN$QG7b$ګ -NtޘN~"\}[-0+ ݩfz96 vﳽ%(`^罸~7Uln^u\=l Dҥ]˗v|> w}W߮8X__E h6]eLOϼeh4pχm>p8KuHfӘ9r8l::fsuNp8'Zp8}-p8>p8GDp8Op!677q\*ZOryt͸}.cDp8=gQG~###=SVo Ο?9NC+~t=N| IDAT_ĉ=_̙3򗿌U<x؎>cCu gvdF&pN:_w]^G'~++\r8ccvP>9<#җ8q|I|Sh9ñhWV >,z衰(*^x~ܒ*ኖ&2J!J]8v{npe4M<U,166 .ڎ&j/|g00BV܀lsra@Pr˾n28r8$Ϡ\bllɖnv1R`&뷾-iFGGl9oau866n/">/FӧO~*UW[*Y{U6o~O?4U:r8 =y,--asso{tW+Z[nwr_5EW :z Ν,rfffo|uN=Bߎ>d2Y|㏣P(8r8bhZpR]l_cuuNN=4N-> ~P(t8"rt";.^\.?~|8qcN=BB w//L |>dx /^#NtL«'ocD#lll`pp0`~_~ad2Y<sNvǹsgz^0cvNr-cjd?GE&3~:r8snǍ7ތV1tlBϥs7h9{*ZtGGx'd?fNT bbZqS=-~O~ͦa8[N=;N  :8w\.p^#0>kaa=qʯ Q* Nchhl6q{ycj__xᇱ|O<~i! ug#'|Aԩ[O#7~7} vb###r-pDdh4u1WFL&#<#뮻O~O\.3 ^z_p94k]Lq1=}ϟAQEqFΝLCcp?GE؏ݎa8yx{ރ\γ;G}ݏl6ss1==iΟ?͉&100BT*A2 ~g~.R)y ]Ò:Ξ=mOOn_^Ýwf&ubELLL\ _?ʗfqO̙3O}Swm;p8/Opߋ/~?rqUx+Z_7_Rx+ ?j ]/?l`quccp8Lӧr;Ŀ^)p86uWb~%)S9ۏYp83:_]wp@p:r8z\G/p8'8r8h9p Np8'Zp8}-p8>p8G3Ѻp8\r8h9p Np8'Zp8}-p8>p8GDp8Opp8'8r8h9p =3{p8p8GDp8Opp8'8r8h9p Np8'Zp8}-p8>a& ( ׺<p8/^ OuՍkZ@p8 LJ#w C׸hp8<>v%:kA<|x5-p8w8`}}Zp8U ~iip83