So this is the second time I've posted a question like this. Last time I got so much help from a user called #quantixed but I need help again.
My code isn't working, but it's something to do with me trying to isolate images from a stack because I need to analyse each layer separately and look for over lap (double positive and triple positive cells). It runs up until I try to name each layer. I've tried adding "startsWith" because each layer regardless of stack starts with c:1/3, c:2/3 or c:3.
This is the code:
macro "Process My Files" {
dir1 = getDirectory("C:/Users/laure/OneDrive/Documents/MSc Molecular Biology & Biotechnology/Masters Project - Dombrowski Lab/Project Data/LD005 CX5/CellHealthProfiling.V4_03-06-20_09;46;02/CEM-133432_200229080001/");
dir2 = getDirectory("C:/Users/laure/OneDrive/Documents/MSc Molecular Biology & Biotechnology/Masters Project - Dombrowski Lab/Project Data/LD005 CX5/CellHealthProfiling.V4_03-06-20_09;46;02/CEM-133432_200229080001/ImageJ Macro Batch Results");
list = getFileList(dir1);
// Make an array of C01 files only
C01list = newArray(0);
for (i=0; i<list.length; i++) {
if (endsWith(list[i], ".C01")) {
C01list = append(C01list, list[i]);
}
}
x=startsWith("c:1/3")
y=startsWith("c:2/3")
a=startsWith("c:3/3")
b="Result of" + x
c="Result of" + b
d="Drawing of" + x
e="Drawing of" + y
f="Drawing of" + a
g="Drawing of" + b
h="Drawing of" + g
function DAPI() {
selectWindow(x);
run("8-bit");
setThreshold(45, 255);
run("Convert to Mask");
run("Analyze Particles...", "size=30-350 show=Outlines clear summarize add");
}
function OLIG2() {
selectWindow(y);
run("8-bit");
setThreshold(25, 255);
run("Conert to Mask");
run("Analyze Particles...", "size=30-250 show=Outlines clear summarize add");
}
function MBP() {
selectWindow(a);
run("8-bit");
setThreshold(52, 255);
run("Convert to Mask");
run("Analyze Particles...", "size=30.00-250.0 show=Outlines clear summarize add");
}
function DAPI_Olig2_overlay() {
imageCalculator("AND create", x, y);
selectWindow(b);
run("Analyze Particles...", "size=30-250 show=Outlines clear summarize add");
}
function DAPI_Olig2_MBP_overlay() {
imageCalculator("AND create", b, a); //overlay DAPI, Olig2 and Ki-67
selectWindow(d);
run("Analyze Particles...", "size=30-250 show=Outlines clear summarize add");
}
setBatchMode(true);
for (i=0; i<C01list.length; i++) {
showProgress(i+1, C01list.length);
// your code goes here - an example is shown
s = "open=["+dir1+C01list[i]+"] autoscale color_mode=Composite rois_import=[ROI manager] view=Hyperstack stack_order=XYCZT";
DAPI();
OLIG2();
Ki67orMBP();
DAPI_Olig2_overlay();
DAPI_Olig2_Ki67_overlay();
saveAs("tiff", dir2+replace(C01list[i],".C01",".tif"));
close();
// and ends here
}
setBatchMode(false);
}
function append(arr, value) {
arr2 = newArray(arr.length+1);
for (i=0; i<arr.length; i++)
arr2[i] = arr[i];
arr2[arr.length] = value;
return arr2;
}
The problem I'm having is that it reaches x=startsWith("c:1/3"), it can't go any further. I know the rest of the code works, its just having an issue selecting which layer of the stack I want it to analyse. Whenever I hit run I get this error back:
Error: Number or numeric function expected in line 14:
x = startsWith ( "c:1/3" <)>
Any ideas?
Related
As mentioned in the title, I have part of my vscode Chinese, which is troublesome since looking up error codes in English will be easier. How can I turn it into complete English? I've tried How to set Visual Studio Code Terminal output to English and it didn't work for me.
Since someone asked for the code to be post in text, this is the code (I'm using a tutorial code for camera calibration from opencv, so the error code appearing also confused me):
#include <iostream>
#include <opencv2/calib3d.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
int main(int argc, char **argv) {
(void)argc;
(void)argv;
std::vector<cv::String> fileNames;
cv::glob("../calibration/Image*.png", fileNames, false);
cv::Size patternSize(25 - 1, 18 - 1);
std::vector<std::vector<cv::Point2f>> q(fileNames.size());
std::vector<std::vector<cv::Point3f>> Q;
// 1. Generate checkerboard (world) coordinates Q. The board has 25 x 18
// fields with a size of 15x15mm
int checkerBoard[2] = {25,18};
// Defining the world coordinates for 3D points
std::vector<cv::Point3f> objp;
for(int i = 1; i<checkerBoard[1]; i++){
for(int j = 1; j<checkerBoard[0]; j++){
objp.push_back(cv::Point3f(j,i,0));
}
}
std::vector<cv::Point2f> imgPoint;
// Detect feature points
std::size_t i = 0;
for (auto const &f : fileNames) {
std::cout << std::string(f) << std::endl;
// 2. Read in the image an call cv::findChessboardCorners()
cv::Mat img = cv::imread(fileNames[i]);
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_RGB2GRAY);
bool patternFound = cv::findChessboardCorners(gray, patternSize, q[i], cv::CALIB_CB_ADAPTIVE_THRESH + cv::CALIB_CB_NORMALIZE_IMAGE + cv::CALIB_CB_FAST_CHECK);
// 2. Use cv::cornerSubPix() to refine the found corner detections
if(patternFound){
cv::cornerSubPix(gray, q[i],cv::Size(11,11), cv::Size(-1,-1), cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 30, 0.1));
Q.push_back(objp);
}
// Display
cv::drawChessboardCorners(img, patternSize, q[i], patternFound);
cv::imshow("chessboard detection", img);
cv::waitKey(0);
i++;
}
cv::Matx33f K(cv::Matx33f::eye()); // intrinsic camera matrix
cv::Vec<float, 5> k(0, 0, 0, 0, 0); // distortion coefficients
std::vector<cv::Mat> rvecs, tvecs;
std::vector<double> stdIntrinsics, stdExtrinsics, perViewErrors;
int flags = cv::CALIB_FIX_ASPECT_RATIO + cv::CALIB_FIX_K3 +
cv::CALIB_ZERO_TANGENT_DIST + cv::CALIB_FIX_PRINCIPAL_POINT;
cv::Size frameSize(1440, 1080);
std::cout << "Calibrating..." << std::endl;
// 4. Call "float error = cv::calibrateCamera()" with the input coordinates
// and output parameters as declared above...
float error = cv::calibrateCamera(Q, q, frameSize, K, k, rvecs, tvecs, flags);
std::cout << "Reprojection error = " << error << "\nK =\n"
<< K << "\nk=\n"
<< k << std::endl;
// Precompute lens correction interpolation
cv::Mat mapX, mapY;
cv::initUndistortRectifyMap(K, k, cv::Matx33f::eye(), K, frameSize, CV_32FC1,
mapX, mapY);
// Show lens corrected images
for (auto const &f : fileNames) {
std::cout << std::string(f) << std::endl;
cv::Mat img = cv::imread(f, cv::IMREAD_COLOR);
cv::Mat imgUndistorted;
// 5. Remap the image using the precomputed interpolation maps.
cv::remap(img, imgUndistorted, mapX, mapY, cv::INTER_LINEAR);
// Display
cv::imshow("undistorted image", imgUndistorted);
cv::waitKey(0);
}
return 0;
}
This is my second go at writing a macro (with virtually zero coding knowledge) after the first one was a success, but I am adding a layer of complexity that I can't seem to make functional.
I am trying to set up a batch process where I count particles of 2 different colors and then which of those particles is positive for both colors. I am getting this error:
Error: ')' expected in line 38:
selectWindow ( "Result of " - "+Title" ) ;
I really don't know what I need to fix because it seems that I have closed all open parentheses. However, I know that the root issue is that I don't know how to generically name the window I am interested in. It is a window that is created by the macro and is not one of the input files.
dir1 = getDirectory("Input");
dir2 = getDirectory("Output");
list = getFileList(dir1);
run("Close All");
setBatchMode(true);
for (i=0; i<list.length; i++) {
file1 = dir1 + list[i];
file2 = dir2 + list[i];
file3 = dir2 + list[i];
file3 = replace(file3, ".tif", ".csv");
open(file1);
Title = getTitle();
Title = replace(Title, ".tif", "");
run("Stack to Images");
selectWindow(Title+"-0002");
rename ("mNG-"+Title);
run("Subtract Background...", "rolling=50");
setAutoThreshold("RenyiEntropy dark");
//run("Threshold...");
run("Convert to Mask");
run("Fill Holes");
run("Watershed");
run("Set Measurements...", "area mean min centroid center perimeter integrated median kurtosis area_fraction stack limit display redirect=None decimal=2");
run("Analyze Particles...", "size=9-475 circularity=0.50-1.00 show=Outlines display exclude summarize in_situ");
run("Fill Holes");
selectWindow(Title+"-0003");
rename ("tdT-"+Title);
run("Subtract Background...", "rolling=50");
setAutoThreshold("RenyiEntropy dark");
//run("Threshold...");
run("Convert to Mask");
run("Fill Holes");
run("Watershed");
run("Set Measurements...", "area mean min centroid center perimeter integrated median kurtosis area_fraction stack limit display redirect=None decimal=2");
run("Analyze Particles...", "size=9-475 circularity=0.50-1.00 show=Outlines display exclude summarize in_situ");
run("Fill Holes");
imageCalculator("Add create", "mNG-"+Title,"tdT-"+Title);
rename ("doublepositive"+Title)
selectWindow(Result of "mNG-"+Title);
run("Analyze Particles...", "size=9-475 circularity=0.50-1.00 show=Outlines display exclude summarize in_situ");
run("Images to Stack", "name=[] title=[] use");
saveAs("Tiff", file2);
run("Close All");
}
setBatchMode(false);
selectWindow("Summary");
saveAs("Results", "file3");
run("Close All");
If I could get help on why my syntax is wrong and also feedback on how to generically name the window with the third result, I would greatly appreciate it.
I think, that you just have to change the whole name in to a string
selectWindow("Result of mNG-"+Title);
or
selectWindow("Result of "+"mNG-"+Title);
If this doesn't help, could you give me an example of your data so I can properly test it.
Thanks so much for all the help! #Petra and another person whose comment appears to have been deleted were super helpful! I have worked out the save issues. Here is the final code. It does work and generally yields good results. The resolution in some of my images isn't great so I do get a few false positives where 2 particles are very close and some unfocused light overlaps from the 2 signals, but I will continue to try to adjust things now that the macro totally works! :)
Here is the final code that I have been working with:
dir1 = getDirectory("Input");
dir2 = getDirectory("Output");
list = getFileList(dir1);
run("Close All");
setBatchMode(true);
for (i=0; i<list.length; i++) {
file1 = dir1 + list[i];
file2 = dir2 + list[i];
file3 = dir2 + list[i];
file4 = dir2 + list[i];
file3 = replace(file3, ".tif", "Summary.csv");
file4 = replace(file4, ".tif", "Results.csv");
open(file1);
Title = getTitle();
Title = replace(Title, ".tif", "");
run("Stack to Images");
selectWindow(Title+"-0001");
rename ("mNG-"+Title);
run("Subtract Background...", "rolling=50");
setAutoThreshold("RenyiEntropy dark");
//run("Threshold...");
setThreshold(400, 65535);
run("Convert to Mask");
run("Fill Holes");
run("Watershed");
run("Set Measurements...", "area mean min centroid center perimeter integrated median kurtosis area_fraction stack limit display redirect=None decimal=2");
run("Analyze Particles...", "size=9-475 circularity=0.50-1.00 show=[Bare Outlines] display exclude summarize in_situ");
run("Fill Holes");
run("Watershed");
selectWindow(Title+"-0002");
rename ("tdT-"+Title);
run("Subtract Background...", "rolling=50");
setAutoThreshold("RenyiEntropy dark");
//run("Threshold...");
run("Convert to Mask");
run("Fill Holes");
run("Watershed");
run("Set Measurements...", "area mean min centroid center perimeter integrated median kurtosis area_fraction stack limit display redirect=None decimal=2");
run("Analyze Particles...", "size=9-475 circularity=0.50-1.00 show=[Bare Outlines] display exclude summarize in_situ");
run("Fill Holes");
run("Watershed");
imageCalculator("ADD create","mNG-"+Title,"tdT-"+Title);
run("Analyze Particles...", "size=9-475 circularity=0.50-1.00 show=Outlines display exclude summarize in_situ");
run("Images to Stack", "name=[] title=[] use");
saveAs(".tif", file2);
run("Close All");
}
setBatchMode(false);
selectWindow("Summary");
saveAs("text", file3);
selectWindow("Results");
saveAs("text", file4);
run("Close All");
Thanks again for all the help!
for my Thesis I must count cells on pictures stained with Immunofluorescence and I am writing a macro in ImageJ to do it for me.
For this I coloursplit the picture, analyse particles in the red channel (my Antibody´s colour) and then I want to take the coordinates of analysed particles and only count them if at the same coordinates in the blue channel there is also a staining (DAPI - just a general cell staining).
This way I assure that there is as little dirt counted as possible.
The problem is that when I get the coordinates from the results table and use them to makePoint(x,y) the coordinates are "distorted" - usually the correct coordinates but plus 4ish, though never in exactly the same, which is why i can´t simply distract a number from the coordinates
Below I first write down the critical lines of code, then the whole code.
Thank you very much in advance
run("Analyze Particles...", " circularity=r1-r2 display clear in_situ");
roiManager("deselect");
z=nResults;
for (j=0; j<z; j++) { //loops through the Results table and adds to "counter" if a match is found
selectImage(channelsplit[2]); //selects blue window
setThreshold(d,l);
run("Threshold...");
setOption("BlackBackground", true);
run("Convert to Mask", "method=Default background=Default black");
run("Coordinates...", "left=0 right=19373 top=0 bottom=13600"); //I tried to set the boundaries of the pictures equally, but it didnt work
makePoint(getResult("X", j)), (getResult("Y", j));
print ("X" + j + ": " + getResult("X",j)); //this and the following line are not to be in the program, once it works
print ("Y" + j + ": " + getResult("Y",j));
run ("Measure");
if (getResult("Mean", nResults-1)>100) {
counter++;
}
} //for j
print (i + ": " + counter);
setBatchMode(true);
if (isOpen("ROI Manager")) {
r=roiManager("count");
} else {
setBatchMode(false);
exit("You need a ROI Manager open with the ROIs of all the pictures to be measured");
}
Dialog.create ("Variables")
Dialog.addCheckbox ("red", true);
Dialog.addCheckbox ("green", false);
Dialog.addCheckbox ("blue", false);
Dialog.addCheckbox ("watershed", true);
Dialog.addNumber ("Thresholddark:", 110);
Dialog.addNumber ("Thresholdlight:", 255);
Dialog.addNumber ("greenThresholddark:", 110);
Dialog.addNumber ("greenThresholdlight:", 253);
Dialog.addNumber ("pmin:", 15);
Dialog.addNumber ("pmax:", 100);
Dialog.addNumber ("roundness1:", 0.5);
Dialog.addNumber ("roundness2:", 1.0);
Dialog.addMessage("batch - select 'true' for your macro to run faster, but you will not see what it does until finished\nstack - selecht this box if you use unstacked pictures and want them stacked, deselect if you use a readied stack or a single picture\nred - select this box if you want to count red coloured cells \ngreen - select this box if you want to count green coloured cells \nblue - select this box if you want to count blue coloured cells\nall variables are the same for the colours you count. It may be better to adjust variables for each colour and count seperately\nwatershed - select this box only if you have overlapping cells in your image\nThresholdlight - particles lighter than this won't be measured/counted \nThresholddark - particles darker than this won't be measured/counted \nThresholdlight and Thresholddark can be between 0 and 255 \ntry thresholding manually at least once manually to get best results \npmin - particles with a size smaller than this won't be counted \npmax - particles with a size greater than this won't be counted")
Dialog.show ();
cr=Dialog.getCheckbox();
cg=Dialog.getCheckbox();
cb=Dialog.getCheckbox();
w=Dialog.getCheckbox();
d=Dialog.getNumber();
l=Dialog.getNumber();
dg=Dialog.getNumber();
lg=Dialog.getNumber();
pmin=Dialog.getNumber();
pmax=Dialog.getNumber();
r1=Dialog.getNumber();
r2=Dialog.getNumber();
dir = getDirectory("Choose a Directory "); //choose the folder with all the pictures to be measured
listFiles(dir); //I found this in a listFiles recursively Demo and changed it to open my pictures
function listFiles(dir) {
list = getFileList(dir);
for (o=0; o<list.length; o++) { //loops through the file list, opening then analysing then closing one after another
if (endsWith(list[o], "/")) {
listFiles(""+dir+list[i]);
} else {
open(dir + list[o]);
//the previous opens all pictures in a given folder in a way i do not understand
//I only use this function because I could not use the variable list.length outside of it for unknown reasons
//following is my cellcount program to be executed for each picture
//then the picture will be closed before the new one is opened
run("Split Channels");
channelsplit = getList("image.titles");
if (cr==1) {
a=0;
}
if (cg==1) {
a=1;
}
selectImage(channelsplit[a]); //selects red or green window, depending on input in the Dialog
setThreshold(d,l);
run("Threshold...");
setOption("BlackBackground", true);
run("Convert to Mask", "method=Default background=Default black");
run("Coordinates...", "left=0 right=19373 top=0 bottom=13600");
for (i=2*o; i<=((2*o)+1); i++) { //loops through two ROIs of the ROIManager
counter=0;
selectImage(channelsplit[a]);
roiManager("deselect");
roiManager("select", i);
if (w==1) {
run("Watershed", "slice");
}
run("Analyze Particles...", " circularity=r1-r2 display clear in_situ");
roiManager("deselect");
z=nResults;
for (j=0; j<z; j++) { //loops through the Results table and adds to "counter" if a match is found
selectImage(channelsplit[2]); //selects blue window
setThreshold(d,l);
run("Threshold...");
setOption("BlackBackground", true);
run("Convert to Mask", "method=Default background=Default black");
run("Coordinates...", "left=0 right=19373 top=0 bottom=13600"); //I tried to set the boundaries of the pictures equally, but it didnt work
makePoint(getResult("X", j)), (getResult("Y", j));
print ("X" + j + ": " + getResult("X",j)); //this and the following line are not to be in the program, once it works
print ("Y" + j + ": " + getResult("Y",j));
run ("Measure");
if (getResult("Mean", nResults-1)>100) {
counter++;
}
} //for j
print (i + ": " + counter);
close("Results"); //closes Results to get the variables in order for the next window
// IJ.renameResults("res" + i);
} //for i
close("*"); //closes all image windows to save RAM
} //else
} //for o
} //function
setBatchMode(false);
I am trying to write an imageJ macro that will
1) Open and split an image into two channels
2) Perform particle analysis on each image and save measurements
3) Save the original image with the particle outlines overlayed
So far I have realised I need to first duplicate the original image so that the ROI can later be saved on top of this. However I at the moment I can't figure out how to rename this duplicated image so I can later select it for flattening:
dir=getDirectory("Choose a Directory");
print(dir);
greenDir=dir + "/Green/";
blueDir=dir + "/Blue/";
print(greenDir);
print(blueDir);
File.makeDirectory(greenDir);
File.makeDirectory(blueDir);
list = getFileList(dir);
for (i=0; i<list.length; i++) {
if (endsWith(list[i], ".tif")){
print(i + ": " + dir+list[i]);
open(dir+list[i]);
imgName=getTitle();
baseNameEnd=indexOf(imgName, ".tif");
baseName=substring(imgName, 0, baseNameEnd);
run("Split Channels");
selectWindow("C1-" + imgName);
run("Duplicate...", "title= imgName + "original");
selectWindow("C1-" + imgName);
setAutoThreshold("Default dark");
//run("Threshold...");
//setThreshold(1, 255);
run("Convert to Mask");
run("Analyze Particles...", "size=60-Infinity pixel show=Outlines display exclude summarize add");
selectWindow(imgName + "original");
roiManager("Show All without labels");
run("Flatten");
saveAs("Tiff", greenDir + baseName + "green.tif");
close();
Sorry in advance if this is something very simple, this is all very new to me and learnt off googling!
I modified my code to this last night and now it is working:
dir=getDirectory("Choose a Directory");
print(dir);
greenDir=dir + "/Green/";
blueDir=dir + "/Blue/";
print(greenDir);
print(blueDir);
File.makeDirectory(greenDir);
File.makeDirectory(blueDir);
list = getFileList(dir);
for (i=0; i<list.length; i++) {
if (endsWith(list[i], ".tif")){
print(i + ": " + dir+list[i]);
open(dir+list[i]);
imgName=getTitle();
baseNameEnd=indexOf(imgName, ".tif");
baseName=substring(imgName, 0, baseNameEnd);
run("Split Channels");
roiManager("Reset");
selectWindow("C1-" + imgName);
run("Duplicate...", "title=");
saveAs("Tiff", greenDir + "originalgreen" + baseName);
selectWindow("C1-" + imgName);
setAutoThreshold("Default dark");
//run("Threshold...");
//setThreshold(1, 255);
setOption("BlackBackground", false);
run("Convert to Mask");
run("Analyze Particles...", "size=60-Infinity pixel show=Outlines display exclude summarize add");
selectWindow("originalgreen" + imgName);
roiManager("Show All with labels");
run("Flatten");
saveAs("Tiff", greenDir + baseName + "overlaygreen.tif");
close();
}
}
for (i=0; i<list.length; i++) {
if (endsWith(list[i], ".tif")){
print(i + ": " + dir+list[i]);
open(dir+list[i]);
imgName=getTitle();
baseNameEnd=indexOf(imgName, ".tif");
baseName=substring(imgName, 0, baseNameEnd);
run("Split Channels");
roiManager("Reset");
selectWindow("C2-" + imgName);
run("Duplicate...", "title=");
saveAs("Tiff", blueDir + "originalblue" + baseName);
selectWindow("C2-" + imgName);
//run("Threshold...");
//setThreshold(23, 255);
setOption("BlackBackground", false);
run("Convert to Mask");
run("Analyze Particles...", "size=60-Infinity pixel show=Outlines display exclude summarize add");
selectWindow("originalblue" + imgName);
roiManager("Show All with labels");
run("Flatten");
saveAs("Tiff", blueDir + baseName + "overlayblue.tif");
run("Close All");
}
}
Every GUI action in ImageJ can be recorded in different script languages with the macro recorder which is a great help, see:
https://imagej.nih.gov/ij/docs/guide/146-31.html#sub:Record...
http://imagej.net/Introduction_into_Macro_Programming
The macro command for rename is simply: rename("YourImageTitle");
Imagine a std:vector, say, with 100 things on it (0 to 99) currently. You are treating it as a loop. So the 105th item is index 4; forward 7 from index 98 is 5.
You want to delete N items after index position P.
So, delete 5 items after index 50; easy.
Or 5 items after index 99: as you delete 0 five times, or 4 through 0, noting that position at 99 will be erased from existence.
Worst, 5 items after index 97 - you have to deal with both modes of deletion.
What's the elegant and solid approach?
Here's a boring routine I wrote
-(void)knotRemovalHelper:(NSMutableArray*)original
after:(NSInteger)nn howManyToDelete:(NSInteger)desired
{
#define ORCO ((NSInteger)[original count])
static NSInteger kount, howManyUntilLoop, howManyExtraAferLoop;
if ( ... our array is NOT a loop ... )
// trivial, if messy...
{
for ( kount = 1; kount<=desired; ++kount )
{
if ( (nn+1) >= ORCO )
return;
[original removeObjectAtIndex:( nn+1 )];
}
return;
}
else // our array is a loop
// messy, confusing and inelegant. how to improve?
// here we go...
{
howManyUntilLoop = (ORCO-1) - nn;
if ( howManyUntilLoop > desired )
{
for ( kount = 1; kount<=desired; ++kount )
[original removeObjectAtIndex:( nn+1 )];
return;
}
howManyExtraAferLoop = desired - howManyUntilLoop;
for ( kount = 1; kount<=howManyUntilLoop; ++kount )
[original removeObjectAtIndex:( nn+1 )];
for ( kount = 1; kount<=howManyExtraAferLoop; ++kount )
[original removeObjectAtIndex:0];
return;
}
#undef ORCO
}
Update!
InVariant's second answer leads to the following excellent solution. "starting with" is much better than "starting after". So the routine now uses "start with". Invariant's second answer leads to this very simple solution...
N times do if P < currentsize remove P else remove 0
-(void)removeLoopilyFrom:(NSMutableArray*)ra
startingWithThisOne:(NSInteger)removeThisOneFirst
howManyToDelete:(NSInteger)countToDelete
{
// exception if removeThisOneFirst > ra highestIndex
// exception if countToDelete is > ra size
// so easy thanks to Invariant:
for ( do this countToDelete times )
{
if ( removeThisOneFirst < [ra count] )
[ra removeObjectAtIndex:removeThisOneFirst];
else
[ra removeObjectAtIndex:0];
}
}
Update!
Toolbox has pointed out the excellent idea of working to a new array - super KISS.
Here's an idea off the top of my head.
First, generate an array of integers representing the indices to remove. So "remove 5 from index 97" would generate [97,98,99,0,1]. This can be done with the application of a simple modulus operator.
Then, sort this array descending giving [99,98,97,1,0] and then remove the entries in that order.
Should work in all cases.
This solution seems to work, and it copies all remaining elements in the vector only once (to their final destination).
Assume kNumElements, kStartIndex, and kNumToRemove are defined as const size_t values.
vector<int> my_vec(kNumElements);
for (size_t i = 0; i < my_vec.size(); ++i) {
my_vec[i] = i;
}
for (size_t i = 0, cur = 0; i < my_vec.size(); ++i) {
// What is the "distance" from the current index to the start, taking
// into account the wrapping behavior?
size_t distance = (i + kNumElements - kStartIndex) % kNumElements;
// If it's not one of the ones to remove, then we keep it by copying it
// into its proper place.
if (distance >= kNumToRemove) {
my_vec[cur++] = my_vec[i];
}
}
my_vec.resize(kNumElements - kNumToRemove);
There's nothing wrong with two loop solutions as long as they're readable and don't do anything redundant. I don't know Objective-C syntax, but here's the pseudocode approach I'd take:
endIdx = after + howManyToDelete
if (Len <= after + howManyToDelete) //will have a second loop
firstloop = Len - after; //handle end in the first loop, beginning in second
else
firstpass = howManyToDelete; //the first loop will get them all
for (kount = 0; kount < firstpass; kount++)
remove after+1
for ( ; kount < howManyToDelete; kount++) //if firstpass < howManyToDelete, clean up leftovers
remove 0
This solution doesn't use mod, does the limit calculation outside the loop, and touches the relevant samples once each. The second for loop won't execute if all the samples were handled in the first loop.
The common way to do this in DSP is with a circular buffer. This is just a fixed length buffer with two associated counters:
//make sure BUFSIZE is a power of 2 for quick mod trick
#define BUFSIZE 1024
int CircBuf[BUFSIZE];
int InCtr, OutCtr;
void PutData(int *Buf, int count) {
int srcCtr;
int destCtr = InCtr & (BUFSIZE - 1); // if BUFSIZE is a power of 2, equivalent to and faster than destCtr = InCtr % BUFSIZE
for (srcCtr = 0; (srcCtr < count) && (destCtr < BUFSIZE); srcCtr++, destCtr++)
CircBuf[destCtr] = Buf[srcCtr];
for (destCtr = 0; srcCtr < count; srcCtr++, destCtr++)
CircBuf[destCtr] = Buf[srcCtr];
InCtr += count;
}
void GetData(int *Buf, int count) {
int srcCtr = OutCtr & (BUFSIZE - 1);
int destCtr = 0;
for (destCtr = 0; (srcCtr < BUFSIZE) && (destCtr < count); srcCtr++, destCtr++)
Buf[destCtr] = CircBuf[srcCtr];
for (srcCtr = 0; srcCtr < count; srcCtr++, destCtr++)
Buf[destCtr] = CircBuf[srcCtr];
OutCtr += count;
}
int BufferOverflow() {
return ((InCtr - OutCtr) > BUFSIZE);
}
This is pretty lightweight, but effective. And aside from the ctr = BigCtr & (SIZE-1) stuff, I'd argue it's highly readable. The only reason for the & trick is in old DSP environments, mod was an expensive operation so for something that ran often, like every time a buffer was ready for processing, you'd find ways to remove stuff like that. And if you were doing FFT's, your buffers were probably a power of 2 anyway.
These days, of course, you have 1 GHz processors and magically resizing arrays. You kids get off my lawn.
Another method:
N times do {remove entry at index P mod max(ArraySize, P)}
Example:
N=5, P=97, ArraySize=100
1: max(100, 97)=100 so remove at 97%100 = 97
2: max(99, 97)=99 so remove at 97%99 = 97 // array size is now 99
3: max(98, 97)=98 so remove at 97%98 = 97
4: max(97, 97)=97 so remove at 97%97 = 0
5: max(96, 97)=97 so remove at 97%97 = 0
I don't program iphone for know, so I image std::vector, it's quite easy, simple and elegant enough:
#include <iostream>
using std::cout;
#include <vector>
using std::vector;
#include <cassert> //no need for using, assert is macro
template<typename T>
void eraseCircularVector(vector<T> & vec, size_t position, size_t count)
{
assert(count <= vec.size());
if (count > 0)
{
position %= vec.size(); //normalize position
size_t positionEnd = (position + count) % vec.size();
if (positionEnd < position)
{
vec.erase(vec.begin() + position, vec.end());
vec.erase(vec.begin(), vec.begin() + positionEnd);
}
else
vec.erase(vec.begin() + position, vec.begin() + positionEnd);
}
}
int main()
{
vector<int> values;
for (int i = 0; i < 10; ++i)
values.push_back(i);
cout << "Values: ";
for (vector<int>::const_iterator cit = values.begin(); cit != values.end(); cit++)
cout << *cit << ' ';
cout << '\n';
eraseCircularVector(values, 5, 1); //remains 9: 0,1,2,3,4,6,7,8,9
eraseCircularVector(values, 16, 5); //remains 4: 3,4,6,7
cout << "Values: ";
for (vector<int>::const_iterator cit = values.begin(); cit != values.end(); cit++)
cout << *cit << ' ';
cout << '\n';
return 0;
}
However, you might consider:
creating new loop_vector class, if you use this kind of functionality enough
using list if you perform many deletions (or few deletions (not from end, that's simple pop_back) but large array)
If your container (NSMutableArray or whatever) is not list, but vector (i.e. resizable array), you most definitely don't want to delete items one by one, but whole range (e.g. std::vector's erase(begin, end)!
Edit: reacting to comment, to fully realize what must be done by vector, if you erase element other than the last one: it must copy all values after that element (e.g. 1000 items in array, you erase first, 999x copying (moving) of item, that is very costly).
Example:
#include <iostream>
#include <vector>
#include <ctime>
using namespace std;
int main()
{
clock_t start, end;
vector<int> vec;
const int items = 64 * 1024;
cout << "using " << items << " items in vector\n";
for (size_t i = 0; i < items; ++i) vec.push_back(i);
start = clock();
while (!vec.empty()) vec.erase(vec.begin());
end = clock();
cout << "Inefficient method took: "
<< (end - start) * 1.0 / CLOCKS_PER_SEC << " ms\n";
for (size_t i = 0; i < items; ++i) vec.push_back(i);
start = clock();
vec.erase(vec.begin(), vec.end());
end = clock();
cout << "Efficient method took: "
<< (end - start) * 1.0 / CLOCKS_PER_SEC << " ms\n";
return 0;
}
Produces output:
using 65536 items in vector
Inefficient method took: 1.705 ms
Efficient method took: 0 ms
Note it's very easy to get inefficient, look e.g. have at http://www.cplusplus.com/reference/stl/vector/erase/