diff --git a/src/main/java/io/bioimage/modelrunner/bioimageio/description/ModelDescriptor_old.java b/src/main/java/io/bioimage/modelrunner/bioimageio/description/ModelDescriptor_old.java deleted file mode 100644 index 72583c73..00000000 --- a/src/main/java/io/bioimage/modelrunner/bioimageio/description/ModelDescriptor_old.java +++ /dev/null @@ -1,1165 +0,0 @@ -/*- - * #%L - * Use deep learning frameworks from Java in an agnostic and isolated way. - * %% - * Copyright (C) 2022 - 2023 Institut Pasteur and BioImage.IO developers. - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -package io.bioimage.modelrunner.bioimageio.description; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Map.Entry; -import java.util.stream.Collectors; - -import io.bioimage.modelrunner.bioimageio.BioimageioRepo; -import io.bioimage.modelrunner.bioimageio.description.exceptions.ModelSpecsException; -import io.bioimage.modelrunner.bioimageio.description.weights.ModelWeight; -import io.bioimage.modelrunner.utils.Constants; -import io.bioimage.modelrunner.utils.Log; -import io.bioimage.modelrunner.utils.YAMLUtils; - - -/** - * A data structure holding a single Bioimage.io pretrained model description. This instances are created by opening a {@code model.yaml} file. - * More info about the parameters can be found at: - * https://github.com/bioimage-io/spec-bioimage-io/blob/gh-pages/model_spec_latest.md - * - * @author Daniel Felipe Gonzalez Obando and Carlos Garcia Lopez de Haro - */ -public class ModelDescriptor_old -{ - private String format_version; - private String name; - private String nickname; - private String timestamp; - private String description; - private String type; - private List authors; - private List maintainers; - private List packaged_by; - private List cite; - private List badges; - private List tags; - private String license; - private String git_repo; - private String documentation; - private String rdf_source; - private List covers; - private List sample_inputs; - private List sample_outputs; - private List test_inputs; - private List test_outputs; - private List input_tensors; - private List output_tensors; - private ExecutionConfig config; - private ModelWeight weights; - private Map attachments; - private String download_url; - private String icon; - private String version; - private List links; - private Map parent; - private boolean isModelLocal; - private static String fromLocalKey = "fromLocalRepo"; - private static String modelPathKey = "modelPath"; - private static List sampleBioengineModels = Arrays.asList(new String[] {"cell_pose"});//, "inception", "stardist"}); - private String modelID; - private String localModelPath; - private boolean supportBioengine = false; - - private ModelDescriptor_old() - { - } - - /** - * Opens the provided file and builds an instance of {@link ModelDescriptor_old} from it. - * - * @param modelFile - * Model descriptor file. - * @return The instance of the model descriptor. - * @throws ModelSpecsException if any of the parameters in the rdf.yaml file does not make fit the constraints - */ - public static ModelDescriptor_old readFromLocalFile(String modelFile) throws ModelSpecsException - { - return readFromLocalFile(modelFile, true); - } - - /** - * Opens the provided file and builds an instance of {@link ModelDescriptor_old} from it. - * - * @param modelFile - * Model descriptor file. - * @param verbose - * whether to print the path to the file and the time to the console or not - * @return The instance of the model descriptor. - * @throws ModelSpecsException if any of the parameters in the rdf.yaml file does not make fit the constraints, - */ - public static ModelDescriptor_old readFromLocalFile(String modelFile, boolean verbose) throws ModelSpecsException - { - // Get the date to be able to log with the time - if (verbose) - System.out.println(Log.gct() + " -- LOCAL: Searching model at " + new File(modelFile).getParent()); - Map yamlElements; - try { - yamlElements = YAMLUtils.load(modelFile); - } catch (IOException ex) { - throw new IllegalStateException("", ex); - } - yamlElements.put(fromLocalKey, true); - yamlElements.put(modelPathKey, new File(modelFile).getParent()); - return buildModelDescription(yamlElements); - } - - /** - * Reads a yaml text String and builds an instance of {@link ModelDescriptor_old} from it - * - * @param yamlText - * text read from a yaml file that contains an rdf.yaml file - * @return The instance of the model descriptor. - * @throws ModelSpecsException if any of the parameters in the rdf.yaml file does not make fit the constraints - */ - public static ModelDescriptor_old readFromYamlTextString(String yamlText) throws ModelSpecsException - { - return readFromYamlTextString(yamlText, true); - } - - /** - * Reads a yaml text String and builds an instance of {@link ModelDescriptor_old} from it. - * - * @param yamlText - * text read from a yaml file that contains an rdf.yaml file - * @param verbose - * whether to print info about the rdf.yaml that is being read or not - * @return The instance of the model descriptor. - * @throws ModelSpecsException if any of the parameters in the rdf.yaml file does not make fit the constraints - */ - public static ModelDescriptor_old readFromYamlTextString(String yamlText, boolean verbose) throws ModelSpecsException - { - // Convert the String of text that contains the yaml file into Map - Map yamlElements = YAMLUtils.loadFromString(yamlText); - // Let the user know the model the plugin is trying to load - if (yamlElements.get("name") != null && verbose) { - System.out.println(Log.gct() + " -- Bioimage.io: Inspecting model: " + (String) yamlElements.get("name")); - } else if (verbose) { - System.out.println(Log.gct() + " -- Bioimage.io: Inspecting model defined by: " + yamlText); - } - yamlElements.put(fromLocalKey, false); - return buildModelDescription(yamlElements); - } - - @SuppressWarnings("unchecked") - /** - * Build a {@link ModelDescriptor_old} object from a map containing the elements read from - * a rdf.yaml file - * @param yamlElements - * map with the information read from a yaml file - * @return a {@link ModelDescriptor_old} with the info of a Bioimage.io model - * @throws ModelSpecsException if any of the parameters in the rdf.yaml file does not make fit the constraints - */ - private static ModelDescriptor_old buildModelDescription(Map yamlElements) throws ModelSpecsException - { - ModelDescriptor_old modelDescription = new ModelDescriptor_old(); - - Set yamlFields = yamlElements.keySet(); - String[] yamlFieldsArr = new String[yamlFields.size()]; - Arrays.sort(yamlFields.toArray(yamlFieldsArr)); - for (String field : yamlFieldsArr) - { - Object fieldElement = yamlElements.get(field); - try - { - switch (field) - { - case "format_version": - modelDescription.format_version = (String) fieldElement; - break; - case "name": - modelDescription.name = (String) fieldElement; - break; - case "timestamp": - modelDescription.timestamp = fieldElement.toString(); - break; - case "description": - modelDescription.description = (String) fieldElement; - break; - case "id": - modelDescription.modelID = (String) fieldElement; - break; - case "authors": - modelDescription.authors = buildAuthorElements((List) fieldElement); - break; - case "maintainers": - modelDescription.maintainers = buildAuthorElements((List) fieldElement); - break; - case "packaged_by": - modelDescription.packaged_by = buildAuthorElements((List) fieldElement); - break; - case "cite": - modelDescription.cite = buildCiteElements((List) fieldElement); - break; - case "parent": - modelDescription.parent = (Map) fieldElement; - break; - case "git_repo": - modelDescription.git_repo = checkUrl((String) fieldElement); - break; - case "tags": - modelDescription.tags = castListStrings(fieldElement); - break; - case "links": - modelDescription.links = castListStrings(fieldElement); - break; - case "license": - modelDescription.license = (String) fieldElement; - break; - case "documentation": - modelDescription.documentation = (String) fieldElement; - break; - case "test_inputs": - modelDescription.test_inputs = buildTestArtifacts((List) fieldElement); - break; - case "test_outputs": - modelDescription.test_outputs = buildTestArtifacts((List) fieldElement); - break; - case "sample_inputs": - modelDescription.sample_inputs = buildSampleImages((List) fieldElement); - break; - case "sample_outputs": - modelDescription.sample_outputs = buildSampleImages((List) fieldElement); - break; - case "type": - modelDescription.type = (String) fieldElement; - break; - case "icon": - modelDescription.icon = (String) fieldElement; - break; - case "download_url": - modelDescription.download_url = checkUrl((String) fieldElement); - break; - case "rdf_source": - modelDescription.rdf_source = checkUrl((String) fieldElement); - break; - case "attachments": - modelDescription.attachments = (Map) fieldElement; - break; - case "covers": - modelDescription.covers = buildUrlElements((List) fieldElement); - break; - case "badges": - modelDescription.badges = buildBadgeElements((List) fieldElement); - break; - case "inputs": - modelDescription.setInputTensors(buildInputTensors((List) yamlElements.get(field))); - break; - case "outputs": - modelDescription.setOutputTensors(buildOutputTensors((List) yamlElements.get(field))); - break; - case "config": - modelDescription.config = buildConfig((Map) yamlElements.get(field)); - break; - case "weights": - modelDescription.weights = buildWeights((Map) yamlElements.get(field)); - break; - case "fromLocalRepo": - modelDescription.isModelLocal = (boolean) fieldElement; - break; - case "modelPath": - modelDescription.localModelPath = (String) fieldElement; - break; - default: - break; - } - } - catch (IOException e) - { - throw new ModelSpecsException("Invalid model element: " + field + "->" + e.getMessage()); - } - } - Object bio = modelDescription.config.getSpecMap().get("bioimageio"); - if ((bio != null) && (bio instanceof Map)) - modelDescription.nickname = (String) (((Map) bio).get("nickname")); - modelDescription.addBioEngine(); - if (modelDescription.localModelPath == null) - return modelDescription; - modelDescription.addModelPath(new File(modelDescription.localModelPath).toPath()); - SpecialModels.checkSpecialModels(modelDescription); - return modelDescription; - } - - /** - * Every model in the bioimage.io can be run in the BioEngine as long as it is in the - * collections repo: - * https://github.com/bioimage-io/collection-bioimage-io/blob/e77fec7fa4d92d90c25e11331a7d19f14b9dc2cf/rdfs/10.5281/zenodo.6200999/6224243/rdf.yaml - * @throws ModelSpecsException servers do not correspond to an actual url - */ - private void addBioEngine() throws ModelSpecsException { - // TODO decide what to do with servers. Probably need permissions / Implement authentication - if (getName().equals("cellpose-python")) { - supportBioengine = true; - return; - } else if (getName().equals("bestfitting-inceptionv3-single-cell")) { - return; - } else if (getName().equals("stardist")) { - supportBioengine = true; - return; - } else if (modelID == null) { - supportBioengine = false; - return; - } - try { - supportBioengine = BioimageioRepo.isModelOnTheBioengineById(modelID); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * TODO - * TODO - * TODO ADD BIOENGINE SOON - * Method that retrieves the sample BioEngine models that Icy provides as an example - * to test the BioEngine - * @return a list with sample biengine models - */ - public static ArrayList> addSampleBioEngineModels() { - ArrayList> sampleModels = new ArrayList>(); - for (String sample : sampleBioengineModels) { - try { - InputStream inputStream = ModelDescriptor_old.class.getClassLoader() - .getResourceAsStream(sample + ".yaml"); - ByteArrayOutputStream result = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - for (int length; (length = inputStream.read(buffer)) != -1; ) { - result.write(buffer, 0, length); - } - // StandardCharsets.UTF_8.name() > JDK 7 - String txt = result.toString("UTF-8"); - result.close(); - inputStream.close(); - // TODO sampleModels.add(CollectionUtils.createEntry(Paths.get(sample), loadFromYamlTextString(txt))); - } catch (Exception e) { - e.printStackTrace(); - System.out.println(Log.gct() + " -- BioEngine: unable to load sample model " + sample); - } - } - return sampleModels; - } - - /** - * MAke sure that an object that is supposed to be a List - * is actually a List - * @param list - * the possible List - * @return a List or null if the Object was not an instance of a List - */ - private static List castListStrings(Object list) { - List out = null; - if (list instanceof List) { - out = new ArrayList(); - out = (List) list; - } else if (list instanceof String) { - out = new ArrayList(); - out.add((String) list); - } - return out; - } - - /** - * Create a list with the authors of teh model as read from the rdf.yaml file - * @param authElements - * a raw list with the info about the authors - * @return a list with the info about the authors packaged in the {@link Author} object - */ - private static List buildAuthorElements(List authElements) - { - List authors = new ArrayList<>(); - for (Object elem : authElements) - { - if (!(elem instanceof Map)) - continue; - @SuppressWarnings("unchecked") - Map dict = (Map) elem; - authors.add(Author.build(dict.get("affiliation"), dict.get("email"), dict.get("github_user"), dict.get("name"), dict.get("orcid"))); - } - return authors; - } - - /** - * Create a list with the citations of the model as read from the rdf.yaml file - * @param citeElements - * a raw list with the info about the citations - * @return a list with the info about the citations packaged in the {@link Cite} object - */ - private static List buildCiteElements(List citeElements) throws MalformedURLException - { - if (!(citeElements instanceof List)) - return null; - List cites = new ArrayList<>(); - for (Object elem : citeElements) - { - if (!(elem instanceof Map)) - continue; - @SuppressWarnings("unchecked") - Map dict = (Map) elem; - cites.add(Cite.build((String) dict.get("text"), (String) dict.get("doi"), (String) dict.get("url"))); - } - return cites; - } - - /** - * REturns a List of data from the yaml file that is supposed - * to correspond to an URI. - * @param coverElements - * data from the yaml - * @return the List with the URI data - */ - private static List buildUrlElements(Object coverElements) - { - List covers = new ArrayList<>(); - if ((coverElements instanceof List)) { - List elems = (List) coverElements; - for (Object elem : elems) - { - if (checkUrl((String) elem) == null) - continue; - covers.add((String) elem); - } - } else if ((coverElements instanceof String) && checkUrl((String) coverElements) != null) { - covers.add((String) coverElements); - } else { - covers = null; - } - - return covers; - } - - /** - * REturns a List of the sample images that are packed in the model - * folder as tifs and that are specified in the rdf.yaml file - * @param coverElements - * data from the yaml - * @return the List with the sample images data - */ - private static List buildSampleImages(Object coverElements) - { - List covers = new ArrayList<>(); - if ((coverElements instanceof List)) { - List elems = (List) coverElements; - for (Object elem : elems) - { - if (!(elem instanceof String)) - continue; - covers.add(SampleImage.build((String) elem)); - } - } else if ((coverElements instanceof String)) { - covers.add(SampleImage.build((String) coverElements)); - } - return covers.stream().filter(i -> i != null).collect(Collectors.toList()); - } - - /** - * REturns a List of the npy artifacts that are packed in the model - * folder as input and output test objects - * @param coverElements - * data from the yaml - * @return the List with the sample images data - */ - private static List buildTestArtifacts(Object coverElements) - { - List covers = new ArrayList<>(); - if ((coverElements instanceof List)) { - List elems = (List) coverElements; - for (Object elem : elems) - { - if (!(elem instanceof String)) - continue; - covers.add(TestArtifact.build((String) elem)); - } - } else if ((coverElements instanceof String)) { - covers.add(TestArtifact.build((String) coverElements)); - } - return covers.stream().filter(i -> i != null).collect(Collectors.toList()); - } - - private static List buildBadgeElements(List coverElements) - { - if (!(coverElements instanceof List)) - return null; - List badges = new ArrayList<>(); - for (Object elem : coverElements) - { - if (!(elem instanceof Map)) - continue; - Map dict = (Map) elem; - badges.add(Badge.build((String) dict.get("label"), (String) dict.get("icon"), (String) dict.get("url"))); - } - return badges; - } - - @SuppressWarnings("unchecked") - private static List buildInputTensors(List list) throws ModelSpecsException - { - if (!(list instanceof List)) - return null; - List tensors = new ArrayList<>(list.size()); - for (Object elem : list) - { - if (!(elem instanceof Map)) - continue; - tensors.add(TensorSpec.build((Map) elem, true)); - } - return tensors; - } - - /** - * Sets the input tensors of the model specs. Also adds the tiling - * @param inputTensors - * the input tensors - */ - public void setInputTensors(List inputTensors) { - boolean tiling = getConfig() == null ? true : - (getConfig().getDeepImageJ() == null ? true : getConfig().getDeepImageJ().isAllowTiling()); - for (TensorSpec tt : inputTensors) - tt.setTiling(tiling); - this.input_tensors = inputTensors; - } - - @SuppressWarnings("unchecked") - private static List buildOutputTensors(List list) throws ModelSpecsException - { - if (!(list instanceof List)) - return null; - List tensors = new ArrayList<>(list.size()); - for (Object elem : list) - { - if (!(elem instanceof Map)) - continue; - tensors.add(TensorSpec.build((Map) elem, false)); - } - return tensors; - } - - /** - * This method sets the output tensors and creates a total halo that is used - * by the inputs - * @param outputTensors - */ - private void setOutputTensors(List outputTensors) { - this.output_tensors = outputTensors; - float[] halo = calculateTotalInputHalo(); - for (TensorSpec inp : this.input_tensors) - inp.setTotalHalo(halo); - } - - /** - * Calculate the total input halo once the output tensors are set. - * NOte that the total halo is calculated following "xyczb" axes order, - * not the input axes order, as for several inputs the axes order might change - * for each of them - * @return the total input halo in "xyczb" axes order - */ - private float[] calculateTotalInputHalo() { - String[] targetForm = "XYCZB".split(""); - float[] halo = new float[targetForm.length]; - for (TensorSpec out: output_tensors) { - for (int i = 0; i < targetForm.length; i ++) { - int ind = out.getAxesOrder().toUpperCase().indexOf(targetForm[i]); - if (ind == -1) - continue; - float inputHalo = out.getHalo()[ind]; - // No halo in channel C because of offset - float inputOffset = -1 * out.getShape().getOffset()[ind]; - if (targetForm[i].toLowerCase().equals("c")) - inputOffset = 0; - float possibleHalo = (inputHalo + inputOffset) / out.getShape().getScale()[ind]; - if (possibleHalo > halo[i]) - halo[i] = possibleHalo; - } - } - return halo; - } - - private static ExecutionConfig buildConfig(Map yamlFieldElements) - { - return ExecutionConfig.build(yamlFieldElements); - } - - private static ModelWeight buildWeights(Map yamlFieldElements) - { - return ModelWeight.build(yamlFieldElements); - } - - /** - * Method that checks if a String corresponds to an URL - * - * @param str - * the string that should be possible to convert into URL - * @return the original string if it does correspond to an URL - * or null if it does not - */ - public static String checkUrl(String str) { - try { - URL url = new URL(str); - return str; - } catch (MalformedURLException e) { - return null; - } - } - - /** - * Create a set of specifications about the basic info of the model: name od the model, authors, - * references and Deep Learning framework - * @return a set of specs for the model - */ - public String buildBasicInfo() { - String info = ""; - // Display the name - info += "  -Name: " + this.getName().toUpperCase() + "
"; - // Create authors string - String auth = "["; - for (Author author : this.authors) - auth += (author.getName() != null ? author.getName() : "null") + "; "; - // Remove the "; " characters at the end and add "]" - if (auth.length() < 3) - auth = "[]"; - else - auth = auth.substring(0, auth.length() - 2) + "]"; - // Display the authors - info += "  -Authors: " + auth + "
"; - // Create the references String - String refs = "["; - if (cite != null) { - for (Cite citation : this.cite) - refs += (citation.getText() != null ? citation.getText() : (citation.getDoi() != null ? citation.getDoi() : "null")) + "; "; - } - // Remove the "; " characters at the end and add "]" - refs = refs.length() > 2 ? refs.substring(0, refs.length() - 2) + "]" : "[]"; - // Display the references - info += "  -References: " + refs + "
"; - // Add the model description - if (this.getDescription() != null) - info += "  -Description: " + this.getDescription() + "
"; - info += "
"; - // Add the location of the model in the local machine if it exists - String location = localModelPath != null ? localModelPath : rdf_source; - if (location == null) - info += "  -Location: " + "UNKNOWN" + "
"; - else - info += "  -Location: " + location + "
"; - // Display the frameworks available for this model - info += "  -Engine: " + this.weights.getSupportedWeightNamesAndVersion().toString() + "
"; - // Display the model id - info += "  -ID: " + this.modelID + "
"; - info += "
"; - return info; - } - - /** - * Write the tiling specs for the model - * @return the tiling specs for the model - */ - public String buildTilingInfo() { - String info = "  ----TILING SPECIFICATIONS----" + "
"; - HashMap dimMeaning = new HashMap(){{ - put("H", "height"); put("X", "width"); - put("Z", "depth"); put("C", "channels"); - }}; - // Create the String that explains the dimensions letters - info += "  Y: height, X: width, Z: depth, C: channels" + "
"; - // Add tiling info foe each of the arguments - info += "  -input images:" + "
"; - for (TensorSpec inp : this.input_tensors) { - info += "&ensp -" + inp.getName() + ":
"; - String[] dims = inp.getAxesOrder().toUpperCase().split(""); - String minString = "&emsp -minimum size: "; - String stepString = "&emsp -step: "; - for (int i = 0; i < dims.length; i ++) { - minString += dims[i] + ": " + inp.getShape().getTileMinimumSize()[i] + ", "; - stepString += dims[i] + ": " + inp.getShape().getTileStep()[i] + ", "; - } - // Remove the "; " characters at the end and add "]" - minString = minString.substring(0, minString.length() - 2) + "
"; - stepString = stepString.substring(0, stepString.length() - 2) + "
"; - info += minString; - info += stepString; - } - // Add small explanation - info += "  Each dimension is calculated as:" + "
"; - info += "&ensp " + "tile_size = minimum_size + n * step, where n >= 0" + "
"; - return info; - } - - /** - * Create specifications of the model containing the most important - * info that is going to be displayed on the DeepIcy plugin - * @return a String with the most important info - */ - public String buildInfo() { - String basicInfo = buildBasicInfo(); - String tilingInfo = buildTilingInfo(); - return basicInfo + tilingInfo; - } - - /** - * @return The version of the format used in the descriptor file. - */ - public String getFormatVersion() - { - return format_version; - } - - /** - * @return The name of this model. - */ - public String getName() - { - return name; - } - - /** - * @return The nickname of this model. - */ - public String getNickname() - { - return nickname; - } - - /** - * @return The ID of this model. - */ - public String getModelID() - { - return modelID; - } - - /** - * @return The creation timestamp of this model. - */ - public String getTimestamp() - { - return timestamp; - } - - /** - * @return The description of this model. - */ - public String getDescription() - { - return description; - } - - /** - * @return The list of authors for this model. - */ - public List getAuthors() - { - return authors; - } - - /** - * @return The list of citations for this model. - */ - public List getCite() - { - return cite; - } - - /** - * @return The URL of the git repository of this model. - */ - public String getGitRepo() - { - return git_repo; - } - - /** - * @return The list of tags associated with this model. - */ - public List getTags() - { - return tags; - } - - /** - * @return The license description for this model. - */ - public String getLicense() - { - return license; - } - - /** - * @return the type of Bioimage.io artifact that the rdf.yaml - * refers to. It can be model, dataset, application... - */ - public String getType() - { - return type; - } - - /** - * @return The documentation text associated to this model. - */ - public String getDocumentation() - { - return documentation; - } - - /** - * @return The list of URIs of the covers for this model. - */ - public List getCovers() - { - return covers; - } - - /** - * @return The list of input tensor specification instances for this model. - */ - public List getInputTensors() - { - return input_tensors; - } - - /** - * Searches for an input tensor with the given name. - * - * @param name - * Name of the tensor. - * @return The tensor with the provided name. null is returned if no tensor is found or if the input tensors list is not initialized. - */ - public TensorSpec findInputTensor(String name) - { - if (input_tensors == null) - { - return null; - } - - return input_tensors.stream() - .filter(t -> t.getName().equals(name)) - .findAny().orElse(null); - } - - /** - * Searches for an output tensor with the given name. - * - * @param name - * Name of the tensor. - * @return The tensor with the provided name. null is returned if no tensor is found or if the output tensors list is not initialized. - */ - public TensorSpec findOutputTensor(String name) - { - if (output_tensors == null) - { - return null; - } - - return output_tensors.stream() - .filter(t -> t.getName().equals(name)) - .findAny().orElse(null); - } - - /** - * Searches for an input tensor with the given name in the given list. - * - * @param name - * Name of the tensor. - * @param tts - * list of tensors where to look for the wanted name - * @return The tensor with the provided name. null is returned if no tensor is found or if the input tensors list is not initialized. - */ - public static TensorSpec findTensorInList(String name, List tts) - { - if (tts == null) - { - return null; - } - - return tts.stream() - .filter(t -> t.getName().equals(name)) - .findAny().orElse(null); - } - - /** - * @return The list of output tensor specification instances for this model. - */ - public List getOutputTensors() - { - return output_tensors; - } - - /** - * @return The execution configuration instance for this model. - */ - public ExecutionConfig getConfig() - { - return config; - } - - /** - * @return The model weights instance for this model. - */ - public ModelWeight getWeights() - { - return weights; - } - - /** - * @return the sample_inputs - */ - public List getSampleInputs() { - if (sample_inputs == null) - sample_inputs = new ArrayList(); - return sample_inputs; - } - - @Override - public String toString() - { - return "ModelDescription {formatVersion=" + format_version + ", name=" + name + ", timestamp=" + timestamp - + ", description=" + description + ", authors=" + authors + ", cite=" + cite + ", gitRepo=" + git_repo - + ", tags=" + tags + ", license=" + license + ", documentation=" + documentation + ", covers=" + covers - + ", inputTensors=" + input_tensors + ", outputTensors=" + output_tensors + ", config=" + config - + ", weights=" + weights + "}"; - } - - /** - * @return the maintainers - */ - public List getMaintainers() { - return maintainers; - } - - /** - * @return the packaged_by - */ - public List getPackagedBy() { - return packaged_by; - } - - /** - * @return the badges - */ - public List getBadges() { - if (badges == null) - badges = new ArrayList(); - return badges; - } - - /** - * @return the sample_outputs - */ - public List getSampleOutputs() { - if (sample_outputs == null) - sample_outputs = new ArrayList(); - return sample_outputs; - } - - /** - * @return the test_inputs - */ - public List getTestInputs() { - if (test_inputs == null) - test_inputs = new ArrayList(); - return test_inputs; - } - - /** - * @return the test_outputs - */ - public List getTestOutputs() { - if (test_outputs == null) - test_outputs = new ArrayList(); - return test_outputs; - } - - /** - * @return the attachments - */ - public Map getAttachments() { - return attachments; - } - - /** - * @return the download_url - */ - public String getDownloadUrl() { - return download_url; - } - - /** - * @return the rdf_source - */ - public String getRDFSource() { - return rdf_source; - } - - /** - * @return the icon - */ - public String getIcon() { - return icon; - } - - /** - * @return the version - */ - public String getVersion() { - return version; - } - - /** - * @return the links - */ - public List getLinks() { - return links; - } - - /** - * @return the parent - */ - public Map getParent() { - return parent; - } - - /** - * Mark the model as downloaded or not. This method is useful for when the - * user selects a model from the BioImage.io - * @param dd - * whether the model is already downloaded or not - */ - public void setDownloaded(boolean dd) { - isModelLocal = dd; - } - - /** - * Whether the model is already in the local repo or it has to be downloaded - * @return true if the model is already installed or false otherwise - */ - public boolean isModelInLocalRepo() { - return isModelLocal; - } - - /** - * Add the path where the local model is stored to the model descriptor - * @param modelBasePath - * the path to the model in the local machine - */ - public void addModelPath(Path modelBasePath) { - Path absPath = modelBasePath.toAbsolutePath(); - if (!absPath.toFile().exists()) { - throw new IllegalArgumentException("The path '" - + absPath.toString() + "' does not exist in the computer."); - } - localModelPath = absPath.toString(); - if (sample_inputs != null) - sample_inputs.stream().forEach(i -> i.addLocalModelPath(absPath)); - if (sample_outputs != null) - sample_outputs.stream().forEach(i -> i.addLocalModelPath(absPath)); - if (test_inputs != null) - test_inputs.stream().forEach(i -> i.addLocalModelPath(absPath)); - if (test_outputs != null) - test_outputs.stream().forEach(i -> i.addLocalModelPath(absPath)); - } - - /** - * Return String to path where the model is stored - * @return String directory where the model is stored - */ - public String getModelPath() { - return this.localModelPath; - } - - /** - * Method that returns whether tiling is allowed or not for the model - * @return true if tiling is allowed and false otherwise - */ - public boolean isTilingAllowed() { - if (this.config == null) - return true; - else if (this.config.getDeepImageJ() == null) - return true; - else - return this.getConfig().getDeepImageJ().isAllowTiling(); - } - - /** - * Method that returns whether the model is pyramidal or not - * @return true if the model is pyramidal, false otherwise - */ - public boolean isPyramidal() { - if (this.config == null) - return false; - else if (this.config.getDeepImageJ() == null) - return false; - else - return this.getConfig().getDeepImageJ().isPyramidalModel(); - } - - /** - * - * @return whether the model can be run on the bioengino or not - */ - public boolean canRunOnBioengine() { - return this.supportBioengine; - } - - /** - * Get the models at the local repo defined by the argument local repo - * @param localRepo - * String containing the path the directory that contains the model folders - * @return a list of the {@link ModelDescriptor_old}s of the available models - */ - public static List getModelsAtLocalRepo(String localRepo) { - File repoFile = new File(localRepo); - if ( !repoFile.isDirectory() ) - { - boolean created = repoFile.mkdirs(); - if ( !created ) - throw new IllegalArgumentException( "The directory " + repoFile.getAbsolutePath() + " cannot be created." ); - return Collections.emptyList(); - } - return Arrays.asList(repoFile.listFiles()).stream().map(ff -> { - try { - return ModelDescriptor_old.readFromLocalFile(ff.getAbsolutePath() + File.separator + Constants.RDF_FNAME, false); - } catch (Exception e) { - return null; - } - }).filter(mm -> mm != null).collect(Collectors.toList()); - } - - /** - * Get the models at the local repo. - * The default local repo is the 'models' folder in the directory where the program is being executed - * - * @return a list of the {@link ModelDescriptor_old}s of the available models - */ - public static List getModelsAtLocalRepo() { - return getModelsAtLocalRepo(new File("models").getAbsolutePath()); - } -} diff --git a/src/main/java/io/bioimage/modelrunner/bioimageio/description/TensorSpec_old.java b/src/main/java/io/bioimage/modelrunner/bioimageio/description/TensorSpec_old.java deleted file mode 100644 index ce4d6bb1..00000000 --- a/src/main/java/io/bioimage/modelrunner/bioimageio/description/TensorSpec_old.java +++ /dev/null @@ -1,914 +0,0 @@ -/*- - * #%L - * Use deep learning frameworks from Java in an agnostic and isolated way. - * %% - * Copyright (C) 2022 - 2023 Institut Pasteur and BioImage.IO developers. - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -package io.bioimage.modelrunner.bioimageio.description; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.IntStream; - -import io.bioimage.modelrunner.bioimageio.description.exceptions.ModelSpecsException; -import io.bioimage.modelrunner.tiling.PatchGridCalculator; -import io.bioimage.modelrunner.utils.YAMLUtils; - - -/** - * A tensor specification descriptor. It holds the information of an input or output tensor (name, shape, axis order, data type, halo, etc.). - * It is built from a input or output tensor map element in the yaml file. - * - * @author Carlos Garcia Lopez de Haro and Daniel Felipe Gonzalez Obando - */ -public class TensorSpec_old { - /** - * Whether the tensor represents an input or an output - */ - private boolean input; - /** - * The name of the tensor - */ - private String name; - /** - * The String axes order of the tensor (Ex: bcyx, xyc...) - */ - private String axes; - /** - * The data type of the tensor - */ - private String dataType; - /** - * The description of the tensor - */ - private String description; - /** - * For inputs, the total padding that the outputs have (halo + offset) and for outputs - * the halo of a particualr tensor. - * REgard that the input halo follows the "xyczb" axes order - * whereas the output follows the tensor axes order. - * This is done because for several inputs the axes order might change - * for each of them - */ - private float[] halo; - /** - * The shape information of a tensor - */ - private ShapeSpec shape; - /** - * The type of tensor (image, list) - */ - private String type; - /** - * The list of pre-processing routines - */ - private List preprocessing; - /** - * The list of post-processing routines - */ - private List postprocessing; - /** - * Whether this tensor allows tiling or not. - * Tiling is always allowed unless the contrary is defined - */ - private boolean tiling = true; - /** - * Tile/patch size selected by the user and used to process the image - */ - private int[] processingTile; - /** - * String representing the object tensor type image - */ - public static final String IMAGE = "image"; - /** - * String representing the object tensor type list (a matrix of 1 or 2 dimensions). - * In order for the object to be a list, the shape has to contain either only 1 dimension ("c"), - * contain 2, one of them being the batch_size ("bc"), or contain the letter "i", ("bic"). - * "I" comes from instance - */ - public static final String LIST = "list"; - - private static final long OPTIMAL_MAX_NUMBER_PIXELS = 4096 * 4096 * 3; - - /** - * Builds the tensor specification instance from the tensor map and an input flag. - * - * @param tensorSpecMap - * The map of elements describing the tensor. - * @param input - * Whether it is an input (true) or an output (false) tensor. - * @return The tensor specification instance. - * @throws ModelSpecsException if any of the fields does not fulfill the requirements - */ - @SuppressWarnings("unchecked") - public static TensorSpec_old build(Map tensorSpecMap, boolean input) throws ModelSpecsException - { - TensorSpec_old tensor = new TensorSpec_old(); - tensor.name = (String) tensorSpecMap.get("name"); - tensor.axes = (String) tensorSpecMap.get("axes"); - tensor.dataType = (String) tensorSpecMap.get("data_type"); - tensor.description = (String) tensorSpecMap.get("description"); - tensor.input = input; - // TODO - // List rangeList = (List) tensorSpecMap.get("data_range"); - // tensor.range = rangeList == null ? null : new ArrayList<>(rangeList); - List haloList = (List) tensorSpecMap.get("halo"); - tensor.halo = (input - ? null - : (haloList == null ? new float[tensor.axes.length()] : YAMLUtils.castListToFloatArray(haloList))); - tensor.shape = ShapeSpec.build(tensorSpecMap.get("shape"), input); - tensor.type = IMAGE; - if ((tensor.axes == null) || - (tensor.axes.length() <= 2 && tensor.axes.toUpperCase().matches(".*[B|I].*")) - || tensor.axes.toUpperCase().contains("I")|| tensor.axes.length() == 1) - { - tensor.type = LIST; - } - tensor.processingTile = tensor.shape.getTileRecomendedSize(); - - List preprocessingTensors = (List) tensorSpecMap.get("preprocessing"); - if (preprocessingTensors == null) - { - tensor.preprocessing = new ArrayList<>(0); - } - else - { - tensor.preprocessing = new ArrayList(preprocessingTensors.size()); - for (Object elem : preprocessingTensors) - { - tensor.preprocessing.add(TransformSpec.build((Map) elem)); - } - } - - List postprocessingTensors = (List) tensorSpecMap.get("postprocessing"); - if (postprocessingTensors == null) - { - tensor.postprocessing = new ArrayList<>(0); - } - else - { - tensor.postprocessing = new ArrayList(postprocessingTensors.size()); - for (Object elem : postprocessingTensors) - { - tensor.postprocessing.add(TransformSpec.build((Map) elem)); - } - } - - return tensor; - } - - /** - * In order to handle tensors that might be too big, or to handle halo to remove border artifacts, - * the image tensors can be tiled, into smaller images and the passed to the corresponding Neural NEtwork. - * This method allows setting the size of the tile. Note that the tile size should fulfil the parameters - * specified in the bioimage.io rdf.yaml file, and if it does not, running the model will fail - * @param tileSize - * size of the tile the input is going to be divided in - */ - public void setTileSize(int[] tileSize) { - this.processingTile = tileSize; - } - - /** - * REturn the axes order in a readable format for humans - * Example: byxc converts into Y,X,C; xbyc converts into X,Y,C - * @return the axes order in a readable format - */ - public String getDisplayableAxesOrder() { - String displayableAxes = axes.toUpperCase().replace("B", ""); - String[] displayableAxesArr = displayableAxes.split(""); - // REturn substring without the first and last chars. Foe ex: [X,Y,C] -> X,Y,C - String outStr = Arrays.toString(displayableAxesArr); - return outStr.substring(1, outStr.length() - 1); - } - - /** - * REturn an array of sizes separated by commas and without the batch size - * Example: if byxc and [1,256,256,3], then 256,256,3 - * @param arr - * arra contianing the whole size of a tensor - * @return the tile/patch size in displayable format - */ - public int[] getDisplayableSizes(int[] arr) { - int bInd = axes.toUpperCase().indexOf("B"); - if (bInd == -1) { - return arr; - } - return IntStream.range(0, arr.length) - .filter(i -> i != bInd) - .map(i -> arr[i]) - .toArray(); - } - - /** - * REturn a String of an array of sizes separated by commas and without the batch size - * Example: if byxc and [1,256,256,3], then 256,256,3 - * @param arr - * arra contianing the whole size of a tensor - * @return the tile/patch size in a String displayable (without b dimension) format - */ - public String getDisplayableSizesString(int[] arr) { - int[] displayableArr = getDisplayableSizes(arr); - // In case -1 is shown, display auto to be more human uunderstandable - String patchStr = Arrays.toString(displayableArr).replace("-1", "auto"); - // Remove the begining and end "[" "]" - patchStr = patchStr.substring(1, patchStr.length() - 1); - return patchStr; - } - - /** - * Set the constraints for each of the axes regarding step and minimum size - * @return the String containing the info for the tensor corresponding to min and step - * in the format: Minimum size: X=8, Y=8, C=0, Z=4 Step: X=8, Y=8, C=1, Z=4 - */ - public String getDisplayableStepMinConstraints() { - String minStr = "MINIMUM SIZE: "; - String stepStr = "STEP: "; - String[] axesArr = axes.toUpperCase().split(""); - for (int i = 0; i < axesArr.length; i ++) { - if (axesArr[i].equals("B")) - continue; - minStr += axesArr[i] + "=" + shape.getTileMinimumSize()[i] + ", "; - stepStr += axesArr[i] + "=" + shape.getTileStep()[i] + ", "; - } - // Remove the final ", " - minStr = minStr.substring(0, minStr.length() - ", ".length()); - stepStr = stepStr.substring(0, stepStr.length() - ", ".length()); - return minStr + " " + stepStr; - } - - /** TODO this method does not realy required to be non-static because the instance already - * contains the axes order, maybe separate two methods - * REturn an array containing the optimal patch for the given sequence. - * - * If the ouptut tensor size depends on the size of the current tensor, - * please use {@link #getOptimalTileSize(int[], String, TensorSpec_old)} - * - * @param seqSize - * array containing the size of the sequence with the axes order defined - * @param seqSizeAxes - * axes order of the size array - * @return the array with the patch of interest - */ - public int[] getOptimalTileSize(int[] seqSize, String seqSizeAxes) { - return getOptimalTileSizeForTiling(seqSize, seqSizeAxes, tiling); - } - - /** TODO this method does not realy required to be non-static because the instance already - * contains the axes order, maybe separate two methods - * REturn an array containing the optimal patch for the given sequence. - * @param seqSize - * array containing the size of the sequence with the axes order defined - * @param seqSizeAxes - * axes order of the size array - * @param affectedTensor - * the output tensor whose size depends on the size of the current tensor. Mainly to avoid that the - * ouput tensor is too big. - * @return the array with the patch of interest - */ - public int[] getOptimalTileSize(int[] seqSize, String seqSizeAxes, TensorSpec_old affectedTensor) { - return getOptimalTileSizeForTiling(seqSize, seqSizeAxes, tiling, affectedTensor); - } - - /** - * REturn an array containing the optimal patch for the given sequence. - * TODO add: An optimal patch has the arbitrary requirement of not being bigger than - * 10^6 pixels - * - * - * If the ouptut tensor size depends on the size of the current tensor, - * please use {@link #getOptimalTileSizeForTiling(int[], String, boolean, TensorSpec_old)} - * - * @param seqSize - * array containing the size of the sequence with the axes order defined - * @param seqSizeAxes - * axes order of the size array - * @param applyTiling - * whether tiling is considered or not. - * @return the array with the patch of interest - */ - public int[] getOptimalTileSizeForTiling(int[] seqSize, String seqSizeAxes, boolean applyTiling) { - return getOptimalTileSizeForTiling(seqSize, seqSizeAxes, applyTiling, null); - } - - /** - * REturn an array containing the optimal patch for the given sequence. - * TODO add: An optimal patch has the arbitrary requirement of not being bigger than - * 10^6 pixels - * @param seqSize - * array containing the size of the sequence with the axes order defined - * @param seqSizeAxes - * axes order of the size array - * @param applyTiling - * whether tiling is considered or not. - * @param affectedTensor - * the output tensor whose size depends on the size of the current tensor. Mainly to avoid that the - * ouput tensor is too big. - * @return the array with the patch of interest - */ - public int[] getOptimalTileSizeForTiling(int[] seqSize, String seqSizeAxes, boolean applyTiling, TensorSpec_old affectedTensor) { - int[] patch = new int[axes.length()]; - String seqSizeAxesUpper = seqSizeAxes.toUpperCase(); - seqSizeAxesUpper = seqSizeAxesUpper.replace("T", "B"); - String[] axesArr = axes.toUpperCase().split(""); - for (int ii = 0; ii < axesArr.length; ii ++) { - float haloVal = halo[ii]; - int min = shape.getTileMinimumSize()[ii]; - int step = shape.getTileStep()[ii]; - int ind = seqSizeAxesUpper.indexOf(axesArr[ii]); - int size = seqSize[ind]; - if (step != 0 && applyTiling) { - patch[ii] = (int)Math.ceil((size + 2 * haloVal) / step) * step; - // The patch cannot be 3 times bigger than the size of the image, because then - // mirroring would stop working - if (patch[ii] > 3 * size && (patch[ii] - step) >= min) - patch[ii] = patch[ii] - step; - if (patch[ii] > 3 * size && ((int)Math.ceil((double)size / (double)step) * step) >= min) - patch[ii] = (int)Math.ceil((double)size / (double)step) * step; - } else if (step != 0 && !applyTiling){ - // If tiling is not allowed and the step can vary, the patch size for that - // dimension is left to -1 and then calculated automatically after pre-processing - // just in case pre-processing changes the image size - patch[ii] = -1; - } else if (step == 0){ - patch[ii] = min; - } - } - - if (!applyTiling || Arrays.stream(shape.getTileStep()).allMatch(i -> i == 0)) - return patch; - long totPix = 1; - for (int ii : patch) totPix *= (long) ii; - long outputTotByteSize = calculateByteSizeOfAffectedOutput(this.axes, patch, affectedTensor); - - if (totPix < OPTIMAL_MAX_NUMBER_PIXELS && outputTotByteSize < Integer.MAX_VALUE) - return patch; - - long minPix = 1; - for (int ii : shape.getTileMinimumSize()) minPix *= (long) ii; - if (minPix > OPTIMAL_MAX_NUMBER_PIXELS) - return shape.getTileMinimumSize(); - - double ratioSize = (double) OPTIMAL_MAX_NUMBER_PIXELS / (double) totPix; - double ratioByte = (double) Integer.MAX_VALUE / (double) outputTotByteSize; - - double ratio = Math.min(ratioSize, ratioByte); - - for (int ii = 0; ii < axesArr.length; ii ++) { - int step = shape.getTileStep()[ii]; - if (step == 0) continue; - int min = shape.getTileMinimumSize()[ii]; - int prevTile = patch[ii]; - long nTot = totPix / prevTile; - if ((prevTile * ratio < min) && (min < 100) && (min != 1) && (min != 0)) - patch[ii] = (int)Math.ceil((double)100 / (double)step) * step; - else if (prevTile * ratio < min) - patch[ii] = min; - else - patch[ii] = (int)Math.floor((prevTile * ratio - min) / step) * step + min; - totPix = nTot * patch[ii]; - ratioSize = (double) OPTIMAL_MAX_NUMBER_PIXELS / (double) totPix; - ratioByte = (double) Integer.MAX_VALUE / (double) calculateByteSizeOfAffectedOutput(this.axes, patch, affectedTensor); - ratio = Math.min(ratioSize, ratioByte); - if (ratio > 1) - break; - } - return patch; - } - - private long calculateByteSizeOfAffectedOutput(String inputAxes, int[] inputSize, TensorSpec_old affectedOutput) { - if (affectedOutput == null) return 0; - inputSize = PatchGridCalculator.arrayToWantedAxesOrderAddOnes(inputSize, inputAxes, affectedOutput.axes); - int[] outputSize = new int[inputSize.length]; - if (!affectedOutput.shape.isFixedSize()) { - float[] scale = affectedOutput.shape.getScale(); - float[] offset = affectedOutput.shape.getOffset(); - for (int i = 0; i < scale.length; i ++) { - outputSize[i] = (int) (inputSize[i] * scale[i] + offset[i] * 2); - } - } else { - outputSize = affectedOutput.shape.getTileRecomendedSize(); - } - - long flatSize = 1; - for (int i : outputSize) flatSize *= (long) i; - if (affectedOutput.dataType.toLowerCase().equals("float32") - || affectedOutput.dataType.toLowerCase().equals("int32") - || affectedOutput.dataType.toLowerCase().equals("uint32")) - flatSize *= 4; - else if (affectedOutput.dataType.toLowerCase().equals("int16") - || affectedOutput.dataType.toLowerCase().equals("uint16")) - flatSize *= 2; - else if (affectedOutput.dataType.toLowerCase().equals("int64") - || affectedOutput.dataType.toLowerCase().equals("float64")) - flatSize *= 8; - return flatSize; - } - - /** - * REturn an array containing the optimal patch for the given sequence for a tensor that - * does not allow tiling. Should only be calculate after pre-processing and right - * before feeding the tensor to the model - * @param seqSize - * array containing the size of the sequence with the axes order defined - * @param seqSizeAxes - * axes order of the size array - * @return the array with the patch of interest - */ - public int[] getOptimalTileSizeNoTiling(int[] seqSize, String seqSizeAxes) { - return getOptimalTileSizeForTiling(seqSize, seqSizeAxes, true); - } - - /** - * Gets the patch size as an int[] from a String introduced by the user. - * The String has the form of tile_dimension1,tile_dimension2,tile_dimension3,... - * REgards that batch size is ignored by the user, thus it is always set to one - * @param patchStr - * the patch size as a String introduced by the user - * @return the patch int array - */ - public int[] getTileSizeFromStr(String patchStr) { - String[] axesArr = axes.toUpperCase().split(""); - int[] patchArr = new int[axesArr.length]; - String[] patchstrArr = patchStr.split(","); - int count = 0; - for (int i = 0; i < axesArr.length; i ++) { - if (axesArr[i].equals("B")) { - patchArr[i] = 1; - } else { - patchArr[i] = patchstrArr[count].trim().equals("auto") ? -1 :Integer.parseInt(patchstrArr[count].trim()); - count ++; - } - } - return patchArr; - } - - /** - * Validates if a given patch array fulfills the conditions specified in the yaml file. - * To check whether the tile size works also for a given image, - * please use {@link #setTileSizeForTensorAndImageSize(int[], int[])} - * @param tileSize - * the patch array size to validate, it has to be in the axes order of the tensor - * @throws Exception if the patch does not comply with the constraints specified - */ - public void validate(int[] tileSize) throws Exception { - // VAlidate that the minimum size and step constraints are fulfilled - validateStepMin(tileSize); - } - - /** - * Validates if a given patch array fulfills the conditions specified in the yaml file. - * To check whether the tile size works also for a given image, - * please use {@link #setTileSizeForTensorAndImageSize(int[], int[])} - * @param tileSize - * the patch array size to validate, it has to be in the axes order of the tensor - * @param tileAxesOrder - * the axes order of the 'tileSize' argument - * @throws Exception if the patch does not comply with the constraints specified - */ - public void validate(int[] tileSize, String tileAxesOrder) throws Exception { - tileSize = PatchGridCalculator.arrayToWantedAxesOrderAddOnes(tileSize, tileAxesOrder, this.axes); - // VAlidate that the minimum size and step constraints are fulfilled - validateStepMin(tileSize); - } - - /** - * Sets the tile size to process the given tensor regarding that the tensor - * has the dimensions specified by the second argument. - * If also validates if the tile size selected fufils the requirements - * specified in the bioimage.io rdf.yaml file. - * Both arguments must follow the same axis order of this tensor. - * If everything is correct, sets {@link #processingTile} to the first argument. - * @param tileSize - * the size of the tile/patch in which the main tensor is going to be divided. - * It should follow the tensor axes order as defined in the Bioimage.io rdf.yaml file - * @param tensorImageSize - * size of the image that is used for the tensor. - * It should follow the tensor axes order as defined in the Bioimage.io rdf.yaml file - * @throws Exception if the patch size is not able to - * fulfill the requirements of the tensor - */ - public void setTileSizeForTensorAndImageSize(int[] tileSize, int[] tensorImageSize) throws Exception { - if (tileSize.length != tensorImageSize.length) - throw new Exception("tileSize and tensorImageSize need to have the same number of dimensions."); - // If tiling is not allowed, the patch array needs to be equal to the - // optimal patch - if (!tiling) { - validateNoTiling(tileSize, tensorImageSize); - } - // VAlidate that the minimum size and step constraints are fulfilled - validateStepMin(tileSize); - // Finally validate that the sequence size complies with the patch size selected - validateTileSizeVsImage(tileSize, tensorImageSize); - this.processingTile = tileSize; - } - - /** - * Sets the tile size to process the given tensor regarding that the tensor - * has the dimensions specified by the second argument. - * If also validates if the tile size selected fufils the requirements - * specified in the bioimage.io rdf.yaml file. - * Both arguments must follow the same axis order of this tensor. - * If everything is correct, sets {@link #processingTile} to the first argument. - * @param tileSize - * the size of the tile/patch in which the main tensor is going to be divided - * @param tileAxesOrder - * axes order of the tile provided - * @param tensorImageSize - * size of the image that is used for the tensor. - * @param imageAxesOrder - * the axes order of the image size provided - * @throws Exception if the patch size is not able to - * fulfill the requirements of the tensor - */ - public void setTileSizeForTensorAndImageSize(int[] tileSize, String tileAxesOrder, - int[] tensorImageSize, String imageAxesOrder) throws Exception { - if (tileSize.length != tileAxesOrder.length()) - throw new Exception("tileSize and tileAxesOrder should have the same length."); - if (tensorImageSize.length != imageAxesOrder.length()) - throw new Exception("tensorImageSize and imageAxesOrder should have the same length."); - tileSize = PatchGridCalculator.arrayToWantedAxesOrderAddOnes(tileSize, tileAxesOrder, this.axes); - tensorImageSize = PatchGridCalculator.arrayToWantedAxesOrderAddOnes(tensorImageSize, imageAxesOrder, this.axes); - // If tiling is not allowed, the patch array needs to be equal to the - // optimal patch - if (!tiling) { - validateNoTiling(tileSize, tensorImageSize); - } - // VAlidate that the minimum size and step constraints are fulfilled - validateStepMin(tileSize); - // Finally validate that the sequence size complies with the patch size selected - validateTileSizeVsImage(tileSize, tensorImageSize); - this.processingTile = tileSize; - } - - - /** - * VAlidate that the wanted tile size, is compatible with the image size. - * The patch cannot be 3 times bigger than the image size because mirroring - * would not work, cannot be smaller than total halo * 2, and patching cannot - * exist along the channels dimension - * @param tileSize - * the proposed tile/patch size - * @param seqSize - * the sequence size - * @throws Exception if any of the constraints is not fulfilled - */ - private void validateTileSizeVsImage(int[] tileSize, int[] seqSize) throws Exception { - validateTileVsImageSize(tileSize, seqSize); - validateTileVsHalo(tileSize); - validateTileVsImageChannel(tileSize, seqSize); - } - - /** - * VAlidate that the tile/patch selected, is compatible with the image size. - * The tile/patch cannot be 3 times bigger than the image size because mirroring - * would not work. - * @param tileSize - * the proposed tile/patch size - * @param seqSize - * the sequence size - * @throws Exception if any of the constraints is not fulfilled - */ - private void validateTileVsImageSize(int[] tileSize, int[] seqSize) throws Exception { - boolean tooBig = IntStream.range(0, tileSize.length).anyMatch(i -> tileSize[i] > seqSize[i] * 3); - if (tooBig) { - int[] maxPatch = new int[seqSize.length]; - IntStream.range(0, tileSize.length).forEach(i -> maxPatch[i] = seqSize[i] * 3); - throw new Exception("Error in the axes size selected.\n" - + "The axes size introduced in any of the dimensions cannot\n" - + "be bigger than 3 times the Sequence size.\n" - + "The Sequence selected for tensor '" + name + "' has\n" - + "the following dimensions: " + getDisplayableSizesString(seqSize) - + " for axes " + getDisplayableAxesOrder() + ".\n" - + "With those dimensions the biggest axes size \n" - + "for each dimension should be: " + getDisplayableSizesString(maxPatch) + ".\n" - + "However, the axes size introduced is: " + getDisplayableSizesString(tileSize) + "."); - } - } - - /** - * VAlidate that the tile/patch selected, is compatible with the image size. - * The patch cannot be smaller than total halo * 2. - * @param tile - * the proposed patch size - * @param seqSize - * the sequence size - * @throws Exception if any of the constraints is not fulfilled - */ - private void validateTileVsHalo(int[] tile) throws Exception { - boolean tooSmall = IntStream.range(0, tile.length).anyMatch(i -> tile[i] <= halo[i] * 2); - if (tooSmall) { - int[] minPatch = new int[halo.length]; - for (int i = 0; i < halo.length; i ++) {minPatch[i] = (int) (halo[i] * 2);} - throw new Exception("Error in the axes size selected.\n" - + "The axes size introduced in any of the dimensions cannot\n" - + "be smaller than 2 times the total halo size.\n" - + "Regarding the total halo (max(offset + halo)) of" - + "tensor '" + name + "' the minimum size for each \n" - + "dimension should be: " + getDisplayableSizesString(minPatch) + ".\n" - + "However, the axes size introduced is: " + getDisplayableSizesString(tile) - + " for axes " + getDisplayableAxesOrder() + "."); - } - } - - /** - * VAlidate that the patch selected, is compatible with the image size. - * The patch cannot be different than the image size along the channel dimension - * @param tile - * the proposed patch size - * @param seqSize - * the sequence size - * @throws Exception if any of the constraints is not fulfilled - */ - private void validateTileVsImageChannel(int[] tile, int[] seqSize) throws Exception { - int channelInd = axes.toLowerCase().indexOf("c"); - if (channelInd != -1 && tile[channelInd] != seqSize[channelInd]) { - throw new Exception("Error in the axes size selected.\n" - + "DeepIcy does not allow tiling along the channels dimension.\n" - + "The axes size introduced for axis 'C' should be equal to\n" - + "the number of channels in the image.\n" - + "For input tensor '" + name + "', the sequence selected\n" - + "has '" + seqSize[channelInd] + "' channels whereas the\n" - + "axes size for 'C' is '" + tile[channelInd] + "'."); - } - } - - /** - * Validate that the patch size is a product of min_size + step * n where n can be any integer >= 0 - * @param tileSize - * the size of the tile/patch introduced by the user - * @throws Exception if the patch does not fulfill the min and step conditions - */ - private void validateStepMin(int[] tileSize) throws Exception { - boolean badSize = IntStream.range(0, tileSize.length) - .anyMatch(i -> - shape.getTileStep()[i] != 0 ? - (tileSize[i] - shape.getTileMinimumSize()[i]) % shape.getTileStep()[i] != 0 - : tileSize[i] != shape.getTileMinimumSize()[i] - ); - if (badSize) { - throw new Exception("Error in the axes size selected.\n" - + "Tensor " + getName() + " with the following requirements:\n" - + getDisplayableStepMinConstraints() + " are not compatible with\n" - + "the introduced tile size (" + getDisplayableSizesString(tileSize) + ").\n" - + "Regard that when step = 0, the tile size has to be equal\n" - + "to the minimum size"); - } - } - - /** - * Validate that the size of the patch is correct in the case that the tensor - * does not allow tiling - * @param tile - * size of the patch following the axes order - * @param seqSize - * size of the image following the tensor axes order - * @throws Exception if there is any issue with the validation - */ - private void validateNoTiling(int[] tile, int[] seqSize) throws Exception { - int[] optimalPatch = getOptimalTileSize(seqSize, axes); - boolean patchEqualsOptimal = true; - for (int i = 0; i < tile.length; i ++) { - if (optimalPatch[i] != tile[i]) { - patchEqualsOptimal = false; - break; - } - } - boolean seqBiggerThanPatch = true; - for (int i = 0; i < tile.length; i ++) { - if (seqSize[i] > tile[i]) { - seqBiggerThanPatch = false; - break; - } - } - if (!patchEqualsOptimal && !seqBiggerThanPatch){ - throw new Exception(" Error in the axes size selected.\n" - + "Tensor " + getName() + " does not allow tiling and due to the tensor specs\n" - + "and image size (" + Arrays.toString(getDisplayableSizes(seqSize)) + ") the tile size can\n" - + "only be (" + getDisplayableSizes(optimalPatch) + ") in order to process the\n" - + "whole image at once. These dimensions do not coincide with the ones\n" - + "introduced (" + Arrays.toString(getDisplayableSizes(tile)) + ")."); - } - if (seqBiggerThanPatch){ - throw new Exception("Error in the tiling size selected. " - + "Tensor " + getName() + " does not allow tiling and the " - + "image size (" + Arrays.toString(getDisplayableSizes(seqSize)) + ") is bigger than the" - + " tile size (" + Arrays.toString(getDisplayableSizes(tile)) + ") selected. " - + "With this parameters it would be impossible to process the whole" - + "image without tiling."); - } - this.processingTile = getOptimalTileSizeNoTiling(seqSize, axes); - } - - /** - * Sets the total halo for the inputs defined by the outputs. - * REgard that the input halo follows the tensor axes order - * @param totalHalo - * total halo, amount of pixels that have to be removed from the sides of each dim - */ - public void setTotalHalo(float[] totalHalo){ - this.halo = new float[axes.length()]; - String totalHaloAxesOrder = "XYCZB".toLowerCase(); - String[] axesArr = axes.toLowerCase().split(""); - for (int i = 0; i < axesArr.length; i ++) { - int ind = totalHaloAxesOrder.indexOf(axesArr[i]); - this.halo[i] = totalHalo[ind]; - } - } - - /** - * Return a String containing the dimensions of the optimal - * tile/patch for the selected image - * @param imSize - * size of the image. The order is Width, Height, Channel, Slices, time - * @param axesOrder - * the axes order of the imSize parameter - * @return the String containing a tile/patch in the format 256,256,3 - */ - public String getDisplayableOptimalTile(int[] imSize, String axesOrder) { - // Remove the B axes from the optimal patch size and get the String representation - String patchStr = getDisplayableSizesString(getOptimalTileSize(imSize, axesOrder)); - return patchStr; - } - - /** - * Validates the selected tile/patch array fulfills the conditions specified in the yaml file - * with respect to the tensor size before introducing it into the model - * If it is valid, it sets the value as the {@link #processingTile} - * @param seqSize - * size of the tensor before inference (after pre-processing) - * @param axesOrder - * axes order of the tensor - * @throws Exception if the seqsize is not compatible with the the tensor constraints - */ - public void validateTensorSize(int[] seqSize, String axesOrder) throws Exception { - // Convert the Icy sequence array dims into the tensor axes order - seqSize = PatchGridCalculator.arrayToWantedAxesOrderAddOnes(seqSize, axesOrder, axes); - // If tiling is not allowed, the patch array needs to be equal to the - // optimal patch - if (!tiling) { - validateNoTiling(processingTile, seqSize); - } - // VAlidate that the minimum size and step constraints are fulfilled - validateStepMin(processingTile); - // Finally validate that the sequence size complies with the patch size selected - validateTileSizeVsImage(processingTile, seqSize); - } - - - /** - * REturn the tile/patch size for this tensor introduced by the user for processing - * @return the tile size to process the tensor - */ - public int[] getTileSize() { - return this.processingTile; - } - - /** - * Return whether the tensor represents an image or not. - * Currently only the types {@link #IMAGE} and {@link #LIST} - * are supported. - * In order for the object to be a list, the shape has to contain either only 1 dimension ("c"), - * contain 2, one of them being the batch_size ("bc"), or contain the letter "i", ("bic"). - * "I" comes from instance - * An image is everythin else. - * - * @return The type of tensor. As of now it can hold "image" or "list" values. - */ - public String getType() - { - return type; - } - - /** - * @return The standard preprocessing applied to this tensor. Used when this is an input tensor. - */ - public List getPreprocessing() - { - return preprocessing; - } - - /** - * @return The standard postprocessing applied to this tensor. Used when this is an output tensor. - */ - public List getPostprocessing() - { - return postprocessing; - } - - /** - * Sets whether tiling is allowed or not - * @param tiling - * whether tiling is allowed or not - */ - public void setTiling(boolean tiling) { - this.tiling = tiling; - } - - /** - * Gets whether tiling is allowed or not - * @return whether tiling is allowed or not - */ - public boolean getTiling() { - return tiling; - } - - /** - * @return The data type accepted by the tensor. It can be float32, float64, (u)int8, (u)int16, (u)int32, (u)int64. However, when passing the tensor to the - * model it must be of type float. - */ - public String getDataType() - { - return dataType; - } - - /** - * @return The halo size on each axis used to crop the output tile. - * It is the padding used on each axis at both begin and end on each axis of the image. - */ - public float[] getHalo() - { - return halo; - } - - /** - * @return The shape specification of the tensor. It holds either the recommended tensor size or the elements to establish the correct size. - */ - public ShapeSpec getShape() - { - return shape; - } - - /** - * @return the description of the tensor. - */ - public String getDescription() - { - return description; - } - - /** - * @return True if this instance describes an input tensor. False if it's an output tensor. - */ - public boolean isInput() - { - return input; - } - - /** - * @return The name of this tensor. It is usually the name found in the saved model. - */ - public String getName() - { - return name; - } - - /** - * A string containing the axis order. Can contain characters "bitczyx". It has no specific size, but it must not be more than 7 and must contain each - * character at most once. - * - * @return axis order. - */ - public String getAxesOrder() - { - return axes; - } - - @Override - public String toString() - { - return "TensorSpec {input=" + input + ", name=" + name + ", axes=" + axes + ", dataType=" + dataType + ", halo=" - + halo + ", shape=" + shape + ", type=" + type + ", preprocessing=" + preprocessing - + ", postprocessing=" + postprocessing + "}"; - } - - /** - * Return whether the tensor represents an image or not. - * Currently only the types {@link #IMAGE} and {@link #LIST} - * are supported. - * In order for the object to be a list, the shape has to contain either only 1 dimension ("c"), - * contain 2, one of them being the batch_size ("bc"), or contain the letter "i", ("bic"). - * "I" comes from instance - * An image is everythin else. - * - * @return whether the tensor represents an image or not - */ - public boolean isImage() { - return this.type.equals(IMAGE); - } -}