package org.openpnp.machine.reference.vision; import java.util.HashMap; import java.util.Map; import javax.swing.Action; import javax.swing.Icon; import org.apache.commons.io.IOUtils; import org.opencv.core.RotatedRect; import org.openpnp.gui.MainFrame; import org.openpnp.gui.components.CameraView; import org.openpnp.gui.support.PropertySheetWizardAdapter; import org.openpnp.gui.support.Wizard; import org.openpnp.machine.reference.vision.wizards.ReferenceBottomVisionConfigurationWizard; import org.openpnp.machine.reference.vision.wizards.ReferenceBottomVisionPartConfigurationWizard; import org.openpnp.model.BoardLocation; import org.openpnp.model.Length; import org.openpnp.model.LengthUnit; import org.openpnp.model.Location; import org.openpnp.model.Part; import org.openpnp.spi.Camera; import org.openpnp.spi.Nozzle; import org.openpnp.spi.PartAlignment; import org.openpnp.spi.PropertySheetHolder; import org.openpnp.util.MovableUtils; import org.openpnp.util.OpenCvUtils; import org.openpnp.util.VisionUtils; import org.openpnp.vision.pipeline.CvPipeline; import org.openpnp.vision.pipeline.CvStage.Result; import org.pmw.tinylog.Logger; import org.simpleframework.xml.Attribute; import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementMap; import org.simpleframework.xml.Root; public class ReferenceBottomVision implements PartAlignment { @Element(required = false) protected CvPipeline pipeline = createDefaultPipeline(); @Attribute(required = false) protected boolean enabled = false; @ElementMap(required = false) protected Map<String, PartSettings> partSettingsByPartId = new HashMap<>(); @Override public PartAlignmentOffset findOffsets(Part part, BoardLocation boardLocation, Location placementLocation, Nozzle nozzle) throws Exception { PartSettings partSettings = getPartSettings(part); if (!isEnabled() || !partSettings.isEnabled()) { return new PartAlignmentOffset(new Location(LengthUnit.Millimeters),false); } Camera camera = VisionUtils.getBottomVisionCamera(); // Create a location that is the Camera's X, Y, it's Z + part height // and a rotation of 0. Location startLocation = camera.getLocation(); Length partHeight = part.getHeight(); Location partHeightLocation = new Location(partHeight.getUnits(), 0, 0, partHeight.getValue(), 0); startLocation = startLocation.add(partHeightLocation).derive(null, null, null, 0d); MovableUtils.moveToLocationAtSafeZ(nozzle, startLocation); CvPipeline pipeline = partSettings.getPipeline(); pipeline.setCamera(camera); pipeline.process(); Result result = pipeline.getResult("result"); if (!(result.model instanceof RotatedRect)) { throw new Exception("Bottom vision alignment failed for part " + part.getId() + " on nozzle " + nozzle.getName() + ". No result found."); } RotatedRect rect = (RotatedRect) result.model; Logger.debug("Result rect {}", rect); // Create the offsets object. This is the physical distance from // the center of the camera to the located part. Location offsets = VisionUtils.getPixelCenterOffsets(camera, rect.center.x, rect.center.y); // We assume that the part is never picked more than 45º rotated // so if OpenCV tells us it's rotated more than 45º we correct // it. This seems to happen quite a bit when the angle of rotation // is close to 0. double angle = rect.angle; if (Math.abs(angle) > 45) { if (angle < 0) { angle += 90; } else { angle -= 90; } } // Set the angle on the offsets. offsets = offsets.derive(null, null, null, -angle); Logger.debug("Final offsets {}", offsets); CameraView cameraView = MainFrame.get().getCameraViews().getCameraView(camera); String s = rect.size.toString() + " " + rect.angle + "°"; cameraView.showFilteredImage(OpenCvUtils.toBufferedImage(pipeline.getWorkingImage()), s, 1500); return new PartAlignmentOffset(offsets,false); } public static CvPipeline createDefaultPipeline() { try { String xml = IOUtils.toString(ReferenceBottomVision.class .getResource("ReferenceBottomVision-DefaultPipeline.xml")); return new CvPipeline(xml); } catch (Exception e) { throw new Error(e); } } public CvPipeline getPipeline() { return pipeline; } public void setPipeline(CvPipeline pipeline) { this.pipeline = pipeline; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } @Override public String getPropertySheetHolderTitle() { return "Bottom Vision"; } @Override public PropertySheetHolder[] getChildPropertySheetHolders() { // TODO Auto-generated method stub return null; } @Override public PropertySheet[] getPropertySheets() { return new PropertySheet[] { new PropertySheetWizardAdapter(new ReferenceBottomVisionConfigurationWizard(this))}; } @Override public Action[] getPropertySheetHolderActions() { // TODO Auto-generated method stub return null; } @Override public Icon getPropertySheetHolderIcon() { // TODO Auto-generated method stub return null; } public PartSettings getPartSettings(Part part) { PartSettings partSettings = this.partSettingsByPartId.get(part.getId()); if (partSettings == null) { partSettings = new PartSettings(this); this.partSettingsByPartId.put(part.getId(), partSettings); } return partSettings; } public Map<String, PartSettings> getPartSettingsByPartId() { return partSettingsByPartId; } @Override public Wizard getPartConfigurationWizard(Part part) { PartSettings partSettings = getPartSettings(part); try { partSettings.getPipeline().setCamera(VisionUtils.getBottomVisionCamera()); } catch (Exception e) { } return new ReferenceBottomVisionPartConfigurationWizard(this, part); } @Root public static class PartSettings { @Attribute protected boolean enabled; @Element protected CvPipeline pipeline; public PartSettings() { } public PartSettings(ReferenceBottomVision bottomVision) { setEnabled(bottomVision.isEnabled()); try { setPipeline(bottomVision.getPipeline().clone()); } catch (Exception e) { throw new Error(e); } } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public CvPipeline getPipeline() { return pipeline; } public void setPipeline(CvPipeline pipeline) { this.pipeline = pipeline; } } }