1
Tasks_capture_item = class
2
Menupullright "_Capture"
3
"useful stuff for capturing and preprocessing images" {
5
Csv_import_item = class
6
Menuaction "_CSV Import" "read a file of comma-separated values" {
11
path = Pathname "File to load" "empty";
12
start_line = Expression "Start at line" 1;
13
rows = Expression "Lines to read (-1 for whole file)" (-1);
14
whitespace = String "Whitespace characters" " \"";
15
separator = String "Separator characters" ",;\t";
18
= Image blank, path.value == "empty"
19
= Image (im_csv2vips filename)
21
filename = search (expand path.value) ++ ":" ++
22
"skip:" ++ print (start_line.expr - 1) ++ "," ++
23
"whi:" ++ escape whitespace.value ++ "," ++
24
"sep:" ++ escape separator.value ++ "," ++
25
"line:" ++ print rows.expr;
27
// prefix any ',' with a '\' in the separators line
32
= '\\' : x : l, x == ','
36
blank = image_new 1 1 1
37
Image_format.DOUBLE Image_coding.NOCODING Image_type.B_W
43
// interpret Analyze header for layout and calibration
44
Analyze7_header_item = class
45
Menuaction "_Interpret Analyze 7 Header"
46
"examine the Analyze header and set layout and value" {
50
// read bits of header
51
dim n = get_header ("dsr-image_dimension.dim[" ++ print n ++ "]");
60
glmax = get_header "dsr-image_dimension.glmax" x;
61
cal_max = get_header "dsr-image_dimension.cal_max" x;
66
// lay out higher dimensions width-ways
68
= grid dim2 dim3 1 x', dim0 == 3
69
= grid dim2 dim3 dim4 x', dim0 == 4
70
= grid (dim2 * dim4) dim5 1 (grid dim2 dim3 dim4) x', dim0 == 5
71
= grid (dim2 * dim4) dim5 dim6 (grid dim2 dim3 dim4) x', dim0 == 6
72
= grid (dim2 * dim4 * dim6) dim7 1
73
(grid (dim2 * dim4) dim5 dim6 (grid dim2 dim3 dim4)) x',
75
= error (_ "unsupported dimension " ++ dim0);
77
// multiply by scale factor to get kBeq
78
x''' = x'' * (cal_max / glmax);
83
Menuaction "Capture _Video Frame" "capture a frame of still video" {
85
prefs = Workspaces.Preferences;
91
device = prefs.VIDEO_DEVICE;
92
channel = Option "Input channel" [
97
] prefs.VIDEO_CHANNEL;
98
b = Scale "Brightness" 0 32767 prefs.VIDEO_BRIGHTNESS;
99
col = Scale "Colour" 0 32767 prefs.VIDEO_COLOUR;
100
con = Scale "Contrast" 0 32767 prefs.VIDEO_CONTRAST;
101
hue = Scale "Hue" 0 32767 prefs.VIDEO_HUE;
102
frames = Scale "Frames to average" 0 100 prefs.VIDEO_FRAMES;
103
mono = Toggle "Monochrome grab" prefs.VIDEO_MONO;
104
crop = Toggle "Crop image" prefs.VIDEO_CROP;
106
// grab, but hide it ... if we let the crop edit
107
_raw_grab = Image (im_video_v4l1 device channel.value
108
b.value col.value con.value
109
hue.value frames.value);
112
= Region _raw_grab left top width height
114
left = prefs.VIDEO_CROP_LEFT;
115
top = prefs.VIDEO_CROP_TOP;
117
prefs.VIDEO_CROP_WIDTH (_raw_grab.width + left);
119
prefs.VIDEO_CROP_HEIGHT (_raw_grab.height + top);
122
aspect_ratio = Expression "Stretch vertically by"
133
= colour_transform_to Image_type.B_W frame, mono
139
Smooth_image_item = class
140
Menuaction "_Smooth" "remove small features from image" {
145
feature = Scale "Minimum feature size" 1 50 20;
147
_result = map_unary (smooth feature.value) in;
151
Light_correct_item = class
152
Menuaction "_Flatfield" "use white image w to flatfield image i" {
157
= clip2fmt i.format (w' * i)
159
fac = mean w / max w;
160
w' = fac * (max w / w);
165
Image_rank_item = Filter_rank_item.Image_rank_item;
167
Tilt_item = Filter_tilt_item;
169
sep1 = Menuseparator;
171
White_balance_item = class
172
Menuaction "_White Balance"
173
"use average of small image to set white of large image" {
178
white_hint = "Set image white to:";
179
white = Colour_picker "Lab" [100, 0, 0];
185
= colour_transform_to (get_type image) image_xyz'
187
area x = x.width * x.height;
188
larger x y = area x > area y;
189
[image, patch] = sortc larger [a, b];
190
to_xyz = colour_transform_to Image_type.XYZ;
192
// white balance in XYZ
193
patch_xyz = to_colour (to_xyz patch);
194
white_xyz = to_xyz white;
196
facs = (mean patch_xyz / mean white_xyz) *
197
(white_xyz / patch_xyz);
199
image_xyz = to_xyz image;
200
image_xyz' = image_xyz * facs;
206
Gamma_item = Image_levels_item.Gamma_item;
208
Tone_item = Image_levels_item.Tone_item;
210
sep2 = Menuseparator;
212
Crop_item = Image_crop_item;
214
Rotate_item = Image_transform_item.Rotate_item;
216
Flip_item = Image_transform_item.Flip_item;
218
Resize_item = Image_transform_item.Resize_item;
220
Rubber_item = Image_transform_item.Image_rubber_item;
222
sep3 = Menuseparator;
224
ICC_item = Colour_icc_item;
226
Temp_item = Colour_temperature_item;
228
Find_calib_item = class
229
Menuaction "Find _Colour Calibration"
230
"find an RGB -> XYZ transform from an image of a colour chart" {
234
[image, "image", check_Image]
238
// get macbeth data file to use
239
macbeth = Pathname "Pick a Macbeth data file"
240
"$VIPSHOME/share/$PACKAGE/data/macbeth_lab_d65.mat";
242
mode = Option "Input LUT" [
244
"Fit intercept from chart greyscale",
245
"Linearize input from chart greyscale"
248
// get max of input image
249
_max_value = Image_format.maxval image.format;
251
// measure chart image
252
_camera = measure 0 0 image.width image.height 6 4 image.value;
255
_true_Lab = Matrix_file macbeth.value;
256
_true_XYZ = colour_transform
257
Image_type.LAB Image_type.XYZ _true_Lab;
259
// get Ys of greyscale
260
_true_grey_Y = map (extract 1) (drop 18 _true_XYZ.value);
262
// camera greyscale (all bands)
263
_camera_grey = drop 18 _camera.value;
265
// normalise both to 0-1 and combine
266
_camera_grey' = map (map (multiply (1 / _max_value))) _camera_grey;
267
_true_grey_Y' = map (multiply (1 / 100)) _true_grey_Y;
269
= Matrix [[0, 0], [1, 1]], mode == 0
270
= Matrix [0: intercepts, replicate (_camera.width + 1) 1],
272
= Matrix (map2 cons _true_grey_Y' _camera_grey')
274
intercepts = [(linreg _true_grey_Y' cam).intercept ::
275
cam <- transpose _camera_grey'];
278
// make a linearising lut ... zero on left
279
_linear_lut = im_invertlut _comb (_max_value + 1);
282
// plot from 0 explicitly so we see the effect of mode1 (intercept
284
linearising_lut = Plot [$ymin => 0] _linear_lut;
286
// map the original image through the lineariser to
287
// get linear 0-1 RGB image
288
_image' = hist_map linearising_lut.value image.value;
290
// remeasure and solve for RGB -> XYZ
291
_camera' = im_measure _image' 0 0 image.width image.height 6 4;
292
_pinv = (transpose _camera' * _camera') ** -1;
293
M = transpose (_pinv * transpose _camera' * _true_XYZ);
295
// convert linear RGB camera to Lab
297
colour_transform Image_type.XYZ Image_type.LAB @
301
// measure again and compute dE76
302
_camera'' = im_measure _result.value 0 0
303
image.width image.height 6 4;
305
_dEs = map abs_vec (_camera'' - _true_Lab).value;
306
final_dE76 = mean _dEs;
307
_max_dE = foldr max_pair 0 _dEs;
308
_worst = index (equal _max_dE) _dEs;
310
= name _worst ++ " (patch " ++
311
print (_worst + 1) ++ ", " ++
312
print _max_dE ++ " dE)"
315
= macbeth_names?i, i >= 0 && i < len macbeth_names
321
Apply_calib_item = class
322
Menuaction "_Apply Colour Calibration"
323
"apply an RGB -> LAB transform to an image" {
325
(map_binary process a b) {
327
= result, is_instanceof calib_name calib && is_Image image
328
= error (_ "bad arguments to " ++ "Calibrate_image")
330
// the name of the calib object we need
331
calib_name = "Tasks_capture_item.Find_calib_item.action";
333
// get the Calibrate_chart arg first
334
[image, calib] = sortc (const (is_instanceof calib_name))
337
// map the original image through the lineariser to get
338
// linear 0-1 RGB image
339
image' = hist_map calib.linearising_lut image;
341
// convert linear RGB camera to Lab
342
result = colour_transform Image_type.XYZ Image_type.LAB
343
((float) (recomb calib.M image'));
348
sep4 = Menuseparator;
350
Graph_hist_item = Hist_find_item;
352
Graph_bands_item = class
353
Menuaction "Plot _Bands" "show image bands as a graph" {
358
style = Option_enum Plot_style.names "Style" "Line";
360
auto = Toggle "Auto Range" true;
361
ymin = Expression "Y range minimum" 0;
362
ymax = Expression "Y range maximum" 1;
365
= Plot options (to_image (bands (image x))).value
368
= [$style => style.value] ++
370
[$ymin => ymin.expr, $ymax => ymax.expr];
372
// try to make something image-like from it
374
= extract_area x.left x.top 1 1 x.image, is_Mark x
375
= get_image x, has_image x
376
= get_image (to_image x);
378
// get as [[1],[2],[3]]
380
= transpose [map mean (bandsplit x)];
386
Tasks_mosaic_item = class
387
Menupullright "_Mosaic" "build image mosaics" {
388
/* Check and group a point list by image.
391
= error "mosaic: not all points",
393
= error "mosaic: points not on two images",
394
!is_list_len 2 images
395
= error "mosaic: images do not match in format and coding",
396
!all_equal (map get_format l) || !all_equal (map get_coding l)
397
= error "mosaic: not same number of points on each image",
398
!foldr1 equal (map len l')
401
// test for all elements of a list equal
402
all_equal l = all (map (equal (hd l)) (tl l));
404
// all the different images
405
images = mkset pointer_equal (map get_image l);
407
// find all points defined on image
408
test_image image p = (get_image p) === image;
409
find l image = filter (test_image image) l;
411
// group point list by image
412
l' = map (find l) images;
415
/* Sort a point group to get right before left, and within each group to
416
* get above before below.
421
// sort to get upper point first
422
above a b = a.top < b.top;
423
l' = map (sortc above) l;
425
// sort to get right group before left group
426
right a b = a?0.left > b?0.left;
427
l'' = sortc right l';
430
/* Sort a point group to get top before bottom, and within each group to
431
* get left before right.
436
// sort to get upper point first
437
left a b = a.left < b.left;
438
l' = map (sortc left) l;
440
// sort to get right group before left group
441
below a b = a?0.top > b?0.top;
442
l'' = sortc below l';
445
/* Put 'em together! Group by image, sort vertically (or horizontally) with
446
* one of the above, transpose to get pairs matched up, and flatten again.
448
mosaic_sort fn = concat @ transpose @ fn @ mosaic_sort_test;
450
Mosaic_1point_item = class
451
Menupullright "_One Point" "join two images with a single tie point" {
452
check_ab_args a b = [
453
[a, "a", check_Mark],
458
prefs = Workspaces.Preferences;
459
search_area = prefs.MOSAIC_WINDOW_SIZE;
460
object_size = prefs.MOSAIC_OBJECT_SIZE;
461
blend_width_widget = Scale "Maximum blend width" 0 100 prefs.MOSAIC_MAX_BLEND_WIDTH;
462
refine_widget = Toggle "Refine selected tie-points" prefs.MOSAIC_REFINE;
464
lr_mos _refine a b = class
466
_check_args = check_ab_args a b;
468
bw = blend_width_widget;
472
= im_lrmosaic a'.image.value b'.image.value 0
473
a'.left a'.top b'.left b'.top
474
(object_size / 2) (search_area / 2) 0 bw.value,
476
= im_lrmerge a'.image.value b'.image.value
477
(b'.left - a'.left) (b'.top - a'.top) bw.value
479
[a', b'] = mosaic_sort mosaic_sort_lr [a, b];
483
tb_mos _refine a b = class
485
_check_args = check_ab_args a b;
487
bw = blend_width_widget;
491
= im_tbmosaic a'.image.value b'.image.value 0
492
a'.left a'.top b'.left b'.top
493
(object_size / 2) (search_area / 2) 0 bw.value,
495
= im_tbmerge a'.image.value b'.image.value
496
(b'.left - a'.left) (b'.top - a'.top) bw.value
498
[a', b'] = mosaic_sort mosaic_sort_tb [a, b];
502
Left_right_item = class
503
Menuaction "_Left to Right"
504
"join two images left-right with a single tie point" {
505
action a b = lr_mos refine_widget a b;
508
Top_bottom_item = class
509
Menuaction "_Top to Bottom"
510
"join two images top-bottom with a single tie point" {
511
action a b = tb_mos refine_widget a b;
514
sep1 = Menuseparator;
516
Left_right_manual_item = class
517
Menuaction "Manual L_eft to Right"
518
"join left-right, no auto-adjust of tie points" {
519
action a b = lr_mos false a b;
522
Top_bottom_manual_item = class
523
Menuaction "Manual T_op to Bottom"
524
"join top-bottom, no auto-adjust of tie points" {
525
action a b = tb_mos false a b;
529
Mosaic_2point_item = class
530
Menupullright "_Two Point" "join two images with two tie points" {
531
check_abcd_args a b c d = [
532
[a, "a", check_Mark],
533
[b, "b", check_Mark],
534
[c, "c", check_Mark],
539
prefs = Workspaces.Preferences;
540
search_area = prefs.MOSAIC_WINDOW_SIZE;
541
object_size = prefs.MOSAIC_OBJECT_SIZE;
542
blend_width_widget = Scale "Maximum blend width" 0 100 prefs.MOSAIC_MAX_BLEND_WIDTH;
543
refine_widget = Toggle "Refine selected tie-points" prefs.MOSAIC_REFINE;
545
Left_right_item = class
546
Menuaction "_Left to Right"
547
"join two images left-right with a pair of tie points" {
548
action a b c d = class
550
_check_args = check_abcd_args a b c d;
552
bw = blend_width_widget;
553
refine = refine_widget;
556
= im_lrmosaic1 a'.image.value b'.image.value 0
557
a'.left a'.top b'.left b'.top
558
c'.left c'.top d'.left d'.top
559
(object_size / 2) (search_area / 2)
562
= im_lrmerge1 a'.image.value b'.image.value
563
a'.left a'.top b'.left b'.top
564
c'.left c'.top d'.left d'.top
567
[a', b', c', d'] = mosaic_sort mosaic_sort_lr [a, b, c, d];
572
Top_bottom_item = class
573
Menuaction "_Top to Bottom"
574
"join two images top-bottom with a pair of tie points" {
575
action a b c d = class
577
_check_args = check_abcd_args a b c d;
579
bw = blend_width_widget;
580
refine = refine_widget;
583
= im_tbmosaic1 a'.image.value b'.image.value 0
584
a'.left a'.top b'.left b'.top
585
c'.left c'.top d'.left d'.top
586
(object_size / 2) (search_area / 2)
589
= im_tbmerge1 a'.image.value b'.image.value
590
a'.left a'.top b'.left b'.top
591
c'.left c'.top d'.left d'.top
594
[a', b', c', d'] = mosaic_sort mosaic_sort_tb [a, b, c, d];
600
sep1 = Menuseparator;
603
Menuaction "Mosaic _Balance"
604
"disassemble mosaic, scale brightness to match, reassemble" {
606
= map_unary balance x
609
= oo_unary_function balance_op x, is_class x
610
= im_global_balancef x
611
Workspaces.Preferences.MOSAIC_BALANCE_GAMMA,
613
= error (_ "bad arguments to " ++ "balance")
615
balance_op = Operator "balance" balance
616
Operator_type.COMPOUND_REWRAP false;
621
////////////////////////////////////////////////////////////////////////////////////
622
////////////////////////////////////////////////////////////////////////////////////
623
Manual_balance_item = class
624
Menupullright "Manual B_alance" "balance tonality of user defined areas" {
625
prefs = Workspaces.Preferences;
627
////////////////////////////////////////////////////////////////////////////////////
628
Balance_find_item = class
629
Menuaction "_Find Values"
630
"calculates values required to scale and offset balance user defined areas in a given image"
631
/* Outputs a matrix of scale and offset values. Eg. Values required to balance the secondary
632
* structure in an X-ray image. Takes an X-ray image an 8-bit control mask and a list of
633
* 8-bit reference masks, where the masks are white on a black background.
636
action im_in m_control m_group = class
640
_control_im = if m_control then im_in else 0;
641
_control_meanmax = so_meanmax _control_im;
642
_group_check = is_Group m_group;
643
_m_list = m_group.value, _group_check
646
process m_current mat_in = mat_out
647
{so_values = so_calculate _control_meanmax im_in m_current;
648
mat_out = join [so_values] mat_in;}
650
values = (foldr process [] _m_list);
654
////////////////////////////////////////////////////////////////////////////////////
655
Balance_check_item = class
656
Menuaction "_Check Values"
657
"allows calculated set of scale and offset values to be checked and adjusted if required"
658
/* Outputs adjusted matrix of scale and offset values and scale and offset image maps.
659
* Eg. Check values required to balance the secondary structure in an X-ray image.
660
* Takes an X-ray image an 8-bit control mask and a list of 8-bit reference masks,
661
* where the masks are white on a black background.
664
action im_in m_matrix m_group = class
669
blur = Scale "Blur" 1 10 1;
670
_blur = (blur.value/2 + 0.5), blur.value > 1
673
_group_check = is_Group m_group;
674
_m_list = m_group.value, _group_check
677
adjust = Matrix_rec mat_a
679
no_masks = len _m_list;
680
mat_a = replicate no_masks [0, 0];
683
// Apply the user defined adjustments to the inputted matrix of scale and offset values
684
_adjusted = map2 fn_adjust m_matrix.value adjust.value;
685
fn_adjust a b = [(a?0 + b?0), (a?1 + (a?1 * b?1))];
687
_scaled_ims = map (fn_so_apply im_in) _adjusted;
688
fn_so_apply im so = map_unary adj im
689
{adj im = im * (so?0) + (so?1);}
690
_im_pairs = zip2 _m_list _scaled_ims;
692
// Prepare black images as starting point. ////////////
693
_blank = image_new (_m_list?0).width (_m_list?0).height 1 6 Image_coding.NOCODING 1 0 0 0;
694
_pair_start = [(_blank + 1), _blank];
696
Build = Toggle "Build Scale and Offset Correction Images" false;
702
offset_im = _build?1;
703
so_values = Matrix _adjusted;
705
_build = [Image so_images?0, Image so_images?1], Build
706
= ["Scale image not built.", "Offset image not built."]
708
m_list' = transpose [_m_list];
709
m_all = map2 join m_list' _adjusted;
710
so_images = foldr process_2 _pair_start m_all;
714
value = (foldr process_1 im_in_b _im_pairs).value
715
{im_in_b = map_unary cast_float im_in;}
717
process_1 m_current im_start
720
bl_mask = convsep (matrix_blur _blur) (get_image m_current?0);
721
blended_im = im_blend bl_mask (m_current?1).value im_start.value;
722
im_out = Image (clip2fmt im_start.format blended_im);
725
// Process for building scale and offset image.
726
process_2 current p_start
729
im_s = if ((current?0) > 128) then current?1 else _blank;
730
im_o = if ((current?0) > 128) then current?2 else _blank;
732
im_s' = convsep (matrix_blur _blur) (im_s != 0);
733
im_o' = convsep (matrix_blur _blur) (im_o != 0);
735
im_s'' = im_blend im_s'.value im_s.value p_start?0;
736
im_o'' = im_blend im_o'.value im_o.value p_start?1;
738
p_out = [im_s'', im_o''];
743
////////////////////////////////////////////////////////////////////////////////////
744
Balance_apply_item = class
745
Menuaction "_Apply Values"
746
"apply scale and offset corrections, defined as image maps, to a given image"
747
/* Outputs the balanced image. Eg. Balance the secondary structure in an X-ray image. Takes an
748
* X-ray image an 32-bit float scale image and a 32-bit offset image.
751
action im_in scale_im offset_im = class
756
xfactor = im_in.width/scale_im.width;
757
yfactor = im_in.height/scale_im.height;
759
_scale_im = resize xfactor yfactor 1 scale_im;
760
_offset_im = resize xfactor yfactor 1 offset_im;
762
value = get_image ( clip2fmt im_in.format ( ( im_in * _scale_im ) + _offset_im ) );
767
Tilt_item = Filter_tilt_item;
769
sep2 = Menuseparator;
772
Menuaction "_Rebuild"
773
"disassemble mosaic, substitute image files and reassemble" {
778
old = String "In each filename, replace" "foo";
779
new = String "With" "bar";
782
= map_unary remosaic x
785
= Image (im_remosaic image.value old.value new.value);
790
sep3 = Menuseparator;
792
Clone_area_item = class
793
Menuaction "_Clone Area"
794
"replace dark or light section of im1 with pixels from im2"
796
action im1 im2 = class
799
[im1, "im1", check_Image],
800
[im2, "im2", check_Image]
802
_vislevel = 3; /* Region on first
803
image placed in the top left hand corner,
804
* positioned and size relative to the height and width of im1.
806
r1 = Region_relative im1 0.05 0.05 0.05 0.05;
807
/* Mark on second image placed in the top left hand corner,
808
* positioned relative to the height and width of im2. Used to
809
* define _r2, the region from which the section of image is cloned
812
p2 = Mark_relative im2 0.05 0.05; _r2 = Region im2 p2.left
813
p2.top r1.width r1.height;
814
mask = [r1 <= Options.sc, r1 >=
815
Options.sc]?(Options.replace);
819
pause = Toggle "Pause process" true;
820
/* Option toggle used to define whether the user is
821
* replacing a dark or a light area.
823
replace = Option "Replace" [ "A Dark Area", "A Light Area" ] 1;
825
// Used to select the area to be replaced.
826
sc = Scale "Scale cutoff" 0.01 mx (mx / 2)
827
{mx = Image_format.maxval im1.format;}
828
//Allows replacement with scale&offset balanced gaussian noise.
829
balance = Toggle "Balance cloned data to match surroundings." true;
830
//Allows replacement with scale&offset balanced
832
process = Toggle "Replace area with Gaussian noise." false;
834
_result = im1, Options.pause
835
= Image (im_insert im1.value patch r1.left r1.top)
836
{ r2 = Region im2 p2.left p2.top r1.width r1.height;
837
ref_meanmax = so_meanmax (if mask then 0 else r1);
838
mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255],
840
mask_a = map_unary (dilate mask8) mask;
841
mask_b = convsep (matrix_blur 2) mask_a;
842
patch = so_balance ref_meanmax r1 r2 mask_b
843
Options.process, Options.balance
844
= so_balance ref_meanmax r1 r2 mask_b Options.process,
846
= im_blend (get_image mask_b) (get_image r2) (get_image r1);
852
Tasks_frame_item = Frame_item;
854
Tasks_print_item = class
855
Menupullright "_Print" "useful stuff for image output" {
857
Rotate_item = Image_transform_item.Rotate_item;
859
Flip_item = Image_transform_item.Flip_item;
861
Resize_item = Image_transform_item.Resize_item;
863
Tone_item = Image_levels_item.Tone_item;
866
Menuaction "_Sharpen"
867
"unsharp filter tuned for typical inkjet printers" {
872
target_dpi = Option "Sharpen for print at" [
880
= map_unary process x
883
= sharpen params?0 params?1
884
params?2 params?3 params?4 params?5
885
(colour_transform_to Image_type.LABQ image)
887
// sharpen params for various dpi
888
// just change the size of the area we search
890
[7, 2.5, 40, 20, 0.5, 1.5],
891
[5, 2.5, 40, 20, 0.5, 1.5],
892
[3, 2.5, 40, 20, 0.5, 1.5],
893
[11, 2.5, 40, 20, 0.5, 1.5]
895
params = param_table?target_dpi;
901
sep1 = Menuseparator;
903
Temp_item = Colour_temperature_item;
905
ICC_item = Colour_icc_item;