function [new_cal, new_res, exit_flag] = pinhole_fit_constrained(old_cal, im_pos, re_pos, flags)
	% new_cal = pinhole_fit_constrained(old_cal, im_pos, re_pos, flags)
	%
	% given a previous pinhole model, fit a new pinhole model to the data
	% im_pos and re_pos, which are image and object space coordinates of
	% points
	%
	% the new model will be constrained to be similar to the previous model
	% in a manner specified by flags
	%
	% flags:
	%	b_move_camera		allows camera position to be changed
	%	b_rotate_camera	allows camera orientation to be changed
	%
	
	%% apply least squares minimisation  
	X0							= empty_perturbation(flags);
	opts						= optimset('Display', 'off');

	function im_proj = model_fun(X,obj_pos)
		new_model=perturb_model(old_cal, flags, X);
		im_proj = pinhole_model(obj_pos, new_model, true);
		im_proj = im_proj(:);
	end

	[X,~,new_res, exit_flag]	= lsqcurvefit(@model_fun, X0, re_pos, im_pos(:), [], [], opts);
	new_cal						= perturb_model(old_cal, flags, X);


end

function X0 = empty_perturbation(flags)
	n_vars	= flags.b_move_camera*3 ...
				+ flags.b_rotate_camera * 3;
	X0			= zeros(1, n_vars);
end

function new_cal = perturb_model(old_cal, flags, X)
	%% unpack perturbation
	Y			= X;
	% unpack shift in pinhole position
	d_xc		= [0 0 0]';
	if flags.b_move_camera
		d_xc	= Y(1:3)';
		Y		= Y(4:end);
	end
	
	% unpack rotation
	omega		= [0 0 0];
	if flags.b_rotate_camera
		omega = Y(1:3);
	end
	omega		= [omega / (eps+norm(omega)), norm(omega)];
	Omega		= vrrotvec2mat(omega);
	
	%% apply perturbation
	new_cal		= old_cal;
	new_cal.xc	= old_cal.xc + d_xc;
	new_cal.R	= old_cal.R * Omega;
	new_cal.P	= new_cal.G*new_cal.R*[eye(3), -new_cal.xc];
end
