Params to Python external script?
Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller
Re: Params to Python external script?
I'll try to retreive one of my previous test code with for each tomorrow (I'm not on the right machine at this time) and also post my test stack.
I have to say that, even if the loop is optimized and well coded, one shouldn't expect very fast processes, as LC is not designed to image processing. I as an old Hypercard user, I love LC, and ImageData is a very nice feature, but I think I really should use some language with external graphic library for this kind of project. In fact, it's a very small project and I will not use the compiled app often, so I decided to realized it in pure LC.
I have to say that, even if the loop is optimized and well coded, one shouldn't expect very fast processes, as LC is not designed to image processing. I as an old Hypercard user, I love LC, and ImageData is a very nice feature, but I think I really should use some language with external graphic library for this kind of project. In fact, it's a very small project and I will not use the compiled app often, so I decided to realized it in pure LC.
-
- VIP Livecode Opensource Backer
- Posts: 9833
- Joined: Sat Apr 08, 2006 7:05 am
- Location: Los Angeles
- Contact:
Re: Params to Python external script?
Python isn't either, which helps make the prospect of a comparison interesting.
Python's advantage here is that it has indexed arrays, which LC Builder has, but oddly have not been added to LC Script.
Richard Gaskin
LiveCode development, training, and consulting services: Fourth World Systems
LiveCode Group on Facebook
LiveCode Group on LinkedIn
LiveCode development, training, and consulting services: Fourth World Systems
LiveCode Group on Facebook
LiveCode Group on LinkedIn
Re: Params to Python external script?
Python has this: https://en.wikipedia.org/wiki/Python_Imaging_LibraryFourthWorld wrote: ↑Tue Jul 13, 2021 2:00 pmPython isn't either, which helps make the prospect of a comparison interesting.
Python's advantage here is that it has indexed arrays, which LC Builder has, but oddly have not been added to LC Script.
So it may be that what Zax is trying to do is done in that C library (the library has pixel transparency manipulation). I'm not expecting LC to be able to do what Python can do, quite simply because so many millions of hours have gone into enhancing Python and its libraries. But given what Zax is doing, I would hope that "repeat for each" and memoization would significantly increase the speed of what he's doing.
Re: Params to Python external script?
I don't know what you might find in Hermann's stacks, but he did some amazing things, with a number of image processing tools among them. Some of them, I believe, leverage imagemagick libraries and from within LiveCode could produce high-speed results.
It's definitely worth delving to see what stacks are left and appreciate his legacy whatever you might find.
It's definitely worth delving to see what stacks are left and appreciate his legacy whatever you might find.
Re: Params to Python external script?
Totally agree. Also Wilhelm Sanke did a lot of work with images. As did Scott Rossi.SparkOut wrote: ↑Tue Jul 13, 2021 6:37 pmI don't know what you might find in Hermann's stacks, but he did some amazing things, with a number of image processing tools among them. Some of them, I believe, leverage imagemagick libraries and from within LiveCode could produce high-speed results.
It's definitely worth delving to see what stacks are left and appreciate his legacy whatever you might find.
If I was going to look at what was possible with Livecode, I'd be googling all those names and seeing what's available.
-
- VIP Livecode Opensource Backer
- Posts: 7230
- Joined: Sat Apr 08, 2006 8:31 pm
- Location: Minneapolis MN
- Contact:
Re: Params to Python external script?
I agree, the three names mentioned did good work with images and I vaguely remember that one of them did a grayscale conversion (but it was too long ago for me to be sure.)
At any rate, I think a lot of the slowdown is with this line inside the repeat loop:
That's because it has to count from 0 to the current position every time through the loop, and as the position increases it takes longer for the count.
Instead of putting the original imagedata into tImage variable and trying to alter it, I'd put empty into the variable to start. Then for each iteration, just append the converted RGB to the end of tImage variable. This should work because you're doing the conversion in order, from the first quadruple to the last.
When tImage is fully populated, set the imageData of the destination image to tImage as you do now.
EDIT: It looks like you're already doing that with the alpha channel.
At any rate, I think a lot of the slowdown is with this line inside the repeat loop:
Code: Select all
put ntcGray & ntcGray & ntcGray into char (tImgPosition + 2) to (tImgPosition + 4) of tImage
Instead of putting the original imagedata into tImage variable and trying to alter it, I'd put empty into the variable to start. Then for each iteration, just append the converted RGB to the end of tImage variable. This should work because you're doing the conversion in order, from the first quadruple to the last.
When tImage is fully populated, set the imageData of the destination image to tImage as you do now.
EDIT: It looks like you're already doing that with the alpha channel.
Jacqueline Landman Gay | jacque at hyperactivesw dot com
HyperActive Software | http://www.hyperactivesw.com
HyperActive Software | http://www.hyperactivesw.com
Re: Params to Python external script?
This is the little stack I use for testing.
Result for a very small image (400 x 232 pixels) without any brightness or contrast modifications:
- current script (posted below) : repeat for... step 4 --> 590 ms
- repeat for each --> 1020 ms
- repeat for each with switch --> 1400 ms
- "after" with a brand new imageData variable : 17200 ms !
Best performance with this script:
But there is a strange thing: when commenting line 119 "put ntcGray & ntcGray & ntcGray into char (tImgPosition + 2) to (tImgPosition + 4) of tImage", process takes 13000 ms! I don't understand why not modifying the imageData takes longer!
If line 119 is commented, the script only modifies the alphaData.
And yes, we could have use of external like ImageMagick, even less powerful.
Result for a very small image (400 x 232 pixels) without any brightness or contrast modifications:
- current script (posted below) : repeat for... step 4 --> 590 ms
- repeat for each --> 1020 ms
- repeat for each with switch --> 1400 ms
- "after" with a brand new imageData variable : 17200 ms !
Best performance with this script:
Code: Select all
local arrParams
on preProcess
if ("button" is in the target) then set hilite of the target to true
set cursor to watch
------- reset PixEnd (delete image pour full reset si l'alphaData est corrompu)
if (there is an image arrParams["destName"]) then delete image arrParams["destName"]
create image arrParams["destName"]
set threeD of image arrParams["destName"] to (the threeD of image arrParams["sourceName"])
set borderWidth of image arrParams["destName"] to (the borderWidth of image arrParams["sourceName"])
set showBorder of image arrParams["destName"] to (the showBorder of image arrParams["sourceName"])
------- visuel
set the rect of image arrParams["destName"] to (the rect of image arrParams["sourceName"])
set the topLeft of image arrParams["destName"] to \
(the left of image arrParams["sourceName"] & "," & the bottom of image arrParams["sourceName"] + 6)
-------
put the milliseconds into fld "Timer" -- /////
end preProcess
on getParams
put "Pix" into arrParams["sourceName"]
put "PixEnd" into arrParams["destName"]
put the hilite of btn "ChkInvert" into arrParams["invert"] -- négatif
get the thumbPos of scrollbar "SliderLighten" -- de 0 (aucun éclaircissement) à 10 (éclaircissement maxi)
put round(it * 200 / 10) into arrParams["brightness"] -- de 0 à 200
get the thumbPos of scrollbar "SliderContrast" -- de 0 (aucune modif) à 10 (contraste maxi)
put it / 10 into arrParams["contrast"] -- de 0 à 1
put 0.30 into arrParams["xRed"] -- augmenter pour éclaircir les rouges (avec total = 1)
put 0.59 into arrParams["xGreen"] -- augmenter pour éclaircir les verts (avec total = 1)
put 0.11 into arrParams["xBlue"] -- augmenter pour éclaircir les bleus (avec total = 1)
put the width of image arrParams["sourceName"] into arrParams["w"]
put the height of image arrParams["sourceName"] into arrParams["h"]
end getParams
on postProcess
------- visual effect
show image "PixEnd"
put (the milliseconds - fld "Timer") && "ms" into fld "Timer"
if ("button" is in the target) then set hilite of the target to false
end postProcess
on mouseUp
getParams
preProcess
turnToGrayTranspa arrParams["sourceName"], arrParams["destName"], arrParams["w"], arrParams["h"], arrParams["xRed"], arrParams["xGreen"], arrParams["xBlue"], arrParams["brightness"], arrParams["contrast"], arrParams["invert"]
postProcess
end mouseUp
on turnToGrayTranspa pixSourceName, pixDestName, imgWidth, imgHeight, xRed, xGreen, xBlue, brightness, fc, invertBWboolean
put "" into log
addToLog log, "Button:" && the short name of me
addToLog log, imgWidth && "x" && imgHeight && "pixels"
addToLog log, "r = " & xRed && " g = " & xGreen && " b = " & xBlue
addToLog log, "brightness = " & brightness
----------------------------------------------------- prépa : négatif
if (invertBWboolean) then
put 255 into grayCorrection
else put 0 into grayCorrection
addToLog log, "invertBWboolean = " & invertBWboolean && " --> grayCorrection = " & grayCorrection
----------------------------------------------------- prépa : contraste
put 0 - (fc^5 + 0.05) into fp5 -- pré-calcul
put sqrt(fc) into racf -- pré-calcul
addToLog log, "fc = " & fc
----------------------------------------------------- prépa : image et transparence
put the imageData of image pixSourceName into tImage
local aImage -- pour alphaData
-----------------------------------------------------
local tPixel, tImgPosition, tGray
# repeat for all the pixels in the image
repeat with tImgPosition = 0 to 4*((imgWidth * imgHeight) - 1) step 4
----------------------------------------------------- extractions des composantes RGB
# multiply the image pixel position by 4 (Alpha + Red + Green + Blue)
/* ============================== plus long
# calculate the gray level
put charToNum(char(tImgPosition + 2) of tImage) into tRed
put charToNum(char(tImgPosition + 3) of tImage) into tGreen
put charToNum(char(tImgPosition + 4) of tImage) into tBlue
----------------------------------------------------- gris
# here you can use any grayscale formula
# set the RGB of the pixel to the same value this gives us the gray level (0-255)
--put (xRed * tRed) + (xGreen * tGreen) + (xBlue * tBlue) into tGray
--------------------- application auto "négatif"
put abs(grayCorrection - ((xRed * tRed) + (xGreen * tGreen) + (xBlue * tBlue))) into tGray
============================== */
------------ calcul valeurs gris et application auto "négatif"
put abs(grayCorrection - ((xRed * charToNum(char(tImgPosition + 2) of tImage)) + \
(xGreen * charToNum(char(tImgPosition + 3) of tImage)) + \
(xBlue * charToNum(char(tImgPosition + 4) of tImage)))) into tGray
----------------------------------------------------- contraste
if (fc > 0) then
put (tGray * (1 - racf)) + racf * (255 / (1 + (0.9921875 * exp(fp5 * (tGray - 128))))) into tGray
-- (x * (1 - sqrt(fc))) + sqrt(fc) * (255 / (1 + (0.9921875 * exp(-(fc^5 + 0.05) * (x - 128)))))
/* -- plus précis mais plus long
if (fc >= 0.5) then
put exp(-2 * (tGray - 128) * fc^4) into tmg
put floor(128 + 127.5 * (1 - tmg) / ( 1 + tmg)) into tGray
else
put exp(-2 * (tGray - 128) * 0.5^4) into tmg
get 128 + 127.5 * (1 - tmg) / ( 1 + tmg)
put floor(tGray * (1 - 2 * fc) + 2 * fc * it) into tGray
end if
*/
end if
----------------------------------------------------- éclaircissement général
put min(255, brightness + tGray) into tGray
----------------------------------------------------- application des nouvelles valeurs de gris
put numToChar(tGray) into ntcGray
put ntcGray & ntcGray & ntcGray into char (tImgPosition + 2) to (tImgPosition + 4) of tImage
----------------------------------------------------- transparence toutes valeurs (alphaData)
--------- transparence proportionnelle à la valeur de gris : plus c'est clair -> plus c'est transparent
/*
binaryEncode("C",0) --> transparentPixel
binaryEncode("C",255) --> opaquePixel
*/
put binaryEncode("C", 255 - tGray) after aImage -- 0 = transparent et 255 : opaque
end repeat
-------------------------------------------------------- affectation image dest
set the imageData of image pixDestName to tImage
set the alphaData of img pixDestName to aImage
if (there is a fld "Log") then put log into fld "Log" --/////
end turnToGrayTranspa
on addToLog @log, str
put str & return after log
end addToLog
If line 119 is commented, the script only modifies the alphaData.
And yes, we could have use of external like ImageMagick, even less powerful.
-
- VIP Livecode Opensource Backer
- Posts: 7230
- Joined: Sat Apr 08, 2006 8:31 pm
- Location: Minneapolis MN
- Contact:
Re: Params to Python external script?
I ran your stack with script profiling turned on. When timing only the turnToGrayscale handlers, the "put after" method was the fastest. The scripted timer doesn't isolate that handler so the variance seems to be in something else.
You can run the profiler by choosing "Start profiling scripts" from the Development menu. I did that and then ran each of the buttons, then stopped profiling. This puts up a profiler log that shows the total time for each handler as well as the time for each line of every handler. You'll also see a lot of LC back-end stuff going on, which could affect the total timing.
Note that the times are not real-time calculations because the act of profiling slows down the entire run. But it will give you relative timings and a pretty good idea of where the slowdowns occur. Since profiling is slow, I chose a very small image 128x128 px.
The summary of my runs in the profiler:
However, when I used your scripted timing, the "after" script was much slower than the others. I'm not sure where the differences are. The preProcess, postProcess, and getParams handlers seem to take almost no time at all.
You can run the profiler by choosing "Start profiling scripts" from the Development menu. I did that and then ran each of the buttons, then stopped profiling. This puts up a profiler log that shows the total time for each handler as well as the time for each line of every handler. You'll also see a lot of LC back-end stuff going on, which could affect the total timing.
Note that the times are not real-time calculations because the act of profiling slows down the entire run. But it will give you relative timings and a pretty good idea of where the slowdowns occur. Since profiling is slow, I chose a very small image 128x128 px.
The summary of my runs in the profiler:
Code: Select all
button "GreyTranspa_switch" of card "Main" 1820 ms
button "GreyTranspa_Each" of card "Main" 1533 ms
button "GreyTranspa_after" of card "Main" 749 ms
-- followed by lots of LC back-end handlers and the other handlers in the buttons
Jacqueline Landman Gay | jacque at hyperactivesw dot com
HyperActive Software | http://www.hyperactivesw.com
HyperActive Software | http://www.hyperactivesw.com
Re: Params to Python external script?
Jacque, isn't the profiler only with the Business IDE?
Zax, since the loop over the pixels is where you say the speed is lost, in my opinion you need to strip out most of your processing in those loops to prove to yourself that "repeat... step 4" is faster than "repeat for each". For instance, one thing I did was at the end of your different handlers I stored the processed image as a custom property, then I could compare afterwards to see if they were the same size at the end of the processing, and they were not the same. This difference in size could be a sign that you're not doing the same processing in each variant of your handler.
In my tests on an image (testing just looping over the pixels) "repeat for each" is at least 10x faster than using the kind of repeat loop you have chosen. Strip down the various handlers to a minimum and assure yourself which is the fastest at extracting pixels and putting them into a new image, then assure yourself that the new image is the same size at the end of each handler as you add back more functionality. It seems that through two different approaches Jacque and I are getting confirmation of what we have long known to be true. Maybe something has changed in LC and "repeat for each" is not now as fast as the multi-pass repeat loop ("repeat ... step 4"). Whilst obviously your outer loop is only addressing 1 in 4 of the pixels in the imagedata to drive the loop, within the loop you then address the other pixels at least 3 times for each repetition.
Moreover, having read some of the discussions on this forum about imageData people were saying that charToNum is deprecated (in this context) and byteToNum should be used instead (they were saying that this is faster, and they are the users who specialise in image processing speeds). You'd want to use byte instead of char, too. Use the forum's search box to look for imageData and byteToNum. In many of the instances where they were discussing speed they were using the kind of repeat loop you have preferred, but their image processing didn't lend itself to a single pass the way your task does.
I hope that helps.
Zax, since the loop over the pixels is where you say the speed is lost, in my opinion you need to strip out most of your processing in those loops to prove to yourself that "repeat... step 4" is faster than "repeat for each". For instance, one thing I did was at the end of your different handlers I stored the processed image as a custom property, then I could compare afterwards to see if they were the same size at the end of the processing, and they were not the same. This difference in size could be a sign that you're not doing the same processing in each variant of your handler.
In my tests on an image (testing just looping over the pixels) "repeat for each" is at least 10x faster than using the kind of repeat loop you have chosen. Strip down the various handlers to a minimum and assure yourself which is the fastest at extracting pixels and putting them into a new image, then assure yourself that the new image is the same size at the end of each handler as you add back more functionality. It seems that through two different approaches Jacque and I are getting confirmation of what we have long known to be true. Maybe something has changed in LC and "repeat for each" is not now as fast as the multi-pass repeat loop ("repeat ... step 4"). Whilst obviously your outer loop is only addressing 1 in 4 of the pixels in the imagedata to drive the loop, within the loop you then address the other pixels at least 3 times for each repetition.
Moreover, having read some of the discussions on this forum about imageData people were saying that charToNum is deprecated (in this context) and byteToNum should be used instead (they were saying that this is faster, and they are the users who specialise in image processing speeds). You'd want to use byte instead of char, too. Use the forum's search box to look for imageData and byteToNum. In many of the instances where they were discussing speed they were using the kind of repeat loop you have preferred, but their image processing didn't lend itself to a single pass the way your task does.
I hope that helps.
-
- VIP Livecode Opensource Backer
- Posts: 7230
- Joined: Sat Apr 08, 2006 8:31 pm
- Location: Minneapolis MN
- Contact:
Re: Params to Python external script?
Oh. Maybe. I've had a business license so long I've lost track.Jacque, isn't the profiler only with the Business IDE?
Jacqueline Landman Gay | jacque at hyperactivesw dot com
HyperActive Software | http://www.hyperactivesw.com
HyperActive Software | http://www.hyperactivesw.com
-
- VIP Livecode Opensource Backer
- Posts: 9833
- Joined: Sat Apr 08, 2006 7:05 am
- Location: Los Angeles
- Contact:
Re: Params to Python external script?
Is that library written in C, or Python?Bernard wrote: ↑Tue Jul 13, 2021 6:00 pmPython has this: https://en.wikipedia.org/wiki/Python_Imaging_LibraryFourthWorld wrote: ↑Tue Jul 13, 2021 2:00 pmPython isn't either, which helps make the prospect of a comparison interesting.
Python's advantage here is that it has indexed arrays, which LC Builder has, but oddly have not been added to LC Script.
So it may be that what Zax is trying to do is done in that C library...
If C, why is it only compatible with older versions of Python?
Richard Gaskin
LiveCode development, training, and consulting services: Fourth World Systems
LiveCode Group on Facebook
LiveCode Group on LinkedIn
LiveCode development, training, and consulting services: Fourth World Systems
LiveCode Group on Facebook
LiveCode Group on LinkedIn
Re: Params to Python external script?
Thank you very much for all your interesting advices, I reallu appreciate
I'll look deeper into my code tomorrow, and also try byte intead of char.
@Jacqueline: as I don't have the Business edition, could you please test button "GreyTranspa" of card "Main" with the Script Profiler? It's for me the fastest process.
For information, processes on MacOS compiled version take almost the same durations (I remember older LC versions where running script in IDE were slower).
I'll look deeper into my code tomorrow, and also try byte intead of char.
@Jacqueline: as I don't have the Business edition, could you please test button "GreyTranspa" of card "Main" with the Script Profiler? It's for me the fastest process.
For information, processes on MacOS compiled version take almost the same durations (I remember older LC versions where running script in IDE were slower).
Re: Params to Python external script?
I spent a lot of time running tests and writing the results here. When I went to post, the stupid forum software/setup demanded I login again. I did so and all my work was lost.
What kind of design trashes a user's work like that?
My tests revealed lots of contradictory results (some back up Zax claims and contradict Jacque's profiling, others back up my expectations). I really can't be bothered to spend more of my time on this when the forum is so badly designed.
What kind of design trashes a user's work like that?
My tests revealed lots of contradictory results (some back up Zax claims and contradict Jacque's profiling, others back up my expectations). I really can't be bothered to spend more of my time on this when the forum is so badly designed.
Code: Select all
repeat for each ... byteToNum() 3,108 ms
repeat for each... charToNum() 3,356 ms
repeat with ... the number of chars... charToNum() 25,631 ms
repeat with ... the number of bytes in... byteToNum() 3,169 ms
Last edited by Bernard on Thu Jul 15, 2021 2:12 pm, edited 1 time in total.
Re: Params to Python external script?
According to the Wikipedia page, it is C and Python.Is that library written in C, or Python?
If C, why is it only compatible with older versions of Python?
-
- VIP Livecode Opensource Backer
- Posts: 9833
- Joined: Sat Apr 08, 2006 7:05 am
- Location: Los Angeles
- Contact:
Re: Params to Python external script?
Thanks, I see that now. If critical, the C core could be migrated to an external for LC.
But that it's C was really the main point here: Python and LC have enough in common that I would imagine both have very similar performance in areas of equivalent functionality. The only area where Python has a clear advantage is algos that benefit from indexed arrays, but while those still aren't in LC Script they are in LC Builder, so we may see good parity between the two scripting languages.
Richard Gaskin
LiveCode development, training, and consulting services: Fourth World Systems
LiveCode Group on Facebook
LiveCode Group on LinkedIn
LiveCode development, training, and consulting services: Fourth World Systems
LiveCode Group on Facebook
LiveCode Group on LinkedIn