Search in sources :

Example 1 with ObservableMeasurementTableData

use of qupath.lib.gui.measure.ObservableMeasurementTableData in project qupath by qupath.

the class TMADataIO method writeTMAData.

/**
 * Write TMA data in a human-readable (and viewable) way, with JPEGs and TXT/CSV files.
 *
 * @param file
 * @param imageData
 * @param overlayOptions
 * @param downsampleFactor The downsample factor used for the TMA cores. If NaN, an automatic downsample value will be selected (>= 1).  If <= 0, no cores are exported.
 */
public static void writeTMAData(File file, final ImageData<BufferedImage> imageData, OverlayOptions overlayOptions, final double downsampleFactor) {
    if (imageData == null || imageData.getHierarchy() == null || imageData.getHierarchy().getTMAGrid() == null) {
        logger.error("No TMA data available to save!");
        return;
    }
    final ImageServer<BufferedImage> server = imageData.getServer();
    String coreExt = imageData.getServer().isRGB() ? ".jpg" : ".tif";
    if (file == null) {
        file = Dialogs.promptToSaveFile("Save TMA data", null, ServerTools.getDisplayableImageName(server), "TMA data", "qptma");
        if (file == null)
            return;
    } else if (file.isDirectory() || (!file.exists() && file.getAbsolutePath().endsWith(File.pathSeparator))) {
        // Put inside the specified directory
        file = new File(file, ServerTools.getDisplayableImageName(server) + TMA_DEARRAYING_DATA_EXTENSION);
        if (!file.getParentFile().exists())
            file.getParentFile().mkdirs();
    }
    final File dirData = new File(file + ".data");
    if (!dirData.exists())
        dirData.mkdir();
    // Write basic file info
    String delimiter = "\t";
    TMAGrid tmaGrid = imageData.getHierarchy().getTMAGrid();
    try {
        PrintWriter writer = new PrintWriter(file);
        writer.println(server.getPath());
        writer.println(ServerTools.getDisplayableImageName(server));
        writer.println();
        writer.println("TMA grid width: " + tmaGrid.getGridWidth());
        writer.println("TMA grid height: " + tmaGrid.getGridHeight());
        writer.println("Core name" + delimiter + "X" + delimiter + "Y" + delimiter + "Width" + delimiter + "Height" + delimiter + "Present" + delimiter + TMACoreObject.KEY_UNIQUE_ID);
        for (int row = 0; row < tmaGrid.getGridHeight(); row++) {
            for (int col = 0; col < tmaGrid.getGridWidth(); col++) {
                TMACoreObject core = tmaGrid.getTMACore(row, col);
                if (!core.hasROI()) {
                    writer.println(core.getName() + delimiter + delimiter + delimiter + delimiter);
                    continue;
                }
                ROI pathROI = core.getROI();
                int x = (int) pathROI.getBoundsX();
                int y = (int) pathROI.getBoundsY();
                int w = (int) Math.ceil(pathROI.getBoundsWidth());
                int h = (int) Math.ceil(pathROI.getBoundsHeight());
                String id = core.getUniqueID() == null ? "" : core.getUniqueID();
                writer.println(core.getName() + delimiter + x + delimiter + y + delimiter + w + delimiter + h + delimiter + !core.isMissing() + delimiter + id);
            }
        }
        writer.close();
    } catch (Exception e) {
        logger.error("Error writing TMA data: " + e.getLocalizedMessage(), e);
        return;
    }
    // Save the summary results
    ObservableMeasurementTableData tableData = new ObservableMeasurementTableData();
    tableData.setImageData(imageData, tmaGrid.getTMACoreList());
    SummaryMeasurementTableCommand.saveTableModel(tableData, new File(dirData, "TMA results - " + ServerTools.getDisplayableImageName(server) + ".txt"), Collections.emptyList());
    boolean outputCoreImages = Double.isNaN(downsampleFactor) || downsampleFactor > 0;
    if (outputCoreImages) {
        // Create new overlay options, if we don't have some already
        if (overlayOptions == null) {
            overlayOptions = new OverlayOptions();
            overlayOptions.setFillDetections(true);
        }
        final OverlayOptions options = overlayOptions;
        // Write an overall TMA map (for quickly checking if the dearraying is ok)
        File fileTMAMap = new File(dirData, "TMA map - " + ServerTools.getDisplayableImageName(server) + ".jpg");
        double downsampleThumbnail = Math.max(1, (double) Math.max(server.getWidth(), server.getHeight()) / 1024);
        RegionRequest request = RegionRequest.createInstance(server.getPath(), downsampleThumbnail, 0, 0, server.getWidth(), server.getHeight());
        OverlayOptions optionsThumbnail = new OverlayOptions();
        optionsThumbnail.setShowTMAGrid(true);
        optionsThumbnail.setShowGrid(false);
        optionsThumbnail.setShowAnnotations(false);
        optionsThumbnail.setShowDetections(false);
        try {
            var renderedServer = new RenderedImageServer.Builder(imageData).layers(new TMAGridOverlay(overlayOptions)).downsamples(downsampleThumbnail).build();
            ImageWriterTools.writeImageRegion(renderedServer, request, fileTMAMap.getAbsolutePath());
        // ImageWriters.writeImageRegionWithOverlay(imageData.getServer(), Collections.singletonList(new TMAGridOverlay(overlayOptions, imageData)), request, fileTMAMap.getAbsolutePath());
        } catch (IOException e) {
            logger.warn("Unable to write image overview: " + e.getLocalizedMessage(), e);
        }
        final double downsample = Double.isNaN(downsampleFactor) ? (server.getPixelCalibration().hasPixelSizeMicrons() ? ServerTools.getDownsampleFactor(server, preferredExportPixelSizeMicrons) : 1) : downsampleFactor;
        // Creating a plugin makes it possible to parallelize & show progress easily
        var renderedImageServer = new RenderedImageServer.Builder(imageData).layers(new HierarchyOverlay(null, options, imageData)).downsamples(downsample).build();
        ExportCoresPlugin plugin = new ExportCoresPlugin(dirData, renderedImageServer, downsample, coreExt);
        PluginRunner<BufferedImage> runner;
        var qupath = QuPathGUI.getInstance();
        if (qupath == null || qupath.getImageData() != imageData) {
            runner = new CommandLinePluginRunner<>(imageData);
            plugin.runPlugin(runner, null);
        } else {
            try {
                qupath.runPlugin(plugin, null, false);
            } catch (Exception e) {
                logger.error("Error writing TMA data: " + e.getLocalizedMessage(), e);
            }
        // new Thread(() -> qupath.runPlugin(plugin, null, false)).start();
        // runner = new PluginRunnerFX(QuPathGUI.getInstance());
        // new Thread(() -> plugin.runPlugin(runner, null)).start();
        }
    }
}
Also used : TMACoreObject(qupath.lib.objects.TMACoreObject) DefaultTMAGrid(qupath.lib.objects.hierarchy.DefaultTMAGrid) TMAGrid(qupath.lib.objects.hierarchy.TMAGrid) IOException(java.io.IOException) ROI(qupath.lib.roi.interfaces.ROI) RenderedImageServer(qupath.lib.gui.images.servers.RenderedImageServer) BufferedImage(java.awt.image.BufferedImage) IOException(java.io.IOException) FileNotFoundException(java.io.FileNotFoundException) HierarchyOverlay(qupath.lib.gui.viewer.overlays.HierarchyOverlay) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) OverlayOptions(qupath.lib.gui.viewer.OverlayOptions) File(java.io.File) RegionRequest(qupath.lib.regions.RegionRequest) TMAGridOverlay(qupath.lib.gui.viewer.overlays.TMAGridOverlay) PrintWriter(java.io.PrintWriter)

Example 2 with ObservableMeasurementTableData

use of qupath.lib.gui.measure.ObservableMeasurementTableData in project qupath by qupath.

the class QPEx method saveMeasurements.

/**
 * Save measurements for the specified image for objects of a fixed type.
 * @param imageData the image data
 * @param type the type of objects to measure
 * @param path file path describing where to write the results
 * @param includeColumns specific columns to include, or empty to indicate that all measurements should be exported
 */
public static void saveMeasurements(final ImageData<?> imageData, final Class<? extends PathObject> type, final String path, final String... includeColumns) {
    File fileOutput = new File(resolvePath(path));
    if (fileOutput.isDirectory()) {
        String ext = ",".equals(PathPrefs.tableDelimiterProperty().get()) ? ".csv" : ".txt";
        fileOutput = new File(fileOutput, ServerTools.getDisplayableImageName(imageData.getServer()) + " " + PathObjectTools.getSuitableName(type, true) + ext);
    }
    ObservableMeasurementTableData model = new ObservableMeasurementTableData();
    model.setImageData(imageData, imageData == null ? Collections.emptyList() : imageData.getHierarchy().getObjects(null, type));
    try (PrintWriter writer = new PrintWriter(fileOutput, StandardCharsets.UTF_8)) {
        Collection<String> excludeColumns;
        if (includeColumns.length == 0) {
            excludeColumns = Collections.emptyList();
        } else {
            excludeColumns = new LinkedHashSet<>(model.getAllNames());
            excludeColumns.removeAll(Arrays.asList(includeColumns));
        }
        for (String row : SummaryMeasurementTableCommand.getTableModelStrings(model, PathPrefs.tableDelimiterProperty().get(), excludeColumns)) writer.println(row);
        writer.close();
    } catch (IOException e) {
        logger.error("Error writing file to " + fileOutput, e);
    }
}
Also used : ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) IOException(java.io.IOException) File(java.io.File) PrintWriter(java.io.PrintWriter)

Example 3 with ObservableMeasurementTableData

use of qupath.lib.gui.measure.ObservableMeasurementTableData in project qupath by qupath.

the class TMASummaryViewer method getEntriesForTMAData.

private static List<TMAEntry> getEntriesForTMAData(final ImageData<BufferedImage> imageData) {
    List<TMAEntry> entriesNew = new ArrayList<>();
    if (imageData.getHierarchy().getTMAGrid() == null)
        return entriesNew;
    ObservableMeasurementTableData data = new ObservableMeasurementTableData();
    data.setImageData(imageData, imageData.getHierarchy().getTMAGrid().getTMACoreList());
    for (TMACoreObject core : imageData.getHierarchy().getTMAGrid().getTMACoreList()) {
        entriesNew.add(TMAEntries.createTMAObjectEntry(imageData, data, core));
    }
    return entriesNew;
}
Also used : ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) TMACoreObject(qupath.lib.objects.TMACoreObject) TMAEntry(qupath.lib.gui.tma.TMAEntries.TMAEntry) ArrayList(java.util.ArrayList)

Example 4 with ObservableMeasurementTableData

use of qupath.lib.gui.measure.ObservableMeasurementTableData in project qupath by qupath.

the class MeasurementExporter method exportMeasurements.

/**
 * Exports the measurements of one or more entries in the project.
 * This function first opens all the images in the project to store
 * all the column names and values of the measurements.
 * Then, it loops through the maps containing the values to write
 * them to the given output stream.
 * @param stream
 */
public void exportMeasurements(OutputStream stream) {
    long startTime = System.currentTimeMillis();
    Map<ProjectImageEntry<?>, String[]> imageCols = new HashMap<>();
    Map<ProjectImageEntry<?>, Integer> nImageEntries = new HashMap<>();
    List<String> allColumns = new ArrayList<>();
    Multimap<String, String> valueMap = LinkedListMultimap.create();
    String pattern = "(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)";
    for (ProjectImageEntry<?> entry : imageList) {
        try {
            ImageData<?> imageData = entry.readImageData();
            ObservableMeasurementTableData model = new ObservableMeasurementTableData();
            Collection<PathObject> pathObjects = imageData == null ? Collections.emptyList() : imageData.getHierarchy().getObjects(null, type);
            if (filter != null)
                pathObjects = pathObjects.stream().filter(filter).collect(Collectors.toList());
            model.setImageData(imageData, pathObjects);
            List<String> data = SummaryMeasurementTableCommand.getTableModelStrings(model, separator, excludeColumns);
            // Get header
            String[] header;
            String headerString = data.get(0);
            if (headerString.chars().filter(e -> e == '"').count() > 1)
                header = headerString.split(separator.equals("\t") ? "\\" + separator : separator + pattern, -1);
            else
                header = headerString.split(separator);
            imageCols.put(entry, header);
            nImageEntries.put(entry, data.size() - 1);
            for (String col : header) {
                if (!allColumns.contains(col) && !excludeColumns.contains(col))
                    allColumns.add(col);
            }
            // To keep the same column order, just delete non-relevant columns
            if (!includeOnlyColumns.isEmpty())
                allColumns.removeIf(n -> !includeOnlyColumns.contains(n));
            for (int i = 1; i < data.size(); i++) {
                String[] row;
                String rowString = data.get(i);
                // Check if some values in the row are escaped
                if (rowString.chars().filter(e -> e == '"').count() > 1)
                    row = rowString.split(separator.equals("\t") ? "\\" + separator : separator + pattern, -1);
                else
                    row = rowString.split(separator);
                // Put value in map
                for (int elem = 0; elem < row.length; elem++) {
                    if (allColumns.contains(header[elem]))
                        valueMap.put(header[elem], row[elem]);
                }
            }
        } catch (Exception e) {
            logger.error(e.getLocalizedMessage(), e);
        }
    }
    try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8))) {
        writer.write(String.join(separator, allColumns));
        writer.write(System.lineSeparator());
        Iterator[] its = new Iterator[allColumns.size()];
        for (int col = 0; col < allColumns.size(); col++) {
            its[col] = valueMap.get(allColumns.get(col)).iterator();
        }
        for (ProjectImageEntry<?> entry : imageList) {
            for (int nObject = 0; nObject < nImageEntries.get(entry); nObject++) {
                for (int nCol = 0; nCol < allColumns.size(); nCol++) {
                    if (Arrays.stream(imageCols.get(entry)).anyMatch(allColumns.get(nCol)::equals)) {
                        String val = (String) its[nCol].next();
                        // NaN values -> blank
                        if (val.equals("NaN"))
                            val = "";
                        writer.write(val);
                    }
                    if (nCol < allColumns.size() - 1)
                        writer.write(separator);
                }
                writer.write(System.lineSeparator());
            }
        }
    } catch (Exception e) {
        logger.error("Error writing to file: " + e.getLocalizedMessage(), e);
    }
    long endTime = System.currentTimeMillis();
    long timeMillis = endTime - startTime;
    String time = null;
    if (timeMillis > 1000 * 60)
        time = String.format("Total processing time: %.2f minutes", timeMillis / (1000.0 * 60.0));
    else if (timeMillis > 1000)
        time = String.format("Total processing time: %.2f seconds", timeMillis / (1000.0));
    else
        time = String.format("Total processing time: %d milliseconds", timeMillis);
    logger.info("Processed {} images", imageList.size());
    logger.info(time);
}
Also used : Arrays(java.util.Arrays) LoggerFactory(org.slf4j.LoggerFactory) HashMap(java.util.HashMap) Multimap(com.google.common.collect.Multimap) ArrayList(java.util.ArrayList) PathRootObject(qupath.lib.objects.PathRootObject) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) Map(java.util.Map) OutputStreamWriter(java.io.OutputStreamWriter) OutputStream(java.io.OutputStream) PrintWriter(java.io.PrintWriter) LinkedListMultimap(com.google.common.collect.LinkedListMultimap) ImageData(qupath.lib.images.ImageData) Logger(org.slf4j.Logger) Iterator(java.util.Iterator) ProjectImageEntry(qupath.lib.projects.ProjectImageEntry) BufferedImage(java.awt.image.BufferedImage) Predicate(java.util.function.Predicate) Collection(java.util.Collection) FileOutputStream(java.io.FileOutputStream) Collectors(java.util.stream.Collectors) File(java.io.File) StandardCharsets(java.nio.charset.StandardCharsets) PathObject(qupath.lib.objects.PathObject) List(java.util.List) SummaryMeasurementTableCommand(qupath.lib.gui.commands.SummaryMeasurementTableCommand) Collections(java.util.Collections) PathPrefs(qupath.lib.gui.prefs.PathPrefs) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) PathObject(qupath.lib.objects.PathObject) Iterator(java.util.Iterator) ProjectImageEntry(qupath.lib.projects.ProjectImageEntry) OutputStreamWriter(java.io.OutputStreamWriter) PrintWriter(java.io.PrintWriter)

Example 5 with ObservableMeasurementTableData

use of qupath.lib.gui.measure.ObservableMeasurementTableData in project qupath by qupath.

the class ObservableMeasurementTableDataTest method test.

@SuppressWarnings("javadoc")
@Test
public void test() {
    // See https://github.com/locationtech/jts/issues/571
    for (int counter = 0; counter < 50; counter++) {
        ImageData<BufferedImage> imageData = new ImageData<>(null);
        PathClass tumorClass = PathClassFactory.getPathClass(StandardPathClasses.TUMOR);
        PathClass stromaClass = PathClassFactory.getPathClass(StandardPathClasses.STROMA);
        // PathClass otherClass = PathClassFactory.getDefaultPathClass(PathClasses.OTHER);
        PathClass artefactClass = PathClassFactory.getPathClass("Artefact");
        PathObjectHierarchy hierarchy = imageData.getHierarchy();
        // Add a parent annotation
        PathObject parent = PathObjects.createAnnotationObject(ROIs.createRectangleROI(500, 500, 1000, 1000, ImagePlane.getDefaultPlane()));
        // Create 100 tumor detections
        // ROI emptyROI = ROIs.createEmptyROI();
        ROI smallROI = ROIs.createRectangleROI(500, 500, 1, 1, ImagePlane.getDefaultPlane());
        for (int i = 0; i < 100; i++) {
            if (i < 25)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getNegative(tumorClass)));
            else if (i < 50)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getOnePlus(tumorClass)));
            else if (i < 75)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getTwoPlus(tumorClass)));
            else if (i < 100)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getThreePlus(tumorClass)));
        }
        // Create 100 stroma detections
        for (int i = 0; i < 100; i++) {
            if (i < 50)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getNegative(stromaClass)));
            else if (i < 60)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getOnePlus(stromaClass)));
            else if (i < 70)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getTwoPlus(stromaClass)));
            else if (i < 100)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getThreePlus(stromaClass)));
        }
        // Create 50 artefact detections
        for (int i = 0; i < 50; i++) {
            parent.addPathObject(PathObjects.createDetectionObject(smallROI, artefactClass));
        }
        hierarchy.addPathObject(parent);
        ObservableMeasurementTableData model = new ObservableMeasurementTableData();
        model.setImageData(imageData, Collections.singletonList(parent));
        // Check tumor counts
        assertEquals(100, model.getNumericValue(parent, "Num Tumor (base)"), EPSILON);
        assertEquals(25, model.getNumericValue(parent, "Num Tumor: Negative"), EPSILON);
        assertEquals(25, model.getNumericValue(parent, "Num Tumor: 1+"), EPSILON);
        assertEquals(25, model.getNumericValue(parent, "Num Tumor: 2+"), EPSILON);
        assertEquals(25, model.getNumericValue(parent, "Num Tumor: 3+"), EPSILON);
        assertTrue(Double.isNaN(model.getNumericValue(parent, "Num Tumor: 4+")));
        // Check tumor H-score, Allred score & positive %
        assertEquals(150, model.getNumericValue(parent, "Tumor: H-score"), EPSILON);
        assertEquals(75, model.getNumericValue(parent, "Tumor: Positive %"), EPSILON);
        assertEquals(2, model.getNumericValue(parent, "Tumor: Allred intensity"), EPSILON);
        assertEquals(5, model.getNumericValue(parent, "Tumor: Allred proportion"), EPSILON);
        assertEquals(7, model.getNumericValue(parent, "Tumor: Allred score"), EPSILON);
        // Check tumor H-score unaffected when tumor detections added without intensity classification
        for (int i = 0; i < 10; i++) parent.addPathObject(PathObjects.createDetectionObject(smallROI, tumorClass));
        hierarchy.fireHierarchyChangedEvent(this);
        model.refreshEntries();
        // model.setImageData(imageData, Collections.singletonList(parent));
        assertEquals(100, model.getNumericValue(parent, "Num Stroma (base)"), EPSILON);
        assertEquals(50, model.getNumericValue(parent, "Num Stroma: Negative"), EPSILON);
        assertEquals(150, model.getNumericValue(parent, "Tumor: H-score"), EPSILON);
        assertEquals(75, model.getNumericValue(parent, "Tumor: Positive %"), EPSILON);
        // Check stroma scores
        assertEquals(100, model.getNumericValue(parent, "Num Stroma (base)"), EPSILON);
        assertEquals(120, model.getNumericValue(parent, "Stroma: H-score"), EPSILON);
        // Check complete scores
        assertEquals(135, model.getNumericValue(parent, "Stroma + Tumor: H-score"), EPSILON);
        // Add a new parent that completely contains the current object, and confirm complete scores agree
        PathObject parentNew = PathObjects.createAnnotationObject(ROIs.createRectangleROI(0, 0, 2000, 2000, ImagePlane.getDefaultPlane()));
        hierarchy.addPathObject(parentNew);
        model.refreshEntries();
        assertEquals(135, model.getNumericValue(parent, "Stroma + Tumor: H-score"), EPSILON);
        assertEquals(135, model.getNumericValue(parentNew, "Stroma + Tumor: H-score"), EPSILON);
        // Create a new object and demonstrate Allred dependence on a single cell
        PathObject parentAllred = PathObjects.createAnnotationObject(ROIs.createRectangleROI(4000, 4000, 1000, 1000, ImagePlane.getDefaultPlane()));
        ROI newROI = ROIs.createEllipseROI(4500, 4500, 10, 10, ImagePlane.getDefaultPlane());
        for (int i = 0; i < 100; i++) parentAllred.addPathObject(PathObjects.createDetectionObject(newROI, PathClassFactory.getNegative(tumorClass)));
        hierarchy.addPathObject(parentAllred);
        model.refreshEntries();
        assertEquals(0, model.getNumericValue(parentAllred, "Tumor: Allred score"), EPSILON);
        parentAllred.addPathObject(PathObjects.createDetectionObject(newROI, PathClassFactory.getThreePlus(tumorClass)));
        hierarchy.fireHierarchyChangedEvent(parentAllred);
        model.refreshEntries();
        assertEquals(4, model.getNumericValue(parentAllred, "Tumor: Allred score"), EPSILON);
    }
}
Also used : PathClass(qupath.lib.objects.classes.PathClass) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) PathObject(qupath.lib.objects.PathObject) ImageData(qupath.lib.images.ImageData) ROI(qupath.lib.roi.interfaces.ROI) BufferedImage(java.awt.image.BufferedImage) Test(org.junit.jupiter.api.Test)

Aggregations

ObservableMeasurementTableData (qupath.lib.gui.measure.ObservableMeasurementTableData)7 BufferedImage (java.awt.image.BufferedImage)5 File (java.io.File)5 PrintWriter (java.io.PrintWriter)4 IOException (java.io.IOException)3 ArrayList (java.util.ArrayList)3 ImageData (qupath.lib.images.ImageData)3 PathObject (qupath.lib.objects.PathObject)3 TMACoreObject (qupath.lib.objects.TMACoreObject)3 ROI (qupath.lib.roi.interfaces.ROI)3 FileNotFoundException (java.io.FileNotFoundException)2 StandardCharsets (java.nio.charset.StandardCharsets)2 Arrays (java.util.Arrays)2 Collection (java.util.Collection)2 Collections (java.util.Collections)2 List (java.util.List)2 Map (java.util.Map)2 Collectors (java.util.stream.Collectors)2 Logger (org.slf4j.Logger)2 LoggerFactory (org.slf4j.LoggerFactory)2