Commit 70aac2ff authored by Chris Coughlin's avatar Chris Coughlin

Added an ROI bundle for easier model serialization and distribution

parent 21df458e
/*
* com.emphysic.myriad.core.data.roi.ROIBundle
*
* Copyright (c) 2017 Emphysic LLC.
*
* 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.
*/
package com.emphysic.myriad.core.data.roi;
import com.emphysic.myriad.core.data.io.Dataset;
import com.emphysic.myriad.core.data.ops.DatasetOperation;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
/**
* ROIBundle - bundles an ROIFinder, a preprocessing DatasetOperation, and assorted metadata into a single "black box"
* Region Of Interest finder for easier serialization and distribution of trained models.
* Created by ccoughlin on 4/16/17.
*/
@Slf4j
public class ROIBundle implements ROIFinder {
private static final long serialVersionUID = 1L; // try never to change - indicates backwards compatibility is broken
private static final int VERSION = 1; // current implementation version
/**
* The actual Region Of Interest (ROI) finder
*/
private ROIFinder finder;
/**
* A preprocessing operation if required by the ROIFinder
*/
private DatasetOperation preproc;
/**
* Metadata for this bundle
*/
private Map<String, String> metadata;
public ROIBundle(ROIFinder roiFinder, DatasetOperation preprocessor) {
finder = roiFinder;
preproc = preprocessor;
metadata = new HashMap<>();
}
public ROIBundle(ROIFinder roiFinder) {
this(roiFinder, null);
}
public ROIBundle() {
this(null, null);
}
public String getMetadataEntry(String key) {
return metadata.getOrDefault(key, null);
}
public void setMetadataEntry(String key, String val) {
metadata.put(key, val);
}
/**
* Examine and reports on whether the data appears to contain a Region Of Interest (ROI).
* Assumes any required preprocessing has already been performed.
*
* @param data raw data to examine (WILL NOT run preprocessor if defined!)
* @return true if the data contains a Region Of Interest, false if it does not or no ROIFinder has been set.
*/
@Override
public boolean isROI(double[] data) {
return finder != null && finder.isROI(data);
}
/**
* Examine and reports on whether the data appears to contain a Region Of Interest (ROI).
* Runs the preprocessing operation (if any) prior to examining the data.
* @param dataset data to examine (WILL run preprocessor if defined!)
* @return true if the data contains a Region Of Interest, false if it does not or no ROIFinder has been set.
*/
@Override
public boolean isROI(Dataset dataset) {
if (preproc != null) {
return finder.isROI(preproc.run(dataset));
} else {
return finder.isROI(dataset.getData());
}
}
@Override
public long getSerializationVersion() {
return serialVersionUID;
}
@Override
public int getVersion() {
return VERSION;
}
@Override
public Map<String, Object> getObjectMap() {
Map<String, Object> map = ROIFinder.super.getObjectMap();
if (finder != null) {
map.put("roiclz", finder.getClass());
map.put("roimap", finder.getObjectMap());
}
if (preproc != null) {
map.put("preprocclz", preproc.getClass());
map.put("preprocmap", preproc.getObjectMap());
}
map.put("metadatamap", metadata);
return map;
}
@Override
public void initCurrentVersion(Map<String, Object> objectMap) {
if (objectMap.containsKey("roiclz")) {
Class<? extends ROIFinder> roiclz = (Class<? extends ROIFinder>) objectMap.get("roiclz");
try {
finder = roiclz.getConstructor().newInstance();
if (objectMap.containsKey("roimap")) {
finder.init((Map<String, Object>) objectMap.get("roimap"));
}
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
log.error("Unable to deserialize ", roiclz, ": ", e, " using no-op");
}
}
if (objectMap.containsKey("preprocclz")) {
Class<? extends DatasetOperation> preprocclz = (Class<? extends DatasetOperation>) objectMap.get("preprocclz");
try {
preproc = preprocclz.getConstructor().newInstance();
if (objectMap.containsKey("preprocmap")) {
preproc.init((Map<String, Object>) objectMap.get("preprocmap"));
}
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
log.error("Unable to deserialize ", preprocclz, ": ", e, " using no-op");
}
}
metadata.putAll((Map<? extends String, ? extends String>) objectMap.get("metadatamap"));
}
public ROIFinder getFinder() {
return finder;
}
public void setFinder(ROIFinder finder) {
this.finder = finder;
}
public DatasetOperation getPreproc() {
return preproc;
}
public void setPreproc(DatasetOperation preproc) {
this.preproc = preproc;
}
public Map<String, String> getMetadata() {
return metadata;
}
public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}
}
/*
* com.emphysic.myriad.core.data.roi.ROIBundleTest
*
* Copyright (c) 2017 Emphysic LLC.
*
* 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.
*/
package com.emphysic.myriad.core.data.roi;
import com.emphysic.myriad.core.data.io.Dataset;
import com.emphysic.myriad.core.data.ops.*;
import com.emphysic.myriad.core.ml.CrossValidation;
import com.emphysic.myriad.core.ml.MLDataCompiler;
import com.emphysic.myriad.core.ml.MonteCarloCV;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.net.URL;
import java.util.Date;
import java.util.Map;
import java.util.Random;
import static org.junit.Assert.assertEquals;
/**
* Tests the ROIBundle
* Created by ccoughlin on 4/16/17.
*/
public class ROIBundleTest {
static DatasetOperation[] ops = {
new AbsoluteValueOperation(),
new BoxBlur(3),
new BinarizeOperation(0.5),
new CannyOperation(new BoxBlur(5), new PrewittOperation()),
new ConvolutionOperation(),
new DifferenceOfGaussiansOperation(3, 4),
new GaussianBlur(2),
new NormalizeSignalOperation(),
new PrewittOperation(),
new SobelOperation(),
};
static DatasetOperation preproc;
static MLROIFinder[] finders = {
new AdaptiveSGDROIFinder(),
new PassiveAggressiveROIFinder(),
new SGDROIFinder()
};
static MLROIFinder finder;
static ROIBundle bundle;
static int opidx;
static int fidx;
static MLDataCompiler mldc;
static MonteCarloCV mcv;
@BeforeClass
public static void setUpClass() throws Exception {
Random random = new Random();
opidx = random.nextInt(ops.length);
fidx = random.nextInt(finders.length);
finder = finders[fidx];
preproc = ops[opidx];
URL dataUrl = Thread.currentThread().getContextClassLoader().getResource("data/ml/generated");
File generatedDataFolder = new File(dataUrl.getPath());
File posFolder = new File(generatedDataFolder, "signal");
File negFolder = new File(generatedDataFolder, "noise");
mldc = new MLDataCompiler(posFolder, negFolder);
mcv = new MonteCarloCV(mldc.readData(preproc));
Map.Entry<MLROIFinder, Double> model = mcv.findBestModel(1, new MLROIFinder[]{finder});
finder = model.getKey();
bundle = new ROIBundle(finder, preproc);
}
@Test
public void testGetSetMetadataEntry() throws Exception {
String key = "date";
String val = new Date().toString();
bundle.setMetadataEntry(key, val);
assertEquals(bundle.getMetadataEntry(key), val);
}
@Test
public void isROIArray() throws Exception {
CrossValidation.Data data = mldc.readData(preproc);
for (int sample=0;sample<data.numSamples();sample++) {
double[] arr = data.samples[sample];
// ROIBundle assumes arrays have already been preprocessed
assertEquals(finder.isROI(arr), bundle.isROI(arr));
}
}
@Test
public void isROIDataset() throws Exception {
CrossValidation.Data data = mldc.readData();
for (int sample=0;sample<data.numSamples();sample++) {
double[] arr = data.samples[sample];
Dataset raw = new Dataset(arr, 15, 15);
Dataset preprocessed = preproc.run(raw);
// ROIBundle assumes Datasets need to be preprocessed
assertEquals(finder.isROI(preprocessed), bundle.isROI(raw));
}
}
@Test
public void serialize() throws Exception {
File out = File.createTempFile("tmpbundle", "out");
String key = "date";
String val = new Date().toString();
bundle.setMetadataEntry(key, val);
bundle.save(out);
ROIBundle reread = new ROIBundle();
reread.load(out);
assertEquals(bundle.getMetadata().keySet(), reread.getMetadata().keySet());
assertEquals(bundle.getMetadataEntry(key), reread.getMetadataEntry(key));
CrossValidation.Data data = mldc.readData(preproc);
for (int sample=0;sample<data.numSamples();sample++) {
double[] arr = data.samples[sample];
assertEquals(bundle.isROI(arr), reread.isROI(arr));
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment