Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FURTHER DEVELPMENT of VIEW for everyone who stumbles upon this repository #51

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8b9462d
Update README.md
naixx Jun 1, 2024
25ea4ed
Aperture 1.7 is not supported, basically fast lenses can't switch to …
naixx Jun 1, 2024
7d1a5b4
Fix a typo
naixx Jun 1, 2024
da9a540
Modify sunrise algorithm a bit to make it more consistent (work in pr…
naixx Jun 1, 2024
9f9acff
Add additional logging
naixx Jun 1, 2024
42d721d
New way of calculating highlights
naixx Jun 4, 2024
d085508
Better infrastructure for development. Scripts for: faster retrieve f…
naixx Jun 5, 2024
8c383b1
Write camera data to a separate file for debugging offline
naixx Jun 5, 2024
6bf75ca
Fix highlights issue when ramping went up instead of down
naixx Jun 7, 2024
9e0df39
Smooth highlights protection based on 3 frames
naixx Jun 7, 2024
a5b4ec2
Refactor camera/api code to make it testable offline
naixx Jun 9, 2024
c522050
Better names for scripts
naixx Jun 9, 2024
fe9898f
Shutter should be changed first
naixx Jun 9, 2024
d95db09
Add fixed jpeg-lum to the repo, so that users don't need to update an…
naixx Jun 9, 2024
7824453
Update README.md
naixx Jun 9, 2024
2015234
If the image was overexposed, returning to normal stable values could…
naixx Jun 10, 2024
9d59d3e
Show estimated photo luminance on the main VIEW screen during timelapse
naixx Jun 10, 2024
c87090c
Add histogram and photo luminance value to "Exposure" and "Capture"
naixx Jun 10, 2024
5688a55
Fix issue with logfiles
naixx Jun 11, 2024
20c8a58
Add an option to speed up timelapse clips in video preview mode. Just…
naixx Jun 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ frontend/DEV/*
.DS_Store
*/.DS_Store
segfault.log/*
.idea
work
logs
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
This fork aims to enhance the stability and predictability of the VIEW intervalometer, refine its ramping algorithms, and eliminate bugs. I can only provide support for Sony cameras as I have tested it solely on the Sony A6300 and A6600.

Currently, I have a general understanding of how VIEW operates, the algorithms involved, and why it may sometimes produce extreme light or dark exposures. At present, it appears that the estimated ramping needs adjustment, and issues with the histogram and luminance calculations are causing inaccuracies. Highlight protection calculations may result in underexposure, so it's advisable to disable this feature. Further investigation is necessary.

Please note that the releases are provided 'AS IS.' During development, I inadvertently caused a soft brick on my VIEW and spent several anxious nights trying to recover it. The provided releases have been tested on my own device. Simply download the archive onto your SD card and install the update via the 'Firmware Update' menu. In case of any issues, consult the troubleshooting section in the official documentation to revert to previous versions. Feel free to reach out to discuss ideas, VIEW development, and future enhancements.
## Changes
### 1.8.52
* Implemented a new method for calculating the histogram. Past experiences highlighted many overexposed timelapses due to incorrect histogram calculations, leading to failures in actual highlights protection algorithms. Day and night luminance calculations are now more precise, based on the YUV colorspace formula. Adjust your luminance references accordingly for `day` and `night`.
* Revised the `sunrise ramping algorithm`. Previously, the algorithm sometimes hesitated to adjust to the increasing light levels in the mornings. It has been modified to ramp faster towards brighter conditions.
* Enhanced camera exposure values in balance mode (`S=A=I`). If previous change in one direction was of the same parameter, then the opposite direction change will be of the same parameter. Except iso in up direction, we want it to ramp as quickly as possible.
For instance, transitioning to darkness with the previous algorithm: `6s 5.6f`, `8s 5.6f`(s), `8s 4f`(a), `10s 4f`(s) -> transitioning light -> `10s 5.6f`(a) -> transitioning dark -> `12s 5.6f`(s). Notice that the shutter and aperture change sequentially, which can lead to a peculiar situation where you may end up with a longer shutter speed instead of a wider aperture at night.
The new algorithm will maintain the adjusted parameter sequence: `6s 5.6f`, `8s 5.6f`(s), `8s 4f`(a), `10s 4f`(s) -> transitioning light -> `8s 4f`(s) -> transitioning dark -> `10s 4f`(s). Notice that the `aperture remains unchanged` as the EV decreases and increases.
* In balance mode (`S=A=I`), ISO will consistently be the last parameter to change when transitioning to darker settings and the first parameter to adjust when transitioning to brighter settings.


## TODO:
* video advance in clips via knob
* histogram and luminance in capture and exposure screen
* new mobile app
* new cameras?
* make aperture changes not so quick as it is now, but scaled to the number of shutter changes

------------------------------------------
<img alt="Timelapse+ VIEW Intervalometer" src="https://static1.squarespace.com/static/5318bacfe4b03ba2018b9945/5318bdcce4b04f773bfbb207/585c56295016e19f2cbefd4a/1482446382681/284A7536.jpg?format=475w" width="475">

# Timelapse+ VIEW Intervalometer
### Timelapse+ VIEW Intervalometer

📷 Innovative solutions for time-lapse.

## Docs
### Docs

**[Official VIEW documentation](http://docs.view.tl/)**

Expand Down
2 changes: 1 addition & 1 deletion camera/camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ camera.setEv = function(ev, options, cb) {
if (!options) options = {};
var doSet = function(settings) {
var shutter = settings.details ? settings.details.shutter : settings.shutter;
var aperture = settings.details ? settings.details.aperture : settings.sperture;
var aperture = settings.details ? settings.details.aperture : settings.aperture;
var iso = settings.details ? settings.details.iso : settings.iso;

var apertureEnabled = false;
Expand Down
12 changes: 6 additions & 6 deletions camera/image/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ exports.writeXMP = function(fileName, exposureCompensation, description, name, l
</rdf:Description>\n\
</rdf:RDF>\n\
</x:xmpmeta>';

var gpsData = "";
if(lat != null && lat != null) {
if(lat < 0) {
Expand Down Expand Up @@ -215,7 +215,7 @@ exports.downsizeJpeg = function(jpeg, size, crop, callback) {

exports.downsizeJpegSharp = function(jpeg, size, crop, exposureCompensation, callback) {
return exports.downsizeJpeg(jpeg, size, crop, callback);



console.log("IMAGE: (sharp) Resizing photo...");
Expand Down Expand Up @@ -329,7 +329,7 @@ exports.histogramArray = function(jpegBuffer, callback) {
fs.writeFile(TMP_IMAGE_THUMB, new Buffer(jpegBuffer), function() {
luminance.read(TMP_IMAGE_THUMB, function(err, res) {
var histArray = null;
if(!err && res && res.histogram) histArray = res.histogram;
if(!err && res && res.histogram) histArray = res.histogram;
if (callback) callback(err, histArray);
});
/*pixelr.read(TMP_IMAGE_THUMB, "jpeg", function(err, data) {
Expand Down Expand Up @@ -358,10 +358,10 @@ exports.exposureValue = function(jpegBuffer, callback) {
var lum = 0;
if(!err && res && res.luminance) {
lum = res.luminance;
if(res.clipped && highlightProtection) {
/* if(res.clipped && highlightProtection) {
console.log("IMAGE: Compensating for clipped highlights: ", res.clipped * highlightProtection);
lum += res.clipped * highlightProtection;
}
}*/
}
if (callback) callback(err, lum, res.histogram);
});
Expand All @@ -384,4 +384,4 @@ exports.faceDetection = function(jpegBuffer, callback) {
}
});
});
}
}
170 changes: 36 additions & 134 deletions camera/ptpjs/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var fs = require('fs');
var path = require('path');
var async = require('async');
var test = require('./test');
var api_util = require('./api_util');

var api = new EventEmitter();

Expand Down Expand Up @@ -124,12 +125,12 @@ while (us < 1000000 * 60 * 10) {
util.inherits(CameraAPI, EventEmitter);


usb.on('attach', function(device) {
usb.on('attach', function(device) {
//console.log("device attached", device);
if(api.enabled) tryConnectDevice(device);
});

usb.on('detach', function(device) {
usb.on('detach', function(device) {
//console.log("DETACHED:", device);
var port = device.busNumber + ':' + device.deviceAddress;
var camIndex = null;
Expand All @@ -149,7 +150,7 @@ usb.on('detach', function(device) {
api.emit('disconnect', name); // had been connected
break;
}
}
}
});


Expand Down Expand Up @@ -323,7 +324,7 @@ function ensurePrimary() {
if(api.cameras.length > 0) {
if(primaryIndex == -1) {
primaryIndex = 0;
api.cameras[primaryIndex].primary = true;
api.cameras[primaryIndex].primary = true;
}
api.available = true;
api.model = api.cameras[primaryIndex].model;
Expand Down Expand Up @@ -377,7 +378,7 @@ api.cameraList = function(callback) {
api.switchPrimary = function(cameraObject, callback) {
//if(camera.lvOn) camera.lvOff();
if(cameraObject._port) {
console.log("switching primary camera to ", cameraObject.model);
console.log("switching primary camera to ", cameraObject.model);
var index = null;
for(var j = 0; j < api.cameras.length; j++) {
if(api.cameras[j].camera._port == cameraObject._port) {
Expand Down Expand Up @@ -506,71 +507,6 @@ api.lvZoom = function(zoom, callback) {
primaryCamera.camera.lvZoom(zoom, callback);
}

function listEvs(param, minEv, maxEv) { // returns a sorted list of EV's from a camera available list
var base = api.cameras[0].camera.exposure;
//console.log("API:", param, "base", base);
if(!base || !base[param] || !base[param].list) return null;
var list = base[param].list;
//console.log("API:", param, "base list", list);

return list.map(function(item) {
return item.ev;
}).filter(function(ev, index, self) {
if(ev == null) return false;
if(minEv != null && ev < minEv) return false;
if(maxEv != null && ev > maxEv) return false;
return self.indexOf(ev) === index; // ensure unique
}).sort(function(a, b){return a-b});
}

function evIndexOf(ev, evList) {
var i = evList.indexOf(ev);
if(i != -1) return i;
for(i = 0; i < evList.length; i++) {
if(ev <= evList[i]) {
if(i == 0) return i;
if(Math.abs(ev - evList[i]) > Math.abs(ev - evList[i - 1])) {
return i - 1;
} else {
return i;
}
}
}
return -1;
}

function incEv(ev, evList) {
//console.log("incEv: index", i, "ev", ev, "list", evList);
if(!evList) return null;
var i = evIndexOf(ev, evList);
if(i != -1 && i < evList.length - 1 && evList[i + 1] != null) return evList[i + 1];
if(ev != null && evList && evList.length > 0) {
var min = Math.min.apply(null, evList),
max = Math.max.apply(null, evList);
if(ev < min) return min;
if(ev > max) return max;
}
return ev;
}

function decEv(ev, evList) {
if(!evList) return null;
var i = evIndexOf(ev, evList);
if(i > 0 && evList[i - 1] != null) return evList[i - 1];
if(ev != null && evList && evList.length > 0) {
var min = Math.min.apply(null, evList),
max = Math.max.apply(null, evList);
if(ev < min) return min;
if(ev > max) return max;
}
return ev;
}

function equalEv(ev1, ev2) {
if(ev1 == null || ev2 == null) return true; // equal means ignore
return Math.abs(ev1 - ev2) < 0.25;
}

api.getEv = function(shutterEv, apertureEv, isoEv) {
if(shutterEv == null) shutterEv = api.cameras.length > 0 && api.cameras[0].camera.exposure.shutter ? api.cameras[0].camera.exposure.shutter.ev : null;
if(apertureEv == null) apertureEv = api.cameras.length > 0 && api.cameras[0].camera.exposure.aperture ? api.cameras[0].camera.exposure.aperture.ev : null;
Expand All @@ -588,8 +524,7 @@ api.getSecondsFromEv = function(ev) { // only accurate to 1/3 stop
return 0.1;
}

var lastParam = null;
api.setEv = function(ev, options, callback) {
api.setEv = function(targetEv, options, callback) {
if (!options) options = {};

var returnData = {
Expand All @@ -599,7 +534,7 @@ api.setEv = function(ev, options, callback) {
iso: {}
}

if(ev == null) return callback && callback("invalid ev", returnData);
if(targetEv == null) return callback && callback("invalid targetEv", returnData);
if(api.cameras.length == 0) return callback && callback("camera not connected", returnData);

var exposure = api.cameras[0].camera.exposure;
Expand Down Expand Up @@ -631,7 +566,7 @@ api.setEv = function(ev, options, callback) {
}
var origEv = currentEv;

if(equalEv(ev, currentEv)) {
if(api_util.equalEv(targetEv, currentEv)) {
return callback && callback(null, {
ev: currentEv,
shutter: {ev: shutterEv},
Expand All @@ -640,9 +575,10 @@ api.setEv = function(ev, options, callback) {
});
}

var shutterList = listEvs('shutter', options.shutterMax, null);
var apertureList = listEvs('aperture', options.apertureMin, options.apertureMax);
var isoList = listEvs('iso', options.isoMax, options.isoMin);
var base = api.cameras[0].camera.exposure;
var shutterList = api_util.listEvs(base, 'shutter', options.shutterMax, null);
var apertureList = api_util.listEvs(base, 'aperture', options.apertureMin, options.apertureMax);
var isoList = api_util.listEvs(base, 'iso', options.isoMax, options.isoMin);

console.log("API setEv: shutterList.length", shutterList && shutterList.length, "max =", options.shutterMax);
console.log("API setEv: apertureList.length", apertureList && apertureList.length);
Expand All @@ -656,63 +592,29 @@ api.setEv = function(ev, options, callback) {
});
}

if(!options.blendParams) lastParam = null;

for (var trys = 0; trys < 3; trys++) {
while (ev < currentEv - 1 / 4) {
//console.log("ev < currentEv");
var s = decEv(shutterEv, shutterList);
if (apertureEnabled) var a = decEv(apertureEv, apertureList);
var i = decEv(isoEv, isoList);

if (!equalEv(shutterEv, s) && lastParam != 's') {
shutterEv = s;
if(options.blendParams) lastParam = 's';
} else if (apertureEnabled && !equalEv(apertureEv, a) && lastParam != 'a') {
apertureEv = a;
if(options.blendParams) lastParam = 'a';
} else if (!equalEv(isoEv, i) && lastParam != 'i') {
isoEv = i;
if(options.blendParams) lastParam = 'i';
} else {
lastParam = null;
currentEv = api.getEv(shutterEv, apertureEv, isoEv);
break;
}
currentEv = api.getEv(shutterEv, apertureEv, isoEv);
//console.log(" update: ", currentEv);
}

while (ev > currentEv + 1 / 4) {
//console.log("ev > currentEv");
var s = incEv(shutterEv, shutterList);
if (apertureEnabled) var a = incEv(apertureEv, apertureList);
var i = incEv(isoEv, isoList);

if (!equalEv(isoEv, i) && lastParam != 'i') {
isoEv = i;
if(options.blendParams) lastParam = 'i';
} else if (apertureEnabled && !equalEv(apertureEv, a) && lastParam != 'a') {
apertureEv = a;
if(options.blendParams) lastParam = 'a';
} else if (!equalEv(shutterEv, s) && lastParam != 's') {
shutterEv = s;
if(options.blendParams) lastParam = 's';
} else {
lastParam = null;
currentEv = api.getEv(shutterEv, apertureEv, isoEv);
break;
}
currentEv = api.getEv(shutterEv, apertureEv, isoEv);
//console.log(" update: ", currentEv);
}

if (Math.abs(ev - currentEv) <= 1 / 4) break;

}


console.log("API setEv: current:", origEv, "target:", ev, "new:", currentEv);
if(!options.blendParams) api_util.setZeros()
const result = api_util.adjustCameraExposure(targetEv, currentEv,
shutterEv, shutterList,
apertureEnabled, apertureEv, apertureList,
isoEv, isoList,
options,
api.getEv
);
currentEv = result.currentEv;
shutterEv = result.shutterEv;
apertureEv = result.apertureEv;
isoEv = result.isoEv;

console.log("API setEv: current:", origEv, "target:", targetEv, "new:", currentEv);
console.log("API setEv: current:",
api_util.findEvName(exposure, 'shutter', result.shutterEv), " ",
api_util.findEvName(exposure, 'aperture', result.apertureEv), " ",
api_util.findEvName(exposure, 'iso', result.isoEv), " ",
result.direction, " ",
result.lastParam, " ",
targetEv.toFixed(2), " ",
result.currentEv.toFixed(2)
);

function runQueue(queue, callback) {
console.log("API: runQueue length", queue.length);
Expand Down
Loading