/**
*
*/
package photoSpreadUtilities;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import photoSpread.PhotoSpreadException;
import photoSpread.PhotoSpreadException.IllegalArgumentException;
import photoSpreadObjects.PhotoSpreadImage;
import photoSpreadObjects.PhotoSpreadObject;
import photoSpreadTable.PhotoSpreadCell;
/**
* @author paepcke
*
* Special purpose class for Eric Abelson.
* Given two cells, A and B. Assume A has many
* Recon Camera photos with metadata. Cell B
* contains a subset of those photos. This class
* goes through each photo b in B and adds all
* photos from A that were taken in the same
* 15-shot series as photo b.
*
* This grouping is determined by comparing three
* pieces of metadata:
* - The serial number of the camera
* - The group ID as extracted from the filename
* - The shot date/time
*
*
*
*/
public class ReconCameraGrouper {
private static long _interShotTime = 60000; // milliseconds
private static int _maxPhotosInReconyxGroup = 15;
// Column names for relevant Reconyx records:
private static String _filenameKey =
Const.permanentMetadataAttributeNames[Const.FILENAME_METADATA_ATTR_NAME];
private static String _serialNumberKey = "sn"; // ***from "SN"
private static String _dateKey = "imageDate"; // ***from "Image Date"
private static String _timeKey = "imageTime"; // ***from "Image Time"
// Support metadata we add to each photo in the
// full-set cell:
private static String _shotGroupNumKey = "@shotGroupNum";
private static String _shotNumKey = "@shotNum";
private static String _dateSecsKey = "@dateSecs";
private static ArrayList<String> _uniquifyingKeyset =
new ArrayList<String>();
private static String _dateTimePattern = "mm/dd/yyy kk:mm:ss";
MetadataIndexer _metadataIndexer;
PhotoSpreadCell _fullSetCell = null;
PhotoSpreadCell _subsetCell = null;
PhotoSpreadCell _resultSetCell = null;
/****************************************************
* Constructors
* @throws IllegalArgumentException
*****************************************************/
public ReconCameraGrouper (
PhotoSpreadCell fullSetCell,
PhotoSpreadCell subsetCell,
PhotoSpreadCell resultSetCell) throws IllegalArgumentException {
Iterator<PhotoSpreadObject> fullSetIterator = null;
PhotoSpreadObject photo = null;
_fullSetCell = fullSetCell;
_subsetCell = subsetCell;
_resultSetCell = resultSetCell;
_uniquifyingKeyset.add(_shotGroupNumKey);
_uniquifyingKeyset.add(_dateSecsKey);
_uniquifyingKeyset.add(_serialNumberKey);
// Ensure that the fullSetCell has
// an indexer for the metadata fields
// that we care about (the ones that
// together make a Reconyx photo unique):
ensureAllKeysIndexed(_fullSetCell);
// Same for subset cell:
ensureAllKeysIndexed(_subsetCell);
// Now check whether at least one of the fullSet
// cell's objects have one of our special metadata
// keys (shot group number or dateSecs). If so, we
// assume that all objects in the fullset cell
// have those Reconyx-specific metadata fields set.
// If not, we add those fields to the cell's
// objects, which will also update the index to
// include the new fields.
// We only want to do this once,
// of course, therefore the prior check:
fullSetIterator = _fullSetCell.getObjectsIterator();
if (!fullSetIterator.hasNext())
// Full-set cell is empty. Nothing to do:
return;
photo = fullSetIterator.next();
if (photo.getMetaData(_shotGroupNumKey) == Const.NULL_VALUE_STRING)
addSignaturesToCellObjs(fullSetCell);
return;
}
/****************************************************
* Inner class PhotoSignature
*****************************************************/
public class PhotoSignature {
private String _cameraSN = "";
private int _shotGroupNum = -1;
private int _shotNum = -1;
private long _dateSecs = -1;
/****************************************************
* Constructor Inner class PhotoSignature
*****************************************************/
public PhotoSignature (
String cameraSN,
int shotGroupNum,
int shotNum,
long dateSecs) {
_cameraSN = cameraSN;
_shotGroupNum = shotGroupNum;
_shotNum = shotNum;
_dateSecs = dateSecs;
}
/****************************************************
* Getters/Setters Inner class PhotoSignature
*****************************************************/
void setCameraSN(String cameraSN) {
this._cameraSN = cameraSN;
}
String getCameraSN() {
return _cameraSN;
}
void setShotGroup(int _shotGroup) {
this._shotGroupNum = _shotGroup;
}
int getShotGroup() {
return _shotGroupNum;
}
void setShotNum(int _shotNum) {
this._shotNum = _shotNum;
}
int getShotNum() {
return _shotNum;
}
void setDateSecs(long _dateSecs) {
this._dateSecs = _dateSecs;
}
long getDateSecs() {
return _dateSecs;
}
/****************************************************
* Methods Inner class PhotoSignature
*****************************************************/
} // end class PhotoSignature
/****************************************************
* Methods
* @throws IllegalArgumentException
* @throws IllegalArgumentException
*****************************************************/
public TreeSetRandomSubsetIterable<PhotoSpreadObject> expandToGroups ()
throws IllegalArgumentException {
return expandToGroups(_fullSetCell, _subsetCell, _resultSetCell);
}
public TreeSetRandomSubsetIterable<PhotoSpreadObject> expandToGroups (
PhotoSpreadCell fullSetCell,
PhotoSpreadCell subsetCell,
PhotoSpreadCell resultSetCell)
throws IllegalArgumentException {
TreeSetRandomSubsetIterable<PhotoSpreadObject> res =
new TreeSetRandomSubsetIterable<PhotoSpreadObject>();
res.setIndexer(new PhotoSpreadObjIndexerFinder());
HashSet<PhotoSpreadObject> sameGroupObjects;
PhotoSpreadObject photoGroup[] =
new PhotoSpreadObject[_maxPhotosInReconyxGroup];
PhotoSignature subsetPhotoSig;
long fullPhotoTime;
int fullPhotoShotNum;
// Initialize all photoGroup[] entries to null:
for (int i=0; i<_maxPhotosInReconyxGroup; i++)
photoGroup[i] = null;
for (PhotoSpreadObject photo : subsetCell.getObjects()) {
// Get the photo' signature:
subsetPhotoSig = makePhotoSignature((PhotoSpreadImage) photo);
// Find in the full set cell all the objects
// with the same shot group number:
sameGroupObjects = fullSetCell.find(
_shotGroupNumKey,
((Integer) subsetPhotoSig.getShotGroup()).toString());
// For each photo in the same shot group, get its
// shot time in seconds and its shot number key. Then
// see whether camera id and shot time agree to the
// specified tolerance (of _interShotTime seconds).
// From the above search we already know that the group
// numbers agree:
for (PhotoSpreadObject candidate : sameGroupObjects) {
try {
fullPhotoTime = Long.parseLong(candidate.getMetaData(_dateSecsKey));
} catch (NumberFormatException e) {
throw new RuntimeException("No proper time/date available " +
"in seconds format from photo:\n'" +
photo +
"'");
}
try {
fullPhotoShotNum = Integer.parseInt(candidate.getMetaData(_shotNumKey));
} catch (NumberFormatException e) {
throw new RuntimeException("No proper shot number available " +
"from photo:\n'" +
photo +
"'");
}
if ((candidate.getMetaData(_serialNumberKey).equals(
subsetPhotoSig.getCameraSN())) &&
(timeDiff(fullPhotoTime, subsetPhotoSig.getDateSecs()) <=
_interShotTime))
// The 'candidate' photo from the full set does
// belong to the same group as the current 'photo'
// from the subset cell. Put the candidate into
// our array of photos of one group:
photoGroup[fullPhotoShotNum - 1] = candidate;
} // end collect all photos of one group into an array
// Now we have one Reconyx shot group of photos, sorted,
// in photoGroup[]. We add that whole group to our result
// set in order:
for (int i=0; i<_maxPhotosInReconyxGroup; i++) {
if (photoGroup[i] == null)
continue;
res.add(photoGroup[i]);
// Clear the entry in the array for the next
// object whose group we'll find (in the outer loop):
photoGroup[i] = null;
} // end transfer one shot group to final result set
} // look at one photo in the subset cell
_resultSetCell.addObjects(res);
_resultSetCell.getTableModel().fireTableCellUpdated(
resultSetCell.getRow(), resultSetCell.getColumn());
return res;
}
public PhotoSignature makePhotoSignature (PhotoSpreadImage photo)
throws PhotoSpreadException.IllegalArgumentException {
// Camera serial number:
String serialNumCamera = photo.getMetaData(_serialNumberKey);
if (serialNumCamera.equals(Const.NULL_VALUE_STRING))
throw new PhotoSpreadException.IllegalArgumentException(
"Photo has no camera serial number: " +
photo.getFilePath());
// Shot group ID: (The top n-2 decimal digits of the numeric
// part of the filename. And shot ID: the last two digits.
// Ex.: M0000103: group ID: 1; shot id: 3
Integer fullShotID = extractShotNumberFromFilename(photo);
if (fullShotID == null)
throw new PhotoSpreadException.IllegalArgumentException(
"Photo has no proper shot number: " +
photo.getFilePath());
int groupID = fullShotID/100;
int shotID = fullShotID % 100;
// Photo date and time:
String shotDate = photo.getMetaData(_dateKey);
if (shotDate.equals(Const.NULL_VALUE_STRING))
throw new PhotoSpreadException.IllegalArgumentException(
"Photo has no proper date field: " +
photo.getFilePath());
String shotTime = photo.getMetaData(_timeKey);
if (shotTime.equals(Const.NULL_VALUE_STRING))
throw new PhotoSpreadException.IllegalArgumentException(
"Photo has no proper time field: " +
photo.getFilePath());
// Wrap a Date instance around the data and the time;
Date thisPhotoDateTime;
try {
thisPhotoDateTime = makeDateTimeInstance(shotDate, shotTime);
} catch (ParseException e) {
throw new PhotoSpreadException.IllegalArgumentException(
"Cannot convert photo date or time into proper format: " +
photo.getFilePath());
}
return new PhotoSignature(
serialNumCamera,
groupID,
shotID,
thisPhotoDateTime.getTime());
}
@SuppressWarnings("unused")
private boolean areGroupSiblings(PhotoSignature sig1, PhotoSignature sig2) {
if (sig1.getCameraSN() != sig2.getCameraSN())
return false;
if (sig1.getShotGroup() != sig2.getShotGroup())
return false;
if (timeDiff(sig1.getDateSecs(), sig2.getDateSecs()) > _interShotTime)
return false;
return true;
}
private Integer extractShotNumberFromFilename (PhotoSpreadImage photo) {
String fileName = photo.getMetaData(_filenameKey);
if (fileName.equals(Const.NULL_VALUE_STRING))
return null;
Integer res = 0;
File photoFile = new File(fileName);
// Grab 7 consecutive digits exactly:
Pattern pattern = Pattern.compile("(\\d\\d\\d\\d\\d\\d\\d)");
// Only want the file name itself, not whole path:
String baseName = photoFile.getName();
Matcher matcher = pattern.matcher(baseName);
if (!matcher.find())
return null;
try {
res = Integer.parseInt(matcher.group());
} catch (IllegalStateException e) {
return null;
} catch (NumberFormatException e) {
return null;
}
return res;
}
private Date makeDateTimeInstance (String dataStr, String timeStr)
throws ParseException {
SimpleDateFormat dateTimeParser = new SimpleDateFormat(_dateTimePattern);
Date dateTime;
dateTime = dateTimeParser.parse(dataStr + " " + timeStr);
return dateTime;
}
@SuppressWarnings("unused")
private long timeDiff(Date date1, Date date2) {
long diffMsecs = Math.abs(date1.getTime() - date2.getTime());
long diffSecs = (long) Math.ceil(diffMsecs / 1000.0);
return diffSecs;
}
private long timeDiff(long timeInSecs1, long timeInSecs2) {
return Math.abs(timeInSecs1 - timeInSecs2);
}
private void ensureAllKeysIndexed(PhotoSpreadCell cell) {
MetadataIndexer indexer = cell.getMetadataIndexer();
if (indexer == null) {
indexer = new MetadataIndexer(_uniquifyingKeyset);
cell.setMetadataIndexer(indexer);
}
else // ensure that the existing indexer indexes all of
// the special metadata items we need (group shot ID, etc.):
indexer.addMetadataKeysToIndex(_uniquifyingKeyset);
}
/**
* Given a cell with photos that are assumed to have the
* standard Reconyx metadata, ensure that all photos also
* have all the metadata fields that are derived from the
* standard Reconyx photos: the shot group number, the
* shot time in seconds since the beginning of time,
* and the shot number within the group.
* @param cell
* @throws IllegalArgumentException
*/
private void addSignaturesToCellObjs (PhotoSpreadCell cell)
throws IllegalArgumentException {
PhotoSignature sig = null;
for (PhotoSpreadObject photo : cell.getObjects()) {
sig = makePhotoSignature((PhotoSpreadImage) photo);
photo.setMetaData(
_shotGroupNumKey,
((Integer) sig.getShotGroup()).toString());
photo.setMetaData(
_dateSecsKey,
((Long) sig.getDateSecs()).toString());
photo.setMetaData(
_shotNumKey,
((Integer) sig.getShotNum()).toString());
}
}
/****************************************************
* Main for Testing
*****************************************************/
/*
public static void main(final String[] args) {
ReconCameraGrouper test = new ReconCameraGrouper();
Date testDate1 = null;
Date testDate2 = null;
Date testDate3 = null;
// Integer shotNum;
try {
testDate1 = test.makeDateTimeInstance("5/11/2007", "18:58:58");
testDate2 = test.makeDateTimeInstance("5/11/2007", "18:58:59");
testDate3 = test.makeDateTimeInstance("5/11/2007", "21:58:59");
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("D1 diff D2: " + test.timeDiff(testDate1, testDate2) + "sec");
System.out.println("D2 diff D1: " + test.timeDiff(testDate2, testDate1) + "sec");
System.out.println("D1 diff D3: " + test.timeDiff(testDate1, testDate3) + "sec");
//shotNum = test.extractShotNumberFromFilename("C:/foo/M0001234.jpg");
//System.out.println("shotNum 00001234: " + shotNum);
//shotNum = test.extractShotNumberFromFilename("C:/foo/M1234.jpg");
//System.out.println("shotNum 00001234: " + shotNum);
}
*/
}