From 0b98f0273114c06d65d2a4f8296d29fc25b3f381 Mon Sep 17 00:00:00 2001 From: Programmers3539 Date: Sun, 24 Dec 2023 18:05:11 -0800 Subject: [PATCH] Enable multi-CSI-camera with libcamera (#1068) We should continue being paranoid about multi-cam with all these changes --------- Co-authored-by: Matt --- .../configuration/PhotonConfiguration.java | 2 +- .../hardware/metrics/cmds/LinuxCmds.java | 4 +- .../common/hardware/metrics/cmds/PiCmds.java | 2 +- .../org/photonvision/raspi/LibCameraJNI.java | 66 ++- .../vision/camera/CameraInfo.java | 83 ++++ .../vision/camera/LibcameraGpuSettables.java | 48 +- .../provider/LibcameraGpuFrameProvider.java | 27 +- .../vision/processes/VisionSourceManager.java | 467 +++++++++--------- .../processes/VisionSourceManagerTest.java | 167 +++++-- .../nativelibraries/libphotonlibcamera.so | Bin 167016 -> 166624 bytes 10 files changed, 513 insertions(+), 353 deletions(-) create mode 100644 photon-core/src/main/java/org/photonvision/vision/camera/CameraInfo.java diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java b/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java index fac672636c..cfd3b677a8 100644 --- a/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java +++ b/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java @@ -137,7 +137,7 @@ public Map toHashMap() { generalSubmap.put( "gpuAcceleration", LibCameraJNI.isSupported() - ? "Zerocopy Libcamera on " + LibCameraJNI.getSensorModel().getFriendlyName() + ? "Zerocopy Libcamera Working" : ""); // TODO add support for other types of GPU accel generalSubmap.put("hardwareModel", hardwareConfig.deviceName); generalSubmap.put("hardwarePlatform", Platform.getPlatformName()); diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/LinuxCmds.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/LinuxCmds.java index 1a7d18f35e..9abff7ce9f 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/LinuxCmds.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/LinuxCmds.java @@ -22,7 +22,7 @@ public class LinuxCmds extends CmdBase { public void initCmds(HardwareConfig config) { // CPU - cpuMemoryCommand = "awk '/MemTotal:/ {print int($2 / 1000);}' /proc/meminfo"; + cpuMemoryCommand = "free -m | awk 'FNR == 2 {print $3}'"; // TODO: boards have lots of thermal devices. Hard to pick the CPU @@ -32,7 +32,7 @@ public void initCmds(HardwareConfig config) { cpuUptimeCommand = "uptime -p | cut -c 4-"; // RAM - ramUsageCommand = "awk '/MemAvailable:/ {print int($2 / 1000);}' /proc/meminfo"; + ramUsageCommand = "free -m | awk 'FNR == 2 {print $3}'"; // Disk diskUsageCommand = "df ./ --output=pcent | tail -n +2"; diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/PiCmds.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/PiCmds.java index 6eb005acc5..ba72dc5531 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/PiCmds.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/cmds/PiCmds.java @@ -25,7 +25,7 @@ public void initCmds(HardwareConfig config) { super.initCmds(config); // CPU - cpuMemoryCommand = "vcgencmd get_mem arm | grep -Eo '[0-9]+'"; + cpuMemoryCommand = "free -m | awk 'FNR == 2 {print $2}'"; cpuTemperatureCommand = "sed 's/.\\{3\\}$/.&/' /sys/class/thermal/thermal_zone0/temp"; cpuThrottleReasonCmd = "if (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x01 )) != 0x00 )); then echo \"LOW VOLTAGE\"; " diff --git a/photon-core/src/main/java/org/photonvision/raspi/LibCameraJNI.java b/photon-core/src/main/java/org/photonvision/raspi/LibCameraJNI.java index 8a12aeabfa..d97d102681 100644 --- a/photon-core/src/main/java/org/photonvision/raspi/LibCameraJNI.java +++ b/photon-core/src/main/java/org/photonvision/raspi/LibCameraJNI.java @@ -30,8 +30,6 @@ public class LibCameraJNI { private static boolean libraryLoaded = false; private static final Logger logger = new Logger(LibCameraJNI.class, LogGroup.Camera); - public static final Object CAMERA_LOCK = new Object(); - public static synchronized void forceLoad() throws IOException { if (libraryLoaded) return; @@ -93,8 +91,13 @@ public String getFriendlyName() { } } - public static SensorModel getSensorModel() { - int model = getSensorModelRaw(); + public static SensorModel getSensorModel(long r_ptr) { + int model = getSensorModelRaw(r_ptr); + return SensorModel.values()[model]; + } + + public static SensorModel getSensorModel(String name) { + int model = getSensorModelRaw(name); return SensorModel.values()[model]; } @@ -107,54 +110,64 @@ public static boolean isSupported() { private static native boolean isLibraryWorking(); - public static native int getSensorModelRaw(); + public static native int getSensorModelRaw(long r_ptr); + + public static native int getSensorModelRaw(String name); // ======================================================== // /** * Creates a new runner with a given width/height/fps * + * @param the path / name of the camera as given from libcamera. * @param width Camera video mode width in pixels * @param height Camera video mode height in pixels * @param fps Camera video mode FPS - * @return success of creating a camera object + * @return the runner pointer for the camera. */ - public static native boolean createCamera(int width, int height, int rotation); + public static native long createCamera(String name, int width, int height, int rotation); /** * Starts the camera thresholder and display threads running. Make sure that this function is * called synchronously with stopCamera and returnFrame! */ - public static native boolean startCamera(); + public static native boolean startCamera(long r_ptr); /** Stops the camera runner. Make sure to call prior to destroying the camera! */ - public static native boolean stopCamera(); + public static native boolean stopCamera(long r_ptr); // Destroy all native resources associated with a camera. Ensure stop is called prior! - public static native boolean destroyCamera(); + public static native boolean destroyCamera(long r_ptr); // ======================================================== // // Set thresholds on [0..1] public static native boolean setThresholds( - double hl, double sl, double vl, double hu, double su, double vu, boolean hueInverted); + long r_ptr, + double hl, + double sl, + double vl, + double hu, + double su, + double vu, + boolean hueInverted); - public static native boolean setAutoExposure(boolean doAutoExposure); + public static native boolean setAutoExposure(long r_ptr, boolean doAutoExposure); // Exposure time, in microseconds - public static native boolean setExposure(int exposureUs); + public static native boolean setExposure(long r_ptr, int exposureUs); // Set brightness on [-1, 1] - public static native boolean setBrightness(double brightness); + public static native boolean setBrightness(long r_ptr, double brightness); // Unknown ranges for red and blue AWB gain - public static native boolean setAwbGain(double red, double blue); + public static native boolean setAwbGain(long r_ptr, double red, double blue); /** * Get the time when the first pixel exposure was started, in the same timebase as libcamera gives * the frame capture time. Units are nanoseconds. */ - public static native long getFrameCaptureTime(); + public static native long getFrameCaptureTime(long p_ptr); /** * Get the current time, in the same timebase as libcamera gives the frame capture time. Units are @@ -162,33 +175,36 @@ public static native boolean setThresholds( */ public static native long getLibcameraTimestamp(); - public static native long setFramesToCopy(boolean copyIn, boolean copyOut); + public static native long setFramesToCopy(long r_ptr, boolean copyIn, boolean copyOut); // Analog gain multiplier to apply to all color channels, on [1, Big Number] - public static native boolean setAnalogGain(double analog); + public static native boolean setAnalogGain(long r_ptr, double analog); /** Block until a new frame is available from native code. */ - public static native boolean awaitNewFrame(); + public static native long awaitNewFrame(long r_ptr); /** * Get a pointer to the most recent color mat generated. Call this immediately after * awaitNewFrame, and call only once per new frame! */ - public static native long takeColorFrame(); + public static native long takeColorFrame(long pair_ptr); /** * Get a pointer to the most recent processed mat generated. Call this immediately after * awaitNewFrame, and call only once per new frame! */ - public static native long takeProcessedFrame(); + public static native long takeProcessedFrame(long pair_ptr); /** * Set the GPU processing type we should do. Enum of [none, HSV, greyscale, adaptive threshold]. */ - public static native boolean setGpuProcessType(int type); + public static native boolean setGpuProcessType(long r_ptr, int type); + + public static native int getGpuProcessType(long p_ptr); - public static native int getGpuProcessType(); + /** Release a pair pointer back to the libcamera driver code to be filled again */ + public static native boolean releasePair(long p_ptr); - // Release a frame pointer back to the libcamera driver code to be filled again */ - // public static native long returnFrame(long frame); + /** Get an array containing the names/ids/paths of all connected CSI cameras from libcamera. */ + public static native String[] getCameraNames(); } diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/CameraInfo.java b/photon-core/src/main/java/org/photonvision/vision/camera/CameraInfo.java new file mode 100644 index 0000000000..568453bdfe --- /dev/null +++ b/photon-core/src/main/java/org/photonvision/vision/camera/CameraInfo.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.photonvision.vision.camera; + +import edu.wpi.first.cscore.UsbCameraInfo; +import java.util.Arrays; + +public class CameraInfo extends UsbCameraInfo { + public final CameraType cameraType; + + public CameraInfo( + int dev, String path, String name, String[] otherPaths, int vendorId, int productId) { + super(dev, path, name, otherPaths, vendorId, productId); + cameraType = CameraType.UsbCamera; + } + + public CameraInfo( + int dev, + String path, + String name, + String[] otherPaths, + int vendorId, + int productId, + CameraType cameraType) { + super(dev, path, name, otherPaths, vendorId, productId); + this.cameraType = cameraType; + } + + public CameraInfo(UsbCameraInfo info) { + super(info.dev, info.path, info.name, info.otherPaths, info.vendorId, info.productId); + cameraType = CameraType.UsbCamera; + } + + /** + * @return True, if this camera is reported from V4L and is a CSI camera. + */ + public boolean getIsV4lCsiCamera() { + return (Arrays.stream(otherPaths).anyMatch(it -> it.contains("csi-video")) + || getBaseName().equals("unicam")); + } + + /** + * @return The base name of the camera aka the name as just ascii. + */ + public String getBaseName() { + return name.replaceAll("[^\\x00-\\x7F]", ""); + } + + /** + * @param baseName + * @return Returns a human readable name + */ + public String getHumanReadableName() { + return getBaseName().replaceAll(" ", "_"); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof UsbCameraInfo || o instanceof CameraInfo)) return false; + UsbCameraInfo other = (UsbCameraInfo) o; + return path.equals(other.path) + // && a.dev == b.dev (dev is not constant in Windows) + && name.equals(other.name) + && productId == other.productId + && vendorId == other.vendorId; + } +} diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/LibcameraGpuSettables.java b/photon-core/src/main/java/org/photonvision/vision/camera/LibcameraGpuSettables.java index 2fdde391d2..45aefb5fc0 100644 --- a/photon-core/src/main/java/org/photonvision/vision/camera/LibcameraGpuSettables.java +++ b/photon-core/src/main/java/org/photonvision/vision/camera/LibcameraGpuSettables.java @@ -34,11 +34,13 @@ public class LibcameraGpuSettables extends VisionSourceSettables { private boolean lastAutoExposureActive; private int lastGain = 50; private Pair lastAwbGains = new Pair<>(18, 18); - private boolean m_initialized = false; + public long r_ptr = 0; private final LibCameraJNI.SensorModel sensorModel; - private ImageRotationMode m_rotationMode; + private ImageRotationMode m_rotationMode = ImageRotationMode.DEG_0; + + public final Object CAMERA_LOCK = new Object(); public void setRotation(ImageRotationMode rotationMode) { if (rotationMode != m_rotationMode) { @@ -53,7 +55,7 @@ public LibcameraGpuSettables(CameraConfiguration configuration) { videoModes = new HashMap<>(); - sensorModel = LibCameraJNI.getSensorModel(); + sensorModel = LibCameraJNI.getSensorModel(configuration.path); if (sensorModel == LibCameraJNI.SensorModel.IMX219) { // Settings for the IMX219 sensor, which is used on the Pi Camera Module v2 @@ -121,7 +123,7 @@ public double getFOV() { @Override public void setAutoExposure(boolean cameraAutoExposure) { lastAutoExposureActive = cameraAutoExposure; - LibCameraJNI.setAutoExposure(cameraAutoExposure); + LibCameraJNI.setAutoExposure(r_ptr, cameraAutoExposure); } @Override @@ -147,7 +149,7 @@ public void setExposure(double exposure) { } lastManualExposure = exposure; - var success = LibCameraJNI.setExposure((int) Math.round(exposure) * 800); + var success = LibCameraJNI.setExposure(r_ptr, (int) Math.round(exposure) * 800); if (!success) LibcameraGpuSource.logger.warn("Couldn't set Pi Camera exposure"); } @@ -155,7 +157,7 @@ public void setExposure(double exposure) { public void setBrightness(int brightness) { lastBrightness = brightness; double realBrightness = MathUtils.map(brightness, 0.0, 100.0, -1.0, 1.0); - var success = LibCameraJNI.setBrightness(realBrightness); + var success = LibCameraJNI.setBrightness(r_ptr, realBrightness); if (!success) LibcameraGpuSource.logger.warn("Couldn't set Pi Camera brightness"); } @@ -163,7 +165,7 @@ public void setBrightness(int brightness) { public void setGain(int gain) { lastGain = gain; // TODO units here seem odd -- 5ish seems legit? So divide by 10 - var success = LibCameraJNI.setAnalogGain(gain / 10.0); + var success = LibCameraJNI.setAnalogGain(r_ptr, gain / 10.0); if (!success) LibcameraGpuSource.logger.warn("Couldn't set Pi Camera gain"); } @@ -185,7 +187,7 @@ public void setBlueGain(int blue) { public void setAwbGain(int red, int blue) { if (sensorModel != LibCameraJNI.SensorModel.OV9281) { - var success = LibCameraJNI.setAwbGain(red / 10.0, blue / 10.0); + var success = LibCameraJNI.setAwbGain(r_ptr, red / 10.0, blue / 10.0); if (!success) LibcameraGpuSource.logger.warn("Couldn't set Pi Camera AWB gains"); } } @@ -201,29 +203,35 @@ protected void setVideoModeInternal(VideoMode videoMode) { // We need to make sure that other threads don't try to do anything funny while we're recreating // the camera - synchronized (LibCameraJNI.CAMERA_LOCK) { - if (m_initialized) { + synchronized (CAMERA_LOCK) { + if (r_ptr != 0) { logger.debug("Stopping libcamera"); - if (!LibCameraJNI.stopCamera()) { + if (!LibCameraJNI.stopCamera(r_ptr)) { logger.error("Couldn't stop a zero copy Pi Camera while switching video modes"); } logger.debug("Destroying libcamera"); - if (!LibCameraJNI.destroyCamera()) { + if (!LibCameraJNI.destroyCamera(r_ptr)) { logger.error("Couldn't destroy a zero copy Pi Camera while switching video modes"); } } logger.debug("Creating libcamera"); - if (!LibCameraJNI.createCamera( - mode.width, mode.height, (m_rotationMode == ImageRotationMode.DEG_180 ? 180 : 0))) { + r_ptr = + LibCameraJNI.createCamera( + getConfiguration().path, + mode.width, + mode.height, + (m_rotationMode == ImageRotationMode.DEG_180 ? 180 : 0)); + if (r_ptr == 0) { logger.error("Couldn't create a zero copy Pi Camera while switching video modes"); + if (!LibCameraJNI.destroyCamera(r_ptr)) { + logger.error("Couldn't destroy a zero copy Pi Camera while switching video modes"); + } } logger.debug("Starting libcamera"); - if (!LibCameraJNI.startCamera()) { + if (!LibCameraJNI.startCamera(r_ptr)) { logger.error("Couldn't start a zero copy Pi Camera while switching video modes"); } - - m_initialized = true; } // We don't store last settings on the native side, and when you change video mode these get @@ -234,7 +242,7 @@ protected void setVideoModeInternal(VideoMode videoMode) { setGain(lastGain); setAwbGain(lastAwbGains.getFirst(), lastAwbGains.getSecond()); - LibCameraJNI.setFramesToCopy(true, true); + LibCameraJNI.setFramesToCopy(r_ptr, true, true); currentVideoMode = mode; } @@ -243,4 +251,8 @@ protected void setVideoModeInternal(VideoMode videoMode) { public HashMap getAllVideoModes() { return videoModes; } + + public LibCameraJNI.SensorModel getModel() { + return sensorModel; + } } diff --git a/photon-core/src/main/java/org/photonvision/vision/frame/provider/LibcameraGpuFrameProvider.java b/photon-core/src/main/java/org/photonvision/vision/frame/provider/LibcameraGpuFrameProvider.java index 7b76f658ad..36ebe47cfb 100644 --- a/photon-core/src/main/java/org/photonvision/vision/frame/provider/LibcameraGpuFrameProvider.java +++ b/photon-core/src/main/java/org/photonvision/vision/frame/provider/LibcameraGpuFrameProvider.java @@ -20,6 +20,7 @@ import org.opencv.core.Mat; import org.photonvision.common.util.math.MathUtils; import org.photonvision.raspi.LibCameraJNI; +import org.photonvision.raspi.LibCameraJNI.SensorModel; import org.photonvision.vision.camera.LibcameraGpuSettables; import org.photonvision.vision.frame.Frame; import org.photonvision.vision.frame.FrameProvider; @@ -47,35 +48,38 @@ public String getName() { @Override public Frame get() { - // We need to make sure that other threads don't try to change video modes while we're waiting + // We need to make sure that other threads don't try to change video modes while + // we're waiting // for a frame // System.out.println("GET!"); - synchronized (LibCameraJNI.CAMERA_LOCK) { - var success = LibCameraJNI.awaitNewFrame(); + synchronized (settables.CAMERA_LOCK) { + var p_ptr = LibCameraJNI.awaitNewFrame(settables.r_ptr); - if (!success) { + if (p_ptr == 0) { System.out.println("No new frame"); return new Frame(); } - var colorMat = new CVMat(new Mat(LibCameraJNI.takeColorFrame())); - var processedMat = new CVMat(new Mat(LibCameraJNI.takeProcessedFrame())); + var colorMat = new CVMat(new Mat(LibCameraJNI.takeColorFrame(p_ptr))); + var processedMat = new CVMat(new Mat(LibCameraJNI.takeProcessedFrame(p_ptr))); // System.out.println("Color mat: " + colorMat.getMat().size()); // Imgcodecs.imwrite("color" + i + ".jpg", colorMat.getMat()); // Imgcodecs.imwrite("processed" + (i) + ".jpg", processedMat.getMat()); - int itype = LibCameraJNI.getGpuProcessType(); + int itype = LibCameraJNI.getGpuProcessType(p_ptr); FrameThresholdType type = FrameThresholdType.NONE; if (itype < FrameThresholdType.values().length && itype >= 0) { type = FrameThresholdType.values()[itype]; } var now = LibCameraJNI.getLibcameraTimestamp(); - var capture = LibCameraJNI.getFrameCaptureTime(); + var capture = LibCameraJNI.getFrameCaptureTime(p_ptr); var latency = (now - capture); + LibCameraJNI.releasePair(p_ptr); + return new Frame( colorMat, processedMat, @@ -87,7 +91,9 @@ public Frame get() { @Override public void requestFrameThresholdType(FrameThresholdType type) { - LibCameraJNI.setGpuProcessType(type.ordinal()); + if (settables.getModel() == SensorModel.OV9281 && type.equals(FrameThresholdType.GREYSCALE)) + LibCameraJNI.setGpuProcessType(settables.r_ptr, 4); // 4 = Grayscale pass through. + else LibCameraJNI.setGpuProcessType(settables.r_ptr, type.ordinal()); } @Override @@ -98,6 +104,7 @@ public void requestFrameRotation(ImageRotationMode rotationMode) { @Override public void requestHsvSettings(HSVParams params) { LibCameraJNI.setThresholds( + settables.r_ptr, params.getHsvLower().val[0] / 180.0, params.getHsvLower().val[1] / 255.0, params.getHsvLower().val[2] / 255.0, @@ -109,6 +116,6 @@ public void requestHsvSettings(HSVParams params) { @Override public void requestFrameCopies(boolean copyInput, boolean copyOutput) { - LibCameraJNI.setFramesToCopy(copyInput, copyOutput); + LibCameraJNI.setFramesToCopy(settables.r_ptr, copyInput, copyOutput); } } diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java index 970b1a6ae7..a84e6aba4e 100644 --- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java +++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java @@ -18,13 +18,11 @@ package org.photonvision.vision.processes; import edu.wpi.first.cscore.UsbCamera; -import edu.wpi.first.cscore.UsbCameraInfo; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Supplier; import java.util.stream.Collectors; import org.photonvision.common.configuration.CameraConfiguration; import org.photonvision.common.configuration.ConfigManager; @@ -35,6 +33,7 @@ import org.photonvision.common.logging.Logger; import org.photonvision.common.util.TimedTaskManager; import org.photonvision.raspi.LibCameraJNI; +import org.photonvision.vision.camera.CameraInfo; import org.photonvision.vision.camera.CameraQuirk; import org.photonvision.vision.camera.CameraType; import org.photonvision.vision.camera.LibcameraGpuSource; @@ -44,9 +43,11 @@ public class VisionSourceManager { private static final Logger logger = new Logger(VisionSourceManager.class, LogGroup.Camera); private static final List deviceBlacklist = List.of("bcm2835-isp"); - final List knownUsbCameras = new CopyOnWriteArrayList<>(); + final List knownCameras = new CopyOnWriteArrayList<>(); + final List unmatchedLoadedConfigs = new CopyOnWriteArrayList<>(); private boolean hasWarned; + private boolean hasWarnedNoCameras = false; private String ignoredCamerasRegex = ""; private static class SingletonHolder { @@ -60,7 +61,7 @@ public static VisionSourceManager getInstance() { VisionSourceManager() {} public void registerTimedTask() { - TimedTaskManager.getInstance().addTask("VisionSourceManager", this::tryMatchUSBCams, 3000); + TimedTaskManager.getInstance().addTask("VisionSourceManager", this::tryMatchCams, 3000); } public void registerLoadedConfigs(CameraConfiguration... configs) { @@ -77,11 +78,37 @@ public void registerLoadedConfigs(Collection configs) { unmatchedLoadedConfigs.addAll(configs); } - protected Supplier> cameraInfoSupplier = - () -> List.of(UsbCamera.enumerateUsbCameras()); + /** + * Pre filter out any csi cameras to return just USB Cameras. Allow defining the camerainfo. + * + * @return a list containing usbcamerainfo. + */ + protected List getConnectedUSBCameras() { + List cameraInfos = + List.of(UsbCamera.enumerateUsbCameras()).stream() + .map(c -> new CameraInfo(c)) + .collect(Collectors.toList()); + return cameraInfos; + } + + /** + * Retrieve the list of csi cameras from libcamera. + * + * @return a list containing csicamerainfo. + */ + protected List getConnectedCSICameras() { + List cameraInfos = new ArrayList(); + if (LibCameraJNI.isSupported()) + for (String path : LibCameraJNI.getCameraNames()) { + String name = LibCameraJNI.getSensorModel(path).getFriendlyName(); + cameraInfos.add( + new CameraInfo(-1, path, name, new String[] {}, -1, -1, CameraType.ZeroCopyPicam)); + } + return cameraInfos; + } - protected void tryMatchUSBCams() { - var visionSourceList = tryMatchUSBCamImpl(); + protected void tryMatchCams() { + var visionSourceList = tryMatchCamImpl(); if (visionSourceList == null) return; logger.info("Adding " + visionSourceList.size() + " configs to VMM."); @@ -94,62 +121,57 @@ protected void tryMatchUSBCams() { "fullsettings", ConfigManager.getInstance().getConfig().toHashMap())); } - protected List tryMatchUSBCamImpl() { - return tryMatchUSBCamImpl(true); + protected List tryMatchCamImpl() { + return tryMatchCamImpl(null); } - protected List tryMatchUSBCamImpl(boolean createSources) { - // Detect cameras using CSCore - List connectedCameras = - new ArrayList<>(filterAllowedDevices(cameraInfoSupplier.get())); - - // Remove all known devices - var notYetLoadedCams = new ArrayList(); - for (var connectedCam : connectedCameras) { - boolean cameraIsUnknown = true; - for (UsbCameraInfo knownCam : this.knownUsbCameras) { - if (usbCamEquals(knownCam, connectedCam)) { - cameraIsUnknown = false; - break; - } - } - if (cameraIsUnknown) { - notYetLoadedCams.add(connectedCam); - } + /** + * @param cameraInfos Used to feed camera info for unit tests. + * @return New VisionSources. + */ + protected List tryMatchCamImpl(ArrayList cameraInfos) { + boolean createSources = true; + List connectedCameras; + if (cameraInfos == null) { + // Detect USB cameras using CSCore + connectedCameras = new ArrayList<>(filterAllowedDevices(getConnectedUSBCameras())); + // Detect CSI cameras using libcamera + connectedCameras.addAll(new ArrayList<>(filterAllowedDevices(getConnectedCSICameras()))); + } else { + connectedCameras = new ArrayList<>(filterAllowedDevices(cameraInfos)); + createSources = + false; // Dont create sources if we are using supplied camerainfo for unit tests. } - if (notYetLoadedCams.isEmpty()) return null; - - if (connectedCameras.isEmpty()) { - logger.warn( - "No USB cameras were detected! Check that all cameras are connected, and that the path is correct."); + // Return no new sources because there are no new sources + if (connectedCameras.isEmpty() && !cameraInfos.isEmpty()) { + if (hasWarnedNoCameras) { + logger.warn( + "No cameras were detected! Check that all cameras are connected, and that the path is correct."); + hasWarnedNoCameras = true; + } return null; - } - logger.debug("Matching " + notYetLoadedCams.size() + " new cameras!"); + } else hasWarnedNoCameras = false; - // Sort out just the USB cams - var usbCamConfigs = new ArrayList<>(); - for (var config : unmatchedLoadedConfigs) { - if (config.cameraType == CameraType.UsbCamera) usbCamConfigs.add(config); - } + // Remove any known cameras. + connectedCameras.removeIf(c -> knownCameras.contains(c)); + + // All cameras are already loaded return no new sources. + if (connectedCameras.isEmpty()) return null; + + logger.debug("Matching " + connectedCameras.size() + " new cameras!"); // Debug prints - for (var info : notYetLoadedCams) { + for (var info : connectedCameras) { logger.info("Adding local video device - \"" + info.name + "\" at \"" + info.path + "\""); } - if (!usbCamConfigs.isEmpty()) - logger.debug("Trying to match " + usbCamConfigs.size() + " unmatched configs..."); + if (!unmatchedLoadedConfigs.isEmpty()) + logger.debug("Trying to match " + unmatchedLoadedConfigs.size() + " unmatched configs..."); // Match camera configs to physical cameras - - List matchedCameras; - - if (!createSources) { - matchedCameras = matchUSBCameras(notYetLoadedCams, unmatchedLoadedConfigs, false); - } else { - matchedCameras = matchUSBCameras(notYetLoadedCams, unmatchedLoadedConfigs); - } + List matchedCameras = + matchCameras(connectedCameras, unmatchedLoadedConfigs); unmatchedLoadedConfigs.removeAll(matchedCameras); if (!unmatchedLoadedConfigs.isEmpty() && !hasWarned) { @@ -167,11 +189,8 @@ protected List tryMatchUSBCamImpl(boolean createSources) { } // We add the matched cameras to the known camera list - for (var cam : notYetLoadedCams) { - if (this.knownUsbCameras.stream().noneMatch(it -> usbCamEquals(it, cam))) { - this.knownUsbCameras.add(cam); - } - } + this.knownCameras.addAll(connectedCameras); + if (matchedCameras.isEmpty()) return null; // for unit tests only! @@ -201,115 +220,64 @@ protected List tryMatchUSBCamImpl(boolean createSources) { * disk. * * @param detectedCamInfos Information about currently connected USB cameras. - * @param loadedUsbCamConfigs The USB {@link CameraConfiguration}s loaded from disk. + * @param loadedCamConfigs The USB {@link CameraConfiguration}s loaded from disk. * @return the matched configurations. */ - protected List matchUSBCameras( - List detectedCamInfos, List loadedUsbCamConfigs) { - return matchUSBCameras(detectedCamInfos, loadedUsbCamConfigs, true); - } - - /** - * Create {@link CameraConfiguration}s based on a list of detected USB cameras and the configs on - * disk. - * - * @param detectedCamInfos Information about currently connected USB cameras. - * @param loadedUsbCamConfigs The USB {@link CameraConfiguration}s loaded from disk. - * @param useJNI If false, this is a unit test and the JNI should not be used for CSI devices. - * @return the matched configurations. - */ - private List matchUSBCameras( - List detectedCamInfos, - List loadedUsbCamConfigs, - boolean useJNI) { + public List matchCameras( + List detectedCamInfos, List loadedCamConfigs) { var detectedCameraList = new ArrayList<>(detectedCamInfos); - ArrayList cameraConfigurations = new ArrayList<>(); + ArrayList cameraConfigurations = new ArrayList(); + ArrayList unloadedConfigs = + new ArrayList(loadedCamConfigs); - List unmatchedAfterByID = new ArrayList<>(loadedUsbCamConfigs); + if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) + cameraConfigurations.addAll(matchByPathByID(detectedCameraList, unloadedConfigs)); + else logger.debug("Skipping matchByPath no configs or cameras left to match"); - // loop over all the configs loaded from disk, attempting to match each camera - // to a config by path-by-id on linux - for (CameraConfiguration config : loadedUsbCamConfigs) { - UsbCameraInfo cameraInfo; + if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) + cameraConfigurations.addAll(matchByPath(detectedCameraList, unloadedConfigs)); + else logger.debug("Skipping matchByPath no configs or cameras left to match"); - if (config.otherPaths.length == 0) { - logger.debug("No valid path-by-id found for config with name " + config.baseName); - } else { - // attempt matching by path and basename - logger.debug( - "Trying to find a match for loaded camera " - + config.baseName - + " with path-by-id " - + config.otherPaths[0]); - cameraInfo = - detectedCameraList.stream() - .filter( - usbCameraInfo -> - usbCameraInfo.otherPaths.length != 0 - && usbCameraInfo.otherPaths[0].equals(config.otherPaths[0]) - && cameraNameToBaseName(usbCameraInfo.name).equals(config.baseName)) - .findFirst() - .orElse(null); - - // If we actually matched a camera to a config, remove that camera from the list - // and add it to the output - if (cameraInfo != null) { - logger.debug("Matched the config for " + config.baseName + " to a physical camera!"); - detectedCameraList.remove(cameraInfo); - unmatchedAfterByID.remove(config); - cameraConfigurations.add(mergeInfoIntoConfig(config, cameraInfo)); - } - } - } + if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) + cameraConfigurations.addAll(matchByName(detectedCameraList, unloadedConfigs)); + else logger.debug("Skipping matchByName no configs or cameras left to match"); - if (!unmatchedAfterByID.isEmpty() && !detectedCameraList.isEmpty()) { - logger.debug("Match by path-by-id failed, falling back to path-only matching"); - - List unmatchedAfterByPath = new ArrayList<>(loadedUsbCamConfigs); - - // now attempt to match the cameras and configs remaining by normal path - for (CameraConfiguration config : unmatchedAfterByID) { - UsbCameraInfo cameraInfo; - - // attempt matching by path and basename - logger.debug( - "Trying to find a match for loaded camera " - + config.baseName - + " with path " - + config.path); - cameraInfo = - detectedCameraList.stream() - .filter( - usbCameraInfo -> - usbCameraInfo.path.equals(config.path) - && cameraNameToBaseName(usbCameraInfo.name).equals(config.baseName)) - .findFirst() - .orElse(null); - - // If we actually matched a camera to a config, remove that camera from the list - // and add it to the output - if (cameraInfo != null) { - logger.debug("Matched the config for " + config.baseName + " to a physical camera!"); - detectedCameraList.remove(cameraInfo); - unmatchedAfterByPath.remove(config); - cameraConfigurations.add(mergeInfoIntoConfig(config, cameraInfo)); - } - } - - if (!unmatchedAfterByPath.isEmpty() && !detectedCameraList.isEmpty()) { - logger.debug("Match by ID and path failed, falling back to name-only matching"); - - // if both path and ID based matching fails, attempt basename only match - for (CameraConfiguration config : unmatchedAfterByPath) { - UsbCameraInfo cameraInfo; + if (detectedCameraList.size() > 0) + cameraConfigurations.addAll( + createConfigsForCameras(detectedCameraList, unloadedConfigs, cameraConfigurations)); - logger.debug("Trying to find a match for loaded camera with name " + config.baseName); + logger.debug("Matched or created " + cameraConfigurations.size() + " camera configs!"); + return cameraConfigurations; + } + // loop over all the configs loaded from disk, attempting to match each camera + // to a config by path-by-id on linux + private List matchByPathByID( + List detectedCamInfos, List unloadedConfigs) { + List ret = new ArrayList(); + List unloadedConfigsCopy = + new ArrayList(unloadedConfigs); + + for (CameraConfiguration config : unloadedConfigsCopy) { + // Only run match path by id if the camera is not a CSI camera. + if (config.cameraType != CameraType.ZeroCopyPicam) { + CameraInfo cameraInfo; + if (config.otherPaths.length == 0) { + logger.debug("No valid path-by-id found for config with name " + config.baseName); + } else { + // attempt matching by path and basename + logger.debug( + "Trying to find a match for loaded camera " + + config.baseName + + " with path-by-id " + + config.otherPaths[0]); cameraInfo = - detectedCameraList.stream() + detectedCamInfos.stream() .filter( usbCameraInfo -> - cameraNameToBaseName(usbCameraInfo.name).equals(config.baseName)) + usbCameraInfo.otherPaths.length != 0 + && usbCameraInfo.otherPaths[0].equals(config.otherPaths[0]) + && usbCameraInfo.getBaseName().equals(config.baseName)) .findFirst() .orElse(null); @@ -317,60 +285,123 @@ && cameraNameToBaseName(usbCameraInfo.name).equals(config.baseName)) // and add it to the output if (cameraInfo != null) { logger.debug("Matched the config for " + config.baseName + " to a physical camera!"); - detectedCameraList.remove(cameraInfo); - cameraConfigurations.add(mergeInfoIntoConfig(config, cameraInfo)); + ret.add(mergeInfoIntoConfig(config, cameraInfo)); + detectedCamInfos.remove(cameraInfo); + unloadedConfigs.remove(config); } } } } + return ret; + } - // If we have any unmatched cameras left, create a new CameraConfiguration for - // them here. + private List matchByPath( + List detectedCamInfos, List unloadedConfigs) { + List ret = new ArrayList(); + List unloadedConfigsCopy = + new ArrayList(unloadedConfigs); + // now attempt to match the cameras and configs remaining by normal path + for (CameraConfiguration config : unloadedConfigsCopy) { + CameraInfo cameraInfo; + + // attempt matching by path and basename + logger.debug( + "Trying to find a match for loaded camera " + + config.baseName + + " with path " + + config.path); + cameraInfo = + detectedCamInfos.stream() + .filter( + usbCameraInfo -> + usbCameraInfo.path.equals(config.path) + && usbCameraInfo.getBaseName().equals(config.baseName)) + .findFirst() + .orElse(null); + + // If we actually matched a camera to a config, remove that camera from the list + // and add it to the output + if (cameraInfo != null) { + logger.debug("Matched the config for " + config.baseName + " to a physical camera!"); + ret.add(mergeInfoIntoConfig(config, cameraInfo)); + detectedCamInfos.remove(cameraInfo); + unloadedConfigs.remove(config); + } + } + return ret; + } + + // Try matching cameras to configs by name. + private List matchByName( + List detectedCamInfos, List unloadedConfigs) { + List ret = new ArrayList(); + List unloadedConfigsCopy = + new ArrayList(unloadedConfigs); + // if both path and ID based matching fails, attempt basename only match + for (CameraConfiguration config : unloadedConfigsCopy) { + CameraInfo cameraInfo; + + logger.debug("Trying to find a match for loaded camera with name " + config.baseName); + + cameraInfo = + detectedCamInfos.stream() + .filter(CameraInfo -> CameraInfo.getBaseName().equals(config.baseName)) + .findFirst() + .orElse(null); + + // If we actually matched a camera to a config, remove that camera from the list + // and add it to the output + if (cameraInfo != null) { + logger.debug("Matched the config for " + config.baseName + " to a physical camera!"); + ret.add(mergeInfoIntoConfig(config, cameraInfo)); + detectedCamInfos.remove(cameraInfo); + unloadedConfigs.remove(config); + } + } + return ret; + } + + // If we have any unmatched cameras left, create a new CameraConfiguration for + // them here. + private List createConfigsForCameras( + List detectedCameraList, + List loadedCamConfigs, + List loadedConfigs) { + List ret = new ArrayList(); logger.debug( "After matching loaded configs " + detectedCameraList.size() + " cameras were unmatched."); - for (UsbCameraInfo info : detectedCameraList) { + for (CameraInfo info : detectedCameraList) { // create new camera config for all new cameras - String baseName = cameraNameToBaseName(info.name); - String uniqueName = baseNameToUniqueName(baseName); + String baseName = info.getBaseName(); + String uniqueName = info.getHumanReadableName(); int suffix = 0; - while (containsName(cameraConfigurations, uniqueName) || containsName(uniqueName)) { + while (containsName(loadedConfigs, uniqueName) || containsName(uniqueName)) { suffix++; uniqueName = String.format("%s (%d)", uniqueName, suffix); } logger.info("Creating a new camera config for camera " + uniqueName); - // HACK -- for picams, we want to use the camera model String nickname = uniqueName; - if (isCsiCamera(info)) { - if (useJNI) { - nickname = LibCameraJNI.getSensorModel().toString(); - } else { - nickname = "CSICAM-DEV"; - } - } CameraConfiguration configuration = new CameraConfiguration(baseName, uniqueName, nickname, info.path, info.otherPaths); - cameraConfigurations.add(configuration); - } - logger.debug("Matched or created " + cameraConfigurations.size() + " camera configs!"); - return cameraConfigurations; - } + configuration.cameraType = info.cameraType; - private boolean isCsiCamera(UsbCameraInfo configuration) { - return (Arrays.stream(configuration.otherPaths).anyMatch(it -> it.contains("csi-video")) - || cameraNameToBaseName(configuration.name).equals("unicam")); + ret.add(configuration); + } + return ret; } - private CameraConfiguration mergeInfoIntoConfig(CameraConfiguration cfg, UsbCameraInfo info) { + private CameraConfiguration mergeInfoIntoConfig(CameraConfiguration cfg, CameraInfo info) { if (!cfg.path.equals(info.path)) { logger.debug("Updating path config from " + cfg.path + " to " + info.path); cfg.path = info.path; } cfg.otherPaths = info.otherPaths; + cfg.cameraType = info.cameraType; if (cfg.otherPaths.length != info.otherPaths.length) { logger.debug( @@ -400,94 +431,40 @@ public void setIgnoredCamerasRegex(String ignoredCamerasRegex) { this.ignoredCamerasRegex = ignoredCamerasRegex; } - private List filterAllowedDevices(List allDevices) { - List filteredDevices = new ArrayList<>(); - List badDevices = new ArrayList<>(); - + /** + * Filter out any blacklisted or ignored devices. + * + * @param allDevices + * @return list of devices with blacklisted or ingore devices removed. + */ + private List filterAllowedDevices(List allDevices) { + List filteredDevices = new ArrayList<>(); for (var device : allDevices) { - // Filter devices that are physically the same device but may show up as multiple devices that - // really cant be accessed. First noticed with raspi 5 and ov5647. - - List paths = new ArrayList<>(); - - boolean skip = false; - if (device.otherPaths.length != 0) { - // Use the other paths to filter out devices that share the same path other than the index - // select only the lowest index. - // A ov5647 on a raspi 5 would show another path as - // platform-1000880000.pisp_be-video-index0, - // platform-1000880000.pisp_be-video-index4, and platform-1000880000.pisp_be-video-index5. - // This code will remove "indexX" from all the other paths from all the devices and make - // sure - // that we only take one camera stream from each device the stream with the lowest index. - for (String p : device.otherPaths) { - paths.add(p.split("index")[0]); - } - for (var otherDevice : filteredDevices) { - if (otherDevice.otherPaths.length == 0) continue; - List otherPaths = new ArrayList<>(); - for (String p : otherDevice.otherPaths) { - otherPaths.add(p.split("index")[0]); - } - if (paths.containsAll(otherPaths)) { - if (otherDevice.dev >= device.dev) { - badDevices.add(otherDevice); - } else { - skip = true; - break; - } - } - } - } - - filteredDevices.removeAll(badDevices); if (deviceBlacklist.contains(device.name)) { logger.trace( "Skipping blacklisted device: \"" + device.name + "\" at \"" + device.path + "\""); } else if (device.name.matches(ignoredCamerasRegex)) { logger.trace("Skipping ignored device: \"" + device.name + "\" at \"" + device.path); - } else if (!skip) { + } else if (device.getIsV4lCsiCamera()) { + } else { filteredDevices.add(device); logger.trace( "Adding local video device - \"" + device.name + "\" at \"" + device.path + "\""); - } else { - logger.trace("Skipping duplicate device: \"" + device.name + "\" at \"" + device.path); } } return filteredDevices; } - private boolean usbCamEquals(UsbCameraInfo a, UsbCameraInfo b) { - return a.path.equals(b.path) - // && a.dev == b.dev (dev is not constant in Windows) - && a.name.equals(b.name) - && a.productId == b.productId - && a.vendorId == b.vendorId; - } - - // Remove all non-ASCII characters - private static String cameraNameToBaseName(String cameraName) { - return cameraName.replaceAll("[^\\x00-\\x7F]", ""); - } - - // Replace spaces with underscores - private static String baseNameToUniqueName(String baseName) { - return baseName.replaceAll(" ", "_"); - } - private static List loadVisionSourcesFromCamConfigs( List camConfigs) { var cameraSources = new ArrayList(); for (var configuration : camConfigs) { logger.debug("Creating VisionSource for " + configuration); - // Picams should have csi-video in the path - boolean is_picam = - (Arrays.stream(configuration.otherPaths).anyMatch(it -> it.contains("csi-video")) - || configuration.baseName.equals("unicam")); boolean is_pi = Platform.isRaspberryPi(); - if (is_picam && is_pi) { - configuration.cameraType = CameraType.ZeroCopyPicam; + + if (configuration.cameraType == CameraType.ZeroCopyPicam && is_pi) { + // If the camera was loaded from libcamera then create its source using libcamera. var piCamSrc = new LibcameraGpuSource(configuration); cameraSources.add(piCamSrc); } else { diff --git a/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java b/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java index 4a995d26fd..487a9e5ae9 100644 --- a/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java +++ b/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java @@ -20,21 +20,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import edu.wpi.first.cscore.UsbCameraInfo; import java.util.ArrayList; import org.junit.jupiter.api.Test; import org.photonvision.common.configuration.CameraConfiguration; import org.photonvision.common.configuration.ConfigManager; +import org.photonvision.vision.camera.CameraInfo; +import org.photonvision.vision.camera.CameraType; public class VisionSourceManagerTest { @Test public void visionSourceTest() { var inst = new VisionSourceManager(); - var infoList = new ArrayList(); - inst.cameraInfoSupplier = () -> infoList; + var cameraInfos = new ArrayList(); ConfigManager.getInstance().load(); - inst.tryMatchUSBCamImpl(); + inst.tryMatchCamImpl(cameraInfos); var config3 = new CameraConfiguration( @@ -51,40 +51,39 @@ public void visionSourceTest() { "dev/video2", new String[] {"by-id/321"}); - UsbCameraInfo info1 = new UsbCameraInfo(0, "dev/video0", "testVideo", new String[0], 1, 2); + CameraInfo info1 = new CameraInfo(0, "dev/video0", "testVideo", new String[0], 1, 2); - infoList.add(info1); + cameraInfos.add(info1); inst.registerLoadedConfigs(config3, config4); - inst.tryMatchUSBCamImpl(false); + inst.tryMatchCamImpl(cameraInfos); - assertTrue(inst.knownUsbCameras.contains(info1)); + assertTrue(inst.knownCameras.contains(info1)); assertEquals(2, inst.unmatchedLoadedConfigs.size()); - UsbCameraInfo info2 = - new UsbCameraInfo(0, "dev/video1", "secondTestVideo", new String[0], 2, 3); + CameraInfo info2 = new CameraInfo(0, "dev/video1", "secondTestVideo", new String[0], 2, 3); - infoList.add(info2); + cameraInfos.add(info2); - var cams = inst.matchUSBCameras(infoList, inst.unmatchedLoadedConfigs); + var cams = inst.matchCameras(cameraInfos, inst.unmatchedLoadedConfigs); // assertEquals("testVideo (1)", cams.get(0).uniqueName); // Proper suffixing - inst.tryMatchUSBCamImpl(false); + inst.tryMatchCamImpl(cameraInfos); - assertTrue(inst.knownUsbCameras.contains(info2)); + assertTrue(inst.knownCameras.contains(info2)); assertEquals(2, inst.unmatchedLoadedConfigs.size()); - UsbCameraInfo info3 = - new UsbCameraInfo(0, "dev/video2", "thirdTestVideo", new String[] {"by-id/123"}, 3, 4); + CameraInfo info3 = + new CameraInfo(0, "dev/video2", "thirdTestVideo", new String[] {"by-id/123"}, 3, 4); - UsbCameraInfo info4 = - new UsbCameraInfo(0, "dev/video3", "fourthTestVideo", new String[] {"by-id/321"}, 5, 6); + CameraInfo info4 = + new CameraInfo(0, "dev/video3", "fourthTestVideo", new String[] {"by-id/321"}, 5, 6); - infoList.add(info4); + cameraInfos.add(info4); - cams = inst.matchUSBCameras(infoList, inst.unmatchedLoadedConfigs); + cams = inst.matchCameras(cameraInfos, inst.unmatchedLoadedConfigs); var cam4 = cams.stream() @@ -96,14 +95,14 @@ public void visionSourceTest() { assertEquals(cam4.nickname, config4.nickname); - infoList.add(info3); + cameraInfos.add(info3); - cams = inst.matchUSBCameras(infoList, inst.unmatchedLoadedConfigs); + cams = inst.matchCameras(cameraInfos, inst.unmatchedLoadedConfigs); - inst.tryMatchUSBCamImpl(false); + inst.tryMatchCamImpl(cameraInfos); - assertTrue(inst.knownUsbCameras.contains(info2)); - assertTrue(inst.knownUsbCameras.contains(info3)); + assertTrue(inst.knownCameras.contains(info2)); + assertTrue(inst.knownCameras.contains(info3)); var cam3 = cams.stream() @@ -121,8 +120,8 @@ public void visionSourceTest() { assertEquals(cam3.nickname, config3.nickname); assertEquals(cam4.nickname, config4.nickname); - UsbCameraInfo info5 = - new UsbCameraInfo( + CameraInfo info5 = + new CameraInfo( 2, "/dev/video2", "Left Camera", @@ -132,13 +131,13 @@ public void visionSourceTest() { }, 7, 8); - infoList.add(info5); - inst.tryMatchUSBCamImpl(false); + cameraInfos.add(info5); + inst.tryMatchCamImpl(cameraInfos); - assertTrue(inst.knownUsbCameras.contains(info5)); + assertTrue(inst.knownCameras.contains(info5)); - UsbCameraInfo info6 = - new UsbCameraInfo( + CameraInfo info6 = + new CameraInfo( 3, "dev/video3", "Right Camera", @@ -148,52 +147,118 @@ public void visionSourceTest() { }, 9, 10); - infoList.add(info6); - inst.tryMatchUSBCamImpl(false); + cameraInfos.add(info6); + inst.tryMatchCamImpl(cameraInfos); - assertTrue(inst.knownUsbCameras.contains(info6)); + assertTrue(inst.knownCameras.contains(info6)); // RPI 5 CSI Tests - UsbCameraInfo info7 = - new UsbCameraInfo( + // CSI CAMERAS SHOULD NOT BE LOADED LIKE THIS THEY SHOULD GO THROUGH LIBCAM. + CameraInfo info7 = + new CameraInfo( 4, "dev/video4", "CSICAM-DEV", // Typically rp1-cfe for unit test changed to CSICAM-DEV new String[] {"/dev/v4l/by-path/platform-1f00110000.csi-video-index0"}, 11, 12); - infoList.add(info7); - inst.tryMatchUSBCamImpl(false); + cameraInfos.add(info7); + inst.tryMatchCamImpl(cameraInfos); - assertTrue(inst.knownUsbCameras.contains(info7)); + assertTrue(!inst.knownCameras.contains(info7)); // This camera should not be recognized/used. - UsbCameraInfo info8 = - new UsbCameraInfo( + CameraInfo info8 = + new CameraInfo( 5, "dev/video8", "CSICAM-DEV", // Typically rp1-cfe for unit test changed to CSICAM-DEV new String[] {"/dev/v4l/by-path/platform-1f00110000.csi-video-index4"}, 13, 14); - infoList.add(info8); - inst.tryMatchUSBCamImpl(false); + cameraInfos.add(info8); + inst.tryMatchCamImpl(cameraInfos); - assertTrue(!inst.knownUsbCameras.contains(info8)); // This camera should not be recognized/used. + assertTrue(!inst.knownCameras.contains(info8)); // This camera should not be recognized/used. - UsbCameraInfo info9 = - new UsbCameraInfo( + CameraInfo info9 = + new CameraInfo( 6, "dev/video9", "CSICAM-DEV", // Typically rp1-cfe for unit test changed to CSICAM-DEV new String[] {"/dev/v4l/by-path/platform-1f00110000.csi-video-index5"}, 15, 16); - infoList.add(info9); - inst.tryMatchUSBCamImpl(false); + cameraInfos.add(info9); + inst.tryMatchCamImpl(cameraInfos); - assertTrue(!inst.knownUsbCameras.contains(info9)); // This camera should not be recognized/used. - assertEquals(7, inst.knownUsbCameras.size()); + assertTrue(!inst.knownCameras.contains(info9)); // This camera should not be recognized/used. + assertEquals(6, inst.knownCameras.size()); + assertEquals(0, inst.unmatchedLoadedConfigs.size()); + + // RPI LIBCAMERA CSI CAMERA TESTS + CameraInfo info10 = + new CameraInfo( + -1, + "/base/soc/i2c0mux/i2c@0/ov9281@60", + "OV9281", // Typically rp1-cfe for unit test changed to CSICAM-DEV + new String[] {}, + -1, + -1, + CameraType.ZeroCopyPicam); + cameraInfos.add(info10); + inst.tryMatchCamImpl(cameraInfos); + + assertTrue(inst.knownCameras.contains(info10)); + assertEquals(7, inst.knownCameras.size()); + assertEquals(0, inst.unmatchedLoadedConfigs.size()); + + CameraInfo info11 = + new CameraInfo( + -1, + "/base/soc/i2c0mux/i2c@1/ov9281@60", + "OV9281", // Typically rp1-cfe for unit test changed to CSICAM-DEV + new String[] {}, + -1, + -1, + CameraType.ZeroCopyPicam); + cameraInfos.add(info11); + inst.tryMatchCamImpl(cameraInfos); + + assertTrue(inst.knownCameras.contains(info11)); + assertEquals(8, inst.knownCameras.size()); + assertEquals(0, inst.unmatchedLoadedConfigs.size()); + + CameraInfo info12 = + new CameraInfo( + -1, + " /base/axi/pcie@120000/rp1/i2c@80000/ov5647@36", + "Camera Module v1", + new String[] {}, + -1, + -1, + CameraType.ZeroCopyPicam); + cameraInfos.add(info12); + inst.tryMatchCamImpl(cameraInfos); + + assertTrue(inst.knownCameras.contains(info12)); + assertEquals(9, inst.knownCameras.size()); + assertEquals(0, inst.unmatchedLoadedConfigs.size()); + + CameraInfo info13 = + new CameraInfo( + -1, + "/base/axi/pcie@120000/rp1/i2c@88000/imx708@1a", + "Camera Module v3", + new String[] {}, + -1, + -1, + CameraType.ZeroCopyPicam); + cameraInfos.add(info13); + inst.tryMatchCamImpl(cameraInfos); + + assertTrue(inst.knownCameras.contains(info13)); + assertEquals(10, inst.knownCameras.size()); assertEquals(0, inst.unmatchedLoadedConfigs.size()); } } diff --git a/photon-server/src/main/resources/nativelibraries/libphotonlibcamera.so b/photon-server/src/main/resources/nativelibraries/libphotonlibcamera.so index c2d0d9b496acfcd3b9b2ba2eff0652f0c8ceef2b..fe65d5813227d2226722443d22780b8093bafcb8 100644 GIT binary patch delta 53025 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

xM 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 AssI20 literal 167016 zcmeFadwf*I`9D6p8z5YSa0wuwY=Q^`g(M`9pvY#E5F&^%2}sqCyPISof!s&}1X0-p zR4mqJ)e2s!Azn&SZBqp`)@lrDQCmyFTW`7vqA0eafMiw7@BPe~v-@PT2QRGoMzvpPA z(C?8dc?9ls?bDL=xu2)v>351tznsH0Mf9A1rIs-CzC}q+zw7C)KsqYt_x(kwh`B01 zBe{%KO!d2}j;z}#@Sx&kEmzmfDxhV1ZdOKyY8CN?l%5M-Z zalUnkRcx(!IIUBRjx8~Zu6-+<4OSuQg;;EF6m^Rm)3#;DPhL2-{${gj%TN&?6=M-~ z;~(<8d&Rzs2b$08*AOM_BW%|CEmmS2x7E{VYTVn{UyKl2b0WpSq^Sd=BA@RzM^+A~ zwAcOfPEjgy>pg8kc!bp?kdgLlSN9He->dF)dkNReihc$6KjL~#(RBM0u0P}Y3laGJ z71x`%-of=QuD{{>J1+kF2Z;A^btse zU*q}~7ytDTfrc^ZjRVq*=LlT=7}g)pk%}IS`w(116+KMdiB%MPCYpZ36*fZMN8uig zYYeWl6-M#KIk`9j(4; z@%T^Y^^9r#_n^`jzP#tNu7^v@o_g_N~j-@7?-H!olaGr;d*G)!MAZPhaaO+CK5LtGsab%U$QB&wTNm1+kBh{%GgM z$z9X|I740eY9X{&zl=wyJ7vmURn6up~3gP`NW^v z{W?bprBVXx! z_Rt;IKXS@ zCwC`Y@ciGe9G}_#_3tior=(}^j(A|qvkzQ%{GK^&j}|Ak)LirJfK6A=8FlO1+|@bOPg3yb|7!{hx7>_CNqepVX9Uu___`wZeo81%cjiQ&t6d_s79s6jd7F|QBTzONbR`P&BV zb<&{UEr3l$)_s1zw}#L6HUm8zGbw!hKO5BRs6jbn49dA4D8!|2JO4WpuS(jn1^fMIR^4~8}zRM2KAa{fV&Os-Y6_|!j)&YLAy^e z(DS*FXE;794D9^9267Mv`HnXze}sYD{%Bx_?~M*$&XWf7kgNNLkN=#3{k#&>r*LxE zVbCsP4CGmCP_MxT`Mzd=PewlqSDrrr42Q=X^rQX;cJgHd{k+qlzx>po9e;0NH>Mll z9R}mYiw5J1)gb-8uK+ahs!q@jF26{sNL{y>A?}$Np+6?-|y9V`& zHkfZT8tCD>2J`vl2KagdIlMPLyc|v##IHyBhxd_!=JmFQ!T)U_&n!en6ZH9^JBF3> z7*UmB+-@imV#)`qBa+*oX z=wkV=5T8z=at;;`pVEM96z+pPj1gyvTIGb)9Y}6zS5fI-X%TfiOYwXBSphkX2}7h{QXX@uLq0JaxO(E z@sDcM;-Pr*_is6l4;JxX_QtP;lE`+sOT&vH68il@+Tp>X!K={?+D()W)5_yh<6gmUayM$(fCxPyUNOq}oe5@qt18!uUtO55{TbFaEl>Jik))b>5-H11Wz8`)Tn#Dn8j$ z`VCR>8k<{?@{fm*LSbVw^ikv zq4Hgi_N8*}Z`1%?udc=ewY&2+4ZmO2_h+ae>FvG{IqyOKJ0S;OnpQwvp6jg|pN!vW ze2zix^t%}Sj`;W#AE<}?70GdDu*mtfM%O6(_s|pKKQ>q6|Ej|O1wEnidn_8R^SMs- zcdL@8YEI!(dfu+|+^YDrDEWxV8lP`eeWy&*%HP#^z#x)AR4;fK;#@aNXQVSNF&(`#0YzY5FF`C?xp4K9EkJj#Qquq#qd!t4} zjpgr{^v4bs8ILJI>ES=2&m{jGwXyeHe=U5{EZVajE80W$FC2=0YoiuV=O3%^#t?j_ z0iR{)zf{gvrMIg2MVcD#_J5`EFV=42Qq?bxHEX~%N^WhaHi&svYCi#;J}!4?Yw>wBNZPKf)V!YK)5%|MyhA zJj!0_dOmHE7QgZfE#e2NUj0xWDrcp#+exZCi;Dz1eHTVT0oZS?)aQ{R zUg@oF53>#8N2`8h3yFV@LBD%Y#y?9$ss5X$^1j4?|F4vsohP)2v=>dk2{QheK6>7P zd}Y4~>A!Cb(r}>++YXJ5xPFR?ujX$TDEukhNgu3@8lc21ey`-bRmnL?#qUN0itkhL zA6EDWYMecy#@Tp<4=}K|amtQWDn5F?QL3CLl-<5q@wpN5pnRPn@dqlsb%ylE=T&*y zRe53+pR*16b&VQF_AC9_s^mY}P}0E1k~(#Fy;1*9%6W_qbl|r|@fiaJp!yzD_0{!xj_MaarB}M1%vE;aytx{mKPf)X zsP?rgJ=f*-vg+^g3dgXe{Tz;!WfkR)>Ka#7jl&@v+4+|_iriK1l_k|R?yCID(n~AK z-TAJ9QnwZsKEzR2>vF6pDR-5YT!)BPEm@RTlU!J{q0*gQn3+;k;aFc%n_b%lINI{>t3_gU0suz znVFH8x$ge}mxTWlTxOS)ud7(?&di$y!S$+GGOJjQt8jJp_o>JCNTiCLSCcr)kyq&` zg!pRQMUKjvDo06qWvQ#soz3MNo?|?hXJ(!&dtK(@C8M;)=r6j>?MC zlEMv{i9wnFx3m4_p&B*$XdKSv2npsS{$Dw|g` zf$M{ z=e5__Vglea3jH3#u@g%2=yehQ%diB{hb{Pne~% zltMBrW1=1{zog8#gz<*s9~v6)7BT9S!k(wQ2xq5!-T3_k{q$t zy7d_(x8!`lVp^xe80^-9klStRGleS$Ky`A~j#pAeOW9B$R~iYj-n ztd^FqN5^pFx~tcgxe;e&ZLI@CMs)?v*=sg9*2RbGu(Ra4Ag#RU3aVb{Y)Dl;Sw=#! zd7q-bC}kDWAW79V6_o~zSXfA^RCL+P%QG8pK1662-uv#(vT!d;c7b-s!!6_18f zcU2h{tTpa%s?e9+k6EjxxG?8khb(A?Sb$c`U-n;~^~yY%xO&ah!Y@oi8-Py;WIHG7 zwUQG6=wZB?eqa@vZ8#msr23Ov09v@vRb5;|17Y^6yqcs+S4ma&qE)< z4#LruWI$rPBg4JIwYIcI3oMeMdQeUkS>!4=rO;QxlHHtO=^ZsyE)1yIiwoj2wJQNj zflO@%l8Yq()i|V!r6;0%y(JdxPO7(MW|owcP}*QfcxA>|5y~vj5n$4rgWi)}g=^N9 zR4G%wD42r^Pl&IqqNPMliMu+~O7pN}ns=1ilgVF@AXSF(>fLFRXu%?RC15zAxlMkB zOr47Xqh=0`j^L4y2wPbKQzv6&7ouVbg&5g&As3Ps6hgeH7Frmdj~e*|d+Vss>^3IH z)hKzmw6uCkn5E^QVrS<&u-?jqisURp_berG<Sk-VaGZFO;| zJe0^16IQy*Y4PRmH6}ol97%bVuJUX-F2?7{u_duR|4+ZO_+tri}dZH^Oe+~hm^U?3M)521P~s&X3zsvh^Y-t zXk69QNgXR-#I;!PJZiD9zJmdfvtVrsjKMNK5a=#nSyRkI`1c0sq^!=q8Tm3d&lA`V>}1Q{~n%X?oN`W>0M@Oz3P&%#qP!2 zxY+*-(z5ALPAEF3qzY+)L=sSBj-_==8>~Fgk8dqWf#gxE+z|0v5C?r z6Mc2nt0D=Ba`NE7JixJ{D6>qIR20@|v$DV>F}0>bO%UZMORM1EiU3atra5D^1D3M{ zI}}(0z`I_!D&LVw@k&-=x=te$hG`UP4u&{#xs-_N@=DBgYF1E5X`&eFk@=Alc3hR> z4&;HSEX)olLn)BiS)~iB*X3hLUtNsFzPk!7iD|z!w^@jFekpv7%9&Gu?O576sVOX0 zNEP->vA3cqH#|Ek6z$oUa{_Uu2I> zS`=w}DZ!G4Wv_GHpJg&G-Xa_I;NkXt2w%F4dIghpuBV;ZTY5YM`iw z>mBH$0c*K_$xpz16bqPS2lfb39OTn4mE9U@jC9(>iZ2g91M=BtpDXMA>EDA8osX-hqG!G8jLN_JB4q!>CTcwe@tv#2lLnt&|qq4DIvAAL- zAWg+od=w*576Y?WQaKa@URGi&OA=JXw3-@MVR0ar#UDkHecF{!{d?tFR~4F2Te?f}mgl2Y;Qpu1X@_H1 zk)ZID<07{|?IVC*TJ7#7F|1ZrR8*B=i(lI=L<|i{pxD~FAjpk)Yi0m}kT=?+s-%D= z)|~UpATW2W7AV&jh|RH_3Yb^umR4A1M<`y2dwpd^73o+yyr2{*qsp~jvZ3lNz{;Y! zm{4h?RN1PPo|3TAii#i!-B@bEQXWerQ`MWbBKVw0mY%OJc!N^2Yc6{w_5v!LRHfu1 z+WyWifh(dUGZX8Z%N&((3}6v}^AfJLwb_^EE;p3l2qa9c zDqr;ksFX^eI*#m3@#A8^D1%ODI8U`+eG-=CRlJ@yxtxYx_dY9mkrxq*vHQT0z8>hj}QF?=AcqnyQmU(@OP& z(y5t^yXXg|MQdhNuI)813bic>v(?cwHSa8{#jyFk3x}rZn?Z2o!DPDECRfmi2Xx{)BLQoR0Fazs^#Q@Id=8^ijtzBQ=iPT~VcN{td8 z>D_E?tqY$YY^Gc1+Iy7MQ7{W*ELc|It;K9@xg4l|7#_Ni1La9b#*|JD{WRCbVm!>A ziYjy1O%=I_U9fJQG*!W-lUrVNRxBY|)0}`sl@Xb0C-4oO**FM|^FUe+7&ll3oRV9R zf#PFYsaZi(0s3E2UNOtDEHNPwJ^@$J26(JtKxdU#tUr?!65`7%YD!jYaNvNvVO~t! zVFVdqfz`QWA@>QwsidY*iHQzvM1!hUx;B(nKzC^iBU_*5!OcZ;YuHPzM7=dbdYHaf zr$*<1`tY54w%*7omJ1G8yC1RONH$t3+Ovof*#WSiaW+c$FX7!1T=fH)e^$dk@0!c21Ut8l4PaMe2UAPVh zs;Xpyr1;r{R@W32PM=O`B)PH>Cl3M~3N_r}rjrY(Pyu$`q;=!Z^s5f@5;zictYfN2k?N%)voH&j`p=^75q%In z2F2}%FAwW6&G$a0UMxh<9$OUS?cc$%o@v3m*VP!+89G z!1Nd4%7y6um*FA1n^&w2VJUysv1Nqz8)sDEi=USNFr$pv7rL zuaV)j{q*muThxoE$F^zs zG4GT_K2affrNYOC;H?U`hTz8(J~aeyROL?z z!N;oa!P`P`TR-hR%Y`9$zQS`taN8g)zB2@mSNw}Z@CL=FG6a8E;k6;SXOhN0O7YX> z*}7E2ts%HjcuEMqT;ck6jdeac?NRxfz;+>;gmj61`g`D^xc|*gbu777CG`{|QhfrMq-byI0e;*_i*Yzb7*V{Q1-@(cm zuQipdS0lq?8Q#ip^(g=u{~3lW6_NNphO1A?NPIuT=^O0&?_Gw|_v7{75r*3sevIMt zJ$L=r&G5kt7wSE2l0%Ok8TUwrf6VYGhJV9wE5qqq^7?No!!sD3!f^WDy#8Cr@QDmx z&hRXTI~lIOlTExT8Gb&CU(0Z+v;N!4@Nabx_ZVu75+Hf_E`|0OQ}v@Yfih zXBa+;#cyZ$X%>HL4~8N9OtAAD-_PRH_xJVRy9`&K#F0Tq7=BcbjC!s1VOE+{*BFhQ}~` zAHydz{9T40W4MpuX$(KY@c%IU7{iY2E{mzbJ;!WcXNy zFViFAZe{p>hEHYqwG5AExRc>23}3-;8^b#ozL4Q-7@ouM4Gdq-@aq`vWcZs5FJ}1X z46kIk`s9TSt!4OKEWU@~Z!x@q;h!>m2gB7TZe(a9!yjewcQO1uhPN{OHw=G<;jIjB zXZRBg-^cJh4ByZ2Hio~;@ZU4s$M6>zzEyp2JU2zgmV5)j`}n496#q0>7;c*XL*y*ue1N zf!G)a8BTw1M*lT3e1s0-zKh{gS^QRpk7DtkVK_b^6Zo|=9G{*F{Pr;%pK=NO_A^}n z1`Gwh%W!;JCh+qy9G`Lt{EjdjpDqjhjxl^f0Hwbb6x3UM(kA#*e|n4b8J~m+{302y zKItRFq8N@(4F!H<86F!zVYeARiQ!WjejdZ)8Gb&)Qy6}j;Wmcj(>sCRLWbkhHGy9a z!>0vMA(k^dj^R#*PiJ^B!!KZXCByN_sKBq5;WGj#^q=7~8NQX_Cm7zq@OXyrV0Z$< z8yOCFL*TcI;aKtqeyt2o3ZR&uFg%&z?F^sI@O=!Q!|?qK*T20)!S6CWmBsfld@jR} zF#IBhA7l6*8Q#tCiy1D=!G1B1;gJl#gyB&PpU?2I47V}d%J4LXPi45B;qeSlXLt(3 zGZ=1TcqYRaGJFBUa~S?U!XTwJG@jwtu=ptq{~w0i817*BLWc9dUzfvh7mL4~;ROtLGQ5!C#SAZEcqPO2zmZ45 zwG4N&_#TEIWcXHwuVi=w!;2ZdgW)9%Z)A8k!*?-!6~kK@{x60G%Z)bQZ!}l?~ zjN$tkUe54$8D7C~AHypdeuUvw3_r&3YKC_+yoTW-BB=js86L^-bqtSU_}n;#(Qs#qg;NznkIl3?IqLpTh9%EWVB5Z!>%$!~f3k9ER^;_;QBd!*D0V z?`3!~!|!8wCByG$crC+!$#4(DFJ<-J%J6+GegnfFVE7J(pJI3;!yjb$E{5xW^Ob^I z8Q#d^Kf~~y3~y(66T|m0{9%UgXZRxwf0yCEVz`gtyBL0i;mr&`#_$$~cQgFg3>V5T zMC;$j7#_*+#~B{Q@ZU0gEW@8*xRv2gGJGn-cQZVm;ZHFmkB9L#?K=vYQy3fj)-C7=r!oeR32(aS(LG5RXd9gMyfbQhxwK$|VW{8xaEW%Mf0 zc1D+jE?{&O=z2!41Kq^v>p*uf`bN-QjIIZ5jtJ&|Gw4`G{}i;H(YJ#xVDufJ>luAF z=q5(r3%Y~R4}k7sbQ5TEzhM54f{tZ$3urr|e*?OJ(NBP`XY^B`n;88p=nh6d54wxd z9iYwqgZaM#I+oF|fwnXH4bTOQJ^;F&(QkupV)WlZcQE=r&|Qq~0&N};%>P5sv5fu% zw4Kra0bRi8FF@Bb`b*GF0lH!MSD-r>{VnJ&Mw`=+f25v&!|?u~V;MaNw4KpIK^HK3 z1n7E3j|Sbu=y9Ms7=142E=I?KHV+Kue?I6~M#q7+GkON-0!Al-u4nXY&`pe<3%Y~R zmw@hKv>mj0P%!@mpko<*DQG*Rmw+x{bS~(6MlS>1#OSL)cQE=|&|Qo!0Bs%|%zp*w zSVpe`ZD({j=mJJpfv#utI?zpwz7BK;qi+P=#prs_<{`oSZw4L9=%0eNGx~PW1&qD} zbUmZ*2HnKydqHlytN=q5%#3%Y~R z&x7t_bO&hj&|vf6^4|Er!yFix zV;Q{)w4KrApbHpX1-hQm>p(X#`Z~}ZjJ^?c7o+Pzn@0unzZrBaqkjt8&gk1g7clw` z(DjVI8*~$+?*-k#=m$V|F}ew~IXamCqo89M-2&Rq=-+@YVDuB9>lytN=q5%#3%Y~R z&x7t_bO&hj=wSY@fR1JKYoP6negkv?qYr?tXY|{kn;89f&>f6^4|Er!yFi=A1oQt8 zbS$Gk0c~gWe?S*7`U}wYjQ$dI6QjQZ-NEQ@L3c6QoR0j*2J`O^I+oFcK-(ET6m$Wj zM}V$p^k~pcj2;KNgVE=L?qYN-X!F^@{Lcp+%jh`Jc1F(tUBKu>(DjU-4Z4ZZb3u1# z^y!Vy%Qe#R!NQ-1by0QfcyD#A@cJwxTjurjZ12P!=cgKu54QR3Vx0H%#*ST9lQ<@h zW%<3|_KfM1z5wa5)}Ztkenl+B0V zk#?>sM<4l`V`TcbzaxEIApHx?!t6Of>)?BkMpz^B(jZS-4_hN*QfW;rW$VlG+l1J? zxTj|fVY&=#h%3Z+FQtjWeZCM!+l9!Ffeys%&+@yF=9tCQBCw98bfNKMgm^vnn@tDP zhSY6~`6lho7ZD%lMUUA($ns;~18L2HwBh9KG+&(xp2@O|m05ld(#Sk)>Hf*5d$zv| z{jds8uG2g|m2{N!lb8RWsE=M3y2s|&+8mK$jm<3b(^01gPdc`LE#88C3)<`vVtB#6 z{%u7jk)+Gr9BK1+4ikR(5_g-2;JHwEZDRi0dOnob=5Kno>v^2s7}V$E7SSS#ZEZ;r zHvhy?Vom30CAVj?{65R5mKb4cyB=wM;9dBH#e0+R95@e`8THwTYdGrXTxDycv@s)9 z+CK%;mg#A+mJLm7Mq1fM*97Cc^!Nw*#{UvwfxL!_d@1iDw9$x+R1?bCG*BePqD)lo zuFWEKiQ+XGi99zS^YhG-hj?a`8$I= zyAbC5yk~czIii>RsV?imbBmtlHZ6_wM%!sAw~^xXLE`&qPoV8|xl!6;q`gW{`>>Xl z>P%^0Mmo=tp55WyN@rOl7%jNuQq_G_f zmQU|PBrj^G`G|iV@}fSf_v_;rZ)E?pW~9QVBy0RM+i?Q+EQa!ee5rn&xYPJD5VpoS z12zEkD&WpM*Bh}3t$-(wcZcP`6l-eitG+GrB_ zcNdtvl-^~wAe%mAUR3H0DSE(Mi@_INg^!|@Q zJ?=z#DgF&SKI(l1=o64Luj3J5RL8rK<}29-5vf#{C8xFa=m+~dDJre`CfInqb>^LT z{g7ryl&Se4^waZ3*_tCp4QZZ#y{S1pQuxWv+dM*!S-PF=*<@wRogG*ZM5pQ1c3u z$%!#w5^Vo`|D^h*l*t7=d*|>97t#DC&!olVpo=52wSS?FOr;tVDlry z6|O;|jcjQ<;yf8^^FM7idB5y$^KUYbK8pPFYoO1&%~a-5E%P8h(rJ1&4fY)PGwS}n z((k8`7W)~yI|osJ7>#~8`gO8L)X#mG|B>#W8c*|uS9YbNt%wh$HJ$Fsr?j6#SE>JO z>MxKUeHgkxve5b$Xv_tOeh2Yzs&2%i)5Ki|@Jw;1eATo4uXr|Uhbs|IVS9Ue&_?{}C}-@jJ)UqBx_JTkJ`8X+EkANK8_A2wrwNNz`;=!QI`EzQq@o=3L` zr|{Y_wsaj7ZIj7h%o?|uUh!EaU#0qcm?9@wWPK}rm%l;79mD;qTlrZlkM?XmjA_7qOAjV zJq=?;jF|1sMBHGQtt|~@m17e6p731T?KF#R(Y3JqiDGwSmo2q%sz};7G%b~M>{`gE z4zeK`r6Zs2y=L!`LNnGzV_RZrPWGf|dl7Lc{8xxaWmyAy;sjB(^K6lCLm7ngBJW)! z>&rEpbt|69_8eM``I5rYqeRxkvDymXj@KgfVhr><~1(l5fky? zI5Wy`vWd1d)n_JRzAe&4K9!{taaV(%ZEp7*JrAOfpxhTyIR`Il)@5=UbU1k|y+D(P z=iHz?#(~E?#P?l|JkZt?h3TjV>y%EkRZN8NzCvNDPM0IDHb#wZnLB~vk89}wW}VYL zrycf>%Gh(c@V^M%1drX^7E)ZYCs%=Abg8Y`zuD~lXMxGP$r61OeJ}rVw7V}4dVaI$ zz7w`%5Be2_Sur>CSZw|yma|(-XrC{;d$u>B&m2O%DL(P-fqwpp>Wn%Zmv(t1#U0^* zo=}_DA>CaFzw>4r#(mS#7r*Jre+Rb8EWW+NX&&>sZg&M^RMj_MZWVKu}5Iu+YfwvJ^h=zX2CWh@18v2Z!C`OruZJzp$qzd2=jRw(=xW$ zx{J05gqe@-giZG$jj#%DqY&GCbA1&tsQs zW8i#@ee=hPwnOuB^`pZNy;V6#l%f52TF1*xV zzCze0%b25e(6Mg#5@OKiR`l;rr--{MK+_m{Hu7tP4yTzsx#qEG>-n$)G%vsyO5rw) zXQl~a#0MA)i7)Xpebtly0i~Z~^OKI~`Y~IMozX23;7R?0cUwEIAbr=5Qg+jJp7j!cQG9kM!WfI2rr|s{_FX%6R=fu4eIvy~*4NZ>3&O~@pQGXr!Wejx#xTS=DdR-T zaqMwwFYuyqtP5#QVth!(x}C5~k(TZ>F8l*w83R(Oh$Q zCagbigj{L-6R`2RZxi;n zRlJ#nIlDkV@#JzJiIviZ!IOZ~diGlItH)Sn#(I_7kK&pzUVo(IuGg1jK=VbCM>_6S z$a)iOJ<5ghd2&g9>A>juyeJBbIO;-Qv`agS`Q|7Z8%#&r;nT1QQw!35?z33wDBcJm z9vl}cf07USS*v|tmS6X;kPgkqoXLZ^59JSkO>0bJ7RE)>Q6I{I@}-V~&N_BMj-cO1 z-E8m~)nH5`+s)+?fjVHW(pvaZmVXP%OKa!{h2>}(cz+*VI%bKG{%~DBdV9(_DC$i6 zG*n(I##Y%r`WhNzyK|^oLl4!~&{S6L6L-lm3HC$z$!ToT_0N?ka2(e&;>2We7p;AH z{w9nsQdTPONm$Eco$$ma$V@=5cscOgXDx3-96e8J56b87$j6U-s9sp_%l4-_ovDmu zV{|@CzSR9IzYnaHrJoEk(tItZ7Uux5TcCf?y7@}XL)u^ur0gQZFzSc+&O+htZU&HTxM$SygZ`W4u6FW`sl zmw^9?DezB(Sx>hQ!1@Qgoc;Q)qp>c}H}z=#k@?X5#t1F0$I@4pR`B8T)YBVZmh;p4 z{`TfA7?UQ!PI-i7n+tZTuDGx}7Iuo(&6I}hmFDw%c^B>12yy7B(Ktpj{M*T%?a$+x_{g>f zU!B*1lPDi#OKpt0VazbKeEg+0?tCQgqgrl2zob2y0u$Cb&;x;Xh=s4fg7HW|&(I%s zo8Sw9y;v>BbJE2qTk{me>9h=pn;iMly^m~(xKGr&yRGoekZ#5zevdG(ZbbhDFF)nm z2|0Fw7i>`5hZb|(UZgAB5pmxO=;w{FLy+46P0!(fMIL%zqC55HL*QYC{_sBK1n)Fp zQK(C!=$_*=i#A=?B`=H%F$hc3=M0`m?@7o@+OpuBp_Tm1(;){wSGYyVDH*taCKmE& zy9fHJpNrMYczWY=%^#qA6<~ z%_d*iIM^oY@1upuOZ}YuViSBQcckZlYmk^`!g}}m{=%O=K=?Ne6@CZy@-~eVepj6M zgR_3~#m;*3RjNNr;SDXWIIq_vBB2c`ib4q&@Y->Y>~2!Zh?*2{Wbbp zLAlnaI^`V8{Ca`xTjrMQ5YLIZm-x14_gm*1V9`U&r zZF>&cKjfK?JN4(E0DBSgBt9`%!?+Q56Xg0nWIaN~O~IYYNH)ekK=VU6k;abrUZkgI z2Ymc#2zwCgV<*Nm;*~4wF{;Ib^gNHnz=0} zyWsP_(bP=xBcG9v<_l!Y5r=e4^N;^=*D2Uu!a1hfHL_f^1cK*oDKU5W#=|w8Te7%BG4A6H)?w-N2XZHZnZhj-G%FunGt2v zalaJTr*rz14Z*z(7wxr-@8s#}4=Qru_8^PY4+!Wc|lWtC}2fr??e@V6u5iRw)a}kYK+B%!sgY*MBc{zM} zsV4CGz%mMR7o6{beJ-?ka_!E6UW_|9Gh@O9yNEM1#|K0l}! z8#LvG@gd-=9#solGFt5Rz*p@XB$DcsulmX3J=@<#T}cNjamV=SB|DLTdIsAbz7m>u zWBl3(+^C)lzoVXuzoVX$zN4PSA@#h*pq_8Sr$+Te-wo7r1L}Fds^@H0PxPO#^-M!O zN%yi4UXT8&d|s^rzCPXOWs|-%oMXV;n0#p;!md%8xo1e@JV8FMV44S!rmI@huR7=# z>CcyYwefN}%5c(QYx{Cjf6PlpxAg2ar9OS5?apX?S`qt)q#tyq<3I=MM$baZ!wj2+ z{tk@V@d$Lt1|90$3|l6Kcz-MFJ`#R|Q7z9Pz7KlY34NvY6l`EV>17OjKb?pduKmA8 znv)UTb9%5hGZl7_{Ovv1pK)RBdJ497;=bgRL-2Q=K)Gq$=vgN|e-&#CAN+dVmN6|a zfG_#Bsca50TJt%<53Towy$C-6ziKypXH-V=cawcQNp(azTBFjQ7OhX-#ah;f>nNW4 zpOX3-aJc!ps)FJL$gh`&7UrEpFE0P8H>{5BsdVqMnj{E72PY3KD?N3sA>k3V;+SIu;%%S20 zusxu$cNtfu@rlEj8Fw|Fe+vH2p`>G@rH+w(zX=%G$j1JfjimZ|bZjhaq_V4N(BT%s z7vXGGH1JtL-6lCbgs_vBL#LsSUt&*P>h)$#uahVC*6Xc^dmMd%+9O-bOV{Vt&UE07 zL%d%Dr*nQJcWQs~S(6P5=rh`Z+V(=oGuVF8p2_y3{(Tbh$);&Gdq8uK#he*nvM=QmV7R?lOg z!__lsR9}027U~&~_*Bo=@T}K!n?XI_0!}*c52RuCy5YO+b#&YlNCP_-*NVGZ->Px_ zRXMH`->z#UqHx(K)z|?$uJ_3# zgrAs>u><1=wV6J4tU!HyNM|&5tV6oBxN30elL^9@UjX3X3HVx-1;<_ynakM7)!zV)Pa1;E5E+OVj zLD?Foh-8W@;78N(7~t)b74FgSv{*4)#*4tbvPJB^4r^^6_Qvmq?$9{W_@GF3#o7GV z+f82QT%6B>zs#3}y>0BxY{Xh>HvAPLO8iyg)3H{J5?&X45yWE;@{dB@;+EWeU^C9L z{4CKvE$-K^ec_s6o7Oex=G?90Y}4WveE3E7lACi^;_~78_vQ)Heg?nl8wmG|v$bsj z)-@n{S`+Rs;`-0b(bEQBesk_8*f)&B{o_evr)A?_jO(NEXHUBY_x=NJ&i!E6xM^K; z#!vg;A)L`TB;b?s9QgDz(PjpZqsMW62lOXji?)v~rnV!4O>G~IG_`#=-qhwhCE6Yx zYV-StncAE`wfRqtF}2-tclxxCdPLjd0j9Qg%nSW!&)cB~*hh|=IK;Ll4d-Rsc8c96 z9x}=GE1d%yig3>bp4=SVJ8{0F@iA}ii;zV-utmVk?OSq9;-*}}j{_GMdU7)r9;5Jo z1GfR654;I$#!l=(Q+%46Qu|Q;o*0qbgmo;1QF$qBCF0Y3fXe0?EAmsOi)1I(!(-4^ z*e8qoJ^0>?^c1IY295n=;(mg8QA~Q2w-h*JlRF4JZcuS-86tV&0Gts^#vZ|rhTNY6 zU#G&_=VE+U^}7{(D;4fp1ik6N`IQmg+$WD=eA9X5iDa6C#aM?!&&I^nA`R8!?}+14 zaT>4E%0%ZTB2X5zU2cb`J~s{bYf!E$RXm>yk^y2mBr2H=$-#%c9$gRXAcu#Q94=vU=mC##dYBD4=<{!~F=nhIW1PdhS%?o` zM?(ze8Lr1X9&%3`GV&hFlN{{r-$_UOl-A&uuh zz0*`8O{ku~gYxq>ZVYMTe<0jQ&o|FJbNhB9EpOjXL6fb}?Mx%iRFa$zUaQGjz<)z> zrg{y8j4z03EOF5D>yd>mmSw2DqFlKm->tI=4FU8F0SDD-f>I_vnF8t$K7RKKr)Dn zr})6g-cmj`soVPnYUqRKV60JzWp>{uNn>^k2Cetn~|Q{QOXnjzoGYe$Ey>&O;2@-S-0cAU$jw$|HmTo}EML3%Ee zP`#dva09(=*!vxNZAV%zkJ+HZ$zxWQk_Xf4Jji36l1CJi$6vuCoII$1OFI!^YukbM zG^pO^BCe5ad^jV>Wkct3Li_yRk?)yg^B$G2mv1_#E0Yq>tSetrotUnC23nVm3FoMH znoZkgQJ=p=*;e?b^*%oYvYAPFLswqKv#u*YHIU7lz^SfOA47Q@?JbY1!^q=qq(759 z?xivr$Yb+&$m40GmI4mj|7**T;mBGy2M73*_-DC66>F53_+h z7AtvBeg6N*V<^&}Ngg8*ZX}QN?~n)SJD0~K(BbAlYbR@SAnU~79LNTF6e@X~#!0!VttXp$8fcAmj4w|ov z#!&h>o?I_**8z=h7o`Pu8!)^J5?E_Fgjkb4L<}!%tshs9`-7nEty{(wUT+?0$5n)M zBE^$?qssFd&`!|T5skIzr=Z(Glf8AlVr#QLSC{({$txA>FJNR>1=f4t0&@Z@RT!;x z32#MuTF)&3-j47h6)xujjl*S}Um*_kH_kd2>raGxZnix~W4Ein?GNJJm}_Hjkv(-H zoW?&ci#I<7U*N?5Bre-U82@qi&>D4kaILyQotg0s4Zl{sgX)R27b7iilL^4B7khGN z1E)3_1=<5T5wxs(l&$Rr)ZGW{LWRk?17Cx{g4X4JEAp{k;>jHe%!)WSQCUC}f7csY+hD%By&c#fU{pStmlz@Q zx(xBjuWg-=^X&*P>~EU}|5zM+OKY&s8*Xo{8)p+!&UN8=6R?=p&ErfFmkhH46Sz}5 zPMcKjJdQ6|;o)qSS=xn!C@pFkVy z_`g&<@*hyWC=I?}An$Lh`~y}_d8G-2;cwee$7(|$C)G-jdjBG`a~P{=ke~-Qs~bfeD{a?rnIx| zBfMoe<40%E4#Cb6Mqx45v%TFT&0f4q(&k1ug;AV9I_orVdW2}BGvDZUJX^PE1rRLl`|0Iid`oeu*EV}u_FD}I%K?%aP;x6|R;9Eb0{fG`+hX$NI z&4RY*nl+B*Fb9rS2tVy9d~)@KX&&pY&{h39=)*b}jCm4{3hjiKr87 zhWg%u^v~=k|4a$+CX7uU#QzEOFco>xyLN=tqV4Ts?CY5Ceg3CWB7ba-*t5PI{nUK+ zwo|!yzwtfMoDUmtq7Y{Qv2TB(X9#@cmei@>(X}!}J{3XvT!8St@;N&wpD3fX%8=$x zwB16a@7%UHAfJmt>+<1il{;%S`Pj}1%I7DL&-F?^qe(u{AxirWc!ZNr4CE79zhg*u zruv;g_?hZAY51Az7Zp;!2!s0Ntx;>S3xf5FLH#mR{occ~UcUzo>i02lD|Av|)8|{H zlQs}GZ3>>>!2CrEJIi|tH0=%4p&wddLt`fh|GB`P)Z@U<{~sO)b~N@p;g1H^s>i_| z!(ko=HZ}GFypsp)am1ni@n<}lJBTr;dj(;RY?8FzJ%*{PhO+!QfyE!H==$IsQRU_`uV{lT)#_4 z{m8z^Hdf0awf2QHtHx}PJqO!fN?Okp`Ue>ccx$kMbkJ)YGyqhO@ zb4_?JjP^EjfO`=BHts&8k^8V6LXKxYqp)$<|HGa5(RgITb33rtaM$;M39kYz#(Q$f z-V&~lU!|aJz@NvxaD;7I|Cr6WU6%fFJr?1gf;~~}k;?r@;_)KN>OtHm5jRklSG2NR z1sQlIcyb>@7?p+Uej4%Hf&GejW9@9Z-1dd6*me?gZY z`5UOLl~iY|C$|8+0(E;stJ^@xP{eq0uSS?I!^aTc25cFyLacFh`H?J1W)TSYAe{OO z(XF7dK3cu=8clYmH_|!pbeuQLz+5h~*@^cQj_28NUM<4gZlP~ljcmCOayPPL?FV|> zu{&V-ycoydp&Q{+qYkj7CWxqhY=rk@wstiqf_FyESl`3ZF34V3o= zg?q*W-=^sd$!ER7ZIeXuA-@(fJ=@FmIPoHx>RknzbcggN4fCMuu~$#_DIRr0 zxmtYSUq1}*9zd^`SxnQqriwkD!I(>6AHTz-?c@8b-#|deoTjn55Q*hnhdtAYuAH~u4 zE$&zO#GNUh-M#bK34cWBJn=)w?@aT=PY~XBya~<|Cq*006SR3^f0U2U6Tbq#u;Wd< zHr})k4~{ojpl_dx_|%?n;#r?3{sKJ1+2i+t>tn3k>k63@|2m|ujUjX5-%?(^eD%S4 z?!~+E;eFFuJ^zHXyq@i#!`5?}R!@O_9eqFgUmqb~${Y2(m(}xXgL*y!oOFTeW27(I zdGnyYkdGs@?N=f_Z~IVrtfn#<$m5~!X!os1%jI!B=y39gj?v`d!CsCok9#1G=O71? z$1*05F$VH*DS1$RjO5|@ckljF97Z0Kkp4{an1XO4c^vupnlL^PTc^!Zv{V~6YHu&_(sV__#QX@ z+;m^R+Ktj*`4HxaPUZ_N0v+^MTBI*2rGf;n1?N0t*`Yz3Q zYV*ye`?dq8@r}-zzKXKb80T86jdAb`kLkjio&2I-A4e|!qR94pH{K%d`#JH% z7)$MTFV1YjZ@qgn;+z-hX{#e&af9W)akVkst^$mo_+HUA#BW5IAH?+lu3zH1AJ=`j z$R6HhDjGY@VX{Soc$W zfp_re8yFNn9`VUnsJ~-z3ePkLp!{e)Fjete27c6r@!%7KH7SM1hzM^4X!J$Zj?y11 z+mXgEibH-B0{;V8_w! zfjWTJ{lu*#AGAZ~A?7DmZSgnCHw$5fEbMYk?gtPSl$-PuOSydtez#EhP^PW8Q=cZ@ zLV!mO1i1vgI(<1AM5x>3+Ssm*XCgU5Dr;yGWJxZKMsvsqZb{ z9>|d7^=HNZD)6Ht{UN&;i9(NfIF3EG42a-QM$XeblUf2@Sf!= z(7a6xRhmV(b9@D8nz!i}y$?=n6neLl&U|A{9QUOq65kUrH9z_2FRl`4sPA}u=j_Ib zd7I7`Yo-mucgJowt$7vSDcJPDFiE4WT3wgFhy*mGDqUQW~#%h z{c&!N-g7L?@>gFsG0)Xs3?I3$I>ofml|t{L)kTQZyzJx@^5qBLiG4|)yL)<&7@mZ( zAiba74WN7-kUiy1<&gP{G224(&c+#Xi%9Of=Nhx;?5?9nOoRspu5UUqr zzmMv))!&m}-64ik`K!+rBaF&F2QoZ?e0~c(coX$Fh3hdqpTK<#!kY2?G~_W#)$vzE z4;91n6;1D5KRrSWuTeCe8H~a6Zd~`^`SeEZdmXwiMM5?nd`pGosDH}>`xp9mZ{x`K z`X#@Ndo&e z+Iy~_AbscHbn?5(G5S4~*Z;@fyTC_TT?^lPW}aLi2?TQGqC5e_fT#fiB#1J(prEJ} zsI`{UOcDYFLlTp4$HQ=GOADtbk#JF?j7lw}w1-+$I4`YHIX(2i=}Q%r)>Ma!r?hZ- zu%afKI^Taimt-=`sQtckzW4k6zKIU|dDdQg?X}lld+oi~zC6MHE#rKvi*?qJbDy7{ zXZ;a;X?KU-gU+jpbu-nvinbrlM!jm;6)DdKbfN&c8u@;~xzG13o@7mkgP=QYIUv7z zXDm5v6*%SnXYvqPNq=OwWeG#&?*&iR0@g$0%hbVQ=$}RYLR-#Owpn@G_Oy!lFRX{4 z^?}fO*i8E1dRWW5p?d|osVa5LZqDh>zy?cs1~WDvHkEhm{D;^Y zS#uItCi@e7&cR#Qm;Amz4f~BRTYQtn&|F3vSVLJ}T5?MsL!(DJ?{n{432lYX`z5Ek zc#Jb64dA@!%-r&>GxegI^DQEiVcf@B&bi+-+514;OW1oa>x}029R{`D2yIEX0@}Nm zOvZMzc5K^hd(Sjys^n~c8@{a<(>q=q+_CQUfOGK0qJe7OC)jUyocgK8GmHDiIOjBD z(o$$jIdtGsjui5l=2GwP8SMVVPkZR*1wL(F`hw#j9eXg0_3l#f=~G`m=r&NdtEt;J zX{)CObiBBm^UO2oi%wZ*kGFucW{KOlKe{kZH9f#Q!5Ocff7dzWqL;o?Xt4*_xKD2@ zxT_7>U4BHV!N-?Dy8+t6xH}`8eszdzFfugn3~wOaZfFeWY~h-I9WOS2vhH!z`e`+i{Fkfe?muaKYU^QYJ%n_oF6Ej$bSoA4q2^WRzW zDnVXu?m0TknD}4M1$K4-xAj41tYGT{`)tF?N!qhr7MZVzFY$SBMFtM`sUHOm%bo9Ys(hhw=EJ}|o4&}0eq`RK)UBb< zuGEWt{uG#Zh%~ySSu!P0be6k5jCb#nygbn}89Rahh{uxo59IyNffqi8PdnYOB#koj zmA+A)z3?FW+Qk04%$Z>7?=tmoVS_tw1c zk?|{bO=Oiuc~>CQcc>Serm4#&&G(V1q*06~C5_lSNf&3Olf6Cvi*_eAXPs;C#b;=D zpP<8Ha~AOY52Tm%>Qh!5+TBenwe_R3oQDMNM^2K54}767`TQ4gtKduOK_RbKz{zr@ zZt**vM!9|Hwv_ul@LocuJ-RT+upw*6cZFq>WPE4nB|0i)ea0xO=;@?9(dB>sB=EvC z$|JUc^DgsliSKxE-3s;oF~*yATa&xRUieGk;9jAHzWbpi_oEA4|B~E1xr0D|up!UC zWO!aX&xV|7!@)9p(%{M6xsiCU)6Bn_=k3lx$6uoSZu$YU{Aq@a9|8XrXWDVEGmW}S z-@+Y;?@Kugk=YL^x0FTNoOj9cJnxc;dGcPxyIoef1JAp}lP7Ic;wmJLF{IMtno1}y zbF!22F84Q|rB1W_F-^)H+jNP1O}v<QP*&}J_ z@JrlDy;tT=m)5@i4*AJCN*Q_V;cn_u&V+92DtNt+yfPBCEi&)(8uj5|T|1+76ms*# zx{bSs-X`tp`xxV}7sHS26?b=K;I~uo#d)Sq|B_W5UijY*e*%+n(Z5nZ8QekrJnJ}5 zBeRdWvuj0S2Ki)M{1y33lY26ZyHqk-6Ua;U@d-@MF6bjDzj7W&FSlsKiC;v~Pb_2H zBD$+o*CW)y#hK8V3U3?HAEScjwdbuRm7U zobvek)ny$H_J-=fJc_X*?=HeUp9P-xA=hCl{vvSW?yQaEb?_d!pWB?j+TW}3k%y#x zhJ3!WdU%HiT5j6Q#?_-bxaY3*46+a&clUF3TtMf=2Pm?UvWRRxgy*UJzQNB+z4_8r zXD0F#{g(bpY;`kXu)Ji?bHVIyvks>o(zW%*eVuYg-!$$h)v;aN@%@kw+J4SaBg2PY z=2^ySiZl}HRw(ySDaQTA{cz~WeefV{RPxU9B{YrUow-<}eYXnx3LC{=>VcnbzZC87 z2zbKh5{plrx{!Qqo|ViG_Y@g-3)*+-oip-cuFhSu_I-L%hw^Ul8YYeKlV#zAOJnC} zlnXqgTw*hzwe8uW&J^@O_^di(^6Plf`&tt9y8BsIr|@jf3tlnWEM+~A1<$3VDdY|z z;k(50>F9d@rZe=f--nK*vw2ZG+q^qSyB5=w-1xiXJ~g+d z9`C{Lxtqb(jn}z*!^56TV~mDx&X1oXFv5{PojUf!tBE3?Wy7^5@w51W%RE-TsV9E! zJi#d}%J&8WxgQoe@Y#xAH?4IcWm$pW@}pP9kDCE3y|olrX&L_2E%Xzlkuw`-xU-EmNzgXm(_59#(bR|cvs>Jq^3A_9LkbQH%?%4x=X&-WeB>9s zPHVkFD7q{>_xRe(Gp>Go9*Vu@k3&b+Dc2Rc@})2NgOL`0t%UHw`R!c8mk%8!vbE00 z?jxPp2g&Dm7Oq?F0vEc}@3)AL?OroTZ~cuyH?8%5TlA&;lKwB`ZR>~8UeQ;(y{72b zG>N@{?m?BvJec|{rH_(y14u7zIJm9KGd#X#-<#(kjo4hlOCzm}AB#x$W%KKz45d%w zPlJZo=f5StPSQ$#*9YZi=qu&XDUawp>pWwmtlx+BaAeEepxXvIt#?~jg=Zsu<_yDz z$h&=)o-vOY!8c48!^+smo?pnk;!~G4=4}6@(M{y>!8y)^x>Am3bltu!z_{qglju`_ z@clU}24`$(xMzQzhkhpme$(UK`-amWzQLH6`>x;r6@7{5S7D<1%^7^MPg6E2gM&LL zrm_D;=bI>svCPv>bzJa|F;@`&WM1O>67&rF8AOk7ZG@-Cy$~`!|04cYhw#Uqm+=q(JP=ZjF@de(mAJ0LGJ9~L>QuHtRO;+gmG{g7eE#d|HGjHR=nD2#&?~b>TBLftr(9sGo}$gj)&*%jB&Sf*X~oa z37z|5#HXV(cT~&`^v?nlX$u4I?XQ~2^PvLjMB1vP|Mj~v_fNg(um$RIN?j^=P$@#?`}UC54xGphHH;rXWOm2@j)&_Z#;ZUYCCCqbZ6*A5B>u;y5r{F zhEUzp8E@Caujr5PoW+{-mP_Z1x5Uf5)-J1h57-9PB>p8E=EoPqT1u1nZ){j8 za|;;{KSg<&k9LZlW--sx)wDMSX1phw@3L$>)8sE6=(*Wi294)Z>PqU=hD+T_oZSB|@j_>jm4}0GG1zrnhHQG#^PBYM zhoeVaFul1pJ$y7ay?ZQrW!Cpj+VB&|_f9;73Oy8=%APRc$$mF@)^m(<*f7c`(|OMN>a~zUn}fvOC1y5>2<{)nB)@WdGV63n)dVfbw24$e;I*Z)Sj#gA0T+)dieS@y)` zXL3I+a#rYl;AZCF>k{?_)^Uz(UDCcC;GYMd^NXz?A(OUOvYR^2Ihqu0$IJKjR&j<= z?(f^?SIn{a{&2aYiTTuP=eYm4oV0R&R`iAW3+ssHxWa=jdE?ac{+k&az2-E&!xP}1 zO0ln!&zt1&=2_XB2fd9Zz2&pizLoLDw;Vd}jzkw-ZKFQ?6QKL1v(t|czz-jCS9A_C zqOJ^|f}eNOmmuFu<~%uZ(oe1D4toEq+-1dC+4@(~j4}0a;@rH;eEm4jBKLhpnAW-* znc4Qx7>6<+|LdAx8@?a*b)*r#$nOPyXNZ%!DWF{pM=sJY7Fe=L5+4UV z81XX4M+V<*XK$r7zmJi4(h48qxBWKsqzr4}MPLgFFOZ-8ZC2@v(sen5>)6*un-cwS zc+#2_{xq4(%DnXnXh@twZwisEqY*#zNyh4Vd`msiz3=QlChU{D`x~ee;pg}8qb6|1 zmVQNP>Ng%Iew9(uzw-@W;YZqzm;0d0T}`s*QtZlG&<||H?{T(olb`d0C!pcyjDwEN z>!5vqaJEm=l``VMG3!RwM>1P~0iNWup5HY5w{F?fczZ$T73=`_GInO^>NlK?xTwp# zlX9Nfh))Q=@OsX=8s$yHpFz12Dc2e7&wp%CO&Z_QEVxk}e9slzs`Krm5%C51owbW+ zv4Mry13BLx3@<0oXVBsIkT>7#cM7hg-#dn9zVp;a^nyCdT%8Cd02vaQ`*4Ts75~ zG1lEMtg8fC4zZ8OqK!LGuuX^8v;MMy_>BX(qq6^y(QT}m__?>UfcTLT&%9f$b<8bH zb|2pOifTH)W$0)>_jx)-n%|r$1%5W}+R=W_-u5R?k(u-@gS+0{e46L3bLKwS%n|*K z@sHg9CHrDGflFH*bDsOU&N2rT-4iEdG8P6Jap|5)0 zft=PW^?ZNyS^B%Dkh{!nOV|rWJDfM%js9U{N|KpR>*n_;3pj`TtjI;$4DI@D`13rI z)U>udvB@uYq%Yz=Y0)e0&R$Pj6y5R{VSCvJL|<>{iCgyM(f%CJ&RncUJ2ldA-|Tw) zk3SP#EAQ9zFyRTxA?43}RqQ}!s}4+EW9pRn6-B3Bf)|PV0dXHwhr8)-{8m3I_Tppe zwd<`FFhM#@9-aiEnJ_0SNgB5=YJTGnZg||a&Ovv{> zM`Aa`x8*{%_IH66a<|1oY)cz*au_mo824|ucz;IvJ7E8TKK=o}zn^acsTTPrkTUkO z^W8_;e`?&n@iO=?e-L;+fUIRr-;1s}+}_u&fqyS;$w7NlP=Y}}+RmB{4CwgVh z|2~JzjdjEe$WiVlK2Kfi)baZ3kuQ2M9G;~glDgM`kAP?K$2hhs`379)!%`R2We({* z!}%T}WtF*>TXnp!2-r`k4=-zr{<*5NL{k@KuIX0l_*wE&tQ!_X|CvO!WdmiL2CuS5 zb2n|?C+m=uHywJ?$7P}S!};!)Th=96lRQ(Tuy3x5$M6fty5u2?|Fd(^b@Cd4oHkH~ za`IT8!I^OKaV5Hp^+_K(C$tO6^E1*3eL3rJ?rYd(WF+IL1Hk?L@7(A4M@Of~j{djv zeB8`^9_m=+BkPko{c9QD4jh3T#2iX-1 zGFJ2&b;S2HX}2Eo`&9VlyG%=oKPP!M+|fxNak6l+`OUR6JZIg12c}(ht^{BD9hW5jr{RpQm%L=H*v82PM|hC_So+`I-$Ic6F`6|lbg)m_IY!0>A9FXhj0pzo zTGOhsm?jw;40H1h;;y89!@Bz6a~s^c?z8^;_CecAAGGdS)wym%%D(m3EAe&t84m8b30!$I-sQs+a$A*_;CK3J!O?>S#Le%3u#{I(kG=Bp?HDh6YQ20r z#_QC6>>cC1^=%ea&}E_8ItH@3Hx4)Q+Rlt*nxmYRu ztbNYW`4qf71uw#HFhBm3*nOferPN0m_2D6H75ao;G_7E6C++tm`qypbC2gzRKcGqO zAvn)EwpmvhtqX^`_uW2J?JK|R?kKvfI!flN{Ie->`_9wu9~E7skL|t_9@~(G-P-~^ zk%PyX*|fYYt*MMLH*M(v_Y_?&clT@B`oZ@nB`S1fr z(?Ms-@pbIw@>=8A6~7Ja6h6cUb_Tm%u2Q$WN+vxsT9W&PW)(?PtId8~G~( zcS!35^w5JZpiSn8tl#Yf*Ym8ocSP)`hxt6RAdd4;qOqsZcCz<&u$WY9*q z8(Do59!2lOrpUf{AN3;RBzs@{v!wZm@`&GF(!D?^<9K;~3SY9!RVRY;9B`=zp)cVp z7JjuM>$KKQY? zMbsnqaTc(%ocS6?8%q(N2=Rj$PYp+B{c$t)t>*mU*ArfTEdQdMH(k~79&jIHFTMkQ zc%9Q_jL$XRM*`mnOunDK5Zlzoc}2x}(_w1R#RA%~pMG2LpJMDK>FGPD6V8>v!<(FE zoCF_IE-zylk%N3cTE3-3`!l|~H^R!#LwcEaiO)mEdcpji|C;DSW-Ib|?fk!DpP(t{ zR~_t$m9gV+g_*Z9nvVr9Q5Xc5wUz9!#n3e@;yWu0!6X;ig}(NUVb5IbJfH_%+oTMQ=~G-_=tGZ zXCQk(2DSba9I<15eq!I;%)K1D3pz8ICl1xjxnOFQd$i2Oy;;rGhI+Abb$6&@NgT_oCpN?{kzR-LZnYrNmM&Kfcu9xU* z7a@CmTc2)p9PZjleI;Wrs1Gl5-|fr+6m{Skh<}WF*=>Di;TiIExVOGGhR>hb zp2ZfGlQ(re#^Koemrs$!1M6Hv?nGWbWGQv6@cpW;cjKk5)>Bso3_q|9dzrQLbI=po zLeHjk4qAE8@sBs@;CCA%bI?gm#Bm59i8GAJ+rak@R_;J!*1~ln0dE~{LX+czR?_!+4TjT z-ZJu;TX@*}Z|bnWd}cm#%ZbwdCGX>sH@?>Gf=gZ*Mwro>Mz|b3Ka1XA$C_@HvY^vn zPiUjPsmFtLc0KcIX%Fks*(=O7U#4B012_2WZYpPYxSLX6 zxuBzsF|X)`trs5Ft>s+-7ua>!hNriSLcOGEa*P{uO=uW8mP! z@Gd%StQ}JJx!8=SsaFr4^1R(J@Fr~C-VdFUq?p5uh*c3ng+8N6Sa)xp0CFZnGuhr%X?G1B>Z&B*q zS?n8~T4K@lRSg~JVjqWtG3fADv$hl_y7M>OrIYXA2mf>y|5vga5EHKk41aaF8Fzd{ zUWqH_!6b>()d0DZrzh?@;#?21?mBOH4(UkS6KAHon|o1*yGh5n)1J8NjQp0oOWbVI zWd_HY>Hgsz;yk1q8XRZl_hZ(>hu4x0etKw{>HfW)I4|i&2FID{{_}0(8cCNO9A}kJ z;(VkV8yshq?{~xxZNl z`f}#MTw_SZPvB_%Ik?7J3^qX49DXWz%-PFV^mM|AI%q+VtGxs190ha>Pg^Dc9Qo$%MjzSYwE3h;R|=e&n;IO`L94>DhT5Hj9y zV~f&RH}f(Nmo(|{C2NHqSpzZF#{XsS@86WO?yh|<_N=d9O{4?AYw4W@ayIV$|6m;D za5;`IxUHb`J$$BRtc7HWzm#(qS@=N7Veu9pc&4Nk`Lf-VmKkX?+o0?~(V5^eyVLEpx$bz(1htDc$Q<{h=ZG zK%g?-%qA~UIK2^5px5P_07BsA5s_IL97uBXDkb^Udn9NlQUoH$j_dx zVL^4Y9G^-C<@pim8}RE$T{ZCR*^u6}9G>X|X{+h2lh=BFmd0~{u~GmZz(?de5ImhR zWv{$gi#R*N%uC1L>tOu({NKBuOPh}m4Lag4Y83kTZXS^E>GY=gebmlE|M)c>G)cG0CnTWo~K2*^c(VH*S89BRZ>5QMfrnb&h z;?EYHNtX6RpBJh#^vj0Mw4pOEqcd_ofA!OeO)G~dH4UQg)bQgMvQK;(x*Qv4`Tchk z;D3%iehYdne*Z%Bc&(fta=8rOzv=haE_&EQSk8RfOPPif7kYN+BC=YKz9CcYd2l}` z^-5o9`1?h+@;xU9-*a;ISDnv_eF)OkW9ZFJ(#oE?mIOIFH1wjemY{|n&mV#B(crD% z^|ko+r>UVAIV{Clok7OgAXyL7k%#E6*M&cU{O6O$qpl(J`|(Z8F+05vuIZ3AHVC-D zWUlc#dAXVIDdtLpSOb{m(k^DxRt^6@W!V5fBV0p|ug51Ke*Z$sBI{m5Ex*5)@s;@e zC4WW!>yeA?2RTT3KjSbDzK}<$C;6tX@T-`Mc_@$g>hWD{k^V~fQ1mye(A~50EN5k0 z=2@9B#6N^=;t8cqN&AW6S@cla@h^$9pI_zKY}01@6I`=Ba{o8{nQ_jhH%^)D{eK0) zPFk=REIAr9y@|%yP~>X#W%%E-Xe-!-&ZBSj*k*~h_Vyge0w;LimW_W1y2cn5Kb*Z6 z=Rxe1q_xurr&GE+pRsnX$B7IX|L&)3>xQd*JFV=i(Xkz`01unq%{sK~1xYaXiV42# z`}Z){`J+`bF-u`bnk98AuA^pF79;+ID{-@4k4I{7riSt-Z@O&DXe9X^_ zyyZNW=&$I@?+B&daxA^*sV{TCool4o7t5JM2W!frbN90kY6a~={68WarO8~TXj?ts z-LB`1C_(OevdHD5zp?vkMAxK^uY18$< zq)jiEHHCrO9+P}K;4?*~keAD_DZ+!4yD#~8t@DO2L1!1|4a=MvTT0pIs zzV8(s+mM^lM_+5&81Hb^xnY#D?p`=*>`M{)in*enxva5XNBQ1|u8iY?&kv3jo~e%> zdHzac``LaBNkc-&1K}}*0n132$y$|sR$eE&Z_zYwn z{S~nz=$GhQ2YVguy)+-P)*V>m%>O8%@4j>2VBOZ^?g)J9%y$qzQ>Hpz<|pe=Zshy% z+!^fsKtHi*9;fP*`zem$cVe#ozJqbW8TNUW7OMOP?04BK^ht|z^Hyw7O=SaBXXo5I z_X)n7Ll^v)xf@((Ke^`>x$lC#RGg0}0pHL1are5*HyG#8*W5_n;uA5x;Vb@M#{2@m z9=L~kEI>vMhp#S!`ltPj!OuC)f&P0q6Jp0D2gPN{9(cyx$iPVxI@7(V}{;7PusYf@Gzr<7O9Ddn}x`YY~Fkh?DC-P!S?m-`boamM4% zEraJH=r(=*m_P6?esA9#b?`jt#Bc3mo_6+i&W|yl z2>|mrWUp6xtLTf&lin0rk#;5FyZoqp<4*dQxxYiDetXU@q50b&cot)nGYR*MHgw1{ z#L)M1=j41wx-l+x(8rDg|3pw459dI1?hpEK9lBHG%s(t;qJG47mDt}~r;cnN{m+S~ z{oK3TiC&TJJibVCETayTC*Y6a-Wu8q>zX}loLA!bzA3u?e6s*Lx|q+?^*ftfVZ=MEmE@Vt?KZgJrppynO7bMR#%Ft7`9(k68l<2;b*tw_87UELcC@fxlq=2$kVkDMy61R>Zw$pV!s`Qud&>_5ttV+S-nhW?SRT zT6bGZrpyy0Z*=3|dA8fy_bE#_8Or#nYaMF{j4_rnwrRoNDZcPQ*c+KM6s%d*A@VuL zoZyw1Z_il?ZQ4Sve(Fi+_Ds^}%XJ@VgXQ`J?_uRS$g*K+ zJ#zi$-voC49(jsfR|Ls5kMe|*>jtyTtx}#1(l>GknqO>Z&>d(#{H|hu-Rv=NF!tloi1MNL_$Adrxywe@A2vWg)uj$+ysh@hTvYaL>RLgEv^O2Sd5SfG=jn5^7!Qm9 zcb$v1dqWQxV=-=#_8iWS|JOAqjPa$Ul{5wT%VnRe@G;YnA!}%+EXVu+8i_oM{)q1A z%t?cFrxU)VKEsVsK9joZ)gF8F=2gD!^Lg!Y2>Ax4>&vtukg+@5NxdL3u{t0O!)n}XWt+MoPy^*t5lIA}@Cyk%=4Z*Q> z;4Vwo{G^GbpR7Ig(lyzCA@){uM(Ht4Z&02N(tphFBYq$9yTb1S2@U;8Z`FAYS09f7 z7u}LN_j9*eus(U|k3^qDmm0wn-S~*SWv(G~#iuQM1m#(L+H%Ih%rC7~Xu3!v``m1%1_>9U#0BR zc!NBiwqVZ??&c?LPv$?jS@~B%Lwp93Um1A^`$@C>Lz=E&qndxh9{d#c;~^X2c`j+a z*cQ=srmlvr=i+}dbe;LtdKu@5ZDU+mdy+Li_%FqeD8AEXBj2>v`^eXWpDCG8&SiPn zCt>=nU)}ppubDozTfvQ}i`a!=yC-{Dz7BrLNY@*eS+7WrEzPf`U(%KTkoZh4VBT-{+sb9P87I|+2mZml)L9H5Y_#+u(YzOVu9XJqd&B)Vp)Y=S3y|cpnBl- z4W)GrdU@&6in>x21TQUrXjx@lMR$xza-LpQS!N*?nSgg;}Z`JD?7EGB^Qc}Kh<+yQErj(V|SC*I5H`G;DFPd7uxU}xJ zTl9uiwH4K5Z=&*8R#j0_xo~QBP)-Xf8YoEplqr?(SIkByLnlC-OZKNb%=*=FEDktS_jmD_w;f+WZR^{1mg2 z>Xwx^RMu4M3+rl@8ocV2^?G$pgN|G)7g*`;u31n~r7Kkc?(zz~w63DGehP7avmSJp zdCHWzQh}zP1VQIilsA-CFRH5e|Ak82Q=^v#iB!$0X(%;n<5vAT?ne%4hemCnlTv95 zD;HVy($juT@G@E|&8nhwsfo}NUs_dFQ!eStmMvUZQCDxqFGt!nb+pX8OGqRrC6(3n z6?F|3{kaX}@=NB{mMkrOu%e`%hFY*(2Oxrb`Klm6wLHH|&WCOfOGpwwp@s?N2{9;-;y@<^%ow@-S1c{7sGB)%LXly}@2D#+<2{VvEO|c= zL}=37%0<t!UaFrTGpFX|YG+7*(fscbw^0!2zN9Qm3dLwWf|0?e^rP{2Y zJO{Uv8hR0Z!P3%_#TBKsLG7no|B1WNh?8`8%+f3B>T2r5zEV88NGxGhWp#yq-GV84 z&dTd@a#zv})$}ik*wfz1DytXh6^p8*z0ypq2GtW^PJ5#@(xw(v)G73+yF;{EqoKql zrOQ?dpF!zrme$gjTM2cegEfjUMel`2As*Cg>uMI!gN0%#wWx{`&0Jc#sA67e9X<9u zIK+00FTQvBT*(i{iwp)0oV#Ys5g>oSiy1f6)vW3rQC?atT?Ul&(gh3ZD(dU?nuYoo z<)Tz|4gC(PY8oZ_(+4YSYZ(-nxdoTIa%pW11C)gebcsWv21-@Uii)~9XpYI^>SUSXx>;WlE`S?;uoDwM;B9W<>Hq)M6?n#6sGU z$gdcl7u`)kkzon2Vc8g#ysw%m?u5?k6g-_z#Y$NeaWsB;@>N%rtd06!-e`x~)y)rrrlU1`0BCsEh zKbr-Z%eiTyK1Rmar5 zt}r7aNPoK{%4%w=^lnA&21lx3sY|MA{NnoMREvzl4a&7fU3UX=^%;oM5R@UH8j zjqWDV5KN*5)D?nI*FypYs<~;^>XkMLa7+v#VKfE(UT8Gjv{JtjW~#FrZh}O^O{*l{ zES-r}4HnE-+&C_Wn7Rsdy4sLLc4=8XsaB&sk{B|QnzCn4qTFL3e}i55=-DF%#LWTa?j@`jpPD>-JVJ|u0B?!aTZyY1PI0+)+?&&D@;3L5e-Tgn#f4;aF|AJNCzt-b^G}S zD1!MhjSqH_qMEhS(+H|Y^M_UX|3dqZ)NCW#ZA&5>hWAeO<&3Fr=#+Uu^bppW7{>XXCL3c1KPxAFfL@Tk+D;l_m8x zB@0WXTcjaVGAp98x+el7BN6nSXkLR}DY&-~Ox1G>gX7ZpiI1Op{8F#d08NoXNVJu%96m70iPdWL#Iin4Omm(&#L9L*eh z>K82zqbp`tZKw*`Vm2tt21T@PqOW!j@nr_7Ce@l{4YkV}aG%k5CqhB`!5|Gc2M|_6 z9&zr)=OLq0;rOaE*$(1n!x@w+jPD4ZHpQX&Astwm)Z~VFn zV^RvG>*}ZIC3tx1_3RakE6W#)&+xj{%S4I|i!1AIysf@+bp>wV-mxkqAH(ZplQj6V z=8^W~Se(WS#NDG;$uzYN;#7!H0S0{~gR7kJITLbnb0+3Y%9)&#my$j!9unPrB!$%Fjz*;&Dh0NoDi zt6cNO7!YK`Bme)Vd)}Bsph16we?BY3VSe+1isd)k-Xz@g)yr0nEniw1K zS@94SEv_hkP~^5$79ytT*VT(xPN|yZ6KSPNRW4mQejKfc5F22Gxs!p{EYBaGH%`LI z<0ldpcY87`4Etu*Z(Zfh<)w8Ci0}2DqtsW8WhhxUGyIC!)LP74J#Hu?`=EHsWZhAC zH@?K>mF1?#sVBX-#>9(=FBOZo%1o|iR^zi^onAD4dmE~-clH;>n zAhSLr~XTJBGM!Vb8m&;r$4Q`4S=6 zMaXx3)KoTLTpplQJK_BUl^QxB5SX8?)O|03} z2cI(n0rp9#6NJgoZ71Z6xJsNE2;>nCC7eY#me4~ui?D%kKH+Al$ zL^z93B_l6FoiLGw`<3!ec!2N{;RV96djf%Y^lU0&En&-#phq}%KlLws5f0^j{*MXK z!%Kw62o;y~wi8YzR4MR9m`b>nFpuyA;Vi;-LJwgVVS~IMK(2(B*l3WM3jCh~fvto` zUPk}Z$oB+tBAiONhj9PDBNqvO13%<f-@lIZj05#*0pS5~Q1k9Tp|_Ubp7SDID5gFAvpZy@VgAG z8U9BKFCc!fjf!%ek&QQ|H&aAC)aP$LiU$3=N5U{9!h^3=uuahJ?=V{CI3I zq4rNSlpY;ub_A6}@}9--{t1DAUvQfZTqFNt$-ij3bBE)rWc`)Ycl1ks!uh0Qx`iY1>4Ns=Aljne z$;d_YWhKvcS&-}9A`z*iV)LO0Q)kGN^ARc)8Mg%C7j1LSw(;+>@r%qBkqe&ahfcn^ zQnu&dq@bVYiNq%p3b)7ah%4UKx4F;GSZ5gt-|CTtw3V%-n?YX5l5Vq+ZuS#>pX^h( zExtLfXnX9AnBrZoo!au#ZxwWlvEOWolsLhh6$ZXj+vSqDuk{U1m++O|uOhFpno_54 zr$3Rh@+W$wiwx;o7b6fY-r&jWd_sS6SmCy7o3AO{K6J;BqMd_x4SF>5v5dmU(_7M9 z4>%~<+wH&o-CL)7il?M)Z-xHe(Sd-QXIu8zk~>Yw2~Zh$&E$C*KD@kZw;6dFc87YJ zVT!vTK_~7Acr)%W+rBo+z%z89*wO*fUq3j-;4})YSOdrXMEsL+(k!LE3%Akf@;8;= zSo-7aZv_IgNdtcdUeR`KhpT8?Y;#P}PN%bgRNa+U6Z|5&H49zI`wsm8^{72dCsokT ztUsw9X)ABMX=K?Pw=G`q*Mh%!Gj?C_w};@1wFts524BWM@swpM{Au4Y@V$;FlAr89 zeOtfgq{2tzAB!v8p132SXlLJDeIAc(i81)?T@`NVu=h0)-O;CTXT0;y;Nl@ttK1}&x-`Z@jAa^l)^0Xv7B54=J+vD$%a=(5mc2?}&v3JJa6+0t#rqIhF`3Y#9 zMqW~v(CV>m*cNG!BokC=1oEWZp!w0c-0#wN6KXpF+HFy?E8H2oE5>M*g^y~Fxe6b5 zu7J$j!Kzye4H>tMqzsBsD>rEP98aV^nNqZEVDo?nNMdGOxTF8}w1NSgSmOTH?$g|P$#K0#96mCmy?q9sU z-;SixT?soAE8aR?B;hjXOXw_m>oon9)$;l^Is{#?cP8#iko0$33CP3EkA3_qcPnjD zbRt+bw~Mh7`B;)^aa4$GLff_2h+^vH%G^MpgK>v8#gvcpiIGo^DBL!@Ijd;9zGGO? z&TDsFQ}pQ2$A%O>KDcF&>z!~$LTvc~_&NW5#;WKh{21-0NK9Cf*tue9Nkxx3A2XN< zt|zg(q92QwSZ%$?(8(ShncH1WJ^e6gM6Wc)*`7wmAJQK9pkOcEDwKY!lEgj6tQfpJ z8KaZe0x5IAD6`jD@Wi!GUQ@VjX!DTi+XwF$RI)2$XJ)};X^*DYw4^+q>TGzsefHz2 zEh*00w@&}|cW<|s?abVj@o4&EX#%|a?e;s&$hX^_`!LnN`)$$h-?8h_>lCU&G&qf9 z=e)0P``ZNx&XTv=izL14K8FdIYi3g1%|wAPQA9Qp7QWqXycdMUa?Z5CeM3?wgpj*E zgj}(i?v9XTCbXb8RO!gx$K2x7h{%0HGC0OO<{xRpJjhfmwF4Xq#3XGt&_>!#OKS@|1dn;2O+)DB0ZRsE z%UpP#1uPTTES~N0mN7QwRno}3at84P#-F6|@=^?JtU#0v-4UpI@BL@PeXU>BaB;k z*2WujjsD4~2V>A?^c6+jRslRomn`zM)Ajvw>7<;CLG2=)*f3jXP&5pKDVxp24`nWG z%Vsw47GQbIr#l469&gA{L&hA_cR6-Czj^wtAY<;f<90fCIb;xXC$`)+Opp+LJ8AJh zAN!;|KyC=g=&K4JO?)ijcImLTC-3OLv)`_y$NRSQah?fd)I|~Z;q2LW^XSJj&)~zvQa5`iBKfjw>krQ`280Fu}v-WEk55}L_oevgx zX5Tj$ftZzVw(Zm8vzC0^q$Qm_FTFEZue-p>eyK<1qNB-}ijlxlB^`e{zmdRlfEjgX z(UP%dFfG1YZ}>if({bm70SityeVdbJY|f1(KQB1B)NydSW()Rt>BQ$Ti*#;i$e+*> z|Hn#Twhjr*53B*0=%D;f<#!6b@&fZqoB9q7Ay}?PJvi@2x^{;qHEKT_^2mj-=NFc3 z>XEHcBQn5}JVZabTCBOs^+q0k#}h-J98$P5eOFrHw!zJVineF&$S8a?)tGMnHfS~= zdb*PIC-4!8Pf#0Xq_^f3GNqUylM8!VA+rkSpJ6g|@FDu zZV8cbm@$LoUqza|_?s4kZsqUS3ZIzu^8o4o|=e(Ftp5y&EF{vrm$n8Y~6;00BMF=yA1VI#C8 zot9#hrDxt*C_}FMf>fkH=AaeK9?^4KZEk<-v;kIzFK8b04!mgkQMKRWOe|m=Dr3(g z8H`RB>56b?>`2<)?{*oqK7@HMJS`?&%4#-q=S~?E7YI&);M^gvMcuE`SBZb_(9o;( z)NX&E7;_a3|JecL;unCW&-4TJ!FZyRiO6p;cHkflOshBeZgV`5`DDf&+f#R>+_5da zIc?{_T?2|9O@6HZmad&A|8xPsX`&8nmbO?eFeMZ+=+@?|`%NYi_d1WpT#8eT@n)_|lIP(EbL??k?xT)5wXS#^85+-B z&OMI(ebfO>+e?I&!}F$?Y$8s?knl=OVn-k4i`5SHQTt=Ht)z-A2GS8{q>a-+-yE+2 zIUTQ^1RlTADB~u_P#J%naA;iVaM__7unOi+!Q#`At{ zkAoqsKSpCwpd%)S=Yz2{o71t`#J0rkaqLM@C*!pfebt5d9G=_z2&IF4HJ%Uk)xbZ} zR|p;JEAZw7v)Ef*Ev`d6UWoHKHr=3h_0haHr~`d8o{#p?+DEJNeX@CO>8tr}P;Gs+ ztv9F(eSMCOF=|($c4f3Wkf`x|DG?!!QNDiKsWIvh3N}Wa>^HSzv}*64&2wXNHb^Iv zeU3e2xK&#_Fk0;zn8S1XKw8+rl;VS9RCDSc$B8lOSeoV=qx@+a&ll3NA+a~z=Quh> zozBo2$EeF0Q+Ym`>2sVOqpl3nc8yV62Wvbx56&jVslh(Sg)wT=P_1Q*Y8k5Wyk}@O z(dUQy9DcB_)wYgN`>)k_K6q_5(O0hZIWB{xYt3WS5nbc?sGd!yHRbrvC7blc#pPLt;`fXALJO2cfzsB>1cFnc<6l608F(; zr}w$GIu7AN@S_)vH>l=V$pIDQ`A95?UNqvwSamR7yK=ocfnJPN{`fr(?`YMGGF-3r z_tkhtF^=4zP64?=b@bKRvsFuiwl!NFNysL_E~<6&4eCUac6pRKpQQ2Jk+jFrLhAn7 z$!vABzsB>a{$5-ro6@qOcQnlhhyILg;LVxYBsi9t4NIE`?Q!_wY_PUb8CAlGX(o=;ty4Q}%=ti|=zjCN_1YUkhU)p#WUcVsq5?IW{!-hUnPICh<2Hja`~H;)p&ca0LEw2TsF ze4|8gdq!z+v3HbYynmDw_rNG2c5syBcwrP4;^g&O2ane?x;UDxoyt~S+-If^j+SO~ zaGb2saQVC?u{4z?$=nkZFXq* zgO57o`I1A!w|T^=op7o1PU&a%xZ;6t7Sk!Zx6JY2SOyixFi`zeBQt5G+FaV+evVVx zW=CUxbv{-*&|e*k1KrzKJKrB2iU%dZ>*TpD3GmS*0iTq|D@pMr+0#z|mxbA_{RN@F zcC5eJES-U=)sk=WVfBqZ&w=~K8LnxAa-A4 zbhlrV4zf8$Ap0@B1JspRt!*IoRy&lU_Qz>^Qq-xqxzU%_l1 z5Kr2c0rI?OpvDO5$UvcXDn;|9s!J&X-;}ByNL4MV@_b(Wrqo?}yJP+-rw9P0b80+N zPe#lPSsl;Q4#laxjz%Qsb7>bi&yQT<)MZVcy|IlBUK-=5!M$+;KNu(QlTuyh_;*k=NW47vNgu%;7UnsOXDDQDpuq9 zQmodJ;b@FYB>F&{wt0}~1J9>1JzeQ)E47d=vf%k(AIxmJI**}AS6zKIo;M{3^_GNU zQgkHv9PJruZ<4ktL$rtI%SjS(lDTv(ErVpt!m}?q8-k~jv!SthKsJ1w8US%P zOlfhP%20<=wM*&hWU9vV>C|jUZB5H2U0YfqQx0Jgr045NQh2}C6jl|WPiQ3*sP5S2hw0#OM> zB@mTBR02^6L?sZFKvV)z2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTBR02^6L?sZFKvV)z z2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTBR02^6L?sZFKvV)z2}C6jl|WPiQ3*sP5S2hw z0#OM>B@mTBR02^6L?sZFKvV)z2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTBR02^6L?sZF zKvV)z2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTBR02^6L?sZFKvV)z2}C6jl|WPiQ3*sP z5S2hw0#OM>B@mTBR02^6L?sZFKvV)z2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTBR02^6 zL?sZFz!yp2@)i>#`MYK~+X@@4Fz0(Fyw(a2Sm9+W^jYccd@{S7#-lsNVTALn(B0R3 zxAAf;yhba2j}>}H^v1LCqv8Kq2^{!pNWFx2xMm0Fx) zMx6-)8}n|Go3gh=BEO^KsdFPkz9R@3h|2 zEIR+)!3@i8{CCrh(hHHmTI)R;nu3VRAu0!vKvWN+dJxrv|G9QxYM1iH+9M1Lpu8_B z^EZvq{zZh{QB*WV&n~Vgt1PY7$Bn;f{7pGGj>{cwJ=!$Ghc4w%$LxV>5Qc&xI=S-s(L${w%vhQ}*=T-O`kN7>`q-f#waw*3l< zaH#}k&;NqJlqyl#xjyj{?tBZCB~3!0lN> z4;3k;rrrh))r>tqCce*}2r zQ@zM2n~r)qJlqok9|oG}gU8~h#WIf`Bl*?s(WZYOgpLi*c_w_mCnLc9KM0Q>YB$P3 z6FzOY+e+p!!U5`XNV_)SNowV^-gdx-H-?3$C~ru6xAEJ;;wPyKVc{vNF#?@TcQ}4h z)ZVc8DXJ|3+#eR6q&7#OpZRHTyJVIN`xX|Sq}n3TaYw-SN5G#Q0YAAQyc`xsz`s8N zzBepB@(&A7QvR@XlGNo0a8($N&rEeJ#6EvZDdoU!+i;C{`FoY0z)xEEHZ_k#V*fXX z&@tit)$DKBmH{t*7{cIy3ujcJB9zBdBC`eSc=o6dnB z_J*5ul2u-9Z~kridn3^I{wW-tBsJqJz3JHbCSUB0{~E7iABy)|@E9|E--iFxe7|hL zJ;p2cAp-rJ|1$CIe{K8=5%6pOVq==2&8O!|c=&9~4{pb%P)y<9hP$l@j}Z<~9TDtX z=A7R0G4V50V~AemgXTcrYwa~zXxuXN2&j^;C>6=hA+Uph@M{vfuFSC zmqXxFTqb@;2>gc@T*dLgpG|)t4NB<8hrkzD@Z=EqK?|N40{_&4>mhJAdLi_)L*Sb& zcuolX*A_f41a7x?w*|Lr$77VHzfv9>VgS@q{Ij3euD!0___kiDt)bgm&^(YMA=O$zfZ2d+)OF+@MVY2#b)W-DVe-vLU6?T<3m3`-6cx>^Svk+TiA z>-T#We71#u(n?^rC4R>1+ z9wQu};vnnE)X-0B{mzCzZo%z! zJ)6#l7Tj;)yDj>5d22(;n1<6;P(2Z#ZLD)U%w$dKegYp1YiZR=#S91;ZrTR zS*`&pIjlZ2zMa>bzKNfrj)fgxr6_+`IICYF>vz|~k3;pqziRQb&Vt+PefL@L(-z!b zFTBTsoBU*|ypVQhw=WxRm%_wPQSl+`J)>E$bEqD8vPFNj1-I9q?e(kIEV#YiY{PB( zp?-rKD5}u6;n^1bjTZcVOa3<8q=S8{v^h5uTm=8~?7;o^^~SgLt@g&=aFc$r+O(oK z+{Ql;qCf5AEBa%@L-i*!g8n3j_-z74d6Se4A8W~BAPcCX4{i(JZto_anaUlZer>p2 z3KO4xHpKrk3i{#XW|#MJNO{MBFXgr2BP~8n`U8|6w!E3DAcRkwp93NM-0=nc7TgnpZ{~Zoe`%40AG*KdVc^m)+kU6s^1RwV_FV)2YWM+*zPAww~aP@2A(?3R5IyK9{(yryKWrYn@^kn(@T59heV)n^M83tZ+x51t}m&# zHvY*F{OifrfuA-MF478D-ihEVb+x~DhC%0Qeg3Kix98Djd6aqxxY2&Ca@q5p?-}r` z{h5sh{A&N_4uk&tflz<%ZUdgt-Cl%i`TLOp@38~6RDWsF3H1+~{L;yX_|MzHA83;7 z_3xj750`%28b8=}IF$}79RAn{@canyk_hlx;EeLG`g_E{XWklXmeg+*crS2+PitO$ z-h#JBp#Nu!j1o#aR;9DZVPep+LB?4Sa7k<#IADR-FYPnyB0T=%5@w3hUEe5_Vt)B26 z;Nj}A#-bnU@7fpv|A!IipNIhOiU9A!#xJ8?`%E5jrx<@;;Nkdu3b^Qr?U&BA@P7+D zoL+ru(YNO{_Pps91Qiay0=UT2_MhAG{A~pM&mzG4u(TGA{-_A>NfF>P3^?{@pxLmF zFi)1hjS=v-0T+2DTlO6FHvXQAfdAVF@Dw%-ioU7q%zV2{cZnJcT;y;2^RKn&-(%og zFF`?F1o*lL@a72c;}PH=MS#b%0aEx6U5C2Of`|I48h|H@A1P$Kz1F}FDv@e}|_O&Sj{y$mxwtv+gPyW$@`x;F;elt)>ETBrg z*nW>}3qBfnIDU$NhqIFn27ZES`?X160rpq^_C%o59swSshtKyq;KHZ9p2(~5_jMiw zZm*Z+S@0)qxHZqU>-{z0;rRa)c)0c$mnHlle`_3m!lIvH!7n>a#ouJX=NNG7B`Ek- z1o$6-8{?U$P2?F?nrSFuIQh>8F8H>;&TZjuwBYu7`T`3+eT0c`uWPqh@b3W+NB_|X z@SjJ3|GP!s?=uPSvFPs|8J_-G1J3yW5fd@jX@;E^{J=-%d$C0)=eqE8<^vZw_;~O_v93 z_?FnzeHL5~fwuq`xw$_z@ojtZmH{_m!T%=T5Wc*61o-#}aJK=!y1v#%z;B8Gf5D>D z*kp2PnS0e40lyCl7EW(7flK?c_r2KdWifCWkJ1*BT?qb(K3L3XTcHgb*1Sk(JmV5!g*vKW3&0hMk?M+3lUj z4%5ABV-a0fS5fUqjJ@>rtsqQZWPV-cCO;3wyRhE9N z82_ab_{$~me*?Toj@~V4|FK%}xIYXy+0*ONZayUEd6l=vdFw3!Q1tnA3D4{E7XhdB zzH&pfe?a1SOSV5F>AWK0A2*8UbEX9TSPA@G37l~_`1x%y;7E@9rIPl43HbNQHu~jj zCGc;S;CZkvHf9epL^K?dGpr5|65@*_HN= zp7;8=Y@=U3RRSNCz-J}!7Xjz~q~ss9zdr;6NB&hluktA1Me}*Q1in`ScQ_pKCHeEL zT-=vR+W)a^e@5mvo|oX*)aP~iVnz>E0g_e$V@UIPDH4u>9)8~X+6KfGJg z{-Z0!eETrqTpm9p5PVV6;V}-EAG8YtoYs}klYG9U{qM{6dAs%%z>D<8R{^JeQFVsT z$+4eq7t`(M0WZSyhrB)f>RSTAqY}@5O1P@~eNw{D{J2<`s>e$tJs*|u+6~cA$>R~= zMf>>Avc0OO{SAr#)z1{S{|^p_zw~v{@C&m2!#^SR<@z_X<6Z`w@*b*w@*&y&S-?qO z)#SOqg5ThHWFzhJio}2J8v+P*!CzmO@Y+9!&(BNvBae&usPnZ?Ncc8~%MaScD}jHm z1pX@}@ZTwczr^9+Rrzl@e@vUd-YjYVzp{Nz%JmP*_Gf-l()kYr;JSqGbGZDVU7iJ; z_+QmYD*F6cN&7cs`@A0h(Vr^jhn*6*TLS+a;6?oZyCv=ax&;0&9KNV0-vIoKwtn^Y z!E=Ic`S|55hpSfU^)n^#r%T{tz`6dE`ByfRp}L zb?G3Zc&!V%)g;}{NbpKYT+l-MB6>c-@yO2Fr~i< zt;^?Ew-}43eb!;3F&heWReLhQD2v0iB7to!98Y{Vaf^pC4G+D$$<_*EUg!m0f{%tV zwywD>9$AhX?eA{d`h;-1&Tmhm5OWygcDKedEwb|+`fL=>STc&-coh0Tna+9Xf`2id z_0n6dII*I{+}rFXOEcF_!l>hQt&V4!reQoYwH!9HqR2YJEH7Neq8AXf6Hssi^Kv_(yO#&Pdx;+pl9p>JN;>Wr!F!u`RDJ?82oT=yWcG;`1F;>D*MX{lJr2 zbZaS^27wzva<`fj%fp+j{+0G(Yz3IzT=yES3vRON`(n(_dOS0YX0|CX?m!Kb;XZHrN zajy{9?mFVyJ)?!feD{f=Fwd!BEVGSiU?;@VBP(!xu)@Zy(4jH&jIq{);q|(SQG=5a zxel8o5typRmIV8NQLgyiIs?-fHMY$p^O}3zq|H`?Bj&(>!%0{LQyMF5TktjuC-Adq zLidEjmM{^>n&O80wEi|m^|+*QhbCSAWzYxf%#(I5b32lqD}{1SwQG zLNQ4_)$87UWnyCecNec*nLwN;Z86uT4!q91yE|Qj%{_XPcBj>`=Ng;}NPw(p_ zy?Qqh3nj|8>aOBVC!FQ#5zL?uBTs5382ZZo8Vx%P2HtQrNg}TwW23iVNfF>7Zcs2k zb|0hQ3K1b)pf6`G1CUusIQHzA!o@h54hB`bf&j@KdU4`LEEz+3!@vbn=7enMhkeUu z4x*x%SyM!uGPZN7wc-|RfZ-UAftZVs^QbbQ(1_7TU}AbxXKbVF6luxaA?UUI~5D(4||TtOS(u^fjrS(D;mwz0Rm4RyY=&L9iy!p;?DUX;L# z>xes;6Nl1{RMwxUM(&`>*zoX>L8r%IfM_^5Vl!H&WkvRAsmVD0CbR5kSmgQ@zSas~ zMun~a%+Bg|XAP};+iUYtRnG!w+6k{K6cFyD$3HiaD8NKEh%dz$V8YI1o^eG{woIu^kb z43G`1VxAr#;vj8dwEM_XgcEW@Agq>6sfFgj$V&1ns@FIgK@7O!Ncups)ov}Uye-rW zQV~eyAfM3|t`Z0hRV`G6)hcF~GQiPcAx0Jo3FDVSqtSe}o@ zU2Kn?7)XTy4weNSlMqQD^c(>X-tBaoj2yqvK{yo*U1*MYQF7;4+!O<eo#1XHf7$zL@A}j`uco>EyeH%uzr~yb4OV zheG#rX$Q4?-ELBE=)4M-7s_hlTejQLRDsbt4`z0@-DDIxyAaII>=O37ra2QPk!@z) zna?ZCB_|oox(;e4uEw%l`AD^5bOA$+Pt{X!a!7JKk`4|k|>`ttpvq`L8>n6~Ps-t0oR7qk?*=-AH12{daed9nS)vm_HiY0%+%Wi^U=VJF!z_vB+I@q{04zXTY95`3h*emooQS(npgmoA5XE>*J&2_ znZs2oA!VwRVymtYMOrzD-iggbmZ}+h)`U~#2o(zBFHCA-*ejKr3Br@!;C>XPP{!88 z9QOsqj0U2lgfx;HMPUT4K|(#lkadIB06*hCs}EgT)ft8iv{j5*L8g`L`RZ;5o|b(G ze-W;0fFp{;9-V{Jw0ge7jB2Ao?xHR+!#+G)?&?74&pg{DVq|kiYxSZOLAuxTU<+(0 zG#t|SbRZ$=T4aWmZJkFq(!vubWocd~Ea_myrg}x~C^yJULP-(F<(88ANC;L_q!~?r zo?ISDM1_{QhqlWd37wXtHUi-^#6pK$3A{lVjp68M$&VsMXw-r*@dif>F-300265Sr z=EAl2V1i**I|KysqtRApyJsb?m7>uI3(;QHM|K z*$h?(JJ7Moh|w&Rhhmd2U<>nCZn*__4gFQ5>t?QWS!vHwZj|yQMSGXqUEPZ}1+PgV zQwA$*Dn$g8T`Os>Q)yp~7mc=%t&ERQz?MP>K|Lc{eAdZw)4RF@nIeX_`rgbSN5w{H7PACLvT%|Ip{WxIs22S#$+!h4$5}pyl@USn zRm-2c4bTyR0h=3mehA#(-JFfdkkDf0Y9t&&4!PLowTR@q$b}UzQrUcksi+-;SGeDD zRTEERSqlrc-7zW&I?5``$^srI=nTh^lzQ{UOv=qyI@P&HWwg3(5Qk9@t_rq&JRxJ% z#})}y?1=N$i!w!ad_KbgyFkpt#y#tRQ@u{=Hg#1gW5NRWfC*Kev(@d~2G_dY5E_7V zo>6nW*bZ?%X7gB5`LmskK7|8KoWdai;2~h4(7VIqYCYoNdQ-+0DN!=}R~!@>HIcI? zBFk`H+dqN|mF$Ifk~3Z8M%US8l|YzSM}7!@y^rN|mXQeB#Tg$A4tbQsyz(@y@HC=i zktHjUi-nvTY)R0tL!qah$Hs9?O3?)dBzxFn+ue1x(L)s9Mury0I4wHHDLux(ZQ8$6 z7zwD1c_s&BfKA;-*~;29ije9goSKT4*3Hlo73cz6jSQ;iyCMpc^C&vk`Zl5@Uv}An z*ybG$#58gg`?Jd;qeWFoU2p)8xw$^bDH`rZAKQ{h)gZD}Sy5HzBdAqE5)OK}#^841 z?S^j+bDAh4bj7-BU7k&51BrRC0***IUok`-3DK#m60WpxHL2YAsV0P1W#Ek;*W6;FvB%?TvI^ z5J1k3uYDvrY`w6A9I!!|Ja1zEyMq-uS1SJ8bo@u!=i$r^k^$ci<-A$+gnC=y& z3rZ4(yCa@4Y@8}%*ec5ywpeM#kl!lK7y{j?GKOlyq}7JQpWpg)b0wEjnVLrm4hbXS z1B>J$ao9YTHCj-I+#}<)c9z5O%A*Q#zRpxen{1yWQ>4h&B0boFn#VqD%w`MeZX7`& zgAPMYX$sw`#^{TF*!wKN%L+}go&t~ONd|o-_!xOWI<^=^xrwGUO$E8Gc$l0Tif=^Z zmKuY_)u{|Z5xO>yBG+ZkZOa=Y9?U6`j1nkOV)C>m9pN;t-^o-oMEb&HittF4L1uRW zsl>TQwXD3UVqd8;Msh1l`rsgYh9=l}-HVa>raI1ifo#FQJrXJywGA3v4tQNC!v54xEGcL)d7wWE!&oM+!95}im2yuy>(lJuin0!7 zx4}r~^E76gP7qXeE)~us*03_31`AZr<^oO?JctC{+@)7DC^+6CVsvnP?D4ppsH+@R zm7~4S*~^o$KUCWy`{&fO(MARaXAnaDs=T_&5Wt8ZdbuPIxNwQFP;^5OOPQ)7E6EQg z8gTCsUCP!SZW+z)5`$rEFc~1oD4w%84jSh!htYuI=ROBN{LZpE1^wJg+}G?eBCBn( z7|8CDafw9f(ma~4#K8pVtYnbY6q33oH&-18;h4FXeh)gj*CMV5GmU$e` z@;*d5s}(3uD7B4s*Z_EgIF>xNSDGO7lLib3sK^C$5F(f+5^S8Ie%~M`7RCbX?9K)-7fkw z@IHA_2{$e$;ZUwqRFb-l)Hw7Uh-i8+gjZtP3Uw9(RW(neW3dI!G}jh^3u~qh*T)%` zp=zvToL_!^4Zjy&UKoT8cC~KQk@K{iBPy4&uQh`3Ag={UYf^IZzN$f)A`?zIov{lS zLCOX{EVuxVUSup_S)d*gTB&?`K|zi!1QePf^A!#$HpOrlXxAcHfC^qbYCr%=Cl0*E zYv7pj;7mPsL;5u4;wXx9Qx2T_V06BTP)yWxW81|sRAT^A8^{e4KlDP8*E`*j20!YW zQ;SlKK%F~WQ@ZUwZ>>^>AQJ@?xrzgd!gYs}TVbC0PnD4#JRMS2rN8dA*k0>scoHlY%oqR|7SByQ%AKU&x{rwsHA+q;)OByDw^bj z=UILs2>uVIom7D7Ly%WsG-)i;ITCyS8Hh!f6QJ)NNS3B> z*)-a`IP=&<15c;)lx{{^JfrDaQL>04B8OKG;R3}U9zmxg_Ok3zQ4(ezT%0U9fc@#I z=aZGkW>TpZNnp-mHqm%3oE(Y6yV&gWTz$qqpISt*Hj$pxT0>*#6uOgRIsA?21-_t#Q!;vR)| z?3LA4O{$OOr+|9F?k*+XJn;MoFtJflZNro6*K6{zTSB{)F2 z6x(y-DP_>t*6=U^x9$4e^lZiwa-+}(;#9Mz%4y}U6?y}7^rhkp5phGAaB9lx6;U>y zr)r`1N&=Fx2r7rvo>w=}y7VTP8a>{W?#H)>zW~piKObShAwWenqM%?X90D+pMtrGj z>YkIx-O4TZ8W^tZg=n$lE5Uv%l>)RdQc7JY-n@hv8$dbp{bJK=fXtjT>d z{a80;OqE#_F0RJ0r*X(mw*888xUeiZZ+RwIoU+PFoGR`QjL$scoqiNBofz=dWZU4w~A+%s{N_wJCAuzg@2-F zRVr2IC_qnfO(Jn;hNt7G29P(C9~kbTw$P#u^4-KwDph*IuTtenrYb!chs+kF2+z-Wq+?3J6J6YbSi(OE#nT-AHJ z#`x3w&wroj=R~WAzFShtIEM(?kFw|jp5ifSpY`To!4`k#@%8?UbFdjJ&{ zqP70|WqhFV75&f2{_6S(^+kNLQ-1tk%k}Tc-=$O6=hVi@{`v859<9H)lMN$pVjp>+@O}Kzxv(7n(Y6u+8BvI^;7u1hBw5!s=xaE zt&8u7@zwY7s&%XWY9Id#ef9Cx?{2;Dp6o9!YPdQ56`nV9{cqkD6TW#{bbMY3mc*d? zsq3v=|LgL1j<0`M;5jGzsf(Jo`urZ=5H7WT^}WBZ$o}ekS5